Showing posts with label mvc. Show all posts
Showing posts with label mvc. Show all posts

Wednesday, April 20, 2011

microsoft-web-helpers MVC3 | SimpleMembershipProvider Error

I tried to install the microsoft-web-helpers (version 1.1) package via NuGet today and then all of a sudden I could not build because I received an error saying that SimpleMembershipProvider could not be found.

The solution is to add the following references to the project:

  • WebMatrix.Data
  • WebMatrix.WebData

I found the solution here.

Tuesday, March 22, 2011

Gravatar Helpers | ASP.NET MVC

Update: 2011-04-26 Changed the methods from returning string to returning MvcHtmlString (silly me) and created a few more overloads to assign a title attribute.

Was looking for a helper that I'd seen previously. I still haven't found it, but noticed this post with Gravatar helpers.

I've adjusted it in the following ways:
  • The code didn't trim or lowercase the email address before hashing, so I changed that
  • I added an overload which takes an enum for selecting the default image. 
  • The Url for the default image was not being Url encoded. This is fine if you are using one of the defaults that they provide, but might not work if you pass a Url to an image in.
  • There are several more overloads
  • Generally made more of a mess :oD
Some of these changed just help to ensure that there are no problems when getting the Gravatar, some are just niceties for me which are probably a little clearer to read, but not really necessary considering that you would generally call these a total of maybe 1 or 2 places in an application.

Hope this is useful to someone.
using System;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Web.Mvc;

namespace Catechize.Helpers
{
    public enum GravatarDefault
    {
        FileNotFound,
        MysteryMan,
        Identicon,
        MonsterID,
        Wavatar,
        Retro
    }

    // Kudos to Rob Connery http://blog.wekeroad.com/2010/01/20/my-favorite-helpers-for-aspnet-mvc
    public static class GravatarHelpers
    {
        public static MvcHtmlString Gravatar(this HtmlHelper helper, string email)
        {
            var url = GetGravatarUrl(helper, CleanupEmail(email), 40, GetDefaultGravatarString(GravatarDefault.MysteryMan));
            return MvcHtmlString.Create(ConstructImgTag(url));
        }

        public static MvcHtmlString Gravatar(this HtmlHelper helper, string email, string title) {
            var url = GetGravatarUrl(helper, CleanupEmail(email), 40, GetDefaultGravatarString(GravatarDefault.MysteryMan));
            return MvcHtmlString.Create(ConstructImgTag(url, title));
        }

        public static MvcHtmlString Gravatar(this HtmlHelper helper, string email, int size)
        {
            var url = GetGravatarUrl(helper, CleanupEmail(email), size, GetDefaultGravatarString(GravatarDefault.MysteryMan));
            return MvcHtmlString.Create(ConstructImgTag(url));
        }

        public static MvcHtmlString Gravatar(this HtmlHelper helper, string email, string title, int size)
        {
            var url = GetGravatarUrl(helper, CleanupEmail(email), size, GetDefaultGravatarString(GravatarDefault.MysteryMan));
            return MvcHtmlString.Create(ConstructImgTag(url, title));
        }

        public static MvcHtmlString Gravatar(this HtmlHelper helper, string email, int size, string defaultImageUrl)
        {
            var url = GetGravatarUrl(helper, CleanupEmail(email), size, UrlEncode(helper, defaultImageUrl));
            return MvcHtmlString.Create(ConstructImgTag(url));
        }

        public static MvcHtmlString Gravatar(this HtmlHelper helper, string email, string title, int size, string defaultImageUrl)
        {
            var url = GetGravatarUrl(helper, CleanupEmail(email), size, UrlEncode(helper, defaultImageUrl));
            return MvcHtmlString.Create(ConstructImgTag(url, title));
        }

        public static MvcHtmlString Gravatar(this HtmlHelper helper, string email, int size, GravatarDefault defaultImage)
        {
            var mode = GetDefaultGravatarString(defaultImage);

            var url = GetGravatarUrl(helper, CleanupEmail(email), size, mode);
            return MvcHtmlString.Create(ConstructImgTag(url));
        }

        public static MvcHtmlString Gravatar(this HtmlHelper helper, string email, string title, int size, GravatarDefault defaultImage)
        {
            var mode = GetDefaultGravatarString(defaultImage);

            var url = GetGravatarUrl(helper, CleanupEmail(email), size, mode);
            return MvcHtmlString.Create(ConstructImgTag(url, title));
        }

        public static string GetDefaultGravatarString(GravatarDefault defaultGravatar) {
            var mode = String.Empty;
            switch (defaultGravatar)
            {
                case GravatarDefault.FileNotFound:
                    mode = "404";
                    break;
                case GravatarDefault.Identicon:
                    mode = "identicon";
                    break;
                case GravatarDefault.MysteryMan:
                    mode = "mm";
                    break;
                case GravatarDefault.MonsterID:
                    mode = "monsterid";
                    break;
                case GravatarDefault.Wavatar:
                    mode = "wavatar";
                    break;
                case GravatarDefault.Retro:
                    mode = "retro";
                    break;
                default:
                    mode = "mm";
                    break;
            }
            return mode;
        }


        private static string ConstructImgTag(string src, string title = "Gravatar")
        {
            var result = "\"Gravatar\"";
            return String.Format(result, src, title);
        }

        static string GetGravatarUrl(HtmlHelper helper, string email, int size, string defaultImage)
        {
            string result = "http://www.gravatar.com/avatar/{0}?s={1}&r=PG";
            string emailMD5 = EncryptMD5(CleanupEmail(email));

            result = (string.Format(result,
                        EncryptMD5(email), size.ToString()));

            if (false == String.IsNullOrEmpty(defaultImage))
                result += "&d=" + defaultImage;
        
            return result;
        }

        private static string UrlEncode(HtmlHelper helper, string url)
        {
            var urlHelper = new UrlHelper(helper.ViewContext.RequestContext);
            return urlHelper.Encode(url);
        }

        private static string CleanupEmail(string email)
        {
            email = email.Trim();
            email = email.ToLower();

            return email;
        }

        private static string EncryptMD5(string value)
        {
            byte[] bytes;

            using (var md5 = MD5.Create())
            {
                bytes = Encoding.ASCII.GetBytes(value);
                bytes = md5.ComputeHash(bytes);
            }

            return String.Concat(bytes.Select(t => t.ToString("x2")));
        }
    }
}

Wednesday, March 16, 2011

Custom Server and Client Side Required Validator in MVC 2 using jQuery.validate

Note: This post was originally posted on my old blog on 2010-03-22

Update: Added information on issues that I’ve still yet to solve.

Update: Added information about replacing the custom validator provider to apply a custom error message when a numeric field is not a number (custom validator provider applies validation which is in english, and i wanted to get the values from my custom resource file which is globalized.

My goals were as follows:
  • Custom Required validation on the Client and Server side; validation using DataAnnotations (A custom one)
  • Localized message (The validation message just says “Required” in the given user’s language.)
  • Wanted the DataAnnotation attribute to be called JRequire and not require anything passed to the attribute (The normal RequiredAttribute allows you to assign the type of a resource file and the key and therefore localize validation messages. I want to avoid having to repeatedly assign these values: to make the code cleaner and make it quicker to apply)
  • Wanted to use jQuery.Validate for client side validation.
Phil Haack has a good article here on custom validation. It covers most of what i needed for this so I wont explain a lot of the following.

Contents:

  1. Custom Validation Attribute
  2. Custom Data Annotations Model Validator
  3. Registering Validation on the server side
  4. Registering Validation on the Client side
  5. Implicit Required Attribute Problem (DateTimes and ints are required for example!)
  6. Implicit Non-Numeric-Field problem

Custom Validation Attribute

[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple=false)]
public class JRequiredAttribute : ValidationAttribute
{
    public JRequiredAttribute()
    {
        ErrorMessageResourceName = "Required";
        ErrorMessageResourceType = typeof(ValidationMessages);
    }

    public override bool IsValid(object value)
    {
        if (value == null)
            return false;

        string str = value as string;

        if (String.IsNullOrEmpty(str))
            return false;

        return true;
    }
}
In the constructor I’m specifying the key in the resource file that contains the message. The second line is the type containing the resources. If I have a resource file for a given culture other than english and the user’s culture is set to that culture then the message will be shown in that culture instead of in english.

Note that I’ve set <globalization uiCulture="auto" culture="auto"/> in the configuration file.

The IsValid method is overridden and i return true if there is content and false if there is not. Fairly simple.

Custom Data Annotations Model Validator

public class JRequiredValidator : DataAnnotationsModelValidator
{
    public JRequiredValidator(
        ModelMetadata metadata,
        ControllerContext context,
        JRequiredAttribute attribute)
        : base(metadata, context, attribute)
    {
    }

    public override IEnumerable
    GetClientValidationRules()
    {
        var rule = new ModelClientValidationRule {
            ErrorMessage = ValidationMessages.Required,
            ValidationType = "jrequired"
        };

        return new [] {rule};
    }
}
This piece of code is is used to generate the client side JSON that is written to the page. The client side validation framework can then hook into this information to do the client side validation. Now for registration:

Registering Validation on the server side

This involved modifying the Global.asax.cs file. After registering the routes do the following:
DataAnnotationsModelValidatorProvider.RegisterAdapter(
    typeof(JRequiredAttribute),
    typeof(JRequiredValidator));

Registering Validation on the Client side

I’ll assume that you have the following JavaScript files registered on the page:
  • jQuery 1.4.*.js
  • jquery.validate.js
  • The MicrosoftMvcJQueryValidation.js file which can be downloaded from here (Download “ASP.NET MVC 2 Futures” and in explorer expand MvcFutures and you will see the javascript file there)
Once this is done you only have one more things to do and that is to extend jQuery.validate to use your new custom required attribute by adding a custom method:
jQuery.validator.addMethod("jrequired", function(value, element) {
    if (value == undefined || value == null)
        return false;

    if (value.toString().length == 0)
        return false;

    return true;
});
The above can be added to the top of the MicrosoftMvcJQueryValidation.js file (where is says “register custom jQuery methods”

Note the first parameter  to the addMethod.. er… method. This is the same as the ValidationType property set when we defined the JRequiredValidator class. The function takes the value and checks that it is not null (or undefined) and checks that if it has a length of 0 characters that we also return false; otherwise we know that the field has an appropriate value and we can return true. True meaning that the field is valid, false meaning it is not.

The above code is rough as we don’t check to see if there are any valid characters (for example a string of length 1 where the character is a space should not be valid in this case. The server side String.IsNullOrEmpty does do this check, but the client code doesn’t. As this was just to get things going i will update the above when I refine it (if i remember).

Implicit Required Attribute Problem

The above will work well on text fields (where your model has a string property), but when you try and apply this sort of logic onto DateTime properties etc. We have an issue because the framework automatically applies the required attribute to them :p not fair! :)

A solution to this is to make the property nullable and let the validation handle enforcing whether the property is valid… not very nice though.

I found another method which is to set the DataAnnotationsModelValidatorProvider’s AddImplicitRequiredAttributeForValueTypes to false (by default it’s true obviously!)
DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;

Implicit Non-Numeric-Field problem

The MVC framework automatically applies a couple of Model Validator Providers
  • DataAnnotationsModelValidatorProvider
  • DataErrorInfoModelValidatorProvider
  • ClientDataTypeModelValidatorProvider
I have no idea what the first two are, but i needed to replace the 3rd one’s error message, and it seems the only way to do that is to replace the whole ClientDataTypeModelValidatorProvider with a custom one that references the correct error message, remove the registered provider and register my own. So I created a file called CustomClientTypeModelValidatorProvider which containes the same code as the ClientDataTypeModelValidatorProvider class in the MVC framework with a change to the internal NumericModelValidator classes MakeErrorString() method. It is by default this:
private static string MakeErrorString(string displayName) {
    // use CurrentCulture since this message is intended for the site visitor
    return String.Format(CultureInfo.CurrentCulture,
        MvcResources.ClientDataTypeModelValidatorProvider_FieldMustBeNumeric,
        displayName);
}
And is now this:
private static string MakeErrorString(string displayName) {
    // use CurrentCulture since this message is intended for the site visitor
    return String.Format(CultureInfo.CurrentCulture,
        ValidationMessages.FieldMustBeNumeric);
}
... referencing my custom resource file.

Now to unregister the old and register the new
// Remove the item that validates fields are numeric!
foreach (ModelValidatorProvider prov in ModelValidatorProviders.Providers) {
    if (prov.GetType().Equals(typeof(ClientDataTypeModelValidatorProvider))) {
        ModelValidatorProviders.Providers.Remove(prov);
        break;
    }
}

// Add our own of the above with a custom message!
ModelValidatorProviders.Providers.Add(
    new CustomClientDataTypeModelValidatorProvider());