Maarten Balliauw {blog}

ASP.NET MVC, Microsoft Azure, PHP, web development ...

NAVIGATION - SEARCH

How we built TwitterMatic.net - Part 2: Creating an Azure project

TwitterMatic - Schedule your Twitter updates “Knight Maarten The Brave Coffeedrinker was about to start working on his TwitterMatic application, named after the great god of social networking, Twitter. Before he could start working, he first needed the right tools. He downloaded the Windows Azure SDK, a set of tools recommended by the smith (or was it the carpenter?) of the digital village. Our knight’s work shack was soon ready to start working. The table on which the application would be crafted, was still empty. Time for action, the knight thought. And he started working.”

This post is part of a series on how we built TwitterMatic.net. Other parts:

kick it on DotNetKicks.com

Creating an Azure project

Here we go. After installing the Windows Azure SDK, start a new project: File > New > Project... Next, add a “Web And Worker Cloud Service”, located under Visual C# (or VB...) > Cloud Service > Web And Worker Cloud Service.

The project now contains three projects:

TwitterMatic Visual Studio Solution

The web role project should be able to run ASP.NET, but not yet ASP.NET MVC. Add /Views, /Controllers, Global.asax.cs, references to System.Web.Mvc, System.Web.Routing, … to make the web role an ASP.NET MVC project. This should work out fine, except for the tooling in Visual Studio. To enable ASP.NET MVC tooling, open the TwitterMatic_WebRole.csproj file using Notepad and add the following: (copy-paste: {603c0e0b-db56-11dc-be95-000d561079b0})

Enable ASP.NET MVC tooling in Windows Azure project

Visual Studio will prompt to reload the project, allow this by clicking the "Reload" button.

(Note: we could have also created a web role from an ASP.NET MVC project, check my previous series on Azure to see how to do this.)

Sightseeing

True, I ran over the previous stuff a bit fast, as it is just plumbing ASP.NET MVC in a Windows Azure Web Role. I have written about this in more detail before, check my previous series on Azure. There are some other things I would like to show you though.

The startup project in a Windows Azure solution contains some strange stuff:

TwitterMatic startup project

This project contains 3 types of things: the roles in the application, a configuration file and a file describing the required configuration entries. The “Roles” folder contains a reference to the projects that perform the web role on one side, the worker role on the other side. ServiceConfiguration.cscfg contains all configuration entries for the web and worker roles, such as the number of instances (yes, you can add more servers simply by putting a higher integer in there!) and other application settings such as storage account info.

ServiceDefinition.csdef is basically a copy of the other config file, but without the values. If you are wondering why: if someone screws with ServiceConfiguration.cscfg when deploying the application to Windows Azure, the deployment interface will know that there are settings missing or wrong. Perfect if someone else will be doing deployment to production!

Conclusion

We now have a full Windows Azure solution, with ASP.NET MVC enabled! Time to grab a prefabricated CSS theme, add a logo, … Drop-in ASP.NET MVC themes can always be found at http://www.asp.net/mvc/gallery. The theme we used should be there as well (thanks for that, theme author!).

In the next part of this series, we’ll have a look at where and how we can store our data.

kick it on DotNetKicks.com

How we built TwitterMatic.net - Part 1: Introduction

TwitterMatic “Once upon a time, Microsoft started a Windows Azure developing contest named new CloudApp();. While it first was only available for US candidates, the contest was opened for international submissions too. Knight Maarten The Brave Coffeedrinker and his fellow knightsmen at RealDolmen decided to submit a small sample application that could be hosted in an unknown environment, known by the digital villagers as “the cloud”. The application was called TwitterMatic, named after the great god of social networking, Twitter. It would allow digital villagers to tell the latest stories, even when they were asleep or busy working.”

There, a nice fairy tale :-) It should describe the subject of a blog post series that I am starting around the techncal background of TwitterMatic, our contest entry for the new CloudApp(); contest. Now don't forget to vote for us between 10 July and 20 July!

Some usage scenario’s for TwitterMatic:

  • Inform your followers about interesting links at certain times of day
  • Stay present on Twitter during your vacation
  • Maintain presence in your activity stream, even when you are not available
  • Never forget a fellow Twitterer's birthday: schedule it!
  • Trick your boss: of course you are Tweeting you're leaving the office at 8 PM!

Perfect excuses to build our application for the clouds. Now for something more interesting: the technical side!

If you are impatient and immediately want the source code for TwitterMatic, check http://twittermatic.codeplex.com.

kick it on DotNetKicks.com

TwitterMatic architectural overview

Since we’re building a demo application, we thought: why not make use of as much features as possible which Windows Azure has to offer? We’re talking web role, worker role, table storage and queue storage here!

  • The web role will be an application built in ASP.NET MVC, allowing you to schedule new Tweets and view archived scheduled Tweets.
  • The worker role will monitor the table storage for scheduled Tweets. If it’s time to send them, the Tweet will be added to a queue. This queue is then processed by another thread in the worker role, which will publish the Tweet to Twitter.
  • We’ll be using OAuth, delegating authentication for TwitterMatic to Twitter itself. This makes it easier for us: no need to store credentials, no need to maintain a user database, …
  • The web role will perform validation of the domain using data annotations. More on this in one of the next parts.

For people who like images, here’s an architecture image:

TwitterMatic Architecture

What’s next?

RealDolmen Windows Azure The next parts of this series around Windows Azure will be focused on the following topics:

Stay tuned during the coming weeks! And don’t forget to start scheduling Tweets using TwitterMatic.

kick it on DotNetKicks.com

Announcing: Azure User Group Belgium

After the summer, I'll be joining Kurt Claeys and Yves Goeleven and for the start of the Azure User Group Belgium (or AZUG.BE).  AZUG.BE is a Belgian user group with focus on development and architecture of the Microsoft Azure Services Platform. Azure is a cloud hosted development platform for internet oriented applications aimed at high scalability and based on .NET technology.

azuglogo

Our goal is to share knowledge and experiences with the .NET community in development and architecture in the Azure Services Platform and the .NET Services technology. As new programming skills and a new architectural approach are needed we are inviting you to become member of this user group to prepare you for building this new style of applications.

We’ll have regular public meetings (free of charge) providing demos, training, presentations and discussions on the technology part of Azure. First public meeting is planned in september 09. E-mail us at newsletter@azug.be with the word subscribe as subject. We’ll inform you on what, when and where.

Interested in participating as boardmember ? Shoot a mail at board@azug.be.

Hope to see you!

Application-wide action filters in ASP.NET MVC

Ever had a team of developers using your ASP.NET MVC framework? Chances are you have implemented some action filters (i.e. for logging) which should be applied on all controllers in the application. Two ways to do this: kindly ask your developers to add a [Logging] attribute to the controllers they write, or kindly ask to inherit from SomeCustomControllerWithActionsInPlace.

If you have been in this situation, monday mornings, afternoons, tuesdays and other weekdays are in fact days where some developers will forget to do one of the above. This means no logging! Or any other action filters that are executed due to a developer that has not been fed with enough coffee… Wouldn’t it be nice to have a central repository where you can register application-wide action filters? That’s exactly what we are going to do in this blog post.

Note: you can in fact use a dependency injection strategy for this as well, see Jeremy Skinner’s blog.

Download the example code: MvcGlobalActionFilter.zip (24.38 kb)

kick it on DotNetKicks.com

The idea

Well, all things have to start with an idea, otherwise there’s nothing much left to do. What we’ll be doing in our solution to global action filters is the following:

  1. Create a IGlobalFilter interface which global action filters have to implement. You can discuss about this, but I think it’s darn handy to add some convenience methods like ShouldBeInvoked() where you can abstract away some checks before the filter is actually invoked.
  2. Create some IGlobalActionFilter, IGlobalResultFilter, IGlobalAuthorizationFilter, IGlobalExceptionFilter interfaces, just for convenience to the developer that is creating the global filters. You’ll see the use of this later on.
  3. Create a GlobalFilterActionInvoker, a piece of logic that is set on each controller so the controller knows how to call its own action methods. We’ll use this one to inject our lists of global filters.
  4. Create a GlobalFilterControllerFactory. I’m not happy with this, but we need it to set the GlobalFilterActionInvoker instance on each controller when it is created.

IGlobalFilter, IGlobalActionFilter, …

Not going to spend too much time on these. Actually, these interfaces are just descriptors for our implementation so it knows what type of filter is specified and if it should be invoked. Here’s a bunch of code. No comments.

[code:c#]

public interface IGlobalFilter
{
    bool ShouldBeInvoked(ControllerContext controllerContext);
}

public interface IGlobalAuthorizationFilter : IGlobalFilter, IAuthorizationFilter

public interface IGlobalActionFilter : IGlobalFilter, IActionFilter { }

public interface IGlobalResultFilter : IGlobalFilter, IResultFilter { }

public interface IGlobalExceptionFilter : IGlobalFilter, IExceptionFilter { }

[/code]

And yes, I did suppress some Static Code Analysis rules for this :-)

GlobalFilterActionInvoker

The GlobalFilterActionInvoker will take care of registering the global filters and making sure each filter is actually invoked on every controller and action method in our ASP.NET MVC application. Here’s a start for our class:

[code:c#]

public class GlobalFilterActionInvoker : ControllerActionInvoker
{
    protected FilterInfo globalFilters;

    public GlobalFilterActionInvoker()
    {
        globalFilters = new FilterInfo();
    }

    public GlobalFilterActionInvoker(FilterInfo filters)
    {
        globalFilters = filters;
    }

    public GlobalFilterActionInvoker(List<IGlobalFilter> filters)
        : this(new FilterInfo())
    {
        foreach (var filter in filters)
            RegisterGlobalFilter(filter);
    }

    public FilterInfo Filters
    {
        get { return globalFilters; }
    }

    // - more code -

}

[/code]

We’re providing some utility constructors that take a list of global filters and add it to the internal FilterInfo instance (which is an ASP.NET MVC class we can leverage in here). RegisterGlobalFilter() will do the magic of adding filters to the right collection in the FilterInfo instance.

[code:c#]

public void RegisterGlobalFilter(IGlobalFilter filter)
{
    if (filter is IGlobalAuthorizationFilter)
        globalFilters.AuthorizationFilters.Add((IGlobalAuthorizationFilter)filter);

    if (filter is IGlobalActionFilter)
        globalFilters.ActionFilters.Add((IGlobalActionFilter)filter);

    if (filter is IGlobalResultFilter)
        globalFilters.ResultFilters.Add((IGlobalResultFilter)filter);

    if (filter is IGlobalExceptionFilter)
        globalFilters.ExceptionFilters.Add((IGlobalExceptionFilter)filter);
}

[/code]

One override left in our implementation: ControllerActionInvoker, the class we are inheriting from, provides a method named GetFilters(), which is used to get the filters for a specific controller context. Ideal one to override:

[code:c#]

protected override FilterInfo GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
    FilterInfo definedFilters = base.GetFilters(controllerContext, actionDescriptor);

    foreach (var filter in Filters.AuthorizationFilters)
    {
        IGlobalFilter globalFilter = filter as IGlobalFilter;
        if (globalFilter == null ||
            (globalFilter != null && globalFilter.ShouldBeInvoked(controllerContext)))
        {
            definedFilters.AuthorizationFilters.Add(filter);
        }
    }

    // - same for action filters -

    // - same for result filters -

    // - same for exception filters -

    return definedFilters;
}

[/code]

Basically, we are querying our IGlobalFilter if it should be invoked for the given controller context. If so, we add it to the FilterInfo object that is required by the ControllerActionInvoker base class. Piece of cake!

GlobalFilterControllerFactory

I’m not happy having to create this one, but we need it to set the GlobalFilterActionInvoker instance on each controller that is created. Otherwise, there is no way to specify our global filters on a controller or action method… Here’s the class:

[code:c#]

public class GlobalFilterControllerFactory : DefaultControllerFactory
{
    protected GlobalFilterActionInvoker actionInvoker;

    public GlobalFilterControllerFactory(GlobalFilterActionInvoker invoker)
    {
        actionInvoker = invoker;
    }

    public override IController CreateController(System.Web.Routing.RequestContext requestContext, string controllerName)
    {
        IController controller = base.CreateController(requestContext, controllerName);
        Controller controllerInstance = controller as Controller;
        if (controllerInstance != null)
        {
            controllerInstance.ActionInvoker = actionInvoker;
        }
        return controller;
    }
}

[/code]

What we do here is let the DefaultControllerFactory create a controller. Next, we simply set the controller’s ActionInvoker property to our GlobalFilterActionInvoker .

Plumbing it all together!

To plumb things together, add some code in your Global.asax.cs class, under Application_Start:

[code:c#]

protected void Application_Start()
{
    RegisterRoutes(RouteTable.Routes);

    ControllerBuilder.Current.SetControllerFactory(
        new GlobalFilterControllerFactory(
            new GlobalFilterActionInvoker(
                new List<IGlobalFilter>
                {
                    new SampleGlobalTitleFilter()
                }
            )
        )
    );
}

[/code]

We are now setting the controller factory for our application to GlobalFilterControllerFactory, handing it a GlobalFilterActionInvoker which specifies one global action filter: SampleGlobalTitleFilter.

Sidenote: SampleGlobalTitleFilter

As a sidenote, I created a sample result filter named SampleGlobalTitleFilter, which is defined as a global filter that always appends a string (“ – Sample Application”) to the page title. Here’s the code for that one:

[code:c#]

public class SampleGlobalTitleFilter : IGlobalResultFilter
{
    public bool ShouldBeInvoked(System.Web.Mvc.ControllerContext controllerContext)
    {
        return true;
    }

    public void OnResultExecuted(System.Web.Mvc.ResultExecutedContext filterContext)
    {
        return;
    }

    public void OnResultExecuting(System.Web.Mvc.ResultExecutingContext filterContext)
    {
        if (filterContext.Controller.ViewData["PageTitle"] == null)
            filterContext.Controller.ViewData["PageTitle"] = "";

        string pageTitle = filterContext.Controller.ViewData["PageTitle"].ToString();

        if (!string.IsNullOrEmpty(pageTitle))
            pageTitle += " - ";

        pageTitle += "Sample Application";

        filterContext.Controller.ViewData["PageTitle"] = pageTitle;
    }
}

[/code]

Conclusion

Download the sample code: MvcGlobalActionFilter.zip (24.38 kb)

There is no need for my developers to specify SampleGlobalTitleFilter on each controller they write. There is no need for my developers to use the ControllerWithTitleFilter base class. People can come in and even develop software without drinking 2 liters of coffee! Really, development should not be hard for your developers. Make sure all application-wide infrastructure is there and our people are ready to go. And I’m really loving ASP.NET MVC’s extensibility on that part!

kick it on DotNetKicks.com

Revised: ASP.NET MVC and the Managed Extensibility Framework (MEF)

A while ago, I did a blog post on combining ASP.NET MVC and MEF (Managed Extensibility Framework), making it possible to “plug” controllers and views into your application as a module. I received a lot of positive feedback as well as a hard question from Dan Swatik who was experiencing a Server Error with this approach… Here’s a better approach to ASP.NET MVC and MEF.

kick it on DotNetKicks.com

The Exception

Server Error

The stack trace was being quite verbose on this one:

InvalidOperationException

The view at '~/Plugins/Views/Demo/Index.aspx' must derive from ViewPage, ViewPage<TViewData>, ViewUserControl, or ViewUserControl<TViewData>.

at System.Web.Mvc.WebFormView.Render(ViewContext viewContext, TextWriter writer) at System.Web.Mvc.ViewResultBase.ExecuteResult(ControllerContext context) at System.Web.Mvc.ControllerActionInvoker.InvokeActionResult(ControllerContext controllerContext, ActionResult actionResult) at System.Web.Mvc.ControllerActionInvoker.<>c__DisplayClass11.<InvokeActionResultWithFilters>b__e() at System.Web.Mvc.ControllerActionInvoker.InvokeActionResultFilter(IResultFilter filter, ResultExecutingContext preContext, Func`1 continuation) at System.Web.Mvc.ControllerActionInvoker.<>c__DisplayClass11.<>c__DisplayClass13.<InvokeActionResultWithFilters>b__10() at System.Web.Mvc.ControllerActionInvoker.InvokeActionResultWithFilters(ControllerContext controllerContext, IList`1 filters, ActionResult actionResult) at System.Web.Mvc.ControllerActionInvoker.InvokeAction(ControllerContext controllerContext, String actionName) at System.Web.Mvc.Controller.ExecuteCore() at System.Web.Mvc.ControllerBase.Execute(RequestContext requestContext) at System.Web.Mvc.ControllerBase.System.Web.Mvc.IController.Execute(RequestContext requestContext) at System.Web.Mvc.MvcHandler.ProcessRequest(HttpContextBase httpContext) at System.Web.Mvc.MvcHandler.ProcessRequest(HttpContext httpContext) at System.Web.Mvc.MvcHandler.System.Web.IHttpHandler.ProcessRequest(HttpContext httpContext) at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)

Our exception seemed to be thrown ONLY when the following conditions were met:

  • The View was NOT located in ~/Views but in ~/Plugins/Views (or other path)
  • The View created in our MEF plugin was strong-typed

Problem one… Forgot to register ViewTypeParserFilter…

Allright, go calling me stupid… Our ~/Plugins/Views folder was not containing the following Web.config file:

[code:c#]

<?xml version="1.0"?>
<configuration>
  <system.web>
    <httpHandlers>
      <add path="*" verb="*"
          type="System.Web.HttpNotFoundHandler"/>
    </httpHandlers>

    <!--
        Enabling request validation in view pages would cause validation to occur
        after the input has already been processed by the controller. By default
        MVC performs request validation before a controller processes the input.
        To change this behavior apply the ValidateInputAttribute to a
        controller or action.
    -->
    <pages
        validateRequest="false"
        pageParserFilterType="System.Web.Mvc.ViewTypeParserFilter, System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"
        pageBaseType="System.Web.Mvc.ViewPage, System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"
        userControlBaseType="System.Web.Mvc.ViewUserControl, System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
      <controls>
        <add assembly="System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" namespace="System.Web.Mvc" tagPrefix="mvc" />
      </controls>
    </pages>
  </system.web>

  <system.webServer>
    <validation validateIntegratedModeConfiguration="false"/>
    <handlers>
      <remove name="BlockViewHandler"/>
      <add name="BlockViewHandler" path="*" verb="*" preCondition="integratedMode" type="System.Web.HttpNotFoundHandler"/>
    </handlers>
  </system.webServer>
</configuration>

[/code]

Now why would you need this one anyway? Well: first of all, you do not want your views to expose their source code. Therefore, we add the HttpNotFoundHandler for this folder. Next, we do not want request validation to happen again (because this is already done when invoking the controller). Next: we want the MvcViewTypeParserFilter to be used for enabling strong-typed views (more on this by Phil Haack).

Problem two: MEF’s approach to plugins and ASP.NET’s approach to rendering views…

When compiling a view, ASP.NET dynamically compiles the markup into a temporary assembly, after which it is rendered. This compilation process knows only the assemblies loaded by your web application’s AppDomain. Unfortunately, assemblies loaded by MEF are not available for this compilation process… I went ahead and checked with Reflector if we could do something about this on ASP.NET side: nope. The main classes we need for this are internal :-( The MEF side could be easily tweaked since its source code is available on CodePlex, but… it’s still subject to change and will be included in .NET 4.0 as a framework component, which would limit my customizations a bit for the future.

Now let’s describe this problem as one, simple sentence: we need the MEF plugin assembly loaded in our current AppDomain, available for all other components in the web application.

The solution to this: I want a MEF DirectoryCatalog to monitor my plugins folder and load/unload the assemblies in there dynamically. Loading should be no problem, but unloading… The assemblies will always be locked by my web server’s process! So let’s go for another approach: monitor the plugins folder, copy the new/modified assemblies to the web application’s /bin folder and instruct MEF to load its exports from there. The solution: WebServerDirectoryCatalog. Here’s the code:

[code:c#]

public sealed class WebServerDirectoryCatalog : ComposablePartCatalog
{
    private FileSystemWatcher fileSystemWatcher;
    private DirectoryCatalog directoryCatalog;
    private string path;
    private string extension;

    public WebServerDirectoryCatalog(string path, string extension, string modulePattern)
    {
        Initialize(path, extension, modulePattern);
    }

    private void Initialize(string path, string extension, string modulePattern)
    {
        this.path = path;
        this.extension = extension;

        fileSystemWatcher = new FileSystemWatcher(path, modulePattern);
        fileSystemWatcher.Changed += new FileSystemEventHandler(fileSystemWatcher_Changed);
        fileSystemWatcher.Created += new FileSystemEventHandler(fileSystemWatcher_Created);
        fileSystemWatcher.Deleted += new FileSystemEventHandler(fileSystemWatcher_Deleted);
        fileSystemWatcher.Renamed += new RenamedEventHandler(fileSystemWatcher_Renamed);
        fileSystemWatcher.IncludeSubdirectories = false;
        fileSystemWatcher.EnableRaisingEvents = true;

        Refresh();
    }

    void fileSystemWatcher_Renamed(object sender, RenamedEventArgs e)
    {
        RemoveFromBin(e.OldName);
        Refresh();
    }

    void fileSystemWatcher_Deleted(object sender, FileSystemEventArgs e)
    {
        RemoveFromBin(e.Name);
        Refresh();
    }

    void fileSystemWatcher_Created(object sender, FileSystemEventArgs e)
    {
        Refresh();
    }

    void fileSystemWatcher_Changed(object sender, FileSystemEventArgs e)
    {
        Refresh();
    }

    private void Refresh()
    {
        // Determine /bin path
        string binPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "bin");

        // Copy files to /bin
        foreach (string file in Directory.GetFiles(path, extension, SearchOption.TopDirectoryOnly))
        {
            try
            {
                File.Copy(file, Path.Combine(binPath, Path.GetFileName(file)), true);
            }
            catch
            {
                // Not that big deal... Blog readers will probably kill me for this bit of code :-)
            }
        }

        // Create new directory catalog
        directoryCatalog = new DirectoryCatalog(binPath, extension);
    }

    public override IQueryable<ComposablePartDefinition> Parts
    {
        get { return directoryCatalog.Parts; }
    }

    private void RemoveFromBin(string name)
    {
        string binPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "bin");
        File.Delete(Path.Combine(binPath, name));
    }
}

[/code]

Download the example code

First of all: this was tricky, and the solution to it is also a bit tricky. Use at your own risk!

You can download the example code here: RevisedMvcMefDemo.zip (1.03 mb)

kick it on DotNetKicks.com

ASP.NET MVC TDD using Visual Studio 2010

Phil Haack announced yesterday that the tooling support for ASP.NET MVC is available for Visual Studio 2010. Troy Goode already blogged about the designer snippets (which are really really cool, just like other parts of the roadmap for ASP.NET MVC 2.0). I’ll give the new TDD workflow introduced in VS2010 a take.

kick it on DotNetKicks.com

Creating a new controller, the TDD way

First of all, I’ll create a new ASP.NET MVC application in VS2010. After installing the project template (and the designer snippets if you are cool), this is easy in VS2010:

Step 1

Proceed and make sure to create a unit test project as well.

Next, in your unit test project, add a new unit test class and name it DemoControllerTests.cs.

Step 2Go ahead and start typing the following test:

Step 3Now when you type CTRL-. (or right click the DemoController unknown class), you can pick “Generate other…”:

Step 4A new window will appear,  where you can select the project where you want the new DemoController class to be created. Make sure to enter the MvcApplication project here (and not your test project).

Step 5

Great, that class has been generated. But how about the constructor accepting List<string>? Press CTRL-. and proceed with the suggested action.

Step 6

Continue typing your test and let VS2010 also implement the Index() action method.

Step 7You can now finish the test code:

Step 8The cool thing is: we did not have to go out of our DemoControllerTests.cs editor while writing this test class, while VS2010 took care of stubbing my DemoController in the background:Step 9Run your tests and see it fail. That’s the TDD approach: first make it fail, and then implement what’s needed:

Step 10

If you run your tests  now, you’ll see the test pass.

Conclusion

I like this new TDD approach and ASP.NET MVC! It’s not ReSharper yet, but I think its a fine step that the Visual Studio team has taken.

kick it on DotNetKicks.com

A view from the cloud (or: locate your ASP.NET MVC views on Windows Azure Blob Storage)

Hosting and deploying ASP.NET MVC applications on Windows Azure works like a charm. However, if you have been reading my blog for a while, you might have seen that I don’t like the fact that my ASP.NET MVC views are stored in the deployed package as well… Why? If I want to change some text or I made a typo, I would have to re-deploy my entire application for this. Takes a while, application is down during deployment, … And all of that for a typo…

Luckily, Windows Azure also provides blob storage, on which you can host any blob of data (or any file, if you don’t like saying “blob”). These blobs can easily be managed with a tool like Azure Blob Storage Explorer. Now let’s see if we can abuse blob storage for storing the views of an ASP.NET MVC web application, making it easier to modify the text and stuff. We’ll do this by creating a new VirtualPathProvider.

Note that this approach can also be used to create a CMS based on ASP.NET MVC and Windows Azure.

kick it on DotNetKicks.com

Putting our views in the cloud

Of course, we need a new ASP.NET MVC web application. You can prepare this for Azure, but that’s not really needed for testing purposes. Download and run Azure Blob Storage Explorer, and put all views in a blob storage container. Make sure to incldue the full virtual path in the blob’s name, like so:

Azure Blob Storage Explorer

Note I did not upload every view to blob storage. In the approach we’ll take, you do not need to put every view in there: we’ll support mixed-mode where some views are deployed and some others are in blob storage.

Creating a VirtualPathProvider

You may or may not know the concept of ASP.NET VirtualPathProviders. Therefore, allow me to quickly explain quickly: ASP.NET 2.0 introduced the concept of VirtualPathProviders, where you can create a virtual filesystem that can be sued by your application. A VirtualPathProvider has to be registered before ASP.NET will make use of it. After registering, ASP.NET will automatically iterate all VirtualPathProviders to check whether it can provide the contents of a specific virtual file or not. In ASP.NET MVC for example, the VirtualPathProviderViewEngine (default) will use this concept to look for its views. Ideal, since we do not have to plug the ASP.NET MVC view engine when we create our BlobStorageVirtualPathProvider!

A VirtualPathProvider contains some methods that are used to determine if it can serve a specific virtual file. We’ll only be implementing FileExists() and GetFile(), but there are also methods like DirectoryExists() and GetDirectory(). I suppose you’ll know what all this methods are doing by looking at the name…

In order for our BlobStorageVirtualPathProvider class to access Windows Azure Blob Storage, we need to reference the StorageClient project you can find in the Windows Azure SDK. Next, our class will have to inherit from VirtualPathProvider and need some fields holding useful information:

[code:c#]

public class BlobStorageVirtualPathProvider : VirtualPathProvider
{
    protected readonly StorageAccountInfo accountInfo;
    protected readonly BlobContainer container;
    protected BlobStorage blobStorage;

    // ...

    public BlobStorageVirtualPathProvider(StorageAccountInfo storageAccountInfo, string containerName)
    {
        accountInfo = storageAccountInfo;
        BlobStorage blobStorage = BlobStorage.Create(accountInfo);
        container = blobStorage.GetBlobContainer(containerName);
    }

    // ...

}

[/code]

Allright! We can now hold everyhting that is needed for accessing Windows Azure Blob Storage: the account info (including credentials) and a BlobContainer holding our views. Our constructor accepts these things and makes sure verything is prepared for accessing blob storage.

Next, we’ll have to make sure we can serve a file, by adding FileExists() and GetFile() method overrides:

[code:c#]

public override bool FileExists(string virtualPath)
{
    // Check if the file exists on blob storage
    string cleanVirtualPath = virtualPath.Replace("~", "").Substring(1);
    if (container.DoesBlobExist(cleanVirtualPath))
    {
        return true;
    }
    else
    {
        return Previous.FileExists(virtualPath);
    }
}

public override VirtualFile GetFile(string virtualPath)
{
    // Check if the file exists on blob storage
    string cleanVirtualPath = virtualPath.Replace("~", "").Substring(1);
    if (container.DoesBlobExist(cleanVirtualPath))
    {
        return new BlobStorageVirtualFile(virtualPath, this);
    }
    else
    {
        return Previous.GetFile(virtualPath);
    }
}

[/code]

These methods simply check the BlobContainer for the existance of a virtualFile path passed in.  GetFile() returns a new BlobStorageVirtualPath instance. This class provides all functionality for really returning the file’s contents, in its Open() method:

[code:c#]

public override System.IO.Stream Open()
{
    string cleanVirtualPath = this.VirtualPath.Replace("~", "").Substring(1);
    BlobContents contents = new BlobContents(new MemoryStream());
    parent.BlobContainer.GetBlob(cleanVirtualPath, contents, true);
    contents.AsStream.Seek(0, SeekOrigin.Begin);
    return contents.AsStream;
}

[/code]

We’ve just made it possible to download a blob from Windows Azure Blob Storage into a MemoryStream and pass this on to ASP.NET for further action.

Here’s the full BlobStorageVirtualPathProvider class:

[code:c#]

public class BlobStorageVirtualPathProvider : VirtualPathProvider
{
    protected readonly StorageAccountInfo accountInfo;
    protected readonly BlobContainer container;

    public BlobContainer BlobContainer
    {
        get { return container; }
    }

    public BlobStorageVirtualPathProvider(StorageAccountInfo storageAccountInfo, string containerName)
    {
        accountInfo = storageAccountInfo;
        BlobStorage blobStorage = BlobStorage.Create(accountInfo);
        container = blobStorage.GetBlobContainer(containerName);
    }

    public override bool FileExists(string virtualPath)
    {
        // Check if the file exists on blob storage
        string cleanVirtualPath = virtualPath.Replace("~", "").Substring(1);
        if (container.DoesBlobExist(cleanVirtualPath))
        {
            return true;
        }
        else
        {
            return Previous.FileExists(virtualPath);
        }
    }

    public override VirtualFile GetFile(string virtualPath)
    {
        // Check if the file exists on blob storage
        string cleanVirtualPath = virtualPath.Replace("~", "").Substring(1);
        if (container.DoesBlobExist(cleanVirtualPath))
        {
            return new BlobStorageVirtualFile(virtualPath, this);
        }
        else
        {
            return Previous.GetFile(virtualPath);
        }
    }

    public override System.Web.Caching.CacheDependency GetCacheDependency(string virtualPath, System.Collections.IEnumerable virtualPathDependencies, DateTime utcStart)
    {
        return null;
    }
}

[/code]

And here’s BlobStorageVirtualFile:

[code:c#]

public class BlobStorageVirtualFile : VirtualFile
{
    protected readonly BlobStorageVirtualPathProvider parent;

    public BlobStorageVirtualFile(string virtualPath, BlobStorageVirtualPathProvider parentProvider) : base(virtualPath)
    {
        parent = parentProvider;
    }

    public override System.IO.Stream Open()
    {
        string cleanVirtualPath = this.VirtualPath.Replace("~", "").Substring(1);
        BlobContents contents = new BlobContents(new MemoryStream());
        parent.BlobContainer.GetBlob(cleanVirtualPath, contents, true);
        contents.AsStream.Seek(0, SeekOrigin.Begin);
        return contents.AsStream;
    }
}

[/code]

Registering BlobStorageVirtualPathProvider with ASP.NET

We’re not completely ready yet. We still have to tell ASP.NET that it can possibly get virtual files using the BlobStorageVirtualPathProvider. We’ll do this in the Application_Start event in Global.asax.cs:

[code:c#]

protected void Application_Start()
{
    RegisterRoutes(RouteTable.Routes);

    // Register the virtual path provider with ASP.NET
    System.Web.Hosting.HostingEnvironment.RegisterVirtualPathProvider(new BlobStorageVirtualPathProvider(
        new StorageAccountInfo(
            new Uri("http://blob.core.windows.net"),
            false,
            "your_storage_account_name_here",
            "your_storage_account_key_here"),
            "your_container_name_here"));
}

[/code]

Add your own Azure storage account name, key and the container name that you’ve put your views in and you are set! Development storage will work as well as long as you enter the required info.

Running the example code

Download the sample code here: MvcViewInTheCloud.zip (58.72 kb)

Some instructions for running the sample code:

  • Upload all views from the ____Views folder to a blob container (as described earlier in this post)
  • Change your Azure credetials in Application_Start

kick it on DotNetKicks.com

PHP and Silverlight - DevDays session

I just returned from The Hague where Kevin and I delivered a session on PHP and Silverlight. As promised, we are putting our slides and demos online. Download the demo code from here: PHP and Silverlight - DevDays.zip (1.00 mb)

Abstract:

"So you have an existing PHP application and would like to spice it up with a rich and attractive front-end. Next to Adobe Flex, you can also choose Silverlight as a solution. This session shows you around in Silverlight and shows that PHP and Silverlight can go together easily."

We really enjoyed DevDays and want to thank everyone who was there (and was in our session while beer drinking seemed more appropriate that time of day).

ConnectedShow Podcast - PHP SDK for Windows Azure

The fifth episode of the ConnectedShow podcast is up. This podcast is all about cloud computing, Windows Azure, … Recently, they have asked me if I wanted to be in one of their podcasts on the PHP SDK for Windows Azure.

In this episode Dmitry welcomes a new co-host, Peter Laudati. Next, we speak to Maarten Balliauw about the new PHP SDK for Windows Azure which is designed to help PHP developers use Windows Azure services.

Here’s the link to the podcast: http://www.connectedshow.com/default.aspx?Episode=5

ASP.NET MVC Domain Routing

Routing Ever since the release of ASP.NET MVC and its routing engine (System.Web.Routing), Microsoft has been trying to convince us that you have full control over your URL and routing. This is true to a certain extent: as long as it’s related to your application path, everything works out nicely. If you need to take care of data tokens in your (sub)domain, you’re screwed by default.

Earlier this week, Juliën Hanssens did a blog post on his approach to subdomain routing. While this is a good a approach, it has some drawbacks:

  • All routing logic is hard-coded: if you want to add a new possible route, you’ll have to code for it.
  • The VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values) method is not implemented, resulting in “strange” urls when using HtmlHelper ActionLink helpers. Think of http://live.localhost/Home/Index/?liveMode=false where you would have just wanted http://develop.localhost/Home/Index.

Unfortunately, the ASP.NET MVC infrastructure is based around this VirtualPathData class. That’s right: only tokens in the URL’s path are used for routing… Check my entry on the ASP.NET MVC forums on that one.

Now for a solution… Here are some scenarios we would like to support:

  • Scenario 1: Application is multilingual, where www.nl-be.example.com should map to a route like “www.{language}-{culture}.example.com”.
  • Scenario 2: Application is multi-tenant, where www.acmecompany.example.com should map to a route like “www.{clientname}.example.com”.
  • Scenario 3: Application is using subdomains for controller mapping: www.store.example.com maps to "www.{controller}.example.com/{action}...."

Sit back, have a deep breath and prepare for some serious ASP.NET MVC plumbing…

kick it on DotNetKicks.com

Defining routes

Here are some sample route definitions we want to support. An example where we do not want to specify the controller anywhere, as long as we are on home.example.com:

[code:c#]

routes.Add("DomainRoute", new DomainRoute(
    "home.example.com", // Domain with parameters
    "{action}/{id}",    // URL with parameters
    new { controller = "Home", action = "Index", id = "" }  // Parameter defaults
));

[/code]

Another example where we have our controller in the domain name:

[code:c#]

routes.Add("DomainRoute", new DomainRoute(
    "{controller}.example.com",     // Domain with parameters< br />    "{action}/{id}",    // URL with parameters
    new { controller = "Home", action = "Index", id = "" }  // Parameter defaults
));

[/code]

Want the full controller and action in the domain?

[code:c#]

routes.Add("DomainRoute", new DomainRoute(
    "{controller}-{action}.example.com",     // Domain with parameters
    "{id}",    // URL with parameters
    new { controller = "Home", action = "Index", id = "" }  // Parameter defaults
));

[/code]

Here’s the multicultural route:

[code:c#]

routes.Add("DomainRoute", new DomainRoute(
    "{language}.example.com",     // Domain with parameters
    "{controller}/{action}/{id}",    // URL with parameters
    new { language = "en", controller = "Home", action = "Index", id = "" }  // Parameter defaults
));

[/code]

HtmlHelper extension methods

Since we do not want all URLs generated by HtmlHelper ActionLink to be using full URLs, the first thing we’ll add is some new ActionLink helpers, containing a boolean flag whether you want full URLs or not. Using these, you can now add a link to an action as follows:

[code:c#]

<%= Html.ActionLink("About", "About", "Home", true)%>

[/code]

Not too different from what you are used to, no?

Here’s a snippet of code that powers the above line of code:

[code:c#]

public static class LinkExtensions
{
    public static string ActionLink(this HtmlHelper htmlHelper, string linkText, string actionName, string controllerName, bool requireAbsoluteUrl)
    {
        return htmlHelper.ActionLink(linkText, actionName, controllerName, new RouteValueDictionary(), new RouteValueDictionary(), requireAbsoluteUrl);
    }

    // more of these...

    public static string ActionLink(this HtmlHelper htmlHelper, string linkText, string actionName, string controllerName, RouteValueDictionary routeValues, IDictionary<string, object> htmlAttributes, bool requireAbsoluteUrl)
    {
        if (requireAbsoluteUrl)
        {
            HttpContextBase currentContext = new HttpContextWrapper(HttpContext.Current);
            RouteData routeData = RouteTable.Routes.GetRouteData(currentContext);

            routeData.Values["controller"] = controllerName;
            routeData.Values["action"] = actionName;

            DomainRoute domainRoute = routeData.Route as DomainRoute;
            if (domainRoute != null)
            {
                DomainData domainData = domainRoute.GetDomainData(new RequestContext(currentContext, routeData), routeData.Values);
                return htmlHelper.ActionLink(linkText, actionName, controllerName, domainData.Protocol, domainData.HostName, domainData.Fragment, routeData.Values, null);
            }
        }
        return htmlHelper.ActionLink(linkText, actionName, controllerName, routeValues, htmlAttributes);
    }
}

[/code]

Nothing special in here: a lot of extension methods, and some logic to add the domain name into the generated URL. Yes, this is one of the default ActionLink helpers I’m abusing here, getting some food from my DomainRoute class (see: Dark Magic).

Dark magic

You may have seen the DomainRoute class in my code snippets from time to time. This class is actually what drives the extraction of (sub)domain and adds token support to the domain portion of your incoming URLs.

We will be extending the Route base class, which already gives us some properties and methods we don’t want to implement ourselves. Though there are some we will define ourselves:

[code:c#]

public class DomainRoute : Route

    // ...

    public string Domain { get; set; }

    // ...

    public override RouteData GetRouteData(HttpContextBase httpContext)
    {
        // Build regex
        domainRegex = CreateRegex(Domain);
        pathRegex = CreateRegex(Url);

        // Request information
        string requestDomain = httpContext.Request.Headers["host"];
        if (!string.IsNullOrEmpty(requestDomain))
        {
            if (requestDomain.IndexOf(":") > 0)
            {
                requestDomain = requestDomain.Substring(0, requestDomain.IndexOf(":"));
            }
        }
        else
        {
            requestDomain = httpContext.Request.Url.Host;
        }
        string requestPath = httpContext.Request.AppRelativeCurrentExecutionFilePath.Substring(2) + httpContext.Request.PathInfo;

        // Match domain and route
        Match domainMatch = domainRegex.Match(requestDomain);
        Match pathMatch = pathRegex.Match(requestPath);

        // Route data
        RouteData data = null;
        if (domainMatch.Success && pathMatch.Success)
        {
            data = new RouteData(this, RouteHandler);

            // Add defaults first
            if (Defaults != null)
            {
                foreach (KeyValuePair<string, object> item in Defaults)
                {
                    data.Values[item.Key] = item.Value;
                }
            }

            // Iterate matching domain groups
            for (int i = 1; i < domainMatch.Groups.Count; i++)
            {
                Group group = domainMatch.Groups[i];
                if (group.Success)
                {
                    string key = domainRegex.GroupNameFromNumber(i);
                    if (!string.IsNullOrEmpty(key) && !char.IsNumber(key, 0))
                    {
                        if (!string.IsNullOrEmpty(group.Value))
                        {
                            data.Values[key] = group.Value;
                        }
                    }
                }
            }

            // Iterate matching path groups
            for (int i = 1; i < pathMatch.Groups.Count; i++)
            {
                Group group = pathMatch.Groups[i];
                if (group.Success)
                {
                    string key = pathRegex.GroupNameFromNumber(i);
                    if (!string.IsNullOrEmpty(key) && !char.IsNumber(key, 0))
                    {
                        if (!string.IsNullOrEmpty(group.Value))
                        {
                            data.Values[key] = group.Value;
                        }
                    }
                }
            }
        }

        return data;
    }

    public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
    {
        return base.GetVirtualPath(requestContext, RemoveDomainTokens(values));
    }

    public DomainData GetDomainData(RequestContext requestContext, RouteValueDictionary values)
    {
        // Build hostname
        string hostname = Domain;
        foreach (KeyValuePair<string, object> pair in values)
        {
            hostname = hostname.Replace("{" + pair.Key + "}", pair.Value.ToString());
        }

        // Return domain data
        return new DomainData
        {
            Protocol = "http",
            HostName = hostname,
            Fragment = ""
        };
    }

    // ...
}

[/code]

Wow! That’s a bunch of code! What we are doing here is converting the incoming request URL into tokens we defined in our route, on the domain level and path level. We do this by converting {controller} and things like that into a regex which we then try to match into the route values dictionary. There are some other helper methods in our DomainRoute class, but these are the most important.

Download the full code here: MvcDomainRouting.zip (250.72 kb)

(if you want to try this using the development web server in Visual Studio, make sue to add some fake (sub)domains in your hosts file)

kick it on DotNetKicks.com