MVC - Austausch von Kontextinformationen zwischen Ansichten

8

Bitte entschuldigen Sie den langen Beitrag. Es gibt eine Frage, nimm sie einfach mit.

Ein kleiner Kontext

Wir haben eine Site, die sich aufgrund einer Vielzahl von Benutzereinstellungen, der Gruppe, zu der der Benutzer gehört, woher sie kommen und anderen Dingen erheblich anpassen muss. Früher haben wir die relevanten Bits in das Modell für die Seite aufgenommen. Wenn die Seite also eine Tabelle enthält, aus der hervorgeht, ob der Benutzer älter als ein bestimmtes Alter ist, haben wir auf dem Modell Folgendes ausgeführt:

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

Dies wurde schnell sehr kompliziert zu wissen, was wir welchen Benutzern zeigen sollten. Um dieses Problem zu lösen, haben wir die gesamte Logik dahingehend zentralisiert, wann eine bestimmte Sache angezeigt oder ausgeblendet werden soll. Wir haben diese Klasse aufgerufen UserConfigurationund sie enthielt (meistens) nur eine Reihe von Funktionen, die Boolesche Werte zurückgeben und angeben, was angezeigt werden soll. Auf diese Weise konnten wir eine Reihe von Spezifikationen und Tests einrichten, die zeigen, was einem Benutzer angezeigt werden soll. Dies UserConfigratuionwurde dann in eine Basisklasse gestellt, von der alle Seitenmodelle erben mussten. Was wir derzeit haben, ist also ungefähr so:

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

Dies hat geholfen, vor allem, weil es uns ermöglicht hat, eine Reihe von Tests zu erstellen, was ein Benutzer sehen sollte und was nicht usw. Es ist jedoch keine sehr saubere Lösung, diese zusätzliche Klasse zusammenzustellen und in das Modell aufzunehmen. Es hat auch Auswirkungen auf das Rendern von Teilansichten. Wenn das Modell eine Eigenschaft enthält IEnumerable<Foo> Foos, die wir in einem Teil rendern möchten, dieser Teil jedoch auch von der Benutzerkonfiguration abhängt, liegt ein Problem vor. Sie können die Foos nicht einfach als Modell an das Partial übergeben, da das Partial dann keinen Zugriff auf das hat UserConfiguration. Was wäre der beste Weg, um auf diese Informationen zuzugreifen? So wie ich es sehe, stehen im Kontext von asp.net MVC vier Möglichkeiten zur Verfügung:

1) Haben Sie ein neues Modell für den Teil zB

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

Dies ist die wahrscheinlich "reinste" Lösung, die sich am besten an die Prinzipien von MVC hält, aber viele (wohl unnötige) Modelle umfasst, was zu einem Aufblähen des Projekts führt.

2) Stellen Sie die Benutzerkonfiguration über die ViewData bereit. z.B :

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

Ich mag das nicht wirklich, weil es nicht typsicher ist und sich auf magische Zeichenfolgen stützt, um es aus den ViewData zu erhalten.

3) Legen Sie die Benutzerkonfiguration in das ViewBag. Gleiche Probleme wie oben

4) Ändern Sie das Seitenmodell und machen Sie die Benutzerkonfiguration über eine Eigenschaft der Seite selbst verfügbar, wie unter http://haacked.com/archive/2011/02/21/changing-base-type-of-a-razor-view angegeben .aspx /

Ich denke, da es sich bei der Benutzerkonfiguration um kontextbezogene Umgebungsinformationen handelt, ist es sinnvoll, sie über die Klasse wie in Option 4 oben verfügbar zu machen. Gibt es in MVC allgemein anerkannte Best Practices für die Offenlegung dieser Art von Daten? Hat jemand in der Vergangenheit so etwas wie Option 4 ausprobiert und gab es irgendwelche "Gotchas"?

tl; dr: Wie können Kontextinformationen am besten für die Ansichten auf Ihrer Website verfügbar gemacht werden, in MVC im Allgemeinen oder in asp.net MVC im Besonderen?

Anduril
quelle

Antworten:

2

Sie sollten mit # 5 gehen: Keine der oben genannten.

Ich habe angefangen, Erweiterungsmethoden für die IPrincipalBenutzeroberfläche zu erstellen, die mir stark typisierte Erklärungen darüber geben, was der aktuelle Benutzer tun kann. Sie können sogar ein UserConfigurationDTO erstellen , das Sie in die Sitzung einfügen, um es mit diesen Erweiterungsmethoden zu verwenden.

Zunächst die Erweiterungsmethoden:

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

Wenn sich der Benutzer erfolgreich angemeldet hat, erstellen Sie die Instanz von UserConfigurationund speichern Sie sie 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");
    }
}

Fügen Sie als Nächstes den Namespace, in dem die Erweiterungsmethoden vorhanden sind, zu Ihren Standard-Namespaces in den Razor-Vorlagen hinzu.

YourApplication / Views / Web.config

<?xml version="1.0"?>

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

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

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

Schließen Sie nun die Visual Studio-Lösung und öffnen Sie sie erneut. Dann stehen Ihren Razor-Vorlagen neue Methoden zur Verfügung:

@if (User.CanViewTable())
{
    foreach(var foo in Model)
    {
        //Some HTML
    }
}
Greg Burghardt
quelle
0

Ich würde mit Ihrer ersten Option gehen und die notwendigen Änderungen an Ihren Modellklassen vornehmen. Es scheint, dass Ihre Option 4, das Seitenmodell zu ändern, darin besteht, Modellreferenzen gegen Hilfsreferenzen in Ihren Rasiereransichten zu tauschen. Option 1 scheint einfacher zu warten zu sein als Option 4, da anscheinend weniger Code erforderlich ist und mehr MVC-Entwickler dies verstehen würden.

gpersell
quelle
0

Meiner Meinung nach sind Kontextdaten mit ViewBag / ViewData vom Kontextdatendienst konfiguriert. Ich ärgerte mich über keine oder eine sehr schlechte Flexibilität, "BaseController" zu haben, der alle Inhalte von "GodModel" festlegt, da jede Ansicht diese haben muss, es sei denn, ich muss eine neue dünne Ansicht hinzufügen, in der ich all diese Dinge nicht benötige. Wobei "GodModel" natürlich auch eine Basisklasse für Modelle in Ansichten war.

Es ist schwer im Voraus zu sagen, ob etwas von "allen" Ansichten wirklich benötigt wird, und viele Dinge obligatorisch zu machen, macht es für mich viel schwieriger als Dinge optional zu machen, und wenn ich ab und zu vergesse, es einzurichten, weil es dynamisch ist .

Natürlich sollten alle sichtspezifischen und wirklich obligatorischen Dinge zum Modell gehen und stark typisiert sein und eine Validierung haben. Aber allgemeine Dinge, die die Leistung beeinträchtigen können, weil jemand dachte, dass "alles stark getippt werden muss", ist nicht schön.

Mateusz
quelle
0

Es hört sich so an, als ob die meisten Informationen, die Sie überprüfen müssen, benutzerzentriert und nicht handlungsbasiert sind. Warum speichern Sie Ihre Benutzerkonfiguration nicht in der Sitzung? Ein anderer Ansatz, abhängig davon, wie Sie die Benutzerauthentifizierung / -verwaltung durchführen, besteht darin, alle Informationen, die Sie benötigen, im ClaimsPrincipal zu speichern (Beispiel unten) ...

    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; }
    }
jklDev
quelle