Creating Slugs with Action Filters

16 02 2012

Slugs are the possibility to create SEO-friendly URLs (speaking URLs). Slugs are a great way to optimize you page URL for search engines.

http://www.<domain&gt;.de/Post/145732 – URL with a meaningless Id
http://www.<domain&gt;.de/Post/Creating-slugs-with-Action-filters – speaking URL with a slug

Even for humans speaking URLs are much better instead of meaningless URLs with Ids. You can already see what will occur when opening a link.

Basic slug implementation

We start with the creation of a new Route. This can be done by adding the following route to the Global.asax:

routes.MapRoute(
    null,
    "Post/{slug}",
    new { controller = "Post", action = "Show", slug = string.Empty });

With this route we are able to create URLs like shown in the sample above. As you can see inside the route, we need a post controller with an action called show. The following code shows a sample implementation of this controller (I assume you have a working DI-Configuration and the BlogPostRepository is injected)

private readonly IBlogPostRepository blogPostRepository;

public PostController(IBlogPostRepository blogPostRepository)
{
    this.blogPostRepository = blogPostRepository;
}

public ActionResult Show(string slug)
{
    if (slug.Equals(string.Empty))
    {
        return this.HttpNotFound();
    }

    var blogPost = blogPostRepository.FindPost(slug);
    if (blogPost == null) {
        return this.HttpNotFound();
    }

    return View(blogPost);
}

Inside the action show, we check that a slug value is provided thru the URL. If no slug value is present we return HttpNotFound (404) as result. Having a slug value inside the URL we can call the repository which is responsible for fetching data from the data store. If the URL is valid, a BlogEntry is fetched from the repository. The result should be given to the view to display the data. For the reason the repository returns no result we return another 404.
The problem with this implementation is that the size of the controller can increase very fast, especially when you have more parameters.

Slugs with Action Filters

Now I want to show you the same implementation using action filters. In my opinion this is quiet handy and better then the basic implementation.
We start with the implementation of the new action filter.

public class BlogPostFilterAttribute : ActionFilterAttribute {

    [Dependency]
    public IBlogPostRepository BlogPostRepository { get; set; }

    public override void OnActionExecuting(ActionExecutingContext 
        filterContext) {
        var slug = filterContext.RouteData.Values["slug"] as string;

        if (slug.Equals(string.Empty)) {
            filterContext.Result = new HttpNotFoundResult();
            return;
        }

        var blogPost = BlogPostRepository.FindPost(slug.ToString());

        if (blogPost == null) {
            filterContext.Result = new HttpNotFoundResult();
            return;
        }

        filterContext.ActionParameters["BlogPost"] = blogPost;
    }
}

As you can see, the code looks similar to the basic implementation. What changed is the fact that we extract the slug value from the RouteData of the filterContext. For the cases we want to display a 404 we provide the filterContext result with a new instance of HttpNotFoundResult. On the happy path when we found a post with the help of the repository, the blogPost entry is stored as action parameter on the filterContext. Now we need to update the show action. First we need to provide the new attribute on the action. Next we need to change the method parameters from string to blogPost. The blogPost parameter inside the action will now be filled with the blogPost data which was fetched inside the action filter. Last but not least we can remove all the unnecessary code from the action. The show action should look similar to the following code:

[BlogPostFilter]
public ActionResult Show(BlogPost blogPost) {
    return View(blogPost);
}

Add support for Property Injection on Action Filters

The special thing about action filters is that we need to write some additional code to support injecting data into action filters. In the code above you can already see that I provided the Dependency attribute on the BlogPostRepository property. Without the customizing the property injection didn’t work. What we need to do, I found inside a blog post from Brad Wilson (http://bradwilson.typepad.com/blog/2010/07/service-location-pt4-filters.html).
We need write a filter attribute provider, which is provided with your dependency injection container. The following code shows a sample implementation for unity.

public class UnityFilterAttributeFilterProvider : 
    FilterAttributeFilterProvider {
    private readonly IUnityContainer container;

    public UnityFilterAttributeFilterProvider(IUnityContainer container) {
        this.container = container;
    }

    protected override IEnumerableGetControllerAttributes(
        ControllerContext controllerContext,
        ActionDescriptor actionDescriptor) {

        var attributes = base.GetControllerAttributes(controllerContext, 
            actionDescriptor);

        foreach (var attribute in attributes) {
            this.container.BuildUp(attribute.GetType(), attribute);
        }

        return attributes;
    }

    protected override IEnumerableGetActionAttributes(
        ControllerContext controllerContext,
        ActionDescriptor actionDescriptor) {

        var attributes = base.GetControllerAttributes(controllerContext, 
            actionDescriptor);

        foreach (var attribute in attributes) {
            this.container.BuildUp(attribute.GetType(), attribute);
        }

        return attributes;
    }
}

Inside the Global.asax you need to add the following code inside the Application_Start. This removes the default filter and adds the filter with the DI container to the FilterProviders.

var filterAttributProvider = FilterProviders.Providers.Single(f => f is FilterAttributeFilterProvider);
FilterProviders.Providers.Remove(filterAttributProvider);

var provider = new UnityFilterAttributeFilterProvider(container);
FilterProviders.Providers.Add(provider);

After this last step you should be able to use the injected repository.

Conclusion

With the help of action filters, it’s easier to reduce the controller size and make the code reusable thru an attribute which can be provided on different controllers and actions.

Advertisements

Actions

Information

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s




%d bloggers like this: