0

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.

pixelmeow
  • 654
  • 1
  • 9
  • 31
Laurence
  • 7,633
  • 21
  • 78
  • 129
  • 1
    unobstructive validation not work for custom validation attribute,it just work for built in validation , if you want to validate base on your custom validation , you must write jquery code for it. – Hamed Khatami Jun 26 '14 at 17:16
  • Ok ... Thanks .. your comment seems it's still possible to do it. If you don't mean seperate implémentation of jquery validation (which won't use data annotation), can you tell me what do i need on the client to make this work? – Laurence Jun 27 '14 at 08:08

2 Answers2

2

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:

  1. Enable unobtrusive validation in web.config
  2. Include jQuery, jQuery Validate and unobtrusive scripts into your page
  3. Implement IClientValidatable for your custom validation attribute
  4. Implement and register client-side rules for your custom attribute
Lanorkin
  • 7,310
  • 2
  • 42
  • 60
  • Are you basically saying what I am trying to do is impossible because it requires server side resources? I think I have tried most things you have said and unobtrusive validation isn't designed for Ajax requrest, my best guess .. Meanwhile, i found out about jquery validation remote property .. client side validation is possible with ajax request out of the box in jquery validation .. how do i mix with data annotation though? mystery isn't it. – Laurence Jul 07 '14 at 08:43
  • @lawphotog It is possible, but you will need to write pretty complex custom client validation rule. It should request server synchronously inside client validation rule - refer to link I shared in answer, where they describe `$.validator.addMethod("dategreaterthan"` logic. That server request should basically return if `value` is already exists in DB - that is, if validation failed or passed – Lanorkin Jul 07 '14 at 16:40
  • sorry .. i still don't fully understand .. the example you mentioned put a value on the client side .. there is no ajax request involved .. or is there? my case is that once user entered a value and it should go and check to the database via ajax .. i can't just put a thousands of customer names hidden on the page before the user entered anything .. what am i missing? Thanks for your help. – Laurence Jul 07 '14 at 20:13
  • What I'm trying to say - you fully control validation logic on client. All you need to do is to return true if value is valid, and false otherwise. So, having some value from user inside your client validation function, you may go to server using ajax to check if it is unique and return validation result - yes, you will need AJAX request inside validation function. This request should be synchronous like it is described here http://stackoverflow.com/a/6685294/2170171 – Lanorkin Jul 08 '14 at 06:52
2

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:

  1. 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

  1. 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;
    });
    
  2. 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

Richard
  • 29,854
  • 11
  • 77
  • 120
Dustin E
  • 358
  • 4
  • 10