How to correctly iterate elements? - html

I'm iterating my Model.Payments collection (which is an public IEnumerable<Payments> Payments { get; set; }):
#using (Html.BeginForm())
{
<div class="payments">
#foreach (var payment in Model.Payments)
{
Html.RenderPartial("_Payment", payment);
}
</div>
<input type="submit" value="Aggiorna" />
}
And this is my _Payment partial:
#model MyProject.Models.Payments
<div class="payment-row">
<span>ActivityID:</span> #Html.TextBoxFor(x => x.ActivityID)
<span>PaymentType:</span> #Html.TextBoxFor(x => x.PaymentType)
<span>Amount:</span> #Html.TextBoxFor(x => x.Amount)
</div>
But it doesn't create a proper HTML. Name/ID are the same, so once I postback, I can't retrieve data.
Where am I wrong?

The correct approach is to use the EditorFor() method, which will correctly prefix your inputs with the collection indexer
Rename you partial view to Payments.cshtml (to match the name of the class), and move it to the /Views/Shared/EditorTemplates (or /Views/YourControllerName/EditorTempatesFolder)
Then you view becomes
#using (Html.BeginForm())
{
<div class="payments">
#Html.EditorFor(m => m.Payments)
</div>
<input type="submit" value="Aggiorna" />
}
The EditorFor() method generates the correct html for each item in the collection, and will generate inputs such as <input name="Payments[0].ActivityID" ... /> rather than <input name="ActivityID" ... /> which you are currently generating.
As a side note, you should consider using #Html.LabelFor() to generate a <label> associated with your form controls, rather than using a <span>.

Related

How to create single page search form with ajax in MVC Razor

I'm a beginner in the ways of MVC / Razor... Basically I have 2 pages just for showing a search result from an API that I'm consuming with Json.
Index.cshtml: (Just a form)
#model ConsultaInterna.Models.SearchApi
<div class="jumbotron">
<h2>Consulta Interna</h2>
Placa
#using (Html.BeginForm("Search", "Home"))
{
<input asp-for="Model.Name" class="form-control" name="name" id="txtName" />
<input class="form-control" type="submit" id="ok" />
}
and Search.cshtml (Which also have the form. I want to leave just this page, right now I can't because if I load directly from Search.cshtml it gives me an error like
"Unexpected character encountered while parsing value: <. Path '', line 0, position 0. " (Obviously because I'm trying to load labels with a null search)
this is Search.cshtml
#model ConsultaInterna.Models.SearchApi
<div id="loader" class="spinner" style="display:none">OIE</div>
<div class="header formPesquisa">
#using (Ajax.BeginForm("Search", new AjaxOptions()
{
HttpMethod = "POST",
LoadingElementId = "loader",
UpdateTargetId = "resultado"
}))
{
<input placeholder="Placa" asp-for="Model.Name" name="name" id="txtName" width="30" />
<input class="pesquisa" type="submit" id="enviar" value="Ok" />
}
<div class="panel">Placa #Model.Name</div>
<div class="panel">Last Name / Model.LastName</div>
ALSO Important, the Ajax form isn't working properly... the loader won't show up.
You need to move your loader div into the Ajax.BeginForm.
You need also need to add the resultado div to display your result.
#using (Ajax.BeginForm("Search", "Home", new AjaxOptions()
{
HttpMethod = "POST",
LoadingElementId = "loader",
UpdateTargetId = "resultado"
}))
{
<div id="loader" class="spinner" style="display:none">OIE</div>
<input placeholder="Placa" asp-for="Model.Placa" class="pesquisa placa" maxlength="7" name="placa" id="txtPlaca" width="30" />
<input placeholder="CPF" asp-for="Model.CPF" class="pesquisa cpf" maxlength="11" name="cpf" id="txtCPF" />
<input class="pesquisa" type="submit" id="enviar" value="Enviar" />
<div id="resultado"></div>
}
<div class="panel">Placa #Model.Name</div>
<div class="panel">Last Name / Model.LastName</div>
</div>
You also need to make sure you have added the relevant reference to the specific unobtrusive ajax library. As an example:
BundleConfig
bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
"~/Scripts/jquery.unobtrusive-ajax.min.js",
"~/Scripts/jquery.validate*"));
_layout.cshtml
#Scripts.Render("~/bundles/jqueryval")
Example controller action
[HttpPost]
public ActionResult Search(string placa, string cpf)
{
return new JsonResult { Data = "found", JsonRequestBehavior = JsonRequestBehavior.AllowGet };
}

Binding in ASPNET MVC CORE works with #for but not to #foreach [duplicate]

In my view model, I have a list of objects. I iterate these objects, and create controls for each of them. In the situation below, I want to show people a textbox and a button for each object. When the user clicks the button, a post is made, and I can save the data in my controller.
In the UI, a user can change the form they want, and click save.
My problem is the model is null when it's posted to the controller..
My Razor code:
using (Html.BeginForm())
{
foreach (var contributor in Model.Contributor)
{
#Html.HiddenFor(model => contributor.Id)
<div class="formrow">
#Html.ValidationSummary(true)
</div>
<h2>#Html.TextRaw("AuthorInfo", "Author")</h2>
<div class="formrow">
#Html.EditorFor(model => contributor.FirstName)
<div class="formvalidation">
#Html.ValidationMessageFor(model => contributor.FirstName)
</div>
</div>
<div class="formrow right">
<input type="hidden" name="formsubmitted" value="true" />
<input type="submit" class="button" value="#Html.Text("ButtonText", "Save")" />
</div>
}
}
My view model code
public class ProfileModel
{
public string Message { get; set; }
public List<PublisherModel> Publisher { get; set; }
public List<ContributorModel> Contributor { get; set; }
public ContributorModel NewContributor { get; set; }
}
My controller code
[HttpPost]
public ActionResult Mine(ProfileModel model, string newuser)
{
//
}
How to fix it?
I guess I have to expand my view model with a way to store the changes in some way. But I really can't see how.
Right now all the properties in the ProfileModel is null when it reaches the controller.
Any ideas?
Basically the problem is that default model binder is unable to bind collection items correctly in foreach loop. In other words, it names the element incorrectly and that's why the collection displays as null in parameters.
I'm sure there are all kinds of different workarounds, helpers and stuff but I'm not familiar with those, so I just use for loop instead of foreach, this way the elements are named correctly.
Try this:
#for (int i = 0; i < Model.Contributor.Count(); i++)
{
#Html.HiddenFor(model => Model.Contributor[i].Id)
<div class="formrow">
#Html.ValidationSummary(true)
</div>
<h2>#Html.TextRaw("AuthorInfo", "Author")</h2>
<div class="formrow">
#Html.EditorFor(model => Model.Contributor[i].FirstName)
<div class="formvalidation">
#Html.ValidationMessageFor(model => Model.Contributor[i].FirstName)
</div>
</div>
<div class="formrow right">
<input type="hidden" name="formsubmitted" value="true" />
<input type="submit" class="button" value="#Html.Text("ButtonText", "Save")" />
</div>
}
I suggest you to use a debugging tool to see if elements have correct name attribute, in your case they should look like Contributor[0].Id, Contributor[0].FirstName etc.
You can use PartialView for Contributor object.
PartialView:
#model Contributor
using (Html.BeginForm("ContributorUpdate", "YourController"))
{
#Html.HiddenFor(model => Model.Id)
<div class="formrow">
#Html.ValidationSummary(true)
</div>
<h2>#Html.TextRaw("AuthorInfo", "Author")</h2>
<div class="formrow">
#Html.EditorFor(model => Model.FirstName)
<div class="formvalidation">
#Html.ValidationMessageFor(model => Model.FirstName)
</div>
</div>
<div class="formrow right">
<input type="hidden" name="formsubmitted" value="true" />
<input type="submit" class="button" value="#Html.Text("ButtonText", "Save")" />
</div>
}
View will be:
#foreach (var contributor in Model.Contributor)
{
#{Html.RenderPartial("Conributor", contributor);}
}
And controller code:
[HttpPost]
public ActionResult Mine(Conributor conributor, string newuser)
{
//
}

Html.BeginForm inside of Html.BeginForm MVC3

I have a main view that renders two partial views. The main view encompasses both of the partial views within a form. Each of the partial views also contain forms. All 3 views share the same viewmodel. What I want to do is encapsulate the data from all views with the main view, and run specific Controller action results with the partial views.
I want to know if this is even possible. When debugging I see that my content always posts to the HTTPPost of the Main views form. I have submit buttons for each of the forms accordingly. Sorry for the code post, its coming out all split up.
Main View:
#using (Html.BeginForm("Main", "Registration", FormMethod.Post,
new { #class="mainform" }))
{
<form>
<fieldset>
<div id ="option1" class="conglomerate">
#Html.Partial("_GetBusiness")
</div>
<div id ="option2" class="dealership">
#Html.Partial("_GetLocation")
</div>
<button type="submit" value="Create" class="buttonBlue" id="">
<span>Create a new dealer</span>
</button>
</fieldset>
</form>
Partial 1
#using (Html.BeginForm("CreateBusiness", "Business", FormMethod.Post,
new { #class="buisinessform" }))
{
<form>
<div class="editor-field">
#Html.DropDownListFor(m =>m.BusinessId, new SelectList(Model.Businesses,
"BusinessId", "BusinessName"), "")
</div>
<label>Your company not listed? Register yours below:</label>
<div class="editor-label">
#Html.LabelFor(model => model.BusinessName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.BusinessName)
#Html.ValidationMessageFor(model => model.BusinessName)
</div>
<button type="Button" value="" class="buttonBlue"id="TestSubmit">
<span>Add Dealer</span>
</button>
<div class ="confirm">
<button type="submit" value="Create" class="buttonBlue" id="">
<span>Click here to confirm dealer addition</span>
</button>
</div>
</form>
}
As Dave mentions, It is not valid HTML to nest forms. It's not just HTML5, but any version of HTML.
It might work in some browsers, in certain circumstances, but it is never valid. And you can never depend on what will happen even if it does seem to work. Your best course of action is to use multiple non-nested forms on a page.
Can you explain why you think you need nested forms?
No, you cannot have nested forms. Sorry.
See HTML5 guidelines
"Flow content, but with no form element descendants"

How to display the values from one partial view control(Search) to a web grid partial view control?

In my view i have two partial views (Search,webgrid). When i click the update button in the search , the values filtered are not binding in the webgrid ? How can i do this?
You could try something like this. Hope I understood your question correctly.
search partial:
...
#using(Ajax.BeginForm("Search", new AjaxOptions(){UpdateTargetId = "SearchResults",
HttpMethod = "post" InsertionMode=InsertionMode.Replace})){
<input id="searchString" type="text" value="Search for this ..." />
<input type="submit" value="Search" />
}
...
controller:
[HttpPost]
public PartialViewResult Search(string searchString){
IList<Results> results = _service.Search(searchString);
return new PartialView("Webgrid", results)
}
webgrid partial:
#Model IList<Result>
<div id="SearchResults">
// Display Results
</div>
Didn't compile the code. Hope it is almost compileable.

How to Get Model Data from Partial View?

I am creating a site in which I utilize partial views to display various bits of data about a single Model. Here is a bit of the HTML. (Note, all of these are contained within a single form and the Index page that these partials are rendered in is strongly typed to the main model. The main model contains various lists of data.)
<div id="tab1"><% Html.RenderPartial("Tab1", Model); %></div>
<div id="tab2"><% Html.RenderPartial("Tab2", Model.AnItemList1.FirstOrDefault<AnItemList1>()); %></div>
<div id="tab3"><% Html.RenderPartial("Tab3", Model.AnItemList2.FirstOrDefault()); %></div>
Here is ONE of the partial views headers (for 'tab2'):
<%# Language="C#" Inherits="System.Web.Mvc.ViewUserControl<AnItem1>" %>
The pages display correctly. The issue is that, when I enter data into the various parts of the partial pages and then submit the entire form (via POST), the data is not making it back to my data store (MSSQL) - but this only happens for any of the list items (that are contained within the Model). The first partial page does properly have its data set within the data store.
What am I doing wrong here? Should I only be passing the model to Html.RenderPartial and then get the specific model I need on the partial page? Should I pass the entire list and then get the first (right now, I only care about the first item in the list - that will EVENTUALLY change, but not any time soon).
Suggestions or thoughts appreciated.
Update: Here is how I accessing the properties on the partial views.
<div class="data-group">
<%: Html.CheckBoxFor(model => model.Property1) %>
<%: Html.LabelFor(model => model.Property1) %>
</div>
Update 2: Per request...
Controller Action (ScenarioController):
public ActionResult Index(int id = 0)
{
if (id == 0)
{
SavedScenario scenario = new SavedScenario();
scenario.AnItemList1.Add(new AnItem1());
scenario.AnItemList2.Add(new AnItem2());
return View("Index", scenario);
}
else
{
SavedScenario scenario = repository.GetScenario(id);
if (scenario == null)
return View("NotFound");
else
return View("Index", scenario);
}
}
[HttpPost]
public ActionResult Index(SavedScenario scenario)
{
if (ModelState.IsValid && TryUpdateModel(scenario, "SaveScenario"))
{
repository.Add(scenario);
repository.Save();
}
return View(scenario);
}
Rendered HTML (I can only include parts of it - this is a small sample of what is in the form):
<form action="/Scenario" id="form0" method="post">
<!-- This is the one that works - the basic Scenario. Top level. -->
<fieldset>
<legend>Scenario Information</legend>
<div class="data-group">
<div class="editor-label">
<label for="ScenarioName">Scenario Name</label>
</div>
<div class="option1">
<input class="wide" id="ScenarioName" name="ScenarioName" type="text" value="" />
</div>
<div class="validation">
<div><span class="field-validation-valid" id="ScenarioName_validationMessage"></span></div>
</div>
</div>
</fieldset>
<!-- This does not work or get submitted (as far as I can tell). -->
<div id="tab2">
<fieldset>
<legend>Tab2</legend>
<div class="data-group">
<input id="Property1" name="Property1" type="checkbox" value="true" /><input name="Property1" type="hidden" value="false" />
<label for="Property1" />
</div>
</div>
</fieldset>
</form>
My apologies for having to keep this so generic.
Hard to guess from this much code. However you should make sure that all properties of your models have the same prefix when they are posted back to the server
Edit: form field names should match property names of your model to correctly bind all values. You have two fields with the same name that you can bind in following way
[HttpPost]
public ActionResult Index(SavedScenario scenario, List<bool> Property1)
{
// here you can do with values coming in property1
if (ModelState.IsValid && TryUpdateModel(scenario, "SaveScenario"))
{
repository.Add(scenario);
repository.Save();
}
return View(scenario);
}
It might be issue with naming the fields on your partial forms. Try naming the fields on your partial views by prefixing it with the name of the Model passed into it...like 'AnItemList1.name' instead of just 'name'..I am just guessing here though...but that's what I did sometimes to fix the problem when I was getting values as null..