Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

SharpDOM, View Engine for ASP.NET MVC

0.00/5 (No votes)
12 Jun 2014 1  
Sharp DOM is a view engine for the ASP.NET MVC platform allowing developers to design extendable and maintenable dynamic HTML layouts using the C# 4.0 language. It is also possible to use the Sharp DOM project to generate HTML layouts outisde of the MVC framework.

Introduction

Nowadays, there are a lot of Web frameworks (in PHP, Java, .NET, etc.) allowing to generate HTML layout with the help of certain template engines. Indeed, sometimes, these frameworks are quite powerful and amazing. The only problem is that these template engines introduce their own external DSLs which are not native for .NET developers. It makes the usage of such template engines a little bit difficult, not to say more.

The Sharp DOM project allows you to develop HTML layout as C# code using all the power of the modern object-oriented C# 4.0 language. You can use features of C# 4.0 like inheritance, polymorphism, etc. In other words, designing of HTML layout can be transformed into the development of strongly typed HTML code. :-)

Usage of Sharp DOM

  1. Simply download DLL files from the Downloads page - http://sharpdom.codeplex.com/releases
  2. Reference SharpDom.dll file and SharpDom.Mvc.dll file in your ASP.NET MVC project.
  3. Create a class inherited from the SharpDomView<> class (where the generic parameter is the view model).
  4. Override the CreateTagTree() method of this newly created class and write the HTML layout inside this method in the form of C# code.
  5. In the controller, create the view model and populate it with proper data.
  6. Write some plumbing code to embed the Sharp DOM view engine into the MVC platform:
public class MvcApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();
        RegisterRoutes(RouteTable.Routes); 
        RegisterViewEngines(ViewEngines.Engines);
    }
    public static void RegisterViewEngines(ViewEngineCollection viewEngines)
    {
        viewEngines.Clear();
        viewEngines.Add(new SharpDomViewEngine());
        // SEE HERE! - we register Sharp DOM engine into MVC platform
    }
}

Example of Transformation

Let's look at the source HTML layout we are going to transform from old plain HTML into strongly typed HTML:

<html>
    <head>
        <title>Title of the page</title>
        <meta http-equiv="Content-Type" 
          content="text/html;charset=utf-8">
        <link href="css/style.css" 
          rel="stylesheet" type="text/css">
        <script 
          href="http://www.codeproject.com/JavaScripts/jquery-1.4.2.min.js" 
          type="text/javascript">
    </head>
    <body>
        <div>
            <h1>Test Form to Test</h1>
            <form id="Form1" type="post">
                <label>Parameter</label> =
                <input type="text">Enter value</input>
                <input type="submit" text="Submit !" />
            </form>
            <div>
                <p>Textual description of the footer</p>
                <a href="http://google.com">
                    <span>You can find us here</span>
                </a>
                <div>
                    Another nested container
                </div>
            </div>
        </div>
    </body>
</html>

This could be rewritten using C# 4.0 language as:

html[
    head[
        title[ "Title of the page" ],
        meta.attr(http-equiv: "contenttype", content: "html", charset: "utf-8"),
        link.attr(href: "css/style.css", rel: "stylesheet", type: "css"),
        script.attr(href:"/JavaScripts/jquery-1.4.2.min.js", type: "javascript")
    ],
    body[
        div[
            h1[ "Test Form to Test" ],
            form.attr(id: "Form1", type: "post")[
                label[ "Parameter" ], "=", input.attr(type:"text", value: "Enter value"), br,
                input.attr(type: "submit", value: "Submit !")
            ],
            div[
                p[ "Textual description of the footer" ],
                a.attr(href: "http://google.com" )[
                    span[ "You can find us here"]
                ]
            ],
            div[ "Another nested container" ]
        ]
    ]
]

As you can see, both the listings are very correlating to each other - each line in the HTML layout unambiguously correlates to the only line in C# code and vice verse. In other words, the HTML-to-C# mismatch impedance is quite small. On the other hand, a developer having some skills in the C# language could easily write strongly typed HTML code. And all the benefits of using the C# compiler are available now: compile-time checks, tight control over generated HTML by using embedded C# code, etc.

Advanced Topics

ViewModel: You can embed the values coming from your view model (that you pass from the controller to the view) into your HTML code.

public class ViewModel
{
    public string Title { get; set; }
}

public override ContainerTag CreateTagTree(ContainerTag container)
{
    return container
    [
        html[
            head[
                title[ Model.Title ]  // SEE HERE ! - we can use use view model
            ],
            body[
                div[
                    span[ "Hello World !!!" ]
                ]
            ]
        ]
    ];
}

As you can see, all you have to do is to override the CreateTagTree() method and fill it with HTML layout using C# language. The variable container is a container into which you are to add your custom HTML layout. Finally, the container variable is to be returned. During the construction of the HTML layout, you are free to use the view model supplied by the controller.

View Logic: Besides HTML tags expressed in the form of C#, you can embed some logic into HTML code using the so-called "operators".

Operator is some C# construct allowing you to manipulate the nested HTML layout. Now there are only two operators supported - Foreach and If.

  • ForEach duplicates nested HTML tags using data items supplied in the form of IEnumerable collections - so each duplicate of the nested HTML piece can be customized using the proper supplied item.
  • If just "hides" nested HTML tags if the parameter is "False". HTML tags are not cut away, just the Visibility property is set to "False". So HTML tags are not rendered but they are still present in the tree of tags.
public class ViewModel
{
    public class UlItem
    {
        public string Text { get; set; }
        public int Value { get; set; }
    }
    public string Title { get; set; }
    public UlItem[] UlItems { get; set; }
}

public override ContainerTag CreateTagTree(ContainerTag container)
{
    return container
    [
        html[
            head[
                title[ Model.Title ]
            ],
            body[
                div[
                    ul[
                        // SEE HERE ! - example of usage of the "Foreach" operator
                        ForEach(Model.UlItems, (item, c) => c[
                            li.attr(id: item.Value.ToString())[ item.Text ]
                        ])
                    ],
                    // SEE HERE ! - example of usage of the "If" operator
                    If(Model.UlItems.Length % 2 != 0)[
                        span[ "Amount of items is odd ..." ]
                    ]
                ]
            ]
        ]
    ];
}

User Controls: You can divide the HTML layout into small pieces using a-la WebForms' UserControls.

User Control is a packed form of HTML island which can be reused in other places many times. It correlates to ASP.NET WebForms' Custom Control rather well.

Here is the code for a User Control:

public class ControlModel
{
    public string Text { get; set; }
    public string Value { get; set; }
}

public class ControlBuilder : UserControlBuilder<controlmodel>
{
    public override ContainerTag CreateTagTree(ContainerTag container)
    {
        return container
        [
            div[ "Text is " + Model.Text ],
            div[ "Value is " + Model.Value ] 
        ];
    }
}

Code for the page:

public class ViewModel
{
    public string Title { get; set; }
    public ControlModel ControlModel { get; set; }
}

public class PageBuilder : HtmlPageBuilder<viewmodel>
{
    /// <summary>SEE HERE ! - instance of the user control
    ///    - should be public and virtual.</summary>
    public virtual ControlBuilder ControlBuilder 
         { get { return CreateBuilder<controlbuilder>(); } }

    public override ContainerTag CreateTagTree(ContainerTag container)
    {
        return container
        [
            html[
                head[
                    title[ Model.Title ]
                ],
                body[
                    div[
                        // SEE HERE ! - example of using the user control
                        ControlBuilder.CreateTags(Model.ControlModel)
                    ]
                ]
            ]
        ];
    }
}
  1. Initially, during design, the user control should derive from the UserControlBuilder class and override the CreateTagTree method in order to supply the needed HTML layout. The user control has its own view model which can be used for its customization.
  2. When using, the page builder should have a property of the user control's class type. This property should create the user control, and then this property can be used in the HTML layout directly.
  3. User controls could have nested user controls in the same manner as pages could have nested user controls (see such an example in the following section).

Master Pages: You can use the master-page approach similar to the same approach existing in WebForms. It is also possible to dynamically (at run-time) choose what master page to use with the current content page.

Our master pages are quite similar to ASP.NET WebForms' Master Pages - you design a master page builder which is completely separated from the content page builder. Then the master page builder's output is combined with the content page builder's output, and finally the combined resulting HTML is shipped to the browser. For it, the master page builder is to contain some placeholders. The placeholders are to be substituted in the content page builders. The chain of {master page, contain page} could be extended, and the master page could have a super master page, and so on. Another useful feature around the master page approach is the possibility to dynamically (at run-time) choose what master page should be used with the current content page.

Master page code:

public class MasterModel
{
    public string Title { get; set; }
}

public class MasterBuilder : HtmlPageBuilder<mastermodel>
{
    /// <summary>SEE HERE ! - we declare two placeholders of the master page</summary>
    public virtual PlaceHolderDelegate FirstPlaceHolder { get; set; }
    public virtual PlaceHolderDelegate SecondPlaceHolder { get; set; }

    public override ContainerTag CreateTagTree(ContainerTag container)
    {
        return container
        [
            html[
                head[
                    title[Model.Title]
                ],
                body[
                    div[
                        // SEE HERE! - example of placeholder without default content
                        PlaceHolder(FirstPlaceHolder),
                        span[ "some intermediary text" ],
                        // SEE HERE! - example of placeholder with default content
                        PlaceHolder(SecondPlaceHolder, c => c[
                            span[ "stub for second placeholder" ]
                        ])
                    ]
                ]
            ]
        ];
    }
}

Content page code:

public class ContentModel
{
    public string Text { get; set; }
    public string Value { get; set; }
    public MasterModel MasterModel;
}

public class ContentBuilder : HtmlPageBuilder<contentmodel>
{
    // SEE HERE! - example of method defining the content for the placeholder
    public virtual ContainerTag FirstPlaceHolderContent(ContainerTag container)
    {
        return container
        [
            span[ Model.Text ],
            span[ "somtehing other" ],
            span[ Model.Value ]
        ];
    }

    public override ContainerTag CreateTagTree(ContainerTag container)
    {
        return
            // SEE HERE ! - example how to create master page
            CreateBuilder<masterbuilder>(container)
                // SEE HERE ! - example how to attach specific
                // content to master page's placeholder
                .Attach<masterbuilder>(b => b.FirstPlaceHolder = FirstPlaceHolderContent)
                // just call master page method to generate combined content 
                .CreateTags(Model.MasterModel);
    }
}
  1. In the master page builder, you declare some placeholder delegates. Then, in the master page's HTML layout, you can use these delegates with or without predefined content for such placeholders.
  2. In the content page builder, you specify the functions (with signature the same as the placeholder delegates) which are to define the HTML layouts.
  3. In the content page builder, in the CreateTagsTree() method, you have to execute three steps:
    1. Create the needed master page (so, you can choose the master page dynamically),
    2. Attach your functions by creating content for the master page's placeholder delegates,
    3. Ask the master page to create combined content.

Templated User Controls: You can use templated user controls. In WebForms, the nearest analogy is Repeater's TemplateItem tag in layout.

This feature is not likely to be widely used, so please see the full example of this feature on the Documentation on the CodePlex site - http://sharpdom.codeplex.com/documentation.

Using ASP.NET MVC's HTML and URL Helpers

Although Sharp DOM view engine rather differs from the default view engine of ASP.NET MVC (= WebForms), we believe, that some useful things from the WebForms view engine could be great and helpful additions to your Sharp DOM projects. So we implemented some initial support for the Html and Url helper classes defined in the WebForms view engine. You can continue to use methods like ActionLink(), ValidationMessageFor(), etc., directly inside your HTML layout expressed in C# form. Sounds great, doesn't it? Following is an example about how the usage of MVC's helpers can be seamlessly transformed into Sharp DOM:

Example of usage of MVC helper inside WebForms view engine:

<div class="editor-label">
    <%= Html.LabelFor(m => m.UserName) %>
</div>

It can be easily transformed into a Sharp DOM project like:

div.attr(class: "editor-label")[
    Html.LabelFor(m => m.UserName).ToLiteral()
]

The only thing to notice is that you have to use the ToLiteral extension method in order to convert a MvcHtmlString object to Sharp DOM's Tag object.

Unfortunately, full 100% support of all such helpers is not possible because they are very aligned to WebForm's internals. So our current support is limited to the helper methods returning a MvcHtmString instance. Although the biggest majority of methods of such MVC helpers return a MvcHtmlString object, some methods don't. Probably the most important exception is BeginForm()/EndForm() methods. In such cases, you will have to use similar (but not identical) things of the Sharp DOM project:

Example of usage of BeginForm()/EndForm() inside WebForms view engine:

<% using (Html.BeginForm()) { %>
    <div>
        Blah-blah-blah-blah
    </div>
<% } %>

This can be transformed with the help of Sharp DOM elements into:

form.attr(action:UrlHelper.Action("LogOn"), method:"POST")[
    div[
        "Blah-blah-blah-blah"
    ]
]

As you can see, we use the UrlHelper.Action() helper without any change to get the correct action URL.

Extensibility: There is also a possibility to traverse the generated tree of HTML tags and alter it with the help of LINQ. Not only traverse, but also alter.

Maintainability: In the case when your HTML code expressed in C# is supported by another developer, she can use all features of C# to extend/override/modify the tree of generated HTML tags before the tags are shipped to the browser. Open/close approach is in action.

See an example of using each concrete feature in the Documentation on CodePlex site - http://sharpdom.codeplex.com/documentation.

What C# 4.0 artifacts are used?

C# Artifacts

The mostly used thing is indexer - each time you see square brackets, you see an indexer of a certain C# object, and this C# object has the same name as the name of the correlated HTML tag. So if you see code like:

h1[ "Test Form to Test" ]

you know, that there is C# object named "h1" and this object correlates to the H1 tag of the HTML specification. Next, you can notice that there are optional named parameters, in sentences like:

form.attr(id: "Form1", type: "post")

Also, params parameters are used (all parameters are of the same base type - Tag):

body[ h1[...], form[...], div[...], div[...] ]

There is also an overloaded implicit conversion operator from string type to Tag class:

label[ "Parameter" ], "=", input.attr(type: "text", value: "Enter value")

Advantages

There are some advantages of switching from plain HTML code to C# code:

  • Compile-time checks.
  • Freedom for you as a developer to use any presentation-level helper classes/constants, etc., directly inside the view.
  • Possibility to maintain already written HTML layout (expressed in a C# manner) using features of C# like inheritance (all methods of Sharp DOM are public and virtual).
  • Presence of all generated HTML tags in the form of a tree - so an amount of possibilities are open now:
    1. possibility to use LINQ to Objects to find certain elements in the tree,
    2. possibility to change/alter/extend found items in your sub-class - and even in the inherited classes.

Rather good things for maintainability.

Maintainability

Extendibility and maintainability of the HTML layout is one of the main reasons why the Sharp DOM project was developed. During our programming lives, we have come across many situations when we had HTML layout as an ASPX file, but:

  • we weren't able to alter the ASPX layout directly - because there was a possibility that in the near future the layout would be reverted back to the source state,
  • we weren't able to extend the code-behind code - because it was obfuscated or sealed or not properly architected, not allowing desired changes.

That's why we spent a lot of time thinking over the architecture of the Sharp DOM project, trying to find the optimal form of code from the point of view of maintainability. That's why we did the following things inside the code:

  • Practically each and every method/property is public and virtual. It allows to use class inheritance for future maintenance (in case when subscribing to events is not enough).
  • Template Method Design Pattern is widely used (and sequence steps are virtual methods).
  • Each tag has a unique internal ID which can be reused for maintenance benefits.
  • Each tag has some events - OnPreRender and OnPostRender. Each builder (master page builders, content page builders, user control builders) has the same events plus OnPre/PostCreateTags and some other. Also, all these events are propagated up through the tag tree. So it is possible to subscribe only to the root tag to listen to all events.
  • The whole tree of generated HTML tags is split into nested areas - areas where different builders have worked. So it is possible to easily recognize what code generates what HTML. It could simplify the life of developers working in support departments and who have to improve the somehow already designed HTML code in order to fulfill changed business requirements.

Authors

Please leave your comments and other feedback on the Discussion page - http://sharpdom.codeplex.com/Thread/List.aspx.

We quite hope the project is helpful for you!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here