ASP.NET MVC: Binding to multiple models - asp.net-mvc

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

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)) %>

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.

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

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.

Related

Multiply select html5 form sends only one result if selected several options

I am trying to use a simple html5 form in Ruby on Rails. My fieldset code:
<fieldset class="form-group">
<legend><%= question.title %></legend>
<% question.answers.each do |answer| %>
<div class="form-check">
<label class="form-check-label">
<input type="checkbox" class="form-check-input" name="question-<%= question.id %>" value="answer-<%= answer.id %>">
<%= answer.title %>
</label>
</div>
<% end %>
</fieldset>
is displayed correct, and when I select one or several options and submit, I have only one parameter in the request as it were radiobutton form although I selected several variants:
{"question-162"=>"answer-467"}
How to make this form working correct and send multiply parameters in the submit request?
Change the name attribute of the checkbox to question-<%= question.id %>[] (adding trailing []), and you will get request parameters like below:
{"question-1"=>["answer-1", "answer-3"]}

Handling form with repeatable inputs

Before i start with my question, i will try to explain on what i'm trying to do.
I'm creating a form using Grails, and in the form, there are 2 sections on it, the primary input and the repeatable input. See below for ideas on how the structure looks like.
<div class="primary">
<div class="${hasErrors(bean: dataInputInstance, field: 'mainInput', 'error')}">
<label class="control-label">
<g:message code="dataInput.mainInput.label" default="Main Input"/>
</label>
<div class="controls">
<g:textField name="mainInput" value="${dataInputInstance?.mainInput}" />
</div>
</div>
</div>
<div class="repeatable">
<div class="span6">
<div class="${hasErrors(bean: dataInputInstance, field: 'inputA', 'error')}">
<label class="control-label">
<g:message code="dataInput.InputA.label" default="Input A"/>
</label>
<div class="controls">
<g:textField id='inputA_1' name="inputA" value="${dataInputInstance?.inputA}" />
</div>
</div>
</div>
<div class="span6">
<div class="${hasErrors(bean: dataInputInstance, field: 'inputB', 'error')}">
<label class="control-label">
<g:message code="dataInput.InputB.label" default="Input B"/>
</label>
<div class="controls">
<g:textField id='inputB_1' name="inputB" value="${dataInputInstance?.inputB}" />
</div>
</div>
</div>
The repeatable part of the input can be repeated from 1-50 times. The number of instances generated and saved in the database depends on how many repeated form created. Each instance is a combination of primary input + repeated input.
If say, i created 5 repeated sections, then when saving the form, the params will look like below
params:[mainInput: valMain, inputA:[valA1, valA2, valA3, valA4, valA5], inputB :[valB1, valB2, valB3, valB4, valB5]]
In the controller, i used the following when i try to form the instances and save it to the database
def save() {
def length = inputA.size()
def i
if (i = 0; i < length; i++){
DataInput dataInputInstance = new DataInput()
dataInputInstance.mainInput = params.mainInput.trim()
dataInputInstance.inputA = params.inputA[i].trim()
dataInputInstance.inputB = params.inputB[i].trim()
dataInputInstance.save(flush:true)
}
}
So far i managed to get that part correct.
Now here comes the problem.
Inside the save controller, i plan to include some data checking before i save it to the database. If checking is success, then it will proceed to save the data. If its not, then it will return back to the form page, together with the data that user input in the form. The code for that is as follow
if (insert checking condition here){
render(view: "create", model: [dataInputInstance: dataInputInstance]) //If fail
}
If the amount of repeatable part is 1, then the form will render along with the data that the user input. However if the repeatable part is more than 1, then it will have trouble to pass the data back to the form.
I tried to pass back the params back to the form because i thought since the params contain arrays on it, so it would be logical to pass back the params to the form, like below
if (insert checking condition here){
render(view: "create", model: [dataInputInstance: params]) //If fail
}
But still not working.
So anyone have any idea on this?
I'm looking at this block where you are setting dataInputInstance.inputA to be a single value and not the entire list object. Is this what you intended?
dataInputInstance.inputA = params.inputA[i].trim()
dataInputInstance.inputB = params.inputB[i].trim()
Could you post your view GSP code if this is not the cause? Need to see how are you reading the values from your view on validation failure.

Problem with error about model being passed into partial view

So I am trying to render a partial view into my page and I am getting the below error:
ERROR
The model item passed into the dictionary is of type
'System.Collections.Generic.List`1[GettingOrganized.Models.Todo]', but this
dictionary requires a model item of type 'GettingOrganized.Models.Todo'.
I don't see what is wrong with the partial view or controller.
PARTIAL VIEW
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<GettingOrganized.Models.Todo>" %>
<% using (Html.BeginForm("Create", "Todo", FormMethod.Post, new {id="CreateTodo"})) {%>
<fieldset>
<legend>Fields</legend>
<div class="editor-label">
<%= Html.LabelFor(model => model.Title) %>
</div>
<div class="editor-field">
<%= Html.TextBoxFor(model => model.Title) %>
<%= Html.ValidationMessageFor(model => model.Title) %>
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
<% } %>
Controller Index View that Renders the Partial View:
<% Html.RenderPartial("CreateElements"); %>
Any thoughts? I would like to keep it as close to this setup since is strong typed.
UPDATE
So to provide a few more details, now that problem is becoming more clear. I am rendering the view on the page in a div and hiding it the user clicks a certain link. Then I want to show the div. This same partial is used in a "Create" view where you can create a "Todo". But I am now wanting to use the partial in the Index view which shows a list of the model "Todo".
The model passed in, in the "Index" view:
Inherits="System.Web.Mvc.ViewPage<IEnumerable<GettingOrganized.Models.Todo>>" %>
So if I don't want to loop through a foreach loop, and just want to show one instance of the model, who do I do that?
Also I can use the following view for the partial and it will work which takes away the strongly typed to the model:
WORKING PARTIAL
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<% using (Html.BeginForm("Create", "Todo",
FormMethod.Post, new { id="CreateTodo"})) {%>
<fieldset>
<legend>Fields</legend>
<p>
<label for="Title">Title:</label>
<%=Html.TextBox("Title")%>
<%=Html.ValidationMessage("Title", "*")%>
</p>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
<p>
<input type="submit" value="Create" />
</p>
<% } %>
Possible Answer
However, I believe I might have found an answer.
<% Html.RenderPartial("CreateElements", new Todo()); %>
Is this a proper way to handle this?
However, I believe I might have found an answer.
<% Html.RenderPartial("CreateElements", new Todo()); %>
It looks like you need to pass the model to the partial view - as in:
<% Html.RenderPartial("CreateElements", myModel); %>
I would look into how you're passing in your model into the RenderPartial:
<% Html.RenderPartial("CreateElements", model); %>
And make sure that model is of type GettingOrganized.Models.Todo.
Since you're not passing a model into your RenderPartial call, MVC is effectively trying to create one for you using the ViewDataDictionary and model from the parent page.
It looks like the parent page has a model type which is a List of ToDo items, so I guess you can just call your RenderPartial method inside of a loop; something along the lines of:
<% foreach (GettingOrganized.Models.Todo todoItem in Model) {
Html.RenderPartial("CreateElements", todoItem);
} %>

asp mvc user controls

I want to write user control to sending email.
I write that control:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<form action="" method="post">
<div class="box">
<div class="box-header">
Rejestracja</div>
<div class="box-content">
<div>
Imię
</div>
<div>
<input name="firstname" type="text" />
</div>
<div>
Nazwisko
</div>
<div>
<input name="lastname" type="text" />
</div>
<div>
Email
</div>
<div>
<input name="email" type="text" />
</div>
<div>
Ulica nr domu mieszkania
</div>
<div>
<input name="street" type="text" />
</div>
</div>
<div class="box-info">
Wypełnij formularz rejestracyjny i dołącz do klubu Oriflame.
</div>
</div>
<div style="clear: both;">
</div>
</form>
And i put this control in masterpage:
<% Html.RenderPartial("Kontakt"); %>
That control named :kontakt.aspx" and it is in shared folder
My question is where i must write code with sending email. What action i myst set in controls form.
This control was be on all sites.
Regards
The form needs to post to a URL that is setup to route to a controller action. That could be the current page's Url or a different Url.
In your controller you want a method that accepts the form fields. This could be a FormCollection object or a strongly typed model who's properties map to the form names.
[HttpPost]
public ActionResult Foo(FormCollection form)
{
.. use the form collection to construct your email ...
}
If you're using a strongly typed view, rather than building the HTML inputs yourself you could do:
<%= Html.TextBoxFor(x => x.FirstName) %>
And in your controller action you can use the model rather than the FormCollection:
[HttpPost]
public ActionResult Foo(KontaktModel details)
{
.. use the details object to construct your email ...
}
I suggest taking a look through the tutorials at http://asp.net/mvc as well as doing the NerdDinner tutorial.
You have to create some sort of Controller that will receive form data. And you can send those emails from the server (from controller or whatever you chose to send it).
Write your email creation and sending code in a controller method. You'd then call it from this Kontakt partial view like this:
<% using (Html.BeginForm("SendMail", "Mail")) { %>
Where SendMail is the method, and Mail is the name of the controller.
public ActionResult SendMail()
{
//build your mail objects and send as needed.
return View();
}

MVC more than one form to submit

I have a standard Edit form view within MVC, and I need to place a user control, in this case Create inside the BeginForm, like below:
When the issue is that the Create ascx when the form is submitted does not fire it's action in the controller, what am I doing wrong?
<% using (Html.BeginForm())
{%>
<fieldset>
<legend>Tax</legend>
<p>
<label for="Name">
Name:</label>
<%= Html.TextBox("Name", Model.Tax.Name) %>
<%= Html.ValidationMessage("Name", "*") %>
</p>
<p>
<% Html.RenderAction("Create", "Mileage"); %>
</p>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
<% } %>
Here is the Create.ascx
<% using (Html.BeginForm())
{%>
<fieldset>
<p>
<label for="Distance">
Distance:</label>
<%= Html.TextBox("Distance", Model.Mileage.Distance)%>
<%= Html.ValidationMessage("Distance", "*") %><span class="field-validation-error"
id="field-validation-error-distance">*</span>
</p>
</fieldset>
<% } %>
You have nested forms in your resulting HTML. This will not work as expected. Remove the form from the inner view. The inner view will then be incomplete, so if you were using it as a stand-alone, you should make it shared, and create another view, which will just open the form, render the inner view, and close the form.
As a margin note: you are not using the default binder. You can if you want to, it will work even with nested objects (Html.TextBox("Mileage.Distance"), for example).
Nested form are not supported in HTML. See here: The FORM element
Every form must be enclosed within a FORM element. There can be several forms in a single document, but the FORM element can't be nested.
Either remove the form from your partial view and let the container view provide it, or remove the form from the view and add it to the partial thus making it independent of the parent.

Resources