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"
Посмотрите, как словарь содержит содержимое словаря внутри него? Да, фактические данные в этом внутреннем словаре, однако, неудивительно, это не работает.
Добавлена награда.
Ответы
Ответ 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));
Благодаря оригинальному автору, Энтони Джонстону!