【问题标题】:EditorFor() and additionalViewData: how to add data in helper class?EditorFor() 和 additionalViewData:如何在帮助类中添加数据?
【发布时间】:2011-09-11 21:36:21
【问题描述】:

EditorFor() 可以采用object additionalViewData 参数,典型的填充方法如下:

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

如何在自定义 HTML Helper 中检查 AdditionalViewData 的内容、添加或附加到现有值的键等?

我尝试了这些方法:

  • 转换为 Dictionary<string, object>() 并添加/附加值:不起作用,因为 MVC 中 EditorFor 的实现似乎使用了新的 RouteValueDictionary(additionalViewData),它将字典嵌入字典中
  • 使用 new RouteValueDictionary(additionalViewData) 转换为 RouteValueDictionary,但问题与上述相同(或非常相似)

我也对“你做错了”持开放态度——也许我错过了一种更简单的方法。请记住,我要做的是编写一个可重用的 HTML 帮助程序,并将一些值添加到 AdditionalViewData 以供自定义视图使用。一些值依赖于属性中的元数据,所以它并不像使用一堆不同的模板那么容易。

更新我正在做的例子:

    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&lt;string, object&gt;() 并将该字典传递给 EditorFor() 时发生的情况:

我在 Razor 视图中设置了一个断点并检查了 ViewData。似乎传递给EditorFor() 的字典被放在另一个Dictionary&lt;string, object&gt;() 中。在“立即窗口”中,ViewData 如下所示:

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

看看字典里面是如何包含字典的内容的?是的,实际数据在那个内部字典中,但不出所料,这不起作用。

增加了赏金。

【问题讨论】:

  • 听起来你根本没有写 HtmlHelper。您能否详细解释一下您的目标是什么?
  • 我添加了一个示例——它是一个使用 EditorFor 的 HTML 助手,以便调用者可以指定模板。类似于 TextBoxFor() 或其他 HTML 助手的源代码。
  • 我已经找到了另一种方法来做到这一点(回到 RadioButtonFor()),因为我不再需要覆盖一些 HTML 输出。所以我会说这个例子是一个例子,但不是我会使用的东西。我仍然很好奇在 HTML 帮助器中的 AdditionalViewData 中添加/删除/替换值是否是一种现实的方法。
  • 您是否能够将对象转换为Dictionary&lt;string, object&gt;?我不太清楚
  • @David 我能够毫无问题地将对象转换为Dictionary&lt;string, object&gt;,但是当该对象被传递给EditorFor()然后发射到视图时(使用Razor,输入一些代码并捕获它与调试器一起)它被错误地发出:它是一个Dictionary&lt;string, object&gt;(),这是正确的,但是该字典中有我们想要的实际字典。我的猜想是 EditorFor 中的代码无法判断它已经是 Dictionary&lt;string, object&gt;() 并再次对其进行重新词典。我在问题的底部更新了有关此案例的更多详细信息。

标签: asp.net-mvc asp.net-mvc-3 views anonymous-types


【解决方案1】:

就我而言,我有一个布尔字段编辑器,我想成为一个是/否收音机。我使用 AdditionalViewData 属性来设置是/否的文本进行本地化。似乎对我很有用!

这是我的自定义编辑器的代码:

@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})

【讨论】:

    【解决方案2】:

    如果我理解正确,您正在尝试迭代匿名类型的属性。如果是:How do I iterate over the properties of an anonymous object in 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);
    

    必须找到更好的方法。

    【讨论】:

    • 真的是在给匿名类型添加值。或者在不使用匿名类型的 ASP.NET MVC 3 中执行此操作的正确方法。
    • 我同意——必须有更好的方法。我猜它是“只是不要这样做,希望我们能实现一些有意义的东西”,尽管其他答案之一看起来也很有希望。接受您的答案,因为您显然花了一些时间研究这个问题,这是很好的信息。我会坚持我的其他方法,希望 ASP.NET MVC 团队能够清理这个松散的结局。
    • 您可以将 ExpandoObject 转换为 IDictionary 并对其进行迭代。
    【解决方案3】:

    您是否尝试过将数据添加到

    helper.ViewData[xxx] = yyy;
    

    ViewData 集合是 ViewContext 的全局集合,因此我认为只需将其添加到全局 ViewData 即可在呈现 EditorTemplate 时使其可用。

    更多信息:据我了解,additionalViewdata 属性只是在您决定要呈现哪个控件后即时将集合/任何内容添加到全局 ViewData 的一种简单方法。仍然使用相同的上下文集合,与其说是一个不同的对象,不如说是添加到同一个上下文字典的一种较晚且干净的方式。

    我还没有尝试过,所以如果我错过了重点,请说出来,我会删除答案。

    【讨论】:

      【解决方案4】:

      RouteValueDictionary 使用 TypeDescriptor 基础结构 (TypeDescriptor.GetProperties(obj)) 来反映附加视图数据对象。由于此基础结构是可扩展的,您可以创建一个实现 ICustomTypeDescriptor 并提供您选择的假属性的类,例如基于内部字典。 在以下文章中,您将找到一个实现:http://social.msdn.microsoft.com/Forums/en/wpf/thread/027cb000-996e-46eb-9ebf-9c41b07d5daa 有了这个实现,您可以轻松地添加具有任意名称和值的“属性”,并将其作为 addtionalViewData 对象传递。要从初始对象获取现有属性,您可以使用与 MVC 稍后将执行的方法相同的方法,调用 TypeDescriptor.GetProperties,枚举属性并获取属性的名称和值,并将它们添加到您的新“对象”中好吧。

      【讨论】:

        【解决方案5】:

        有点晚了,但有一个使用 CodeDom 的有效解决方案here

        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));
        

        感谢原作者安东尼·约翰斯顿!

        【讨论】:

          猜你喜欢
          • 2011-09-14
          • 1970-01-01
          • 1970-01-01
          • 2015-02-05
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多