Monthly Archives: December 2007

New view engines in the MvcContrib project for asp.net mvc

When I got back from visiting my parents over the christmas holiday I checked the MvcContrib project to find out that both Andrew Peter’s NHAML view engine and my XSLT view engine have been accepted in the trunk of those projects.

What’s left to do for me is provide a sample site that shows usage of the view engine.  This has been a very rewarding experience so far :) . I found out about IViewSourceLoader to get the path to the current view file for example.

I also learnt a new trick to increase my test coverage and be able to test the content of the rendered view.

To get the mvc contrib project you can either download it from the releases section on the codeplex site.

Or you can check out the source code and build it yourself from the subversion repository

del.icio.us Tags: ,

Technorati Tags:

And another view engine for ASP.NET MVC has been born

Andrew Peters wrote a view engine for HAML

NHaml (pronounced enamel) is a pure .NET implementation of the popular RailsHaml view engine. From the Haml website:

“Haml is a markup language that‘s used to cleanly and simply describe the XHTML of any web document, without the use of inline code. Haml functions as a replacement for inline page templating systems such as PHP, ERB, and ASP. However, Haml avoids the need for explicitly coding XHTML into the template, because it is actually an abstract description of the XHTML, with some code to generate dynamic content.”

Andrew and I share the same dislike for the xml family. XML is not fun to type, and I think his haml engine eases a lot of that pain. It results in far more readable views too.

2 view engines in one week and both from Wellington. Nice :)

del.icio.us Tags:

Implementing filters in asp.net mvc

One of the things I wanted for the framework I’m building for Xero was to implement filters.

I have a first pass of those ready.. didn’t take me to long to implement either.  I only tested the before filters I haven’t gotten round to testing the After filter. I have to do a demo today that demonstrates databinding (like the castle project solved it.)  What I’m putting on my blog here is very much a proof of concept implementation and you should not use this in a production environment.

Here’s how I went about it. In this post I’ll show how I implemented a filter that directs anonymous users to the login page.

1. Define a couple enumerations

    public enum Execute

    {

        Before,

        After,

        BeforeAndAfter

    }

 

    public enum SecureFor

    {

        None,

        Anonymous,

        PerUser

    }

 

2. Create an IFilter interface

 

using System.Web;

 

namespace Xero.Mvc.Extensions.Filters

{

    public interface IFilter

    {

        Execute WhenToExecute { get; }

        IHttpContext HttpContext { get; set; }

        void Execute();

    }

}

3. Create an AbstractFilter base class

using System;

using System.Web;

namespace Xero.Mvc.Extensions.Filters

{

    [AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = true)]

    public abstract class AbstractFilter : Attribute, IFilter

    {

        private readonly Xero.Mvc.Extensions.Execute whenToExecute;

        private IHttpContext httpContext;

 

        public AbstractFilter(Xero.Mvc.Extensions.Execute whenToExecute)

        {

            this.whenToExecute = whenToExecute;

        }

 

        public IHttpContext HttpContext

        {

            get { return httpContext; }

            set { httpContext = value; }

        }

 

        public Xero.Mvc.Extensions.Execute WhenToExecute

        {

            get { return whenToExecute; }

        }

 

 

        public abstract void Execute();

    }

}

 

4. Create a SecureFilter base class

using System;

using System.Web;

using System.Collections.Generic;

 

namespace Xero.Mvc.Extensions.Filters

{

    [AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = true)]

    public abstract class AbstractSecureFilter : AbstractFilter, IFilter

    {

        private readonly SecureFor secureFor;

 

        public AbstractSecureFilter(Xero.Mvc.Extensions.Execute whenToExecute, SecureFor secureFor)

            : base(whenToExecute)

        {

            this.secureFor = secureFor;

        }

 

        public SecureFor SecureFor

        {

            get { return secureFor; }

        }

 

        protected void RedirectToLogin()

        {

            HttpContext.Response.Redirect(“~/”, true);

        }

    }

}

 

5. Implement the concrete AnonymousUsersFilter

using Xero.Mvc.Extensions.Filters;

using Xero.Mvc.Tasklist.Model.EntityClasses;

using Xero.Mvc.LLBLGenIntegration.Services;

using System;

using System.Collections.Generic;

using Xero.Mvc.Extensions;

 

namespace Xero.Mvc.Tasklist.Filters

{

    [AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = true)]

    public class AnonymousUsersFilter : AbstractSecureFilter

    {

        private User[] users;

        private DataService<User> userService;

 

        public AnonymousUsersFilter(Xero.Mvc.Extensions.Execute whenToExecute, SecureFor secureFor) : this(whenToExecute, secureFor, null) { }

 

        private AnonymousUsersFilter(Xero.Mvc.Extensions.Execute whenToExecute, SecureFor secureFor, User[] users)

            : base(whenToExecute, secureFor)

        {

            this.users = users;

            this.userService = new DataService<User>();

        }

 

        public AnonymousUsersFilter(Xero.Mvc.Extensions.Execute whenToExecute, User[] users) : this(whenToExecute, SecureFor.PerUser, users) { }

 

 

        public User[] Users

        {

            get { return users; }

        }

 

        public override void Execute()

        {

            if (SecureFor == Xero.Mvc.Extensions.SecureFor.Anonymous && HttpContext.Session["userId"] == null)

            {

                RedirectToLogin();

            }

 

            if (SecureFor == Xero.Mvc.Extensions.SecureFor.PerUser && Users == null)

                RedirectToLogin();

 

            if (SecureFor == Xero.Mvc.Extensions.SecureFor.PerUser)

            {

                User currentUser = userService.FindOneById((Guid)HttpContext.Session["userId"]);

                List<User> allowedUsers = new List<User>(Users);

 

                if (allowedUsers.Find(usr => usr.Name.ToUpperInvariant() == currentUser.Name.ToUpperInvariant()) == null)

                RedirectToLogin();

            }

        }

    }

}

 

6. Implement the filter on a controller

[AnonymousUsersFilter(Xero.Mvc.Extensions.Execute.Before, SecureFor.Anonymous)]

    public abstract class SecureControllerBase : SmartXeroController

 

7. There are a couple of places where you can implement the execution of the filter logic. I chose to do it before the actual controller class is being loaded. To do that I had to create a handler and a routehandler

7.a The MvcHandler

using System.Web.Mvc;

using System;

using Xero.Mvc.Core.Exceptions;

using Xero.Mvc.Extensions.Filters;

using System.Linq;

 

namespace Xero.Mvc.Extensions

{

    public class XeroMvcHandler : MvcHandler

    {

        protected override void ProcessRequest(System.Web.IHttpContext httpContext)

        {

            if (this.RequestContext == null)

            {

                throw new NoRequestContextException();

            }

            string controllerName = this.RequestContext.RouteData.Values["controller"].ToString();

            Type controllerType = this.GetControllerType(controllerName);

            if (controllerType == null)

            {

                throw new NoControllerFoundException(this.RequestContext.HttpContext.Request.Path);

            }

 

            IFilter[] filters = controllerType.GetCustomAttributes(typeof(IFilter), true) as IFilter[];

 

            filters

                .Where(attr => attr.WhenToExecute == Xero.Mvc.Extensions.Execute.Before || attr.WhenToExecute == Xero.Mvc.Extensions.Execute.BeforeAndAfter)

                .ToList()

                .ForEach(attr =>

                {

                    if (attr != null)

                    {

                        attr.HttpContext = httpContext;

                        attr.Execute();

                    }

                });

 

            IController controllerInstance = this.GetControllerInstance(controllerType);

 

            ControllerContext controllerContext = new ControllerContext(this.RequestContext, controllerInstance);

            controllerInstance.Execute(controllerContext);

 

            filters

                .Where(attr => attr.WhenToExecute == Xero.Mvc.Extensions.Execute.After || attr.WhenToExecute == Xero.Mvc.Extensions.Execute.BeforeAndAfter)

                .ToList()

                .ForEach(attr =>

                {

                    if (attr != null)

                    {

                        attr.HttpContext = httpContext;

                        attr.Execute();

                    }

                });

        }

    }

}

 

7b. The Route Handler

using System.Web.Mvc;

using System.Web;

 

namespace Xero.Mvc.Extensions

{

    public class XeroMvcRouteHandler : IRouteHandler

    {

 

        #region IRouteHandler Members

 

        public System.Web.IHttpHandler GetHttpHandler(RequestContext requestContext)

        {

            return new XeroMvcHandler { RequestContext = requestContext };

        }

 

        #endregion

    }

}

 

8. And the last step is to tell your application that it needs to use the new route handler. You can do that in the global.asax.cs

protected override void SetupRoutes()

        {

            // Note: Change Url= to Url=”[controller].mvc/[action]/[id]” to enable

            //       automatic support on IIS6

 

            RouteTable.Routes.Add(new Route

            {

                Url = “Login/Default.aspx”,

                Defaults = new { controller = “Login”, action = “Index”, id = (string)null },

                RouteHandler = typeof(XeroMvcRouteHandler) // Our custom route handler

            });

 

            RouteTable.Routes.Add(new Route

            {

                Url = “[controller]/[action]/[id]“,

                Defaults = new { action = “Index”, id = (string)null },

                RouteHandler = typeof(XeroMvcRouteHandler)

            });

 

            RouteTable.Routes.Add(new Route

            {

                Url = “Default.aspx”,

                Defaults = new { controller = “Login”, action = “Index”, id = (string)null },

                RouteHandler = typeof(XeroMvcRouteHandler)

            });

 

        }

 

del.icio.us Tags: ,,

Technorati Tags:

kick it on DotNetKicks.com

ASP.NET MVC XsltViewEngine, patch submitted to the contrib project

I just submitted a patch to the mvc contrib project that contains the xslt view engine I wrote for Xero without any of the dependencies from our own libraries or commercial components.

The bulk of the work is done in a class called XmlResponseBuilder which builds the xml document that is going to be transformed by the xsl stylesheet. Almost all the rest of the code is there just to build up this document.

The implementation of IView and IViewFactory were the easy bits :)

To use the View engine you need to take the following steps:

1. Get the MVC Contrib project from google code

repository url: http://mvccontrib.googlecode.com/svn/trunk/

2. If the patch hasn’t been applied yet you can download it from: http://www.codeplex.com/Project/Download/FileDownload.aspx?ProjectName=MVCContrib&DownloadId=23748

After downloading the patch you can apply it to the downloaded mvc contrib project.

3. I use a base controller to ensure that the correct data is passed to the view factory. This is the base controller class:

public abstract class XeroControllerBase : Controller

    {

        private readonly XsltViewData vData = new XsltViewData();

 

        protected IHttpSessionState Session

        {

            get { return ControllerContext.HttpContext.Session; }

        }

 

        public XeroControllerBase()

        {

            ViewFactory = new XsltViewFactory();  

        }

 

        #region Add datasource methods

 

        protected void AddDataSource(IXmlConvertible dataSource)

        {

            vData.DataSources.Add(new XslDataSource(dataSource));

        }

 

        protected void AddDataSource(IXmlConvertible dataSource, string rootName)

        {

            vData.DataSources.Add(new XslDataSource(rootName, dataSource));

        }

 

        #endregion

 

        #region Add variable methods

 

        protected void AddPageVar(string key, string value)

        {

            vData.PageVars.Add(key, value);

        }

 

        #endregion

 

        #region Add message methods

 

 

        protected void AddErrorMessage(string message)

        {

            vData.Messages.Add(new ErrorMessage(message));

        }

 

        protected void AddErrormessage(string message, string controlID)

        {

            vData.Messages.Add(new ErrorMessage(message, controlID));

        }

 

        protected void AddInfoMessage(string message)

        {

            vData.Messages.Add(new InfoMessage(message));

        }

 

        protected void AddInfoMessage(string message, string controlID)

        {

            vData.Messages.Add(new InfoMessage(message, controlID));

        }

 

        protected void AddAlertMessage(string message)

        {

            vData.Messages.Add(new AlertMessage(message));

        }

 

        protected void AddAlertMessage(string message, string controlID)

        {

            vData.Messages.Add(new AlertMessage(message, controlID));

        }

 

        protected void AddInfoHtmlMessage(string message)

        {

            vData.Messages.Add(new InfoHtmlMessage(message));

        }

 

        protected void AddInfoHtmlMessage(string message, string controlID)

        {

            vData.Messages.Add(new InfoHtmlMessage(message, controlID));

        }

 

        protected void AddErrorHtmlMessage(string message)

        {

            vData.Messages.Add(new ErrorHtmlMessage(message));

        }

 

        protected void AddErrorHtmlmessage(string message, string controlID)

        {

            vData.Messages.Add(new ErrorHtmlMessage(message, controlID));

        }

 

        protected void AddAlertHtmlMessage(string message)

        {

            vData.Messages.Add(new AlertHtmlMessage(message));

        }

 

        protected void AddAlertHtmlMessage(string message, string controlID)

        {

            vData.Messages.Add(new AlertHtmlMessage(message, controlID));

        }

 

        #endregion

 

        #region RenderView method hides

 

        protected new virtual void RenderView(string viewName)

        {

            RenderView(viewName, string.Empty, vData);

        }

 

        protected new void RenderView(string viewName, string masterName)

        {

            RenderView(viewName, string.Empty, vData);

        }

 

        protected new void RenderView(string viewName, object viewData)

        {

            RenderView(viewName, string.Empty, vData);

        }

 

        #endregion 

    }

 

del.icio.us Tags:
 
kick it on DotNetKicks.com

ASP.NET mvc taken for a test drive

A couple of days ago Microsoft released a CTP of the much discussed ASP.NET MVC handler. I decided to take it for a test drive.

At first I just wanted to have a look at how to use the MVC handler with the ADO.NET Entities but quickly found that I can easily replace like the view engine.

So I diverted my intentions and wanted to look at how hard it would be to plug it straight into the architecture of xero.

At Xero we currently use a 2 step view approach where each page transforms its output through xsl and renders html that way.  Our O/R Mapper of choice is LLBLGen Pro, who does a great job at mapping stuff but the API could be a lot nicer IMHO.

I’ll make my application available soon, as soon as I removed the dependencies on LLBLGen and some of our custom classes. I have to do it this way because we’re a publicly listed company and I’m not allowed to give out that information.

Aaanyway in all it took me about 4 hours to plug in an xslt view engine and implement attribute based filters.

Overall I’m quite happy with what I’m seeing that’s in there. A couple of convenience methods like RenderText(…) would be nice. It would also be cool if it could find out the name of the requested action and then pass that as the current view so you don’t have to specify the name of which view you want to render all the time.

We did run into some trouble while writing unit tests apparently we can’t pass the view context a temp data dictionary but I’m sure that will be fixed soon.

So my conclusion is: There did go a fair bit amount of thought into the handler. I like what I see very much and I’ll definitely keep a close eye on new developments in that space.