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
- [HttpPost] //Post
- public ActionResult Contact([Bind(Exclude = “Id”)]Contact contact)
- {
- //if maodel is not valid, send validated contact
- if (!ModelState.IsValid)
- {
- return View(new ContactViewModel { Contact = new Contact() }
- .Add(new ActionMessage(ActionMessaeType.Failure, “Please correct the errors in the highlighted below and try again”, “create-contact-failure”)) as ContactViewModel);
- }
- //save contact here……
- //Clear ModelState or redirect
- ModelState.Clear();
- //display view with action message
- return View(new ContactViewModel { Contact = new Contact() }
- .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);
- }
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
- public static class ActionMessageExtension
- {
- public static readonly string ActionMessageCssClassName = “action-message”;
- public static string getActionMessageCssClassName(ActionMessaeType messagetype)
- {
- switch (messagetype)
- {
- case ActionMessaeType.Success: return “action-message-success”;
- case ActionMessaeType.Alert: return “action-message-alert”;
- case ActionMessaeType.Failure: return “action-message-failure”;
- default: return “action-success-warning”;
- }
- }
- public static string ActionMessage(this HtmlHelper htmlHelper)
- {
- return ActionMessage(htmlHelper, string.Empty /* message */);
- }
- public static string ActionMessage(this HtmlHelper htmlHelper, List<ActionMessage> messages)
- {
- return ActionMessage(htmlHelper, messages, string.Empty /* htmlAttributes */);
- }
- public static string ActionMessage(this HtmlHelper htmlHelper, string foraction)
- {
- return ActionMessage(htmlHelper, null /*messages*/, foraction, (object)null /* htmlAttributes */);
- }
- public static string ActionMessage(this HtmlHelper htmlHelper, List<ActionMessage> messages,string foraction)
- {
- return ActionMessage(htmlHelper, messages,foraction, (object)null /* htmlAttributes */);
- }
- public static string ActionMessage(this HtmlHelper htmlHelper, List<ActionMessage> messages, string foraction, object htmlAttributes)
- {
- return ActionMessage(htmlHelper, messages, foraction, new RouteValueDictionary(htmlAttributes));
- }
- public static string ActionMessage(this HtmlHelper htmlHelper, List<ActionMessage> messages, string foraction, IDictionary<string, object> htmlAttributes)
- {
- //if no messages are passed in, the viewdata model is checked for messages to display
- if (messages == null)
- messages = (htmlHelper.ViewData.Model as ViewModel).ActionMessages;
- else //merge parameter with view actionmessages
- messages.AddRange((htmlHelper.ViewData.Model as ViewModel).ActionMessages);
- if (messages == null) return null;
- if (!(messages.Count > 0)) return null;
- if (foraction != null)
- messages = messages.FindAll(delegate(ActionMessage actmgs) {return actmgs.ForAction == foraction; });
- if (messages == null || !(messages.Count > 0)) return null;
- StringBuilder result = new StringBuilder();
- //Render Success Messages
- RenderActionMessage(messages, ActionMessaeType.Success, foraction, htmlAttributes,ref result);
- //Render Alert Messages
- RenderActionMessage(messages, ActionMessaeType.Failure, foraction, htmlAttributes, ref result);
- //Render Alert Messages
- RenderActionMessage(messages, ActionMessaeType.Alert, foraction, htmlAttributes, ref result);
- //Render Warning Messages
- RenderActionMessage(messages, ActionMessaeType.Warning, foraction, htmlAttributes, ref result);
- //Render …..more types of messages
- return result.ToString();
- }
- private static void RenderActionMessage(List<ActionMessage> messages, ActionMessaeType messagetype, string foraction, IDictionary<string, object> htmlAttributes,ref StringBuilder htmlresults)
- {
- StringBuilder htmlSummary = new StringBuilder();
- //Render Messages
- List<ActionMessage> typemessages = messages.FindAll(delegate(ActionMessage actmgs) { return (actmgs.MessageType == messagetype && actmgs.ForAction == foraction); });
- if (typemessages.Count > 0)
- {
- TagBuilder messageList = new TagBuilder(“ul”);
- messageList.MergeAttributes(htmlAttributes);
- messageList.MergeAttribute(“class”, getActionMessageCssClassName(messagetype));
- foreach (ActionMessage message in typemessages)
- {
- if (!String.IsNullOrEmpty(message.Message))
- {
- TagBuilder listItem = new TagBuilder(“li”);
- listItem.SetInnerText(message.Message);
- listItem.MergeAttribute(“class”, ActionMessageExtension.ActionMessageCssClassName);
- htmlSummary.AppendLine(listItem.ToString(TagRenderMode.Normal));
- }
- }
- messageList.InnerHtml = htmlSummary.ToString();
- htmlresults.Append(messageList.ToString(TagRenderMode.Normal));
- }
- }
- }
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.
One thought on “ASP.NET MVC Action & Reaction”