Hanalei, Hawaii Tuesday, February 09, 2010

ASP.NET MVC: Using UserControls Usefully

This post is in response to a forum user, who is wondering how to properly use a ViewUserControl: I'm sorry if this is a really silly question, but I'm having a hard time grokking what the right usage of a ViewUserControl looks like. Does one invoke the RenderView from a controller?

This post is in response to a forum user, who is wondering how to properly use a ViewUserControl:

I'm sorry if this is a really silly question,  but I'm having a hard time grokking what the right usage of a ViewUserControl looks like. Does one invoke the RenderView from a controller? If so, where is the parent page that the ViewUserControl is embedded in? Or, should you put RenderView into the ASPX of a ViewPage itself?

I can make the thing  render in any number of different ways from different places, but I'm not clear on what the right MVC model is for this situation. I've read the long thread on the ".. very complex page with many MVC modules..." but I'm thinking of a simpler scenario: Let's suppose you have three or four different views that you want to compose into a page? Is this the right scenario for a ViewUserControl? If so, where would you reference the control itself?

This is an interesting issue, and as he went on to point out, there are lots of ways to use these partial UI bits to your advantage. As always, I'll offer some ideas here for you, but this is not the only way to do it.

 

The "Viewlet"
Given that a ViewPage inherits from System.Web.Page, you can still register and reference a UserControl (.ascx file) on a View and have it render. You can even pass it data in the code behind or as a property setting:

<uc1:MyUserControl ID=myControlView runat=server myProperty="Hi Mom" />


But using the WebForms model inside MVC can be a little confusing - especially for folks who will pick up your project later. In this case - the ViewUserControl will render just fine, and in many cases it's all you need. But if you're interesting in keeping things as modular as possible within MVC, read on.

Your UseControl can be one of two things:

  • A granular bit of UI that renders information passed from a Controller
  • A granular bit of UI that renders information from an application-wide data source

The idea here is reusability and maintainability.

I personally don't like the idea of putting logic into ViewUserControls, but at the same time it can serve a purpose - with portal stats for instance where you might want to serve up personalized links (using the Memebership.Profile for instance), etc.

In other MVC frameworks they make a distinction between the two. In Rails, for instance, a "Partial" is simply a View that's shared between views, and not meant to be standalone. If you've ever used Rails to create the demo scaffold, you've used the partial "_form.rhtml" which is responsible for rendering an input form for the New and Edit views.

If you need more than basic rendering of HTML in Rails, you can move to a Layout. Layouts essentially wrap UI around some logic, and are a nice modular way to reuse UI/logic elements around an application. These are like "UserControls Light".

Rails also allows for "sub apps" called "Components", which are useful when you have "sub applications" like an image gallery or rss reader. The idea here is that these components contain their own logic and can be used between many applications.

My point with all this is that if you feel lost trying to make a decision (architecturally) regarding MVC - it helps to see how other platforms do it (like Rails or Django).

 

Rendering a ViewUserControl
The MVC Toolkit has a nice method called "RenderUserControl()" that allows you to process your ViewUserControl and output it's result inline:

 
<%=Html.RenderUserControl("~/UserControls/UserList.ascx")%>


If the ViewUserControl is typed (say ViewUserControl<MyControllerData>), then it's ViewData object will be filled for you, and your ViewPage and rendered control will share the same data.

If you want to be explicit about it, you can do that as well, specifying the data to pass:


<%
=Html.RenderUserControl("~/UserControls/UserList.ascx",ViewData.Users)%>


Finally, if you need to set properties on the ViewUserControl, you can do that as well by passing in an anonymous type:


<%
=Html.RenderUserControl("~/UserControls/UserList.ascx",ViewData.Users, new {GroupID=2})%>

 

Using the RenderUserControl method allows you to have complete control over how your ViewUserControl is supposed to be used

 

Summary
UserControls are a great way (still) to encapsulate UI elements for your MVC app. There are many ways to use them, and if you have one I didn't mention (or dislike my approach here) - do let me know. As always - we're still CTP and there's lots of room for comments.


Vijay Santhanam - January 7, 2008 - Very neat. You've given us so many user control choices, it's great! Will the control instance declaration by default be passed the current view page data??
mike - January 7, 2008 - I don't see why a method RenderUserControl would be member of an object HTML. It might be better as a member of ViewPage. Please consider, thanks!! What I find strange is that we are supposed to call from the ViewPage directly to a user control. We should really call a controller method and have that controller render its view which ends up at the place where we called to the controller. That controller should provide the view with all the data. It migh even return an empty string based on the data. That way, the user control is reusable.
tgmdbm - January 7, 2008 - I agree that there are two types of "sub view" involved here. In one case where the sub views are part of the entity being rendered then RenderUserControl is ideal because we will have the data in the pages ViewData. However, in the case where the page is rendering "widgets" it's impractical for the main controller to know where to get all the data for all the widgets. each widget should be responsible for collecting its own data, and so would require a call to the widgets controller. In second case, the ONLY thing that is required is a call to a method on the controller, because that would in turn render the widget in-place and continue rendering the current page, however, there is some initialisation that needs doing like passing in the correct ControllerContext etc. it would be nice if there was a method on the ViewPage which allowed us to do this. ( x => x.Show() ); %> The second one is more dynamic but less type safe.
Shawn Oster - January 7, 2008 - Mike, if you prefer the concept of a 'WidgetController' there is nothing preventing you from writing your own. That is something I think a lot of WebForms developers are going to have to wrap their minds around, that MVC is a lot more malleable and not quite the rigid framework as WebForms.

It's really a matter of semantics, because you could create a WidgetController, just don't descend it from the base Controller, and expose a Show() method that grabs the needed data as well as either hand generates the HTML or instead makes the call to RenderView itself. The only real downside I see to this approach though is maintainability in teams. Now you have two types of controllers and you need to let others know at a glance that it's not a main controller. I believe some people get around this by instead calling them Services :)

I hope the MVC team keeps that in mind as well, that we don't need a 100 different methods to do the same thing because with extensions and 3.5 it's much easier to extend the framework on our own, keeping MVC a clean framework to build up from instead of an all encompassing beast like WebForms.
Sergey - January 7, 2008 - Hi,

sorry for wrong article to ask, but I have one question on MVC databinding (and UpdateFrom method). Suppose I have control. UpdateFrom method will unbind control's value to my object's Name property (if one exists). Does unbinding using id or name attribute for Property name and can I use different id and name values, for example id="txtName" and name="Name". If so, are you going to implement this kind of overload to HtmlHelper extension method Text()?
mike - January 7, 2008 - Shawn, do you mean just instantiate WeatherController and call its Forecast() action? That seems allright to me. But then why does Ruby on Rails have this special notion of partials, layouts and components? What was the idea behind that? PS. I still really like the declarative markup:
mike - January 7, 2008 - Ugh, can't post html here? Let's try again. PS. I still really like the declarative markup: [my:Weather Display="Forecast"/]
tgmdbm - January 7, 2008 - @Sergy, the unbinding uses the name and not the id, and yes you can have a different id. also, I didn't see the first time but my brackets screwed up my last post. <% CallController<WidgetController>( x => x.Show() ); %> <% CallController( "Widget", "Show" ); %> The second one is obviously more dynamic, you could just return a list of strings and have it output all the different widgets.
tgmdbm - January 7, 2008 - that gives me an idea!!! <mvc:Call Controller="Weather" Action="Forecast" /> that could create the WeatherController and call the Forecast action. However there's the problem of passing arguments to the action. <mvc:Call Controller="Weather" Action="Forecast" > <Arguments> <Argument Name="Date" Value="Monday" /> </Arguments> </mvc:Call> thoughts?
Rob Conery - January 7, 2008 - @tgm - outstanding idea. Circulating it internally to see if the team is working on something like this right now. Will let you know....
tgmdbm - January 17, 2008 - "Will let you know..." Have you heard anything from the team regarding this?
Rob Conery - January 18, 2008 - @tgmdbm - I've created a new method (in your honor!) called "RenderAction" but it's a tad hackish. It works, but not the way I want it to. I'm looking at some other ways to do it but in general the feedback internally was (specifically from Scott) "Awesome idea!".
Rex Morgan - January 20, 2008 - Rob, you answered my question on the ASP.NET forums, but I would like to follow up with that here, since caching is extremely important to me - can we expect to see these methods for rendering ViewUserControls respect Output Caching?
tgmdbm - January 21, 2008 - I feel very honoured that yourself the big S.G. like the idea. Thank you. I have also written this code, and yes, mine too is extremely hacky. check it out here: http://forums.asp.net/t/1207994.aspx Would be nice if the framework would allow this code to be cleaner. When's the next drop of the toolkit? Are you waiting for the next drop of the framework?
Pure Krome - January 21, 2008 - ZOMG! Tgmdbm ... that's my post (with your answers) that you've referenced :) So kewl! Also, your post sent me here ... and i do understand what Rob is saying, a bit easier.

I suppose i'm begging for some sample c# solutions that have a simple example of a few pages with an MVC View User Control being used in each one ... AND ... the MVC VUC having a simple 'postback' ... say a linkbutton (the equiv, if we were using the asp.net webforms stuff .. which we are not). In the code i'm trying to do, i'm trying to drop a reusable control onto various Views which toggles something in the user's Session -- for example .. maybe .. times in 24hour or 12 hour.

Once i've got this working, i was going to try and AJAX that MVC VUC ... but that's a whole other huge problem. Sounds like i'm going to look at JQuery or Prototype ... which is another new learning experience because i'm used to some simple MS Ajax stuff (eg. UpdatePanel).

So much to learn and so little time.

Would love some suggestions / help :)
cxvin - February 27, 2008 - I like tgmdbm's idea lots and glad to hear it's on the way from the team. When will it be? I'm just getting going in MVC framework and when I started making user controls I felt that the way they are placed on the page didn't feel right. I've tried to get away from this before in asp.net by dynamically adding the ascx controls - but to do that here would be to treat the page as a controller, and mess up the seperation.
Gecko