EF Core reusable DbContext - c#

I'm trying to create a reusable base for future web applications made with asp net core.
I created a library that contains a BaseDbContext that inherit from IdentityDbContext:
public class BaseDbContext : IdentityDbContext<ApplicationUser>
{
public BaseDbContext(DbContextOptions options) : base(options)
{
}
}
Inside this library there are some services for login and creation of Users.
Everytime that I will be creating a new WebApplication I will reference the library and I will create a new DbContext like this:
public class ProjectDbContext : BaseDbContext
{
//some generics DBSET
public ProjectDbContext (DbContextOptions<ProjectDbContext> options) : base(options)
{
}
}
And in the startup:
services.AddDbContext<ProjectDbContext>(options =>
{
options.UseSqlServer(connection);
});
Since the service for the login and creation of users require a reference of BaseDbContext, I created a IDbContextFactory inside the base project that will be implemented by the main project like this:
public class ProjectContextFactory : IDbContextFactory
{
private readonly ProjectDbContext _projectDbContext;
public ProjectDbContextFactory(ProjectDbContext remDbContext)
{
_remDbContext = remDbContext;
}
public BaseDbContext GetBaseDbContext()
{
return _projectDbContext;
}
}
This factory will be used inside the base project to get a reference to the BaseDbContext.
Is this a good thing to do? Can this create some kind of problems?

In general, no, this is not a good thing to do.
that will contains the entities that will be used for all web applications
If there's entities that are common to all projects, then those should be factored out completely. In other words, you'd have one project (your base project) with a context like UserContext, which will have your User and Credential entities, and then every other project would have its own separate context that deals with just what it needs. If the other application(s) need to access users, they'd either do so via an instance of UserContext or, better, through a service, such as an API.
That said, it sounds like you're rolling your own auth, which you should emphatically not do. Use Identity. And, if you need to share that between applications, you need a centralized auth provider, such as Identity Server.

Related

What is different between ApiAuthorizationDbContext and IdentityDbContext?

What is different between ApiAuthorizationDbContext<TUser> and IdentityDbContext<TUser> in a Web API project?
DbContext can inherit those but don't know what is different between those
(I'm using .NET 6.0 and Entity Framework Core for Web API project)
Except for what #gbede said about the ApiAuthorizationDbContext<TUser> usage, ApiAuthorizationDbContext<TUser> force TUser to extends IdentityUser instead of IdentityUser<TKey>. This means it is impossible to use Identity Server on application with ApplicationUser : IdentityUser<Guid>(or anything different from IdentityUser<string>).
IdentityDbContext can work with different IdentityUser<TKey>, you can customize your model like:
public class AppUser:IdentityUser<Guid>
{
//add any additional properties
}
Then use the DbContext like:
public class MvcProjContext : IdentityDbContext<AppUser,IdentityRole<Guid>,Guid>
{
public MvcProjContext (DbContextOptions<MvcProjContext> options)
: base(options)
{
}
}
As you can see in the source code, or in the documentation, ApiAuthorizationDbContext inherits from
IdentityDbContext and it also implements IPersistedGrantDbContext which
is responsible for storing consent, authorization codes, refresh tokens, and reference tokens.

Blazor Application Managing Authorization in a muli-tiered solution

I have a solution made up of a Blazor application (Server Side app with .Net 5.0) and a second project that is a Class Library (.Net Standard 2.0). The Blazor app handles all aspects of the website, including authentication. I moved all of my database CRUD procedures into the Class Library. The Blazor app authenticates and uses Roles/Claims to authorize different pages. But, how do I extend that authorization to my Class Library, so that I can use the same Class/Function decorations to designate authorized roles/claims? Here is an example of how my application is setup:
Blazor App - Startup.cs
The Blazor app has a reference to the Class Library and corresponding "Using statements". I then inject those classes into the ConfigureServices method, for use in pages.
public void ConfigureServices(IServiceCollection services)
{
//...adding other required services
// Inject class from my Class Library
services.AddTransient<ISqlDataAccess, SqlDataAccess >();
services.AddTransient<IPeopleData, PeopleData>();
}
Here is an example of how I use the injected classes, to pull data from my database. I can implement roles access here, but I also want to implement it in the Class library.
Somepage.Razor
#page "/mysite/people"
#attribute [Authorize(Roles = "admin")]
#using DataAccessLibrary
#using DataAccessLibrary.Models
#inject IPeopleData_db
//html to display data
#code{
//list of the returned data set, using the model, People
List<PeopleModel> people = new List<PeopleModel>();
protected override async Task OnInitializedAsync()
{
people = await _db.GetAllPeople();
}
}
DataAccessLibarary
IPeopleData.cs
public interface IPeopleData
{
Task<PeopleModel> GetAllPeople();
}
PeopleData.cs
public class PeopleData : IPeopleData
{
// ISqlDataAccess is a class that handles basic CRUD calls to the database. Uses Dapper easy object mapping
private readonly ISqlDataAccess _db;
public PeopleData(ISqlDataAccess db)
{
_db = db;
}
public Task<List<PersonModel>> GetAllPeople()
{
string sql = "select * from dbo.People";
return _db.LoadData<PersonModel, dynamic>(sql, new { });
}
}
So, I can add authorization attributes to the Blazor pages, using [Authorize(Roles = "admin")]. But, this does not protect the Class Library that manages all the database connection stuff. How can I extent the role based access control to that library, so that I can use similar decorations to my classes/functions, like [Authorize(Roles = "admin")]?
To answer your question directly: There is no mechanism for adding authorization to standard DotNetCore classes, which is what your library code is. Authorize is applied to components.
You need to structure your application applying "Separation of Concerns" principles to solve your problems.
You need to reconsider:
services.AddTransient<ISqlDataAccess, SqlDataAccess >();
It will almost certainly leak memory like a sieve. A SQL access service should be either Scoped or normally Singleton.

Pass identity user to services

We are developing a web application in which the user can register orders, customers, etc. and later review them. We have services that are used by MVC controllers in order to interface with the web UI.
Now we face the problem of multiple users: each service should be provided the currently authorised user Id, so all operations (CRUD and bussiness logic) will only be allowed for that user id. How is it supposed to be passed?
I am thinking about having a parameter passed to my IDataService (base class for services), which is instantiated by the WhateverController, which in turn has access to the User.Identity.GetUserId() method, BUT as I am using an IoC container (Ninject) I don't know how to do that. I guess that IDataService needs a reference to a IUserInfoProvider, so it can call IUserInfoProvider.GetUserId(). Then I can inject somehow an implementation based on Identity and having the current web context information, pretty much in the same way that the Controller must be instantiated.
Question is: how to get that data?
A simpler solution, of course, would be to do it by hand in each Controller constructor, but there should be a more automatic and elegant way to solve this.
EDIT: After some more reasearch, thanks to the answer of Cuong Le, the question I had to ask was, in fact, "how to inject the UserManager from the current context?".
However, in order to decouple my services layer from MVC, I created an IUserInfoProvider, which provides access to the authenticated user data. The implementation based in Identity and the UserManager lies in the Web UI (MVC) project, so it has a IPrincipal as suggested by Cuong Le, and an ApplicationUserManager, all injected using Ninject.
The following interface abstract the user information from Identity and the UserManager.
public interface IUserInfoProvider<T>
{
string GetUserId();
T GetUserData();
}
Here is the implementation in the MVC project using Identity and UserManager.
public class IdentityUserInfoProvider : IUserInfoProvider<DatosEmpresa>
{
private readonly ApplicationUserManager _userManager;
private readonly IPrincipal _user;
public IdentityUserInfoProvider(ApplicationUserManager userManager, IPrincipal user)
{
_userManager = userManager;
_user = user;
}
public string GetUserId()
{
return _user.Identity.GetUserId();
}
public DatosEmpresa GetUserData()
{
return _userManager.FindById(_user.Identity.GetUserId()).DatosEmpresa;
}
}
And the Ninject configuration bit
kernel.Bind<IUserInfoProvider<DatosEmpresa>>().To<IdentityUserInfoProvider>();
kernel.Bind<IPrincipal>()
.ToMethod(ctx => HttpContext.Current.User)
.InRequestScope();
kernel.Bind<ApplicationUserManager>()
.ToMethod(ctx => HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>())
.InRequestScope();
Then I can use an IUserInfoProvider inside any service object and it gets the correct user.
The simple solution is you can put IPrincipal into NInject Container:
kernel.Bind<IPrincipal>().ToMethod(context => HttpContext.Current.User);
So in your ServiceBase you can inject IPrincipal via either property or contructor, like this:
class ServiceBase
{
[Inject]
public IPrincipal User { get; set; }
}
Now you can get information from this property.

MVC Multitenant Application Instance Scope on Context using Autofac?

Hi everyone I am optimizing my Application and have a Question about it. I have a Design that looks like this:
ApplicationCore -> T4 from ApplicationDatabases Model Generator
ApplicationData -> Respositories which Access the Database Context in ApplicationCore
public class entityRepository<TEntity> : entityRepository<TEntity> where TEntity : class, IEntity
{
protected readonly DatabaseContext dbContext;
protected readonly IDbSet<TEntity> currentTableContext;
public entityRepository(DatabaseContext context)
{
this.DbContext = context;
currentTableContext = context.Set<TEntity>();
}
ApplicationDatabases -> Database created from Model Generator
ApplicationServices -> Services that creates a entityRepository
private readonly entityRepository<Users> _userRepository;
public userService(entityRepository<Users> userRepository)
{
_userRepository = userRepository;
}
What I would like to know if I am allowed to Register the entityRepository as InstancePerTenant in Autofac or should I use another Instance Scope.
builder.Register(context => new DatabaseContext()).InstancePerTenant();
builder.RegisterGeneric(typeof(entityRepository<>)).As(typeof(IentityRepository<>)).InstancePerTenant;
I am doing that at the moment and it works but I am not sure If I would get any Resources or similiar Problems later. I would also be happy if I get some suggestions.
IMHO, I have worked in a project that is multi-tenant, but does not use autofac. I have few points to put forth to you for a discussion
In case of a multi-tenant application and having the user and entity repositories loaded on memory for each tenant, seems like a bit of overhead
In case of a use case wherein a tenant wishes to view his child tenant's data, how can this be achieved
When there is an application load of 50 tenant's what happens to the parallelism and responsiveness to the load from the application point of view.
Kindly think through these use cases and the others that might come up after moving to production and share your thoughts.

How-to inject the Entity Framework DbContext into the ConfigurationBasedRepository of SharpRepository

I really would like to use SharpRepository together with Ninject, but I do not understand how to configure Ninject to share the Entity Framework DbContext between the repositories.
I am using Entity Framework version 5 and Ninject version 3.
Currently I am using Ef5Repository in my source code, but I want to replace it with ConfigurationBasedRepository. But I cannot figure out how to pass (or inject) the EF DbContext to the repositories.
Example (current state):
using SharpRepository.Repository;
public interface IProductRepository : IRepository<Product>
{
}
using SharpRepository.Ef5Repository;
using System.Data.Entity;
// TODO Tightly coupled to Ef5Repository.
public class ProductRepository : Ef5Repository<Product>, IProductRepository
{
// TODO The DbContext has to be injected manually.
public ProductRepository(DbContext context) : base(context)
{
}
// [...]
}
Goal:
using SharpRepository.Repository;
public interface IProductRepository : IRepository<Product>
{
}
public class ProductRepository : ConfigurationBasedRepository<Product, int>, IProductRepository
{
// [...]
}
I've already read the two blog posts SharpRepository: Getting Started and SharpRepository: Configuration, but they both do not help me, since:
The used DIC is StructureMap, not Ninject.
The source code examples are incomplete (e.g. usage of not declared variables).
So my question: Can someone provide me with some source code example how-to to achieve the goal described above (sharing one Entity Framework DbContext instance between all repositories extending ConfigurationBasedRepository)?
First, you will need to install the SharpRepository.Ioc.Ninject NuGet package. There are extension methods in here for hooking up Ninject to handle the loading a generic repository and setting the dependency resolver that SharpRepository uses.
Where ever you are setting up your Ninject binding rules (all the calls to kernel.Bind<>), you will need to add:
kernel.BindSharpRepository();
Next, in your Global.asax, or App_Start code, or your Bootstrapper logic (where ever you are calling application startup code) you will need to add the following:
// kernel is the specific kernel that you are setting up all the binding for
RepositoryDependencyResolver.SetDependencyResolver(new NinjectDependencyResolver(kernel));
This will tell SharpRepository to use this Ninject Kernel when getting a new DbContext.
The last thing to do is to setup the rules for binding for the DbContext itself. If you are in a web application you will most likely want the scope of the DbContext to be per request. I personally don't use Ninject but I found this reference for using InRequestScope. I believe your code would look something like this:
kernel.Bind<DbContext>().To<MyCustomEfContext>().InRequestScope().WithConstructorArgument("connectionString", ConfigurationManager.ConnectionStrings["MyCustomEfContext"].ConnectionString);
Most people won't need this next piece but if you have custom logic in your CustomEfContext (I have an override for logging on calls to SaveChanges() for example), then you'll need to define your custom context type in the configuration file like so:
<repositories>
<repository name="ef5Repository" connectionString="CustomEfContext" cachingStrategy="standardCachingStrategy" dbContextType="My.Data.CustomEfContext, My.Data" factory="SharpRepository.Ef5Repository.Ef5ConfigRepositoryFactory, SharpRepository.Ef5Repository" />
</repositories>
Where dbContextType defines the type of the custom DbContext you are using using the full type, namespace syntax. If you do this then you'll need to set Ninject to Bind on the custom context by changing .Bind<DbContext>() to .Bind<CustomEfContext>(). But like I said normally you can use DbContext directly without an issue.
First of all, the solution provided in the answer by Jeff T works!
I will conclude the steps I took to make Ninject work in a ASP.NET MVC 4 + EF 5 project. It is important to mention that the Specific Repository pattern is implemented via SharpRepository in the following example.
Required software
Install Ninject and "Ninject.MVC3" (which also installs "Ninject.Web.Common") via NuGet.
Install SharpRepository, "SharpRepository for EF5" and "SharpRepository with Ninject IOC" via NuGet.
Define the Repository layer
Create a DbContext derived class, e.g. Domain.EfContext. It is the
"recommended way to work with context".
Declare all required DbSet<T> as public properties, e.g. public DbSet<Product> Products { get; set; }
Declare the following two constructors in the class Domain.EfContext:
public EfContext() : base() {}
public EfContext(string connectionName) : base(connectionName) {}
Define an interface for the Specific Repository, e.g.:
// TODO By extending IRepository, the interface implements default Create-Read-Update-Delete (CRUD) logic.
// We can use "traits" to make the repository more "specific", e.g. via extending "ICanInsert".
// https://github.com/SharpRepository/SharpRepository/blob/master/SharpRepository.Samples/HowToUseTraits.cs
public interface IProjectRepository : IRepository<Project>
{
// TODO Add domain specific logic here.
}
Define a class which is implementing the Specific Repository and inherits from SharpRepository.Repository.ConfigurationBasedRepository<T, TKey>, e.g.:
public class ProductRepository : ConfigurationBasedRepository<Product, int>, IProductRepository
{
// TODO Implement domain specific logic here.
}
Define the Consumer layer
Create a Controller, e.g. Controllers.ProductController.
public class ProductController : Controller
{
private IProductRepository Repository { get; private set; }
// TODO Will be used by the DiC.
public ProductController(IProductRepository repository)
{
this.Repository = repository;
}
}
Set up Dependency Injection (DI) via the Dependency Injection Container (DiC) Ninject
The file App_Start/NinjectWebCommon.cs is automatically created by Ninject.Web.Common and we can load our modules and register our services in the method RegisterServices(IKernel kernel) : void of the class NinjectWebCommon.
Here is the complete source code of that method for the example:
private static void RegisterServices(IKernel kernel)
{
kernel.BindSharpRepository();
RepositoryDependencyResolver.SetDependencyResolver(
new NinjectDependencyResolver(kernel)
);
string connectionString = ConfigurationManager.ConnectionStrings["EfContext"].ConnectionString;
kernel.Bind<DbContext>()
.To<EfContext>()
.InRequestScope()
.WithConstructorArgument("connectionString", connectionString);
kernel.Bind<IProductRepository>().To<ProductRepository>();
}
Define the following sharpRepository section in the Web.config:
<sharpRepository>
<repositories default="ef5Repository">
<repository name="ef5Repository"
connectionString="EfContext"
cachingStrategy="standardCachingStrategy"
dbContextType="Domain.EfContext, Domain"
factory="SharpRepository.Ef5Repository.Ef5ConfigRepositoryFactory, SharpRepository.Ef5Repository"
/>
</repositories>
</sharpRepository>
In addition, the connectionStrings section to make the example complete (I am using SQL Server LocalDB).
<connectionStrings>
<add name="EfContext" providerName="System.Data.SqlClient" connectionString="Data Source=(localdb)\v11.0;Initial Catalog=Domain;Integrated Security=True" />
</connectionStrings>
I hope that this conclusion helps other people to get ASP.NET MVC 4 together with Entity Framework 5 and SharpRepository up and running!
Please leave me a reply if I took one or more unnecessary steps or if you see possibilities to improve the architecture described in the example.
Btw, I had to add the dbContextType attribute to the repository section to make it work (in contrast to the answer of Jeff T).
EDIT (2013-08-28): Striked out unnecessary steps (not required with the latest version of SharpRepository).

Categories

Resources