MVC more than one form to submit - asp.net-mvc

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.

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"]}

Postback problem with partial view

I have the following view
<form action="/Questionnaire/Submit" method="post">
<%:"UserName : "%>
<%=ViewData["UserName"]%>
<%=Html.TextBox("test",ViewData["tt"])%>
<p />
<%:"Phone Number :"%>
<%=ViewData["PhoneNumber"]%>
<p />
<%
foreach (var q in Model)
{
Html.RenderPartial("Question", q);
}
%>
<input type="submit" name="submit" value="submit" />
</form>
That render the following partial view
<% using (Html.BeginForm("Submit", "Questionnaire", FormMethod.Post))
{%>
<%:"Question Number "%>
<%=Model.QuestionNumber%>
<%:" "%>
<%=Model.Body%>
<%:" "%>
<%
foreach (var option in Model.Options)
{%>
<p/>
<%=Html.RadioButton(option.QuestionId.ToString(), (option.IsSelected) )%> <%= option.OptionBody%>
<%
}
}
%>
The problem is , The form dosn't submit , and when I remove the "foreach" statment from the master view, It works
My Objective, is to have the updated model ( from the master view and partial view ) to save it later on in DB
Your master view contains a form and then your partial view also creates a form each time it renders, so you will be left with a page containing multiple forms but only one 'Submit' button.
I'm not 100% certain what you need to do, but I'd try and remove the 'BeginForm' call from the partial and see if that fixes the problem.

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

Html.BeginForm in partial for a different controller

I have the following code:
<% using (Html.BeginForm("AddComment", "Comments", FormMethod.Post)) { %>
<div id="New_Comment">
<textarea name="newComment" id="newComment">Add comments</textarea>
<input type="submit" value="Add" />
<div><span class="text_grey">Attach:</span>File Link</div>
</div>
<%} %>
This is in a partial rendered by the MyPage controller. For some reason the action on the form comes out blank, if I reference a method on the MyPage controller it works fine what I want to do is point to a different controller with my form.
To solve this issue I simple added in an area route value like so:
new { area = "" }
With the empty string directing the route to the default area.
1) Is your "Comments" action marked as being a POST action?
2) Also
Try just doing:
<% Html.BeginForm("AddComment", "Comments"); %>
// Html and script
<% Html.EndForm(); %>
I know that there shouldn't be difference between what you have and what I suggest, but it's worth a try.

ASP.NET MVC: Binding to multiple models

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.

Resources