views:

915

answers:

4

The 'RenderPartial()' method in ASP.NET MVC offeres a very low level of functionality. It does not provide, nor attempt to provide a true 'sub-controller' model *.

I have an increasing number of controls being rendered via 'RenderPartial()'. They fall into 3 main categories :

1) Controls that are direct descendants of a specific page that use that page's model

2) Controls that are direct descendants of a specific page that use that page's model with an additional key of some type. Think implementation of 'DataRepeater'.

3) Controls that represent unrelated functionality to the page they appear on. This could be anything from a banner rotator, to a feedback form, store locator, mailing list signup. The key point being it doesn't care what page it is put on.

Because of the way the ViewData model works there only exists one model object per request - thats to say anything the subcontrols need must be present in the page model.

Ultimately the MVC team will hopefully come out with a true 'subcontroller' model, but until then I'm just adding anything to the main page model that the child controls also need.

In the case of (3) above this means my model for 'ProductModel' may have to contain a field for 'MailingListSignup' model. Obviously that is not ideal, but i've accepted this at the best compromise with the current framework - and least likely to 'close any doors' to a future subcontroller model.

The controller should be responsible for getting the data for a model because the model should really just be a dumb data structure that doesn't know where it gets its data from. But I don't want the controller to have to create the model in several different places.

What I have begun doing is creating a factory to create me the model. This factory is called by the controller (the model doesn't know about the factory).

public static class JoinMailingListModelFactory {

        public static JoinMailingListModel CreateJoinMailingListModel() {

            return new JoinMailingListModel()
            {
                MailingLists = MailingListCache.GetPartnerMailingLists();
            };
        }
    }

So my actual question is how are other people with this same issue actually creating the models. What is going to be the best approach for future compatibility with new MVC features?


  • NB: There are issues with RenderAction() that I won't go into here - not least that its only in MVCContrib and not going to be in the RTM version of ASP.NET-MVC. Other issues caused sufficent problems that I elected not to use it. So lets pretend for now that only RenderPartial() exists - or at least that thats what I've decided to use.
+2  A: 

One approach I've seen for this scenario is to use an action-filter to populate the data for the partial view - i.e. subclass ActionFilterAttribute. In the OnActionExecuting, add the data into the ViewData. Then you just have to decorate the different actions that use that partial view with the filter.

Marc Gravell
the more i thought about this the less i liked the idea. then the more i thought about it after that the more i DID like it. mainly because its much easier for anyone to add a control to a page - just add the attribute and the RenderPartial and you're done.
Simon_Weaver
That sounds like a lot of thinking ;-p
Marc Gravell
ended up using this approach in several places in my app. makes it much cleaner to apply certain things to models globally. i use it both for a 'base model' that corresponds to a master page and also 'area models' for different areas in the site. quite flexible and while i'm not totally in love with injecting things into my models it is working quite well
Simon_Weaver
+1  A: 

There's a RenderPartial overload I use that let's you specify a new ViewData and Model:

RenderPartial code

If you look at the previous link of the MVC source code, as well as the following (look for RenderPartialInternal method):

RenderPartialInternal code

you can see that if basically copies the viewdata you pass creating a new Dictionary and sets the Model to be used in the control. So the page can have a Model, but then pass a different Model to the sub-control.

If the sub-controls aren't referred directly from the main View Model, you could do the trick Marc Gravell mentions to add your custom logic.

antonioh
+4  A: 

Instead of adding things like MailingListSignup as a property of your ProductModel, encapsulate both at the same level in a class like ProductViewModel that looks like:

public class ProductViewModel() {
    public ProductModel productModel;
    public MailingListSignup signup;
}

Then get your View to be strongly-typed to the ProductViewModel class. You can access the ProductModel by calling Model.productModel, and you can access the signup class using Model.signup.

This is a loose interpretation of Fowler's 'Presentation Model' (http://martinfowler.com/eaaDev/PresentationModel.html), but I've seen it used by some Microsoft devs, such as Rob Conery and Stephen Walther.

JMs
I often refer to asp.net MVC as MVVC :)
Martijn Laarman
This is generally the approach I take, but it still smells kind of funny to me. I don't like the fact that the outer view has been provided data that it doesn't need. I have taken to using Html.RenderAction<T>() which seems better than Html.RenderPartial() as a proxy for true sub-controllers but it's still somewhat weak.
JC Grubbs
A: 

One method I tried was to use a strongly typed partial view with an interface. In most situations an agregated ViewModel is the better way, but I still want to share this.

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

The Viewmodel implements the interface

 public class ProductViewModel:IMailingListSignup

Thats not perfect at all but solves some issues: You can still easily map properties from your route to the model. I am not shure if you can have a route parameter map to the properties of MailingListSignup otherwise.

You still have the problem of filling the Model. If its not to late I prefer to do it in OnActionExecuted. I dont see how you can fill a Model in OnActionExecuting.

Malcolm Frexner