Creating an MVC based website with reusable widgets

By Geert van der Cruijsen at July 12, 2009 10:46
Filed Under: Asp.net, Geert's Projects, MVC

After the Asp.net MVC Framework was released i was pretty impressed of how it worked but one of my main problems was how to handle the data on the page that is not the main goal of the specific page.

In ASP.NET MVC every page has its specific controller and views that can handle the data for the page that is requested. for example when you’re building a web shop you’ll probably have a ProductController that handles everything that has to do with the Products in your shop. But when you are browsing a web shop product page you would also like to see other data that has nothing to do with products, for example your shopping basket, current login state etc. Should you add this logic to the ProductController? imo you shouldn’t since the Products controller only responsibility should be the products. So how should we handle this in the MVC framework?

My first idea that came to mind to solve this problem was doing an Ajax request to for example, the shopping basket controller on your view when you would like a shopping basket added to your page. This can be in some situations the best way to do it but in some cases you might not want to use Ajax since then this content can’t be indexed by search engines for example.

An example how to use Ajax widgets is found here: http://www.ajaxprojects.com/ajax/tutorialdetails.php?itemid=310

My main goal was to be able to add widgets to a page without the controller of that page knowing about these widgets. I found some solutions to this called partial requests found here: http://blog.codeville.net/2008/10/14/partial-requests-in-aspnet-mvc/

These partial requests weren’t my favorites also since i didn’t want to put this logic in the view so i developed my own CMS like solution for this problem.

My idea was that a page can have several page zones and page zones contain widgets. widgets and complete page zones should be reusable by other pages so i stored these in my CMS database. how my database is build up you can see in the image below

image

A widget should be able to render itself so i made an abstract BaseWidget class that has a Render Method. A widget can also be a call to a different controller so i made a SubController class that inherits from BaseWidget. The SubController Widget has an Controller, Action and ID so it can call the controller it belongs to.

   1: public abstract class SubControllerWidget : BaseWidget
   2: {
   3:     public string Controller { get;  set; }
   4:     public string Action { get;  set; }
   5:     public object ID { get; set; }
   6:  
   7:     public SubControllerWidget(string Controller, string Action) : base()
   8:     {
   9:         this.Controller = Controller;
  10:         this.Action = Action;
  11:         this.ID = null;
  12:     }
  13:  
  14:     public SubControllerWidget(string Controller, string Action, object ID)
  15:         : base()
  16:     {
  17:         this.Controller = Controller;
  18:         this.Action = Action;
  19:         this.ID = ID;
  20:     }
  21:  
  22:     public override void Render(System.Web.Mvc.ViewContext vc)
  23:     {
  24:         vc.RouteData.Values["controller"] = Controller;
  25:         vc.RouteData.Values["action"] = Action;
  26:         vc.RouteData.Values["id"] = ID;
  27:         IHttpHandler handler = new MvcHandler(vc.RequestContext);
  28:         handler.ProcessRequest(System.Web.HttpContext.Current);
  29:     }
  30:  
  31:     public abstract void SetSettings(WidgetSettings settings);
  32: }

To build a specific widget just inherit a widget from SubControllerWidget and you are ready to go. Set the Controller, Action and ID to call and that specific controller will be called.

To add these page zones and widgets to the pages dynamically i’ve created a CMSController that overrides the OnActionExecuted method so it can insert the page zones and widgets to the model that’s been send to the View.

   1: protected override void OnActionExecuted(ActionExecutedContext filterContext)
   2: {
   3:     if (ViewData.Model == null)
   4:     {
   5:         ViewData.Model = new CMSViewModel();
   6:     }
   7:     string controller = filterContext.RequestContext.RouteData.Values["controller"] != null ? filterContext.RequestContext.RouteData.Values["controller"].ToString() : "";
   8:     string controllerAction = filterContext.RequestContext.RouteData.Values["action"] != null ? filterContext.RequestContext.RouteData.Values["action"].ToString() : "";
   9:     string controllerActionid = filterContext.RequestContext.RouteData.Values["id"] != null ? filterContext.RequestContext.RouteData.Values["id"].ToString() : "";
  10:     IList<PageZone> zones = _cmsRep.GetPageZonesForPage(controller, controllerAction, controllerActionid);
  11:  
  12:     CMSViewModel page = (CMSViewModel)ViewData.Model;
  13:     if (page.PageZones == null)
  14:     {
  15:         page.PageZones = zones;
  16:     }
  17:     else
  18:     {
  19:         foreach (PageZone zone in zones)
  20:         {
  21:             page.PageZones.Add(zone);
  22:         }
  23:     }
  24:  
  25:  
  26:     base.OnActionExecuted(filterContext);
  27: }

 

So now all widgets beloning to a page will be retrieved from the CMSRepository and are added to the CMSViewModel so now it’s the View’s turn to render all widgets.

In my master page I’ve added the following line that is responsible for rendering all widgets:

<% Html.RenderPageZones(Model); %>

For that to work I’ve created a few html extension methods to render the page zones.

   1: public static class HtmlCMSExtensions
   2: {
   3:     public static void RenderPageZones(this HtmlHelper html, CMSViewModel page)
   4:     {
   5:         if (page != null && page.PageZones != null)
   6:         {
   7:             foreach (PageZone zone in page.PageZones)
   8:             {
   9:                 html.RenderPartial("PageZone", zone);
  10:             }
  11:         }
  12:     }
  13:  
  14:     public static void RenderWidgetsInZone(this HtmlHelper html, PageZone zone)
  15:     {
  16:         if (zone != null && zone.Widgets != null)
  17:         {
  18:             foreach (IWidget widget in zone.Widgets)
  19:             {
  20:                 widget.Render(html.ViewContext);
  21:             }
  22:         }
  23:     }
  24: }

In my PageZone view i’ve added the other html extension method called RenderWidgetsInZone so all widgets are rendered by itself.

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<VanDerCruijsen.MVC.CMS.Model.PageZone>" %>
<div id="<%= Html.Encode(Model.Name) %>">
name:<%
   1: = Html.Encode(Model.Name) 
%>
<%
   1:  Html.RenderWidgetsInZone(Model); 
%>
</div>

This is the way I use to create widgets on my ASP.NET MVC pages. If you have any recommendations or better ways how to do it please let me know.

When I have the time I’m going to build an admin controller to be able to add widgets and pagezones via the webinterface since now the only option is to add them in the database by hand.

My sources aren’t really cleaned up so if you want a working source/solution please send me mail or leave a comment.

Geert van der Cruijsen

Comments

6/4/2010 8:59:43 PM #

Hi Geert
I liked your solution and was looking for a similar solution for one of my projects. I would really appreciate that if you could email me the source code.
Kind Regards
Tej

Tej

6/21/2010 8:29:29 PM #

I would love to take a look at your source code/solution as well.  Please send it if you can!

Thanks!
Frank

Frank

7/13/2010 9:53:45 AM #


I would love to take a look at your source code/solution as well.  Please send it if you can!

Thanks!
D Nguyen

D Nguyen

7/13/2010 11:03:56 PM #

Wow, what a lazy post! Copy/Paste the smallest of things... anyhow, if you can I would appreciate a looksee at the code.

Frank

7/14/2010 11:11:29 AM #

Frank,

this code is more than 1 year old, written in the mvc framework version 1.0. I haven't ported it to 2.0 but started over with a blank project again.

all other source not in this post is kinda messy since it was just some experiment of me to see how this would work.

Geert van der Cruijsen

7/19/2010 5:26:22 PM #

Thanks for sharing this  articles!

Cool New Widgets

7/26/2010 1:51:40 AM #

I was wondering if you need to be a visitor poster on my blog? and in trade you possibly can put one link the post? Please reply while you get an opportunity and I'll ship you my contact details - thanks

Melvina Strano

7/26/2010 1:51:54 AM #

Hey about Asp.net MVC i do believe your blog is quite F - First-class i discovered it in aol and i plant it on my favorite list  hope to view further remarkable posts from you  asap.

Monte Matey

7/26/2010 6:54:13 AM #

I didn't see a button anyplace but do you've gotten promoting? I have several blogs in the identical area of interest and I want to add my button somwhere in your webiste.

Ignacio Sprunk

7/26/2010 6:54:14 AM #

Great web page, keeping me from working

Lloyd Mcarthy

7/27/2010 12:22:26 AM #

HO YA Asp.net MVC Asp.net MVC Asp.net MVCI have been browsingonline just above three hrs today, until now I by no means found any interesting piece like yours about Asp.net MVC It's pretty worth enough for me. Personally, if every web owners and bloggers made high-quality content as you did, the internet will likely be a lot more helpful than ever before.

Hal Schuerman

7/27/2010 12:22:27 AM #

Dude.. I am not a great deal into reading about Asp.net MVC , but somehow I got to read lots of posts in your webpage. Its fantastic how interesting it is for me to visit you very often.

Yong Winrow

7/27/2010 5:21:34 AM #

Whereas this subject will be very touchy for most individuals, my opinion is that there needs to be a middle or frequent floor that all of us can find. I do respect that youve added related and clever commentary right here though. Thank you!

Olen Donelson

7/27/2010 5:21:35 AM #

What a brilliant weblog!

Rema Kenworthy

7/30/2010 5:05:54 AM #

Maby considere to write a Asp.net MVC company to put here adv

Easter Raglow

Add comment



biuquote
Loading