views:

331

answers:

5

Hi,

I'm 3 days into learning MVC for a new project and i've managed to stumble my way over the multitude of issues I've come across - mainly about something as simple as moving data to a view and back into the controller in a type-safe (and manageable) manner. This is the latest.

I've seen this reported before but nothing advised has seemed to work. I have a complex view model:

public class IndexViewModel : ApplicationViewModel
{
 public SearchFragment Search { get; private set; }

 public IndexViewModel()
 {
  this.Search = new SearchFragment();
 }
}
public class SearchFragment
{
 public string ItemId { get; set; }
 public string Identifier { get; set; }
}

This maps to (the main Index page):

%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<IndexViewModel>" %>

<asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server">
    <% Html.BeginForm("Search", AvailableControllers.Search, FormMethod.Post); %>
    <div id="search">
        <% Html.RenderPartial("SearchControl", Model.Search); %>
    </div>
    <% Html.EndForm(); %>
</asp:Content>

and a UserControl:

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

<p>
    <label for="itemId">
        <%= Html.Resource("ItemId") %></label>
    <%= Html.TextBox("itemId", Model.ItemId)%>
</p>
<p>
    <label for="title">
        <%= Html.Resource("Title") %></label>
    <%= Html.TextBox("identifier", Model.Identifier)%>
</p>
<p>
    <input type="submit" value="<%= Html.Resource("Search") %>" name="search" />
</p>

This is returned to the following method:

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Search(IndexViewModel viewModel)
{
 ....
}

My problem is that when the view model is rehydrated from the View into the ViewModel, the SearchFragment elements are null. I suspect this is because the default model binder doesn't realise the HTML ItemId and Identifier elements rendered inline in the View map to the SearchFragment class. When I have two extra properties (ItemId and Identifier) in the IndexViewModel, the values are bound correctly.

Unfortunately, as far as I can tell, I must use the SearchFragment as I need this to strongly type the Search UserControl... as the control can be used anywhere it can operate under any parent view.

I really don't want to make it use "magic strings". There's too much of that going on already IMO.

I've tried prefixing the HTML with "Search." in the hope that the model binder would recognise "Search.ItemId" and match to the IndexViewModel "Search" property and the ItemId within it, but this doesn't work.

I fear I'm going to have to write my own ModelBinder to do this, but surely this must be something you can do out-of-the-box??

Failing that is there any other suggestions (or link to someone who has already done this?)

Here's hoping....

A: 

I think your problem might be here:

public IndexViewModel()
{
  this.Search = new SearchFragment();
}

Did you try leaving your Search property null? I know the CreateModel protected method in the DefaultModelBinder doesn't fire unless the value is already null, but I'm not sure about setting properties. Give it a shot if you haven't tried that yet...

EDIT:

I just noticed:

<%= Html.TextBox("identifier", Model.Identifier)%

Should be:

<%= Html.TextBox("Search.Identifier", Model.Identifier)%
Michael Valenty
Sorry, neither worked. If I leave the SearchFragment as null I get a "The model item passed into the dictionary is of type 'GUI.Web.Models.Search.IndexViewModel' but this dictionary requires a model item of type 'GUI.Web.Models.Fragments.SearchFragment'.2 exception. If I change the Html.Textbox as you said, it still doesn't work.If this is the way of doing it, can you provide a full working example?
Graham
I didn't notice it was a user control. I'm stumped, I would expect your example to work...
Michael Valenty
A: 

I think you will find interesting a solution in this thread Here

SDReyes
Thanks. I'll have a closer look ASAP
Graham
A: 

Thanks so far. It's pretty obvious this fairly basic requirement is missing from MVC :p

Graham
A: 

Maybe you should try changing your action method into something like this and do the bindings manually:

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Search(FormCollection formCollection)
{
   var viewModel = new IndexViewModel();
   viewModel.Search.ItemId = formCollection[0];
   viewModel.Search.Identifier = formCollection[1];
   ...
}

But this approach is somewhat similar to creating your own binder.

Victor
A: 

My first thoughts are that the properties of SearchFragment do not have the same case as the form fields.

I might be wrong, but it might be worth checking.

Edit:

On further inverstigation, you might also want to consider the fact that the form uses SearchFragment but the View uses IndexViewModel - why not just use SearchFragment as the ViewModel?

belugabob