Working with dynamic Model in MVC View

by Anton 26. July 2012 12:21

We are working with the Orchard Framework, which is itself build on top of MVC 3. We ran into some problems working with a dynamic Model and dynamic properties.

Dynamic vs. Object

We have search criteria in lists of sections which are being filled at runtime.

using System.Collections.Generic;
 
namespace antontelle.Models
{
    public class Criterion
    {
        public string Text { get; set; }
        public object Value { get; set; }
    }
 
    public class Section
    {
        public IList<Criterion> Criteria { get; set; }
    }
}

Since we do not know the types of the criteria beforehand, we thought using dynamic would be cool. But it is problematic to use strongly typed ViewTemplates with dynamic properties. Just use the Object type – there were no deficits in doing so for us. Darin helped us a lot with that on stackoverflow. Also, you will be less prone to run into this error: “Templates can be used only with field access, property access, single-dimension array index, or single-parameter custom indexer expressions.” A long time we thought the error was due to us using a double-dimensional array in the view.

Dynamic Model

After fixing all that we ran into another problem. The search from we tried to fill with preset values is being delivered by a SearchFormPartDriver (an Orchard variant of the Controller) as a ContentShape. This ContentShape is of type dynamic. Here is an equivalent controller we build for test purposes (the wrapper takes the place of ContentShape):

using antontelle.Models;
 
namespace antontelle.Controllers
{
    public class SearchViewModel
    {
        public IList<Section> Sections { get; set; }
    }
 
    public class SectionsController : Controller
    {
        //
        // GET: /Sections/
        public ActionResult Index()
        {
            var model = new[]
                {
                    new Section
                        {
                            Criteria = new[]
                               {
                                   new Criterion {Text = "some integer", Value = 2},
                                   new Criterion {Text = "some boolean", Value = true},
                                   new Criterion {Text = "some string", Value = "foo"},
                               }
                        },
                    new Section
                        {
                            Criteria = new[]
                               {
                                   new Criterion {Text = "some other integer", Value = 4},
                                   new Criterion {Text = "some other boolean", Value = true},
                                   new Criterion {Text = "some other string", Value = "foobar"},
                               }
                        }
                };
            dynamic wrapper = new ExpandoObject();
            wrapper.mdl = new SearchViewModel {Sections = model};
            return View(wrapper);
        }
    }
}

But then just using the Editor-Template on the a property of the Model like this would give us a “Reference not set…” exception:

@Html.Editor("mdl", "Section_List")

After quite some research and trial and error, Oliver came up with the solution: We are now casting the SearchViewModel property and saving it into ViewData.

@{
    ViewData["SearchViewModel"] = (antontelle.Controllers.SearchViewModel)Model.mdl;
}
 
@Html.Editor("SearchViewModel", "Section_List")

From there on we can work with strongly typed views and use further “Criterion_” templates we defined (which I have omitted here):

@model antontelle.Controllers.SearchViewModel
 
@using (Html.BeginForm())
{
    for (int i = 0; i < Model.Sections.Count; i++)
    {
        for (int j = 0; j < Model.Sections[i].Criteria.Count; j++)
        {
        <div>
            @Html.LabelFor(x => x.Sections[i].Criteria[j], Model.Sections[i].Criteria[j].Text)
            @Html.EditorFor(x => x.Sections[i].Criteria[j].Value, "Criterion_" + Model.Sections[i].Criteria[j].Value.GetType().Name)
        </div>
        }
    }
    <button type="submit">OK</button>
}

We use special templates because actually we are working with nullable types.

If you are interested in how the data (especially the Model) is passed from the controller to the view, here is a good video explaining it, even though it is based on MVC 2: TekPub MVC Views Working with data (especially minutes 7:40 – 16:50).

enjoyed the post?

Tags:

Comments are closed

About Oliver

shades-of-orange.com code blog logo I build web applications using ASP.NET and have a passion for javascript. Enjoy MVC 4 and Orchard CMS, and I do TDD whenever I can. I like clean code. Love to spend time with my wife and our children. My profile on Stack Exchange, a network of free, community-driven Q&A sites

About Anton

shades-of-orange.com code blog logo I'm a software developer at teamaton. I code in C# and work with MVC, Orchard, SpecFlow, Coypu and NHibernate. I enjoy beach volleyball, board games and Coke.