I'm writing an MVC2 app using DataAnnotations. I have a following Model:
public class FooModel
{
[ScaffoldColumn("false")]
public long FooId { get; set; }
[UIHint("BarTemplate")]
public DateTime? Bar { get; set;}
}
I want to create a custom display template for Bar. I have created following template:
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<DateTime?>" %>
<div class="display-label">
<span><%: Html.LabelForModel() %></span>
</div>
<div class="display-field">
<span><%: Html.DisplayForModel()%></span>
<%: Html.ActionLink("Some link", "Action", new { id = ??FooId?? }) %>
</div>
Now, my problem is that inside template for Bar I want to access another property from my model. I don't want to create a separate template for FooModel because than I will have to hardcode all other FooModel properties.
After a brief investigation with a debugger I can see that:
this.ViewData.ModelMetadata.ContainerType
is FooModel (as expected)
this.ViewData.TemplateInfo has a
non-public property VisitedObjects
(of type System.Collections.Generic.HashSet<object>)
which contains two elements: FooModel and DateTime?.
How can I get access to my FooModel? I don't want to hack my way around using Reflection.
Update:
I've accepted mootinator's answer as it looks to me as the best solution that allows type-safety. I've also upvoted Tx3's answer, as mootinator's answer builds upon it. Nevertheless, I think that there should be a better support form MVC in those kind of scenarios, which I believe are quite common in real world but missing from sample apps.
Maybe you could create new class, let's say UserDateTime and it would contain nullable DateTime and rest of the information you need. Then you would use custom display template for UserDateTime and get access to information you require.
I realize that you might be looking for other kind of solution.
I think you may be better off extracting this functionality to an HtmlHelper call from the Parent View.
Something like RenderSpecialDateTime<TModel>(this HtmlHelper html, Expression<Func<TModel,DateTime?>> getPropertyExpression) would probably do the job.
Otherwise, you will have to do something like what Tx3 suggested. I upvoted his answer, but posted this as an alternative.
Couldn't you use the ViewData dictionary object in the controller and then grab that in the ViewUserControl? It wouldn't be strongly typed but...you could write a helper to do nothing if it's empty, and link to say the example login history page if it had a value.
public override ModelMetadata GetMetadataForProperty(Func<object> modelAccessor, Type containerType, string propertyName)
{
var propMetaData = base.GetMetadataForProperty(modelAccessor, containerType, propertyName);
Object container = modelAccessor.Target.GetType().GetField("container").GetValue(modelAccessor.Target);
propMetaData.Container = container;
return propMetaData;
}
我知道这是反射,但它相当简洁。看来微软正在纠正这个过度站点,所以也许将来有可能替换反射代码。
It would appear that somewhere between MVC 5.0 and 5.2.2 a "Container" property was added on to the ModelMetadata class.
However, because all of the methods in a provider responsible for metadata creation (GetMetadataForProperty, Create etc) do not have container in their signature, the Container property is assigned only in certain cases (GetMetadataForProperties and GetMetadataFromProvider according to reflected code) and in my case was usually null.
So what I ended up doing is overriding the GetMetadataForProperty in a new metadata provider and setting it there:
public override ModelMetadata GetMetadataForProperty(Func<object> modelAccessor, Type containerType, string propertyName)
{
var propMetaData = base.GetMetadataForProperty(modelAccessor, containerType, propertyName);
Object container = modelAccessor.Target.GetType().GetField("container").GetValue(modelAccessor.Target);
propMetaData.Container = container;
return propMetaData;
}
I know this is reflection but it's fairly succinct. It would appear that MS is correcting this oversite so maybe it will be possible to replace the reflection code in the future.
public class FooModel
{
[ScaffoldColumn("false")]
public long FooId { get; set; }
[UIHint("BarTemplate")]
public ParentedDateTime<FooModel> Bar { get; set;}
public FooModel()
{
Bar = new ParentedDateTime<FooModel>(this);
}
}
public class ParentedDateTime<T>
{
public T Parent {get; set;}
public DateTime? Babar {get; set; }
public ParentedDateTime(T parent)
{
Parent = parent;
}
}
Sorry if this suggestion seems daft, I haven't tried it, but couldn't you do what Tx3 suggested without having to create a bunch of new classes by defining a generic class to reference whatever type of parent you want?
public class FooModel
{
[ScaffoldColumn("false")]
public long FooId { get; set; }
[UIHint("BarTemplate")]
public ParentedDateTime<FooModel> Bar { get; set;}
public FooModel()
{
Bar = new ParentedDateTime<FooModel>(this);
}
}
public class ParentedDateTime<T>
{
public T Parent {get; set;}
public DateTime? Babar {get; set; }
public ParentedDateTime(T parent)
{
Parent = parent;
}
}
You could expand that to encapsulate any old type with a <Parent, Child> typed generic, even.
That would also give you the benefit that your strongly typed template would be for
Inherits="System.Web.Mvc.ViewUserControl<ParentedDateTime<FooType>> thus you would not have to explicity name which template to use anywhere. This is more how things are intended to work.
发布评论
评论(5)
也许您可以创建新类,假设 UserDateTime ,它将包含可为空的 DateTime 和您需要的其余信息。然后,您可以使用 UserDateTime 的自定义显示模板并访问您需要的信息。
我意识到您可能正在寻找其他类型的解决方案。
Maybe you could create new class, let's say UserDateTime and it would contain nullable DateTime and rest of the information you need. Then you would use custom display template for UserDateTime and get access to information you require.
I realize that you might be looking for other kind of solution.
我认为您最好从父视图中将此功能提取到 HtmlHelper 调用中。
像 RenderSpecialDateTime(this HtmlHelper html, Expression> getPropertyExpression) 之类的东西可能会完成这项工作。
否则,您将不得不执行 Tx3 建议的操作。我对他的答案投了赞成票,但将其作为替代方案发布。
I think you may be better off extracting this functionality to an HtmlHelper call from the Parent View.
Something like
RenderSpecialDateTime<TModel>(this HtmlHelper html, Expression<Func<TModel,DateTime?>> getPropertyExpression)
would probably do the job.Otherwise, you will have to do something like what Tx3 suggested. I upvoted his answer, but posted this as an alternative.
您不能在控制器中使用 ViewData 字典对象,然后在 ViewUserControl 中获取它吗?它不会是强类型的,但是......您可以编写一个帮助程序,如果它为空,则不执行任何操作,并链接到示例登录历史记录页面(如果它有值)。
Couldn't you use the ViewData dictionary object in the controller and then grab that in the ViewUserControl? It wouldn't be strongly typed but...you could write a helper to do nothing if it's empty, and link to say the example login history page if it had a value.
看起来在 MVC 5.0 和 5.2.2 之间的某个地方,“Container”属性被添加到 ModelMetadata 类中。
但是,由于提供程序中负责元数据创建的所有方法(GetMetadataForProperty、Create 等)的签名中都没有容器,因此仅在某些情况下分配 Container 属性(根据反射代码,为 GetMetadataForProperties 和 GetMetadataFromProvider),在我的情况下通常为空。
所以我最终做的是覆盖新元数据提供程序中的 GetMetadataForProperty 并将其设置在那里:
我知道这是反射,但它相当简洁。看来微软正在纠正这个过度站点,所以也许将来有可能替换反射代码。
It would appear that somewhere between MVC 5.0 and 5.2.2 a "Container" property was added on to the ModelMetadata class.
However, because all of the methods in a provider responsible for metadata creation (GetMetadataForProperty, Create etc) do not have container in their signature, the Container property is assigned only in certain cases (GetMetadataForProperties and GetMetadataFromProvider according to reflected code) and in my case was usually null.
So what I ended up doing is overriding the GetMetadataForProperty in a new metadata provider and setting it there:
I know this is reflection but it's fairly succinct. It would appear that MS is correcting this oversite so maybe it will be possible to replace the reflection code in the future.
抱歉,如果这个建议看起来很愚蠢,我还没有尝试过,但是你不能按照 Tx3 的建议去做,而不必通过定义一个泛型类来引用你想要的任何类型的父类来创建一堆新类吗?
您甚至可以扩展它以使用
类型的泛型封装任何旧类型。这也将为您带来好处,您的强类型模板将适用于
Inherits="System.Web.Mvc.ViewUserControl>
因此您不必明确指定要使用哪个模板任何地方都可以使用。Sorry if this suggestion seems daft, I haven't tried it, but couldn't you do what Tx3 suggested without having to create a bunch of new classes by defining a generic class to reference whatever type of parent you want?
You could expand that to encapsulate any old type with a
<Parent, Child>
typed generic, even.That would also give you the benefit that your strongly typed template would be for
Inherits="System.Web.Mvc.ViewUserControl<ParentedDateTime<FooType>>
thus you would not have to explicity name which template to use anywhere. This is more how things are intended to work.