views:

446

answers:

4

I'm playing with an ASP.NET MVC application and I've run into a bit of a problem. I am pretty new to ASP.NET MVC and just barely understand the basics to get things to work at this point.

I have a PersonModel, a PersonController, and a bunch of views that let a user add a new person, edit a person and search for people.

I am not using a DataBase in the back end. Everything I'm doing depends on an external DLL that returns "person" structures (that I turn into PersonModels).

In order to search for people, I have to provide a person-structure that acts as search criteria to a method in the external DLL. The method returns a collection of person-structures that match the search criteria. If I want to retrieve all of the people in the system I supply an empty person-structure to the method.

So, I have the "retrieve all people" function working.....but I'd like to provide an advanced search.

My Search View is bound to a class that contains 2 properties:

Public Class PersonSearchModel
  Private _searchCriteria As PersonModel
  Private _searchResults As List(Of PersonModel)
  Public Property SearchCriteria As PersonModel
    Get
      return _searchCriteria
    End Get
    Set(ByVal value As PersonModel)
      _searchCriteria = value
    End Set
  End Property
  Public Property SearchResults As List(Of PersonModel)
    Get
      return _searchResults 
    End Get
    Set(ByVal value As List(Of PersonModel))
      _searchResults = value
    End Set
  End Property
End Class

Now the Search View binds to this PersonSearchModel and I have 2 sections...a section where the user can provide search criteria and a section that displays the search results.

I am having a problem binding the PersonSearchModel.SearchCriteria to the controls used to display/gather the Person search criteria.

I cannot retrieve the search criteria.

This what I have in my view for the search criteria:

 <fieldset>
        <legend>Search Criteria</legend>
        <%
            With Model.SearchCriteria
         %>
        <div style="float:left">
        <p>
            <label for="FirstName">
                FirstName:</label>
            <%=Html.TextBox("FirstName", Html.Encode(.FirstName))%>
            <%= Html.ValidationMessage("FirstName", "*") %>
        </p>
        <p>
            <label for="LastName">
                LastName:</label>
            <%=Html.TextBox("LastName", Html.Encode(.LastName))%>
            <%= Html.ValidationMessage("LastName", "*") %>
        </p>
         <!-- More controls -->
        </div>
        <%  End With%>
    </fieldset>
     <%=Html.ActionLink("Search", "Search",Model.SearchCriteria)%>
<!-- The Search Results Section-->

The PersonModel passed into the Search method is a new/empty PersonModel Object. It does not contain the data that the user entered.

What am I doing wrong here?

****** Edit ****** I have tried changing the View to bind differently. I removed the VB "With":

 <fieldset>
        <legend>Search Criteria</legend>
        <div style="float:left">
        <p>
            <label for="FirstName">
                FirstName:</label>
            <%=Html.TextBox("FirstName", Html.Encode(.FirstName))%>
            <%= Html.ValidationMessage("FirstName", "*") %>
        </p>
        <p>
            <label for="LastName">
                LastName:</label>
            <%=Html.TextBox("LastName", Html.Encode(.LastName))%>
            <%= Html.ValidationMessage("LastName", "*") %>
        </p>
         <!-- More controls -->
        </div>
    </fieldset>
     <%=Html.ActionLink("Search", "Search",Model.SearchCriteria)%>
<!-- The Search Results Section-->

But this didn't help.

I also tried:

 <fieldset>
        <legend>Search Criteria</legend>
        <div style="float:left">
        <p>
            <label for="FirstName">
                FirstName:</label>
            <%=Html.TextBox("Model.SearchCriteria.FirstName", Html.Encode(Model.SearchCriteria.FirstName))%>
            <%= Html.ValidationMessage("FirstName", "*") %>
        </p>
        <p>
            <label for="LastName">
                LastName:</label>
            <%=Html.TextBox("Model.SearchCriteria.LastName", Html.Encode(Model.SearchCriteria.LastName))%>
            <%= Html.ValidationMessage("LastName", "*") %>
        </p>
         <!-- More controls -->
        </div>
    </fieldset>
     <%=Html.ActionLink("Search", "Search",Model.SearchCriteria)%>
<!-- The Search Results Section-->

And:

 <fieldset>
        <legend>Search Criteria</legend>
        <div style="float:left">
        <p>
            <label for="FirstName">
                FirstName:</label>
            <%=Html.TextBox("SearchCriteria.FirstName")%>
            <%= Html.ValidationMessage("FirstName", "*") %>
        </p>
        <p>
            <label for="LastName">
                LastName:</label>
            <%=Html.TextBox(".SearchCriteria.LastName")%>
            <%= Html.ValidationMessage("LastName", "*") %>
        </p>
         <!-- More controls -->
        </div>
    </fieldset>
     <%=Html.ActionLink("Search", "Search",Model.SearchCriteria)%>
<!-- The Search Results Section-->

However, I am still getting an empty/new PersonModel passed into the Search method in the controller. I've also checked the PersonSearchModel.SearchCriteria to see if maybe that contained the values entered, but this also has a new/empty PersonModel.

-Frinny

A: 

I think that you're missing the necessary prefixes on the calls to Html.TextBox and Html.ValidationMessage. I recommend not using VB's "With" keyword since it obscures the full name of the member. Both the HTML helpers and model binding (which is what gets used to pass parameters into action methods) need the full name of the property or field in order to retrieve the value.

Try this instead:

<%= Html.TextBox("SearchCriteria.FirstName", SearchCriteria.FirstName) %>
<%= Html.ValidationMessage("SearchCriteria.FirstName", "*") %>

Also, there's no need to call Html.Encode() for the value being passed into the TextBox - it gets automatically encoded anyway.

Eilon
I removed the VB.NET with and tried the code that you recommended. I also tried <%= Html.TextBox("Model.SearchCriteria.FirstName",Model.SearchCriteria.FirstName) %> But this did not help. I am still not change anything: I am still getting a new/empty PersonModel in the Search method.
Frinavale
You should be able to just use Html.TextBox("SearchCriteria.FirstName") (don't prefix the string with "Model." and you also don't need to pass in the explicit value - the TextBox helper will automatically retrieve it.
Eilon
The controller is still receiving an empty/new PersonMode. Please see the original question because I have updated it to include what else I have tried.
Frinavale
A: 

After much testing and debugging I discovered something interesting: I can retrieve the information entered by the user from the FormCollection passed into the Search Function. Originally my search function took 2 parameters. The first parameter was the PersonModel that was supposed bound to the PersonSearchModel.SearchCriteria, the second parameter was the FormCollection for the view.

I am able to create the PersonModel used for the PersonSearchModel.SearchCriteria based on the FormCollection passed into the Search function. I removed the first parameter (the PersonModel) since it was always a new/empty object.

This is my current Search method:

<AcceptVerbs(HttpVerbs.Post)> _
Function Search(ByVal collection As FormCollection) As ActionResult
        Dim searchModel As New SearchPersonsModel

        Dim personProperties() As PropertyInfo = GetType(PersonModel).GetProperties
        For Each pi As PropertyInfo In personProperties
            Dim piName As String = pi.Name
            Dim info As String = Array.Find(collection.AllKeys, Function(x) x.Compare(piName, x, true) = 0)
            If String.IsNullOrEmpty(info) = False Then
                pi.SetValue(searchModel.SearchCriteria, collection.Item(info), Nothing)
            End If
        Next
'The following code uses the searchModel.searchCriteria to search for People.
End Function

My View (if your curious) looks like:

 <% Using Html.BeginForm()%>
 <%With Model.SearchCriteria%>
  <fieldset>
    <legend>Search Criteria</legend>
      <div style="float: left">
        <p>
          <label for="FirstName">FirstName:</label>
          <%=Html.TextBox("FirstName", Html.Encode(Model.SearchCriteria.FirstName))%>
           <%=Html.ValidationMessage("Model.SearchCriteria.FirstName", "*")%>
        </p>
        <p>
            <label for="LastName">LastName:</label>
            <%=Html.TextBox("LastName", Html.Encode(Model.SearchCriteria.LastName))%>
            <%=Html.ValidationMessage("Model.SearchCriteria.LastName", "*")%>
        </p>
      <!---..... more controls .... -->
    </div>
  </fieldset>
  <%End With%>
  <input type="submit" value="Search" />

<!-- Search Results Controls -->

  <%End Using%>

This solution works but I am really not happy with it. Why do I have to recreate the PersonModel used as the search criteria? Why could I not pass this object as a parameter into the Search method?

-Frinny

Frinavale
A: 

Using reflection is pretty much what the MVC model binders are setup to do, my guess is that you weren't naming your fields correctly so when they posted back to your action they didn't map up to your parameters. Try doing something like:

Function Search(ByVal personSearchModel As PersonSearchModel, ByVal collection As FormCollection) As ActionResult

Then your fields (HTML) should be named like so:

<%= Html.TextBox("personSearchModel.SearchCriteria.FirstName", Html.Encode(Model.SearchCriteria.FirstName)) %>
Matt J
Not sure what you're getting at here. I'm a little confused by your choice of variable name...the variable name matches the type. So I'm not sure if you are trying to tell me to match the variable name to the name of the field or if you are telling me to match the object type. I'll try both ways and see what happens. Thank you for your reply.
Frinavale
Thank you very much Matt.It turns out that it is the variable name that has to match. I should probably pick up a book on this topic instead of trying things hands on first. Thanks again!
Frinavale
I'm looking at my other Views (the edit and create) and I don't specify the variable name in the field names for these views...but I'm still getting a fully populated person object passed in as a parameter to the "create" or "edit" methods. Why is this so?
Frinavale
A: 

Seems like UpdateModel() could be your friend here. MVC does not pass objects around web forms style.

Even if your Model consists of two objects, it's perfectly possible to use UpdateModel to retrieve the values for one of them. You just have to specify that object as parameter. E.g.:

Thing t = new Thing();
UpdateModel(t);

You may have to look at parameter names to allow MVC to guess properly. Also, you may have to whitelist properties for security reasons and/or to escape overly keen model validation.

Peder Skou
I tried this but it's still not working. I've looked up the UpdateModel method and it looks like it should be working...but for some reason it's still passing back a new/empty object.
Frinavale