Quame's Rampant Rants (QRR)

Icon

QRR

ASP.NET MVC Reaction to Actions

 

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.  
  11.             //save contact here……
  12.             
  13.             //Clear ModelState or redirect
  14.             ModelState.Clear();
  15.  
  16.             //display view with action message
  17.             return View(new ContactViewModel { Contact = new Contact() }
  18.                         .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);
  19.         }

 

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

 

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.

Filed under: .NET

Timestamp issue with MySql connector 6.0 – 6.1 for Entity Framework

This is really a short post as to highlight an issue that I run into (and most will) when using MySql with Entity Framework employing the mysql connector net.

 

Scenario

I work mostly on my laptop which has mysql connector 6.0.4 installed. Using the connector in conjunction with EF v1 generated my domain object with timestamp fields mapping from a binary in my conceptual schema to timestamp in my physical schema.  Everything worked just fine till I deployed to my production machine and realized I didn’t have mysql connector installed on my production server. So…I did what most will do, download the connect but installed the latest version (v6.1). After deploying my app to the server, the unexpected happened. My app was broken. The error message indicated that Edm.binary was not compatible to mysql.timestamp. OK, I was stumped. The app works just fine my my laptop why not on my server.

 

Solution

After a couple of hours using process of elimination, I realized that using version 6.1 of the connector sets your mapping for timestamp from Edm.DateTimeOffset(conceptual schema) to timestamp (physical schema)which worked fine but version 6.0.4 of the connector sets your mapping for timestamp from Edm.binary(conceptual schema) to timestamp (physical schema) which also works as well. DateTimeOffset is a type specific to Sql server so the best option for me was to stay with 6.0.1  (using binary) which is generic and can be used with other db’s if ever need.

Filed under: ASP.NET MVC

Del.icio.us