Hugoware

The product of a web developer with a little too much caffeine

Sienna MVC

leave a comment »

This last week has been a time of mourning for me but I have reached the acceptance phase. Now, that I’m ready to move on I can share some code I’ve been working on.

I’ve blogged a lot before about using WebForms and more recently using WebControls with ControlAdapters in MVC, but for the most part it was just messing around with small sections at a time. Tonight I published my templating framework on GitHub called Sienna.

Before you read further realize that this is not WebForms in MVC. This is WebControls in MVC — or basically the templating and using code behinds for View logic.

I might be alone on this but I am having a hard time of letting go of using code behinds for separating code from the page markup. No matter how simple a view is server side blocks of code are ugly. They are difficult to read and even harder to refactor, which is something that code behind files still excel at.

Sienna makes it so you can create new instances of Pages, assign to properties and then return them as ActionResults. To take it a step further, Sienna uses ControlAdapters to render cleaner markup and avoid the junk IDs that ASP.NET typically spews out… yes, and it drops the ViewState as well 🙂

Let’s look at an example of how Sienna works. Below is a sample page from a blog engine web application.

[/Views/Index/BlogPost.aspx]

<%@ Page Language="C#" 
    CodeBehind="BlogPost.aspx.cs" 
    Inherits="Site.Views.Home.BlogPost" %>
<html>
    <head runat="server">
        <title></title>
    </head>
    <body>
        <h1 id="name" runat="server" />
        <div id="posted" runat="server" class="date" />
        <div id="content" runat="server" />
        
        <form id="commentBox" method="post" action="[controller:Home][action:Comment]" runat="server" >
            <input id="email" runat="server" type="text" _id="email" _name="email" />
            <textarea id="comment" elementname="comment" runat="server" ></textarea>
        </form>
    </body>
</html>

[/Views/Index/BlogPost.aspx.cs]

using System;
namespace Site.Views.Home {

    //displays a blog post
    public partial class BlogPost : System.Web.UI.Page {

        //the post to publish display
        public Post Post { get; set; }

        //display the content
        protected override void OnLoad(EventArgs e) {

            this.Title = this.Post.Title;
            this.name.InnerText = this.Post.Title;
            this.content.InnerHtml = this.Post.Content;
            this.posted.InnerText = this.Post.Created.ToShortDateString();

            //add the classes for the date
            this.posted.AddClass("newest-post");

            //handle showing comments or not
            this.commentBox.Visible = Visitor.IsLoggedIn;

        }

    }

}

This might be a small amount of code but there are a lot of things to explain about it. I’ll focus in some of the more relevant parts.

  • Instead of using the built-in WebControls in ASP.NET we are using regular HTML controls that have been marked to be handled by the server.
  • Because ‘id’ and ‘name’ have special meanings to ASP.NET, we can use _id and _name to define the actual IDs to display, otherwise, no ID is shown. This allows a control to be available to the code behind using one ID and then rendered using another (or none at all). You can also use elementid, displayid or actualid if you do not like using underscores.
  • Attributes like href and src can use a special format that will be parsed by a URL helper in the background at render time. The format is [name:value][otherName:otherValue].
  • Because we use HTML elements for the template, we have access to the InnerHTML and the InnerText properties to assign values. Additionally, Sienna includes additional HTML helper methods for other common functions.
  • This example shows a normal Page as the type but you can use anything that inherits from the Page type — including ViewPages, which will give you access to the Url and Html helpers. If you aren’t using a ViewPage, the UrlHelper and Controller instances are stored in the Page.Items property.

Now, how would you actually use this view from a controller? Well, it is really quite easy…

//shows the latest blog post
public ActionResult Index() {

    //get the content
    BlogPost post = BlogPostRepository.GetLatest();

    //create the view and assign the value
    var view = this.CreatePage<Views.Home.BlogPost>();
    view.Post = post;

    //show the page
    return this.Page(view);

}

And that is it – The PageResult is returned and executed normally and the final result is nice and clean HTML output.

<html>
    <head>
        <title>My Post</title>
    </head>
    <body>
        <h1>My Post</h1>
        <div class="date newest-post" >10/15/2009</div>
        <div>
            <!-- snip... -->
        </div>
    </body>
</html>

You can also use this same approach to assign directly to properties on the page. For a second example, let’s say our code behind actually looked like the following.

using System;

namespace Site.Views.Home {

    //displays a blog post
    public partial class BlogPost : System.Web.UI.Page {

        //the name of the post to show
        public string Name {
            get { return this.name.InnerHtml; }
            set { 
                this.name.InnerHtml = value;
                this.Title = value;
            }
        }

        //the content to display
        public string Content {
            get { return this.content.InnerHtml; }
            set { this.content.InnerHtml = value; }
        }

        //the day the blog post was created
        public DateTime Posted {
            get { return DateTime.Parse(this.posted.InnerHtml); }
            set { this.posted.InnerText = value.ToShortDateString(); }
        }
    }
}

The nice thing about this approach is that the View defines which properties need to be encoded by using the InnerText or InnerHTML properties. It also allows us to assign other types like DateTime but then the View determines how to use it. Depending on how you look at it, this approach replaces the ViewModel by filling both roles at once.

Unfortunately when we create this control the links between the HTML elements and the properties are not immediately available. That doesn’t take place until after the page starts to render. So, to solve this we can assign to the Init event as part of when we create the Page.

//shows the latest blog post
public ActionResult Index() {

    //get the content
    BlogPost post = BlogPostRepository.GetLatest();

    //show the page
    return this.Page<Views.Home.BlogPost>(page => {
        page.Name = post.Title;
        page.Content = post.Content;
        page.Posted = post.Created;
    });

}

And our page is correctly created and rendered into view!

Technically, this whole approach should work with existing WebForms pages by allowing you to use MVC Controllers and Routes and then returning the correct page automatically. Of course, this hasn’t been tested and I really don’t recommend that you use it that way… but still interesting to think about 🙂

Sienna works with MVC projects so you don’t need to start anything over. Simply add the files to your project, generate the Browser definitions file (which is part of the ControlManagement class in Sienna) and off you go!

[Source Code: Sienna]

So go try it out and let me know what you think!

Advertisements

Written by hugoware

May 9, 2010 at 11:08 pm

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

%d bloggers like this: