views:

718

answers:

5

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?

A: 

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);
Joel Martinez
+8  A: 

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.

Lewis
Sounds sensible - I'll wrap it in a helper
ListenToRick
Actually this is conceptually my favourite answer - since ASP.NET MVC is about convention. A nice little idea.
ListenToRick
A: 

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.

Wyatt Barnett
A: 

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
  }); %>
Will
btw, this may look bizarre, but you can actually reverse-inject chunks of ASP.NET pages into code. Again, not tested, may be buggy, but it is possible.
Will
A: 

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.

BFree
I'm actually interested in multiple types of concrete implementations of the interface :)
ListenToRick
So then have a separate Partial view for each concrete type, and for each one, call this extension method and pass the result in to the partial view.
BFree