How to create "templated" helpers?

Coordinator
May 12, 2010 at 6:36 AM

With MVC there's no more PageList, MenuList, NewsList etc. I think some functionality like that, which enables us to very rapidly build often used features, has a place in EPiMVC. The question is how to do it?

At first I thought that creating extensions for the HtmlHelper class would be a good idea. There could for instance be a Html.PageList(IEnumerable<PageData>) method that rendered an unordered list for a set of pages. The question then is how to allow developers to supply templates for items. Someone might for instance want to add a "selected" CSS-class to the A-element for the current page. Others might want to add the same class to it's LI-element. Yet another might want to add a CSS-class to every other LI-element.

My initial thought was to handle that by providing a bunch of overloads that enabled the developer to provide delegates for rendering parts of the list, sort of like this: Html.PageList(IEnumerable<PageData>, Func<TagBuilder, PageData>) but I'm not found of having 34.5 overloads to every method and I'm pretty sure the method signatures with a bunch of Funcs would confuse many developers, including myself. 

Then I thought of another solution. We could provide a set of Html extensions that would each only have two or three overloads, one that would render the list with default behavior and one that would accept an IPageListRenderer, INewsListRenderer etc. So my previous example method could now have this signature:  Html.PageList(IEnumerable<PageData>, IPageListRenderer). It would also still have an overload that looks like this: Html.PageList(IEnumerable<PageData>). That last overload would use a default implementation of IPageListRenderer which it would pretty much just delegate to. If you want to customize the rendering of the page list you have two options, either create your own IPageListRenderer from scratch or, preferably, inherit from the default one. The default one would be a comprised of a bunch of virtual method, allowing you to override small details as well as bigger chunks of functionality. It would also be highly testable.

I can't say I'm extatic about the last approach either, but it's the best I've got so far. Anyone got any any other suggestions or ideas?

May 12, 2010 at 4:15 PM
Edited May 12, 2010 at 4:16 PM

I agree about your solutions here - they feel a bit too 'WebFormy'. I wouldn't really want to have to override anything just to change simple display behaviour.

I'm presuming you're using MVC2 with EPiMVC? I've not had chance to look in detail at the codebase yet :( If so wouldn't it be possible to use the new DisplayTemplate/EditorTemplate functionality? It seems pretty well matched to the problem here.

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

http://www.codecapers.com/post/Display-and-Editor-Templates-in-ASPNET-MVC-2.aspx

http://blogs.msdn.com/nunos/archive/2010/02/08/quick-tips-about-asp-net-mvc-ui-helpers-and-templates.aspx

So you'd have something like this in your view

<%= Html.DisplayFor( x => Model.PageList ) %>

which will automagically display the PageList in a view partial defined under /Shared/DisplayTemplates/PageList.ascx . This will means that simple mark-up changes are achieved by modifying the view only (as it should be). I think this also allows you to specify Controller specific partials (ie MVC will search /Controller/DisplayTemplates/ first before Shared/DisplayTemplates).

 

Where this needs a little more thought is a nice way to enable multiple Templated views for the same controller, ie I might have two different PageLists with different markup. I think this is possible either using using the [UIHint("SomeOtherPageListView")] attribute on the Model property or with:

<%= Html.DisplayFor( x => Model.PageList1 ) %>

<%= Html.DisplayFor( x => Model.PageList2, "SomeOtherPageListView" ) %>

which would mean PageList1 would be displayed in /Shared/DisplayTemplates/PageList.ascx (resolves by TypeName - so you'd have to create a wrapper around your IEnumerable<PageData>) and PageList2 would would be displayed in /Shared/DisplayTemplates/SomeOtherPageView.ascx

This is a brain dump rather than any true prototype.... What do you think?

Coordinator
May 12, 2010 at 6:07 PM
Sounds very interesting Mark, I'll have to look closer into this! Unfortunately EPiMVC is built with MVC 1.0 as the dashboard in EPiServer doesn't work with MVC 2. Still looking for a workaround for that though. Any thoughts on how to that would be appreciated!
Editor
May 13, 2010 at 6:20 AM

I think Marks approach sound very reasonable (apart from the fact that it's not supported in MVC 1). I think that doing more research on getting MVC 2 to work should be pretty high up on the list since it contains many nice features that could make the implementation both easier to write and work with.

Another thing that might be related to this is something I've been thinking about lately (and I think we briefly spoke about this last time we met Joel) is to try and decouple PageTypeBuilder classes from PageData. Maybe create our own poco-PageData and use a tool like AutoMapper to map between the two. PTB would of course still know about epi-PageData but the TypedPageData would only inherit from the poco one. One big disadvantage to this in the past has been that none of the built in controls (like pagelist etc) would work then (at least I suppose not, I havent actually tried it) but if we must create new controls / templates for MVC anyway it might be a good time to do it.

And since all the project that's not using MVC might want the EPI-PageData it perhaps should be a choice in your code to inherit from PageTypeBuilder.TypedPageData or PageTypeBuilder.Poco.TypedPagedData.

I hope this early morning ramble at least made some sense...