What binds ApplicationUser to the aspnetusers table in Database First?

Gone Coding picture Gone Coding · Jan 27, 2015 · Viewed 9.8k times · Source

I have a database-first EDMX model in a separate library (e.g. Common.Feedback.Data), that includes the AspNetUser table and its related Identity Framework tables (pulled from another existing, working, database/application).

I have updated the ApplicationDbContext connection string to point to the new model and new database connection:

using System.Data.Entity;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;

namespace Feedback.MvcApplication.Models
{
    // You can add profile data for the user by adding more properties to your ApplicationUser class, please visit http://go.microsoft.com/fwlink/?LinkID=317594 to learn more.

    public class ApplicationUser : IdentityUser
    {
        public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
        {
            // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
            var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
            // Add custom user claims here
            return userIdentity;
        }
    }

    public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
    {
        public ApplicationDbContext()
            : base("FeedbackEntities", throwIfV1Schema: false)
        {
        }

        public static ApplicationDbContext Create()
        {
            return new ApplicationDbContext();
        }
    }
}

The connection string in the web.config includes the full path to the assembly and that reference is fine:

<add name="FeedbackEntities" 
     connectionString="metadata=res://Common.Feedback.Data/FeedbackModel.csdl|res://Common.Feedback.Data/FeedbackModel.ssdl|res://Common.Feedback.Data/FeedbackModel.msl;provider=System.Data.SqlClient;provider connection string=&quot;data source=mydatabase.database.windows.net;initial catalog=Feedback;persist security info=True;user id=LeaveFeedbackuser@mydatabase;password=mypassword;MultipleActiveResultSets=True;App=EntityFramework&quot;" 
     providerName="System.Data.EntityClient" />

At runtime, any access to login results in the following error:

"The entity type ApplicationUser is not part of the model for the current context"

All links I have tried, relating to that error, usually involve ways of updating the migration.

I cannot enable migrations as it is an EDMX first setup, but I assume there is a binding between the ApplicationUser object and the aspnetusers table "somewhere" behind the scenes.

Does anyone here have a clear understanding of how the ApplicationUser class is mapped to/from the aspnetusers table at runtime and explain how to get my code to work with the database?

Repro steps

  • Create a new MVC Web Application using VS 2013
  • Update all NuGet packages to latest
  • Take an existing SQL database with the Identity Framework tables and copy it to a new table (strip out any unrelated tables).
  • Add new tables for the project
  • Create a class library to hold the data models (e.g. Common.Feedback.Data)
  • Add an Edmx data model, to the library, based on the database created previously
  • Change the connection string to fully qualify the assembly (not res://*/)
  • Change the connection string name in IdentityModel.cs to match the connection string name in the config.
  • Copy the connection string from the library's app.config to the web project's web.config
  • Try login and you will hit the error mentioned

Update:

Based on a stray post, I changed my connection string to match the normal SQL connection the Identity Framework is configured to use by default (and using a Sql client):

<add name="FeedbackSql" 
     connectionString="data source=mydatabase.database.windows.net;initial catalog=Feedback;persist security info=True;user id=LeaveFeedbackuser@mydatabase;password=mypassword;MultipleActiveResultSets=True;App=EntityFramework" 
     providerName="System.Data.SqlClient" />

and changed to setup to use this new connection:

   public ApplicationDbContext()
        : base("FeedbackSql", throwIfV1Schema: false)
    {
    }

Bizarrely the error changes to:

The magic number in GZip header is not correct. Make sure you are passing in a GZip stream.

I think the initial change to a SqlClient provider is correct, so this new error may related to using an Azure database over that connection. I am open to suggestions on what to try next.

Updated web.config based on suggestion by @rism and this link (but the GZip error persists):

  <entityFramework>
    <defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework">
      <parameters>
        <!--<parameter value="mssqllocaldb" />-->
        <parameter value="data source=mydatabase.database.windows.net;initial catalog=Feedback;persist security info=True;user id=myuserid;password=mypassword;MultipleActiveResultSets=True;App=EntityFramework" />
      </parameters>
    </defaultConnectionFactory>
    <providers>
      <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
    </providers>
  </entityFramework>

Again based on tips from @rism I also tried this version (but the GZip error persists):

  <entityFramework>
    <defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework">
      <parameters>
        <parameter value="v12.0" />
      </parameters>
    </defaultConnectionFactory>
    <providers>
      <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
    </providers>
  </entityFramework>

New update:

I created a brand new Web application with the standard user-security option. I also created an empty database in Azure.

I did nothing but change the default connection string to this:

  <connectionStrings>
    <add name="DefaultConnection" 
         connectionString="data source=mydatabase.database.windows.net;initial catalog=Feedback;persist security info=True;user id=LeaveFeedbackuser;password=mypassword;MultipleActiveResultSets=True;App=EntityFramework"
         providerName="System.Data.SqlClient" />
  </connectionStrings>

and the default connection factory to this:

  <entityFramework>
    <defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework">
      <parameters>
        <parameter value="v12.0" />
      </parameters>
    </defaultConnectionFactory>
    <providers>
      <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
    </providers>
  </entityFramework>

On attempting to login I get the following error:

The magic number in GZip header is not correct. Make sure you are passing in a GZip stream. Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.IO.InvalidDataException: The magic number in GZip header is not correct. Make sure you are passing in a GZip stream.

Source Error:

Line 153: { Line 154: var user = new ApplicationUser { UserName = model.Email, Email = model.Email }; Line 155: var result = await UserManager.CreateAsync(user, model.Password); Line 156: if (result.Succeeded) Line 157: {

Answer

Gone Coding picture Gone Coding · Jan 30, 2015

The original question was about how does OWIN map the ApplicationUser class to the AspNetUsers table.

I decompiled the Identity Framework dlls with DotPeek and found that the table name "AspNetUsers" is simply hard-wired into the code of the OnModelCreating method. e.g.

 protected virtual void OnModelCreating(DbModelBuilder modelBuilder)
    {
      if (modelBuilder == null)
        throw new ArgumentNullException("modelBuilder");
        EntityTypeConfiguration<TUser> typeConfiguration1 = ((EntityTypeConfiguration<TUser>) modelBuilder.Entity<TUser>())
        .ToTable("AspNetUsers");

That code uses reflection to generate a list of field names from the properties in ApplicationUser. It checks for the existence of all those field names using a simple SQL query (of the system table containing all field names) and confirms they are all present.

The rest is simple mapping of names/values using constructed SQL queries.

As for the other part of the problem, the database connection, I have created a new question as it is a) unrelated to the title of this one and b) driving me crazy! The new question is Why am I getting "The magic number in GZip header is not correct." error using OWIN auth against Azure SQL