Come impostare le proprietà ViewBag per tutte le viste senza utilizzare una classe base per i controller?


96

In passato ho bloccato proprietà comuni, come l'utente corrente, su ViewData / ViewBag in modo globale facendo in modo che tutti i controller ereditino da un controller di base comune.

Ciò mi ha permesso di utilizzare IoC sul controller di base e non solo di raggiungere la condivisione globale per tali dati.

Mi chiedo se esiste un modo alternativo per inserire questo tipo di codice nella pipeline MVC?

Risposte:


22

Non provato da me, ma potresti prendere in considerazione la registrazione delle tue visualizzazioni e quindi l'impostazione dei dati di visualizzazione durante il processo di attivazione.

Poiché le viste vengono registrate al volo, la sintassi della registrazione non ti aiuta a connetterti Activatedall'evento, quindi dovresti configurarla in un 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";
            };
        }
    }
}

Questo potrebbe essere uno di quei suggerimenti del tipo "l'unico strumento è un martello" da me; ci possono essere modi più semplici abilitati per MVC per arrivarci.

Modifica: approccio alternativo, meno codice: basta collegarlo al controller

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

Modifica 2: un altro approccio che funziona direttamente dal codice di registrazione del controller:

builder.RegisterControllers(asm)
    .OnActivated(e => {
        dynamic viewBag = ((Controller)e.Instance).ViewBag;
        viewBag.Config = e.Context.Resolve<Config>();
        viewBag.Identity = e.Context.Resolve<IIdentity>();
    });

Esattamente quello di cui avevo bisogno. Aggiornato la risposta per funzionare fuori dagli schemi
Scott Weinstein

Grandi cose - in base al tuo approccio ho aggiunto un'altra semplificazione, questa volta senza la necessità di un modulo.
Nicholas Blumhardt

qual è la Resolveparte di e.Context.Resolve? Devo menzionare che sono abituato a Ninject ...
drzaus

243

Il modo migliore è usare ActionFilterAttribute e registrare la tua classe personalizzata nel tuo file global. 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;
        }
    }

}

registra la tua classe personalizzata nel tuo global. asax (Application_Start)

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

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

    }

Quindi puoi usarlo in tutte le visualizzazioni

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

Inoltre c'è un altro modo

Creazione di un metodo di estensione su HtmlHelper

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

Quindi puoi usarlo in tutte le visualizzazioni

@Html.MyTest()

9
Non capisco perché questo non sia stato votato di più; è un approccio molto meno invasivo rispetto agli altri
joshcomley

5
8 ore di ricerca per trovare questa ... la risposta perfetta. Grazie mille.
deltree

3
+1 Un modo piacevole e pulito per integrare i dati globali. Ho utilizzato questa tecnica per registrare la versione del mio sito su tutte le pagine.
Will Bickford

4
Soluzione brillante, facile e discreta.
Eugen Timm

3
Ma dov'è l'IoC? cioè come cambiereste MembershipService?
drzaus

39

Poiché le proprietà di ViewBag sono, per definizione, legate alla presentazione della vista e a qualsiasi logica di visualizzazione leggera che potrebbe essere necessaria, creerei una WebViewPage di base e imposterò le proprietà sull'inizializzazione della pagina. È molto simile al concetto di controller di base per logica ripetuta e funzionalità comuni, ma per le tue visualizzazioni:

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

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

E poi in \Views\Web.config, imposta la pageBaseTypeproprietà:

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

Il problema con questa configurazione è che se stai impostando il valore su una proprietà nel ViewBag su una vista e poi provi ad accedervi su un'altra vista (come la tua vista _Layout condivisa), il valore del valore impostato sulla prima vista sarà perso nella visualizzazione layout.
Pedro

@Pedro è decisamente vero, ma poi direi che ViewBag non è pensato per essere una fonte di stato persistente nell'applicazione. Sembra che tu voglia quei dati nello stato di sessione e quindi potresti estrarli nella pagina di visualizzazione di base e impostarli nel ViewBag se esiste.
Brandon Linton

Hai un punto valido, ma praticamente tutti usano i set di dati in una vista in altre viste; come quando si imposta il titolo della pagina in una visualizzazione e la visualizzazione del layout condiviso, quindi lo stampa nei tag <title> del documento html. Mi piace anche fare un ulteriore passo avanti impostando valori booleani come "ViewBag.DataTablesJs" in una vista "figlio" per fare in modo che la vista layout "master" includa i riferimenti JS corretti sulla testa dell'html. Finché è correlato al layout, penso che sia giusto farlo.
Pedro

@Pedro bene nella situazione dei tag del titolo, di solito viene gestito con ogni vista che imposta una ViewBag.Titleproprietà e quindi l'unica cosa nel layout condiviso è <title>@ViewBag.Title</title>. Non sarebbe davvero appropriato per qualcosa come una pagina di visualizzazione di un'applicazione di base poiché ogni visualizzazione è distinta e la pagina di visualizzazione di base sarebbe per i dati che sono veramente comuni a tutte le visualizzazioni.
Brandon Linton

@Pedro Capisco cosa stai dicendo e penso che Brandon abbia mancato il punto. Stavo usando una WebViewPage personalizzata e ho provato a passare alcuni dati da una delle viste alla vista layout utilizzando una proprietà personalizzata nella WebViewPage personalizzata. Quando ho impostato la proprietà nella vista, aggiornava ViewData nella mia WebViewPage personalizzata ma quando è arrivato alla visualizzazione layout, la voce ViewData era già persa. Ho aggirato il problema utilizzando ViewContext.Controller.ViewData ["SomeValue"] nella WebViewPage personalizzata. Spero che aiuti qualcuno.
Imran Rashid

17

Il post di Brandon è giusto sui soldi. È un dato di fatto, vorrei fare un ulteriore passo avanti e dire che dovresti semplicemente aggiungere i tuoi oggetti comuni come proprietà della WebViewPage di base in modo da non dover eseguire il cast di elementi dal ViewBag in ogni singola vista. Faccio la mia configurazione CurrentUser in questo modo.


Non sono riuscito a farlo funzionare con l'errore'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 su questo, questo è esattamente quello che sto facendo per condividere un'istanza di una classe di utilità non statica che deve essere disponibile a livello globale in tutte le viste.
Nick Coad

9

Puoi usare un ActionResult personalizzato:

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

O anche un ActionFilter:

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

        base.OnActionExecuting(filterContext);
    }
}

Aveva un progetto MVC 2 aperto ma entrambe le tecniche si applicano ancora con piccole modifiche.


5

Non devi fare confusione con le azioni o cambiare il modello, usa semplicemente un controller di base ed esegui il cast del controller esistente dal layout viewcontext.

Creare un controller di base con i dati comuni desiderati (titolo / pagina / posizione, ecc.) E inizializzazione dell'azione ...

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

    protected override void OnActionExecuting(ActionExecutingContext filterContext) {

        MyCommonValue = 12345;

        base.OnActionExecuting(filterContext);
    }
}

Assicurati che ogni controller utilizzi il controller di base ...

public class UserController:_BaseController {...

Trasmetti il ​​controller di base esistente dal contesto della vista nella tua _Layout.cshmlpagina ...

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

Ora puoi fare riferimento ai valori nel controller di base dalla pagina del layout.

@myController.MyCommonValue

3

Se vuoi controllare il tempo di compilazione e intellisense per le proprietà nelle tue viste, ViewBag non è la strada da percorrere.

Considera una classe BaseViewModel e fai in modo che gli altri tuoi modelli di visualizzazione ereditino da questa classe, ad esempio:

Base ViewModel

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

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

Visualizza ViewModel specifico

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

Ora il codice di visualizzazione può accedere alla proprietà direttamente nella visualizzazione

<p>Is Admin: @Model.IsAdmin</p>

2

Ho trovato che il seguente approccio è il più efficiente e offre un controllo eccellente utilizzando il file _ViewStart.chtml e le istruzioni condizionali quando necessario:

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

VistaA :

@{
   var UserProfile= PageData["Profile"] as List<string>;
 }

Nota :

PageData funzionerà perfettamente in Views; tuttavia, nel caso di PartialView, dovrà essere passato dalla View al Partial figlio.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.