Tutorial :Generic partial view: how to set a generic class as model?


I'm trying to build a generic grid view in an ASP.NET MVC application.

Let me explain with some code:

public interface ITrustGrid<T>  {      IPagedList<T> Elements { get; set; }      IList<IColumn<T>> Columns { get; set; }      IList<string> Headers { get; }  }  

This is an interface of a class that allows me to set columns and expressions in my controller.

I pass implementations to a partial view like this:

<% Html.RenderPartial("SimpleTrustGridViewer", ViewData["employeeGrid"] as TrustGrid<EmployeeInfoDTO>); %>  

The problem is that I can't figure out how to make the partial view that renders the grid generic.

In other words, I want to turn this:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<ITrustGrid<EmployeeInfoDTO>>" %>  

into something like this:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<ITrustGrid<T>>" %>  

=> How can I make my partial view generic in the most simple way?


I solved this by using a TrustGridBuilder that has a public TrustGrid GetTrustGrid() method which returns a non-generic TrustGrid. The TrustGrid contains strings instead of linq stuff. So I execute the linq in the GetTrustGrid() method and put the strings in a TrustGrid object.

Thanks for everybody to help me on the right track.


It's not possible to do it like that. The reason is the .aspx will generate a class that you don't have much control on it and you can't add a generic parameter to it. I guess the most straightforward way is to pass it as object.


You could make all your model types that you would pass into this partial inherit from a base class/interface that establishes the basic behavior that would be used by this partial view and accept any object of that class/interface type, or just have the view accept any type of object and then use reflection to base your partial view behavior off of.


public interface IDisplayModel  {      string DisplayText{get;set;}      string ImageUrl{get;set;}      string AltText{get;set;}  }    public interface ITrustGrid<T> where T : IDisplayModel  {          IPagedList<T> Elements { get; set; }          IList<IColumn<T>> Columns { get; set; }          IList<string> Headers { get; }  }    <%@ Control Language="C#"       Inherits="System.Web.Mvc.ViewUserControl<ITrustGrid<IDisplayModel>>" %>  

Naturally, your IDisplayModel would vary based on your desired behavior. This would then allow you to pass in anything to this partial that implements this base interface to establish general behavior.


I agree with Mehrdad, as far as I know it isn't possible to make generic views. In one of my projects, I used an interface much like your one, and then passed delegate functions to the view that handle the specific rendering of each item.

For instance, I would have used a non-generic view data class with an additional field:

public interface ITrustGrid {      IPagedList Elements { get; set; }      IList<IColumn> Columns { get; set; }      IList<string> Headers { get; }        Func<object, string> ElementRenderer { get; }  }  

In your main view you'll prepare the view data:

<%  ITrustGrid data = (ITrustGrid)ViewData["employeeGrid"];  data.ElementRenderer = new Func<object, string>(delegate(o) {      var employee = (Employee)o;      //render employee      return html;  });    Html.RenderPartial("SimpleTrustGridViewer", data);  %>  

While in your grid partial you'll process the grid as usual, and then call the delegate to render each single cell:

<%  foreach(var element in ViewData.Elements){      %>      <tr>          <td><%=ViewData.ElementRenderer(element) %></td>      </tr>      <%  }  %>  

Of course, the code above only renders a single cell for each element, you'll have to create a slightly more complex delegate to render multiple columns (or pass in an array of delegates, one for each column).

I think this would be one of the cleanest ways to do it, albeit a bit cumbersome.


@ Lck:

I'm doing something like that in my controller:

var columns = new List<IColumn<EmployeeInfoDTO>>                    {                        new Column<EmployeeInfoDTO>("Full name", e => string.Format("{0} {1}", e.FirstName, e.Name)),                        new Column<EmployeeInfoDTO>("Examination date", e => e.ExaminationDate.HasValue? string.Format("{0} days ago", currentDate.Subtract(e.ExaminationDate.Value).Days) : "Unknown")                    };    var employeeGrid = new TrustGrid<EmployeeInfoDTO> { Columns = columns, Elements = GetEmployees(currentPageIndex)};    ViewData["employeeGrid"] = employeeGrid;  

So in my partial view I can do this:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<ITrustGrid<EmployeeInfoDTO>>" %>  <table>      <thead>          <tr>              <%                  foreach (string header in Model.Headers)                      Response.Write(Html.Th(header));              %>          </tr>      </thead>      <tbody>          <%              foreach (var element in Model.Elements)              {                  Response.Write("<tr>");                  foreach (var column in Model.Columns)                      Response.Write(Html.Td(column.ValueExpression(element)));                  Response.Write("</tr>");              }          %>      </tbody>  </table>  <div class="pager">      <%= Html.Pager(Model.Elements.PageSize, Model.Elements.PageNumber, Model.Elements.TotalItemCount)%>  </div>  

As you see, none of the code in my partial depends on the Type used. So I still think there's a simple solution.

Note:If u also have question or solution just comment us below or mail us on toontricks1994@gmail.com
Next Post »