ASP.net MVC - rendering a List containing different types, with a different view for each type

StackOverflow https://stackoverflow.com/questions/955811

  •  12-09-2019
  •  | 
  •  

Question

Imagine I have a list of objects that implement an interface called ISummary The objects within this list MAY have additional properties ie.

public interface ISummary {
  Guid Id {get;set;}
  string Title {get;set;}
  DateTime Created {get;set;}
}

public class GigSummary: ISummary {
 Guid Id {get;set;}
 string Title {get;set;}
 DateTime Created {get;set;}
 string VenueName {get;set}
 string Band {get;set;}
}

public class NewsSummary: ISummary {
 Guid Id {get;set;}
 string Title {get;set;}
 DateTime Created {get;set;}
 string Author{get;set}
}

I now pass this list of Gigs and News Summary objects (as a list of ISummary) to the view as the model.

I want to render this list using a different partial for each type contained in the list.

How can I do this is ASP.NET MVC?

Was it helpful?

Solution

The most obvious way I can think of would be to do something like:

    foreach(ISummary summ in listOfISummary) {
    Html.RenderPartial(String.Fomat("~/Views/Shared/{0}Renderer.ascx", summ.GetType.ToString()), summ, ViewData);%>
}

and create a strongly typed view with a naming convention, like NewsSummaryRenderer.ascx.

I expect that you could move this out to a helper method though, but I'd add it to one of the existing helpers through an extension method rather than putting it in a code behind as suggested previously.

OTHER TIPS

You could put a helper method in the view's codebehind, and then do something like:

Type modelType = this.Model.GetType();

if (modelType == typeof(NewsSummary))  this.RenderPartial("newspartial", this.Model as NewsSummary);
else if (modelType == typeof(GigSummary)) this.RenderPartial("gigpartial", this.Model as GigSummary);

Lewis is on the right track. I would take a slightly different tack--have both of the "widgets" extend from a common base class which provided information about the view names involved. Then add an extension method to your page class to "render widget" which could get the appropriate view in place.

Check out the Kona ASP.NET MVC sample app for a working example of this concept.

I'd create an HtmlHelper extension that did this. Here's some pseudocode that looks shockingly like c# and may actually work:

public static void TemplatedList<T>(this HtmlHelper me, IEnumerable<T> items, 
IDictionary<Type, Action<T>> templates)
{
  foreach(var item in items)
  {
    var template = templates[item.GetType()];
    if(template != null) template(item);
  }
}

I'd use it like this:

<% HtmlHelper.TemplatedList(ViewData.Model, new Dictionary
  {
    {typeof(GigSummary), x => %>

<div class="gigSummary">
SUP!  GIG ANNOUNCEMENT FOR <%= x.Band %>!!

What:  <%= x.Title %>
When: <%= x.Created %>
Who: <%= x.Author %>
</div>

    <%}
  // add more type/template pairs here
  }); %>

Here's a simple extension method you can create to extract just the types you need:

public static class Extensions
{
    public static IEnumerable<U> ExtractOfType<U, T>(this IEnumerable<T> list)
        where T : class
        where U : class
    {
        foreach (var item in list)
        {
            if (typeof(U).IsAssignableFrom(item.GetType()))
            {
                yield return item as U;
            }
        }
    }
}

Test:

public interface IBaseInterface
{
    string Foo { get; }
}

public interface IChildInterface : IBaseInterface
{
    string Foo2 { get; }
}

public interface IOtherChildIntreface : IBaseInterface
{
    string OtherFoo { get; }
}

public class BaseImplementation : IBaseInterface
{
    public string Foo { get { return "Foo"; } }
}

public class ChildImplementation : IChildInterface
{
    public string Foo2 { get { return "Foo2"; } }
    public string Foo { get { return "Foo"; } }
}

public class OtherChildImplementation : IOtherChildIntreface
{
    public string OtherFoo { get { return "OtherFoo"; } }
    public string Foo { get { return "Foo"; } }
}

....

        List<IBaseInterface> b = new List<IBaseInterface>();
        b.Add(new BaseImplementation());
        b.Add(new ChildImplementation());
        b.Add(new OtherChildImplementation());
        b.Add(new OtherChildImplementation());


        foreach (var s in b.ExtractOfType<IOtherChildIntreface, IBaseInterface>())
        {
            Console.WriteLine(s.GetType().Name);
        }

This will get all of the items in the list that are of the derived type you're looking for. So, in your controller, pass in the entire list to the view. Then, have partial views that take IEnumerable's of the type that partial needs, and within your main view, call this extension method and pass on the result to those individual partial views.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top