How to validate the HTML controls with data annotations in MVC? - asp.net-mvc

In .Net MVC. I have a html control. Inorder to bind it with the model property I am using name attribute. How do we get the validations(using data annotation) provided in the model class property into the html control?
In Cshtml
#using (Html.BeginForm("ClaimWarranty", "WarrentyClaim", FormMethod.Post, new{ enctype = "multipart/form-data" }))
{
<div class="form-group row">
<label for="" class="col-md-2 col-form-label input-label">Email Address:</label>
<div class="col-md-8">
<input type="text" name="Emailaddress" class="form-control input-style" placeholder="example#company.com">
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" onclick="ValidateFileSize()" class="btn btn-default" />
</div>
</div>
}
//The model class is below;
public class ClaimWarranty
{
[Required(ErrorMessage = "Email ID is Required")]
[DataType(DataType.EmailAddress)]
[MaxLength(50)]
[RegularExpression(#"[a-z0-9._%+-]+#[a-z0-9.-]+\.[a-z]{2,4}", ErrorMessage = "Incorrect Email Format")]
public string Emailaddress { get; set; }
}
I am using the name property to bind the text box to the model property .
<input type="text" name="Emailaddress" class="form-control input-style" placeholder="example#company.com">
How do I get the validations in the html control ,provided in the model class (using the data annotations) as shown above without using jquery validations or razor code?

In View
#model Demo.Models.Student
#using (Html.BeginForm("SaveStudent", "Student", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<div class="form-group">
#Html.LabelFor(model =>model.Name, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model =>model.Name, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model =>model.Name, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btnbtn-primary" />
</div>
</div>
}
In Model
public class Student
{
[Required(ErrorMessage = "Please enter name"), MaxLength(30)]
public string Name { get; set; }
}
By default, ASP.Net MVC framework executes validation logic during model binding. In Controller side, we need to check
if (ModelState.IsValid)
{
}
OR We can also check Individual validation, as shown below:
if (ModelState.IsValidField("LastName") == false)

if(!ModelState.IsValid)
{
// you can get the error information from model state, convert it into list
var validationErrors = ModelState.Values.Where(E => E.Errors.Count > 0)
.SelectMany(E => E.Errors)
.Select(E => E.ErrorMessage)
.ToList();
// now you have got the list of errors, you will need to pass it to view
// you can use view model, viewbag etc
ViewBag.ErrorList = validationErrors;
return View();
}
else
{
// perform your business operation, save the data to database
return View();
}
On View Page -
you have to add check for validation error list
if(ViewBag.ErrorList != null)
{
foreach(var errorMessage in ViewBag.ErrorList)
{
// here you can display the error message and format in html
}
}
Way you can display error on view page
1. #Html.ValidationSummary() - It will display summary of the validation errors
2. #Html.ValidationMessageFor(x => x.Emailaddress) - It will display error message
for specific property
3. you have to manually retrieve the error information from model state and then store it in list and pass to the view page.

Related

ASP MVC Validation

I need to perform validation on a textbox and Dropdown which triggers only when both the values are empty and does nothing when one of the value is empty. How would i implement it? Do i need to create a custom validator? Below is my Model and View
Model
public class CustomValidators
{
[Required]
[Required(ErrorMessage = "State Required")]
public string drpStateId { set; get; }
public System.Web.Mvc.SelectList drpState { set; get; }
[Required(ErrorMessage ="Region Required")]
public string txtRegion { set; get; }
}
View
#model InterviewTest.Models.CustomValidators
#{
ViewBag.Title = "Custom Validator";
Layout = "~/Views/_Layout.cshtml";
}
<p>#Html.ActionLink("< Back", "Index")</p>
#using (Html.BeginForm("CustomValidatorPost"))
{
#Html.ValidationSummary()
<div class="container-fluid">
<div class="row">
<div class="col-sm-3">
<div class="form-group">
#Html.DropDownListFor(c => c.drpStateId, Model.drpState, "", new { #class = "form-control" })
</div>
</div>
<div class="col-sm-6">
<div class="form-group">
#Html.TextBoxFor(x => Model.txtRegion, new { #class = "form-control" })
#*<input type="text" id="txtRegion" name="txtRegion" class="form-control" />*#
</div>
</div>
<div class="col-sm-3">
<button type="submit" name="btnSubmit" id="btnSubmit" class="btn btn-default">Submit</button>
</div>
</div>
</div>
}
There is no out of the box validation that works on 2 fields except for the compare validator, so in your case you have to create a custom validation.
You can create a JavaScript function and fire it on onchange on both the two text boxes and within it check the values and if both are empty, show an error message and prevent the form from being submitted, you can achieve that using JQuery validation by adding a custom validator, see this link for more details https://jqueryvalidation.org/jQuery.validator.addMethod/
On Server side, you can do a simple if statement in the controller action to validate that both the values are not empty and if both are empty, then add an error to the ModelState

MVC parent child kind of model form submit doesn't send child collection to controller

I have a company model and it has employees list model as shown below
public class Company
{
[Required]
[Display(Name = "Company Name")]
public string Name { get; set; }
public List<EmployeeModel> Managers { get; set; }
}
and the Employee model as below
public class EmployeeModel
{
public string Name { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
}
and my parent view is as shown below
#using (Html.BeginForm("CompanySignupSuccess", "Home", FormMethod.Post, new { #class = "horizontal-form", role = "form", enctype = "multipart/form-data" }))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary("", new { #class = "text-danger" })
<div>
<div class="form-group">
#Html.LabelFor(m => m.Name, new { #class = "control-label" })<span class="required">*</span>
#Html.TextBoxFor(m => m.Name, new { #class = "form-control" })
</div>
<div class="form-group">
<label for="file">Logo:</label>
<input type="file" name="logo" id="logo" accept=".png,.jpg,.jpeg" />
</div>
<div id="managerList">
<div id="editorRowsManagers">
#foreach (var item in Model.Managers)
{
#Html.Partial("DetailsView", item)
}
</div>
</div>
<div class="form-group">
<input type="submit" class="btn btn-default pull-right" value="Send" />
</div>
</div>
}
and the partial view shown below
#model yourAssembly.EmployeeModel
<div style="border:1px solid;margin:20px; padding:10px;">
Manager Details:
<div class="form-group">
#Html.LabelFor(m => m.Name, new { #class = "control-label" })
#Html.TextBoxFor(m => m.Name, new { #class = "form-control" })
</div>
<div class="form-group">
#Html.LabelFor(m => m.Email, new { #class = "control-label" }) <span class="required">*</span>
#Html.TextBoxFor(m => m.Email, new { #class = "form-control" })
</div>
<div class="form-group">
#Html.LabelFor(m => m.Phone, new { #class = "control-label" }) <span class="required">*</span>
#Html.TextBoxFor(m => m.Phone, new { #class = "form-control phoneno" })
</div>
</div>
When I click on submit button, the model that goes to controller does have only Name and Logo properties filled in and the list object(Managers) is null, so I am not sure what is that I am missing here. BTW, I used the list of employees , because I would like add more employees by having a 'Add' button, and the Add button will just render another partial view.
public ActionResult CompanySignupSuccess(Company model)
{
if (ModelState.IsValid)
{
//do some process
}
else
{
ModelState.AddModelError("", "Invalid Data entered.");
}
// If we got this far, something failed, redisplay form
return View("CompanySignup", Model);
}
Can anyone please help me on how to send the child list object along with some properties on parent class when the Submit button is hit.
You cannot use a partial to generate controls for a collection unless you pass the HtmlFieldPrefix (refer this answer for an example). However the correct way to do this is to use an EditorTemplate. Rename your partial to EmployeeModel.cshtml (i.e. to match the name of the class) and move it to the /Views/Shared/EditorTemplates folder (or /Views/YourControllerName/EditorTemplates folder).
Then replace your loop in the view with
#Html.EditorFor(m => m.Managers)
which will correctly generate the necessary name attributes for binding, i.e
<input ... name="Managers[0].Name" />
<input ... name="Managers[1].Name" />
etc (currently all your generating is duplicate name attributes (and duplicate id attributes which is invalid html)

Asp.net MVC View Textbox Returns Default Value

I can send lat,lon, neighbors and neighborslimit variables to View.Yet, I want change neighborlimit from view. When I post View, MapViewModel's variables are 0, I have tried to ModelState.Clear() but there is no difference, Could you help me about it ? Thanks
MODEL:
public class MapViewModel
{
public double lat;
public double lon;
public List<Point> neighbors;
public Polygon polygon;
public int neighborlimit;
public double[][] polyTable;
}
CONTROLLER:
[HttpGet]
public ActionResult Map()
{
UserAccount user = (UserAccount)UserManager.FindByName(User.Identity.Name);
MapViewModel model = new MapViewModel() { lat = (double)user.address.latitude, lon = (double)user.address.longitude, neighbors = user.getNeighbors(), neighborlimit= (int)user.neighborsLimit };
return View(model);
}
[HttpPost]
public ActionResult Map(MapViewModel model)
{
UserAccount user = (UserAccount)UserManager.FindByName(User.Identity.Name);
user.neighborsLimit = model.neighborlimit;
UserManager.Update(user);
return View(model);
}
VIEW:
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-group">
<div class="col-md-10">
#Html.TextBoxFor(h => h.neighborlimit, new { #class = "form-control" })
</div>
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Log in" class="btn btn-default" />
</div>
</div>
}
You don't have a property for neighborlimit (just a field). Change it to
public int neighborlimit { get; set; }
which will allow the DefaultModelBinder to set the property when you submit the form
The problem is that you don't have the values in the form that's why when the form is posted the values doesn't exists and the ModelBinder set default values. If the security is not a problem but hidden fields for all values that you want to persist.
Something like this
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.HiddenFor(h => h.lat)
/* Now enter hidden fields for all of the properties that you want */
<div class="form-group">
<div class="col-md-10">
#Html.TextBoxFor(h => h.neighborlimit, new { #class = "form-control" })
</div>
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Log in" class="btn btn-default" />
</div>
</div>
}
UPDATE
As Stephen Muecke said make sure that you use properties not fields

IValidatableObject.Validate does not fire if DataAnnoations add ValidationResult

With a standard ASP.NET MVC controller and view and a model that both implements IValidatableObject and has DataAnnotations, the Validate method never fires if the DataAnnotations generate an exception.
Here's the model...
public class ModelStaticDA : IValidatableObject {
public long Id { get; set; }
[EmailAddress]
public string EmailAddress { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
yield return new ValidationResult("MODEL NOT VALID!")
}
}
Here's the view (client validation is disabled for this demo)...
#model BindingAndValidation.Models.ModelStaticDA
#{
ViewBag.Title = "Create";
HtmlHelper.ClientValidationEnabled = false;
}
<h2>Create</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>ModelStaticDA</h4>
<hr />
#Html.ValidationSummary(true)
<div class="form-group">
#Html.LabelFor(model => model.EmailAddress, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.TextBoxFor(model => model.EmailAddress)
#Html.ValidationMessageFor(model => model.EmailAddress)
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
If you post something like "invalid" to EmailAddress, only the DataAnnotation message displays. If you post a valid e-mail address, the message from Validate displays.
Is this the correct behavior? If so, why? If not, what am I doing wrong?
You are doing everything right, that's the behavior. My guess it was designed this way to avoid having to validate again while working with the properties inside the Validate method, you know that when it's called you are working with valid data, and you can do things that require valid data.

Upload image included in MVC model

I have the following model:
public class Photo
{
public int PhotoId { get; set; }
public byte[] ImageData { get; set; }
public DateTime DateUploaded { get; set; }
public string Description { get; set; }
public bool IsActive { get; set; }
}
I would like the user to be able to enter the details for the photo then post the model the the controller. My controller action is as follows:
[HttpPost]
public ActionResult Create(WilhanWebsite.DomainClasses.Photo photo)
{
if (ModelState.IsValid)
{
photo.DateUploaded = DateTime.Now;
_context.Photos.Add(photo);
_context.SaveChanges();
return RedirectToAction("Index");
}
//we only get here if there was a problem
return View(photo);
}
My view is as follows:
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Photo</h4>
<hr />
#Html.ValidationSummary(true)
<div class="form-group">
#Html.LabelFor(model => model.ImageData, new { #class = "control-label col-md-2" })
<div class="col-md-10">
<input type="file" name="uploadImages" class="input-files" />
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.DateUploaded, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.DateUploaded)
#Html.ValidationMessageFor(model => model.DateUploaded)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Description, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Description)
#Html.ValidationMessageFor(model => model.Description)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.IsActive, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.IsActive)
#Html.ValidationMessageFor(model => model.IsActive)
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
The view is displayed ok and allows the user to select a file from their local disk and enter the other model details.
My problem is that although the model is posted to the controller ok, the Description, Date and IsActive flags are populated ok - the Image data is null.
Could anyone please let me know what I need to change so that the byte array for the photo is included in the model posted to the controller?
The file input in your view has a name uploadImages. I can't see a property with this name in your view model. You seem to have some ImageData property which is a byte array, but there doesn't seem to be a corresponding input field with this name in your view.
This explains why you get null. You could make this work by respecting the convention. So for example if you intend to have such an input field in your view:
<input type="file" name="uploadImages" class="input-files" />
then make sure that you have a property on your view model with the same name. And of course of type HttpPostedFileBase.
public HttpPostedFileBase UploadImages { get; set; }
Also in your view make sure you are setting the proper content type of multipart/form-data:
#using (Html.BeginForm(null, null, FormMethod.Post, new { enctype = "multipart/form-data" }))
{
...
}
You probably might want to go through the following blog post to better familiarize yourself with the basics of how uploading of files work in ASP.NET MVC. I've also written a similar answer here that you might consult.
So once you add the HttpPostedFileBase property with the UploadImages name in your view model you could adapt your controller action to read the byte array and store it your ImageData property:
[HttpPost]
public ActionResult Create(WilhanWebsite.DomainClasses.Photo photo)
{
if (ModelState.IsValid)
{
photo.DateUploaded = DateTime.Now;
photo.ImageData = new byte[photo.UploadImages.ContentLength];
photo.UploadImages.Read(photo.ImageData, 0, photo.ImageData.Length);
_context.Photos.Add(photo);
_context.SaveChanges();
return RedirectToAction("Index");
}
//we only get here if there was a problem
return View(photo);
}
Now bear in mind that this is an absolutely awful solution. Never do that in a real world application. In a correctly designed application you will have a view model that your controller action will take as a parameter. You're never gonna directly use your autogenerated EF model as parameter to your controller action. You will have a view model with the HttpPostedFileBase property which will be mapped to your domain model.
So in a properly designed application you will have a PhotoViewModel view model class that your controller action will take.
Change this line:
#using (Html.BeginForm())
To this:
#using (Html.BeginForm(null, null, FormMethod.Post, new { enctype = "multipart/form-data" }))
Then change:
<input type="file" name="uploadImages" class="input-files" />
To:
<input type="file" name="ImageData" class="input-files" />
Then change this line:
public byte[] ImageData { get; set; }
To this:
public HttpPostedFileBase ImageData { get; set; }
Finally, use some code like this to read the image into a byte array:
var bs = new byte[ImageData.ContentLength];
using (var fs = ImageData.InputStream)
{
var offset = 0;
do
{
offset += fs.Read(bs, offset, bs.Length - offset);
} while (offset < bs.Length);
}
View:
#using (Html.BeginForm(null, null, FormMethod.Post, new { enctype = "multipart/form-data" }))
{
...
<input type="file" id="ImageFile" name="ImageFile" .../>
...
}
Controller:
[HttpPost]
public ActionResult Create(Photo photo, HttpPostedFileBase ImageFile)
{
byte[] buf = new byte[ImageFile.ContentLength];
ImageFile.InputStream.Read(buf, 0, buf.Length);
photo.ImageData = buf;
...
}

Resources