How does MVC populate the model when data is posted back - asp.net-mvc

MVC is very clear on how data is sent to the browser. You go to a URL, it runs code to create a model, passes that typed model into a view, which then renders HTML based on the state of the model.
What I'm not finding so clear however is when a user submits a form on the page, how does MVC map that form post back to the model for use in the controller?
I'm guessing the magic happens somewhere in:
#Html.EditorFor(model => model.Title)
But I'm not understanding why....
I'm following Getting started with ASP.NET MVC 3. Which is where the code below is from for easy reference.
Controller:
public ActionResult Edit(int id)
{
Movie movie = db.Movies.Find(id);
return View(movie);
}
[HttpPost]
public ActionResult Edit(Movie movie)
{
if (ModelState.IsValid)
{
db.Entry(movie).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(movie);
}
View:
#model MvcMovie.Models.Movie
#{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Movie</legend>
#Html.HiddenFor(model => model.ID)
<div class="editor-label">
#Html.LabelFor(model => model.Title)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Title)
#Html.ValidationMessageFor(model => model.Title)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.ReleaseDate)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.ReleaseDate)
#Html.ValidationMessageFor(model => model.ReleaseDate)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Genre)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Genre)
#Html.ValidationMessageFor(model => model.Genre)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Price)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Price)
#Html.ValidationMessageFor(model => model.Price)
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
Which generates:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Edit</title>
<link href="/Content/Site.css" rel="stylesheet" type="text/css" />
<script src="/Scripts/jquery-1.5.1.min.js" type="text/javascript"></script>
<script src="/Scripts/modernizr-1.7.min.js" type="text/javascript"></script>
</head>
<body>
<div class="page">
<header>
<div id="title">
<h1>MVC Movie App</h1>
</div>
...
</header>
<section id="main">
<h2>Edit</h2>
<script src="/Scripts/jquery.validate.min.js" type="text/javascript"></script>
<script src="/Scripts/jquery.validate.unobtrusive.min.js" type="text/javascript"></script>
<form action="/Movies/Edit/4" method="post"> <fieldset>
<legend>Movie</legend>
<input data-val="true" data-val-number="The field ID must be a number."
data-val-required="The ID field is required." id="ID" name="ID" type="hidden" value="4" />
<div class="editor-label">
<label for="Title">Title</label>
</div>
<div class="editor-field">
<input class="text-box single-line" id="Title" name="Title" type="text" value="Rio Bravo" />
<span class="field-validation-valid" data-valmsg-for="Title" data-valmsg-replace="true"></span>
</div>
<div class="editor-label">
<label for="ReleaseDate">ReleaseDate</label>
</div>
<div class="editor-field">
<input class="text-box single-line" data-val="true" data-val-required="The ReleaseDate field is required."
id="ReleaseDate" name="ReleaseDate" type="text" value="4/15/1959 12:00:00 AM" />
<span class="field-validation-valid" data-valmsg-for="ReleaseDate" data-valmsg-replace="true"></span>
</div>
<div class="editor-label">
<label for="Genre">Genre</label>
</div>
<div class="editor-field">
<input class="text-box single-line" id="Genre" name="Genre" type="text" value="Western" />
<span class="field-validation-valid" data-valmsg-for="Genre" data-valmsg-replace="true"></span>
</div>
<div class="editor-label">
<label for="Price">Price</label>
</div>
<div class="editor-field">
<input class="text-box single-line" data-val="true" data-val-number="The field Price must be a number."
data-val-required="The Price field is required." id="Price" name="Price" type="text" value="9.99" />
<span class="field-validation-valid" data-valmsg-for="Price" data-valmsg-replace="true"></span>
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
</form>
<div>
Back to List
</div>
</section>
<footer>
</footer>
</div>
</body>
</html>

MVC just matches the public properties of a model against values in the FormsCollection object of the same name. If there is a match of name and type, then an instance of the model is created and the values copied into those properties.
This process is called Model Binding, and you can create custom Model Binders.
The process has little to do with EditorFor directly, although EditorFor uses templates that make sure to name the form inputs in a manner that the model binder can understand.

ASP.Net MVC is based on Convention over Configuration concept. So most of the things work like magic but underneath mechanisms are there to work in default as well as to customize if you want.
The term Model Binding is the keyword you should check out to understand this.
Check here
6-tips-for-asp-net-mvc-model-binding

MVC uses whats referred to as a Model Binder to take post back values and recreate a model.
Here is a good read: Models and Validation in ASP.NET MVC

Related

ASP .NET core razor view conditional display doesn't work as expected

I have a view where I conditionally iterate and print items: SPANs are not displayed (as expected).
<div>
#if (Model.SomeCondition)
{
#foreach (var x in Model.SomeData)
{
<span>#x.Title</span>
}
}
</div>
Now I'd like not to display the enclosing DIV, however it doesn't work: SPANs are still not displayed, but the DIV is. Why does this happen?
#if (Model.SomeCondition)
{
<div>
#foreach (var x in Model.SomeData)
{
<span>#x.Title</span>
}
</div>
}
This is the full view code:
#using Team.L
#using Microsoft.AspNetCore.Mvc.Localization
#inject IViewLocalizer Localizer
#model Team.ViewModels.DeptTaskGroupView
#{
var EventHandler = "Aventin." + ViewBag.AspAction + "(event)";
}
<form asp-controller="ModulesEx" asp-action="#ViewBag.AspAction" onsubmit="return false">
<div class="form-horizontal">
<input type="hidden" asp-for="Errors">
<div class="MessageBox">#Html.Raw(Utility.GetMessage(Model))</div>
<div class="form-group col-md-offset-2">
<label class="col-md-2"></label>
<div class="col-md-10">
<span data-valmsg-for="" class="text-danger" />
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label">#MyText.Department</label>
<div class="col-md-10">
<select asp-for="DepartmentID" class="form-control" asp-items="#Model.DepartmentList" onchange="#EventHandler"></select>
<span asp-validation-for="DepartmentID" class="text-danger" />
</div>
</div>
#if (Model.AssignedTaskGroup != null)
{
<div class="form-group">
<label class="col-md-2 control-label">#MyText.TaskGroup</label>
<div class="col-md-10">
#foreach (var x in Model.AssignedTaskGroup)
{
<input type="checkbox" name="selectedItems" value="#x.ID" #(Html.Raw(x.Assigned ? "checked=\"checked\"" : "")) />
#x.Title
}
</div>
</div>
}
<div class="form-horizontal">
<div class="form-group">
<div class="col-md-1 col-md-offset-2">
<input type="submit" value="#MyText.Save" class="btn btn-default" />
</div>
</div>
</div>
</div>
</form>
As written, it doesn't happen. I'm guessing you either didn't reload the page or there is code around your demonstrated code that renders a div that this div is contained in.

Google's new recaptcha not validating

Am I missing something? I copied this code snippet over:
Automatically render the reCAPTCHA widget
I entered my registered data-sitekey. The reCAPTCHA displays and works when I check the box, but if I don't check the box and submit my form, the reCAPTCHA doesn't stop the user submission and my controller processes the request.
#model Medicaid.WebUI.ViewModels.RequestModel
<script src='https://www.google.com/recaptcha/api.js' async defer></script>
<table class="table">
<tr>
<td>
#using (Html.BeginForm("Index", "Home", FormMethod.Post, new { id = "request-form", #class = "form-horizontal" }))
{
#Html.AntiForgeryToken()
<fieldset>
<legend>Request</legend>
<div class="form-group">
<label for="inputFirstName" class="col-lg-2 control-label">First Name</label>
<div class="col-lg-10">
<input type="text" value="#Model.FirstName" class="form-control" maxlength="50" name="FirstName" id="FirstName" placeholder="First Name" required>
</div>
</div>
<div class="form-group">
<label for="inputLastName" class="col-lg-2 control-label">Last Name</label>
<div class="col-lg-10">
<input type="text" value="#Model.LastName" class="form-control" maxlength="50" name="LastName" id="LastName" placeholder="Last Name" required>
</div>
</div>
<div class="form-group">
<label for="inputEmail" class="col-lg-2 control-label">Email</label>
<div class="col-lg-10">
<input type="email" value="#Model.Email" class="form-control" maxlength="100" name="Email" id="Email" placeholder="Email" required>
</div>
</div>
<div class="form-group">
<div class="col-lg-10 col-lg-offset-2"><br /><br />
<div class="g-recaptcha" data-sitekey="IenterMykeyHere"></div><br /><br />
<button type="submit" class="btn btn-primary">Submit Request</button>
</div>
</div>
</fieldset>
}
</td>
</tr>
</table>
<br /><br />
#Html.ValidationSummary()
I was having this issue and re-read the documentation, and worked it out as the following:
function onBegin() {
$("input[type='hidden']").val(grecaptcha.getResponse());
}
You have to add a javascript function to the "onBegin" parameter of the AjaxOptions object from the MVC html helper - that will copy the value of the widget's response to a hidden variable you need to declare inside your form, so the submit button sends it to the controller. Don't forget to add a variable with the same name to the "RequestModel" view model so that the mvc model-binding passes it and you then will be able, in the server, to know if the user has clicked the button or not.
#using (Ajax.BeginForm("NewRecaptchaVerify", "ReCaptcha", new AjaxOptions
{
HttpMethod = "Post",
OnSuccess = "onSuccess",
OnFailure = "onFailure",
OnBegin = "onBegin",
OnComplete = "onComplete"
}))
{
#Html.HiddenFor(m => m.Response)
<div class="g-recaptcha" data-sitekey="your-site-key"></div>
<input type="submit" id="btnSubmit" value="Submit" />
}
(for the record, I am now facing the problem of knowing, in the server, in cases where the widget prompted the user to enter a text input, after clicking the button - whether he/she entered the text or not. Seems to me that in the end I will have no way out other than performing a GET request, in the server, to the widget's api, as they put it in the documentation: https://www.google.com/recaptcha/api/siteverify?secret=your_secret&response=response_string&remoteip=user_ip_address

MVC File uploads collection is always null

This was working and now for reasons unknown my file collection is null
VIEW
#section termimalContent {
#using (Html.BeginForm("Add", "Terminals_Policies", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.ValidationSummary(true)
<fieldset>
<legend>
<h2>
Create new policy</h2>
</legend>
<p>
<strong>Assigning devices to Node:</strong> #Model.GroupName</p>
<div class="editor-label">
#Html.LabelFor(model => model.PolicyName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.PolicyName)
#Html.ValidationMessageFor(model => model.PolicyName)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.PolicyType)
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.SelectedItem, new SelectList(Model.PolicyType, "Value", "Text"),new {#class = "PackageDDL"})
#Html.ValidationMessageFor(model => model.SelectedItem)
</div>
#foreach (var pick in Model.PackageTypeItems)
{
<div class="editor-label">
<label for="#pick.Name">
#pick.Name:</label>
</div>
<div class="editor-field">
<input class="text-box single-line" type="file" name="#pick.Name.Trim()" id="#pick.Name.Trim()" data-val="#pick.IsRequired.ToString().ToLower()" data-val-required="Please select a file" />
#Html.ValidationMessage(pick.Name.Trim())
</div>
}
#Html.HiddenFor(model => model.GroupId)
#Html.HiddenFor(model => model.GroupName)
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
}
HTML Generated
<form action="/Terminals_Policies/Add/1" enctype="multipart/form-data" method="post"> <fieldset>
<legend>
<h2>
Create new policy</h2>
</legend>
<p>
<strong>Assigning devices to Node:</strong> Root</p>
<div class="editor-label">
<label for="PolicyName">PolicyName</label>
</div>
<div class="editor-field">
<input class="text-box single-line" data-val="true" data-val-length="Policy name cannot be longer than 50 characters." data-val-length-max="50" data-val-required="Please enter the policy name" id="PolicyName" name="PolicyName" type="text" value="" />
<span class="field-validation-valid" data-valmsg-for="PolicyName" data-valmsg-replace="true"></span>
</div>
<div class="editor-label">
<label for="PolicyType">PolicyType</label>
</div>
<div class="editor-field">
<select class="PackageDDL" data-val="true" data-val-number="The field SelectedItem must be a number." id="SelectedItem" name="SelectedItem"><option value="1">IT application</option>
<option value="3"> definition</option>
<option value="4">definition</option>
<option value="5">project</option>
<option value="6">relay schedules</option>
<option value="7">table data</option>
<option value="9">transfer definition</option>
<option value="10">firmware update request</option>
<option value="11"> firmware update request</option>
<option value="12">dat</option>
<option value="15"> firmware</option>
<option value="16"> hex</option>
<option value="17">project</option>
<option value="18">firmware</option>
</select>
<span class="field-validation-valid" data-valmsg-for="SelectedItem" data-valmsg-replace="true"></span>
</div>
<div class="editor-label">
<label for="IT application file ">
IT application file :</label>
</div>
<div class="editor-field">
<input class="text-box single-line" type="file" name="IT application file" id="IT application file" data-val="true" data-val-required="Please select a file" />
<span class="field-validation-valid" data-valmsg-for="IT application file" data-valmsg-replace="true"></span>
</div>
<input data-val="true" data-val-number="The field GroupId must be a number." data-val-required="The GroupId field is required." id="GroupId" name="GroupId" type="hidden" value="1" />
<input id="GroupName" name="GroupName" type="hidden" value="Root" />
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
</form>
Controller:
[HttpGet]
public ActionResult Add(int id)
{
_polAdd = CreatePolicyAddModel(id);
return View(_polAdd);
}
[HttpPost]
public ActionResult Add(IEnumerable<HttpPostedFileBase> files, vmPoliciesAdd model)
{
_policyLogic.AddPolicyFile(files,model.PolicyName,(int)model.SelectedItem, "FILE");
return View();
}
On the post action of controller the colelction is empty, can anyone see an obvious mistake
Naming issue:
<input class="text-box single-line" type="file" name="IT application file" id="IT application file" data-val="true" data-val-required="Please select a file" />
Should of course be:
<input class="text-box single-line" type="file" name="files" id="IT application file" data-val="true" data-val-required="Please select a file" />
So fix your Razor code and make sure you have applied correct name to your file input if you expect the model binder to be able to bind to an action argument called files:
<input class="text-box single-line" type="file" name="files" id="#pick.Name.Trim()" data-val="#pick.IsRequired.ToString().ToLower()" data-val-required="Please select a file" />
Ah an by the way id's cannot contain spaces. So you've got a broken HTML. The following seems wrong: id="#pick.Name.Trim()" as well.

MVC form multipart/form-data isnt rendering correctly

here is my view. I am attempting to have a file upload control.
#section termimalContent {
#using (Html.BeginForm("Add", "Terminals_Policies", "multipart/form-data"))
{
#Html.ValidationSummary(true)
<fieldset>
<legend>
<h2>
Create new policy</h2>
</legend>
<p>
<strong>Assigning devices to Node:</strong> #Model.GroupName</p>
<div class="editor-label">
#Html.LabelFor(model => model.PolicyName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.PolicyName)
#Html.ValidationMessageFor(model => model.PolicyName)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.PolicyType)
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.SelectedItems, new SelectList(Model.PolicyType, "Value", "Text"))
#Html.ValidationMessageFor(model => model.SelectedItems)
</div>
<div class="editor-label">
<label for="file1">
Filename:</label>
</div>
<div class="editor-field">
<input type="file" name="files" id="file1" />
</div>
<div class="editor-label">
<label for="file2">
Filename:</label>
</div>
<div class="editor-field">
<input type="file" name="files" id="file2" />
</div>
#Html.HiddenFor(model => model.GroupId)
#Html.HiddenFor(model => model.GroupName)
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
}
but the html is being rendered as:
<form action="/Terminals_Policies/Add/1?Length=19" id="form0" method="post">
Anyone know what i have done wrong
Try:
#using (Html.BeginForm("Add", "Terminals_Policies", FormMethod.Post,new{enctype="multipart/form-data"}))
As it should be in the format
Html.BeginForm(action, controller, FormMethod.Post, new { enctype="multipart/form-data"})

ASP.NET MVC [Required] does not trigger

As the title says my Required field does not trigger.
This is my ViewModel class its name is SignUpForm.cs
public class SignUpForm
{
[Required]
[StringLength(100,ErrorMessage="You must enter a your firstname!",
MinimumLength = 1)]
public string FirstName { get; set; }
[Required]
[StringLength(100, ErrorMessage = "You must enter a your surname!",
MinimumLength = 1)]
public string SurName { get; set; }
}
This is my Razor view! Name of view is Step2.cshtml
#model MvcApplication2.Models.ViewModel.SignUpForm
#{
ViewBag.Title = "Step2";
}
<div id="content-header">
<h1 class="title">Complete your registration</h1>
</div>
<div id="content-area">
<h4>Fill in the form below to complete your registration
</h4>
#using(Html.BeginForm("testar","Interested", FormMethod.Post))
{
<h3>Personal information</h3>
<p class="app-label">Surname<span class="requiredField">*</span></p>
<div class="editor-label">
#Html.EditorFor(model => model.FirstName)
#Html.ValidationMessageFor(m => m.FirstName)
</div>
<p class="app-label">Surname<span class="requiredField">*</span></p>
<div class="editor-label">
#Html.EditorFor(model => model.SurName)
#Html.ValidationMessageFor(m => m.SurName)
</div>
}
Source as Requested
<script src="/Scripts/jquery-1.7.1.min.js" type="text/javascript"></script>
<script src="/Scripts/modernizr-2.5.3.js" type="text/javascript"></script>
<link href="/Content/remigium.css" rel="stylesheet" />
<h3>Personal information</h3>
First name<span class="requiredField">*</span>
<div class="editor-label">
<input class="text-box single-line" data-val="true" data-val-length="You must enter a your firstname!" data-val-length-max="100" data-val-length-min="1" data-val-required="The FirstName field is required." id="FirstName" name="FirstName" type="text" value="" />
<span class="field-validation-valid" data-valmsg-for="FirstName" data-valmsg-replace="true"></span>
</div>
<p class="app-label">Surname<span class="requiredField">*</span></p>
<div class="editor-label">
<input class="text-box single-line" data-val="true" data-val-length="You must enter a your surname!" data-val-length-max="100" data-val-length-min="1" data-val-required="The SurName field is required." id="SurName" name="SurName" type="text" value="" />
<span class="field-validation-valid" data-valmsg-for="SurName" data-valmsg-replace="true"></span>
</div>
I was missing these script references, I added them and all was cool!
<script src="/Scripts/jquery.validate.min.js" type="text/javascript"></script>
<script src="/Scripts/jquery.validate.unobtrusive.min.js" type="text/javascript"></script>

Resources