O ASP.NET Identity possui um padrão de nomenclatura para suas tabelas e campos, muitas vezes é necessário customizar este padrão para atender as necessidades da aplicação, confira como é simples realizar esta tarefa.
Como apresentado aqui algumas vezes, o ASP.NET Identity é um componente muito completo e simples de customizar, ele é escrito baseado no conceito Code-First e sua arquitetura é bem aberta possibilitando a customização de funcionalidades, comportamentos e até mesmo fonte de dados.
As tabelas que o ASP.NET Identity cria automaticamente segue o mesmo processo de qualquer desenvolvimento Code-First, permitindo que o desenvolvedor mapeie na configuração do DbContext toda a modelagem da base que será criada.
Vamos abordar de forma muito simples e pontual como realizar este processo partindo do pré-suposto que sua aplicação foi criada com base no template de aplicação ASP.NET MVC já com o ASP.NET Identity configurado. Independente deste fato, o que é realmente necessário fazer é editar o contexto do ASP.NET Identity.
Uma dica válida para qualquer situação é manter sempre o contexto do ASP.NET Identity separado do contexto da aplicação.
Iremos trabalhar também com a hipótese de customização do IdentityUser através de sua classe derivada ApplicationUser. Vamos ao código.
public class ApplicationUser : IdentityUser { public string Apelido { get; set; } } public class ApplicationDbContext : IdentityDbContext<ApplicationUser> { public ApplicationDbContext() : base("DefaultConnection") { } protected override void OnModelCreating(DbModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.Entity<IdentityUser>() .ToTable("Usuarios") .Property(p => p.Id) .HasColumnName("UsuarioId"); modelBuilder.Entity<ApplicationUser>() .ToTable("Usuarios") .Property(p => p.Id) .HasColumnName("UsuarioId"); modelBuilder.Entity<IdentityUserRole>() .ToTable("UsuarioPapel"); modelBuilder.Entity<IdentityUserLogin>() .ToTable("Logins"); modelBuilder.Entity<IdentityUserClaim>() .ToTable("Claims"); modelBuilder.Entity<IdentityRole>() .ToTable("Papeis"); } }
Na classe ApplicationUser realizamos uma pequena customização adicionando um novo campo (Apelido), apenas para enfatizar a situação.
Na classe ApplicationDbContext repare que ela herda de IdentityDbContext, que é a real classe de contexto do ASP.NET Identity onde ela internamente herda de DbContext.
A ideia de existir a classe ApplicationDbContext é justamente fornecer um contexto para customização, uma vez que a classe IdentityDbContext faz parte dos assemblies do ASP.NET Identity e não é possível ser alterada.
Baseado no conceito da herança o que basicamente fizemos foi sobrescrever o método OnModelCreating da classe base para alterarmos os padrões de nomenclatura. Utilizamos Fluent API para realizar esse mapeamento trocando os nomes das tabelas e campos.
Essa troca não impacta no funcionamento interno do ASP.NET Identity, pois as classes internas permanecem intactas, apenas o mapeamento do modelo objeto / relacional foi modificado e a aplicação irá seguir o que a modelagem manda.
modelBuilder.Entity<IdentityUser>() .ToTable("Usuarios") .Property(p => p.Id) .HasColumnName("UsuarioId"); modelBuilder.Entity<ApplicationUser>() .ToTable("Usuarios") .Property(p => p.Id) .HasColumnName("UsuarioId");
Repare que as duas classes (IdentityUser, ApplicationUser) foram mapeadas para mesma tabela, pois só assim seria possível mapear a classe de usuários e ao mesmo tempo aplicar as customizações, no final tudo vira uma única tabela com informações de ambas as classes.
Detalhes adicionais
- É possível utilizar todos os recursos do Fluent API nessa abordagem, sendo possível modificar tipo dos campos, inserir índices, criar novos relacionamentos, mapear novas tabelas e etc.
- É possível realizar todo este mapeamento em uma outra classe e configurar esta classe no contexto (assim como fazemos com as nossas entidades no Fluent API).
Simples?
Agora é possível incorporar o ASP.NET Identity ao seu modelo já existente de base de usuários, a única premissa é que todas as tabelas e campos do ASP.NET Identity sejam representados (mapeados) de alguma forma no banco de dados existente.
Vamos continuar a enriquecer este artigo participando nos comentários abaixo com dúvidas, complementos e feedbacks.
Referências
Este artigo foi escrito durante minha participação na Campus Party 2015 #CPBR8
“Uma dica válida para qualquer situação é manter sempre o contexto do ASP.NET Identity separado do contexto da aplicação.”
Por que?
Por quê devemos quebrar os contextos por interesses, isolar o contexto do Identity proporciona escalar o mesmo mecanismo para diferentes sistemas que usam o mesmo meio de acesso.
Quebrar contextos do EF é uma estratégia recomendada amplamente.
Olá Eduardo! Muito bacana o post, parabéns!
Quando possível gostaria muito de um post explicando como quebrar o Identity em n camadas, conforme a arquitetura que você abordou em vídeos anteriores.
Abraços!!
Marcus
Boa!!! Também queria ver!!!
Concordo!!!
Olá Eduardo,
Muito legal este artigo, só fiquei com dúvida em um ponto.
Fui tentar implementar estas customizações em minha base de dados (de um novo projeto) e estava recebendo como resposta do comando update-database a mensagem “Cannot find the object [“dbo.AspNetUserClaims” / “dbo.AspNetUserRoles” / “dbo.AspNetUserRoles”] (deu esse mesmo erro para estas três tabelas separadas por “/”) because it does not exist or you do not have permissions.”.
Executando o comando update-database -verbose (no Visual Studio como Administrador) notei que o erro ocorria no trecho em que o script de migração tenta dropar a FK de relacionamento das tabelas mencionadas no erro com a tabela dbo.AspNetUsers (Por exemplo: ALTER TABLE [dbo].[AspNetUserClaims] DROP CONSTRAINT [FK_dbo.AspNetUserClaims_dbo.AspNetUsers_UserId])
Até onde eu conheço do EF Code First Migrations, eu entendo que ele utiliza a autenticação especificada no arquivo de configuração do projeto (web.config / app.config), que está Integrated Security=true (default da criação de um novo projeto) e estou utilizando o SQL Server Express LocalDB (localdbv11.0).
Para seguir em frente encontrei a solução de conectar via SSMS no localdbv11.0 e executar estes comandos de ALTER TABLE manualmente e tudo funcionou normalmente inclusive a recriação destas FKs.
Você também já teve este tipo de problema? Se sim, como você fez para resolvê-lo de uma forma que não precise executar os scripts manualmente?
Desde já, agradeço a atenção.
Olá Diego,
Provavelmente o Migrations não versionou corretamente o estado atual do banco, por isso passou a dar esses problemas.
Isso pode acontecer por vários motivos, eu geralmente crio arquivos de Migrations para poder gerar um histórico de atualização.
Abs!
Parabéns por mais este tutorial, acompanho seu trabalho a algum tempo e aprendi muito com ele.
Gostaria de saber como utilizar o IdentityUser na camada de domínio de minha aplicação(e se é realmente nesta camada que devo usa-lo).
Desde já agradeço.
Guilherme Barros.
Muito obrigado Guilherme!
Vc não deve misturar essa classe com seu domínio, vc pode simular ele através de um VO.
Abs!
Boa tarde Eduardo,
Foi no seu site que tive o primeiro contato com o vNext, mais agora sinto falta de conteúdo sobre o mesmo. Chegou a evoluir em relação ao Visual Studio 2015 ?
Mais uma vez parabéns pelo site e pelos videos no youtube.
Muito obrigado Jeison,
Tenho muito conteúdo a realizar ainda, pois estamos em fases de muitas mudanças no ASP.NET.
Assim que tivermos a primeira versão estável criarei mais conteúdos.
Abs!
Olá Eduardo.
Falando em Identity, pode me responder uma pergunta?
Aonde as claims são armazenadas para que não se precise buscar no banco de dados?
Pensei em cookies, mas ao acessar resources, tenho somente o aspnet.applicationcookie, o mesmo está criptografado pelo que vejo, é lá que é armazenado ? Se sim, qual é o tipo de cryptgrafia que ele utiliza? é possível quebra-la?
Ótimos posts sobre identity, está ajudando muito a comunidade brasileira.
Parabéns.
Ótimo artigo Eduardo!
Tenho visto muitos artigos sobre DDD. Gostaria de saber como implantar em um sistema desenvolvido com DDD a parte do asp.net identity. Tem feito mta confusão na minha cabeça!
Até mais boa semana.
Minha dúvida é a mesma do Rodrigo.
Muito obrigado Rodrigo!
A dica é, não infecte seu DDD com Identity, tem que trabalhar usuário e perfil de usuário de formas separadas (podem estar no mesmo banco). Estou trabalhando numa demo modelo para demonstrar isso.
Abs!
Show! tem previsão pro lançamento dessa demo? 🙂
Realmente gostaria de ver como seria essa separação, pois o Identity impõe até o uso do IdentityDbContext.
Também to aguardando ansiosamente essa demo modelo. =]
Ótimo post.
Gostaria de saber se é possível separar o identity da camada de apresentação?
Desde de já agradeço.
Muito obrigado Victor!
É sim, não dá para isolar 100% se ainda quiser usar o IdentityFactory da forma que está hoje, porém se modificar a forma de gerenciamento único das instâncias por um DI Container por ex é possível. Estou trabalhando numa demo para mostrar como.
Abs!
estou testando o identity para implementá-lo em uma app. separei os contextos da aplicação e do sistema, além disso o profile do usuario (identityUser) foi ampliado.
Quando fui executar o código obtive um erro ao chamar o contexto da aplicação:
dentity.MyCommerce.Data.BaseRepository.IdentityUserLogin: : EntityType ‘IdentityUserLogin’ has no key defined. Define the key for this EntityType.
creio que este erro ocorre porque um DbSet do contexto do sistema está vinculado a User do contexto do Identiy, pois uma chave estrangeira foi inserida em IdentityUser:
1 Loja (contexto sistema) possui um usuário (contexto identity)
existe uma forma de resolver este problema sem a necessidade de unir os contextos?
– onde se lê: separei os contextos da aplicação e do sistema
– na verdade é: separei os contextos do identity e da aplicação.
Ótimo Post Eduardo!
Só queria esclarecer uma dúvida, quando fez o Post qual era a versão do Identity? É 2.1.0? Pergunto porque no meu projeto está instalado a versão 2.2.0 e estou tentando customizar Tabelas e Campos do IdentityUser e está dando alguns erros, Tenho uma classe Usuario que herda de IdentityUser, mas quando a tabela (Usuario) é criada no banco existe apenas as propriedades de IdentityUser, as propriedades customizadas não aparecem (ex: dataNascimento).
Testei criando um novo projeto onde a versão do Identity é a 2.1.0 e não tive problemas, as propriedades customizadas são criadas normalmente junto com as propriedades do IdentityUser, porem o nome da Tabela ficou dbo.AspNetUsers.
A maneira de customizar nas duas versões são diferentes?
Eduardo como vai?
Desde já gostaria de parabenizar pelo conteúdo do site e pelo ótimo treinamento que você ministrou para nós da Totvs BH em Dezembro passado. Em nossas implementações surgiu a seguinte dúvida:
Como posso alterar o tipo da chave primária do Identity removendo esse GUID e utilizando um ID auto incremento. Não achei quase nada a respeito e que achei está bem confuso.
Obrigado!
Eduardo bom dia!
Gostaria de agradecer pelo seu trabalho no intuito de compartilhar informações!
Sou assinante do seu canal do Youtube bem como também do canal AspNetCast, o qual também não perco nenhum! Posso lhe falar que ja assisti todos os Hangout´s bem como também seus videos.
Estou, baseado no que aprendi com voces do aspnetcast, especialmente com voce aqui no site. Tentando elaborar um pequeno sistema mas acabei esbarrando em um problema que a dois dias não encontro solução em lugar nenhum. Por isso recorro a voce pois o tenho como referência sobre o assunto.
O pequeno sistema possui um relacionamento da entidade Receber para a entidade Usuario (ApplicationUser) através das Propriedades UsuarioId e Id respectivamente. Porém ao executar o update-database gera o erro:
One or more validation errors were detected during model generation:
Receber_Usuario_Target_Receber_Usuario_Source: : The types of all properties in the Dependent Role of a referential constraint must be the same as the corresponding property types in the Principal Role. The type of property ‘UsuarioId’ on entity ‘Receber’ does not match the type of property ‘Id’ on entity ‘ApplicationUser’ in the referential constraint ‘Receber_Usuario’.
configurei a propriedade UsuarioId com o tipo Guid e string e com qualquer destes gera o mesmo erro.
Configurei desta forma o relacionamento dentro da entidade Receber:
HasRequired(a => a.Usuario)
.WithMany(a => a.Recebimentos)
.HasForeignKey(a => a.UsuarioId);
Se voce puder me ajudar desde ja lhe agradeço.
Ah sobre o seu curso reservei uma verba este mês para realiza-lo então se Deus quiser farei parte da próxima turma.
Obrigado Edurado e um grande abraço!
Sandro
Eduardo, boa noite.
Tenho uma dúvida, estou adaptando o meu projeto para minha necessidade.
Hoje na minha aplicação o usuário tem que está vinculado a uma empresa, e uma empresa pode ter muitos usuários.
Fui fazer este mapeamento usando a classe ApplicationUser para poder ter referência no banco, só que minha camada de Domain não pode ter referencia a nada, nesta situação devo instalar o Asp.net Identity no Domain? ou o que devo fazer?
Na verdade coloquei errado, é a classe IdentityUser não ApplicationUser
Olá, estou acompanhando o conteúdo de seu blog e aplicando os exemplos me deparei com a seguinte situação utilizando o Entity Framework e habilitando o Migrations para criar e atualizar o banco de dados, acontece que no Postgre o script gerado (e consequentemente o Banco de Dados), os nomes de tabelas e campos são gerados delimitados por aspas, fazendo com que meu select também tenha que ser executado de tal forma pelo Dapper ou pelo editor SQL, pois bem, minha pergunta é, existe alguma propriedade onde eu retire ou suprima a adição de aspas nestas scripts?,
Obrigado.
Boa noite Eduardo. Estou tentando criar esse projeto usando o .NET Core 1.1 mas estou parado nisso aqui:
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
}
Não existe mais o DbModelBuilder e agora não estou conseguindo remover as conventions. Você sabe como? Ainda não consegui encontrar a solução.
Socorro! Estou migrando uma antiga aplicação ADO para Entity com Identity. Temos uma tabela USUARIOS, que meu analista não me permite alterar em nada. Ela tem relacionamentos com outras tabelas do contexto e uma bendita coluna ID.
Eu alterei a entidade USUARIO para herdar IdentityUser. Configurei o contexto do Identity para usar o contexto da minha aplicação. E sobrescrevi o OnModelCreating para renomear a AspNetUsers para USUARIOS. Tudo quase funciona. Porém cheguei em um empasse. Se retiro a propriedade ID da minha classe USUARIO a view chama a Action de create com o POST, porém o userManager.Create me retorna um erro informando que a entidade não tem o ID que é necessário. E se eu deixo a propriedade ID a view nem chama Action da um erro “System.ArgumentException: Já foi adicionado um item com a mesma chave.” Que acredito acontece porque tenho o ID e o Id herdado de IdentityUser. Alguém pode me dar uma ideia? Estou entre a crus e a espada.