Decouple controllers and other objects from context in asp.net mvc

When I decided to write down a little class to manage a menu, I created a simple object that reads the menu data in xml, and transform it into a series of MenuItem object. The first thing I wanted to do is writing some test to verify that the logic is ok, but since my class uses Url.RouteUrl to build the url from the controller and action strings, testing becomes difficult, because it does not runs outside iis.

You can surely mock controller context in asp.net mvc, and there are a lot of good articles in the net dealing with this, but sometimes I prefer a simpler approach that I used a lot in classic webform asp.net applications. Since the class that contains the logic to format the menu needs only to access the UrlHelper class, I abstracted it with a simple interface.

public interface IUrlHelper
{
   String RouteUrl(RouteValueDictionary values);
   String RouteUrl(Object values); 
   String RouteUrl(String routeName, Object values); 
}

This simple interface does not even contains all methods of the standard UrlHelper, but it is enough for me, I’ll add more methods when I’ll need them. Now my class can declare a dependency to this interface.

   public class MasterLogic
   {
      public IUrlHelper Url { get; set; }

      public MasterLogic(IUrlHelper url)
      {
         Url = url;
      }

then create the real object that will be used in the site. It is a simple wrapper for the real UrlHelper

   public class MvcRouteHelper : IUrlHelper
   {
      public UrlHelper Helper { get; set; }

      public MvcRouteHelper(UrlHelper helper)
      {
         Helper = helper;
      }

      #region IUrlHelper Members

      public string RouteUrl(System.Web.Routing.RouteValueDictionary values)
      {
         return Helper.RouteUrl(values);
      }

      public String RouteUrl(Object values)
      {
         return Helper.RouteUrl(values);
      }

      public string RouteUrl(string routeName, object values)
      {
         return Helper.RouteUrl(routeName, values);
      }

      #endregion
   }

Now the controller that uses MasterLogic class can create instance with this simple code.

masterLogic = new MasterLogic(new MvcRouteHelper(Url))

For testing purpose I created another class that implements the IUrlHelper interface.

private class MyTestUrlHelper : IUrlHelper
{

   #region IUrlHelper Members

   public string RouteUrl(RouteValueDictionary values)
   {
      return RouteUrl((IDictionary<String, Object>)values);
   }

   public string RouteUrl(object values)
   {
      IDictionary<String, Object> dic = (IDictionary<String, Object>) values;
      return "/" + dic["controller"] + "/" + dic["action"];
   }

   public string RouteUrl(string routeName, object values)
   {
      return RouteUrl(values);
   }

   #endregion
}

It basically created url with a fixed rule, but the important thing that now I can test MasterLogic without worrying about someone changing routes, since I can inject my MyTestUrlHelper class into my MasterLogic class, here is a test.

[Test]
public void GrabMenuWithActionUrl()
{
   MasterLogic sut = new MasterLogic(new MyTestUrlHelper());
   List<MenuItem> menu = sut.CreateMenu("SampleFiles\\MenuType1.Xml").MenuItems;
   Assert.That(menu, Has.Count(2));
   Assert.That(menu[1].MenuItems[0], Has.Property("Url", "/Photo/ManageAlbum"));
}

You can complain that this is not mvc style of decoupling logic from the context, mvc has introduced the System.Web.Abstractions namespace for doing this, but I still prefer this “old style” solution, because it works perfectly even for webforms. In classic asp.net applications when I need to access Session, or querystring or other context related data, I prefer to abstract everything with interfaces, so I can test outside the pipeline of IIS with little problem. The conclusion is that: if you want your classes to be testable, you should abstract every dependency with an interface, and not declare dependency to any concrete class.

alk.

Tags:

Asp.net Mvc plus jQuery client template engine .. have fun with them

jQuery has really a lot of interesting plugins, but the one I like most is a template engine called jtemplates. Basically it consist of a jQuery extension that is capable of rendering html with javascript on client machine. You really have a lot of flexibility on how to specify a template, basically you can embed it into an hidden textarea, the one you see in the above sample is created in this way

    <textarea id="template" style="display:none">
        <div>{$T.name}: {$T.list_id} [{$P.lang.language}]</div>
        <table>
            <thead style="font-weight: bold">
                <tr>
                    <td>{$P.lang['name']}</td>
                    <td>{$P.lang['mail']}</td>
                </tr>
            </thead>
            <tbody>
                {#foreach $T.table as record}
                <tr>
                        <td>{$T.record.name}</td>
                        <td>{$T.record.mail}</td>
                </tr>
                {#/for}
            </tbody>
        </table>
    </textarea>

As you can see jtemplates has a simple syntax to render data where $T identify the object that contains data to be rendered. The template engine supports many constructs: foreach, if, and many other ones. To actually render something you need to assign the above template to a div:

$(".jTemplatesTest").setTemplateElement("template");

Assigning a template is just a matter of selecting a wrapped-set and then assign the template passing the id of the textarea that contains the template. Now the div has a template assigned, to render something you need only to fire the template engine passing it the object that contains data to be rendered. It is accomplished with the function processTemplate(data). The data is a simple javascript object that will be substituted to the $T object inside the template.

This is not the only way to set a template, the other one is using setTemplateURL and processTemplateURL that actually gets the template from an URL and render it with a json object returned from the url passed to processTemplateURL. If this sounds you interesting, think to asp.net mvc, where you can create a controller that manages templates, and other controllers that returns data with JsonResult. If you structure the site in this way, you can maximize performance passing only json data with the server. You only need to render the page with empty divs, assign template to the div, and finally gets data to be rendered from the server.

Actually I’m experimenting with the PhotoAlbum application I did for the jQuery workshop of our usergroup DotNetMarche, as soon as possible I’ll begin to post some concrete code that works with client-side rendering, showing you pratical example that uses this technique.

alk.

Tags:

Build a menu for asp.net mvc site

I have a little site with mixed webform and mvc pages, and I need a simple way to create a main menu. Since asp.net mvc have the concept of controllers and action I like to express my menu with a simple xml file like this.

<?xml version="1.0" encoding="utf-8" ?>
<menu>
   <submenu text="administration">
      <url url="/Login.aspx" text="Login Page" />
      <url url="/CreateUser.aspx" text="Registration Page" />
   </submenu>
   <submenu text="Web Forms">
      <url url="/Photo/PhotoAlbumManager.aspx" text="Album manager" />
      <url url="/Photo/AlbumSearch.aspx" text="Album Search" />
      <url url="/Photo/AlbumSearchPr.aspx" text="Album Search Pr" />
   </submenu>
   <submenu text="MvcSite">
      <action controller="PhotoManager" action="ManageAlbum" text="Album Manager" />
   </submenu>
</menu>

This xml has a really simple structure, it have root node called menu, then a set of submenu and finally url node or action node, i use url node for classic asp.net pages, and action node for asp.net mvp pages. I like the Idea of Action Node, because it is more mvc like than express a simple route. Moreover If we change routing structure I need not to change a single line of the menu, because links are expressed in forms of Controller and action.

The first thing to do is creating a model capable of parsing this file and building a list of object that can be used from the view engine. I decided to create a couple of classes to represent a menu in memory

image

The MasterModel classes has the responsibility to create data for the master page. In this version it has only the CreateMenu function that can be used to parse the xml files with the menu and create a root menuitem class. The MenuItem has a method called Render that is capable to render an anchor link with the corresponding page. The good part is that the real url is created with the UrlHelper class

         else if (element.Name == "action")
            return new MenuLink(
               element.Attribute("text").Value, 
               Url.RouteUrl("Default", new 
               {
                  controller = element.Attribute("controller").Value,
                  action =  element.Attribute("action").Value, 
               }));

Since I’m doing partial rendering, I do not want each action in the controller to render the menu, so I create a base controller with this property

      public MenuItem RootMenu
      {
         get { return rootMenu ?? (rootMenu = MasterModel.CreateMenu(Path.Combine(Global.PhysicalPath,"WebMvcSitemap.Xml"))); }
      }
      private MenuItem rootMenu;

As you can see, only when I access the MenuItem object the menu gets reconstructed. In this version there is no cache, so the file is read each time the menu needs to be rendered, but it is good enough for now. When I call an action that needs to render the whole page I simply access the RootMenu property to create and pass to the view menudata

public ActionResult ManageAlbum(Guid? id, Int32? pageid, Int32? pagesize)
{
   AlbumManager model = new AlbumManager();
   model.MainMenu = RootMenu;

With the above code I reconstruct the menu and passed to the view in the MainMenu property. When I call an action that needs only to render partial part of the page, I simply avoid to set the MainMenu property, and the menu gets not reconstructed, since the view needs to do partial rendering it has no master page and so it does not access the menu part. Finally I created a simple view that is able to render the menu as a series of nested <ul> tags and called it in the master page.

Code is contained in photoalbum project (Subversion address: http://dotnetmarcheproject.googlecode.com/svn/trunk/src/Projects/DotNetMarche.PhotoAlbum)

alk.

Tags:

Choosing the ritgh route when you generate link in asp.net mvc.

I’m moving my first steps on asp.net mvc, and in my test project I added a route after the default one in this way.

 RouteTable.Routes.MapRoute(
               "Default",
               "{controller}/{action}/{id}",
               new { controller = "Home", action = "Index", id = "" }
             );
            RouteTable.Routes.MapRoute(
                "PagedController",
                "{controller}/{action}/{pageid}/{id}",
                new { controller = "Home", action = "Index", pageid = 0, id = "" }
              );

As you can see the second route is used when the controller need the concept of current page, it is very close to the default one, but it has another parameter called pageid. Now I generate a link to the paged controller with the following code.

Html.ActionLink(
    "Edit", 
    "ManageAlbum", 
    "PhotoManager", 
    new { pageid = ViewData.Model.CurrentPage, id = Guid.Empty},
    null)

But generated link is PhotoManager/ManageAlbum/00000000-0000-0000-0000-000000000000?pageid=0 that is wrong. The page id was put in querystring and not in the path as I want. The problem is derived from the order of routes, because the ActionLink function scans route from the first to the last. Since the first route match with the parameter the ActionLink method decides to use the “Default” route, appending the pageid parameter to the querystring.

If you need to manually choose the route you need to generate your link you can use a different method of the Html helper object.

Html.RouteLink(
    "Edit", 
    "PagedController", 
    new { 
        controller=  "ManageAlbum", 
        action = "PhotoManager", 
        pageid = ViewData.Model.CurrentPage, 
        id = Guid.Empty}, 
    null)

As you can see with the RouteLink() method you can choose exactly the route you want to use, and generate desidered link /ManageAlbum/PhotoManager/0/00000000-0000-0000-0000-000000000000.

alk.

Tags:

Could not load type ‘System.Web.Mvc.ViewPage<..<>’

First of all thanks to Andrea Balducci that gave me this solution. I have a asp.net application where I enabled Asp.Net mvc following a link in the web. Everything works well until I try to use Strongly Typed View. When I try to have a page that inherits from ViewPage<T> where T is one of my model the system gave me an error of type

Could not load type ‘System.Web.Mvc.ViewPage<…>

In a asp.net site created with the wizard everything works ok. I check both web.config to be sure that actually I did not forgot anything, then I stumble across this post. Basically I need to modify Page directive of my web config in this way

<pages 
     pageParserFilterType="System.Web.Mvc.ViewTypeParserFilter, System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" 
     pageBaseType="System.Web.Mvc.ViewPage, System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" 
     userControlBaseType="System.Web.Mvc.ViewUserControl, System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"> 

I really do not understand why the site created with the asp.net wizard works because it does not have this directive, then Andrea told me to chek the View directory, because it has a dedicated web.config. Here is the solution. The view directory must contains a web.config like this

<?xml version="1.0"?>
<configuration>
   <system.web>
      <httpHandlers>
         <add path="*" verb="*"
             type="System.Web.HttpNotFoundHandler"/>
      </httpHandlers>

      <!--
        Enabling request validation in view pages would cause validation to occur
        after the input has already been processed by the controller. By default
        MVC performs request validation before a controller processes the input.
        To change this behavior apply the ValidateInputAttribute to a
        controller or action.
    -->
      <pages
          validateRequest="false"
          pageParserFilterType="System.Web.Mvc.ViewTypeParserFilter, System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"
          pageBaseType="System.Web.Mvc.ViewPage, System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"
          userControlBaseType="System.Web.Mvc.ViewUserControl, System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
         <controls>
            <add assembly="System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" namespace="System.Web.Mvc" tagPrefix="mvc" />
         </controls>
      </pages>
   </system.web>

   <system.webServer>
      <validation validateIntegratedModeConfiguration="false"/>
      <handlers>
         <remove name="BlockViewHandler"/>
         <add name="BlockViewHandler" path="*" verb="*" preCondition="integratedMode" type="System.Web.HttpNotFoundHandler"/>
      </handlers>
   </system.webServer>
</configuration>

Thanks again to Andrea for the solution.

alk.

Tags: