14

Rails has a very convenient uniqueness validation.

ASP.NET MVC doesn't.

I need to make sure that the e-mail address a user has entered hasn't been registered by anyone yet.

I can see only one way of doing this kind of validation: create a new data context object in the UniqueAttribute class.

But I'm afraid that wasting memory on a new data context object just for one validation is dangerous.

Am I wrong? Is there a better way to do that?

Update

This is what I got so far

public class UniqueEmailAttribute : ValidationAttribute {
    public override bool IsValid(object value) {
        DataContext db = new DataContext();
        var userWithTheSameEmail = db.Users.SingleOrDefault(
            u => u.Email == (string)value);
        return userWithTheSameEmail == null;
    }
}

// Usage
[UniqueEmail(ErrorMessage="This e-mail is already registered")]
public string Email { get; set; }

There are two problems.

  1. It would be good to have just one UniqueAttribute class, not separate classes for e-mails, usernames etc. How can I do that?

  2. Creating a new data context every time you need to validate a single attribute.

SOLUTION

So in the end I created a unique constraint on the table and now I just have to intercept SqlException in Users repository. Works great and is probably more efficient than searching for the same node in the whole table. Thanks!

Alex
  • 34,581
  • 26
  • 91
  • 135
  • 2
    Rails vs ASP.NET MVC is like comparing apple to oranges, they are not of the same. ASP.NET MVC does not have a formal data persistence layer, you have to choose one from the many, and struggle with that – TFD Nov 28 '10 at 20:36
  • @TFD, that's a good point. I agree, there's no data layer, so the DataContext has to be stored somewhere, that's what my question is about. – Alex Nov 28 '10 at 20:39
  • 1] By default, the property name that the attribute is applied to is sent to the action method as a query-string parameter. – swapneel Nov 28 '10 at 20:58
  • 2] In my opinon for web project it's best to validate single attribute every time before you insert/update to avoid concurrency problem – swapneel Nov 28 '10 at 20:59
  • You should probably store the current DataContext instance in the HttpRequest.Item collection instead of creating a new one. – Dmitry S. Nov 28 '10 at 22:03
  • @Dmitry, I never heard about storing DataContext in the HttpRequest.Item. I found this question: http://stackoverflow.com/questions/2684551/inheriting-linq-to-sql-data-context-from-base-controller I created the same class and it works fine. I hope that it doesn't eat a lot of memory... – Alex Nov 29 '10 at 07:48
  • Check out this post: [ValidationAttribute that validates a unique field against its fellow rows in the database](http://blogs.microsoft.co.il/blogs/shimmy/archive/2012/01/23/validationattribute-that-validates-a-unique-field-against-its-fellow-rows-in-the-database.aspx), the solution targets either `ObjectContext` or `DbContext`. – Shimmy Weitzhandler May 03 '12 at 23:11

4 Answers4

8

Mvc 3 Relaease candidate has new New Validation Attributes as a remotevalidation -where you can register a method for validation on clientside(jquery).

see below example- RemoteAttribute

The new RemoteAttribute validation attribute takes advantage of the jQuery Validation plug-in's remote validator, which enables client-side validation to call a method on the server that performs the actual validation logic.

In the following example, the UserName property has the RemoteAttribute applied. When editing this property in an Edit view, client validation will call an action named UserNameAvailable on the UsersController class in order to validate this field.

public class User {  
    [Remote("UserNameAvailable", "Users")]  
    public string UserName { get; set; }  
}  

The following example shows the corresponding controller.

public class UsersController {  
        public bool UserNameAvailable(string username) {  
            return !MyRepository.UserNameExists(username);  

       }  
   }

Mvc 3

UPDATE

    public bool UserNameAvailable(string Propertyname)  
    {  
        if (Request.QueryString[0]= "UserName")  
        {   
            //validate username  
        }  
        elseif (Request.QueryString[0]= "Email")  
        {  
            //Validate Email  
        }  

    }   
Community
  • 1
  • 1
swapneel
  • 3,061
  • 1
  • 25
  • 32
  • Note that this only works for client side validation. You need to extend the RemoteAttribute to add server side validation. See: http://www.codeproject.com/Articles/682434/Custom-Remote-Attribute-for-Client-Server-Validati – Kurren Jun 24 '14 at 14:27
2

The right way to make a generic remote unique validator in MVC can be found in this MVC forum. by counsellorben. It's based on my MVC unique remote validator article http://msdn.microsoft.com/en-us/library/gg508808(VS.98).aspx

RickAndMSFT
  • 20,912
  • 8
  • 60
  • 78
2

ASP.Net does have a feature that can automatically check the uniqueness of a user's email address when a user registers. It is the ASP.Net Membership service and you can use it to do what you want even if you don't use all of the features of it.

If you are not using the full Membership feature in your MVC application, then all you need to do is use

Membership.FindUsersByEmail(emailYouAreLookingFor);

If any values come back, you know that the address is not unique. If you ARE using the Membership service to create users, then the Membership service will check AUTOMATICALLY and return a code to you if the user's email address is not unique.

The Membership service sits in the System.Web.Security area so you would need a

using System.Web.Security;

reference in your controller.

Here is an example

            MembershipCreateStatus createStatus = MembershipService.CreateUser(UserName, Password, Email);

            if (createStatus == MembershipCreateStatus.DuplicateEmail)
            {

                   //do something here
            }
            else
            {
                   //do something here

            }

I hope this helps!

Community
  • 1
  • 1
Kila Morton
  • 205
  • 1
  • 5
1

A foolproof way of doing this is to create a validation attribute that would query the database for the email address. It would certainly add latency.

An alternative would be to create a unique constraint on the table and intercept SqlException.

Dmitry S.
  • 8,373
  • 2
  • 39
  • 49
  • 14
    Actually your "foolproof" method isn't. It creates a race condition. Two separate server processes could query for the same email address at the same time and both be told it is not in the DB. Then they would both attempt to save it. Only one would win. The only foolproof method of ensuring this type of uniqueness is a unique constraint in the database. – mlibby Nov 28 '10 at 20:29
  • I've updated my post with the class that works, but it's still not what I want. Is a unique constraint going to be better performance wise? – Alex Nov 28 '10 at 20:36
  • mcl, it should not be a problem if you are using the same unit-of-work/connection for each HTTP request, which is usually the recommended practice. But it is certainly something to be aware of. – Dmitry S. Nov 28 '10 at 22:01
  • @Dimitry - Usually you don't use the same unit of work for each HTTP request. Each request gets its own. Maybe I'm not understanding something having the same unit of work be shared among different http request seems crazy. – John Farrell Nov 29 '10 at 00:20
  • So in the end I created a unique constraint on the table and now I just have to intercept SqlException in Users repository. Works great and is probably more efficient than searching for the same node in the whole table. Thanks! – Alex Dec 23 '10 at 10:29
  • 1
    @Alex I know this old, but just googled it. After you catch the exception what do you do with it? How will it reflect to the ModelState automatically? Your first method works with it I think. This solution is automatic like your first approach? – dvjanm Oct 22 '13 at 15:13
  • 1
    @Alex And another thing: if you want to perform an edit on the model, the first type of validation doesn't seems to be good. In this case you have to validate the User itself, not the just the e-mail address uniqueness. – dvjanm Oct 22 '13 at 15:35