MVC - Compartilhando informações contextuais entre visualizações


8

Por favor, desculpe o post longo. Há uma pergunta, apenas tenha paciência comigo.

Um pouco de contexto

Temos um site que é necessário para se adaptar consideravelmente com base em uma variedade de configurações do usuário, o grupo ao qual o usuário pertence, de onde vem e outras coisas. Costumávamos incluir os bits relevantes no modelo para a página; portanto, se a página tivesse uma tabela que mostrasse se o usuário tinha mais de uma certa idade, no modelo, farí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>
}

Isso rapidamente se tornou muito complicado para saber o que deveríamos mostrar para quais usuários. Para tentar resolver esse problema, centralizamos toda a lógica sobre quando uma coisa específica deve ser mostrada ou ocultada. Chamamos essa classe UserConfiguratione ela (principalmente) continha apenas uma série de funções retornando booleanos indicando o que deveria ser mostrado. Isso nos permitiu configurar uma série de especificações e testes sobre o que um usuário deve ser mostrado. Isso UserConfigratuionfoi colocado em uma classe base, da qual todos os modelos de página foram herdados; portanto, o que temos atualmente é algo como isto:

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

Isso ajudou, principalmente porque nos permitiu criar uma série de testes sobre o que um usuário deve ou não ver etc. No entanto, não é uma solução muito limpa, tendo que reunir essa classe adicional e incluí-la no modelo. Ele também possui ramificações para renderizar vistas parciais. Se o modelo possui uma propriedade IEnumerable<Foo> Foosque queremos renderizar em parte, mas essa parcial também depende da configuração do usuário, temos um problema. Você não pode simplesmente passar os foos para o Parcial como modelo, porque o parcial não tem acesso ao UserConfiguration. Então, qual seria a melhor maneira de acessar essas informações. Do meu ponto de vista, no contexto do asp.net MVC, existem 4 maneiras disponíveis:

1) Tenha um novo modelo para o parcial, por exemplo

// 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 é provavelmente a solução "mais pura", aderindo melhor aos princípios do MVC, mas envolve muitos modelos (indiscutivelmente desnecessários), causando inchaço no projeto.

2) Exponha a configuração do usuário através do ViewData. por exemplo :

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

Eu realmente não gosto disso porque não é do tipo seguro e depende de seqüências de caracteres mágicas para obtê-lo no ViewData.

3) Coloque a UserConfiguration no ViewBag. Os mesmos problemas acima

4) Modifique o modelo da página e exponha a UserConfiguration por meio de uma propriedade da própria página, conforme http://haacked.com/archive/2011/02/21/changing-base-type-of-a-razor-view .aspx /

Sinto que, uma vez que o UserConfiguration é uma informação contextual do ambiente, faz sentido expô-lo por meio da classe, como na opção 4 acima. Existe uma prática recomendada geralmente aceita no MVC para expor esse tipo de dados? Alguém já tentou algo como a opção 4 no passado e houve algum 'pegadinha'

tl; dr: Qual é a melhor maneira de expor informações contextuais às visualizações no seu site, no MVC em geral ou no asp.net MVC em particular?

Respostas:


2

Você deve ir com o item 5: Nenhuma das opções acima.

Comecei a criar métodos de extensão para a IPrincipalinterface, o que me dá declarações fortemente tipadas do que o usuário atual pode fazer. Você pode até criar um UserConfigurationDTO que você preenche na Sessão para ser usado por esses métodos de extensão.

Primeiro, os métodos de extensão:

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

Agora, quando o usuário tiver efetuado login com êxito, crie a instância de UserConfiguratione esconda-a no Session:

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

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

Em seguida, adicione o espaço para nome no qual os métodos de extensão existem aos espaços para nome padrão nos modelos do 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>

Agora feche e reabra a solução do Visual Studio. Em seguida, seus modelos do Razor têm novos métodos disponíveis:

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

0

Eu usaria sua primeira opção, fazendo as modificações necessárias nas suas classes de modelo. Parece que sua opção 4, modificando o modelo da página, negociaria referências de modelo por referências auxiliares em suas visualizações de navalha. A opção 1 parece mais fácil de manter do que a opção 4 porque aparentemente exigiria menos código e porque mais desenvolvedores de MVC entenderiam.


0

Minha opinião é: dados contextuais com o ViewBag / ViewData configurado pelo serviço de dados contextuais. Fiquei aborrecido com a flexibilidade, ou muito ruim, de ter o "BaseController", que define todas as coisas do "GodModel", pois todas as visualizações precisam dele, a menos que eu precise adicionar uma nova visualização fina, onde não preciso de todas essas coisas. Onde é claro que "GodModel" também era uma classe base para modelos em visualizações.

É difícil dizer de antemão se algo é realmente necessário para "todos" os modos de exibição e tornar muitas coisas obrigatórias, para mim é muito mais difícil do que tornar as coisas opcionais e, de vez em quando, esqueço de configurá-lo porque é dinâmico. .

É claro que todas as coisas específicas e realmente obrigatórias devem ser modeladas, tipificadas e validadas. Mas coisas gerais que podem atrapalhar o desempenho porque alguém pensou que "tudo tem que ser fortemente digitado" não é bom.


0

parece que a maioria das informações que você precisa inspecionar é centrada no usuário e não baseada em ação. por que não armazenar sua configuração de usuário na sessão? outra abordagem, dependendo de como você está executando a autenticação / gerenciamento de usuários, é manter todas as informações necessárias no ClaimPrincipal (exemplo abaixo) ...

    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; }
    }
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.