Reputation: 4415
In my MVC 4 Controller, I want to override the View() method
ViewResult View(string viewName, string masterName, object model) {}
So that I can manipulate the view being rendered by the action method. To do this, I want to be able to obtain the physical path of the view file. I have tried the following:
string viewName = this.ControllerContext.RouteData.Route
.GetVirtualPath(this.ControllerContext.RequestContext, null)
.VirtualPath;
For example, this might return "/Errors/MissingParameters" when what I really want it to return is something like:
"~/Views/Errors/MissingParameters"
or, even better:
"~/Views/Errors/MissingParameters.cshtml"
Just to add complication, I also need it to cope with Areas, so if I had the same example running in an Area named "Surveys", I would want it to return something like:
"~/Areas/Surveys/Views/Errors/MissingParameters"
The reason I want to do this is that I'm experimenting with using views for globalization, so I might have two views:
"~/Views/Errors/MissingParameters.cshtml" // default view (en-GB)
"~/Views/Errors/MissingParameters_de-DE.cshtml" // German view (de-DE)
and I want to be able to check if the view exists for the current language/culture before referencing it.
Any advice would be much appreciated.
Thanks.
Upvotes: 0
Views: 6121
Reputation: 330
i am registering the area but i don't want my
url: "{area}/{controller}/{action}/{id}",
instead i want it to be like
url: "{controller}/{action}/{id}",
so i have registered my area like
context.MapRoute(
name: "AreaName_default",
url: "{controller}/{action}/{id}",
namespaces: new[] { "SolutionName.AreaName.Controllers" }
);
and i don't want to add the hard code string viewpath while returning view in every action method like
return View("~/Areas/AreaName/Views/ControllerName/ViewName.cshtml", model);
so i have created one result filter and override OnResultExecuting function
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
string areaName = AreaNameAreaRegistration.PropoertyName;
if (filterContext.Result.GetType() == typeof(ViewResult) || filterContext.Result.GetType() == typeof(PartialViewResult))
{
dynamic viewResult = filterContext.Result;
string viewname = string.IsNullOrEmpty(viewResult.ViewName) ? Convert.ToString(filterContext.RouteData.Values["action"]) : viewResult.ViewName;
string folder = Convert.ToString(filterContext.RouteData.Values["controller"]);
string lateralHireAreaViewPath = $"~/Areas/{areaName}/Views/";
string extension = viewname.Contains(".cshtml") ? "" : ".cshtml";
viewResult.ViewName = string.Concat(lateralHireAreaViewPath, folder, "/", viewname, extension);
ViewEngineResult result = ViewEngines.Engines.FindView(filterContext.Controller.ControllerContext, viewResult.ViewName, null);
if (result.View == null)
{
//searched in shared folder
lateralHireAreaViewPath = string.Concat(lateralHireAreaViewPath, "Shared/");
viewResult.ViewName = string.Concat(lateralHireAreaViewPath, "/", viewname, extension);
}
}
}
Upvotes: 0
Reputation: 39004
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:
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.
Upvotes: 5
Reputation: 15630
Answer was copied from here.
Well if you don't mind having your code tied to the specific view engine you're using, you can look at the ViewContext.View property and cast it to WebFormView
var viewPath = ((WebFormView)ViewContext.View).ViewPath;
I believe that will get you the view name at the end.
EDIT: Haacked is absolutely spot-on; to make things a bit neater I've wrapped the logic up in an extension method like so:
public static class IViewExtensions {
public static string GetWebFormViewName(this IView view) {
if (view is WebFormView) {
string viewUrl = ((WebFormView)view).ViewPath;
string viewFileName = viewUrl.Substring(viewUrl.LastIndexOf('/'));
string viewFileNameWithoutExtension = Path.GetFileNameWithoutExtension(viewFileName);
return (viewFileNameWithoutExtension);
} else {
throw (new InvalidOperationException("This view is not a WebFormView"));
}
}
}
which seems to do exactly what I was after.
Another solution here
((System.Web.Mvc.RazorView)htmlHelper.ViewContext.View).ViewPath
net-mvc
Upvotes: 1