views:

31

answers:

1

I'm trying to call DisplayFor and DisplayForModel to iterate an IEnumerable<> with various element types from within a view. I have Templates defined for each element/Model type.

What I would like to do is check the ViewData.ModelMetadata.ContainerType from within the Template so that Template can determine if it was called as part of a collection.

A simple example:

Index1.aspx: To render a collection of Foos.

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<IEnumerable<Foo>>" %>
<asp:Content ContentPlaceHolderID="MainPlaceHolder" runat="server">
    <ul><%:Html.DisplayForModel()%></ul>
</asp:Content>

Index2.aspx: To render a Foo from Bar.

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<Bar>" %>
<asp:Content ContentPlaceHolderID="MainPlaceHolder" runat="server">
    <%:Html.DisplayFor(m => m.Foo)%>
</asp:Content>

Shared\DisplayTemplates\Foo.ascx: A context aware Template for Foo.

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Foo>" %>
<%  var tag = typeof(IEnumerable).IsAssignableFrom(ViewData.ModelMetaData.ContainerType) ? "li" : "div";
%>    <<%:tag%>><%:Model.Name%></<%:tag%>>

The problem with this example is that ViewData.ModelMetaData.ContainerType is null in the Template when resolved though Index1.aspx. From what I've read on Brad Wilson's post and others it has to do with the use of IEnumerable and its being an interface.

Is there a way to insure that the ContainerType is set? Perhaps by creating a ModelMetadataProvider? I looked into that breifly but it appears the ContainerType value is determined before and then passed to the Provider.

Any suggestions would be appreciated.

A: 

Here is a functional solution I've worked out for now but it's limited and problematic because it requires converting collections to lists in the Model and the Templates are not context aware.

Index1.aspx: To render a collection of Foos.

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<IList<Foo>>" %>
<asp:Content ContentPlaceHolderID="MainPlaceHolder" runat="server">
    <%:Html.DisplayForModel("List")%>
</asp:Content>

Shared\DisplayTemplates\List.ascx: Special collection template.

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IList>" %>
<ul class="list">
<%  for(int i = 0, count = Model.Count; i < count; i++)
    {
%>      <li><%:Html.DisplayFor(m => m[i])%></li>
<%  }
%>
</ul>

Shared\DisplayTemplates\Foo.ascx: A non-context aware Template for Foo.

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Foo>" %>
    <div><%:Model.Name%></div>

I'd still prefer to handle this scenario in a way similar to how I described it before. So the Model Template can determine for itself how it should format the output instead of having to create special case Templates that are referenced by name by the caller.

CodeSponge