views:

525

answers:

4

I'm trying to write an auto-scaffolder template for Index views. I'd like to be able to pass in a collection of models or view-models (e.g., IQueryable<MyViewModel>) and get back an HTML table that uses the DisplayName attribute for the headings (th elements) and Html.Display(propertyName) for the cells (td elements). Each row should correspond to one item in the collection.

Here's what I have so far:


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

<%
    var items = (IQueryable<TestProj.ViewModels.TestViewModel>)Model;
        // How do I make this generic?
    var properties = items.First().GetMetadata().Properties
        .Where(pm => pm.ShowForDisplay && !ViewData.TemplateInfo.Visited(pm));
%>
    <table>
        <tr>
<%
            foreach(var property in properties)
            {
%>
                <th>
                    <%= property.DisplayName %>
                </th>
<%
            }
%>
        </tr>
<%
        foreach(var item in items)
        {
            HtmlHelper itemHtml = ????;
                // What should I put in place of "????"?
%>
            <tr>
<%
                foreach(var property in properties)
                {
%>
                    <td>
                        <%= itemHtml.Display(property.DisplayName) %>
                    </td>
<%          
                }
%>
            </tr>
<%
        }
%>
    </table>

Two problems with this:

  1. I'd like it to be generic. So, I'd like to replace var items = (IQueryable<TestProj.ViewModels.TestViewModel>)Model; with var items = (IQueryable<T>)Model; or something to that effect.

  2. A property Html is automatically created for me when the view is created, but this HtmlHelper applies to the whole collection. I need to somehow create an itemHtml object that applies just to the current item in the foreach loop. I'm not sure how to do this, however, because the constructors for HtmlHelper don't take a Model object.

How do I solve these two problems?

A: 

Have you looked into the MVCContrib Grid? Certainly someone has written an extension method to show all properties by default. If not it shouldn't be difficult.

edit2- I guess don't trust my answers on Sunday. I misread the question before and didn't realize Html.Display() was the MVC2 templated input. (I'm doing some radically different input builders at my shop so I didn't pick up on the name. I just thought Display showed the value.) Anyway, there are two options I see here.

1) Pull the MVC2 source or break out Reflector and write your own method that is not extending HtmlHelper.

2) You can use reflection here to build a new HtmlHelper instance but it's not pretty. (I didn't test this but is should work.)

var modelType = Html.GetType().GetGenericParameters()[0];
var itemHtmlType = typeof(HtmlHelper<>).MakeGenericType(modelType);
var itemHtmlCtor = itemHtmlType.GetConstructor(typeof(ViewContext), typeof(IViewDatacontainer), typeof(RouteCollection));
var itemHtml = itemHtmlCtor.Invoke(Html.ViewContext, Html.ViewDataContainer, Html.RouteCollection);

You'll get an object out of this though so you have to use reflection to invoke itemHtml.Dipslay. I'd recommend going a different route.

Ryan
I'll look into the MvcContrib Grid. Thanks for the tip. As for your second answer, can you give me some idea of how to write the extension method? I mean, I know how to write extension methods in general, but I'm not clear on how I would create my own `HtmlHelper` instance with the current `item` as the model.
DanM
You could use property.GetValue(item, null) to get the value. I just prefer wrapping this kind of stuff up in a method for reuse. I guess it doesn't need to be an extension method. I wasn't thinking for a second.
Ryan
But `property.GetValue(...)` is not equivalent to `Html.Display(...)`. I need something like `var itemHtml = ????`, and then I would call `itemHtml.Display(property.DisplayName)`. What should `????` be?
DanM
I edited my question a little bit. Hopefully, it is a little more clear now what I'm trying to ask.
DanM
A: 

for the geniric IQueryable part, why don't you simply cast it to an IQueryable...

var items = (IQueryable<TestProj.ViewModels.TestViewModel>)Model;

and What is your template for, isn't it almost the same as the default list template when you add a view... If you want to customize it, i think you'd better look for the List.tt template. You could modify that to use Html.Display instead of Html.Encode.

Or you could try using

Html.DisplayFor(o => property.GetValue(item))

I'm not sure if this would work...

moi_meme
Thanks for your answer, but I'm having no luck with any of the three things you suggested. If I cast as `IQueryable` instead of `IQueryable<T>`, I lose the ability to use LINQ, so `items.First()` doesn't compile. If I try just using the default `Display()` or `DisplayFor()` helper on the items, I get a blank display, even if I remove all my custom templates. As for your suggestion to try `property.GetValue()`, `GetValue()` is not even a method on `ModelMetadata` (reference: http://msdn.microsoft.com/en-us/library/system.web.mvc.modelmetadata_members%28VS.100%29.aspx). Any other ideas?
DanM
A: 

Sadly you can't do:

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

or even

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

ASP.NET MVC implements a Html.DisplayForModel() as Brad Wilson shows on his blog which does scaffolding for you out of the box. (which I'm sure you know about, mentioning for completeness).

If you look at the MVC 2 RTM Source Code source code and in particular the DefaultDisplayTemplates.cs it's all basically contained in an HTMLHelper as suppose to a View. Perhaps instead of fighting with the auto generated class for the view (as much as I love asp.net mvc the default view rendering engine is a total pain) go with an HTMLHelper extension modelled after how ASP.NET MVC RTM implements scaffolding. I guess this would open the possibility for code reuse some more too (though almost everything is internal).

It would be nice if the ASP.NET MVC scaffolding would allow for a custom template to be passed in to change the html if you're not a fan of the default <div class=\"display-field\"> etcetera. To me this is one of the weak spots of ASP.net MVC (the 'hardcoded' error handling/ scaffolding HTML).

If your not a fan of string building your HTML, your extension could call out to more granular partial views (like ScaffoldCaption.ascx) when your scaffolding HTMLHelper extension has gathered enough information to sufficiently type the granular view.

Martijn Laarman
Hi Martijn, I don't mind building my own HtmlHelper method, but whether I use an .aspx file or a .cs file, I will still have the issue of how to instantiate helpers for the individual items in the collection/list. When you create a new view, that `Html` property gets populated for you. Now I need to create the equivalent object for each item in the list/collection. How do I do this? That's what the `????` is about.
DanM
Also, I wasn't able to find DefaultDisplayTemplates.cs in the source code (http://aspnet.codeplex.com/SourceControl/changeset/view/23011#).
DanM
+2  A: 

Phil Haack to the rescue!

http://haacked.com/archive/2010/05/05/asp-net-mvc-tabular-display-template.aspx

DanM