Thursday, February 28, 2013

MVC Untyped ViewBag versus Strongly Typed ViewModel


So in MVC when you create a view you pass data in two ways. One is the ViewBag. The ViewBag is a dynamic object (ooohh) that essentially wraps a hashtable. The other way to pass data is the model. The model is generally a strongly typed object. The model is very cool, because you can post and have model binding regenerate the server side model object. You can then pass that object back in to the View and the page will be regenerated. Very elegant.

So you have a divide. You have a bunch of untyped data in a glob and a strongly typed data object that must be fully represented in input fields in the view so that it can be successfully regenerated. Okay, but what if I want something in between like a strongly typed bunch of data that isn't necessarily going to be editted and posted back? For example, suppose I have a page where you pick your favorite fruit. I have a drop down list. My model has an identifier to indicate what fruit has been picked, but where do I store the list of all the fruit. I could store it in the model, but what if I have validation. Unless I put the whole list in some hidden input I can't use the wonderful mechanism I mentioned above where I just dump the newly model bound object into the view. Dumping the whole list into a hidden input seems wrong and could potentially be insecure.

So the other option is to put the list in the ViewBag. That works great, but imagine that you are picking your favorite one of many types of food. You are suddenly storing a large amount of data in some big untyped blob. No compiler or intellisense to warn you when you are messing up. No sense of structure of meaning given by a typed object.

This issue really bothered me and I came up with a solution which I am not super crazy about, but it works okay. So the basics are that you create an object to represent the data which isn't necessarily going to be in your model. For example:

public class FixedModel
{
    private List<Fruit> _fruits;
    public List<Fruit> Fruits
    {
        get { return _fruits ?? (_fruits = new List<Fruit>()); }
    }
}

You then create a descendant of WebPageView with a property of this type:

public abstract class MyPage<TModel> : WebViewPage<TModel>
{
    public FixedModel FixedData { get; private set; }
    protected override void InitializePage()
    {
        FixedData = ViewBag.FixedData;
        base.InitializePage();
    }
}

Now in your Controller you put this new object into the ViewBag:

private void GenerateFixedData()
{
    FixedModel fixedModel = new FixedModel ();
    fixedModel .Fruits.Add(new Fruit { Id = 1, Name = "Apple" });
    fixedModel .Fruits.Add(new Fruit { Id = 2, Name = "Banana" });
    fixedModel .Fruits.Add(new Fruit { Id = 3, Name = "Pear" });
    fixedModel .Fruits.Add(new Fruit { Id = 4, Name = "Mango" });
    ViewBag.FixedData= fixedModel;
}

Now you call this method from both your GET and POST actions. And inside your razor view you can access this object like FixedData.Fruits and this will be typed instead of dynamic.  You have to add @inherits MyPage<ActualModel> at the top to make use of your custom WebViewPage.

What I would really prefer is for the WebViewPage to take two classes as opposed to just the model. Then you could avoid the special WebViewPage descendant and using the ViewBag to transfer data.

No comments:

Post a Comment