ReBuildAll Blog
Thoughts (mostly) on .NET development

Extending MVC validation with new rules   (MVC)   
Did you ever want to extend MVC validation with new rules? For example in my work I need to validate fields that contain Finnish social security numbers, business identification numbers or IBAN bank account numbers. These have a well defined format. They also have check digits/numbers that can be used to verify if the number is actually valid.

While I could use simply regular expressions to verify the format itself, it is not possible to validate the check digits / numbers without actually coding them.

If you have used data annotations to create your ASP.NET MVC models, you know that it is very easy to add different kinds of validation rules to your data model. ASP.NET MVC will take care of generating all the needed client side validation logic for you (which uses jQuery Validation) and also makes sure the data is validated on the server side as well.

In this article I will show how to extend this mechanism and provide easy to use client and server side custom validation using the same mechanics. I will use MVC 4 in this article, but the core validation works with MVC 3 as well (some examples use bundles, which were not in MVC 3, but the actual validation logic will work with MVC 3).

Javascript validation
Let us start by creating a validation method for IBAN account numbers in Javascript. We want to extend the jQuery Validation framework that comes with ASP.NET MVC project template. The following code directly adds a new validation method to jQuery Validation. First the code, then the explanation:
$.validator.addMethod("iban", function (value, element) {
    var conversion = {
        "A": 10, "B": 11, "C": 12, "D": 13, "E": 14, "F": 15, "G": 16,
        "H": 17, "I": 18, "J": 19, "K": 20, "L": 21, "M": 22, "N": 23,
        'O': 24, "P": 25, "Q": 26, "R": 27, "S": 28, "T": 29, 'U': 30,
        "V": 31, "W": 32, "X": 33, "Y": 34, "Z": 35
    };
    var numbers = "0123456789";

    var isValidIBAN = false;
    var ibanregex = /^([A-Z]{2}\d{2})\s?([A-Z0-9]{4}\s?)*([A-Z0-9]{1,4})$/;

    if (ibanregex.test(value)) {
        var iban = value.replace(/\s+/g, "").toUpperCase();
        iban = iban.substr(4) + iban.substr(0, 4);
        var convertediban = "";
        for (var i = 0; i < iban.length; ++i) {
            if (numbers.indexOf(iban.charAt(i), 0) == -1) {
                convertediban += conversion[iban.charAt(i)];
            }
            else {
                convertediban += iban.charAt(i);
            }
        }
        var reminder = 0;
        while (convertediban.length > 0) {
            var p = convertediban.length > 7 ? 7 : convertediban.length;
            reminder = parseInt(reminder.toString() + convertediban.substr(0, p), 10) % 97;
            convertediban = convertediban.substr(p);
        }

        if (reminder == 1) {
            isValidIBAN = true;
        }
    }

    return this.optional(element) || isValidIBAN;
}, "Check your IBAN number!");

$.validator.unobtrusive.adapters.addBool("iban");

The addMethod method will add our validation logic to jQuery Validate. This new function will receive the value to be validated. The actual validation logic might not be the nicest Javascript code, but what it does is check the value if it is indeed a IBAN number. I included a regular expression that does the format checking as well, so I do not need to add that separately.

After the jQuery Validate method is added, we also register the iban validation method with the jQuery Unobtrusive Validation framework. The addBool method adds a new validation that returns true/false based on the result.

Ideally you would put this code into your own Javascript file (for example, customvalidation.js). You need to include this AFTER jQuery, jQuery Validation and jQuery Unobtrusive Validation .js files have been included in your code. In a MVC view, you could add it as follows:
@section scripts
{
    @Scripts.Render("~/bundles/jqueryval")
    <script src='@Url.Content("~/Scripts/customvalidation.js")' />
}

And voila, client side IBAN account validation is now available to your HTML code. Add the data-val-iban attribute to the element where you want to test it. The following ASP.NET MVC sample demonstrates how to create a simple form with one field, that has iban validation enabled on the client side:
@using(Html.BeginForm())
{
    <div>
        <input type="text" name="ibanaccount" id="ibanaccount"
            data-val="true" data-val-iban="Check your IBAN number!" 
            data-val-required="Required"  />
        @Html.ValidationMessage("ibanaccount")
    </div>

    <div>
        <input type="submit" value="Submit" />
    </div>
}

I created the input field manually by typing HTML code and adding all the validation attributes. I exploited MVC's ValidationMessage() method to generate the needed span to display a error message in case validation fails.

You can use the following sample IBAN number from Wikipedia to test: GB29 NWBK 6016 1331 9268 19. Or just enter your own IBAN number :) The validation does not even require the spaces, so you can freely omit those if you like.

Our goal is however, that you would not need to write the attributes manually. Instead we want this to integrate with Data Annotations attributes.

Server side validation
In order to create a new server side validation we will derive a class from ValidationAttribute. To tell MVC that we support client side validation the class will also implement IClientValidatable.

The attribute will contain both the server side logic and the code needed to use the Javascript validation we created above.

Let's create the skeleton of the class:
    [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
    public sealed class IBANAttribute : ValidationAttribute, IClientValidatable
    {
    }

Lets start with the client side validation first. The IClientValidatable interface defines a single member method that returns an enumerable collection of client side rules. We will use yield return to return a single rule:
        public IEnumerable<ModelClientValidationRule> 
                  GetClientValidationRules(ModelMetadata metadata, 
                             ControllerContext context)
        {
            yield return new ModelClientValidationRule()
            {
                ErrorMessage = "Check IBAN account number!",
                ValidationType = "iban"
            };
        }

The rule simply states what error message we would want and what is the validation type. The rule will generate a data-val-iban attribute similar to what we had in our HTML before on the client side.

Finally, lets create the server side validation method.
        public override bool IsValid(object value)
        {
            if (value == null) return true;

            Dictionary<char,int> conversion = new Dictionary<char,int>();
            conversion.Add('A', 10);
            conversion.Add('B', 11);
            conversion.Add('C', 12);
            conversion.Add('D', 13);
            conversion.Add('E', 14);
            conversion.Add('F', 15);
            conversion.Add('G', 16);
            conversion.Add('H', 17);
            conversion.Add('I', 18 );
            conversion.Add('J', 19);
            conversion.Add('K', 20);
            conversion.Add('L', 21);
            conversion.Add('M', 22);
            conversion.Add('N', 23);
            conversion.Add('O', 24);
            conversion.Add('P', 25);
            conversion.Add('Q', 26);
            conversion.Add('R', 27);
            conversion.Add('S', 28 );
            conversion.Add('T', 29);
            conversion.Add('U', 30);
            conversion.Add('V', 31);
            conversion.Add('W', 32);
            conversion.Add('X', 33);
            conversion.Add('Y', 34);
            conversion.Add('Z', 35);

            bool isValidIBAN = false;
            Regex ibanregex = new Regex(@"^([A-Z]{2}\d{2})\s?([A-Z0-9]{4}\s?)*([A-Z0-9]{1,4})$");

            if (ibanregex.IsMatch(value.ToString())) {
                var iban = value.ToString().Replace(" ", "").ToUpper();
                iban = iban.Substring(4) + iban.Substring(0, 4);
                var convertediban = "";
                for (var j = 0; j < iban.Length; ++j) {
                    if ( char.IsDigit ( iban[j] ) == false ) {
                        convertediban += conversion[iban[j]];
                    }
                    else {
                        convertediban += iban[j];
                    }
                }
                var reminder = 0;
                while (convertediban.Length > 0) {
                    var p = convertediban.Length > 7 ? 7 : convertediban.Length;
                    reminder = int.Parse(reminder.ToString() + convertediban.Substring(0, p)) % 97;
                    convertediban = convertediban.Substring(p);
                }

                if (reminder == 1) {
                    isValidIBAN = true;
                }
            }

            return isValidIBAN;
        }

Notice how similar the code is to the Javascript version (thanks to the var keyword, for example). Except maybe the dictionary filling code, which is longer to do in C#. The method check the format using the same regex, and then validates the argument. It returns a boolean value based on the result.

This is all we needed to have both client and server side validation added for IBAN account numbers. Similar methods can be used to add validation to just about any kind of custom validation logic. As a last step, we will test how this works.

Putting our new attribute onto a model
Lets create a new model to test our validation logic.
    public class SimpleTestModel
    {
        [Required]
        [MaxLength(50)]
        public string Name { get; set; }

        [IBAN]
        [Required]
        public string AccountNumber { get; set; }
    }

And create a new action method that will use this model and also accept it back on POST requests.
        [HttpGet]
        public ActionResult TestIban()
        {
            SimpleTestModel model = new SimpleTestModel();
            return View(model);
        }

        [HttpPost]
        public ActionResult TestIban(SimpleTestModel model)
        {
            if (ModelState.IsValid)
            {
                // save
                // ..

                return RedirectToAction("Index");
            }
            else
            {
                return View(model);
            }
        }

And finally add a view. I will only show you the part that renders the account number field. You can easily generate the view based on the model class using MVC's scaffolding feature (use the Create or Edit template).
        <div class="editor-label">
            @Html.LabelFor(model => model.AccountNumber)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.AccountNumber)
            @Html.ValidationMessageFor(model => model.AccountNumber)
        </div>

As you can see, it does not differ from any other field you would have used previously in MVC. And now when you try this, you will see client side validation working. If you want to test server side validation you need to either disable the client side validation (just remove the interface from the attribute class) or use a tool like Fiddler to manipulate the request before it gets to the server.

Summary
Adding client and server side validation to ASP.NET MVC is an easy task if you follow the steps outlined here. You can add as many different kinds of validations as you need.

Plug & Play MVC   (MVC)   
This entry will be available in Finnish only - it is related to the TechDays 2011 speech on 31.3 about extending MVC framework.

Pidin esityksen TechDays 2011 MVC kehikon laajennuksesta. Esityksessä käsiteltiin miten MVC kehikkoa voi laajentaa eri paikoilla.

Itse kehikko on iso kokoelma pieniä osia mitkä toimivat hyvin yhdessä. Kuitenkin näitä pieniä osia voidaan muokata ja räätälöidä, tai jopa tehdä omia osia mitkä käytetään alkuperäisten sijasta. Näin voimme räätälöidä itse kehikonkin että se toimii juuri niin kuin me haluamme.

Näitä laajennuspisteitä on monta, ja esitys yrittää antaa kartan näihin pisteisiin. Ohjeiden perusteella pitäisi olisi helppo löytää juuri se laajennuskohta, mikä antaa mahdollisimman paljon hyötyä.

Esityksen PDF kalvot ovat ladattavissa täältä.

Esityksen demo sovellus on myös ladattava täältä.

Demo vaatii Visual Studio 2010 version (Express pitäisi toimia myös) sekä SQL Server Express version. Kun sovellusta käynnistää ensimmäinen kertaa, se luo itselleen tietokannan (App_Data kansioon). Kuitenkin pitää käsin ajaa alustus. Se löytyy /Home/Setup osoitteesta sovelluksen alta (eli HomeController:in Setup action).

jQuery validate and the comma decimal separator   (MVC)   
In response to the comments I have received to the original article, I have updated it with information how to modify the behavior of the jQuery validator plugin without directly editing the source.

While working with MVC 3, I hit some problems with validation. MVC 3 uses jQuery validation plugin with unobtrusive validation to help you out. Except when I had to use Finnish formatted numbers, there was a problem. jQuery/javascript did not really like the comma as a decimal separator.

A similar problem is with the range validation, which also refuses to work when using a comma as the decimal separator.

A rather nasty set of problems I might say, especially since number validation rules are added AUTOMATICALLY by ASP.NET MVC if you render a TextBox for a Decimal field in the model. It just seems the writer of the validator plugin forgot about everyone outside US (thanks @Deef for this last comment).

Fixing jquery.validate.js

You can of course go into the source of the validator javascript file and make your changes there, but modifying this file should never be done directly. That would create all kinds of nasty problems when updating to future versions.

Instead, one should take advantage of extending (replacing) the validator functions that are provided by jQuery Validate. (thanks to @ruben for the information on this).

To fix these problems, we can take the default implementation from the jquery.validate.js file for the range() and number() functions. We then create another .js file (say jQueryFixes.js), in which we override these default functions with ones that contain support for the comma as a decimal separator. The contents of the file should be something like this:

$.validator.methods.range = function (value, element, param) {
    var globalizedValue = value.replace(",", ".");
    return this.optional(element) || (globalizedValue >= param[0] && globalizedValue <= param[1]);
}

$.validator.methods.number = function (value, element) {
    return this.optional(element) || /^-?(?:\d+|\d{1,3}(?:[\s\.,]\d{3})+)(?:[\.,]\d+)?$/.test(value);
}


This should be the last .js file you include, so it overrides the behavior of the default functions from the jQuery Validator plugin.

For number validation, what we have done is replace the last dot with an expression that also accepts a comma (change is bolded):

return this.optional(element) || /^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:[\.,]\d+)?$/.test(value);

This will solve the number validation problem with the decimal point/comma. If you are dealing with thousand separator, you might need to tweak this further.

As for the range validation, I opted to simply replace the comma with a dot, and proceed normally from there on. You could of course use the jquery globalization plugin, please see the comments section for @ruben's recommendation on that one.

And that is about it. Now decimal separator commas should also be accepted by the validators.

Serve MVC Views from a ZIP file   (MVC)   
Some time ago I wrote an article about MVC and virtual views. I made a comment that you could create a VirtualPathProvider that would serve content from a ZIP file. Some of you commented you would like to see an implementation for that. Well, here it is.

Please read that article for more information on how to implement a VirtualPathProvider. Here I will not go into details on everything involved.

You can download the entire example solution (Visual Studio 2010 and MVC 3 required!) from here.

The example should run as soon as you load it up in VS2010. Try to request the following addresses (relative to the application root): /Test and Test/Complex for non zipped versions of views. And /TestZipped and /TestZipped/Complex for zipped versions of the views.


How to extract files from a .ZIP file?

For this example, I will use SharpZipLib, which a freely available .zip file management library. It is already included in the example solution I linked at the beginning of this article. So if you downloaded that you will not need to download SharpZipLib separately.


Preparing to serve Views from a .ZIP file

I decided that the content I want to serve will be stored in a file called Views.zip, that needs to be stored at the root of the application (~/Views.zip). This .zip file has to have the same directory structure as the real MVC application. So we need a Views folder, and inside that, the controller folder. Inside the controller folder (or folders) there will be the actual views.

For the example solution I put the views for the "TestZipped" controller into the Views.zip file. There are two views, Index.cshtml and Complex.cshtml. Both include Razor markup to demonstrate that the solution demonstrated is actually fully functional. You could of course pack up the entire Views folder in the example solution, and it will still work :)

(I did try this, and it did work. If you do it, make sure you still leave web.config in place, as that is outside the scope of VirtualPathProvider. But you can just put everything else in the Views/ folder into the .ZIP file, and delete them from the actual folder. The application will still work! :))

The directory structure inside the .zip will make it easy to find and extract the files. Because the VirtualPathManager receives virtual file names, like /Views/TestZipped/Complex.cshtml, when we have the same structure in the .zip, it is easy to string compare the names.

Of course you could implement as complex a logic as you want to find the proper file :)


Creating a VirtualPathProvider for serving content from .ZIP files

The next step is to create a new VirtualPathProvider. Please note that the implementation I provide is for demonstration purposes only, and might not be fully ready for production use.

    public class ZippedVirtualPathProvider : VirtualPathProvider
    {
        public static void RegisterMe()
        {
            HostingEnvironment.RegisterVirtualPathProvider(new ZippedVirtualPathProvider());
        }

I also created a little helper method to register our path provider.

We will override FileExists(), GetFile() and GetCacheDependency(). Let's start with FileExists().

We simply want to check if we have the .zip file is available and if the file can be found in the .zip file. If both conditions are true, we can indicate we found the file. Otherwise, we let the default implementation take over and perform any checks needed.

        public override bool FileExists(string virtualPath)
        {
            if (IsViewsZipFound && FileExistsInZip(virtualPath))
            {
                return true;
            }
            else
            {
                return base.FileExists(virtualPath);
            }
        }

        private bool IsViewsZipFound
        {
            get
            {
                if (File.Exists(ViewsZipPath))
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
        }

        private static string ViewsZipPath
        {
            get
            {
                return HttpContext.Current.Server.MapPath ( "~/Views.zip" );
            }
        }

        private static string TransformVirtualPath2ZipPath(string virtualPath)
        {
            string fullPath = virtualPath;
            if (fullPath.StartsWith("~/"))
            {
                fullPath = fullPath.Substring(2);
            }
            if (fullPath.StartsWith("/"))
            {
                fullPath = fullPath.Substring(1);
            }
            return fullPath;
        }

        private bool FileExistsInZip(string virtualPath)
        {
            string zipPath = TransformVirtualPath2ZipPath(virtualPath);

            using (ZipInputStream s = new ZipInputStream(File.OpenRead(ViewsZipPath)))
            {
    			ZipEntry theEntry;
                while ((theEntry = s.GetNextEntry()) != null)
                {
                    if (theEntry.IsFile && theEntry.Name == zipPath)
                    {
                        return true;
                    }
                }
            }

            return false;
        }

So to check if the .zip exists, we just use the System.IO.File.Exists() method. To verify if the file exists, we read the .zip file entries and try to find one that matches our file. If we find it, we return true. Simple enough.

For getting the file, we use similar code:

        public override VirtualFile GetFile(string virtualPath)
        {
            if (IsViewsZipFound)
            {
                var file = ExtractFromZip(virtualPath);
                if (file != null)
                {
                    return file;
                }
            }

            return base.GetFile(virtualPath);
        }

        private VirtualFile ExtractFromZip (string virtualPath)
        {
            string zipPath = TransformVirtualPath2ZipPath(virtualPath);

            using (ZipInputStream s = new ZipInputStream(File.OpenRead(ViewsZipPath)))
            {
                ZipEntry theEntry;
                while ((theEntry = s.GetNextEntry()) != null)
                {
                    if (theEntry.IsFile && theEntry.Name == zipPath)
                    {
                        byte[] data = new byte[theEntry.Size];
                        s.Read(data, 0, data.Length);
                        return new ZippedVirtualFile(virtualPath, data);
                    }
                }
            }

            return null;
        }

Here we check if the .zip file exists, and then try to extract the file from it. If it is found, it is also returned. A new class called ZippedVirtualFile is used to return the file, this will be introduced a little later below.

Finally, we override the GetCacheDependency() method, because MVC uses that to monitor when it needs to rebuild/reread a file. What we will do is instead of returning a cache dependency on the file, we return a dependency on the .zip file. If the .zip file changes we will assume our file changes. Not very robust, but will work for this example.

        public override System.Web.Caching.CacheDependency GetCacheDependency(string virtualPath, System.Collections.IEnumerable virtualPathDependencies, DateTime utcStart)
        {
            if (IsViewsZipFound && FileExistsInZip(virtualPath))
            {
                return new System.Web.Caching.CacheDependency(ViewsZipPath, utcStart); 
            }
            else
            {
                return base.GetCacheDependency(virtualPath, virtualPathDependencies, utcStart);
            }
        }


ZippedVirtualFile

We need a little helper class that will hold our extracted file, and return a stream to it on demand.

    public class ZippedVirtualFile : VirtualFile
    {
        public byte[] data;

        public ZippedVirtualFile(string virtualPath, byte[] data)
            : base(virtualPath)
        {
            this.data = data;
        }

        public override System.IO.Stream Open()
        {
            return new MemoryStream(data);
        }
    }

The class is very simple, it simply holds a byte array, and when a stream is requested, returns a new MemoryStream that can be used to read the contents.


Using the ZippedVirtualPathProvider

As a final step, we need to register our provider. This can be done in global.asax.cs by adding the following code to the Application_Start() method:

        protected void Application_Start()
        {
            // ...

            ZippedVirtualPathProvider.RegisterMe();
        }


And we are ready to go! (As soon as we create the Views.zip file of course).



ASP.NET MVC and virtual views   (MVC)   
Update 1.3.2011: I have created another example for virtual views that you might be interested in. It shows you how to use virtual views by serving them directly from a .ZIP file.

In this post I will describe how to create virtual views for ASP.NET MVC. These are views that do not exist as files in the regular place in the file system (~/Views/xxx/yyy.aspx), instead they are stored somewhere. Elsewhere in this case can be a database or perhaps a .ZIP file. This is all made possible by the VirtualPathProvider class (and some supporting classes) in ASP.NET. While the example will use MVC framework and views, the class provides much more. With it you can virtualize any web content (css, gif/jpg, js, aspx, etc) and it can be used in any ASP.NET application, nut just MVC.

Use cases

When would you need such a virtual view system?

If you want users to be able to customize Views, but without them having access to the Web solution or source code. In this case the user could upload the View into the database, where it is stored. When the application wants to display the view, instead of reading the view source from the file system it will be read from the database. MVC will not know the difference and believes it is found under the regular ~/Views/... path.

Another possibility would be to use a ZIP file based virtual path provider. Here the user could create a custom skin by creating or customizing views (.ascx, .aspx) and adding new content (css, images). The user would then pack it into a ZIP, upload into the server, and server would serve the custom skin directly from the .ZIP file. This way with multiple skins installed the file system would not be polluted by a vast amount of files.

There are of course many more possibilities I could think of, but I hope you get the point ;-)

Getting ready

Before we begin, lets create a simple database table that will store the data for our example. It is very badly designed I have to admit :), but will serve our purpose.

I called the table Pages and the idea is that it will serve dynamic pages into my MVC application. Kind of like a very simple CMS. However, I want to customize the view layout (.aspx), and not just the data itself. So I ended up with a table like this:

a picture in a bl0g

The table contains the data for the page (Body, Title) and the name/virtual path of the view (ViewName) and of course the view file itself (ViewData, uploaded as binary). Here is an example row. I uploaded a simple .ASPX view into the row.

a picture in a bl0g

In my example solution I added a LINQ to SQL .dbml to the solution (named MyMvcVp), dragged and dropped my table into the designer. I could then use the generated data context to access the database.

Controller

Next is my PagesController.cs that will serve our dynamic content pages. To display such a page I decided to use default route already available in the MVC template: /controller/action/id. I added a single Display action to the controller which will get the id parameter as a string value. This will have to correspond to the PageId field in the database. For example: /Pages/Display/d9b07a02-7c47-41d9-8c21-bf546841bb6c. The resulting code follows:

    public class PagesController : Controller
    {
        public ActionResult Display ( string id )
        {
            Guid guid = new Guid ( id );

            MyMvcVpDataContext context = new MyMvcVpDataContext ();

            var res =
                (from p in context.Pages
                 where p.PageId == guid
                 select p).SingleOrDefault ();

            if ( res == null )
            {
                return RedirectToAction ( "Index", "Home" );
            }

            ViewData["Title"] = res.Title;
            ViewData["Body"] = res.Body;

            return View ( System.IO.Path.GetFileNameWithoutExtension ( res.ViewName ) );
        }
    }

The code is very simple: it uses LINQ to look for the page, and if found, we extract the data from it, and instruct MVC to show it using our ViewName. To make the example very simple I included the virtual path in the database column, so here I use GetFileNameWithoutExtension() to get just the View name.

Because I wanted to keep the example simple I decided not to support virtual folders (which is also possible). So the folder where the virtual files seem to reside needed to be created also. I created a new Pages folder under /Views in the solution. If you implement virtual folders than this step could be left out.

At this point we still miss the actual VirtualPathProvider implementation. Without that you will get an error message when accessing this action, because MVC will not find the View.

VirtualPathProvider
So I added a new file MyVirtualPathProvider.cs to the solution. This contains the MyVirtualPathProvider class, that derives from VirtualPathProvider. I overrided two methods, FileExists() and GetFile(). Both get the virtual path (application relative). The first checks if a file exists, and the second returns the file from the file system - in this case the database.

Here is the code I used. Note that Page in this case refers to the LINQ class that was generated for the Pages table, and not the ASP.NET Page class.

    public class MyVirtualPathProvider : VirtualPathProvider
    {
        public override bool FileExists ( string virtualPath )
        {
            var page = FindPage ( virtualPath );
            if ( page == null )
            {
                return base.FileExists ( virtualPath );
            }
            else
            {
                return true;
            }
        }

        public override VirtualFile GetFile ( string virtualPath )
        {
            var page = FindPage ( virtualPath );
            if ( page == null )
            {
                return base.GetFile ( virtualPath );
            }
            else
            {
                return new MyVirtualFile ( virtualPath, page.ViewData.ToArray () );
            }
        }

        private Page FindPage ( string virtualPath )
        {
            MyMvcVpDataContext context = new MyMvcVpDataContext ();

            var page =
                (from p in context.Pages
                 where p.ViewName == virtualPath
                 select p).SingleOrDefault ();
            return page;
        }
    }

As you can see if I don't find the virtual path in question from the database, the base implementation is called. That will just try to look up the file from the regular file system location.

You should notice that I used a class that I did not yet talk about: MyVirtualFile. This is just a simple implementation of the VirtualFile abstract class. Its purpose is to return a Stream where the file can be read. In my example I will just return a MemoryStream using the data from the database table. But this is where you would read the file from the actual place and pass it back to the framework.

    public class MyVirtualFile : VirtualFile
    {
        private byte[] data;

        public MyVirtualFile ( string virtualPath, byte[] data ) : base ( virtualPath )
        {
            this.data = data;
        }

        public override System.IO.Stream Open ()
        {
            return new MemoryStream ( data );
        }
    }

If you wanted to support virtual directories you would also override the DirectoryExists() and GetDirectory() methods from the VirtualPathProvider. You would also need a custom VirtualDirectory derived class.

Registering the custom VirtualPathProvider

As a last step the MyVirtualPathProvider must be registered with ASP.NET. To accomplish this you need to add the following call to the application:

HostingEnvironment.RegisterVirtualPathProvider ( new MyVirtualPathProvider () );

The best place is probably in the Init() override of the application class or perhaps the Application_Start event handler (global.asax, unless you coded a custom application class somewhere in which case that would be the ideal place).

Conclusion

Virtual files and directories can be used to extend an existing MVC application (or a Web Forms application) by allowing you to store files in a place that is different from the regular file system. Be it a database, ZIP file or a remote network location, by using VirtualPathProvider the rest of the application will not have to know anything about the actual storage strategy.

Extending ASP.NET MVC HtmlHelper and Gravatars   (MVC)   
I got excited about Gravatars the other day and decided to add support for them into my blog. You can read more about Gravatars and how they can be used in your project on the following page:

Gravatar

Having used ASP.NET MVC, the view that was displaying the comments had something like the following in them. This displayed the name of the commenter being a hyperlink, and that pointing to a mailto: link with the email address. The address was applied some transformations to prevent spammers from picking them from the page automatically:

<%= Html.Encode ( SomeBlogNamespace.SpamFreeEmail ( Model.Comment.CommentedEmail ) )%>

Now I did not want to change the model at all, but still wanted to add support for Gravatars. For this, I needed a method that could calculate the MD5 hash and display it in the format Gravatar wants it.

As I have my own base classes and model classes, I could have added the code there but decided to extend the MVC HtmlHelper instead. Using extension methods, that is really simple. I just added a new class to hold my extension method:

    public static class GravatarHelper
    {
        public static string GravatarHash ( this HtmlHelper html, string value )
        {
            var byteArray =
                   MD5CryptoServiceProvider.Create ().ComputeHash (
                        System.Text.ASCIIEncoding.ASCII.GetBytes ( value.ToLower () ) );

            string result = string.Empty;
            foreach ( var item in byteArray )
            {
                result += item.ToString ( "x2" );
            }

            return result;
        }
    }


I also needed to add a namespace import into the .ASCX file that contained my comments view:

<%@ Import Namespace="namespace" %>


After that I could change the view to display the picture from Gravatar by calculating the hash from within the view code:

        <%= Html.GravatarHash ( Model.Comment.CommentedEmail ) %>

If you make a comment into this blog now, you can take advantage of Gravatars by giving your Gravatar registered email address. If you have a registered avatar the blog will display the image beside your comment.

This same method can be used to extend HtmlHelper in many different ways, adding small (or big) utilities that you can code and take advantage from the View in ASP.NET MVC.