views:

203

answers:

4

Ok,

I have a new MVC project which uses the entity framework. I'm spitting out messages (This is a bulletin board style section) now depending on some conditional factors the row in the table output must have a differant class style.

The model that is passed to the page from the controller is the entity Model (Called Messages and contains the same fields as the database)

Now to get the row styles in I did the following,

<%
               int i = 0;
               foreach (var message in ViewData.Model.MessageList)
               {
                   string className = "rowEven";

                   if (i % 2 == 0) { className = "rowOdd"; }
                   if (message.Deleted) { className = "deleted"; }
                   if (message.AuthorisedBy == null) { className = "notAuth"; }
                   if (message.Deleted) { className = "deleted"; }

           %>
                    <tr class="<%=className%>">
                        <td><%= Html.CheckBox("mc1")%></td>
                        <td>
                            <%= Html.ActionLink(message.Title, "Details", new { id = message.MessageID })%>
                        </td>
                        <td>User Name Here</td>
                        <td><%= Html.Encode(message.PublishDateTime.ToString())%></td>
                    </tr>                 
           <%
                   i++;
               } 
           %>

Which is pretty ugly, there must be a better way of doing this, any suggestions?

+5  A: 

This is most definitely presentation logic and a view is where it belongs. However, you'd better move that CSS class selection code to a view helper, which will accept both Message and MessageList:

public static string GetMessageCssClassName(this /* Don't remember :) */, Message message, MessageList messages)
{
    var cssClassName = messages.IndexOf(message) % 2  == 0 ?
        "rowOdd" : "rowEven";

    if(message.Deleted) cssClassName = "deleted";
    if(message.AuthorisedBy == null) cssClassName = "notAuth"; 
    if(message.Deleted) cssClassName = "deleted";
}

And now you can call that in your <tr class="Html.GetMessageCssClassName(...)">, getting rid of i counter:

<% foreach (var message in ViewData.Model.MessageList) { %>
    <tr class="<%= Html.GetMessageCssClassName(message, ViewData.Model.MessageList) %>">
        <td><%= Html.CheckBox("mc1")%></td>
        <td>
            <! -- Remaining stuff here -->
<% } %>
Anton Gogolev
You extend GetMessageCssClassName(this HtmlHelper helper, :) Might also want to add a note where to add the extension method and how to incude namespaces.
Martijn Laarman
It also gives you the option of adding unit tests to the parts of the presentation layer which contain logic and would otherwise probably not get automated tests written around it, which can be a nice bonus.
Dean Johnston
+1  A: 

How about adding some helper methods? You can write your own extension methods to the base HtmlHelper class, so you could write something like:

...
<tr class="<%= Html.GetMessageCssClass(i, message) %>">
...
JonoW
+2  A: 

I would reccomend using model-view-viewmodel here. This allows you to encapsulate a lot of your logic in a view model class, you then just call methods on your view model rather than putting your logic inline. this would make it look something like this...

       <%  foreach (var messageViewModel in ViewData.Model.MessageList) { %>
                <tr class="<%=message.RowClass%>">
                    <td><%= Html.CheckBox("mc1")%></td>
                    <td>
                        <%= Html.ActionLink(message.Title, "Details", new { id = message.MessageID })%>
                    </td>
                    <td>User Name Here</td>
                    <td><%= Html.Encode(message.PublishedAt)%></td>
                </tr>                 
       <% } %>

While you end up with a lot more classes you get much more readable markup and your application is far more testable. It turns your view into a very simple window onto your view model. The view model then encapsulates any logic and properties that are needed solely by the view.

In response to your comment here is an article that addresses the model - view - view model pattern in MVC. You just need to create a class with a bunch of properties and/or methods and populate this class with everything you need. Then pass the class to your view instead of passing the model directly. You can even use AutoMapper or another mapping framework to automatically map your models to your view models.

Jack Ryan
Hi jack,I like the idea of this, can you give furthor examples of how we can achieve this?
Pino
Updated the post to answer your question.
Jack Ryan
A: 

As lots have already mentioned, you could just write an extension method for your Message class that sits inside your Namespace.Site project (the mvc project).

Something like:

public static class Extensions
{
    public static string CssClass(this Message message,int counter)
    {
     if (i % 2 == 0)
      return "rowOdd";
     else if (message.AuthorisedBy == null) 
      return "notAuth";    
     else if (message.Deleted) 
      return "deleted";

     return "rowEven";
    }
}

Usage:

<tr class="<%=message.CssClass(i)%>">
    <td><%= Html.CheckBox("mc1")%></td>
    <td><%= Html.ActionLink(message.Title, "Details", new { id = message.MessageID })%></td>
    <td>User Name Here</td>
    <td><%= Html.Encode(message.PublishDateTime.ToString())%></td>
</tr>
Chris S