This is, initially at least, more of an exploratory question to see what is and isn't possible and to see if what I would like to achieve is even possible.
At the moment I am using jQuery Unobtrusive validation in conjunction with MVCs standard server side validation to carry out validation before the data is submitted via a web form. Now, I want to have the form be a little more 'dynamic' so depending on the choice of a dropdown/radiobutton/checkbox etc. parts of the form will show or hide themselves, but this is present 2 issues to me:
jQuery Unobtrusive will still attempt to validate all form fields even if they are hidden or have their display set to none. I think I can get around this using some jQuery, a custom class tag on each element and then when the UI is updated, loop through the elements and remove/add the data-val. This isn't the major issue
This is the big one. Since I will have decorated the model with Data Annotations, when the data hits the server, if parts of it are empty, then obviously the if(ModelState.IsValid()) check is going to fail, but I need the annotations there to trigger the client side validation
I'm wondering, is there a way I can override the existing MVC server side validation logic so that if a certain checkbox or dropdown value has been selected, then some logic will simply be bypassed. I've written a very basic custom validation attribute before when I was wanting to limit access based on the connecting parties IP address.
I know I could make use of multiple ViewModels, but when the user first visits the site, the form won't know what type of submission they will be making until they choose the appropriate option, by that point a generic submission object will have already been passed to the View on the HTTP GET controller method and ideally, I'd like to avoid any reloading of the page once a submission has been made to simply load a secondary ViewModel. I'm also making use of a file uploader and file information and so on is all stored within the ViewModel so this would also need to be passed around which I could see getting messy.
public class Submission
{
[HiddenInput(DisplayValue = false)]
public Guid SubmissionID { get; set; }
[Required(ErrorMessage = "Please provide your name")]
public string Name { get; set; }
[EmailAddress(ErrorMessage = "Invalid email address")]
[Required(ErrorMessage = "Please enter an email address")]
public string Email { get; set; }
[Required(ErrorMessage = "Please provide a contact number")]
[StringLength(13, ErrorMessage = "Phone number can be no more than 13 digits long")]
public string Phone { get; set; }
public string SubmissionPurpose { get; set; }
//Suspicious Transaction
//This should only be validated if, on the POST, the SubmissionPurpose matches the appropriate value
[Required(ErrorMessage = "Please provide a date")]
public string TransDateNoticed { get; set; }
public string TransAdditionalDetails { get; set; }
//Support Query
//This should only be validated if, on the POST, the SubmissionPurpose matches the appropriate value
[Required(ErrorMessage = "Please provide a date")]
public string SupDateNoticed { get; set; }
public string SupAdditionalDetails { get; set; }
//Additional Data
public string Message { get; set; }
public List<UploadedFile> Attachments { get; set; }
public IEnumerable<SelectListItem> SubmissionPurposes()
{
return new List<SelectListItem>
{
new SelectListItem() {Value = "", Text = "Please select an option"},
new SelectListItem() {Value = "Suspicious Transaction Reporting", Text = "Suspicious Transaction Reporting"},
new SelectListItem() {Value = "Request a Support Session", Text = "Request a Support Session"}
};
}
I'm pretty open to any and all suggestions, I'm thinking some sort of custom data annotation is the best way to go since that seems to be the only way I could have granular control over it, but then my question would be how to have an annotation like the idea I'm currently trying to work towards below
[CustomRequirement, SumissionPurpose = "Suspicious Transaction Reporting"]
I know in MVC 5.1 they introduced a new DropdownForEnum feature but from my looking so far I can't see anything new in 5.1 or 5.2 that might allow me to meet this need either.
I've used Foolproof Validation in the past. They provide several extra attributes and the clientside validation.
[Is]
[EqualTo]
[NotEqualTo]
[GreaterThan]
[LessThan]
[GreaterThanOrEqualTo]
[LessThanOrEqualTo]
[RequiredIf]
[RequiredIfNot]
[RequiredIfTrue]
[RequiredIfFalse]
[RequiredIfEmpty]
[RequiredIfNotEmpty]
[RequiredIfRegExMatch]
[RequiredIfNotRegExMatch]
Custom validation attributes are the way to go. Please note that if the properties in the model are complex objects and their validations depend on the parent object properties you will not get the validation context of the parent object property. In that case you have to think of writing your own model validators implementing from ModelValidator class and adding it to ModelValidatorProvider factory class.
Related
I have a model described as below:
public class Projet
{
public int ProjetId { get; set; }
public int SeqNumber{ get; set; }
public string Code{ get; set; }
public string Description { get; set; }
public bool Std{ get; set; }
public bool Archive { get; set; }
}
I have a create view to let a user create a new project, and I must let the SeqNumber field free so the user can input whatever number he wants.
However, I must return an error if the SeqNumber choosen is already taken.
I'm not sure how to implement this. Should I do some validation in my ProjetsController/Create action ? From what I can find, validating stuff in the controller is a bad practise, but I can't think of where to implement validation when it depends on other members of the same class.
Would coding a custom validator for MVC considered a good practise?
Thanks !
You can use this in your http post action method which handles the form submit
[HttpPost]
public ActionResult Create(Projet model)
{
var exist = db.Projects.Any(s=>s.SeqNumber===model.SeqNumber
&& s.ProjectId!=model.ProjectId);
if(exist)
{
ModelState.AddModelError(string.empty,"Sequence number is already in use");
return View(model);
}
// to do : Continue with your save
}
Now when user submits the form, if the sequence number is being used for any other project, it will throw a validation message. Make sure you are using the Validation summary helper in your view to render the validation error message.
#model Project
#Html.ValidationSummary(false)
#using(Html.BeginForm())
{
<!-- your existing code goes here-->
}
Now to give a nice user experience, you may take advantage of the Remote validation feature. What it does is, when user takes the focus out from the input, it makes an ajax call to server to check whether your SequenceNumber exist in db. Based on the result, the validation messages will be shown to the user.
To do this, decorate your property with the Remote attribute.
[Required]
[System.Web.Mvc.Remote("CheckSeqNumber", "Project",
ErrorMessage = "SeqNumber is already used!")]
public int SeqNumber { get; set; }
Now make sure you have an action method called CheckSeqNumber which returns either true or false as a json response.
public ActionResult CheckTagName(int SeqNumber)
{
var exist= !db.Projects.Any(g => g.SeqNumber == SeqNumber);
return Json(exist,JsonRequestBehavior.AllowGet);
}
Also make sure you have the relevant javascript files loaded to do this unobtrusive validation.
<script src="~/Scripts/jquery.validate.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.js"></script>
Another option is to write a custom validation attribute which does this check for you (if you do not like that check in the action method). Here is an SO post explaining how to do it.
I'm new at ASP.NET MVC web framework. My database is compound of a single model class("Movie"). I need to validate user's from entering existing data, for instance, a database row could be:
Title - "Indiana Jones and the lost Arc"
Price - $10.00
If another user tries to insert into the database the same data above, provide an error message and prevent from submitting the form collection.
First of all, I think that your question need a "program as answer" but I'll try to suggest you from where to start for working with validation. Suppose you have this model:
class Movie {
public Guid Id { get; set; }
[Required(ErrorMessage="Title is required.")]
[Remote("UniqueTitle", "Validation")]
public String Title { get; set; }
[Required(ErrorMessage="Price is required.")]
public float Price { get; set; }
}
You can decorate it for "simple validation" using Data Annotation. I've used a specific attribute, called Remote.
This attribute allow you to define a custom, server-side, logic to validate the model.
Now, you can create a validation controller where check that provided value is not already in use:
class ValidationController : Controller {
private IDbContext db = ...;
public ActionResult UniqueTitle(String title) {
var item = db.Movies.FirstOrDefault(m => m.Title.Equals(title));
return Json(item == null, JsonRequestBehavior.AllowGet);
}
}
Now you are ready to validate your model.
I hope this can help.
I am still just a couple days into ASP.NET and WebAPI frameworks so I must be missing out something really simple.
I have a model that has a couple properties and ID (as a property, which has a private setter but that didn't help).
public long ID { get; private set; }
[Required(ErrorMessage = "Location coordinate X is required.")]
public double X { get; set; }
[Required(ErrorMessage = "Location coordinate Y is required.")]
public double Y { get; set; }
And then I have a controller method post:
public HttpResponseMessage Post(MyModel model)
When I start the project and go to auto-generated API documentation, I can see that samples include ID as an input field. I want API to ignore ID input field. I could just ignore it myself but I don't like such must-remember-not-to-use things in my code.
One option would be to create a separate model just for the input but it would mean I have to maintain two classes instead of one.
Is there any data annotation to ignore this property entirely?
Try with:
[ScaffoldColumn(false)]
The ID property will no longer be seen by the html helpers. However, the model binder might still try to move a value into the ID property if it sees a matching value in the request.
So you decorate it with Exclude to avoid property to be binded:
[Exclude]
public long ID { get; set; }
You can also , (inside your Post function) remove the property from state:
ModelState.Remove("Id"); // Key removal
if (ModelState.IsValid)
{
}
}
I'm using MVC3 and unobtrusive validation to check my form. The user can select a facility from a list, and enter an audit name. The audit names must be unique for the facility, but can each facility can have an audit with the same name.
Model looks like this:
[DisplayName("Audit Name")]
[Remote("CheckAuditName", "Audit", ErrorMessage = "The audit name has already been used for this Healthcare Facility", AdditionalFields = "HealthcareFacilitySysId")]
public string AuditName { get; set; }
[DisplayName("Healthcare Facility")]
public long HealthcareFacilitySysId { get; set; }
public IEnumerable<SelectListItem> AvailableHealthcareFacilities { get; set; }
The remote validation is working, I get the right message at the right time, but I can fool it like this:
Select Facility 1
Enter an Audit name that has already been used for facility 2
Select Facility 2
Submit the form
On the Facility list's change event I have tried calling
$('form').validate().valid()
$('#AuditName').blur()
etc but I can't get the form to validate the remote method when I manually call it. Is it even possible?
The problem is probably some caching on the validation. Try something like this:
$("#HealthcareFacilitySysId").change(function () {
$("#AuditName").removeData("previousValue"); //clear cache
$("form").data('validator').element('#AuditName'); //retrigger remote call
$('#AuditName').blur()
});
I have the following:
public class Address
{
public string Email { get; set; }
}
public class CheckoutViewModel
{
public Address Address { get; set; }
[Compare("Address.Email", ErrorMessage = "The email addresses you entered do not match")]
public string ConfirmEmailAddress { get; set; }
}
With client-side JS, this works a treat and validates properly. However, when testing without Javascript enabled, The form posts back but the ModelState error reads:
Could not find a property named Address.Email.
Any ideas as to why this works on the client but not the server? What is the solution in this case?
Many thanks.
If you view the HTML source generated you should find that the input element for Email is called "Address.Email", and this is why the validation works on the client side.
However it looks like the attribute is not built to handle nested properties and so at the server level it is not working (as there is no property called "Address.Email"). As a result you will need to make sure both properties are at the same level (either both on the ViewModel or both on the Address class).
Your best option if probably to put the Email address property onto the view model and then populate the Address object later.