EditorFor () и AdditionalViewData: как добавить данные в вспомогательный класс? - PullRequest
17 голосов
/ 09 июня 2011

EditorFor () может принимать параметр object additionalViewData, типичный метод для заполнения которого выглядит примерно так:

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

Как я могу проверить содержимое AdditionalViewData, добавить или добавитьк существующему значению для ключа и т. д. в пользовательском помощнике HTML?

Я пробовал следующие подходы:

  • преобразовать в Dictionary<string, object>() и добавлять / добавлять значения: неработать так, как будто реализация EditorFor в MVC использует новый RouteValueDictionary (AdditionalViewData), который встраивает словарь в словарь
  • , преобразует в RouteValueDictionary с использованием new RouteValueDictionary(additionalViewData), но имеет ту же (или очень похожую) проблему, что и выше

Я также открыт для "вы делаете это неправильно" - возможно, я пропускаю более простой подход.Помните, что я пытаюсь написать вспомогательный HTML-код, который можно использовать повторно и который добавляет некоторые значения к дополнительному элементу ViewViewData для использования в пользовательских представлениях.Некоторые значения зависят от метаданных свойства, поэтому это не так просто, как просто использовать кучу разных шаблонов.

Обновление с примером того, что я делаю:

    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"

Видите, как в словаре содержится содержимое словаря?Да, фактические данные находятся в этом внутреннем словаре, однако неудивительно, что это не работает.

Добавлена ​​награда.

Ответы [ 5 ]

10 голосов
/ 22 февраля 2012

в моем случае у меня есть редактор для логического поля, которое я хочу использовать для радио да / нет.Я использую свойство AdditionalViewData, чтобы установить текст yes / no для локализации.Кажется, отлично работает для меня!

Вот код для моего пользовательского редактора. Для:

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

Вот как вы называете этот пользовательский редактор для:

@Html.EditorFor(model => model.IsActive, new {TrueText = @Resources.WebSiteLocalizations.Active, FalseText = @Resources.WebSiteLocalizations.InActive})
5 голосов
/ 23 июня 2011

Вы пытались просто добавить данные в

helper.ViewData[xxx] = yyy;

Коллекция ViewData является глобальной коллекцией для ViewContext, поэтому я думаю, что простое добавление ее в глобальные ViewData сделает ее доступной, когда будет отображен шаблон EditorTemplate..

ДОПОЛНИТЕЛЬНАЯ ИНФОРМАЦИЯ: Насколько я понимаю, свойство AdditionalViewdata - это простой способ добавить коллекцию / что-нибудь в глобальные ViewData на лету после того, как вы решили, какой элемент управления отображать.Все еще использует ту же контекстную коллекцию и не столько другой объект, сколько поздний и чистый способ добавления в один и тот же контекстный словарь.

Я еще не пробовал, поэтому, если я упускаю сутьСкажи так, и я просто удалю ответ.

5 голосов
/ 17 июня 2011

Если я правильно понимаю, вы пытаетесь перебрать свойства анонимного типа.Если это так: Как перебрать свойства анонимного объекта в C #?

[Edit] Хорошо, это нечто большее.Это действительно беспокоит меня сейчас, потому что я люблю 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);

Там есть получил , чтобы бытьлучший способ.

2 голосов
/ 07 ноября 2014

Немного поздно, но есть рабочее решение с использованием 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));

Спасибо оригинальному автору, Энтони Джонстону!

2 голосов
/ 22 июня 2011

RouteValueDictionary использует инфраструктуру TypeDescriptor (TypeDescriptor.GetProperties(obj)), чтобы отразить дополнительный объект ViewData. Поскольку эта инфраструктура является расширяемой, вы можете создать класс, который реализует ICustomTypeDescriptor и предоставляет поддельные свойства по вашему выбору, например, на основе внутреннего словаря. В следующей статье вы найдете реализацию: http://social.msdn.microsoft.com/Forums/en/wpf/thread/027cb000-996e-46eb-9ebf-9c41b07d5daa Имея эту реализацию, вы можете легко добавить «свойства» с произвольным именем и значением и передать его как объект addtionalViewData. Чтобы получить существующие свойства из исходного объекта, вы можете использовать тот же метод, что и MVC позже, вызовите TypeDescriptor.GetProperties, перечислите свойства и получите имя и значение свойства, а затем добавьте их в свой новый «объект» как Что ж.

...