Wie kann ich ViewBag-Eigenschaften für alle Ansichten festlegen, ohne eine Basisklasse für Controller zu verwenden?

96

In der Vergangenheit habe ich allgemeine Eigenschaften wie den aktuellen Benutzer global auf ViewData / ViewBag geklebt, indem alle Controller von einem gemeinsamen Basis-Controller geerbt wurden.

Dies ermöglichte es mir, IoC auf dem Basis-Controller zu verwenden und nicht nur auf globale Freigabe für solche Daten zuzugreifen.

Ich frage mich, ob es eine alternative Möglichkeit gibt, diese Art von Code in die MVC-Pipeline einzufügen.

Scott Weinstein
quelle

Antworten:

22

Von mir nicht ausprobiert, aber Sie könnten versuchen, Ihre Ansichten zu registrieren und dann die Ansichtsdaten während des Aktivierungsprozesses festzulegen.

Da Ansichten im laufenden Betrieb registriert werden, hilft Ihnen die Registrierungssyntax beim Herstellen einer Verbindung zum ActivatedEreignis nicht. Sie müssen sie daher wie folgt einrichten 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";
            };
        }
    }
}

Dies könnte einer der Vorschläge vom Typ "Nur ein Werkzeug ist ein Hammer" von mir sein. Möglicherweise gibt es einfachere MVC-fähige Möglichkeiten, um darauf zuzugreifen.

Bearbeiten: Alternativer Ansatz mit weniger Code - einfach an den Controller anschließen

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

Edit 2: Ein weiterer Ansatz, der direkt vom Controller-Registrierungscode aus funktioniert:

builder.RegisterControllers(asm)
    .OnActivated(e => {
        dynamic viewBag = ((Controller)e.Instance).ViewBag;
        viewBag.Config = e.Context.Resolve<Config>();
        viewBag.Identity = e.Context.Resolve<IIdentity>();
    });
Nicholas Blumhardt
quelle
Genau das, was ich brauchte. Die Antwort wurde aktualisiert, um sofort zu funktionieren
Scott Weinstein,
Tolles Zeug - basierend auf Ihrem Ansatz habe ich eine weitere Vereinfachung hinzugefügt, diesmal ohne dass ein Modul erforderlich ist.
Nicholas Blumhardt
Was ist der ResolveTeil von e.Context.Resolve? Ich sollte erwähnen, dass ich an Ninject
gewöhnt
243

Am besten verwenden Sie ActionFilterAttribute und registrieren Ihre benutzerdefinierte Klasse in Ihrer globalen Klasse. 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;
        }
    }

}

Registrieren Sie Ihre benutzerdefinierte Klasse in Ihrem globalen. asax (Application_Start)

protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();

        GlobalFilters.Filters.Add(new UserProfilePictureActionFilter(), 0);

    }

Dann können Sie es in allen Ansichten verwenden

@ViewBag.IsAdmin
@ViewBag.IsAuthenticated
@ViewBag.Avatar

Es gibt auch einen anderen Weg

Erstellen einer Erweiterungsmethode in HtmlHelper

[Extension()]
public string MyTest(System.Web.Mvc.HtmlHelper htmlHelper)
{
    return "This is a test";
}

Dann können Sie es in allen Ansichten verwenden

@Html.MyTest()
Mohammad Karimi
quelle
9
Ich verstehe nicht, warum dies nicht mehr positiv bewertet wurde; Es ist ein viel weniger invasiver Ansatz als die anderen
Joshcomley
5
8 Stunden Recherche, um dies zu finden ... die perfekte Antwort. Ich danke dir sehr.
Deltree
3
+1 Gute und saubere Art, globale Daten zu integrieren. Ich habe diese Technik verwendet, um meine Site-Version auf allen Seiten zu registrieren.
Will Bickford
4
Geniale, einfache und unauffällige Lösung.
Eugen Timm
3
Aber wo ist das IoC? dh wie würden Sie ausschalten MembershipService?
Drzaus
39

Da ViewBag-Eigenschaften per Definition an die Ansichtspräsentation und die möglicherweise erforderliche Light-View-Logik gebunden sind, würde ich eine Basis-WebViewPage erstellen und die Eigenschaften bei der Seiteninitialisierung festlegen . Es ist dem Konzept eines Basis-Controllers für wiederholte Logik und allgemeine Funktionalität sehr ähnlich, aber für Ihre Ansichten:

    public abstract class ApplicationViewPage<T> : WebViewPage<T>
    {
        protected override void InitializePage()
        {
            SetViewBagDefaultProperties();
            base.InitializePage();
        }

        private void SetViewBagDefaultProperties()
        {
            ViewBag.GlobalProperty = "MyValue";
        }
    }

Und dann \Views\Web.configsetzen Sie in die pageBaseTypeEigenschaft:

<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>
Brandon Linton
quelle
Das Problem bei diesem Setup besteht darin, dass der in der ersten Ansicht festgelegte Wert der Wert ist, wenn Sie den Wert in einer Ansicht auf eine Eigenschaft im ViewBag in einer Ansicht festlegen und dann versuchen, in einer anderen Ansicht (wie in Ihrer freigegebenen _Layout-Ansicht) darauf zuzugreifen in der Layoutansicht verloren.
Pedro
@Pedro das ist definitiv wahr, aber dann würde ich argumentieren, dass ViewBag nicht als dauerhafte Statusquelle in der Anwendung gedacht ist. Klingt so, als würden Sie diese Daten im Sitzungsstatus haben wollen, und dann könnten Sie sie auf Ihrer Basisansichtseite herausziehen und im ViewBag festlegen, falls vorhanden.
Brandon Linton
Sie haben einen gültigen Punkt, aber so ziemlich jeder verwendet den Datensatz in einer Ansicht in anderen Ansichten. Wenn Sie beispielsweise den Titel der Seite in einer Ansicht festlegen und Ihre freigegebene Layoutansicht dann in den <title> -Tags des HTML-Dokuments ausdrucken. Ich möchte sogar noch einen Schritt weiter gehen, indem ich Boolesche Werte wie "ViewBag.DataTablesJs" in einer "untergeordneten" Ansicht setze, damit die "Master" -Layoutansicht die richtigen JS-Referenzen auf dem HTML-Kopf enthält. Solange es sich um ein Layout handelt, halte ich es für in Ordnung, dies zu tun.
Pedro
@Pedro gut in der Titel-Tag-Situation, normalerweise wird das mit jeder Ansicht behandelt, die eine ViewBag.TitleEigenschaft festlegt, und dann ist das einzige, was im freigegebenen Layout ist <title>@ViewBag.Title</title>. Es wäre nicht wirklich für so etwas wie eine Basisanwendungsansichtseite geeignet, da jede Ansicht unterschiedlich ist, und die Basisansichtseite wäre für Daten gedacht, die wirklich für alle Ansichten gleich sind.
Brandon Linton
@Pedro Ich verstehe, was du sagst und ich denke, Brandon hat den Punkt dort verpasst. Ich habe eine benutzerdefinierte WebViewPage verwendet und versucht, einige Daten aus einer der Ansichten mithilfe einer benutzerdefinierten Eigenschaft in der benutzerdefinierten WebViewPage an die Layoutansicht zu übergeben. Wenn ich die Eigenschaft in der Ansicht festlegte, wurden die ViewData in meiner benutzerdefinierten WebViewPage aktualisiert, aber als sie in die Layoutansicht gelangte, ging der ViewData-Eintrag bereits verloren. Ich habe es umgangen, indem ich ViewContext.Controller.ViewData ["SomeValue"] in der benutzerdefinierten WebViewPage verwendet habe. Ich hoffe es hilft jemandem.
Imran Rashid
17

Brandons Post ist genau richtig. In der Tat würde ich noch einen Schritt weiter gehen und sagen, dass Sie nur Ihre allgemeinen Objekte als Eigenschaften der Basis-WebViewPage hinzufügen sollten, damit Sie nicht in jeder einzelnen Ansicht Elemente aus dem ViewBag umwandeln müssen. Ich mache mein CurrentUser-Setup auf diese Weise.

Michael Gagne
quelle
Ich konnte dies nicht mit dem Fehler zum '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 Dies ist genau das, was ich tue, um eine Instanz einer nicht statischen Dienstprogrammklasse freizugeben, die in allen Ansichten global verfügbar sein muss.
Nick Coad
9

Sie können ein benutzerdefiniertes ActionResult verwenden:

public class  GlobalView : ActionResult 
{
    public override void ExecuteResult(ControllerContext context)
    {
        context.Controller.ViewData["Global"] = "global";
    }
}

Oder sogar ein ActionFilter:

public class  GlobalView : ActionFilterAttribute 
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        filterContext.Result = new ViewResult() {ViewData = new ViewDataDictionary()};

        base.OnActionExecuting(filterContext);
    }
}

Hatte ein MVC 2-Projekt geöffnet, aber beide Techniken gelten immer noch mit geringfügigen Änderungen.

John Farrell
quelle
5

Sie müssen sich nicht mit Aktionen herumschlagen oder das Modell ändern, sondern nur einen Basis-Controller verwenden und den vorhandenen Controller aus dem Kontext der Layoutansicht umwandeln.

Erstellen Sie einen Basis-Controller mit den gewünschten gemeinsamen Daten (Titel / Seite / Speicherort usw.) und der Aktionsinitialisierung ...

public abstract class _BaseController:Controller {
    public Int32 MyCommonValue { get; private set; }

    protected override void OnActionExecuting(ActionExecutingContext filterContext) {

        MyCommonValue = 12345;

        base.OnActionExecuting(filterContext);
    }
}

Stellen Sie sicher, dass jeder Controller den Basis-Controller verwendet ...

public class UserController:_BaseController {...

Übertragen Sie den vorhandenen Basis-Controller aus dem Ansichtskontext auf Ihrer _Layout.cshmlSeite ...

@{
    var myController = (_BaseController)ViewContext.Controller;
}

Jetzt können Sie auf Ihrer Layoutseite auf Werte in Ihrem Basis-Controller verweisen.

@myController.MyCommonValue
Carter Medlin
quelle
3

Wenn Sie die Überprüfung der Kompilierungszeit und Intellisense für die Eigenschaften in Ihren Ansichten wünschen, ist der ViewBag nicht der richtige Weg.

Stellen Sie sich eine BaseViewModel-Klasse vor und lassen Sie Ihre anderen Ansichtsmodelle von dieser Klasse erben, z.

Basis-ViewModel

public class BaseViewModel
{
    public bool IsAdmin { get; set; }

    public BaseViewModel(IUserService userService)
    {
        IsAdmin = userService.IsAdmin;
    }
}

Spezifisches ViewModel anzeigen

public class WidgetViewModel : BaseViewModel
{
    public string WidgetName { get; set;}
}

Jetzt kann der Ansichtscode direkt in der Ansicht auf die Eigenschaft zugreifen

<p>Is Admin: @Model.IsAdmin</p>
Steven Quick
quelle
2

Ich habe festgestellt, dass der folgende Ansatz am effizientesten ist und eine hervorragende Kontrolle unter Verwendung der Datei _ViewStart.chtml und der erforderlichen bedingten Anweisungen bietet:

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

Hinweis :

PageData funktionieren in Ansichten einwandfrei. Im Fall einer PartialView muss diese jedoch von der View an die untergeordnete Partial übergeben werden.

nützlichBee
quelle