MVC: condivisione delle informazioni contestuali tra le viste

8

Scusate il lungo post. C'è una domanda, limitati a sopportare me.

Un piccolo contesto

Abbiamo un sito che è necessario adattare considerevolmente in base a una varietà di impostazioni utente, al gruppo a cui appartiene l'utente, da dove provengono e ad altre cose. Abbiamo usato per includere i bit rilevanti sul modello per la pagina, quindi se la pagina avesse una tabella che mostra se l'utente aveva più di una certa età, sul modello faremo qualcosa del tipo:

//model
public PageModel
{
    public bool ShowTable {get;set;}
}

//controller
public PageController
{
    public ActionResult ShowPage()
    {
        var model = new PageModel() {
            ShowTable = User.Age > 21
        };
        return View(model);
    }
}

//view
@if(Model.ShowTable)
{ 
    <table>Some Html here</table>
}

Questo è diventato rapidamente molto complicato sapere cosa dovremmo mostrare a quali utenti. Per cercare di risolvere questo problema, abbiamo centralizzato tutta la logica su quando una determinata cosa dovrebbe essere mostrata o nascosta. Abbiamo chiamato questa classe UserConfiguration e questa (per lo più) conteneva solo una serie di funzioni che restituivano booleani indicando cosa dovrebbe essere mostrato. Questo ci ha permesso di impostare una serie di specifiche e test su ciò che un utente dovrebbe mostrare. Questo UserConfigratuion è stato quindi messo su una classe base, da cui tutti i modelli di pagina erano obbligati ad ereditare, quindi quello che abbiamo attualmente è qualcosa del genere:

//UserConfiguration 
public UserConfiguration
{
    private readonly User _user;

    public UserConfiguration(User user) {
        _user = user
    }

    public bool ShowTable() {
        return _user.Age > 21;
    }
}

//model base
public ModelBase
{
    public UserConfiguration {get;set;}
}

//model
public PageModel : ModelBase
{
    // whatever data is needed for the page
}

//controller
public PageController
{
    public ActionResult ShowPage()
    {
        var userConfiguration = new UserConfiguration(User);
        var model = new PageModel {
            UserConfiguration = userConfiguration
        };
        return View(model);
    }
}

//view
@if(Model.UserConfiguration.ShowTable())
{ 
    <table>Some Html here</table>
}

Questo ha aiutato, soprattutto perché ci ha permesso di creare una serie di test su ciò che un utente dovrebbe e non dovrebbe vedere ecc. Tuttavia, non è una soluzione molto pulita, dovendo mettere insieme questa classe aggiuntiva e includerla il modello. Ha anche ramificazioni per il rendering di viste parziali. Se il modello ha una proprietà IEnumerable<Foo> Foos su di esso, che vogliamo rendere in modo parziale, ma quella parte anche si basa sulla configurazione dell'utente, abbiamo un problema. Non puoi semplicemente passare il foos al Partial come modello, perché il partial non ha accesso a UserConfiguration . Quindi quale sarebbe il modo migliore per accedere a queste informazioni. Per come la vedo io, nel contesto di asp.net MVC ci sono 4 modi disponibili:

1) Avere un nuovo modello per il parziale ad es.

// parent view
@{
    var foosModel = new foosModel {
        Foos = Model.Foos,
        UserConfiguration = Model.UserConfiguration
    }
}

@Html.RenderPartial("FooList", foosModel)

// child partial view
@if(Model.UserConfiguration.ShowTable) {
    foreach(var foo in Model.Foos) {
        //Some HTML
    }
}

Questa è probabilmente la soluzione "più pura", che aderisce meglio ai principi di MVC, ma coinvolge un sacco di modelli (probabilmente inutili), causando un gonfiamento del progetto.

2) Esporre la UserConfiguration tramite ViewData. ad esempio:

// parent view
@Html.RenderPartial("FooList", Model.Foos, new ViewDataDictionary { { "UserConfiguration", Model.UserConfiguration } })

// child partial view
@{ 
    var userConfig = (UserConfiguration)ViewData["UserConfiguration"];
}
@if(userConfig.ShowTable) {
    foreach(var foo in Model) {
        //Some HTML
    }
}

Non mi piace molto perché non è sicuro e si basa su stringhe magiche per ottenerlo dal ViewData.

3) Metti la UserConfiguration nel ViewBag. Stessi problemi di cui sopra veramente

4) Modificare il modello di pagina ed esporre UserConfiguration tramite una proprietà della pagina stessa, come per link

Ritengo che, dal momento che UserConfiguration è un'informazione contestualizzata ambientale, abbia senso esporla tramite la classe come nell'opzione 4 di cui sopra. È generalmente accettata la best practice in MVC per esporre questo tipo di dati? Qualcuno ha provato qualcosa come l'opzione 4 in passato e ci sono stati 'gotcha'

tl; dr: Qual è il modo migliore di esporre le informazioni contestuali alle viste sul tuo sito, in MVC in generale o asp.net MVC in particolare?

    
posta Anduril 12.12.2016 - 17:08
fonte

4 risposte

2

Devi andare con # 5: Nessuno dei precedenti.

Ho iniziato a creare metodi di estensione per l'interfaccia IPrincipal , che mi dà dichiarazioni strongmente tipizzate su ciò che l'utente corrente può fare. Potresti anche creare un DTO UserConfiguration incluso nella sessione per l'utilizzo con questi metodi di estensione.

Innanzitutto, i metodi di estensione:

namespace YourApplication.Helpers
{
    public static class UserConfigurationExtensions
    {
        private HttpContext CurrentContext
        {
            get
            {
                return System.Web.HttpContext.Current;
            }
        }

        private static UserConfiguration Config
        {
            get
            {
                if (CurrentContext == null)
                    return null;

                return CurrentContext.Session["UserConfiguration"] as UserConfiguration;
            }
        }

        public static bool CanViewTable(this IPrincipal user)
        {
            return Config.ShowTable;
        }
    }
}

Ora, quando l'utente ha effettuato correttamente l'accesso, crea l'istanza di UserConfiguration e lo memorizza in Session :

public class AccountController : Controller
{
    [HttpPost]
    public ActionResult Login(LoginFormModel model)
    {
        if (ModelState.IsValid)
        {
            // Log in
            Session["UserConfiguration"] = new UserConfiguration(...);
        }

        return RedirectToAction("Index", "Home");
    }
}

Successivamente, aggiungi lo spazio dei nomi in cui i metodi di estensione esistono per gli spazi dei nomi predefiniti nei modelli Razor.

YourApplication / Vista / web.config

<?xml version="1.0"?>

<configuration>
  <!-- ... -->

  <system.web.webPages.razor>
    <namespaces>
      <add namespace="YourApplication.Helpers"/>
    </namespaces>
  </system.web.webPages.razor>

  <!-- ... -->
</configuration>

Ora chiudi e riapri la soluzione di Visual Studio. Quindi i tuoi modelli Razor hanno nuovi metodi disponibili:

@if (User.CanViewTable())
{
    foreach(var foo in Model)
    {
        //Some HTML
    }
}
    
risposta data 21.03.2017 - 13:54
fonte
0

Vorrei andare con la tua prima opzione, apportando le modifiche necessarie alle tue classi di modelli. Sembra che l'opzione 4, che modifica il modello di pagina, stia scambiando i riferimenti del modello per i riferimenti di aiuto nelle tue visualizzazioni di rasoio. L'opzione 1 sembra più semplice da gestire rispetto all'opzione 4 perché richiederebbe meno codice e perché più sviluppatori MVC lo capirebbero.

    
risposta data 06.01.2017 - 22:02
fonte
0

La mia opinione è, dati contestuali con ViewBag / ViewData configurati dal servizio di dati contestuali. Mi sono infastidito con la no o la pessima flessibilità di avere "BaseController" che imposta tutte le cose da "GodModel" perché ogni vista ha bisogno di averla a meno che non abbia bisogno di aggiungere qualche nuova vista sottile dove non ho bisogno di tutte quelle cose. Ovviamente "GodModel" era anche una classe base per i modelli nelle viste.

È difficile capire in anticipo se c'è qualcosa di veramente necessario nelle "tutte" viste e rendere molte cose obbligatorie lo rende molto più difficile rispetto a rendere le cose facoltative e quando una volta ogni tanto Ho dimenticato di configurarlo perché è dinamico.

Ovviamente tutte le cose specifiche e realmente obbligatorie dovrebbero andare sul modello ed essere strongmente digitato e avere la convalida. Ma cose generiche che possono impantanare le prestazioni perché qualcuno ha pensato che "tutto deve essere strongmente digitato" non è bello.

    
risposta data 06.01.2017 - 22:24
fonte
0

sembra che la MAGGIOR PARTE delle informazioni che è necessario esaminare sia incentrata sull'utente e non basata sull'azione. perché non memorizzare la configurazione utente nella sessione? un altro approccio, a seconda di come si esegue l'autenticazione / gestione degli utenti, è quello di conservare tutte le informazioni necessarie in ClaimsPrincipal (esempio sotto) ...

    private ClaimsPrincipal CurrentClaimsPrincipal
    {
        get { return System.Security.Claims.ClaimsPrincipal.Current; }
    }

    public string Firstname
    {
        get { return (null != CurrentClaimsPrincipal) ? CurrentClaimsPrincipal.FindFirst(Helpers.Constants.GIVEN_NAME_KEY)?.Value : string.Empty; }
    }

    public string Lastname
    {
        get { return (null != CurrentClaimsPrincipal) ? CurrentClaimsPrincipal.FindFirst(Helpers.Constants.FAMILY_NAME_KEY)?.Value : string.Empty; }
    }

    public string AccessLevel
    {
        get { return (null != CurrentClaimsPrincipal) ? CurrentClaimsPrincipal.FindFirst(Helpers.Constants.ACCESS_LEVEL_KEY)?.Value : string.Empty; }
    }
    
risposta data 19.01.2017 - 18:21
fonte

Leggi altre domande sui tag