Flexible theming for ASP.NET MVC
?http://blogs.planetcloud.co.uk/mygreatdiscovery/post/Flexible-theming-for-ASPNET-MVC.aspx I’m sure many people have experienced frustration when creating custom themes for various apps. Now if it was just a case of changing CSS then that would be fine. But often there is a need to change master pages and pages for either layout purposes or perhaps to add additional functionality or UI components. Well since I’m loving MVC so much at the moment I thought I would look into how we could do theming. The great thing about ASP.NET MVC is that since you are just calling a controller action,you get so much control over the entire request. For example,you can return a view and override the master page that should be used: public?ActionResult Index()? For my ideal theming solution I would want to: 1) Easily add my own CSS? ASP.NET MVC is built on a lot of convention. For example,the default ViewEngine looks in specific areas for the View names passed by controller actions. The great thing is,you can add your own custom ViewEngine and override how Views are located. This is exactly what Chris Pietschmann did in his article??ASP.NET MVC: Implement Theme Folders using a Custom ViewEngine. In order to meet my theming requirements I made a few changes since I wanted it to be optional to override the default views. So if?http://www.mysite.com/catalog/product/groovy-shoes?was requested,the ViewEngine would first look for a view called “Product” in the currently selected themes folder and if it did not exist then it would use the one in the /Views/Catalog/ directory. I like this approach because it means that if you release a new version of your application and some of the views change then this won’t effect the custom views made by other people. In fact,if your styles no longer worked against a new version of the view,you could grab the view from the existing release (providing your model hadn’t changed) and just drop this inside your theme folder. So to better explain,how about some visuals. The image below displays the default structure of my MVC project: The “Default” views are in their default location. These are the ones we will develop with since things like the “New View” action in Visual Studio will pop the views in your /Views directory. The site master page sits in the default location of /Views/Shared and CSS sits in /Content/css. So if I run my project and I get the default MVC site look and feel: So now I decide that everything looks good,but I want to change the logo on my site and add in another menu item. To do this I just take a copy of the existing master page and drop it in my themes folder (maintaining the same directory structure): Then I make my changes and run the site. So now we have a custom master page but still using the default CSS. So let’s add our own custom stylesheet. Grab a copy of the default CSS stylesheet and paste into your themes folder (again,maintaining the same directory structure): Currently the master page is referencing the default CSS so we need to change the CSS reference to point to our theme CSS. To do this I have written a few helper methods. The default method is shown below: < link? href ="<%=AppHelper.CssUrl(" site. css ")%>"? rel ="stylesheet"? type ="text/css"? /> Simply change to: < link? href ="<%=AppHelper.ThemeCssUrl(" site. css "," Default ")%>"? rel ="stylesheet"? type ="text/css"? />?? ? ? ? ? Here we are passing the name of the theme in order to grab the correct stylesheet. Note: Since I am preserving the same directory structure then a relative reference should have worked but it didn’t (and I don’t know why). If there is a better way of doing this then leave a comment. This approach seems clean enough and does get rid of any hardcoded paths. Now when I run my site I pick up my custom stylesheet: The final requirement was that I should be able to override any of the existing views. Sure I can change the styles using CSS but often the HTML markup isn’t quite what you need or perhaps you want to add some additional elements. Let’s say I want to add another heading and add a widget that displays some product categories. Again,simply copy the index view into your theme directory: Make your changes and launch the site: The source code: public?CommerceViewEngine()? #region Overrides public?override?ViewEngineResult FindView(ControllerContext controllerContext,?string?viewName,?stringmasterName,?bool?useCache)? ????if?(controllerContext.Controller.GetType().BaseType ==?typeof(CommerceController)) {? ????????string?controllerName = controllerContext.RouteData.GetRequiredString("controller"); ????????string[] strArray,strArray2; ??????? masterName = (string.IsNullOrEmpty(masterName)) ??"site"?: masterName; ????????string?viewPath =?this.GetPath(controllerContext,?this.ViewLocationFormats,?"ViewLocationFormats",? ????????string?masterPath =?this.GetPath(controllerContext,?this.MasterLocationFormats,?"MasterLocationFormats",? ????????if?(!string.IsNullOrEmpty(viewPath) && (!string.IsNullOrEmpty(masterPath) ||string.IsNullOrEmpty(masterName)))? public?override?ViewEngineResult FindPartialView(ControllerContext controllerContext,?string?partialViewName,?booluseCache)? ????if?(string.IsNullOrEmpty(partialViewName)) {? ????if?(controllerContext.Controller.GetType().BaseType ==?typeof(CommerceController))? ????????string[] strArray;? ????????if?(string.IsNullOrEmpty(partialViewPath)) {? protected?override?bool?FileExists(ControllerContext controllerContext,?string?virtualPath)? #endregion #region Utilities private?static?readonly?string[] _emptyLocations; private?string?GetPath(ControllerContext controllerContext,?string[] locations,?string?locationsPropertyName,? ????if?((locations ==?null) || (locations.Length == 0))? ????bool?flag = IsSpecificPath(name);? private?static?bool?IsSpecificPath(string?name)? private?string?CreateCacheKey(string?prefix,?string?name,?string?themeName)? private?string?GetPathFromGeneralName(ControllerContext controllerContext,? ????????if?(this.FileExists(controllerContext,str2)) {? private?string?GetPathFromSpecificName(ControllerContext controllerContext,?refstring[] searchedLocations)? #endregion ? ? We are also setting the theme on our custom controller (currently hardcoded): public?class?CommerceController : Controller? ????protected?override?void?Execute(System.Web.Routing.RequestContext requestContext)? One thing that does not seem to be working currently is the caching of the view locations. Whilst ViewLocationCache.InsertViewLocation certainly fires,it does not appear to store anything. I have emailed Chris and posted to StackOverflow to try and find out why (perhaps a difference in MVC 2.0 RC?). Enjoy! [Update] The ViewLocationCache is disabled by default when running in debug mode. If you want to enable it for testing purposes,?see my post here. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |