过去,我将常见属性(例如当前用户)通过让所有控制器从一个共同的基本控制器继承来全局地放入ViewData/ViewBag中。
这使我能够在基本控制器上使用IoC,而不仅仅是在全局共享数据中获取它们。
我想知道是否有其他方法将此类代码插入MVC管道?
过去,我将常见属性(例如当前用户)通过让所有控制器从一个共同的基本控制器继承来全局地放入ViewData/ViewBag中。
这使我能够在基本控制器上使用IoC,而不仅仅是在全局共享数据中获取它们。
我想知道是否有其他方法将此类代码插入MVC管道?
最佳方式是使用ActionFilterAttribute。我将向您展示如何在.Net Core和.Net Framework中使用它。
.Net Core 2.1和3.1
public class ViewBagActionFilter : ActionFilterAttribute
{
public ViewBagActionFilter(IOptions<Settings> settings){
//DI will inject what you need here
}
public override void OnResultExecuting(ResultExecutingContext context)
{
// for razor pages
if (context.Controller is PageModel)
{
var controller = context.Controller as PageModel;
controller.ViewData.Add("Avatar", $"~/avatar/empty.png");
// or
controller.ViewBag.Avatar = $"~/avatar/empty.png";
//also you have access to the httpcontext & route in controller.HttpContext & controller.RouteData
}
// for Razor Views
if (context.Controller is Controller)
{
var controller = context.Controller as Controller;
controller.ViewData.Add("Avatar", $"~/avatar/empty.png");
// or
controller.ViewBag.Avatar = $"~/avatar/empty.png";
//also you have access to the httpcontext & route in controller.HttpContext & controller.RouteData
}
base.OnResultExecuting(context);
}
}
接下来您需要在startup.cs文件中进行注册。
.Net Core 3.1
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews(options => {
options.Filters.Add<Components.ViewBagActionFilter>();
});
}
.Net Core 2.1
->.Net Core 2.1
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
options.Filters.Add<Configs.ViewBagActionFilter>();
});
}
然后您可以在所有视图和页面中使用它
@ViewData["Avatar"]
@ViewBag.Avatar
.Net框架 (ASP.NET MVC .Net框架)
public class UserProfilePictureActionFilter : ActionFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
filterContext.Controller.ViewBag.IsAuthenticated = MembershipService.IsAuthenticated;
filterContext.Controller.ViewBag.IsAdmin = MembershipService.IsAdmin;
var userProfile = MembershipService.GetCurrentUserProfile();
if (userProfile != null)
{
filterContext.Controller.ViewBag.Avatar = userProfile.Picture;
}
}
}
在global.asax文件的Application_Start方法中注册您的自定义类。
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
GlobalFilters.Filters.Add(new UserProfilePictureActionFilter(), 0);
}
然后您可以在所有视图中使用它。@ViewBag.IsAdmin
@ViewBag.IsAuthenticated
@ViewBag.Avatar
还有另一种方式
创建一个HtmlHelper的扩展方法
[Extension()]
public string MyTest(System.Web.Mvc.HtmlHelper htmlHelper)
{
return "This is a test";
}
然后您可以在所有视图中使用它
@Html.MyTest()
根据定义,ViewBag属性与视图呈现和可能需要的任何轻量级视图逻辑相关联,我会创建一个基础WebViewPage并在页面初始化时设置这些属性。这非常类似于为重复的逻辑和常见功能创建基础控制器的概念,但是针对你的视图:
public abstract class ApplicationViewPage<T> : WebViewPage<T>
{
protected override void InitializePage()
{
SetViewBagDefaultProperties();
base.InitializePage();
}
private void SetViewBagDefaultProperties()
{
ViewBag.GlobalProperty = "MyValue";
}
}
然后,在\Views\Web.config
文件中,设置pageBaseType
属性:
<system.web.webPages.razor>
<host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
<pages pageBaseType="MyNamespace.ApplicationViewPage">
<namespaces>
<add namespace="System.Web.Mvc" />
<add namespace="System.Web.Mvc.Ajax" />
<add namespace="System.Web.Mvc.Html" />
<add namespace="System.Web.Routing" />
</namespaces>
</pages>
</system.web.webPages.razor>
ViewBag.Title
属性来处理,然后共享布局中唯一的内容就是<title>@ViewBag.Title</title>
。这对于像基础应用程序视图页面这样的东西并不合适,因为每个视图都是独立的,而基础视图页面将用于在所有视图之间真正共同的数据。 - Brandon Linton我没有亲自尝试过,但你可以查看注册视图,然后在激活过程中设置视图数据。
由于视图是动态注册的,注册语法无法帮助你连接到Activated
事件,所以你需要在一个Module
中设置它:
class SetViewBagItemsModule : Module
{
protected override void AttachToComponentRegistration(
IComponentRegistration registration,
IComponentRegistry registry)
{
if (typeof(WebViewPage).IsAssignableFrom(registration.Activator.LimitType))
{
registration.Activated += (s, e) => {
((WebViewPage)e.Instance).ViewBag.Global = "global";
};
}
}
}
这可能是我那种“只有锤子的工具”类型建议之一;也许有更简单的MVC启用方法来解决它。
编辑:备选方案,更少的代码方法 - 只需附加到控制器
public class SetViewBagItemsModule: Module
{
protected override void AttachToComponentRegistration(IComponentRegistry cr,
IComponentRegistration reg)
{
Type limitType = reg.Activator.LimitType;
if (typeof(Controller).IsAssignableFrom(limitType))
{
registration.Activated += (s, e) =>
{
dynamic viewBag = ((Controller)e.Instance).ViewBag;
viewBag.Config = e.Context.Resolve<Config>();
viewBag.Identity = e.Context.Resolve<IIdentity>();
};
}
}
}
编辑2: 另一种直接从控制器注册代码工作的方法:
builder.RegisterControllers(asm)
.OnActivated(e => {
dynamic viewBag = ((Controller)e.Instance).ViewBag;
viewBag.Config = e.Context.Resolve<Config>();
viewBag.Identity = e.Context.Resolve<IIdentity>();
});
e.Context.Resolve
中的 Resolve
是什么?我应该提一下我习惯使用 Ninject... - drzausBrandon的帖子说得太对了。事实上,我想再进一步说一下,你应该把常用的对象作为基本WebViewPage的属性添加进去,这样你就不必在每个视图中都从ViewBag中强制转换项目了。我会用这种方式设置我的CurrentUser。
'ASP._Page_Views_Shared__Layout_cshtml'不包含'MyProp'的定义,并且没有接受类型为'ASP._Page_Views_Shared__Layout_cshtml'的第一个参数的扩展方法'MyProp'可以找到(您是否缺少使用指令或程序集引用?)
。 - Sprintstarpublic class GlobalView : ActionResult
{
public override void ExecuteResult(ControllerContext context)
{
context.Controller.ViewData["Global"] = "global";
}
}
甚至还可以使用ActionFilter:
public class GlobalView : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.Result = new ViewResult() {ViewData = new ViewDataDictionary()};
base.OnActionExecuting(filterContext);
}
}
我有一个MVC 2项目打开,但两种技术都适用,只需进行微小的更改。
public abstract class _BaseController:Controller {
public Int32 MyCommonValue { get; private set; }
protected override void OnActionExecuting(ActionExecutingContext filterContext) {
MyCommonValue = 12345;
base.OnActionExecuting(filterContext);
}
}
确保每个控制器都使用基础控制器...
public class UserController:_BaseController {...
在您的_Layout.cshml
页面中,将现有的基本控制器从视图上下文中转换...
@{
var myController = (_BaseController)ViewContext.Controller;
}
@myController.MyCommonValue
基本视图模型
public class BaseViewModel
{
public bool IsAdmin { get; set; }
public BaseViewModel(IUserService userService)
{
IsAdmin = userService.IsAdmin;
}
}
查看特定的ViewModel
public class WidgetViewModel : BaseViewModel
{
public string WidgetName { get; set;}
}
<p>Is Admin: @Model.IsAdmin</p>
我发现下面这种方法最高效,利用_ViewStart.chtml文件和必要时的条件语句可以获得出色的控制:
_ViewStart:
@{
Layout = "~/Views/Shared/_Layout.cshtml";
var CurrentView = ViewContext.Controller.ValueProvider.GetValue("controller").RawValue.ToString();
if (CurrentView == "ViewA" || CurrentView == "ViewB" || CurrentView == "ViewC")
{
PageData["Profile"] = db.GetUserAccessProfile();
}
}
ViewA:
@{
var UserProfile= PageData["Profile"] as List<string>;
}
注意:
在视图中,PageData将完美地工作;但是,在部分视图的情况下,它需要从视图传递到子部分视图。
我实现了@Mohammad Karimi的ActionFilterAttribute解决方案。它在我的情况下很有效,因为我需要向每个视图添加数据。该操作过滤器属性对于每个Razor页面请求都会执行,但也会针对每个Web API控制器请求进行调用。
Razor Pages提供了页面过滤器属性,以避免在进行Web API控制器请求时不必要地执行操作过滤器。
Razor Page过滤器IPageFilter和IAsyncPageFilter允许在运行Razor Page处理程序之前和之后运行代码。
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Configuration;
namespace MyProject
{
// learn.microsoft.com/en-us/aspnet/core/razor-pages/filter?view=aspnetcore-6.0
// "The following code implements the synchronous IPageFilter"
// Enable the page filter using 'services.AddRazorPages().AddMvcOptions( ... )
// in the 'ConfigureServices()' startup method.
public class ViewDataPageFilter : IPageFilter
{
private readonly IConfiguration _config;
public ViewDataPageFilter(IConfiguration config)
{
_config = config;
}
// "Called after a handler method has been selected,
// but before model binding occurs."
public void OnPageHandlerSelected(PageHandlerSelectedContext context)
{
}
// "Called before the handler method executes,
// after model binding is complete."
public void OnPageHandlerExecuting(PageHandlerExecutingContext context)
{
PageModel page = context.HandlerInstance as PageModel;
if (page == null) { return; }
page.ViewData["cdn"] = _config["cdn:url"];
}
// "Called after the handler method executes,
// before the action result."
public void OnPageHandlerExecuted(PageHandlerExecutedContext context)
{
}
}
}
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages()
.AddMvcOptions(options =>
{
options.Filters.Add(new ViewDataPageFilter(Configuration));
});
}