MVC2 - 如何获取模板内的父模型(容器)

发布于 2024-09-29 13:59:55 字数 1362 浏览 2 评论 0 原文

我正在使用 DataAnnotations 编写 MVC2 应用程序。我有一个以下模型:

public class FooModel 
{
    [ScaffoldColumn("false")]
    public long FooId { get; set; }

    [UIHint("BarTemplate")]
    public DateTime? Bar { get; set;}
}

我想为 Bar 创建一个自定义显示模板。我创建了以下模板:

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

现在,我的问题是 Bar 的内部模板我想从我的模型访问另一个属性。我不想为 FooModel 创建单独的模板,因为我必须对所有其他 FooModel 属性进行硬编码。

使用调试器进行简短调查后,我可以看到:

  1. this.ViewData.ModelMetadata.ContainerType 是 FooModel (如预期)
  2. this.ViewData.TemplateInfo 有一个 非公共属性VisitedObjects (类型 System.Collections.Generic.HashSet) 其中包含两个元素: FooModelDateTime?

我怎样才能访问我的 FooModel?我不想使用反射来解决问题。

更新:

我已经接受了mootinator的答案,因为它在我看来是允许类型安全的最佳解决方案。我也赞成 Tx3 的答案,因为 mootinator 的答案是建立在它的基础上的。尽管如此,我认为在这些场景中,MVC 应该有更好的支持,我相信这在现实世界中很常见,但在示例应用程序中却缺少。

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:

  1. this.ViewData.ModelMetadata.ContainerType
    is FooModel (as expected)
  2. 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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

享受孤独 2024-10-06 13:59:55

也许您可以创建新类,假设 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.

铁憨憨 2024-10-06 13:59:55

我认为您最好从父视图中将此功能提取到 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.

初见 2024-10-06 13:59:55

您不能在控制器中使用 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.

不弃不离 2024-10-06 13:59:55

看起来在 MVC 5.0 和 5.2.2 之间的某个地方,“Container”属性被添加到 ModelMetadata 类中。

但是,由于提供程序中负责元数据创建的所有方法(GetMetadataForProperty、Create 等)的签名中都没有容器,因此仅在某些情况下分配 Container 属性(根据反射代码,为 GetMetadataForProperties 和 GetMetadataFromProvider),在我的情况下通常为空。

所以我最终做的是覆盖新元数据提供程序中的 GetMetadataForProperty 并将其设置在那里:

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.

吻风 2024-10-06 13:59:55

抱歉,如果这个建议看起来很愚蠢,我还没有尝试过,但是你不能按照 Tx3 的建议去做,而不必通过定义一个泛型类来引用你想要的任何类型的父类来创建一堆新类吗?

    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;
        }

}

您甚至可以扩展它以使用 类型的泛型封装任何旧类型。

这也将为您带来好处,您的强类型模板将适用于

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?

    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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文