ASP.NET MVC Action & Reaction

Standard

If you are familiar with ASP.NET MVC, you are probably familiar with the State Validation mechanism that was shipped starting from v1. Its a great way to validate your model and funnel your validation result to the user if need be. What I find lucky or I should probably say what I thought was lucking was the fact that it only dealt with error. There is nothing wrong with there and it probably the sole intention but I wanted something general. For every event the user executes (action), they expect a reaction. The most common reaction is page reload with content in relation to the user action. With that in mind, I created an Html helper  provide a generic reaction to the user, based on their action. It could be errors, it could be a warning or a successful message. These are thing developers do very often. Below is the code for my Html helper. Please not this is was a quick hack to get it working. I will post my updated to the helper when I make the final changes.

So here is how it works. All my views are strongly typed and accepts a ViewModel type. My ViewModels inherits from a base ViewModel class which provide the implementation for adding ActionMessage’s to the view. As seen below, my ContactViewModel inherits from the ViewModel abstract base class.

public enum ActionMessaeType {
    Success = 1,
    Failure = 2,
    Warning = 3,
    Alert = 4
}

public class ActionMessage {

    public ActionMessaeType MessageType
    {
        get;
        set;
    }

    public string Message
    {
        get;
        set;
    }

    public string ForAction
    {
        get;
        set;
    }

    public ActionMessage(ActionMessaeType messagetype,
                   string message)
    {
        MessageType = messagetype;
        Message = message;
        ForAction = string.Empty;
    }

    public ActionMessage(ActionMessaeType messagetype,
                string message, string foraction)
    {
        MessageType = messagetype;
        Message = message;
        ForAction = foraction;
    }
}

public abstract class ViewModel {
    private List<ActionMessage> _internalactionmessages
                            = new List<ActionMessage>();
    public List<ActionMessage> ActionMessages
    {
        get { return _internalactionmessages; }
    }
    public ViewModel(){}
    public ViewModel Add(ActionMessage actionmessage)
    {
        _internalactionmessages.Add(actionmessage);
        return this;
    }
    public ViewModel Remove(ActionMessage actionmessage)
    {
        _internalactionmessages.Remove(actionmessage);
        return this;
    }
    public ViewModel Clear()
    {
        _internalactionmessages.Clear();
        return this;
    }
}

public class ContactViewModel : ViewModel {
    public Contact Contact
    {
        get;
        set;
    }
}

 

My Contact View take accepts a ContactViewModel. Here is my page tag in my Contact.aspx

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.
Master" Inherits="System.Web.Mvc.ViewPage<YourNameSpace.
Models.ViewModel.ContactViewModel>" %>

 

Below is the post Contact Action method in my Controller

Code Snippet
  1. [HttpPost] //Post
  2. ¬†¬†¬†¬†¬†¬†¬†¬†public ActionResult Contact([Bind(Exclude = “Id”)]Contact contact)
  3.         {
  4.             //if maodel is not valid, send validated contact
  5.             if (!ModelState.IsValid)
  6.             {
  7.                 return View(new ContactViewModel { Contact = new Contact() }
  8. ¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†.Add(new ActionMessage(ActionMessaeType.Failure, “Please correct the errors in the highlighted below and try again”, “create-contact-failure”)) as ContactViewModel);
  9.             }
  10. ¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†//save contact here……
  11.             //Clear ModelState or redirect
  12.             ModelState.Clear();
  13.             //display view with action message
  14.             return View(new ContactViewModel { Contact = new Contact() }
  15. ¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†.Add(new ActionMessage(ActionMessaeType.Success, “Thank you for your message. We will get back to you as soon as possible if a response is need.”, “create-contact-success”)) as ContactViewModel);
  16.         }

 

You will notice from the code above that based on the certain events during the action execution, I add an ActionMessage to the ViewModel before its passed on to the view. The ActionMessage take a messagetype (failure, success, alert,‚Ķ), a message to be displayed and a ‚Äúforaction‚ÄĚ parameter which depicts where the message should show in the view.

When the¬† view is rendering, the html helper is called to display the action messages. The helper can take a ‚Äúforaction‚ÄĚ parameter which is used to determine which ActionMessages to show. If no ‚Äúforaction‚ÄĚ is passed, all messages are displayed. Here is the code for the Helper method

Code Snippet
  1. public static class ActionMessageExtension
  2.     {
  3. ¬†¬†¬†¬†¬†¬†¬†¬†public static readonly string ActionMessageCssClassName = “action-message”;
  4.         public static string getActionMessageCssClassName(ActionMessaeType messagetype)
  5.         {
  6.             switch (messagetype)
  7.             {
  8. ¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†case ActionMessaeType.Success: return “action-message-success”;
  9. ¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†case ActionMessaeType.Alert: return “action-message-alert”;
  10. ¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†case ActionMessaeType.Failure: return “action-message-failure”;
  11. ¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†default: return “action-success-warning”;
  12.             }
  13.         }
  14.         public static string ActionMessage(this HtmlHelper htmlHelper)
  15.         {
  16.             return ActionMessage(htmlHelper, string.Empty /* message */);
  17.         }
  18.         public static string ActionMessage(this HtmlHelper htmlHelper, List<ActionMessage> messages)
  19.         {
  20.             return ActionMessage(htmlHelper, messages, string.Empty /* htmlAttributes */);
  21.         }
  22.         public static string ActionMessage(this HtmlHelper htmlHelper, string foraction)
  23.         {
  24.             return ActionMessage(htmlHelper, null /*messages*/, foraction, (object)null /* htmlAttributes */);
  25.         }
  26.         public static string ActionMessage(this HtmlHelper htmlHelper, List<ActionMessage> messages,string foraction)
  27.         {
  28.             return ActionMessage(htmlHelper, messages,foraction, (object)null /* htmlAttributes */);
  29.         }
  30.         public static string ActionMessage(this HtmlHelper htmlHelper, List<ActionMessage> messages, string foraction, object htmlAttributes)
  31.         {
  32.             return ActionMessage(htmlHelper, messages, foraction, new RouteValueDictionary(htmlAttributes));
  33.         }
  34.         public static string ActionMessage(this HtmlHelper htmlHelper, List<ActionMessage> messages, string foraction, IDictionary<string, object> htmlAttributes)
  35.         {
  36.             //if no messages are passed in, the viewdata model is checked for messages to display
  37.             if (messages == null)
  38.                 messages = (htmlHelper.ViewData.Model as ViewModel).ActionMessages;
  39.             else //merge parameter with view actionmessages
  40.                 messages.AddRange((htmlHelper.ViewData.Model as ViewModel).ActionMessages);
  41.             if (messages == null) return null;
  42.             if (!(messages.Count > 0)) return null;
  43.             if (foraction != null)
  44.                messages = messages.FindAll(delegate(ActionMessage actmgs) {return actmgs.ForAction == foraction; });
  45.             if (messages == null || !(messages.Count > 0)) return null;
  46.             StringBuilder result = new StringBuilder();
  47.             //Render Success Messages
  48.             RenderActionMessage(messages, ActionMessaeType.Success, foraction, htmlAttributes,ref result);
  49.             //Render Alert Messages
  50.             RenderActionMessage(messages, ActionMessaeType.Failure, foraction, htmlAttributes, ref result);
  51.             //Render Alert Messages
  52.             RenderActionMessage(messages, ActionMessaeType.Alert, foraction, htmlAttributes, ref result);
  53.             //Render Warning Messages
  54.             RenderActionMessage(messages, ActionMessaeType.Warning, foraction, htmlAttributes, ref result);
  55. ¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†//Render …..more types of messages
  56.             return result.ToString();
  57.         }
  58.         private static void RenderActionMessage(List<ActionMessage> messages, ActionMessaeType messagetype, string foraction, IDictionary<string, object> htmlAttributes,ref StringBuilder htmlresults)
  59.         {
  60.             StringBuilder htmlSummary = new StringBuilder();
  61.             //Render  Messages
  62.             List<ActionMessage> typemessages = messages.FindAll(delegate(ActionMessage actmgs) { return (actmgs.MessageType == messagetype && actmgs.ForAction == foraction); });
  63.             if (typemessages.Count > 0)
  64.             {
  65. ¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†TagBuilder messageList = new TagBuilder(“ul”);
  66.                 messageList.MergeAttributes(htmlAttributes);
  67. ¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†messageList.MergeAttribute(“class”, getActionMessageCssClassName(messagetype));
  68.                 foreach (ActionMessage message in typemessages)
  69.                 {
  70.                     if (!String.IsNullOrEmpty(message.Message))
  71.                     {
  72. ¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†TagBuilder listItem = new TagBuilder(“li”);
  73.                         listItem.SetInnerText(message.Message);
  74. ¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†listItem.MergeAttribute(“class”, ActionMessageExtension.ActionMessageCssClassName);
  75.                         htmlSummary.AppendLine(listItem.ToString(TagRenderMode.Normal));
  76.                     }
  77.                 }
  78.                 messageList.InnerHtml = htmlSummary.ToString();
  79.                 htmlresults.Append(messageList.ToString(TagRenderMode.Normal));
  80.             }
  81.         }
  82.     }

And here is how its used in the Contact.aspx view.

<%=Html.ActionMessage("create-contact-failure") %>

 

I hope this help. I’m having a hard time finding a good code plug in for Live Writer as I am not so pleased with how the code is presented above. When I do find a good one, I will update the code section of the post.

Future

Like i said, this was just a hack. The best way will be to have the Message Collection as a property of the ViewData since it cannot be used in its current state without using a strongly typed view who’s Model inherits from the base ViewModel class. That’s using Injection of some sort to update the ViewData used in the Views. I also want to pipe all the validation errors for the ModelState through the ActionMessage helper so I can use that as an all purpose notification mechanism.

If you have any suggestions, please let me know.

Advertisements

One thought on “ASP.NET MVC Action & Reaction

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