Some helpful videos for front-end developers

Here is the collection of some helpful videos for front-end developers (by Paul Irish) http://delicious.com/paul.irish/frontend+video

Social Share Toolbar

asp.net MVC3 Versioning of Views and Controllers

Sometimes it could be necessary to have ability to add “Versioning” to Pages on your site. For example we have URL like this – http://localhost:4444/Authentication/ShowLogin and this “ShowLogin” page used on many sites (e.g. integrated via iframes) and suddenly it is necessary to change something on this page, but not for all users of this page (required back compatibility).

First step is to use custom CSS. But when it is needed to make functional changes – completely change View, Model or Controller, in this case CSS won’t be enough. For this purpose we can request new Page via this URL – http://localhost:4444/Authentication/1001/ShowLogin where “1001” is version of the page. It is obviously that in case of “1001” version MVC Engine should find appropriate controller and views, and if no controllers or views were found – should be used default version (it sounds like View’s inheritance or overriding – this mechanism will allow to override only several views, not all).

How to implement this approach? – for this we need the following:

  • override default MVC controller’s factory – here we will define custom logic for searching and creation of controller
  • replace Views engine. Also it is needed to add new route.
  • define custom route to MVC routes
  • add ability to configure versions and other settings via web config

You can find complete example here – https://github.com/DjComandos/MvcVersioning/

Some clarifications how to do this. First of all it is needed to add new rule to Routes – it should be done in Global.asax

routes.MapRoute(
    "ControllerWithVersion",
    "{controller}/{version}/{action}/{id}",
    new {controller = string.Format(@"\{0}\", config.ControllerNameRegexForEnablingCustomControllerFactory), 
        action = "ShowLogin", id = UrlParameter.Optional},
    new {version = @"\d{4}"});

Here “config.ControllerNameRegexForEnablingCustomControllerFactory” is just a regex, something like “^Authentication|ForgotPassword$” (where Authentication or ForgotPassword are controllers names). This setting is stored in web.config.

Then it is needed to override default RouteControllerFactory – for this purpose we will create next class:

    public class RouteControllerFactory : IControllerFactory
    {
        private readonly IControllerFactory _defaultFactory = new DefaultControllerFactory();
        private readonly IAppConfiguration _configuration = new AppConfiguration();
 
        public IController CreateController(RequestContext requestContext, string controllerName)
        {
            IController controller = NeedToUseCustomFactory(controllerName, requestContext)
                                         ? CreateControllerInstance(requestContext)
                                         : _defaultFactory.CreateController(requestContext, controllerName);
 
            return controller;
        }
 
        public SessionStateBehavior GetControllerSessionBehavior(
            RequestContext requestContext, string controllerName)
        {
            return _defaultFactory.GetControllerSessionBehavior(requestContext, controllerName);
        }
 
        public void ReleaseController(IController controller)
        {
            _defaultFactory.ReleaseController(controller);
        }
 
        private IController CreateControllerInstance(RequestContext requestContext)
        {
            var currentVersion = requestContext.RouteData.Values.ContainsKey(_configuration.ApiVersionKey)
                                     ? requestContext.RouteData.Values[_configuration.ApiVersionKey].ToString()
                                     : _configuration.DefaultControllerVersion;
 
            var controllerVersionedTypeName = string.Format(
                "{0}{1}Controller",
                ControllerName(requestContext),
                _configuration.TryGetControllerPostfix(currentVersion));
            var controllerDefaultTypeName = string.Format(
                "{0}{1}Controller",
                ControllerName(requestContext),
                _configuration.TryGetControllerPostfix(_configuration.DefaultControllerVersion));
 
            var typeType = TypesFinder.FindTypeInExecutingAssembly(controllerVersionedTypeName) ??
                           TypesFinder.FindTypeInExecutingAssembly(controllerDefaultTypeName);
 
            var controller = Activator.CreateInstance(typeType) as IController;
 
            return controller;
        }
 
        protected string ControllerName(RequestContext requestContext)
        {
            return requestContext.RouteData.Values[_configuration.ControllerKey].ToString();
        }
 
        private bool NeedToUseCustomFactory(string controllerName, RequestContext requestContext)
        {
            return Regex.IsMatch(controllerName, _configuration.ControllerNameRegexForEnablingCustomControllerFactory)
                && requestContext.RouteData.Values.ContainsKey(_configuration.ApiVersionKey);
        }
    }

The main idea is that we use Custom Controller factory only in case is varsion is defined, for usual cases will be used default controllers factory. Let’s add some code to Global.asax after routing:

// register custom controller factory
ControllerBuilder.Current.SetControllerFactory(typeof(RouteControllerFactory));

Let’s override Views Rendering Engine

    public class VersionedRazorViewEngine : RazorViewEngine
    {
        private readonly IAppConfiguration _configuration = new AppConfiguration();
 
        public VersionedRazorViewEngine()
        {
            AreaViewLocationFormats = new[]
            {
                "~/Areas/{2}/Views/{1}%1%/{0}.cshtml", 
                "~/Areas/{2}/Views/{1}%1%/{0}.vbhtml", 
                "~/Areas/{2}/Views/Shared%1%/{0}.cshtml", 
                "~/Areas/{2}/Views/Shared%1%/{0}.vbhtml"
            };
 
            AreaMasterLocationFormats = new[]
            {
                "~/Areas/{2}/Views/{1}%1%/{0}.cshtml", 
                "~/Areas/{2}/Views/{1}%1%/{0}.vbhtml", 
                "~/Areas/{2}/Views/Shared%1%/{0}.cshtml", 
                "~/Areas/{2}/Views/Shared%1%/{0}.vbhtml"
            };
 
            AreaPartialViewLocationFormats = new[]
            {
                "~/Areas/{2}/Views/{1}%1%/{0}.cshtml", 
                "~/Areas/{2}/Views/{1}%1%/{0}.vbhtml", 
                "~/Areas/{2}/Views/Shared%1%/{0}.cshtml", 
                "~/Areas/{2}/Views/Shared%1%/{0}.vbhtml"
            };
 
            ViewLocationFormats = new[]
            {
                "~/Views/{1}%1%/{0}.cshtml", 
                "~/Views/{1}%1%/{0}.vbhtml", 
                "~/Views/Shared%1%/{0}.cshtml", 
                "~/Views/Shared%1%/{0}.vbhtml"
            };
 
            MasterLocationFormats = new[]
            {
                "~/Views/{1}%1%/{0}.cshtml", 
                "~/Views/{1}%1%/{0}.vbhtml", 
                "~/Views/Shared%1%/{0}.cshtml", 
                "~/Views/Shared%1%/{0}.vbhtml"
            };
 
            PartialViewLocationFormats = new[]
            {
                "~/Views/{1}%1%/{0}.cshtml", 
                "~/Views/{1}%1%/{0}.vbhtml", 
                "~/Views/Shared%1%/{0}.cshtml", 
                "~/Views/Shared%1%/{0}.vbhtml"
            };
        }
 
        protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
        {
            return base.CreatePartialView(controllerContext, GetViewPath(controllerContext, partialPath));
        }
 
        protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
        {
            return base.CreateView(
                controllerContext, GetViewPath(controllerContext, viewPath), GetViewPath(controllerContext, masterPath));
        }
 
        protected override bool FileExists(ControllerContext controllerContext, string virtualPath)
        {
            return base.FileExists(controllerContext, GetViewPath(controllerContext, virtualPath))
                   ||
                   (IsVersioningEnabled(controllerContext) &&
                    base.FileExists(controllerContext, GetViewPath(controllerContext, virtualPath, _configuration.DefaultViewsVersion)));
        }
 
        private bool IsVersioningEnabled(ControllerContext controllerContext)
        {
            return controllerContext.RouteData.Values.ContainsKey(_configuration.ControllerKey)
                   &&
                   Regex.IsMatch(
                       controllerContext.RouteData.Values[_configuration.ControllerKey].ToString(), _configuration.ControllerNameRegexForEnablingCustomControllerFactory)
                   && controllerContext.RouteData.Values.ContainsKey(_configuration.ApiVersionKey);
        }
 
        private string GetVersion(ControllerContext controllerContext)
        {
            if (IsVersioningEnabled(controllerContext))
            {
                return controllerContext.RouteData.Values.ContainsKey(_configuration.ApiVersionKey)
                           ? controllerContext.RouteData.Values[_configuration.ApiVersionKey].ToString()
                           : _configuration.DefaultViewsVersion;
            }
 
            return string.Empty;
        }
 
        private string GetViewPath(ControllerContext controllerContext, string virtualPath, string version = null)
        {
            if (string.IsNullOrEmpty(virtualPath))
            {
                return string.Empty;
            }
 
            version = version ?? GetVersion(controllerContext);
            return base.FileExists(controllerContext, virtualPath.Replace("%1%", _configuration.TryGetViewFolderPostfix(version)))
                       ? virtualPath.Replace("%1%", _configuration.TryGetViewFolderPostfix(version))
                       : virtualPath.Replace(
                           "%1%", _configuration.TryGetViewFolderPostfix(_configuration.DefaultViewsVersion));
        }
    }

Main idea – replace mechanism that will find necessary views (just add version number to path). As a result views for default version will be in “Views/Authentication” folder, and views for “1001” version will be in “Views/Authentication1001” folder (this suffix is configurable in web.config). It’s also needed to register new view’s engine in global.asax:

// register custom razor view engine
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new VersionedRazorViewEngine());
// note: that there is no custom ViewEngine for webforms (it is need to implement it in case it will needed for API versioning)
ViewEngines.Engines.Add(new WebFormViewEngine());

Finally thats it, it is needed just to implement custom RouteControllerFactory that implements IControllerFactory and register it in Global.asax, And implement custom VersionedRazorViewEngine inherited from RazorViewEngine and also register it in global.asax. And views\controllers versioning for MVC3 is ready. It is not only versioning but also Views’ inheritance, because this approach allows us to override only views that should be changed (with reusage of old views).

Sources could be found here – https://github.com/DjComandos/MvcVersioning/

Social Share Toolbar