EDIT: This part will not work or is hard to implement
You'd rather use an action filter which will let you manipulate the Result
before executing it.
Particularly you need a Result filter. Implement the IResultFilter.onResultExecuting method, and change the result there. Particularly when you implement this method:
void OnResultExecuting(ResultExecutingContext filterContext)
You can access the ResultExecutingContext.Result Property. This property will contain your view. If you cast it to System.Web.Mvc.ViewResultBase you'll have access to the ViewName
and you'll be able to change it.
If you've never implemented a filter, this is a good hands-on-lab on the subject. In this case it implements another kind of filter, but it's just the same.
As an answer to the OP comment, it's perfectly normal that ViewName
is missing, and View
is still null. ViewName
wouldn't be empty only if the case that the view is returned with name, like this: return View("Index");
. And, the ViewName
would be just, not the whole path to the view. So this is not a solution. So, to have this solution working you would have to deal with route data, controller context, etc. to find the view. (More on this below.)
EDIT: Solution, register a custom view engine
When MVC has to render a view it gets the information from the route data, the controller context, the view name (that, as explained above can be empty), and the conventions that apply.
Particularly, in MVC there is a collection of registered view engines which are required to find the view calling there FindView()
method. The view engine will return a ViewEngineResult
which has the found view, if one was found, or a list of the paths where the view has been unsuccesfully sought.
So, to modify the template path, you can override this funcionality: let the original class find the view, and, if it is found, modify the path.
To do show you need to take theses steps:
- Inherit the view engine which you're using (my sampel code inherits Razor view engine)
- Register your vie engine, so that it's queried before the original view engine (in my sample code I simply clear the registered engines list, and register mine. The original list includes razor and web form view engines)
This is the code for the inherited view engine:
public class CustomRazorViewEngine : FixedRazorViewEngine
{
public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
{
ViewEngineResult result
= base.FindView(controllerContext, viewName, masterName, useCache);
if (result.View != null)
{
// Modify here !!
}
return result;
}
public override ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache)
{
ViewEngineResult result
= base.FindPartialView(controllerContext, partialViewName, useCache);
if (result.View != null)
{
// Modify here !!
}
return result;
}
static readonly PropertyInfo ViewPathProp
= typeof(RazorView).GetProperty("ViewPath");
public void SetViewPath(RazorView view, string path)
{
ViewPathProp.SetValue(view, path);
}
}
NOTE 1: where you read // Modify here !!
you can modify the path property of the result.View
. Cast it to RazorView
: (result.View as RazorView).ViewPath
. As the ViewPath
setter is protected, you need to set it using Reflection: you can use the SetViewPath
method for this.
NOTE 2: As you can see I'm not inheriting the RazorViewEngine
but the FixedRazorViewEngine
. If you loook for this class in MSDN you'll get not results, but if you look the original content of the registered view engines list, you'll find this class. I think this depends on an installed package in the project, and I think it solves a bug in MVC4. If you don't finf it in Microsoft.Web.Mvc
namespace, inherit the original RazorViewEngined
NOTE 3: after the view is found, the view engine executes it, using the ViewEngineResult
, so, if you change it, it will be executed with the new view path
And finally, you need to change the list of registered engines, in global.asax
application start event, like this:
protected void Application_Start()
{
// Original content:
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
// Added content:
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new CustomRazorViewEngine());
}
NOTE: it would be cleaner, if you created a ViewEngineConfig
class in App_Start
folder, and invoked an static method of this class, just as it's done with all other configurations.