jBlogMvc : part 1 Building the Administration Area

NOTE: In this series I build a blogengine using ASP.NET MVC and jQuery from scratch in order to learn more about these new technologies. If you haven't read the first post in this series, I would encourage you do to that first, or check out the jBlogMvc category. You can also always subscribe to the feeds.

In this part of the series, I build the administration area of the blog engine I am building using the ASP.NET MVC and jQuery, in this part I will cover more basic features used in any blog engine, so lets get started.

What will part 1 cover ?

Basically it will cover how to build an administration area, I chose the wordpress blog engine and tried to clone its structure and some look and feel of it, the operations I will implement in this part will be :

  • Visitor
    • Login — I will just reuse the code available with the default project template for membership stuff.
  • Admin
    • Logout
    • Add Post

The stuff I collected and used all over the net from blogs and used in this part can be summarized in the following,

  1. Using membership for validation
  2. Using the Authorize attribute
  3. Using Model Binders
  4. jQuery Client validation
  5. Small validation framework for business rules and server side validation.(originally written by scott gu)
  6. Using nested master pages in ASP.NET MVC
  7. Applying the "Post/Redirect/Get" (aka PRG) pattern.
  8. Applying some css to make it look nice (based on wordpress blogengine admin layout) [more]

To hold your interest the final look of the administration area will look like this :

admin area

Ok Lets see some code

What's new in version 1 :

Routes

Routes now include an extra route for directing users to the admin area

[code:c#] 

routes.MapRoute(
    "Admin",
    "admin/{action}",
    new { controller = "Admin", action = "Index" }
    );

[/code] 

Models

No new models were added as the database remains as it is, however, I like to highlight a new feature available in the Preview 5, ModelBinders, although ScottGu just mentioned that the team has not yet finalized and will be changed in the beta version.

Note: the MVC team plans to tweak the IModelBinder interface further for the next drop (they recently discovered a few scenarios that necessitate a few changes).  So if you build a custom model binder with preview 5 expect to have to make a few tweaks when the next drop comes out (probably nothing too major – but just a heads up that we know a few arguments will change on its methods). By ScottGu

ModelBinders, which is provided to allow Action methods to take complex types as their parameters. Previously, action methods were only able to take simple types such as strings and integers as their parameters. The new ModelBinder provides the facility to build complex types from component parts that (for example) may be part the result of submitting a form with several fields.

Learn more about ModelBinders from Melvyn Harbour, Timothy Khouri and Maarten Balliauw.

The following code listing is from the PostBinder

 

[code:c#] 

public class PostBinder : IModelBinder
{
    private static string Concat(string modelName, string propertyName)
    {
        return (String.IsNullOrEmpty(modelName)) ? propertyName : modelName + "." + propertyName;
    }

    private static T LookupValue<T>(ControllerContext controllerContext, string propertyName, ModelStateDictionary modelState)
    {
        IModelBinder binder = ModelBinders.GetBinder(typeof(T));
        object value = binder.GetValue(controllerContext, propertyName, typeof(T), modelState);
        return (value is T) ? (T)value : default(T);
    }

    public object GetValue(ControllerContext controllerContext, string modelName, Type modelType, ModelStateDictionary modelState)
    {            if (controllerContext == null)
        {
            throw new ArgumentNullException("controllerContext");
        }
        if (modelType != typeof(Post))
        {
            throw new ArgumentException("This binder only works with Post models.", "modelType");
        }

        // Instantiate a post object, then bind values to each property
        Post p = new Post()
        {

            Title = LookupValue<String>(controllerContext, Concat(null, "Title"), modelState),
            Body = LookupValue<string>(controllerContext, Concat(null, "Body"), modelState),
            Slug = LookupValue<String>(controllerContext, Concat(null, "Slug"), modelState),
            CDate= LookupValue<DateTime>(controllerContext, Concat(null, "CDate"), modelState)
        };
        return p;
    }

}

[/code] 

 

Don't forget to register the Binder (there are four ways to register, check ScottGu's post).

[code:c#] 

protected void Application_Start()
      {
          ModelBinders.Binders[typeof(Post)] = new PostBinder();
          RegisterRoutes(RouteTable.Routes);
      }

[/code] 

Controllers

In this part, the AdminController appears to hold,  the admin tasks, which till now only include the following actions.

  • index : a default action redirects to the write action.
  • write : an action to be responsible for writing things (only have posts now), so it just redirects to posts.
  • writepost : renders a view to enable authenticated users to write posts and publish it.
  • addpost : a Http Post action which inserts the new post into the database.

 

[code:c#] 

[Authorize]
public class AdminController : Controller
{
    jBlogMvcDataContext jbdc = new jBlogMvcDataContext();

    public ActionResult Index()
    {
        //just a default redirection
        //maybe in future this should be configurable
        return RedirectToAction("Write");
    }
    public ActionResult Write()
    {
        //just a default redirection
        //maybe in future this should be configurable
        return RedirectToAction("WritePost");
    }

    [AcceptVerbs("GET")]
    public ActionResult WritePost()
    {
        Post p = new Post();
        return View(p);
    }

    [AcceptVerbs("POST")]
    public ActionResult AddPost(Post p)
    {
        if (!ViewData.ModelState.IsValid)
            return View("WritePost", p);

        try
        {
            Helpers.InsertPost(p);
            return RedirectToRoute("Posts", new { slug = p.Slug });
        }
        catch
        {
            Helpers.UpdateModelStateWithViolations(p, ViewData.ModelState,System.Data.Linq.ChangeAction.Insert);
            return View("WritePost", p);
        }
    }
}

[/code] 

More over, HomeController now has 2 more extra actions :

  • login
  • logout

just copied from the default template nothing new added.

Views

A lot of views are added this part, actually I am trying nesting master pages, one for the admin area overall, and the other for each module (like: write, manage, .. and so in wordpress), so I added :

  • admin.master
  • admin_write.master
  • writepost.aspx
  • login.aspx
  • _loginWidget.acsx

writepost.aspx

<%@ Page Title="Write Post" Language="C#" MasterPageFile="~/Views/Admin/Admin_Write.Master"
    AutoEventWireup="true" CodeBehind="WritePost.aspx.cs" Inherits="jBlogMvc.Views.Admin.WritePost" %>

<asp:Content ContentPlaceHolderID="head" runat="server">

    <script type="text/javascript">
        $(document).ready(function() {
        $("#fields").validate();});
    </script>

</asp:Content>
<asp:Content ContentPlaceHolderID="MainContent" runat="server">
    <h2>Write Post</h2>
    <form id="fields" action="<%=Url.Action("AddPost","Admin")%>" method="post">
    <table cellpadding="0" cellspacing="0">
        <tr>
            <td>
                <div id="postfields">
                    <p>
                        <label for="title">Title</label>
                        <%=Html.TextBox("Title", new { id="title",@class="required"})%>
                        <%=Html.ValidationMessage("Title")%>
                    </p>
                    <p>
                        <label for="body">Body</label>
                        <%=Html.TextArea("Body", new { id = "body", rows = "6", cols = "50", @class = "required" })%>
                        <%=Html.ValidationMessage("Body")%>
                    </p>
                    <p>
                        <label for="slug">Slug</label>
                        <%=Html.TextBox("Slug", new { id = "slug", @class = "required" })%>
                        <%=Html.ValidationMessage("Slug")%>
                    </p>
                    <p>
                        <label for="cdate">Creation Date</label>
                        <%=Html.TextBox("CDate", ViewData.Model.CDate.ToString("MM/dd/yyyy"),
                            new { id = "cdate", @class = "required date" })%>
                        <%=Html.ValidationMessage("CDate")%>
                    </p>
                </div>
            </td>
            <td id="tdsubmitbox" valign="top">
                <div id="submitbox">
                    <div class="buttons">
                        <button type="submit" class="positive">
                            <img src="../../Content/icons/tick.png" alt="" />Publish
                        </button>
                    </div>
                </div>
            </td>
        </tr>
    </table>
    </form>
</asp:Content>

_loginWidget.ascx

And it looks like this

logged in view                                  logged off

logged in widget not logged in

Utils

I added some code to perform the validation logic for custom business rules, this is the simplest implementation for this task copied from ScottGu's post, for more complex implementation scenarios I strongly recommend the following posts,

For client side jquery I used the validation plugin found here, Server side I used the small framework scott gu wrote in his post for simplicity.

Client side validation

And server implementation as well

serverSide

Moreover, The Helper Class (which acts as the business layer) has some additions in order to add a post to the database.

Css and designs

Css http://particletree.com/features/rediscovering-the-button-element/ and http://wordpress.org

Icons http://www.famfamfam.com/lab/icons/silk/

Summary

And thats all for this part, I have more and more features coming while writing this engine I have learned much till now, hope someone is learning with me too.

In this part, I used some features of the ASP.NET MVC to build an administration area, jQuery too was used on client side (validator plugin) so what do you think? you are most welcomed to leave comments.

Download version one : jBlogMvc_version_1.zip

If you liked this blog post then please subscribe to this blog.


Posted

in

,

by

Tags:

Comments

8 responses to “jBlogMvc : part 1 Building the Administration Area”

  1. Łukasz Sowa Avatar

    Gratz on the first part 🙂 You should consider explaining not only what you do, but also how it works (you haven’t done that when you described ModelBinder and I think you should). Giving referencing URLs is ok but short description from you would be even more ok :)) Gratz again!

  2. cowgaR Avatar
    cowgaR

    Great article, thus Open ID would be fine…

  3. lee Avatar

    I’m still taking this all in as I am yet to take the MVC plunge – But this looks awesome to me so far! Can’t WAIT to see this take shape… Top job please keep it up.

  4. redsquare Avatar
    redsquare

    Really good job, has really helped my learning experience of the MVC framework.
    Thanks Amr

  5. Muhammad Mosa Avatar

    It is going to rock. Now you showed me how it will look like. I’m sure you know what I am talking about.

  6. CarlH Avatar
    CarlH

    Nice work, but take a look at http://codebetter.com/blogs/matthew.podwysocki/archive/2008/09/25/dc-alt-net-building-asp-net-mvc-apps-wrapup.aspx and try to implement a similar Repository. Also the "_" before your web controls is very ugly and unnecessary.

  7. Keith Avatar
    Keith

    I made a change to the LookupValue<> function and wondering if this is the proper place for the change. First the new code:
    ———————-
    protected static T LookupValue<T>(ControllerContext controllerContext, string propertyName, ModelStateDictionary modelState, T defaultValue)
    {
    IModelBinder binder = ModelBinders.GetBinder(typeof(T));
    object value = binder.GetValue(controllerContext, propertyName, typeof(T), modelState);
    if (value is T) {
    // Put string value back in ModelState to cause control repopulation
    if (!controllerContext.Controller.ViewData.ModelState.ContainsKey(propertyName))
    controllerContext.Controller.ViewData.ModelState.Add(propertyName, new ModelState() { AttemptedValue = value.ToString() } );
    return (T)value;
    }
    else
    return defaultValue;
    }
    ———————-

    The goal was to put back into the ModelState the field value so that if the form is resubmitted due to an error, the Html.Textbox() (and others) would be refilled. Scott Gu’s P5 post said that the ModelState will be filled on error but I can’t see where that is happening except if a particular field causes the error.

    Anyway, I’m just looking for best practices here and if this functionality should be somewhere else, then let me know.

  8. Janko Avatar

    Wow, great! I can’t wait to see the rest of the articles.

Leave a Reply

Your email address will not be published. Required fields are marked *