EditorFor()可以采用object additionalViewData
参数,填充的典型方法如下:
EditorFor(model => model.PropertyName, new { myKey = "myValue" })
如何在自定义HTML帮助器中检查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<string, object>()
并将该字典传递给EditorFor()
时会发生什么:
我在Razor视图中放置了一个断点并检查了ViewData。传入EditorFor()
的字典似乎放在另一个Dictionary<string, object>()
内。在“立即窗口”中,ViewData如下所示:
ViewData.Keys
Count = 4
[0]: "Comparer"
[1]: "Count"
[2]: "Keys"
[3]: "Values"
查看字典中的字典内容如何?是的,实际数据在内部字典中,但不出所料,这不起作用。
添加了赏金。
答案 0 :(得分:10)
在我的情况下,我有一个布尔字段的编辑器,我想成为是/否收音机。我使用additionalViewData属性来设置要本地化的yes / no文本。似乎对我有用!
以下是我的自定义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)
以下是调用此自定义EditorFor的方法:
@Html.EditorFor(model => model.IsActive, new {TrueText = @Resources.WebSiteLocalizations.Active, FalseText = @Resources.WebSiteLocalizations.InActive})
答案 1 :(得分:5)
如果我理解正确,您正在尝试迭代匿名类型的属性。如果是这样的话: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);
是更好的方式。
答案 2 :(得分:5)
您是否尝试过将数据添加到
helper.ViewData[xxx] = yyy;
ViewData集合是ViewContext的全局集合,因此我认为只需将其添加到全局ViewData,即可在渲染EditorTemplate时使其可用。
更多信息:据我所知,在您决定要渲染哪个控件之后,additionalViewdata属性只是一种简单的方法,可以动态地将集合/任何内容添加到全局ViewData中。仍然使用相同的上下文集合,并不是一个不同的对象,而是一种添加到同一上下文字典的后期和干净的方式。
我还没有尝试过,所以如果我错过了这一点,请说出来,我会删除答案。
答案 3 :(得分:2)
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,枚举属性并获取属性的名称和值,并将它们添加到新的“对象”中好。
答案 4 :(得分:2)
有点晚了,但有一个使用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));
感谢原作者Anthony Johnston!