Unobtrusive validation not working for custom validation attribute - asp.net

I am trying to write a custom validation attribute in MVC and I can't make it work as an unobstructive validation. It works fine with postback (kinda) but as the form is on a dialog, I must have an Ajax style call or it's unusable. Maybe what i am trying to do is unachieveable. The problem is i need to connect to a database to do the check.
I made a simple demo for my problem.
My model
public class Customer
{
public int Id { get; set; }
[IsNameUnique]
[Required]
public string Name { get; set; }
}
The view:
#model WebApplication1.Models.Customer
#using (Html.BeginForm("Index", "Home", FormMethod.Post, new { #id = "NewForm" }))
{
#Html.ValidationSummary(true)
#Html.EditorFor(m => m.Name)
#Html.ValidationMessageFor(m => m.Name)
<br />
<input type="submit" value="Submit" />
}
Custom validation class
public class IsNameUnique : ValidationAttribute
{
private CustomerRepository _repository;
public IsNameUnique()
{
_repository = new CustomerRepository();
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if(value != null)
{
var isValid = _repository.IsNameUnique(value);
if(!isValid)
{
return new ValidationResult("Name must be unique");
}
}
return ValidationResult.Success;
}
}
Post method
[HttpPost]
public ActionResult Index(Customer customer)
{
if(ModelState.IsValid)
{
//add customer
}
return View();
}
database call
class CustomerRepository
{
internal bool IsNameUnique(object value)
{
//call to database
return false;
}
}
There is a form with a name field. I need to check if name is already in the database.
My question is how can I do unobtrusive style validation in my case? I found other posts on SO about IClientValidatable but none of them really show what I need to do. i.e. none of them do check against a database. Thanks.

Basically "unobtrusive validation" means "Client-Side validation, defined in an unobtrusive way". Key point here is "Client-Side", that is, validation which can be done via JavaScript in client browser.
Checking for name uniqueness involves server-side resources, and while it can be done in JavaScript using AJAX requests to server, usually people decide not to do so.
You can follow this guide for details of implementing unobtrusive validation: http://thewayofcode.wordpress.com/tag/custom-unobtrusive-validation/
In general you will need to do the following:
Enable unobtrusive validation in web.config
Include jQuery, jQuery Validate and unobtrusive scripts into your page
Implement IClientValidatable for your custom validation attribute
Implement and register client-side rules for your custom attribute

You may want to look into the [Remote] validation attribute. Just make a controller method that returns a JsonResult and map it to the remote attribute. This is probably the easiest way to accomplish what you're looking to do.
[Remote( "IsNameUnique", "Customers", HttpMethod = "post" )]
public override string Name { get; set; }
[HttpPost]
public JsonResult IsNameUnique( string name )
{
// Code
}
If you want to implement this as a custom validation, you need to do the following:
In your attribute, implement IClientValidatable. This requires you to implement GetClientValidationRules() method. Return a new client validation rule with your type and parameters.
Here's an example:
https://github.com/DustinEwers/dot-net-mvc-ui-demos/blob/master/ASPNET4/UIDemos/UIDemos/Attributes/PastDateOnlyAttribute.cs
Then you need to implement a jQuery validation rule. This is where you'd make your ajax call:
jQuery.validator.addMethod("pastdateonly", function (val, element, params) {
var value = $.trim($(element).val());
if (value === "") return true;
var maxDate = params.maxdate,
dteVal = new Date(value),
mxDte = new Date(maxDate);
return dteVal < mxDte;
});
Then add an unobtrusive adapter method.
jQuery.validator.unobtrusive.adapters.add("pastdateonly", ["maxdate"],
function (options) {
options.rules["pastdateonly"] = {
maxdate: options.params.maxdate
};
options.messages["pastdateonly"] = options.message;
}
);
Example:
https://github.com/DustinEwers/dot-net-mvc-ui-demos/blob/master/ASPNET4/UIDemos/UIDemos/Scripts/site.js

Related

Update relationships on edit POST with model bind from MVC controller

Using Entity Framework with a many-to-many relationship, I'm confused how to update that relationship from an ASP.NET MVC controller where the model is bound.
For example, a blog: where posts have many tags, and tags have many posts.
Posts controller edit action fails to lazy load tags entities:
public class PostsController : Controller
{
[Route("posts/edit/{id}")]
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Edit([Bind(Include = "Id,Title,Tags")] Post post)
{
// Post tags is null
Post.tags.ToList();
}
}
Above, the HTTP Post binds properties to the model; however, the Post.tags relationship is null.
There's no way for me to query, .Include(p => p.Tags), or Attach() the post to retrieve the related tag entities using this [Bind()].
On the view side, I'm using a tokenizer and passing formdata - I'm not using a MVC list component. So, the issue is not binding the view formdata - the issue is that the .tags property is not lazy loading the entities.
This relationship is functional - from the Razor cshtml view I am able to traverse the collection and view children tags.
From the Post View, I can view tags (this works)
#foreach (var tag in Model.Tags) {
}
From the Tag View, I can view posts (this works)
#foreach (var post in Model.Posts) {
}
Likewise on create action from the Posts controller, I can create new tag entities and persist their relationship.
public class PostsController : Controller
{
[Route("posts/create")]
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create([Bind(Include = "Title,Content")] Post post)
{
string[] tagNames = this.Request.Form["TagNames"].Split(',').Select(tag => tag.Trim()).ToArray();
post.Tags = new HashSet<Tag>();
Tag tag = new Tag();
post.Tags.Add(tag);
if (ModelState.IsValid)
{
db.posts.Add(post);
await db.SaveChangesAsync();
return RedirectToAction("Admin");
}
return View(post);
}
These relationships work everywhere except for this edit HTTP Post. For reference, they are defined as:
public class Post
{
public virtual ICollection<Tag> Tags { get; set; }
}
public class Tag
{
public virtual ICollection<Post> Posts { get; set; }
}
Only on HTTP Post of edit action can I not access tags of posts.
How can I load related tags to alter that collection?
Your problem has nothing to do with Entity framework. It is basically an issue when you post a model/viewmodel with a collection property from your form, the collection property becomes null.
You can solve this by using EditorTemplates.
It looks like you are using the entity classes generated by Entity framework in your views. Generally this is not a good idea because now your UI layer is tightly coupled to EF entities. What if tomorrow you want to change your data access code implemenation form EF to something else for any reasons ?
So Let's create some viewmodels to be used in the UI layer. Viewmodels are simple POCO classes. The viewmodels will have properties which the view absolutely need. Do not just copy all your entity class properties and paste in your viewmodels.
public class PostViewModel
{
public int Id { set; get; }
public string Title { set; get; }
public List<PostTagViewModel> Tags { set; get; }
}
public class PostTagViewModel
{
public int Id { set; get; }
public string TagName { set; get; }
public bool IsSelected { set; get; }
}
Now in your GET action, you will create an object of your PostViewModel class, Initialize the Tags collection and send to the view.
public ActionResult Create()
{
var v =new PostViewModel();
v.Tags = GetTags();
return View(v);
}
private List<PostTagViewModel> GetTags()
{
var db = new YourDbContext();
return db.Tags.Select(x=> new PostTagViewModel { Id=x.Id, TagName=x.Name})
.ToList();
}
Now, Let's create an editor template. Go to the ~/Views/YourControllerName directory and create a sub directory called EditorTemplates. Create a new view there with the name PostTagViewModel.cshtml.
Add the below code to the new file.
#model YourNamespaceHere.PostTagViewModel
<div>
#Model.TagName
#Html.CheckBoxFor(s=>s.IsSelected)
#Html.HiddenFor(s=>s.Id)
</div>
Now, in our main view (create.cshtml) which is strongly typed to PostViewModel, we will call Html.EditorFor helper method to use the editor template.
#model YourNamespaceHere.PostViewModel
#using (Html.BeginForm())
{
<label>Post title</label>
#Html.TextBoxFor(s=>s.Title)
<h3>Select tags</h3>
#Html.EditorFor(s=>s.Tags)
<input type="submit"/>
}
Now in your HttpPost action method, you can inspect the posted model for Tags collection.
[HttpPost]
public ActionResult Create(PostViewModel model)
{
if (ModelState.IsValid)
{
// Put a break point here and inspect model.
foreach (var tag in model.Tags)
{
if (tag.IsSelected)
{
// Tag was checked from UI.Save it
}
}
// to do : Save Post,Tags and then redirect.
}
model.Tags = GetTags(); //reload tags again
return View(model);
}
So since our HttpPost action's parameter is an object of PostViewModel, we need to map it to your Entity classes to save it using entity framework.
var post= new Post { Title = model.Title };
foreach(var t in model.Tags)
{
if(t.IsSelected)
{
var t = dbContext.Tags.FirstOrDefault(s=>s.Id==t.Id);
if(t!=null)
{
post.Tags.Add(t);
}
}
}
dbContext.Posts.Add(post);
await dbContext.SaveChangesAsync();
Your create method that missing an Id parametres of post that you want to insert tags.
You have to declare Post Id at the begining of the method.
[Route("posts/create")]
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create([Bind(Include = "Title,Content")] Post post)
{
Post nPost= db.Posts.FirstOrDefault(x => x.Id == post.Id);
nPost.Id= post.Id;
nPost.Title= post.Title;
string[] tagNames = this.Request.Form["TagNames"].Split(',').Select(tag => tag.Trim()).ToArray();
foreach(string item in tagNames)
{
//First check if you got tag in Tags table
Tag tg= db.Tags.FirstOrDefault(x => x.Name.ToLower() == item.ToLower().Trim());
// If no row than create one.
if (tg== null)
{
tg= new Tag();
tg.Name= item;
db.Tags.Add(tg);
await db.SaveChanges();
}
// Now adding those tags to Post Tags.
if (nPost.Tags.FirstOrDefault(x => x.Id == tg.Id) == null)
{
nPost.Tags.Add(etk);
await db.SaveChanges();
}
}
if (ModelState.IsValid)
{
await db.SaveChangesAsync();
return RedirectToAction("Admin");
}
return View(post);
}
That's it.

How do you require certain properties from the URI of the request?

I have a very simple model:
public class FoobarGETRequestModel {
[Required]
public string Id { get; set; }
}
It's being used for binding like so:
[HttpGet]
[ValidateModel] //This is a custom attribute for handling validation errors
public async Task<HttpResponseMessage> Foobar([FromUri]FoobarGETRequestModel model) {
...
}
If I make a request to /api/controller/foobar (notice I'm not providing an Id parameter), the ModelState.IsValid property always returns true (from both an action filter and from within the method above).
How do I bind via the URI and still leverage the frameworks ModelState validation?
Edit: Here is what ValidateModel looks like:
if (actionContext.ModelState.IsValid == false) {
var responseObject = new FooApiExceptionResponse() {
Errors = actionContext.ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage)
};
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.BadRequest, responseObject);
}
Did you check your RouteTable? If you are telling routetable that your "id" parameter is optional in default route, this could occur.

Click a button, call a method in controller in ASP.NET

I want to do something very simple, which is to create an HTML button that calls a controller function when clicked, the same as this HTML actionlink. This should really be remarkably easy. The action link is:
#Html.ActionLink("Submit", "Submit", "Home")
I'm using the Razer viewmodel and .NET 4.5. I've done some research, and it seems that I may have to create my own custom button. I'm fine with that, but is that really necessary? See: Mvc Html.ActionButton. It would seem like an oversight for this to have no native microsoft support, but if not, I can live with that.
Please forgive the naivety of this question - I'm new to ASP.NET, though not to C# or web development. Thanks!
I grabbed this from somewhere. but you can map view actions to controller actions with the following code.
Create a class with the following code.
[AttributeUsage(AttributeTargets.Method, AllowMultiple=false, Inherited=true)]
public class MultipleButtonAttribute : ActionNameSelectorAttribute
{
public string Name { get; set; }
public string Argument { get; set; }
public override bool IsValidName(ControllerContext controllerContext, string actionName, MethodInfo methodInfo)
{
var isValidName = false;
var keyValue = string.Format("{0}:{1}", Name, Argument);
var value = controllerContext.Controller.ValueProvider.GetValue(keyValue);
if (value != null)
{
controllerContext.Controller.ControllerContext.RouteData.Values[Name] = Argument;
isValidName = true;
}
return isValidName;
}
}
In your View code you can have the following submit buttons
<input type="submit" value="Action A" name="action:ActionA" />
<input type="submit" value="Action B" name="action:ActionB" />
And your controller contains the following code.
[HttpPost]
[MultipleButton(Name="action", Argument="ActionA")]
public ActionResult MyActionA(myModel model)
{
...
}
[HttpPost]
[MultipleButton(Name = "action", Argument = "ActionB")]
public ActionResult MyActionB(myModel model)
{
...
}

MVC model binding naming convention for child objects?

I'm having trouble with default model binding naming convention when there is a child property. For example:
I have a ViewModel which looks something like this:
public class UserViewModel
{
public User BusinessObject { get; set; }
}
My User class has a property called "NetworkLogin"
My View has something like this:
<%: Html.LabelFor(model => model.BusinessObject.NetworkLogin)%>
<%: Html.TextBoxFor(model => model.BusinessObject.NetworkLogin)%>
Auto-Fill
And my controller, what I'd like to do, is
[HttpGet]
public ActionResult UserIndex(string networkLogin) { }
The problem:
The input parameter "networkLogin" is always null. This makes sense, because the actual parameter on the html element is name="BusinessObject.NetworkLogin" and id="BusinessObject_NetworkLogin". However, I don't know what parameter name I should use in my action method. I've tried "businessObject_NetworkLogin" and it doesn't work either.
However, I have this workaround that does work, but I don't like it. I add this to my ViewModel:
public string NetworkLogin
{
get
{
if (BusinessObject == null)
BusinessObject = new User();
return BusinessObject.NetworkLogin;
}
set
{
if (BusinessObject == null)
BusinessObject = new User();
BusinessObject.NetworkLogin = value;
}
}
And my View page now says this instead.
<%: Html.TextBoxFor(model => model.NetworkLogin)%>
Can someone tell me what the proper naming convention is for default model binding so that I don't have to employ the above workaround?
Thank you!
Indicate the prefix so that the model binder knows that the BusinessObject.NetworkLogin query string parameter actually refers to networkLogin which is what you use as action argument
public ActionResult UserIndex(
[Bind(Prefix = "BusinessObject")] string networkLogin
)
{
...
}
or reuse your view model:
public ActionResult UserIndex(UserViewModel model)
{
// TODO: use model.BusinessObject.NetworkLogin
// which is gonna be correctly bound here
...
}
As far as your workaround is concerned, once you put one of my two suggestions into action your view model property should really look like this:
public string NetworkLogin { get; set; }

Modern way to handle and validate POST-data in MVC 2

There are a lot of articles devoted to working with data in MVC, and nothing about MVC 2.
So my question is: what is the proper way to handle POST-query and validate it.
Assume we have 2 actions. Both of them operates over the same entity, but each action has its own separated set of object properties that should be bound in automatic manner. For example:
Action "A" should bind only "Name" property of object, taken from POST-request
Action "B" should bind only "Date" property of object, taken from POST-request
As far as I understand - we cannot use Bind attribute in this case.
So - what are the best practices in MVC2 to handle POST-data and probably validate it?
UPD:
After Actions performed - additional logic will be applied to the objects so they become valid and ready to store in persistent layer. For action "A" - it will be setting up Date to current date.
I personally don't like using domain model classes as my view model. I find it causes problems with validation, formatting, and generally feels wrong. In fact, I'd not actually use a DateTime property on my view model at all (I'd format it as a string in my controller).
I would use two seperate view models, each with validation attributes, exposed as properties of your primary view model:
NOTE: I've left how to combining posted view-models with the main view model as an exercise for you, since there's several ways of approaching it
public class ActionAViewModel
{
[Required(ErrorMessage="Please enter your name")]
public string Name { get; set; }
}
public class ActionBViewModel
{
[Required(ErrorMessage="Please enter your date")]
// You could use a regex or custom attribute to do date validation,
// allowing you to have a custom error message for badly formatted
// dates
public string Date { get; set; }
}
public class PageViewModel
{
public ActionAViewModel ActionA { get; set; }
public ActionBViewModel ActionB { get; set; }
}
public class PageController
{
public ActionResult Index()
{
var viewModel = new PageViewModel
{
ActionA = new ActionAViewModel { Name = "Test" }
ActionB = new ActionBViewModel { Date = DateTime.Today.ToString(); }
};
return View(viewModel);
}
// The [Bind] prefix is there for when you use
// <%= Html.TextBoxFor(x => x.ActionA.Name) %>
public ActionResult ActionA(
[Bind(Prefix="ActionA")] ActionAViewModel viewModel)
{
if (ModelState.IsValid)
{
// Load model, update the Name, and commit the change
}
else
{
// Display Index with viewModel
// and default ActionBViewModel
}
}
public ActionResult ActionB(
[Bind(Prefix="ActionB")] ActionBViewModel viewModel)
{
if (ModelState.IsValid)
{
// Load model, update the Date, and commit the change
}
else
{
// Display Index with viewModel
// and default ActionAViewModel
}
}
}
One possible way to handle POST data and add validation, is with a custom model binder.
Here is a small sample of what i used recently to add custom validation to POST-form data :
public class Customer
{
public string Name { get; set; }
public DateTime Date { get; set; }
}
public class PageController : Controller
{
[HttpPost]
public ActionResult ActionA(Customer customer)
{
if(ModelState.IsValid) {
//do something with the customer
}
}
[HttpPost]
public ActionResult ActionB(Customer customer)
{
if(ModelState.IsValid) {
//do something with the customer
}
}
}
A CustomerModelBinder will be something like that:
public class CustomerModelBinder : DefaultModelBinder
{
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor)
{
if (propertyDescriptor.Name == "Name") //or date or whatever else you want
{
//Access your Name property with valueprovider and do some magic before you bind it to the model.
//To add validation errors do (simple stuff)
if(string.IsNullOrEmpty(bindingContext.ValueProvider.GetValue("Name").AttemptedValue))
bindingContext.ModelState.AddModelError("Name", "Please enter a valid name");
//Any complex validation
}
else
{
//call the usual binder otherwise. I noticed that in this way you can use DataAnnotations as well.
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
}
and in the global.asax put
ModelBinders.Binders.Add(typeof(Customer), new CustomerModelBinder());
If you want not to bind Name property (just Date) when you call ActionB, then just make one more custom Model Binder and in the "if" statement, put to return the null, or the already existing value, or whatever you want. Then in the controller put:
[HttpPost]
public ActionResult([ModelBinder(typeof(CustomerAModelBinder))] Customer customer)
[HttpPost]
public ActionResult([ModelBinder(typeof(CustomerBModelBinder))] Customer customer)
Where customerAmodelbinder will bind only name and customerBmodelbinder will bind only date.
This is the easiest way i have found, to validate model binding, and i have achieved some very cool results with complex view models. I bet there is something out there that i have missed, and maybe a more expert can answer.
Hope i got your question right...:)

Resources