Tuesday, July 19, 2011

Save some lines for your Razor view

We all know that we should not put logic in the MVC view. If we have to do something base on a complex condition, we should better make a HtmlHelper method for that logic. In an attempt to remove some simple if/else statement on the view by using some extention method. Let's say we have something like below in the view:

@if (!Request.IsAuthenticated)
{
    Html.RenderPartial("LoginBox");
}
I attempt to make it look like:
 
@{ Html.Do(x => x.RenderPartial("LoginBox"))
       .When(!Request.IsAuthenticated); }
 
So we can save 2 lines of code or event 3 lines if we write the code in 1 line:
 
@{ Html.Do(x => x.RenderPartial("LoginBox")).When(!Request.IsAuthenticated); }
 
I know it is very hard to remove all condition logic from the view unless you create HtmlHelper methods for all the condition logic on your view. But it could lead to so many methods. I think we should not create helper method if we don't use the method 2 places. So the method I suggest could help :D. I think it improves the readablity for your view code especially both the designers and developers are working on the same file. So here is the code:
public interface IAction<T>
{
    T Object { get;}
    Action<T> Action { get; }
    void When(Expression<Func<T, bool>> when);
    void When(bool when);
}
I will make 2 implementation of the above interface. The first one is OneConditionAction and the other is PostConditionAction. So I can combine multi conditions like:
@{ Html.When(!Request.IsAuthenticated)
       .And(....)
       .And(....)
       .Do(x => x.RenderPartial("LoginBox")); }
Below is the implementation:
public class OneConditionAction<T> : IAction<T>
{
    public T Object { get; private set; }

    public Action<T> Action { get; private set; }

    public virtual void When(Expression<Func<T, bool>> when)
    {
        if (when.Compile()(Object))
        {
            Action(Object);
        }
    }

    public void When(bool when)
    {
        if (when)
        {
            Action(Object);
        }
    }

    public OneConditionAction(Action<T> action, T helper)
    {
        Action = action;
        Object = helper;
    }
}       

public class PostConditionsAction<T> : OneConditionAction<T>
{
    public PostConditionsAction(IAction<T> conditionalAction)
        : base(conditionalAction.Action, conditionalAction.Object)
    {
        Condition = x => true;
    }

    public Expression<Func<T, bool>> Condition { get; set; }

    public override void When(Expression<Func<T, bool>> when)
    {
        var newCondition = this.And(when);
        Condition = newCondition.Condition;
    }
}
We'll need the extension methods:
namespace System.Web.Mvc
{
    public static class ActionExtensions
    {
        public static IAction<T> Do<T>(this T obj, Action<T> action) where T : class
        {
            return new OneConditionAction<T>(action, obj);
        }

        public static IAction<T> Do<T>(this T obj, Action action) where T : class
        {
            return new OneConditionAction<T>(a => action(), obj);
        }
    }
    
    public static class PostConditionsActionExtensions
    {
        public static PostConditionsAction<T> When<T>(this T obj, bool condition)
        {
            var newAction = new PostConditionsAction<T>(new OneConditionAction<T>(x => { }, obj));
            newAction = newAction.And(condition);
            return newAction;
        }

        public static PostConditionsAction<T> When<T>(this T obj, Expression<Func<T, bool>> condition)
        {
            var newAction = new PostConditionsAction<T>(new OneConditionAction<T>(x => { }, obj));
            newAction = newAction.And(condition);
            return newAction;
        }

        public static PostConditionsAction<T> And<T>(this PostConditionsAction<T> postConditions, Expression<Func<T, bool>> andCondition)
        {
            var x = Expression.Parameter(typeof(T));
            postConditions.Condition = Expression.Lambda<Func<T, bool>>(Expression.And(Expression.Invoke(postConditions.Condition, x), Expression.Invoke(andCondition, x)), x);
            return postConditions;
        }

        public static PostConditionsAction<T> And<T>(this PostConditionsAction<T> postConditions, bool andCondition)
        {
            return postConditions.And(x => andCondition);
        }

        public static PostConditionsAction<T> Or<T>(this PostConditionsAction<T> postConditions, Expression<Func<T, bool>> orCondition)
        {
            var x = Expression.Parameter(typeof(T));
            postConditions.Condition = Expression.Lambda<Func<T, bool>>(Expression.Or(Expression.Invoke(postConditions.Condition, x), Expression.Invoke(orCondition, x)), x);
            return postConditions;
        }

        public static PostConditionsAction<T> Or<T>(this PostConditionsAction<T> postConditions, bool orCondition)
        {
            return postConditions.Or(x => orCondition);
        }

        public static void Do<T>(this PostConditionsAction<T> postConditions, Action<T> action) where T : class
        {
            postConditions.Object.Do(action).When(postConditions.Condition);
        }

        public static void Do<T>(this PostConditionsAction<T> postConditions, Action action) where T : class
        {
            postConditions.Object.Do(action).When(postConditions.Condition);
        }
    }
}
Because they are generic classes, so we can use this syntax for any object, not only HtmlHelper :D Cheers

1 comments:

Mayrun Digmi said...

Very cool job!
I think it'd be more suitable if you could package all the classes in one file.

Post a Comment