在呈现之前修改MVC控制器的视图路径

时间:2014-01-21 09:33:43

标签: asp.net-mvc-4

在我的MVC 4 Controller中,我想覆盖View()方法

ViewResult View(string viewName, string masterName, object model) {}

这样我就可以操作action方法呈现的视图了。为此,我希望能够获取视图文件的物理路径。我尝试过以下方法:

string viewName = this.ControllerContext.RouteData.Route
   .GetVirtualPath(this.ControllerContext.RequestContext, null)
   .VirtualPath;

例如,当我真正希望它返回时,这可能会返回“/ Errors / MissingParameters”:

"~/Views/Errors/MissingParameters"

或者,甚至更好:

"~/Views/Errors/MissingParameters.cshtml"

只是为了添加复杂功能,我还需要它来处理区域,所以如果我在名为“Surveys”的区域中运行相同的示例,我希望它返回类似的内容:

"~/Areas/Surveys/Views/Errors/MissingParameters"

我想这样做的原因是我正在尝试使用全局视图,所以我可能有两种观点:

"~/Views/Errors/MissingParameters.cshtml"        // default view (en-GB)
"~/Views/Errors/MissingParameters_de-DE.cshtml"  // German view (de-DE)

我希望能够在引用它之前检查当前语言/文化的视图是否存在。

非常感谢任何建议。

感谢。

3 个答案:

答案 0 :(得分:5)

编辑:此部分无效或难以实施

您宁可使用动作过滤器,以便在执行前操作Result

特别需要结果过滤器。实现IResultFilter.onResultExecuting方法,并在那里更改结果。特别是在实现此方法时:

void OnResultExecuting(ResultExecutingContext filterContext)

您可以访问ResultExecutingContext.Result Property。此属性将包含您的视图。如果您将其投放到System.Web.Mvc.ViewResultBase,则可以访问ViewName,然后您就可以更改它。

如果您从未实施过滤器,则为good hands-on-lab on the subject。在这种情况下,它实现了另一种过滤器,但它是一样的。

作为OP评论的答案,ViewName缺失是完全正常的,View仍为空。 ViewName仅在视图以名称返回的情况下才为空,如下所示:return View("Index");。并且,ViewName只是,而不是视图的整个路径。所以这不是解决方案。因此,要使此解决方案正常工作,您必须处理路由数据,控制器上下文等以查找视图。 (更多内容见下文。)

编辑:解决方案,注册自定义视图引擎

当MVC必须呈现视图时,它从路径数据,控制器上下文,视图名称(如上所述可以为空)获取信息,以及适用的约定。

特别是,在MVC中有一组注册视图引擎,需要查找调用FindView()方法的视图。视图引擎将返回ViewEngineResult,其中包含已找到的视图(如果找到),或者视图已被不成功地查找的路径列表。

因此,要修改模板路径,可以覆盖此功能:让原始类找到视图,如果找到,则修改路径。

要做秀,你需要采取以下步骤:

  1. 继承您正在使用的视图引擎(我的sampel代码继承了Razor视图引擎)
  2. 注册您的vie引擎,以便在原始视图引擎之前查询它(在我的示例代码中,我只是清除已注册的引擎列表,并注册我的。原始列表包括剃须刀和Web表单视图引擎)
  3. 这是继承的视图引擎的代码:

    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);
        }
    
    }
    

    注意1:在您阅读// Modify here !!的地方,您可以修改result.View的路径属性。将其投放到RazorView(result.View as RazorView).ViewPath。由于ViewPath setter受到保护,您需要使用Reflection进行设置:您可以使用SetViewPath方法。

    注意2:正如您所看到的,我不是继承RazorViewEngine而是继承FixedRazorViewEngine。如果您在MSDN中浏览此类,则不会得到结果,但如果查看已注册视图引擎列表的原始内容,您将找到此类。我认为这取决于项目中已安装的软件包,我认为它解决了MVC4中的一个错误。如果您未在Microsoft.Web.Mvc命名空间中查找,请继承原始RazorViewEngined

    注3:在找到视图后,视图引擎使用ViewEngineResult执行它,因此,如果您更改它,它将使用新的视图路径 <执行/ p>

    最后,您需要在global.asax应用程序启动事件中更改已注册引擎的列表,如下所示:

    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());
    }
    

    注意:如果你在ViewEngineConfig文件夹中创建了一个App_Start类,并且调用了这个类的静态方法,那就更干净了,就像在所有其他配置中一样。< / em>的

答案 1 :(得分:1)

答案是从here复制的。

如果您不介意将代码绑定到您正在使用的特定视图引擎,那么您可以查看ViewContext.View属性并将其强制转换为WebFormView

var viewPath = ((WebFormView)ViewContext.View).ViewPath;

我相信最终会得到你的观点名称。

编辑:Haacked绝对是现货;为了让事情变得更整洁,我把逻辑包装在一个像这样的扩展方法中:

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"));
        }
    }
}

这似乎完全符合我的目的。

另一种解决方案here ((System.Web.Mvc.RazorView)htmlHelper.ViewContext.View).ViewPath

净-MVC

答案 2 :(得分:0)

我正在注册该地区,但是我不想要我的

url: "{area}/{controller}/{action}/{id}",

相反,我希望它像

url: "{controller}/{action}/{id}",

所以我已经注册了我的区域

context.MapRoute(
                name: "AreaName_default",
                url: "{controller}/{action}/{id}",
                namespaces: new[] { "SolutionName.AreaName.Controllers" }
            );

而且我不想在像

这样的每个操作方法中返回视图时添加硬代码字符串viewpath
return View("~/Areas/AreaName/Views/ControllerName/ViewName.cshtml", model); 

所以我创建了一个结果过滤器并覆盖了OnResultExecuting函数

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);
            }
        }
    }