Showing posts with label ASP.NET MVC. Show all posts
Showing posts with label ASP.NET MVC. Show all posts

Thursday, 22 January 2009

Trying to Bind Form Elements in the View to a Dictionary in the Model with ASP.NET MVC

I've got a class that has, instead of statically typed properties, a dictionary that is used to store different values depending on what is needed for any given instance.

    public class Baz
    {
        public Baz()
        {
            Foo = new Dictionary<string, string> { { "Bar", null } };
        }
 
        public IDictionary<string, string> Foo { get; set; }
    }


I'm using ASP.NET MVC and I want to bind the object to the View. The View is dynamically generated based on a template object, like this:


<% foreach (Field field in ViewData.Model.Template.Fields)
    {%>

        <%= Html.Encode(field.DisplayText) %>
        <%= Html.TextBox(string.Format("Foo[{0}]", field.Name)) %>
 <% } %>


In the above, the field.Name will correspond to the Key of a KVP placed into the dictionary of the Foo instance during its' manufacture.

I am using the DefaultModelBinder, and if I look at the ASP.NET MVC (Beta) source code and in particular the BindModelCore method I see that the first thing it does is to find out if the type being dealt with is a Dictionary type, if it is then the UpdateDictionary method gets called. The first line of the UpdateDictionary method assigns the result of a call to CreateSubPropertyName passing in the BindingContext.ModelName as the prefix, and the string 'index' as the property name.

Now I can't see how this works. The result of this code seems to be that, where the ModelName is foo, a key of 'foo.index' is looked for in the route data, query string and form values (DefaultValueProvider.GetValue). Now this isn't found unless the id's for my form elements that tie in to the Dictionary are called 'foo.index' (yep, same id for multiple elements - already smells very wrong). That means altering my View code to look like this:


 <% foreach (Field field in ViewData.Model.Template.Fields)
    {%>
        <%= Html.Encode(field.DisplayText) %>
        <%= Html.TextBox("Foo.index")) %>
 <% } %>


And, giving each the name of foo.index, I get back the values of each as the attempted values and progress into the code which iterates through these values (now stored in a string[] called 'indicies'. The first line of code in the foreach loop (still in DefaultModelBinder.UpdateDictionary) calls CreateSubIndexName, passing in the ModelName and one of the attempted values, so if the attempted value was 'test', it will return foo[test]. Now this key is formatted how I'd want, but uses the attempted value as the key, which is not what I'd want.

The very next thing that happens is a call to DefaultModelBinder.BindProperty which calls CreateSubPropertyName passing in the ParentContext.ModelName, which right now is 'foo[test]' and the string 'key'. This results in a 'newName' of 'foo[test].key'. The consequence of this is that DefaultValueProvider.GetValue is called and looks for an id called 'foo[bar].key', unsurprisingly this doesn't exist, as I had to give all of the dictionary bound form elements an id of 'foo.index', when the null value is returned up then an exception results and the attempt to bind the elements stops.

What I'd like is for the form element id to read Foo[Bar] (assuming no prefix required, otherwise Baz.Foo[Bar]), and for the binding process to use this to get the attempted value, and to then Try and update the KeyValuePair in the Dictionary Foo where the key is Bar with the attempted value. Is that too much to ask?

Now it's entirely possible (probable?) that I've not understood how to make the UpdateDictionary method tick, Googling has revealed nothing, so I'd be appreciative of any information anyone reading this might be able to impart. Otherwise, if I get a chance, I may look to implement my own ModelBinder that has this behaviour (but i'd rather not).

Tuesday, 23 September 2008

ASP.NET MVC - If it feels this right it can't be wrong!

Having had a few days leave from work I've spent most of my evenings playing with the not-so new MVC framework. It's been great to spend some focused time playing with this new toy for web development from Microsoft. One thing that I've noticed is how nicely and quickly you can start to put together powerful, enterprise happy software with it. My starting point has been a model (a bunch of POCOs based around the educational assessment domain, which I know well) using NHibernate for persistence, Windsor for a DI framework (though not for the controllers yet), MBUnit and RhinoMocks for unit testing, and the latest (5) release of the MVC framework. After getting some simple stories completed (add a candidate, edit a candidate, register a candidate for an assessment) I wanted to try and improve the user experience by getting some Ajax into the Views. I've had a play with using Scriptaculous and JQuery with the MVC framework during brief forays in the past, so this time I thought I'd just use the default MS stuff that ships with it.

One example of the kind of implicit wiring that the framework deals with you that I found really neat was this. I have a view that lists candidates based on a search (I hope to blog about that before long).

<%
foreach (Candidate candidate in ViewData.Model as List<Candidate>)
{%>
<tr>
<td><%=Ajax.ActionLink("View", "ViewDetail", "Candidate", candidate, new AjaxOptions { UpdateTargetId = "CandidateDetail" })%></td>
<td><%=Ajax.ActionLink("Edit","EditDetail","Candidate", candidate, new AjaxOptions{ UpdateTargetId = "CandidateDetail"}) %></td>
<td><%=Html.Encode(candidate.Id.ToString())%></td>
<td><%=Html.Encode(candidate.Name.FullName)%></td>
</tr>
<%
}%>

Note the use of the Ajax.ActionLink method here. This will update my CandidateDetail div with the results of the Ajax call. The call itself will be to the ViewDetail action of the CandidateController class, and the properties of the Candidate object for the row will be available to this action. The neat thing for me is that in the action if I request just an id argument then this is automagically wired up to the Id property of the Candidate object for me!

[AcceptVerbs("POST")]
public ActionResult ViewDetail(Guid id)
{
ICandidateRepository candidateRepository = MvcApplication.WindsorContainer.Resolve<ICandidateRepository>();
Candidate candidate = candidateRepository.GetCandidate(id);
return View("Detail", candidate);
}

As you can see, the code above has one argument that accepts a Guid and is called id. The framework, following convention (love that), realises I mean the id of the candidate object and passes it into the method. Neat huh! I then get the appropriate candidate object from my repository and then return the detail view (an ascx control) passing the retrieved candidate object in. This control will then be rendered into the div originally specified (CandidateDetail).

This is just one example of how easy it can be to get this framework working for you. I'm really, really liking working with all this new MVC goodness, it just feels so, right!