Как установить свойства ViewBag для всех видов без использования базового класса для контроллеров?
В прошлом я вставлял общие свойства, такие как текущий пользователь, в ViewData/ViewBag глобальным способом, так как все контроллеры наследуются от общего базового контроллера.
Это позволило мне использовать IoC на базовом контроллере, а не просто использовать глобальный общий доступ для таких данных.
Мне интересно, есть ли альтернативный способ вставки такого рода кода в конвейер MVC?
9 ответов
Не опробован мной, но вы можете посмотреть на регистрацию ваших представлений, а затем настроить данные представления во время процесса активации.
Поскольку представления регистрируются на лету, синтаксис регистрации не поможет вам подключиться к 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>();
});
Лучший способ - использовать ActionFilterAttribute и зарегистрировать свой собственный класс в глобальном. asax (Application_Start)
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;
}
}
}
зарегистрируйте свой собственный класс в вашем глобальном. 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>
Пост Брэндона прямо на деньги. На самом деле, я бы сделал еще один шаг и сказал, что вам просто нужно добавить свои общие объекты в качестве свойств базовой WebViewPage, чтобы вам не приходилось приводить элементы из ViewBag в каждом отдельном представлении. Я делаю настройку CurrentUser таким образом.
Вы можете использовать собственный ActionResult:
public 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
Если вы хотите проверить время компиляции и intellisense для свойств в ваших представлениях, тогда ViewBag - это не тот путь.
Рассмотрим класс BaseViewModel и пусть ваши другие модели представлений наследуются от этого класса, например:
База ViewModel
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 будет отлично работать в представлениях; однако в случае PartialView его необходимо будет передать из View в дочерний Partial.
Я реализовал решение ActionFilterAttribute от @Mohammad Karimi. Это сработало хорошо, так как у меня был тот же сценарий, что и у ОП. Мне нужно было добавить данные в каждое представление. Атрибут фильтра действий выполнялся для каждого запроса страницы Razor, но он также вызывался для каждого запроса контроллера веб-API.
Razor Pages предлагает атрибут фильтра страницы , чтобы избежать ненужного выполнения фильтра действий при выполнении запроса контроллера веб-API.
Фильтры Razor Page IPageFilter и IAsyncPageFilter позволяют Razor Pages запускать код до и после запуска обработчика 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)
{
}
}
}
Согласно образцу в методах фильтрации для документации Razor Pages, фильтр страницы включается следующим образом:
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages()
.AddMvcOptions(options =>
{
options.Filters.Add(new ViewDataPageFilter(Configuration));
});
}