web-dev-qa-db-ja.com

EditorFor()およびadditionalViewData:ヘルパークラスにデータを追加する方法は?

EditorFor()は、_object additionalViewData_パラメーターを受け取ることができます。これは、入力する一般的なメソッドが次のようなものです。

EditorFor(model => model.PropertyName, new { myKey = "myValue" })

カスタムHTMLヘルパーで、additionalViewDataの内容を検査したり、キーの既存の値に追加または追加したりするにはどうすればよいですか?

私はこれらのアプローチを試しました:

  • Dictionary<string, object>()に変換し、値を追加/追加します。MVCでのEditorForの実装は、辞書を辞書に埋め込む新しいRouteValueDictionary(additionalViewData)を使用しているように見えるため、機能しません。
  • new RouteValueDictionary(additionalViewData)を使用してRouteValueDictionaryに変換しますが、上記と同じ(または非常に類似した)問題があります

私は「あなたが間違っている」ことにもオープンです-多分私はもっと簡単なアプローチを逃しています。私がやろうとしているのは、再利用可能で、カスタムビューで使用されるadditionalViewDataにいくつかの値を追加するHTMLヘルパーを作成することです。一部の値はプロパティのメタデータに依存しているため、さまざまなテンプレートを使用するほど簡単ではありません。

私がしていることの例で更新してください:

_    public static MvcHtmlString myNullableBooleanFor<TModel, TValue>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TValue>> choice, string templateName, object additionalViewData)
    {            
        ModelMetadata metadata = ModelMetadata.FromLambdaExpression(choice, htmlHelper.ViewData);

        /*
    here need to add to additionalViewData some key values among them:
    { propertyName, metadata.PropertyName }

     */

        StringBuilder sb = new StringBuilder();
        sb.AppendLine(htmlHelper.EditorFor(choice, templateName, additionalViewData).ToString());
        MvcHtmlString validation = htmlHelper.ValidationMessageFor(choice);
        if (validation != null)
            sb.AppendLine(validation.ToString());
        return new MvcHtmlString(sb.ToString());
    }
_

匿名オブジェクトをDictionary<string, object>()に変換し、その辞書をEditorFor()に渡すとどうなるかを更新します。

Razorビューにブレークポイントを設定し、ViewDataを調べました。 EditorFor()に渡された辞書は別のDictionary<string, object>()の中に置かれているようです。 「イミディエイトウィンドウ」では、ViewDataは次のようになります。

_ViewData.Keys
Count = 4
    [0]: "Comparer"
    [1]: "Count"
    [2]: "Keys"
    [3]: "Values"
_

辞書に辞書の内容がどのように含まれているかを確認してください。はい、実際のデータはその内部辞書にありますが、当然のことながら、これは機能しません。

報奨金を追加しました。

17
Cymen

私が正しく理解していれば、あなたは匿名型のプロパティを反復しようとしています。もしそうなら: C#で匿名オブジェクトのプロパティを反復処理するにはどうすればよいですか?

[編集]わかりました、それだけではありません。私はC#が大好きで、Pythonが行うことはできませんが、これも大好きです。C#4を使用しているが、面倒な場合の解決策は次のとおりです。そして、私が持っている同様の問題に対しては機能しますが、おそらくあなたにとっては正確ではありません:

    // Assume you've created a class to hold commonly used additional view data
    // called MyAdditionalViewData. I would create an inheritance hierarchy to
    // contain situation-specific (area-specific, in my case) additionalViewData.
class MyAdditionalViewData
{
    public string Name { get; set; }

    public string Sound { get; set; }
}     

/* In your views */
// Pass an instance of this class in the call to your HTML Helper
EditorFor(model => model.PropertyName, 
    new { adv = new MyAdditionalViewData { Name = "Cow", Sound = "Moo" } }) ;               

    /* In your HTML helper */
    // Here's the C# 4 part that makes this work.
dynamic bovine = new ExpandoObject();

// Copy values
var adv = x.adv;
if (adv.Name != null) bovine.Name = adv.Name;
if (adv.Sound != null) bovine.Sound = adv.Sound;

// Additional stuff
bovine.Food = "Grass";
bovine.Contents = "Burgers";

    // When the MVC framework converts this to a route value dictionary
    // you'll get a proper object, not a dictionary within a dictionary
var dict = new RouteValueDictionary(bovine);

より良い方法としてgotがあります。

5

私の場合、yes/noラジオになりたいブールフィールドのエディターがあります。追加のViewDataプロパティを使用して、yes/noのテキストをローカライズするように設定します。私にとってはうまくいくようです!

これが私のカスタムeditorForのコードです:

@model bool?       
@{
    var yes = ViewBag.TrueText ?? @Resources.WebSiteLocalizations.Yes;
    var no = ViewBag.FalseText ?? @Resources.WebSiteLocalizations.No;
}
<div class="title">
    @Html.LabelFor(model => model)
    @Html.RequiredFor(model => model)
</div>
<div class="editor-field">
    @Html.RadioButton("", "True", (Model.HasValue && Model.Value))@yes
    <br />
    @Html.RadioButton("", "False", (Model.HasValue && Model.Value == false))@no
    <br />
    @Html.ValidationMessageFor(model => model)
</div>
@Html.DescriptionFor(model => model)   

このカスタムEditorForの呼び出し方法は次のとおりです。

@Html.EditorFor(model => model.IsActive, new {TrueText = @Resources.WebSiteLocalizations.Active, FalseText = @Resources.WebSiteLocalizations.InActive})
11
jhilden

データをに追加してみましたか

helper.ViewData[xxx] = yyy;

ViewDataコレクションはViewContextのグローバルコレクションであるため、グローバルViewDataに追加するだけで、EditorTemplateがレンダリングされたときに使用できるようになると思います。

詳細:私が理解しているように、additionalViewdataプロパティは、レンダリングするコントロールを決定した後、コレクション/何かをグローバルViewDataにその場で追加する簡単な方法です。それでも同じコンテキストコレクションを使用し、同じコンテキストディクショナリに追加するための遅くてクリーンな方法ほど異なるオブジェクトではありません。

私はまだこれを試していませんので、要点がわからない場合は、そう言ってください。答えを削除します。

5
Gats

少し遅れていますが、CodeDomを使用した実用的なソリューションが利用可能です ここ

public interface IObjectExtender
{
    object Extend(object obj1, object obj2);
}

public class ObjectExtender : IObjectExtender
{
    private readonly IDictionary<Tuple<Type, Type>, Assembly>
        _cache = new Dictionary<Tuple<Type, Type>, Assembly>();

    public object Extend(object obj1, object obj2)
    {
        if (obj1 == null) return obj2;
        if (obj2 == null) return obj1;

        var obj1Type = obj1.GetType();
        var obj2Type = obj2.GetType();

        var values = obj1Type.GetProperties()
            .ToDictionary(
                property => property.Name,
                property => property.GetValue(obj1, null));

        foreach (var property in obj2Type.GetProperties()
            .Where(pi => !values.ContainsKey(pi.Name)))
            values.Add(property.Name, property.GetValue(obj2, null));

        // check for cached
        var key = Tuple.Create(obj1Type, obj2Type);
        if (!_cache.ContainsKey(key))
        {
            // create Assembly containing merged type
            var codeProvider = new CSharpCodeProvider();
            var code = new StringBuilder();

            code.Append("public class mergedType{ \n");
            foreach (var propertyName in values.Keys)
            {
                // use object for property type, avoids Assembly references
                code.Append(
                    string.Format(
                        "public object @{0}{{ get; set;}}\n",
                        propertyName));
            }
            code.Append("}");

            var compilerResults = codeProvider.CompileAssemblyFromSource(
                new CompilerParameters
                    {
                        CompilerOptions = "/optimize /t:library",
                        GenerateInMemory = true
                    },
                code.ToString());

            _cache.Add(key, compilerResults.CompiledAssembly);
        }

        var merged = _cache[key].CreateInstance("mergedType");
        Debug.Assert(merged != null, "merged != null");

        // copy data
        foreach (var propertyInfo in merged.GetType().GetProperties())
        {
            propertyInfo.SetValue(
                merged,
                values[propertyInfo.Name],
                null);
        }

        return merged;
    }
}

使用法:

var merged = Extender.Extend(new { @class }, additionalViewData));

原作者のAnthonyJohnstonに感謝します!

2
Cocowalla

RouteValueDictionaryは、TypeDescriptorインフラストラクチャ(TypeDescriptor.GetProperties(obj))を使用してadditionalViewDataオブジェクトを反映します。このインフラストラクチャは拡張可能であるため、ICustomTypeDescriptorを実装し、選択した偽のプロパティを提供するクラスを作成できます。内部辞書に基づいています。次の記事に実装があります: http://social.msdn.Microsoft.com/Forums/en/wpf/thread/027cb000-996e-46eb-9ebf-9c41b07d5daa この実装を使用すると任意の名前と値で「プロパティ」を簡単に追加し、それをaddtionalViewDataオブジェクトとして渡すことができます。最初のオブジェクトから既存のプロパティを取得するには、MVCが後で行うのと同じメソッドを使用して、TypeDescriptor.GetPropertiesを呼び出し、プロパティを列挙してプロパティの名前と値を取得し、新しい「オブジェクト」に次のように追加します。上手。

2
Tz_