Desacoplando o ASP.NET Identity do MVC e Domínio

O ASP.NET Identity é uma ótima solução para a gestão dos usuários em sua aplicação, porém seu design é diretamente acoplado ao ASP.NET MVC e também devemos evitar de utilizar suas referências no domínio da aplicação.

Desacoplando o ASP.NET Identity. Finalmente após muitos pedidos eu consegui colocar a mão na massa e desenvolver um projeto modelo para ser usado como referência.

Desacoplando o ASP.NET Identity do MVC e Domínio

Como sabemos o ASP.NET Identity depende diretamente do EF (ou outros), Owin e System.Web, logo todas as implementações do ASP.NET Identity ficam de alguma forma acopladas em nossa camada de apresentação. Quando possuímos uma aplicação onde o usuário é uma peça importante no negócio existe a necessidade de representá-lo na camada de domínio, porém é altamente recomendado que a camada de domínio não conheça tecnologias terceiras (Identity, EF, outros).

Como resolver esse problema?

Foi após alguns estudos que cheguei no modelo que considero ser a “melhor abstração possível”, pois para o ASP.NET Identity funcionar na camada de apresentação é obrigatório levar algumas dependências, porém desde que esteja isolado em outra camada já conseguiremos ótimas boas vantagens.

Aviso: Caso você não conheça o ASP.NET Identity, DDD e Injeção de dependência sugiro que comece pelos tutoriais:

Neste tutorial você encontrará as seguintes tecnologias utilizadas

  • ASP.NET MVC 5
  • ASP.NET Identity 2
  • Entity Framework 6
  • Fluent API
  • Injeção de Dependência com Simple Injector
  • Owin
  • RestSharp para implementação do Twilio API (SMS)

Existem dois grandes objetivos nesta proposta, o primeiro objetivo é a reutilização, pois com um uma única solução (camada) de gestão de usuários é possível atender N aplicações, facilitando o gerenciamento e manutenção.

O segundo objetivo é permitir que a camada de domínio represente o usuário de forma abstraída, logo não existe referência direta do IdentityUser no domínio, porém é possível consultar os dados de usuário e incluir / modificar as informações salvas.

O primeiro passo é abstrair tudo, mover as dependências do Identity para uma camada isolada, depois injetar no MVC os objetos necessários, para essa implementação eu utilizei o Simple Injector, que como o próprio nome diz, é bem simples, rápido e atendeu bem a necessidade.

Com o Identity isolado e injetado devidamente na camada de apresentação (MVC) basta criar uma camada de acesso a dados que irá fornecer meios do domínio consumir dados do usuário de forma abstraída. A grande sacada está no uso correto do Fluent API e claro muita injeção de dependência.

A solução desde modelo não visa atender completamente a filosofia do DDD, pois procurei simplificar o bastante para deixar simples de modificar, por exemplo adicionando uma camada de Application. Portanto quem utilizar esta arquitetura deve estar ciente que algumas coisas a mais ainda precisam ser feitas.

Gostaria de ressaltar também que o template de projeto ASP.NET MVC que o Visual Studio fornece possui vários problemas de design de código. As controllers de Account e Manage do usuário precisaram ser parcialmente re-escritas para atender a injeção do Identity, problemas como mais de um construtor público e utilização do pattern (ou anti pattern?) Service Locator foram encontrados e retirados.

Para quem quiser acompanhar em detalhes técnicos toda a implementação dessa solução eu preparei o vídeo a seguir.

* Assine meu canal no Youtube 🙂

O código fonte desta solução está disponível no GitHub, caso queira propor alguma melhoria faça um Pull Request, será muito bem recebido.


Se você estiver interessado em conhecer mais e aprender como desenvolver aplicações Web com arquitetura baseada em DDD, aplicando os princípios SOLID, diversos Design Patterns e escrevendo testes de unidade inscreva-se em meu curso:

Vamos continuar a troca de experiências, deixe seu comentário abaixo, se gostou e concorda com o artigo compartilhe com seus colegas para transmitirmos o conhecimento para o máximo de pessoas possíveis. ;)

98 ideias sobre “Desacoplando o ASP.NET Identity do MVC e Domínio

  1. Eduardo,

    Por coincidência fiz um trabalho muito semelhante há pouco tempo e seu vídeo me deu algumas ideias para aperfeiçoar minha implementação.

    Não entendi porque você levou as ViewModels para a camada de crosscutting. No meu caso preferi deixar Controller, ViewModel e View na mesma camada do MVC, sendo que levei para o crosscutting os Managers, Context, Models do Identity além dos serviços de mensageria que se utilizam de outros serviços da minha camada de infraestrutura.

    Quanto às tabelas do Identity, fiz de maneira muito parecida: criei as entidades no domínio e mapeei com fluent.

    Gostei da sua abordagem para modificar os controllers Account e Manage para que usem injeção de dependências e adotarei esta ideia além de revisar a forma com que crio o ApplicationManager no Owin.

    Obrigado por compartilhar este vídeo.

    Abs.

    • Acredito que a ViewModel deve ser levada para a camada de crosscutting pois ela é comum a aplicação inteira e não somente a camada MVC. Pois imagine que você tenha uma camada MVC e outra WebAPI que compartilham das mesmas viewModels. Ou até mesmo dois projetos MVC.

      • Na verdade é bem desnecessária

        Para aplicar qualquer conceito de arquitetura, ou de DDD, não é necessário ficar separando N projetos, isso só aumenta a complexibilidade

        Essa tal CrossCutting só BR usa mesmo hauhauhau coisa nada a ve, já que Usuário é só replicado, e se usuário tiver referencias de outras classes? aí tem que ficar replicando pra todo lado huahauhau

        Em um projetinho, aonde Login/Senha do usuário já basta, até é valido…

  2. Bom tutorial.

    Eu fiz um desacoplamento semelhante, mas mudei a TKey de string para int e tive que fazer mais algumas coisas.

    Fiquei com uma dúvida na classe ApplicationUserManager ela antes possuía o parâmetro “IdentityFactoryOptions options” para fornecer no final o IDataProtectionProvider.

    Percebi que você substituiu pelo código:

    var provider = new DpapiDataProtectionProvider(“Eduardo”);

    Em exemplos acoplados da internet, eles passam esse parâmetro na classe Startup:

    (Startup)
    internal static IDataProtectionProvider DataProtectionProvider { get; private set; }

    public void ConfigureAuth(IAppBuilder app)
    {
    DataProtectionProvider = app.GetDataProtectionProvider();
    }

    (ApplicationUserManager)
    var provider = Startup.DataProtectionProvider;

    Percebi que você deixou no seu código de exemplo na classe Startup a seguinde propriedade que não está sendo usada:
    public static IDataProtectionProvider DataProtectionProvider { get; set; }

    A dúvida é se você fez isso pra “resolver rápido” e faltou alguma implementação?

  3. Parabéns pela ajuda novamente a comunidade, estava dormindo no teclado quando você falou que tinha feito, foi muito legal saber que ficou show de bola.

    Valeu cada hora que dedicou para nos ajudar.

  4. Olá Eduardo,
    Primeiramente, parabéns pelo tutorial, está me ajudando bastante agora.

    Uma dúvida: como fica a questão do migrations com dois DbContext? Tenho um projeto em que o banco já está criado, com várias tabelas mapeadas etc. Criei o segundo DbContext em separado, habilitei o migrations, mas quando gero uma nova migration, ele tenta excluir as tabelas do Identity pois não encontra no DbContext atual. Alguma ideia de como resolver?

    Abraços!

    • Você deve especificar o seu contexto ao habilitar/realizar as migrations

      enable-migrations -ContextTypeName
      Add-Migration -configuration PrimeiraMigration
      Update-Database -configuration -Verbose

      • Olá Eduardo Pires!

        Estou uns 3 dias quebrando a cabeça no seguinte problema:
        Fiz o Update-Database no contexto do Identity e gerou as tabelas normalmente.

        Depois criei a entidade Usuario com todos os campos da tabela AspNetUsers (da mesma forma que você fez)
        fiz os mappings com Fluent Api em Usuario e no ApplicationUser de forma igual.

        Depois habilitei o Migration no contexto da aplicação e depois inseri o comando “Add-Migration InitialCreate –IgnoreChanges”.

        Após isso dei o Update-Database no contexto da aplicação, não deu nenhum erro, porém não criou nenhuma tabela.
        Eu devo escrever algum código no método Up da classe InitialCreate?

  5. Boa noite, Eduardo eu tenho uma pergunta, mas não sei nem se é pertinente ao identity. Depois de assistir os tutoriais, eu reparei que vc nenhuma vez menciona o que utilizamos na empresa onde trabalho para autenticação de usuário, que é o forms authentication, uma coisa é totalmente diferente da outra?

  6. Olá Eduardo,

    primeiramente parabéns pelo excelente video, era exatamente esta a ideia que eu estava procurando para o meu projeto.
    Aproveitando a oportunidade gostaria apenas de confirmar uma coisa, a classe Usuario que você criou na sua camada de domínio, poderia eu utiliza-la como foreign key (ou seja relacionamento) para as demais classes do meu domínio? Por exemplo poderia eu criar uma propriedade do tipo Usuario na minha classe de dominio Pedido por exemplo?
    Aguardo retorno,

    Agradecendo.

  7. Olá boa noite Eduardo,

    Primeiramente gostaria de parabenizá-lo pelas ótimas vídeos aulas feitas por você estou aprendendo muito…

    Porém, assistindo este vídeo, e comparando ao vídeo sobre o Identity, aonde você usa o projeto do sampleIdentity e dá uma bela de uma modificada nele… Fiquei com algumas dúvidas caso queira utilizar a parte de Claims conforme explicado no vídeo do Identity.

    O que precisa ser mudado neste projeto de isolamento do Identity para que possa funcionar a logica do Claims e da parte do login do cliente?

    É claro que estou aprendendo ainda, mais tentei várias coisas que surgeriram na internet e nada, a parte de Claims ate funciona, porem não consigo adicionar, o Claim ao usuário, ou quando consigo, ele só adiciona ao usuário logado e não a outro qualquer…

    • São poucas mudanças, se comparar os dois projetos perceberá que a estrutura do Identity não foi alterada, basta adicionar os recursos extras do sample anterior.

  8. Ao implementar o Identity Desacoplado + arquitetura em camadas usando DDD. Esbarrei num problema. Se alguém o teve e conseguiu resolver, espero que possam me ajudar.

    Estou com dois context no EF, um do Identity e outro do meu domínio, segundo o tutorial do Eduardo, a classe Usuário ficaria como meio de campo tanto par ao Identity quanto para o Domínio, bastando colocar no mapeamento o mesmo nome da tabela, no caso ToTable(“AspNetUsers”);. Verifiquei também se o usuário está gerando igualzinho a tabela com todos os campos e atributos da tabela iguais, para isso fiz um add-migrations para o Contexto do Domínio e um para o Contexto do Identity, e ambos estão iguais. Dou Update do contexto do Identity, e quando dou update no contexto do domínio, ambos conflitam na tabela AspNetUsers, dizendo que já existe. Só que o tutorial diz que o EF6 reconheceria como a mesma coisa. Alguém já esbarrou com isso e conseguiu resolver?

  9. Olá Eduardo, estou me aprofundando no assunto sobre o Identity, porem conforme conversamos no curso de Abril, informei que gostava bastante de evitar ainda o uso de ORM’s e gosto bastante de ainda trabalhar com o ADO Classico.
    Não encontrei nenhum conteúdo a respeito e ninguém que tenha feito algo parecido.

    Você por acaso poderia dar algum exemplo?

  10. Olá Eduardo!
    Parabéns pelo artigo! Será muito útil no meu trabalho!
    Andei pesquisando na net uma forma de implementar o Identity baseado em Grupos de Usuários e não nos Usuários diretamente. Eu até encontrei um tutorial que faz isso, porem baseado na estrutura tradicional do Identity.
    Tentei implementar nessa estrutura desacoplada, mas fiquei meio perdido.
    Você teria algumsa dicas para dar uma luz de como implementar isso nessa estrutura?
    Obrigado!
    Abraços!

  11. Eduardo, Saudações
    Muito obrigado por compartilhar conosco mais um excelente tutorial.
    Seguindo o seu modelo eu consegui desacoplar o Identity, porém tive um problema. Só foi criado a tabela AspNetUsers, eu ter adicionado 02 atributos (FirstName e LastName) influencia em algo?

    No aguardo

  12. Olá Eduardo.

    Primeiramente, muito obrigado por compartilhar o conhecimento. Todo material é de grande utilidade para a comunidade.

    Estou tentando aplicar esse desacoplamento em meu projeto DDD conforme suas orientações, porém, eu já uso Ninject para IoC e fiquei meio perdido na criação do owin.Environment na inicialização. Ainda não será agora que vou conseguir estudar e usar o SimpleInjector. Poderia dar uma luz como farei isso usando o Ninject?

    Muito obrigado
    Abs

  13. Olá Eduardo,

    Tudo bem?

    Ao customizar o nome da tabela (por exemplo de AspNetUsers para Users) e realizar o migration a tabela é criada com o nome desejado. Porém, ao iniciar a aplicação e realizar por exemplo o simples “cadastro de um novo usuário”, então é gerado novamente a tabela com o nome padrão.

    Tem uma sugestão do que pode ser?

    Abs,
    Eduardo

    • Existem 2 mapeamentos apontando para AspNetUsers, a da classe do Identity e a da classe do Domínio. Você alterou para .ToTable(“Users”) nas duas?

  14. Eduardo, como ambas classes Usuários são “replicas”

    E se na minha classe de Usuário de Domain eu tiver outras propriedades?
    Por exemplo, UserDetails (1-1) ou public virtual Tenant Tenant

    Essas mesmas classes devem ser replicadas na camada CrossCutting?

    Como proceder para estes casos ?

    • A classe de domínio representa a classe do Identity como se fosse uma DTO. Sendo assim, qualquer diferença nas classes devem ser bem justificadas, por que haveria uma prop na classe de domínio e na classe do Identity não?

  15. Olá Eduardo, primeiramente parabéns pelos seus posts!!!

    A minha dúvida fica quanto a existir dois contexts. Por que não usar o Context que você criou com o novo usuário apenas? Qual seria a melhor prática para trabalhar com Claims ou Roles para este context? Pois criei todas minhas entities no mesmo local que o Usuario que você criou e queria apenas um banco, porém gostaria de usar as Claims (ou roles se não for possível), não vejo uma forma legal se não ter que copiar a padrão do Identity User… porém copiar já sai fora do escopo da idéia de desacoplar, certo? Obrigado.

    • Olá Luiz, podem haver N contextos. Eu recomendo separar o contexto do Identity dos demais por questões de separação de responsabilidade. O exemplo do post está totalmente apto a trabalhar com claims e roles.

  16. Eduardo, gostei muito do seu tutorial !
    Estou, inclusive, refazendo ele (com uma ou outra alteração rs rs …).
    Mas surgiu um dúvida …

    Caso eu opte por implementar a camada de serviços de aplicação como eu teria que lidar com o identity? Ele teria que permanecer em Infra.CrossCutting.Identity ou eu teria que encapsulá-lo na camada de serviços de aplicação ?

    [ ]’s

  17. Olá Eduardo,
    gratidão pela contribuição do vídeo, tenho aprendido muito com você.
    Tenho uma dúvida irrelevante em relação a funcionalidade da solução, mas eu percebi que o nome do projeto que utiliza o Simple Ninject é EP.IdentityIsolation.Infra.CrossCutting.IoC, onde IoC seria Inversion of Control, como o Simple Ninject é pra injeção de dependência o nome não deveria ser EP.IdentityIsolation.Infra.CrossCutting.InjectDependency , por exemplo? Estou fazendo essa pergunta, pois os conceitos podem não estar claros pra mim e eu posso tá me confundindo, não é simplesmente por conta do nome em si. 

  18. Olá Eduardo tudo bom?

    Estou tendo dificuldades para integrar o Identity usando o Ninject na estrutura DDD é possível dar uma ajuda ou mandar uma tutorial?

  19. Pingback: ASP.NET Identity | Charles Lomboni

  20. Parabéns por mais um vídeo de altíssimo nível.

    Tenho uma aplicação que usa ninject. Como faço para resolver esses casos abaixo?

    Bind(typeof(IServiceBase)).To(typeof(ServiceBase));
    Bind(typeof(IRepositoryBase)).To(typeof(RepositoryBase));
    Bind(typeof(IContextManager)).To(typeof(ContextManager));
    Bind().To();
    Bind(typeof(IUnitOfWork)).To(typeof(UnitOfWork));

    Obrigado.

  21. Eduardo excelente tutorial.

    Eu baixei o código do Git e estou com o seguinte erro:

    The constructor of type ApplicationUserManager contains the parameter with name ‘store’ and type IUserStore that is not registered. Please ensure IUserStore is registered, or change the constructor of ApplicationUserManager.

    Linha 23: container.RegisterMvcControllers(Assembly.GetExecutingAssembly());
    Linha 24:
    Linha 25: container.Verify();
    Linha 26:
    Linha 27: DependencyResolver.SetResolver(new SimpleInjectorDependencyResolver(container));

    Arquivo de Origem: C:srcEP.IdentityIsolation.MVCApp_StartSimpleInjectorInitializer.cs Linha: 25

  22. Eduardo, consegui resolver. O problema é que atualizei os pacotes e ele bagunçou os arquivos de config da IoC.

    Agora preciso converter o ID para int, quando eu usava o Identity junto e misturado eu conseguia fazer.

    Tem como vc me dá uma dica de como fazer isto?

  23. Sr Eduardo, seguindo uma video aulas sua me deparei com o seguinte problema Não foi possível carregar o tipo System.ComponentModel.DataAnnotations.Schema.IndexAttribute do assembly EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089.

    Procurei ajuda na internet, e aparentemente tem a ver com o versionamento do Entity… Entretanto, esse problema só ocorre em projetos que faço o uso do Identity… Nenhuma ajuda resolveu meu problema… Talvez essa duvida não faça parte desse topico, peço desculpas, mas preciso resolver esse problema para seguir com o curso… Estou usando o visual studio ultimate 2013 e seguindo sua video aula AspNetMvc_Essencial

  24. Boa Tarde Eduardo, Fui fazer o desacoplamento mas tive problemas com return HttpContext.Current.GetOwinContext().Authentication; no SimpleInjector inicializer

  25. Eduardo, parabéns pelo artigo. tenho seu site como referência para muitos conceitos e aplicações de tecnologias.
    Você saberia dizer qual a diferença principal do SimpleInjector para o ninject?
    E por que usar um e não outro?
    Grato pelo conteúdo do seu Site.

  26. Eduardo, ótimo vídeo, mas estou com um problema ao converter para a WebApi.
    Como estou usando WebApi, tenho que usar o SimpleInjector para WebApi. E Quando vou registrar as dependencias, registro assim:
    container.RegisterWebApiRequest<IUserStore>(() => new UserStore(new IdentityContext()));

    e quando vai ser verificado com as controllers:
    container.RegisterWebApiControllers(GlobalConfiguration.Configuration);
    ele diz que na minha AccountController, não está registrado a UserStore.
    The constructor of type ApplicationUserManager contains the parameter with name ‘store’ and type IUserStore that is not registered. Please ensure IUserStore is registered, or change the constructor of ApplicationUserManager.

    O que pode ser?

  27. E aee Eduardo, beleza?
    Excelente proposta de arquitetura e material de qualidade, tomei como base em um projeto. Uma dúvida, o SimpleInjector em questão de performance é superior ao Ninject?
    Parabéns pela didática. Abraço!

  28. Olá Eduardo,
    Seus vídeos tem me ajudado muito em meus estudos/projetos. Em breve poderei fazer o curso ASP.NET MVC 5.
    Enquanto isso, gostaria de uma ajuda. Seguindo esse vídeo, criei um projeto com o Identity desacoplado tentando utilizar o DDD. Minha dúvida é como posso adicionar informações de tabelas que não são do Identity em Claims para um usuário autenticado, quero adiciona-las à Identidade quando o usuário fizer login por exemplo. Pode me ajudar?

  29. Boa noite pessoal.

    Primeiramente, Eduardo, parabéns pelo site e pelos vídeos.

    Ao tentar rodar o comando Enable-Migrations, informa que o comando não é válido:
    The term ‘Enable-Migrations’ is not recognized as the name of a cmdlet, function, script file, or operable
    program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.

    Alguém teve esse problema?

  30. Eduardo

    Imagina que tenho um contexto para o Identity e outro para os dados da aplicacao.

    Como mapeio o ID do AspNetUsers (do contexto 1 ) como chave estrangeira de uma entidade (no contexto 2)

    Uma Luz pf,

    Obrigado,

    J

  31. Boa tarde Eduardo!
    Parabéns pelo tutorial, consegui fazer todos os passos do projeto, ele funcionou legal rodando no visual studio. Quando publiquei na hora de cadastrar um usuário ele trás o seguinte erro.

    The data protection operation was unsuccessful. This may have been caused by not having the user profile loaded for the current thread’s user context, which may be the case when the thread is impersonating.

    sabe me dizer o que pode ser?

  32. Boa noite.
    Alguém tem ideia do que pode ser esse erro?

    The configuration is invalid. The following diagnostic warnings were reported:
    -[Disposable Transient Component] IUserStore is registered as transient, but

    implements IDisposable.
    -[Disposable Transient Component] IRoleStore is registered as transient, but

    implements IDisposable.
    -[Disposable Transient Component] ApplicationRoleManager is registered as transient, but

    implements IDisposable.
    -[Disposable Transient Component] ApplicationUserManager is registered as transient, but

    implements IDisposable.
    -[Disposable Transient Component] ApplicationSignInManager is registered as transient, but

    implements IDisposable.
    See the Error property for detailed information about the warnings. Please see

    https://simpleinjector.org/diagnostics how to fix problems and how to suppress individual

    warnings.

  33. Olá Eduardo,
    Eu estou com um o problema seguinte:
    “The data protection operation was unsuccessful. This may have been caused by not having the user profile loaded for the current thread’s user context, which may be the case when the thread is impersonating.”
    Ele ocorre quando eu registro o usuário.
    Eu estou lendo muito e percebi que isto acontece em decorrência do Azure nesta linha do Statup
    DataProtectionProvider = app.GetDataProtectionProvider();
    De acordo com Vittorio Bertocci, no seu blog:
    http://www.cloudidentity.com/blog/2013/01/28/running-wif-based-apps-in-windows-azure-web-sites-4/
    o “Load User Profile” está ativo no IIS e no Azure está “off” e não tem como mudar.
    O que fazer? :'(

    A propósito, O SEU MATERIAL É MUITO BOM.

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *