Implement Authentication using ASP Identity With 2 Tables - asp.net

Instead of the default 5 tables, I would like to use just UserLoginsTable and UserTable. Because I just have one 'User' type (Admin) and there's no point of having the other tables.
I'm using ASP MVC 5 framework and Entity Framework 6 (Code First approach).
How can I achieve this in my application?

when you run add-migration "ApplicationDbContext", its give you the code of migration file which contains all the queries, what you need here is write the code to delete other 4 tables like:
enable-migrations
add-migration "drop tables" //selected specific context
write down the below code inside the migration code class
DropTable("dbo.AspNetUserClaims");
DropTable("dbo.AspNetUserLogins");
DropTable("dbo.AspNetUserRoles");
DropTable("dbo.AspNetRoles");
finally run the below command
update-database

You create your own User inheriting IdentityUser like this:
public class User : IdentityUser
{
}
Then you ignore the other classes in your DbContext, including the original IdentityUser, including your Users DbSet:
public class MyDbContext : IdentityDbContext
{
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// avoid some base class sets...
modelBuilder.Ignore<IdentityUser>();
modelBuilder.Ignore<IdentityUserRole>();
modelBuilder.Ignore<IdentityRole>();
modelBuilder.Ignore<IdentityUserClaim>();
// keep identity's original configurations for AspNetUsers and AspNetUserLogins
EntityTypeConfiguration<User> configuration = modelBuilder.Entity<User>().ToTable("AspNetUsers");
configuration.HasMany<IdentityUserLogin>(u => u.Logins).WithRequired().HasForeignKey(ul => ul.UserId);
IndexAttribute indexAttribute = new IndexAttribute("UserNameIndex")
{
IsUnique = true
};
configuration.Property((Expression<Func<User, string>>)(u => u.UserName)).IsRequired().HasMaxLength(0x100).HasColumnAnnotation("Index", new IndexAnnotation(indexAttribute));
configuration.Property((Expression<Func<User, string>>)(u => u.Email)).HasMaxLength(0x100);
modelBuilder.Entity<IdentityUserLogin>().HasKey(l => new { LoginProvider = l.LoginProvider, ProviderKey = l.ProviderKey, UserId = l.UserId }).ToTable("AspNetUserLogins");
}
}
The reason why I ignored the original IdentityUser and created my own User is because of this exception:
The navigation property 'Roles' is not a declared property on type
'IdentityUser'. Verify that it has not been explicitly excluded from
the model and that it is a valid navigation property.
I tried using modelBuilder.Entity<IdentityUser>().Ignore(u => u.Roles); but it didn't solve, though if someone knows how to solve this, we could keep things simpler, I would appreciate any suggestions.

Related

EntityFramework dependency injection of DatabaseContext on Asp.Net

i have not much knowledge about Asp and Entity Framework so i really cant figure out what i have to do.
My problem is accessing database context out of main asp method -
There is how db context created and used in Program.cs (Main)
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<DatabaseContext>(
options => options.UseSqlite(builder.Configuration.GetConnectionString("DefaultDataSource"))
);
var app = builder.Build();
using (var scope = app.Services.CreateScope())
{
var context = services.GetRequiredService<DatabaseContext>();
context.Database.EnsureCreated();
}
so my problem is kinda that i making "options" for DatabaseContext constructor out of "builder.Configuration"
But what do i do when i need to acces db from other script? DatabaseContext requires config but i just dont know how to get it outside of that builder.
one guy suggested me to use Dependency Injection, i have looked but i just cant get how to do it properly, like i make a class where i initialize db context, but i still need to somehow make a config here and i really have no clue how.
It could be really stupid question but i really cant figure it out for a couple of days :D
I`ve tried to make DbContext without a config but it gives error
I don't know what you tried, but I think this might be what you want.
Assuming your DatabaseContext class is the most basic, and defines a table
Users in it:
public class DatabaseContext : DbContext
{
public DatabaseContext(DbContextOptions<DatabaseContext> options)
: base(options)
{
}
public DbSet<Models.User> Users { get; set; }
}
Then you register it in Program.cs:
builder.Services.AddDbContext<DatabaseContext>(
options => options.UseSqlite(builder.Configuration.GetConnectionString("DefaultDataSource"))
);
You can generate your database by migrating and updating the database(Of course you can also manually create):
Add-Migration InitialCreate
Update-Database
Then you can access your database in other classes through dependency injection, such as in the controller:
public class HomeController : Controller
{
private readonly DatabaseContext _databaseContext;
public HomeController(DatabaseContext databaseContext)
{
_databaseContext = databaseContext;
}
public IActionResult Index()
{
var user = _databaseContext.Users.ToList();
return View();
}
}
For more details about dependency injection, you can refer to this document.

Creating authentication using own dbcontext

I want to create an owin authentication using my own external sql database instead of the base asp context. I can create an edmx model from the database but i dont know how to use it to identify the users.
When i change the base Database connection used by ApplicationUser to my own one i get this exception.
I have already created at least 30 projects to test the solutions i found on google but none of them was worked for me.
[SOLUTION]
The solution is much more easier than i thougth..
All i had to do was inheriting my User table from IdentityUser (AspNetUser is my own table)
public partial class AspNetUser : IdentityUser
and using my custom connectionstring's name (BCTSDb) instead of DefaultConnection in IdentityModels.cs.
public ApplicationDbContext()
: base("BCTSDb", throwIfV1Schema: false)
This is codefirst from database, database first is not working with this solution (i dont know why).
This solved the FIRST problem. After this i couldn't scaffold any controllers with views because of missing keys and ambiguous references.
[WHATTODO]
1. Ambiguous references
In my AspNetUser.cs class i made all ORIGINAL attributes override:
for example UserName:
public override string UserName { get; set; }
Its possible that this is not necessary, i haven't tried without this, maybe this solve the ambiguous references between my dbcontext and ApplicationDbContext.
2. Missing keys
I dont know where i left my keys but there is the solution:
At the end of my OnModelCreating function (in my dbcontext) i wrote the following lines:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// your table operations, references
// ...
modelBuilder.Entity<IdentityUserLogin>().HasKey<string>(l => l.UserId);
modelBuilder.Entity<IdentityRole>().HasKey<string>(r => r.Id);
modelBuilder.Entity<IdentityUserRole>().HasKey(r => new { r.RoleId, r.UserId });
base.OnModelCreating(modelBuilder);
}
After this i could do anything i want.

DropCreateDataBaseAlways is not working when working with multiple db schemas with Entity Framework 6 Code First

After watching the "Enhancements to Code First Migrations: Using HasDefaultSchema and ContextKey for Multiple Model Support" section of Julie Lerman's PluralSite video, "Entity Framework 6: Ninija Edition-What's New in EF 6" (https://app.pluralsight.com/library/courses/entity-framework-6-ninja-edition-whats-new/table-of-contents), it seems there is a way to run multiple schemas under a single database in Entity Framwork 6 using Code First Migrations...
However, based on the video you still need to these package manager commands for each project that houses a separate context:
1. enable-migrations
2. add-migration [MIGRATION NAME]
3. update-database
This is fine and good if you actually care about maintaining migrations going forward, which is not a concern of mine.
What I'd like to do is have each of my Context's initializers set to DropCreateDatabaseAlways, and when I start up my client app (in this case, an MVC site), code first will create the database for the first context used, create the tables in with the correct schema for that context, and then create the tables for the rest of the contexts with the correct schema.
I don't mind if the whole database is dropped and recreated every time I hit F5.
What is happening now is the last context that is accessed in the client app is the only context tables that are created in the database... any contexts being accessed before the last get their tables blown away.
I am currently using two contexts, a Billing context and a Shipping context.
Here is my code:
My client app is an MVC website, and its HomeController's Index method looks like this:
public ActionResult Index()
{
List<Shipping.Customer>
List<Billing.Customer> billingCustomers;
using (var shippingContext = new Shipping.ShippingContext())
{
shippingCustomers = shippingContext.Customers.ToList();
}
using (var billingContext = new Billing.BillingContext())
{
billingCustomers = billingContext.Customers.ToList();
}
}
Here is my DbMigrationsConfigurationClass and ShippingContext class for the Shipping Context:
internal sealed class Configuration : DbMigrationsConfiguration<ShippingContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
}
protected override void Seed(ShippingContext context)
{
}
}
public class ShippingContext : DbContext
{
public ShippingContext() : base("MultipleModelDb")
{
}
static ShippingContext()
{
Database.SetInitializer(new ShippingContextInitializer());
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema("Shipping");
base.OnModelCreating(modelBuilder);
}
public DbSet<Customer> Customers { get; set; }
class ShippingContextInitializer : DropCreateDatabaseAlways<ShippingContext>
{
}
}
Likewise, here is the DbMigrationConfiguration class for the Billing Context and the BillingContext class:
internal sealed class Configuration : DbMigrationsConfiguration<BillingContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
}
protected override void Seed(BillingContext context)
{
}
}
public class BillingContext : DbContext
{
public BillingContext() : base("MultipleModelDb")
{
}
static BillingContext()
{
Database.SetInitializer(new BillingContextInitializer());
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema("Billing");
base.OnModelCreating(modelBuilder);
}
public DbSet<Customer> Customers { get; set; }
class BillingContextInitializer : DropCreateDatabaseAlways<BillingContext>
{
}
}
based on the order that the contexts are being called in the controller's action method, whichever context is accessed last is the only context that is created... the other context is wiped out.
I feel like what I'm trying to do is very simple, yet code first migrations, as well as trying to "shoehorn" Entity Framework to represent multiple contexts as separate schemas in the same physical database seems a bit "hacky"...
I'm not that versed with migrations to begin with, so what I'm trying to do might not make any sense at all.
Any feedback would be helpful.
Thanks,
Mike

Asp.Net Identity 2.0: The entity type User is not part of the model for the current context

After updating to latest Asp.Net Identity (from 1.0 to 2.0) i got an exception on CRUD of a user from DB:
The entity type User is not part of the model for the current context
// for example here:
var manager = Container.Resolve<UserManager<User>>();
IdentityResult result = manager.Create(user, "Test passwd");
public class User : IdentityUser
{
// Some properties
}
public class AppDbContext : IdentityDbContext<User>
{
static AppDbContext()
{
// Without this line, EF6 will break.
Type type = typeof (SqlProviderServices);
}
public AppDbContext()
: this("DBName")
{
}
public AppDbContext(string connectionString)
: base(connectionString)
{
}
...
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
modelBuilder.Entity<User>()
.HasMany(c => c.SkippedTopics)
.WithMany(i => i.Users)
.Map(t =>
t.MapLeftKey("User_Id")
.MapRightKey("Topic_Id")
.ToTable("UserSkippedTopics"));
...
}
Before the update the code worked correctly. Any ideas?
UPDATE
The problem was in Unity configuration for UserManager types.
Fix: i created empty AppUserStore: UserStore and registered it as IUserStore:
public class AppUserStore : UserStore<User>
{
public AppUserStore(AppDbContext context) : base(context)
{
}
}
...
public static void RegisterTypes(IUnityContainer container)
{
...
_container.RegisterType<AppDbContext, AppDbContext>(new InjectionConstructor());
_container.RegisterType<IUserStore<User>, AppUserStore>();
}
shouldn't there be UserStore somewhere? I mean looks like you're using Unity for DI which I've never used so I don't know how it works but normally to instantiate UserManager you do something like this:
var manager = new UserManager<User>(new UserStore<User>(new AppDbContext()));
The same thing happened to me and it sucks. You need to enable-immigration and update your database from the Nuget manager console.
Check out this blog. Link is here
This just happened to me also when upgrading from Identity 1.0 to 2.0. The identity code scaffold by Visual Studio (such as the AccountController, ApplicationOAuthProvider, Startup.Auth.cs, etc) was generated when I created the project in Asp.Net Identity 1.0. It seems that Visual Studio had an update and now generates slightly different code for Identity 2.0.
The solution for me was to generate a new web app and copy over all the (new) generated code into the project I just upgraded, replacing the old generated code.

Code First Mapping to Database Views

I have been asked to map the ASP.NET Identity classes to existing database Views for read operations, using Stored Procedures for CRUD. There are a number of StackOverflow Questions stating that is possible to map to views, also this question, this one and lastly this one.
I have mapped the classes to the Views as follows-
var applicationUser = modelBuilder.Entity<applicationUser>().HasKey(au => au.Id) //Specify our own View and Stored Procedure names instead of the default tables
.ToTable("User", "Users").MapToStoredProcedures(sp =>
{
sp.Delete(d => d.HasName("spUser_Delete", "Users"));
sp.Insert(i => i.HasName("spUser_Create", "Users"));
sp.Delete(u => u.HasName("spUser_Update", "Users"));
});
Where [Users].[User] is a SQL view retrieving data from the SQL table [Users].[tblUser].
Unfortunately I have had to leave at least one of the classes mapped to a table rather than View as Entity Framework generates the following SQL-
SELECT Count(*)
FROM INFORMATION_SCHEMA.TABLES AS t
WHERE t.TABLE_TYPE = 'BASE TABLE'
AND (t.TABLE_SCHEMA + '.' + t.TABLE_NAME IN ('Users.ApplicationRole','Users.User','Users.AuthenticationToken','Users.UserClaim','Users.UserLogin','Users.UserRole','Users.Department','Users.PasswordResetToken','Users.UserDepartment')
OR t.TABLE_NAME = 'EdmMetadata')
go
Which returns zero as these are Views and not tables.
As a result any attempt to use the UserManager results in the exception-
Value cannot be null. Parameter name: source
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.ArgumentNullException: Value cannot be null.
Parameter name: source
Source Error:
Line 48: if (ModelState.IsValid)
Line 49: {
Line 50: var userAccount = await
UserManager.FindByNameAsync(model.UserName);
Line 51:
Line 52: if (userAccount == null)
Manually changing the query to-
SELECT Count(*)
FROM INFORMATION_SCHEMA.TABLES AS t
WHERE (t.TABLE_SCHEMA + '.' + t.TABLE_NAME IN ('Users.ApplicationRole','Users.User','Users.AuthenticationToken','Users.UserClaim','Users.UserLogin','Users.UserRole','Users.Department','Users.PasswordResetToken','Users.UserDepartment')
OR t.TABLE_NAME = 'EdmMetadata')
go
Returns the correct nine Views and would presumably not cause the error. Simply having one of the classes mapped to a table is sufficient to convince it the database is correct and to carry on as normal.
Is there any way I can persuade Entity Framework to remove the "Is a table" requirement, or assert that the tables do exist and therefore skip this step altogether?
Edit: Following a request, the code for the UserManager is included below-
AccountController.cs
[Authorize]
public class AccountController : Controller
{
public AccountController()
: this(new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationIdentityDbContext())))
{
}
public AccountController(UserManager<ApplicationUser> userManager)
{
UserManager = userManager;
}
public UserManager<ApplicationUser> UserManager { get; private set; }
I have managed to resolve this problem by creating a custom Database Initializer which replaces the default CreateDatabaseIfNotExists initializer. The Codeguru article on Understanding Database Initializers in Entity Framework Code First was enormously helpful in helping me understand what was going on.
Code for solution-
using System.Data.Entity;
namespace NexGen.Data.Identity
{
public class IdentityCustomInitializer : IDatabaseInitializer<ApplicationIdentityDbContext>
{
public void InitializeDatabase(ApplicationIdentityDbContext)
{
return; //Do nothing, database will already have been created using scripts
}
}
}
IdentityManager-
public class ApplicationIdentityDbContext: IdentityDbContext<ApplicationUser>
{
public ApplicationIdentityDbContext() : base("DefaultConnection")
{
Database.SetInitializer(new IdentityCustomInitializer());
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
...
As a result of this code there are no longer any probing queries by Entity Framework attempting to check if the database exists (and failing due to the assumption that tables, rather than views, were mapped) - instead the queries are immediately against the view attempting to retrieve the user data (and then executing a Stored Procedure in the case the initial action was a registration or otherwise updating the user).
please try
[Authorize]
public class AccountController : Controller
{
public AccountController()
{
InitAccountController(new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationIdentityDbContext())))
}
private InitAccountController(UserManager<ApplicationUser> userManager)
{
UserManager = userManager;
}
public UserManager<ApplicationUser> UserManager { get; private set; }
}
some more explanations:
in EF6 code we can see the following function (DatabaseTableChecker.cs):
public bool AnyModelTableExistsInDatabase(
ObjectContext context, DbConnection connection, List<EntitySet> modelTables, string edmMetadataContextTableName)
{
var modelTablesListBuilder = new StringBuilder();
foreach (var modelTable in modelTables)
{
modelTablesListBuilder.Append("'");
modelTablesListBuilder.Append((string)modelTable.MetadataProperties["Schema"].Value);
modelTablesListBuilder.Append(".");
modelTablesListBuilder.Append(GetTableName(modelTable));
modelTablesListBuilder.Append("',");
}
modelTablesListBuilder.Remove(modelTablesListBuilder.Length - 1, 1);
using (var command = new InterceptableDbCommand(
connection.CreateCommand(), context.InterceptionContext))
{
command.CommandText = #"
SELECT Count(*)
FROM INFORMATION_SCHEMA.TABLES AS t
WHERE t.TABLE_TYPE = 'BASE TABLE'
AND (t.TABLE_SCHEMA + '.' + t.TABLE_NAME IN (" + modelTablesListBuilder + #")
OR t.TABLE_NAME = '" + edmMetadataContextTableName + "')";
var executionStrategy = DbProviderServices.GetExecutionStrategy(connection);
try
{
return executionStrategy.Execute(
() =>
{
if (connection.State == ConnectionState.Broken)
{
connection.Close();
}
if (connection.State == ConnectionState.Closed)
{
connection.Open();
}
return (int)command.ExecuteScalar() > 0;
});
}
finally
{
if (connection.State != ConnectionState.Closed)
{
connection.Close();
}
}
}
}
which corresponds to what you discover.
From this function we may says that there is a problem if, and only if, there are/is only views mapped to the model. In this case the initializer considers the database as Existing but Empty, and he tries to create the tables.
This creates problems as there are/is still views in the database with the same name as the tables the initializer wants to create.
So a work around seems to have at least one real table mapped to the context. No need for a custom initializer in this case.
I propose it as an issue : model only mapped to views
From my understanding and tests there is no need to implement an IDatabaseInitializer having an empty InitializeDatabase method like pwdst did.
From what I saw at Understanding Database Initializers in Entity Framework Code First, it is sufficient to call
Database.SetInitializer<ApplicationIdentityDbContext>(null);
when the application is initializing, or better say, before the first time the database will be accessed.
I would not put it inside the ctor of my DbContext class to avoid setting the initializer every time a DbContext instance is created. Instead, I would put it into the application's initialization method or as one of the first statements of the Main() method.
This worked fine for my application using Entity Framework 6.

Resources