asp.net-mvc EditorFor() 和 additionalViewData:如何在辅助类中添加数据?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/6296583/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 01:14:48  来源:igfitidea点击:

EditorFor() and additionalViewData: how to add data in helper class?

asp.net-mvcasp.net-mvc-3viewsanonymous-types

提问by Cymen

EditorFor() can take an object additionalViewDataparameter which the typical method to populate is something like:

EditorFor() 可以采用一个object additionalViewData参数,典型的填充方法是这样的:

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

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

How can I inspect the contents of additionalViewData, add or append to existing value for a key, etc in a custom HTML Helper?

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

I've tried these approaches:

我试过这些方法:

  • convert to Dictionary<string, object>()and add/append values: doesn't work as it looks like the implementation of EditorFor in MVC uses new RouteValueDictionary(additionalViewData) which embeds the dictionary within a dictionary
  • convert to RouteValueDictionary using new RouteValueDictionary(additionalViewData)but that has same (or very similar) issue as above
  • 转换为Dictionary<string, object>()和添加/附加值:不起作用,因为它看起来像 MVC 中的 EditorFor 实现使用新的 RouteValueDictionary(additionalViewData),它将字典嵌入到字典中
  • 使用new RouteValueDictionary(additionalViewData)但具有与上述相同(或非常相似)的问题转换为 RouteValueDictionary

I'm also open to "you're doing it wrong" -- maybe I'm missing a simpler approach. Keep in mind what I'm trying to do is write an HTML helper that is reusable and adds some values to the additionalViewData to be used by custom views. Some of the values depend on metadata from the property so it is not quite so easy as just use a bunch of different templates.

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

Update with example of what I'm doing:

更新我正在做的事情的例子:

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

Update with what happens when I convert the anonymous object to a Dictionary<string, object>()and pass that dictionary to EditorFor():

更新当我将匿名对象转换为 aDictionary<string, object>()并将该字典传递给时会发生什么EditorFor()

I put a break point in the Razor view and examined ViewData. It appears that the dictionary passed into EditorFor()is put inside another Dictionary<string, object>(). In the "Immediate Window", ViewData looks like this:

我在 Razor 视图中放置了一个断点并检查了 ViewData。传入的字典似乎EditorFor()放在另一个Dictionary<string, object>(). 在“立即窗口”中,ViewData 如下所示:

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

See how the dictionary has the contents of a dictionary within it? Yes, the actual data is in that inner dictionary however unsurprisingly, this doesn't work.

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

Added bounty.

添加了赏金。

采纳答案by Shashi Penumarthy

If I understand correctly, you are trying to iterate over properties of an anonymous type. If so: How do I iterate over the properties of an anonymous object in C#?

如果我理解正确,您正在尝试迭代匿名类型的属性。如果是这样:如何在 C# 中迭代​​匿名对象的属性?

[Edit] Ok, it's more than that. This is really bothering me now because I love C# and it won't let me do what Python does, which I also love. So here's a solution, if you are using C# 4 but it's messy and will work for a similar problem I have but maybe not exactly for you:

[编辑] 好的,不仅如此。现在这真的让我很困扰,因为我喜欢 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);

There has gotto be a better way.

目前已经得到了成为一个更好的办法。

回答by jhilden

in my case I have an editor for a boolean field that I want to be a yes/no radio. I use the additionalViewData property to set the text of yes/no to be localized. Seems to work great for me!

在我的情况下,我有一个布尔字段的编辑器,我想成为一个是/否收音机。我使用 additionalViewData 属性来设置要本地化的是/否文本。似乎对我很有用!

Here is the code for my custom editorFor:

这是我的自定义 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)   

Here is how you call this custom EditorFor:

以下是您如何调用此自定义 EditorFor:

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

回答by Gats

Have you tried just adding the data to

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

helper.ViewData[xxx] = yyy;

The ViewData collection is a global collection for the ViewContext so I think just adding it to the global ViewData will make it available when the EditorTemplate is rendered out.

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

MORE INFO: As I understand it, the additionalViewdata property is just an easy way to add a collection/anything to the global ViewData on the fly after you've decided which control to render. Still uses the same contextual collection and is not so much a different object as a late and clean way to add to the same context dictionary.

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

I haven't tried this yet, so if I'm missing the point, say so and I'll just remove the answer.

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

回答by Tz_

The RouteValueDictionary uses the TypeDescriptor infrastructure (TypeDescriptor.GetProperties(obj)) to reflect the additionalViewData object. As this infrastructure is extensible, you can create a class that implements the ICustomTypeDescriptor and provides fake properties of your choice, e.g. based on an internal dictionary. In the following article you will find an implementation: http://social.msdn.microsoft.com/Forums/en/wpf/thread/027cb000-996e-46eb-9ebf-9c41b07d5daaHaving this implementation you can easily add "properties" with arbitrary name and value and pass it as the addtionalViewData object. To get the existing properties from the initial object you can use the same method as MVC will do later, call the TypeDescriptor.GetProperties, enumerate the properties and get the name and value of the property, and add them to your new "object" as well.

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,枚举属性并获取属性的名称和值,并将它们添加到您的新“对象”中作为好。

回答by Cocowalla

A bit late, but there is a working solution using CodeDom available here.

有点晚了,但有使用的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;
    }
}

Usage:

用法:

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

Thanks to the original author, Anthony Johnston!

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