Як встановити властивості ViewBag для всіх подань без використання базового класу для контролерів?


96

У минулому я застосував загальні властивості, такі як поточний користувач, до ViewData / ViewBag у глобальному масштабі, маючи всі контролери, які успадковуються від загального базового контролера.

Це дозволило мені використовувати IoC на базовому контролері, а не просто звертатися до глобальних спільних даних для таких даних.

Мені цікаво, чи існує альтернативний спосіб вставки такого роду коду в конвеєр MVC?

Відповіді:


22

Не спробуваний мною, але ви можете подивитися на реєстрацію своїх подань, а потім встановити дані подання під час процесу активації.

Оскільки подання реєструються на льоту, синтаксис реєстрації не допомагає вам підключитися до 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>();
    });

Саме те, що мені було потрібно. Оновив відповідь, щоб працювати нестандартно
Скотт Вайнштейн

Чудові речі - на основі вашого підходу я додав ще одне спрощення, цього разу без потреби в модулі.
Ніколас Блюмхардт,

яка Resolveчастина e.Context.Resolve? Слід згадати, що я звик до Ninject ...
drzaus

243

Найкращий спосіб - використовувати 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()

9
Я не розумію, чому це не було проголосовано більше; це набагато менш інвазивний підхід, ніж інші
joshcomley

5
8 годин досліджень, щоб знайти це ... ідеальна відповідь. Дуже дякую.
deltree

3
+1 Гарний та чистий спосіб інтеграції глобальних даних. Я використав цю техніку, щоб зареєструвати версію свого сайту на всіх сторінках.
Уілл Бікфорд,

4
Блискуче, легке та ненав’язливе рішення.
Eugen Timm

3
Але де IoC? тобто як би ви вийшли MembershipService?
drzaus

39

Оскільки властивості 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 в одному поданні, а потім намагаєтесь отримати доступ до нього в іншому поданні (наприклад, у загальному поданні _Layout), значення значення, встановлене для першого подання, буде втрачено на поданні макета.
Педро,

@Pedro, це, безумовно, правда, але тоді я б стверджував, що ViewBag не має на меті бути постійним джерелом стану в програмі. Здається, ви хотіли б, щоб ці дані знаходились у стані сеансу, а потім ви могли б витягти їх на своїй базовій сторінці перегляду та встановити їх у ViewBag, якщо вони існують.
Брендон Лінтон,

Ви маєте дійсну точку зору, але майже всі використовують набір даних в одному поданні в інших поданнях; наприклад, коли ви встановлюєте заголовок сторінки в одному поданні, а ваш спільний вигляд макета друкує його в тегах <title> документа html. Мені навіть подобається зробити цей крок далі, встановлюючи логічні значення, такі як "ViewBag.DataTablesJs" у поданні "дочірній", щоб подання макета "master" містило відповідні посилання на JS на голові html. Поки це пов'язано з макетом, я вважаю, що це нормально робити.
Педро

@Pedro добре в ситуації тегів заголовків, як правило, це обробляється з кожним видом, що встановлює ViewBag.Titleвластивість, і тоді єдине, що є у спільному макеті <title>@ViewBag.Title</title>. Це насправді не підходить для чогось на зразок базової сторінки перегляду програми, оскільки кожен вигляд відрізняється, а сторінка базового перегляду буде для даних, які справді є загальними для всіх подань.
Brandon Linton

@Pedro Я розумію, про що ти говориш, і думаю, що Брендон там пропустив суть. Я використовував користувальницький WebViewPage, і я намагався передати деякі дані з одного з подань у подання макета, використовуючи спеціальну властивість у користувацькій WebViewPage. Коли я встановлюю властивість у поданні, воно оновлює ViewData у моєму користувацькому WebViewPage, але коли воно потрапляє до подання макета, запис ViewData вже втрачено. Я обійшов це за допомогою ViewContext.Controller.ViewData ["SomeValue"] у користувацькій WebViewPage. Сподіваюся, це комусь допоможе.
Імран Рашид,

17

Пост Брендона прямо на грошах. Справді, я б цей крок ще далі і сказати , що ви повинні просто додати свої загальні об'єкти як властивості в базовій WebViewPage , тому вам не доведеться литих деталей з ViewBag в кожному View. Я роблю налаштування CurrentUser таким чином.


Я не зміг змусити це спрацювати з помилкою'ASP._Page_Views_Shared__Layout_cshtml' does not contain a definition for 'MyProp' and no extension method 'MyProp' accepting a first argument of type 'ASP._Page_Views_Shared__Layout_cshtml' could be found (are you missing a using directive or an assembly reference?)
Sprintstar

+1 на цьому, саме це я роблю для того, щоб поділитися екземпляром нестатичного класу утиліти, який повинен бути загальнодоступним у всіх поданнях.
Нік Коад,

9

Ви можете використовувати власний 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 був відкритий, але обидві методи все ще застосовуються з незначними змінами.


5

Вам не потрібно возитися з діями або міняти модель, просто використовуйте базовий контролер і додайте наявний контролер з контекстного перегляду макета.

Створіть базовий контролер із бажаними загальними даними (заголовок / сторінка / розташування тощо) та ініціалізацією дії ...

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

3

Якщо ви хочете перевірити час компіляції та intellisense для властивостей у ваших поданнях, тоді ViewBag не є таким шляхом.

Розгляньте клас BaseViewModel і попросіть інші моделі подання успадковуватися від цього класу, наприклад:

Base 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>

2

Я виявив, що наступний підхід є найбільш ефективним і забезпечує чудовий контроль, використовуючи файл _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, його потрібно буде передати з подання дочірньому частковому.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.