Custom ViewEngine required?
i have a website that will have customized designs, depending on what "store" was clicked on...
So, i have a folder structure like:
+ _default
- Site.Master
- Site.css
+ Views
- main.ascx
- xyz.ascx
- zbf.aspx
+ cust开发者_如何学Com_design_01
- Site.Master
- Site.css
+ Views
- xyz.ascx
- zbf.aspx
+ custom_design_02
- Site.Master
- Site.css
+ Views
- xyz.ascx
- zbf.aspx
In my controller i will make the decision which of the folders to use, depending on a parameter from the request ( the default folder should be used when a file is not available as customized one.. )
So, the controller has do something like:
public ActionResult Index(int id)
{
var newRouteParamThatWouldntFitIntoTheCurrentViewMethodLikeThis = DoSomeMagicalRoutingStuff(id);
return View(newRouteParamThatWouldntFitIntoTheCurrentViewMethodLikeThis);
//return View();
}
If i hadn't the "dynamic routing per request" requirement here, i could have used a customized ViewEngine or something...
Does anyone has an idea on how to realize this ( with MVC on-board tools )? I really want to keep using the Html.RenderPartial method and other "benefits" of the MVC... that's why i'd prefer solving this problem with MVC methods/overrides/etc.
Update:
- The sites are different in design, content, using "RenderPartial" theirself and so on..
- The views are really different.. not just the masterpages..
- The ID parameter in the controller is identifying which "folder" should be used ( e.g. custom_design_01 )
- The URLs will look like: "/Stores/{id_of_design}/" which calls the example controller method.. the ctonroller then returns the view "/Stores/{id_of_design}/Views/zbf.aspx" for example
Thanks!
I suggest you use a specialized ViewEngine with some custom search locations. Since you're still using WebForms pages, you can simply derive from the integrated WebFormViewEngine:
public class ThemedViewEngine : WebFormViewEngine {
private static readonly string[] _emptyLocations = new string[0];
//Format: {0} page name {1} controller {2} design
string[] _masterLocations = new[] {
"~/Views/{2}/{0}.master"
};
string[] _viewLocations = new[] {
"~/Views/{2}/{1}/{0}.aspx",
"~/Views/{2}/Shared/{0}.aspx",
};
#region View search
public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache) {
string[] viewLocationsSearched;
string[] masterLocationsSearched;
string viewPath = FindPath(controllerContext, _viewLocations, viewName, _cacheKeyPrefix_View, useCache, out viewLocationsSearched);
string masterPath = FindPath(controllerContext, _masterLocations, masterName, _cacheKeyPrefix_Master, useCache, out masterLocationsSearched);
//Check if one view missing
if (String.IsNullOrEmpty(viewPath) || (String.IsNullOrEmpty(masterPath) && !String.IsNullOrEmpty(masterName))) {
return new ViewEngineResult(viewLocationsSearched.Union(masterLocationsSearched));
}
return new ViewEngineResult(CreateView(controllerContext, viewPath, masterPath), this);
}
//Same thing as above, but without master page
public override ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache) {
string[] viewLocationsSearched;
string viewPath = FindPath(controllerContext, _viewLocations, partialViewName, _cacheKeyPrefix_Partial, useCache, out viewLocationsSearched);
if (String.IsNullOrEmpty(viewPath)) {
return new ViewEngineResult(viewLocationsSearched);
}
return new ViewEngineResult(CreatePartialView(controllerContext, viewPath), this);
}
protected string FindPath(ControllerContext context, string[] locations, string viewName, string prefix, bool useCache, out string[] searched) {
searched = _emptyLocations;
if (string.IsNullOrEmpty(viewName))
return string.Empty;
//Prepare your data here
string controllerName = context.RouteData.GetRequiredString("controller");
string designName = /* YOUR WAY OF GETTING THE DESIGN */;
string result = FindPathGeneral(context, locations, viewName, controllerName, designName, out searched);
return result;
}
/// <summary>
/// Finds the complete path for a general view page.
/// </summary>
private string FindPathGeneral(ControllerContext context, string[] locations, string viewName, string controllerName, string designName, out string[] searched) {
string result = string.Empty;
searched = new string[locations.Length];
for (int i = 0; i < locations.Length; i++) {
//Build virtual path from the locations defined on top
string virtualPath = String.Format(CultureInfo.InvariantCulture, locations[i],
viewName, controllerName, designName);
if (FileExists(context, virtualPath)) {
searched = _emptyLocations;
return virtualPath;
}
searched[i] = virtualPath;
}
return result;
}
#endregion
}
This should work for your case, provided you put some way to find the needed design name in the FindPath
method.
The example above doesn't implement view caching for simplicity. You can check out the WebFormViewEngine
code with reflector (or from source code) to see how it's done by the framework.
One way you can probably solve this is to create a Controller subclass as a common base class for your controllers. You can define an alternate action function like View()
that uses route data and the method params to decide which view should be used, and then just return a standard ViewResult
instance. (You can extract information from a URL in a route that isn't directly used to map to a controller or its parameters.)
It might, however, be cleaner to create a custom subclass of WebFormViewEngine
(since that's where people might expect to find that kind of code). Then again, if not all of your controllers have this behavior, the first approach might be better.
This sounds much like areas, check here and here, and for "asp.net mvc areas" in common. I'm only not sure about the "default" area but I think this can be done. Notice that ASP.NET MVC v2 (which is in Beta now) has native areas support.
精彩评论