MVC - Compartir información contextual entre vistas

8

Por favor, disculpe el largo post. Hay una pregunta, solo tengan paciencia conmigo.

Un pequeño contexto

Tenemos un sitio que debe adaptarse considerablemente en función de una variedad de configuraciones de usuario, el grupo al que pertenece el usuario, de dónde provienen y otras cosas. Solíamos incluir los bits relevantes en el modelo para la página, así que si la página tuviera una tabla que mostraría si el usuario tenía más de cierta edad, entonces en el modelo haríamos algo como:

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

Esto se volvió rápidamente muy complicado para saber qué debemos mostrar a qué usuarios. Para tratar de solucionar este problema, centralizamos toda la lógica sobre cuándo se debe mostrar u ocultar una cosa en particular. Llamamos a esta clase UserConfiguration y (en su mayoría) solo contenía una serie de funciones que devuelven valores booleanos que indican lo que se debe mostrar. Esto nos permitió configurar una serie de especificaciones y pruebas de lo que un usuario debe mostrar. Este UserConfigratuion se colocó en una clase base, de la que todos los modelos de página debían heredar, por lo que actualmente tenemos algo como esto:

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

Esto ha ayudado, principalmente porque nos permitió crear una serie de pruebas de lo que un usuario debería o no debería ver, etc. Sin embargo, no es una solución muy limpia, ya que tiene que armar esta clase adicional e incluirla en el modelo. También tiene ramificaciones para renderizar vistas parciales. Si el modelo tiene una propiedad IEnumerable<Foo> Foos en él, queremos mostrarlo en forma parcial, pero ese parcial también se basa en la configuración del usuario, tenemos un problema. No puedes simplemente pasar los foos al Parcial como modelo, porque entonces el parcial no tiene acceso al UserConfiguration . Entonces, ¿cuál sería la mejor manera de acceder a esta información. A mi modo de ver, en el contexto de asp.net MVC hay 4 formas disponibles:

1) Tener un nuevo modelo para el parcial, por ejemplo,

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

Esta es probablemente la solución "más pura", que se adhiere mejor a los principios de MVC, pero involucra muchos modelos (posiblemente innecesarios), lo que provoca la expansión del proyecto.

2) Exponga la configuración de usuario a través de ViewData. por ejemplo:

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

Realmente no me gusta esto porque no es de tipo seguro y se basa en cadenas mágicas para obtenerlo desde ViewData.

3) Coloque la configuración de usuario en el ViewBag. Los mismos problemas que el anterior realmente

4) Modifique el modelo de página y exponga UserConfiguration a través de una propiedad de la página, según enlace

Me parece que, dado que la Configuración de usuario es información contextual ambiental, tiene sentido exponerla a través de la clase como en la opción 4 anterior. ¿Existe una mejor práctica generalmente aceptada en MVC para exponer este tipo de datos? ¿Alguien ha intentado algo como la opción 4 en el pasado y hubo algún 'gotcha's'?

tl; dr: ¿Cuál es la mejor manera de exponer información contextual a las vistas de su sitio, en MVC en general o en asp.net MVC en particular?

    
pregunta Anduril 12.12.2016 - 17:08

4 respuestas

2

Deberías ir con el número 5: ninguna de las anteriores.

Comencé a crear métodos de extensión para la interfaz IPrincipal , lo que me da declaraciones escritas de lo que el usuario actual puede hacer. Incluso podría crear un UserConfiguration DTO que rellene en la sesión para que lo utilicen estos métodos de extensión.

Primero, los métodos de extensión:

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

Ahora, cuando el usuario haya iniciado sesión correctamente, cree la instancia de UserConfiguration y guárdela en el Session :

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

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

A continuación, agregue el espacio de nombres en el que existen los métodos de extensión a sus espacios de nombres predeterminados en las plantillas de Razor.

YourApplication/Views/Web.config

<?xml version="1.0"?>

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

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

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

Ahora cierre y vuelva a abrir la solución de Visual Studio. Entonces sus plantillas de Razor tienen nuevos métodos disponibles:

@if (User.CanViewTable())
{
    foreach(var foo in Model)
    {
        //Some HTML
    }
}
    
respondido por el Greg Burghardt 21.03.2017 - 13:54
0

Iría con tu primera opción, haciendo las modificaciones necesarias a tus clases modelo. Parece que su opción 4, que modifica el modelo de página, sería intercambiar referencias de modelo por referencias de ayudante en sus vistas de afeitar. La opción 1 parece más fácil de mantener que la opción 4 porque aparentemente requeriría menos código y porque más desarrolladores de MVC lo entenderían.

    
respondido por el gpersell 06.01.2017 - 22:02
0

Mi opinión es, datos contextuales con ViewBag / ViewData configurados por el servicio de datos contextuales. Me enojé sin tener o muy mala flexibilidad de tener "BaseController" que establece todas las cosas de "GodModel" porque cada vista debe tenerlo a menos que tenga que agregar una nueva vista delgada donde no necesite todas esas cosas. Donde, por supuesto, "GodModel" también fue una clase base para los modelos en vistas.

Es difícil decir por adelantado si las vistas "todas" son realmente necesarias y hacer que muchas cosas sean obligatorias me hace mucho más difícil que hacer las cosas de forma opcional y, de vez en cuando Olvidé configurarlo porque es dinámico.

Por supuesto, todas las cosas específicas y realmente obligatorias de la vista deben ir al modelo, deben ser escritas con firmeza y tienen validación. Pero las cosas generales que pueden atascar el rendimiento porque alguien pensó que "todo tiene que escribirse con fuerza" no son agradables.

    
respondido por el Mateusz 06.01.2017 - 22:24
0

suena como que la mayor parte de la información que necesita inspeccionar está centrada en el usuario y no en la acción. ¿Por qué no almacenar su Configuración de usuario en la sesión? otro enfoque, dependiendo de cómo esté haciendo la autenticación / administración de usuarios, es mantener toda la información que necesita en el ClaimsPrincipal (muestra a continuación) ...

    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; }
    }
    
respondido por el jklDev 19.01.2017 - 18:21

Lea otras preguntas en las etiquetas