Подтвердить что ты не робот

EditorFor() и дополнительныеViewData: как добавить данные в вспомогательный класс?

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

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

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

Я пробовал эти подходы:

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

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

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

    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"

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

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

4b9b3361

Ответ 1

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

[Изменить] Хорошо, это больше, чем это. Это действительно беспокоит меня сейчас, потому что я люблю С#, и это не позволит мне делать то, что делает Python, и я тоже люблю. Итак, вот решение, если вы используете С# 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 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

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

Вот код для моего настраиваемого редактора:

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

Ответ 3

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

helper.ViewData[xxx] = yyy;

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

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

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

Ответ 4

В 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, перечислите свойства и получите имя и значение свойства и добавьте их в свой новый "объект" как Что ж.

Ответ 5

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

Благодаря оригинальному автору, Энтони Джонстону!