Jun 29 2009

OpenForum - A Free Forum for MVC Applications

UPDATE: I’ve changed the links at the bottom. Hopefully that will resolve the issue that some people were having with downloading the source. I’m not sure why the original URLs worked for some people and not for others, but as my friend always reminds me, “If computers just worked, we’d all be out of a job.”

UPDATE: I’m happy to announce that OpenForum is now on CodePlex. Check it out here… http://openforum.codeplex.com/

One of the things that I really like about the MVC framework is that it offers some unique possibilities when it comes to third parties developing entire sub-systems that can easily be plugged into existing applications. Recently, I had some “down time” at my work and decided to take the opportunity to experiment with doing just that. Here are the results of that experiment. OpenForum is a forum that can easily be plugged into any MVC application with very little effort. In fact, there are only three steps to get OpenForum working with most MVC applications.

1. Add a reference to OpenForum.dll
2. Initialize OpenForum (via one line of code in the global.asax file)
3. Add an html link to OpenForum in your menu (optional)

Here’s a little screencast that I put together showing the process in greater detail.

This is defiantly still in it’s early infancy, but it’s far enough along that I wanted to put it out there and get some feedback from the community. Notable missing features include the ability to search the forum and a lack of support for any html in posts. The plan is to eventually put OpenForum up on CodePlex once things get a little further along. Until then, I’d really appreciate some community feedback. Here are some links to a few other tutorials on using some of the more advance features of OpenForum…

http://www.youtube.com/watch?v=2ZAyVlvSyeU
http://www.youtube.com/watch?v=ko_98aig44M
http://www.youtube.com/watch?v=9b9Td5CFu2g

And here are the links to the needed dll as well as a link to the source
Bin
Source

  • Share/Save/Bookmark

Jun 24 2009

Fun with IView and IViewEngine

clownI’m currently working on a little side project that I’m kind of excited about. I’m building an MVC based forum that can be plugged into any MVC application with very little effort. In fact, it looks like most scenarios will only require adding a reference to a DLL and one line of code in Application_Start (and maybe an html link in a menu somewhere). It’s not quite ready for the public yet, but there are few things I’m really proud of that I’d like to share.

In order to keep setup/configuration as simple as possible I wanted to find a way to provide a default UI out of the box. The question was “how do I render views when I don’t actually have an .aspx file on disk?” Even more important, most sites use master pages. How do I get my html to render in someone else’s master page? After some googling, reflector-ing, and cursing, I finally came up with this little class


public class DynamicView : IView
{
    public string PrimaryContentPlaceHolderId { get; set; }
    public string TitleContentPlaceHolderId { get; set; }
    public string MasterLocation { get; set; }
    public string DefaultTitle { get; set; }
    public ViewUserControl Control { get; set; }

    public void Render(ViewContext viewContext, TextWriter writer)
    {
        DynamicViewPage viewPage = new DynamicViewPage();
        viewPage.AppRelativeVirtualPath = "/";
        viewPage.MasterLocation = MasterLocation;
        viewPage.ViewData = viewContext.ViewData;

        if (TitleContentPlaceHolderId != null)
        {
            viewPage.AddContentControl(TitleContentPlaceHolderId,
                (w, p) => w.Write(DefaultTitle));
        }

        viewPage.AddContentControl(PrimaryContentPlaceHolderId,
            (w, p) => RenderControl(viewPage.Html, Control));

        viewPage.RenderView(viewContext);
    }

    public void RenderControl(HtmlHelper html,
        ViewUserControl control)
    {
        control.ViewData = html.ViewData;
        control.RenderView(html.ViewContext);
    }

    private class DynamicViewPage : ViewPage
    {
        public void AddContentControl(string contentPlaceHolderId,
            RenderMethod renderMethod)
        {
            CompiledTemplateBuilder compiledTemplateBuilder =
                new CompiledTemplateBuilder(
                    x => x.SetRenderMethodDelegate(renderMethod));

            AddContentTemplate(contentPlaceHolderId,
                compiledTemplateBuilder);
        }
    }
 }

(NOTE: For the sake of simplicity I have removed some code from the original file that didn’t apply to this blog post. I have not tried compiling the code as posted here).

This class looks a little crazy, but it’s actually pretty straight forward. You provide it with the path to a master page, the name of the “title” and “main” content areas on that master page, a title for the page, and a ViewUserControl that should be rendered in the main content area. The DynamicView class implements IView which allows the MVC framework to do its magic. And that’s it! Well, almost… there is one other little detail.

For my forum project, I’ve created a controller, actions, and associated routing rules. When a request is made for, say “http://yoursite/forum”, the request will be handed to my controller as expected. My controller action tells the MVC framework it should render a view by the name of “Index”, but how do I get the MVC framework to find the correct dynamic view? Thankfully, the MVC framework has an IViewEngine interface that can be used to solve the problem.

The MVC framework ships with a default IViewEngine that maps views to .aspx files in the “Views” directory. However, you can actually create and register additional view engines. When multiple view engines are registered, if the first engine is unable to find a valid view for the current request it hands off processing to the next view engine. Armed with that knowledge I created the following DynamicViewEngine class (NOTE: some of this code is specific to my “forum” project, but I think you’ll get the idea).


public class DynamicViewEngine : IViewEngine
{
    private string _masterPageLocation;
    private string _primaryContentPlaceHolderId;
    private string _titleContentPlaceHolderId;

    public DynamicViewEngine(string masterPageLocation,
        string primaryContentPlaceHolderId,
        string titleContentPlaceHolderId)
    {
        _masterPageLocation = masterPageLocation;
        _primaryContentPlaceHolderId = primaryContentPlaceHolderId;
        _titleContentPlaceHolderId = titleContentPlaceHolderId;
    }

    public ViewEngineResult FindPartialView(
        ControllerContext controllerContext,
        string partialViewName,
        bool useCache)
    {
        return new ViewEngineResult(
            new string[] { "Dynamic Forum Views" });
    }

    public ViewEngineResult FindView(
        ControllerContext controllerContext,
        string viewName,
        string masterName,
        bool useCache)
    {
        object controller = controllerContext.RouteData.Values["controller"];
        if (controller.ToString().ToLower() != "forum")
        {
            return new ViewEngineResult(
                new string[] { "Dynamic Forum Views" });
        }

        DynamicView view = new DynamicView();
        view.MasterLocation = _masterPageLocation;
        view.PrimaryContentPlaceHolderId = _primaryContentPlaceHolderId;
        view.TitleContentPlaceHolderId = _titleContentPlaceHolderId;

        switch (viewName.ToLower())
        {
            case "index":
                view.Control = new IndexControl();
                view.DefaultTitle = "Forum";
                break;
            case "view":
                view.Control = new ViewControl();
                view.DefaultTitle = "Forum Post";
                break;
            case "post":
                view.Control = new PostControl();
                view.DefaultTitle = "New Post";
                break;
            case "reply":
                view.Control = new ReplyControl();
                view.DefaultTitle = "Forum Reply";
                break;
            default:
                return new ViewEngineResult(
                    new string[] { "Dynamic Forum Views" });
        }

        return new ViewEngineResult(view, this);
    }

    public void ReleaseView(
        ControllerContext controllerContext,
        IView view)
    {
        IDisposable disposable = view as IDisposable;
        if (disposable != null)
        {
            disposable.Dispose();
        }
    }
}

The interesting method on this class is “FindView”. This is where I determine if the requested view is one of my forum views. If it is, I return an instance of my DynamicView with an appropriate user control being rendered in the master page’s “main” content area. (Not shown here are the various user controls. The user controls are simply sub classes of ViewUserControl with html manually written out by overriding the “Render” method).

The DynamicViewEngine can now be registered with one simple line of code, like this…

ViewEngines.Engines.Add(new DynamicViewEngine("~/Views/Shared/Site.Master", "MainContent", "TitleContent"));

So now with two fairly compact classes I’ve accomplished my goal of being able to distribute views in a DLL that can integrate into any existing application. There’s no requirement for developers to create multiple pages in order to host the forum. There aren’t a ton of files that need to be copied to specific directories. It really is as simple as adding a reference to one DLL and calling an initialization method.

But wait, there’s more! There’s one other really nice (and unintended) side effect of this architecture. Let’s say that a developer decides to use the forum, but wants to change the html of one of the pages. It turns out this is extremely easy. Simply create a “Forum” directory under the “Views” directory in the web project and then create an .aspx page for the view you wish to override (the same way you would create a view for any standard MVC application). Now the default view engine will render the custom view instead of handing control over to the DynamicViewEngine. The best part is that the default controller still handles all the “logic” of the request. All the developer needs to do is provide the html template. This highly pluggable application that is both easy to get up and running as well as simple to extended and customize.

I’m hoping to have my forum ready for people to check out soon, but until then I hope this little snippet gives you some new ideas of how you can use the MVC framework to create easily deployable components/modules.

  • Share/Save/Bookmark

Jun 23 2009

How to Keep Your Sanity and Multiple Projects Version Numbers in Sync

NOTE: This is an article that I originally wrote for codeproject.com. You can view the original article here…

http://www.codeproject.com/KB/cs/Sync_Version_Numbers.aspx

Introduction: Versioning Troubles
In the project I’m currently working on, we have multiple projects in one solution. One of the problems we’ve faced is how to keep all the version numbers (generally found in the ApplicationInfo file) in sync. Ok, so “problem” might be too strong of a word. It’s more of an annoyance, but nonetheless, it would be nice to have some sort of automated solution. The other day, I downloaded some sample code for an unrelated issue, but they had a really nice solution to the versioning puzzle. I’ve now added this solution to my current project, with a few little wrinkles, and it seems to be working really well (we won’t know for sure until the next release). [NOTE: this solution assumes you want the same version number for each project in the solution]

The Short of It
Here’s what you need to do:

1.Remove these attributes from the AssemblyInfo files in each project in the solution:
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
2.Create a new file in the root directory of the solution (I named mine VersionInfo).
3.Add the attributes that we removed from the AssemblyInfo file to your new file (you’ll also need to add a reference to the System.Reflection namespace).
4.On each project in the solution, right click the project and select Add->Existing Item.
5.HERE’S THE KEY. Browse to your newly created version file, but instead of clicking the “Add” button, click the little down arrow next to the word “Add” and then click “Add as link” from the menu it drops down.

What Did We Just Do?
Normally when you add an existing file, the IDE copies the selected file to the current directory. By selecting “Add as link,” what we’ve done is link the file in from its original location (yes, I know you figured that out from the name “Add as link”). Now when we build our solution, each project will compile in the exact same file (VersionInfo in my case) , thus giving each project the same version number.

The Next Step
To make this really cool (well, at least I think it’s cool) I’ve created a build script to automate the process of creating the release build. The script prompts for the version number of the release and updates the version file before doing the build. Here’s a snippet of my script (parts of this script were omitted to protect the innocent)…

Dim versionNumber
Set shell = CreateObject("WScript.Shell")
Set fileSystemObject = CreateObject("Scripting.FileSystemObject")

sub Main()
Echo "Getting version number"
versionNumber = InputBox("What version is this build?", "Version")
UpdateFileVersion versionNumber

CommitToVersionControl
GetLatestFromVersionControl

Echo "Building"
RunCommand _
"""C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\devenv.exe""_
SolutionName.sln /Rebuild Release", "Failed to build"

RunUnitTests
CreateZipFile

MsgBox("Done.")
end sub

sub Echo(message)
WScript.Echo message
end sub

sub UpdateFileVersion(versionNumber)
Set file = fileSystemObject.OpenTextFile("Version.cs", 2)

file.WriteLine "using System.Reflection"+ vbcrlf + _
vbcrlf + _ "[assembly:AssemblyVersion""" + _
versionNumber + ".0.0"")]" + _
vbcrlf + "[assembly:AssemblyFileVersion""" + _
versionNumber + ".0.0"")]"

file.Close
end sub

sub RunCommand(command, failMessage)
result = shell.Run(command, 1 , 1)
TestResult result, failMessage
end sub

sub TestResult(result, failMessage)
if result <> 0 then
MsgBox(failMessage)
WScript.Quit
end if
end sub

Main

Conclusion
I now have a very simple and very automated versioning process. It makes sure that our projects’ version numbers are never out of sync and, more importantly, makes sure that I don’t forget to update the version numbers when I do a final release build. I hope this helps.

  • Share/Save/Bookmark

Jun 6 2009

MVC - Routing to a static file

mvc routing

I have been following the .Net MVC project since very early on. There are lots of things I like about. I like the testability. I like the control over the final html. But one of the things that I’ve really come to appreciate after watching the project evolve over the last year is its extensibility. It seems to me that Phil Haack and his team have done a great job of allowing developers (i.e. me) to plug in our own code at almost every point in the process. This week I had a new feature request at work that was easily solved due to that extensibility.

I have an application that I built using MVC. It’s sort of a CMS system with some specialized logic for allowing our users to download large files (i.e. video games) using a 3rd party downloader. Well, this week management decided they’d like to try out a different downloader to see how it affects conversions. This would be a short term test on 2-3 off the more popular files on the site. The problem is there’s not really a clean way under the current architecture to use a different downloader on only a select number of pages. It also doesn’t really make sense to make extensive code changes in order to support a 4 day test.
After thinking about the problem for a while I came up with a fairly clean, low impact (code wise) solution. Why not use the MVC routing mechanism to route 2-3 specific urls to a temporary hardcoded page? That way, I won’t have to change any logic in the underlying system to handle the handful of temporary edge cases. It also means I won’t have to update any “url” logic in my system. In fact, the urls of the pages won’t change at all. I’ll just intercept calls to the specific pages before they’re handed off to the current controller and instead route them to a static html file. Best of all, the only change to the code base would be to add a couple of new routing rules in the global.asax file (a solution that required zero code changes would be even better, but this still seems pretty reasonable to me).

However, when I did a little searching on how to route to a static file in MVC I couldn’t really find a solution. I did find people adding an “Ignore” route to allow requests to static files to bypass the MVC engine, but that wasn’t really what I was after. What I did find was a handy little class called RouteBase which is a base class that you can inherit in order to create specialized routing rules. Perfect! I experimented for about an hour and came up with the following…

public class StaticRoute : RouteBase, IRouteHandler, IHttpHandler
{
    private string _url;
    private string _filePath;

    public StaticRoute(string url, string filePath)
    {
        _url = url;
        _filePath = filePath;
    }

    public override RouteData GetRouteData
        (HttpContextBase httpContext)
    {
        if (httpContext.Request.Url.AbsolutePath == _url)
        {
            return new RouteData(this, this);
        }
        else
        {
            return null;
        }
    }

    public override VirtualPathData GetVirtualPath
        (RequestContext requestContext,
         RouteValueDictionary values)
    {
        return null;
    }

    public IHttpHandler GetHttpHandler
        (RequestContext requestContext)
    {
        return this;
    }

    public bool IsReusable
    {
        get { return true; }
    }

    public void ProcessRequest(HttpContext context)
    {
        context.Response.WriteFile(_filePath);
    }
}

Not to scary really. Now all I have to do is use it. Here’s an example…

routes.Add(new StaticRoute("/some/url", "~/static_file.html"));

Basically, my StaticRoute class takes two arguments in the constructor, the incoming url to watch for and the static file to return when a request to that url is made. RouteBase has two abstract functions, GetRouteData and GetVirtualPath. The second, GetVirtualPath, is used to create a url based on a given controller, action, parameters, etc. which isn’t really important here so I return null (i.e. “I can’t help you, go ask some other route what the url should be”). GetRouteData is the interesting function. Here I check if the requested url matches the url we’re trying to route to a static file. If it does I return an IRouteHandler that should handle the request. For simplicity, I’ve made my StaticRoute class also implement IRouteHandler. IRouteHandler has one function, GetHttpHandler, which returns the IHttpHandler that is responsible for actually writing content to the response stream. Again for simplicity I’ve made my StaticRoute class implement IHttpHandler as well, so i just return “this”. IHttpHandler has a function called ProcessRequest which passes an HttpContext as a parameter. I simply pass the path to a static file and that files contents get written to the response stream. Simple and easy.

As I said at the beginning, one of my favorite parts of the MVC framework is its extensibility. In this case I was able to provide custom logic to process a very edge case scenario with only a few lines of code. You gotta love that!

  • Share/Save/Bookmark