Click here to Skip to main content
15,884,986 members
Articles / Web Development / ASP.NET

ASP.NET MVC Groups

Rate me:
Please Sign up or sign in to vote.
0.00/5 (No votes)
13 Jan 2010CPOL2 min read 7.4K  
ASP.NET MVC groups

Here's a fictional example of a problem I've been having in ASP.NET MVC...

In an MVC application, I have 1000 controllers. 2 of these controllers share a partial view so I put it in the ~/Views/Shared/ folder. I then find that I have 499 more pairs of controllers that also share different partial views so I do the same. The problem is that I now have a shared folder cluttered with 500 partial views - each of which is only relevant to 0.2% of my entire application! I also have to give each of these views unique names which becomes unwieldy.

So what's the solution?

What I'd like to do is group multiple controllers together and give them their own shared area for views. Any controller can also belong to more than one group.

Firstly, we need to extend the ViewEngine that our application is using. This is primarily responsible for finding the correct view in our filing system. Out of the box, the framework uses WebFormViewEngine. Our new view engine will inject extra, temporary locations in which to search for views. The information about which locations will be supplied by the current controller. Here's the code for the new engine...

C#
using System;
using System.Collections.Generic;
using System.Web.Mvc;

namespace MyMvcApp.Web.Mvc
{
    /// <summary></summary>
    /// GroupWebFormViewEngine extends WebFormViewEngine and allows Controllers to be
    /// decorated with the MvcGroupAttribute to indicate extra locations View can be found.
    /// 
    public class GroupWebFormViewEngine : WebFormViewEngine
    {
        private string[] _defaultMasterLocationFormats;
        private string[] _defaultPartialViewLocationFormats;
        private string[] _defaultViewLocationFormats;

        public GroupWebFormViewEngine()
            : base()
        {
            // We want to make a note of the base formats so that we can reset prior
            // to each Find operation
            _defaultMasterLocationFormats = MasterLocationFormats;
            _defaultPartialViewLocationFormats = PartialViewLocationFormats;
            _defaultViewLocationFormats = ViewLocationFormats;
        }

        public override ViewEngineResult FindView(ControllerContext controllerContext,
            string viewName, string masterName, bool useCache)
        {
            AddGroupLocations(controllerContext);

            return base.FindView(controllerContext, viewName, masterName, useCache);
        }

        public override ViewEngineResult FindPartialView(ControllerContext controllerContext,
            string partialViewName, bool useCache)
        {
            AddGroupLocations(controllerContext);

            return base.FindPartialView(controllerContext, partialViewName, useCache);
        }

        private void AddGroupLocations(ControllerContext controllerContext)
        {
            ResetAllLocations();

            var type = controllerContext.Controller.GetType();

            var attrs = type.GetCustomAttributes(typeof(MvcGroupAttribute), true);

            Array.ForEach(attrs, attr =>
            {
                AddGroupLocations((attr as MvcGroupAttribute).GroupName);
            });
        }

        private void ResetAllLocations()
        {
            MasterLocationFormats = _defaultMasterLocationFormats;
            PartialViewLocationFormats = _defaultPartialViewLocationFormats;
            ViewLocationFormats = _defaultViewLocationFormats;
        }

        private void AddGroupLocations(string folderName)
        {
            AddMasterLocationFormats(folderName);
            AddPartialViewLocationFormats(folderName);
            AddViewLocationFormats();
        }
        private void AddMasterLocationFormats(string folderName)
        {
            List<string> currentLocations = new List<string>(MasterLocationFormats);

            currentLocations.Add("~/Views/Shared/" + folderName + "/{0}.master");

            MasterLocationFormats = currentLocations.ToArray();
        }
        private void AddPartialViewLocationFormats(string folderName)
        {
            List<string> currentLocations = new List<string>(PartialViewLocationFormats);

            currentLocations.Add("~/Views/Shared/" + folderName + "/{0}.aspx");
            currentLocations.Add("~/Views/Shared/" + folderName + "/{0}.ascx");

            PartialViewLocationFormats = currentLocations.ToArray();
        }
        private void AddViewLocationFormats()
        {
            ViewLocationFormats = PartialViewLocationFormats;
        }
    }
}

Now, we just need our MvcGroupAttribute. This will be used to decorate our controller classes and should apply also to any inherited controllers. It is also possible to have more than one attribute on each controller as it might belong to multiple groups. Here's the code...

C#
using System;

namespace MyMvcApp
{
    [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
    public class MvcGroupAttribute : Attribute
    {
        /// <summary></summary/>
        /// Gets or sets the name of a shared group that this controller belongs to.
        /// 
        public string GroupName
        {
            get;
            set;
        }
    }
}

Now that we have all our building blocks, we can finally use the functionality. Here's a trivial example...

C#
using System;
using System.Web.Mvc;

namespace MyMvcApp
{
    [MvcGroup(GroupName = "Group1")]
    public class MyFirstController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }
    }
    [MvcGroup(GroupName = "Group1")]
    public class MySecondController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }
    }
    public class MyThirdController : MySecondController
    {
        public ActionResult Index()
        {
            return View();
        }
    }
}

In the example, above, all 3 controllers have access to views in the ~/Views/Shared/Group1/ directory.

Conclusion

This simple addition to our Mvc applications has been an enormous difference. We now have so much more flexibility to construct our views in a meaningful, clean way. Hopefully, you'll find it useful too?

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Chief Technology Officer Data Interface Ltd
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --