.NET Core how to DI DbContext to AuthrozationFilter - asp.net-core

like as title
I setting db context on Startup.cs
services.AddDbContext<MyContext>(options => options.UseSqlServer(connectionStr));
and I want using it on AuthrozationFilter constructor like this
public class AuthrozationFilter : Attribute, IAuthorizationFilter
{
private readonly MyContext _db;
public AuthrozationFilter(MyContext db)
{
this._db = db;
}
}
but it doesn't work, how to do that ?

You can use service location to resolve components from the built-in IoC container by using RequestServices.GetService:
public class AuthrozationFilter : Attribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
var dbcontext= context.HttpContext.RequestServices.GetRequiredService<MyContext>();
}
}
Or you can use ServiceFilter/TypeFilter :
public class AuthrozationFilter : IAuthorizationFilter
{
private readonly MyContext _db;
public AuthrozationFilter(MyContext db)
{
this._db = db;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
}
}
And add [TypeFilter(typeof(AuthrozationFilter))] on controllers/actions . Please refer to below documents for filters in asp.net core :
https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-3.1
https://www.devtrends.co.uk/blog/dependency-injection-in-action-filters-in-asp.net-core

Found another way to do it. Under the covers this is wrapping ServiceFilter
Use the AddService api on Action<MvcOptions>
serviceCollection.AddControllers(c => c.Filters.AddService<AuthrozationFilter>())
Need to register your service with dependency injection
serviceCollection.AddScoped<AuthrozationFilter>();
Then inject via the constructor
public class AuthrozationFilter : IAuthorizationFilter
{
private readonly MyContext _db;
public AuthrozationFilter(MyContext db)
{
this._db = db;
}
}
Adds the filter to all controllers. If you need more targeted, probably use ServiceFilterAttribute directly.

Related

how to inject depencies to constructor controller

I'm trying to configure an API which a controller use depency injection to inject an object to this controller
public class BaseAPIController
{
private readonly Repository _repository;
public BaseAPIController(Repository repository)
{
_repository = repository;
}
// some common functions and properties are declared here
}
public class AccountController : BaseAPIController
{
public AccountController(Repository repository) : base(repository)
{ }
}
but it throws an exception that tells "Some services are not able to be constructed..."
I tried a solution that use ILogger<Repository> instead of using Repository instance then this runs properly
public class AccountController : BaseAPIController
{
public AccountController(ILogger<Repository> repository) : base(repository)
{ }
}
the registion service in startup.cs code
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddScoped<IRepository, Repository>();
services.AddSingleton<WeatherForecastController, WeatherForecastController>();
}
and the declaration of Repository class
public interface IRepository
{
void DoSomething1();
void DoSomething2();
void DoSomething3();
}
public class Repository : IRepository
{
public readonly string _connectionString;
public Repository(string connectionString)
{
_connectionString = connectionString;
}
public void DoSomething1() {}
public void DoSomething2() {}
public void DoSomething3() {}
}
How can I archive the configuration above without using ILogger instance
Thanks
This is the registration you made:
services.AddScoped<IRepository, Repository>();
But this is AccountController's constructor:
AccountController(Repository repository)
Notice how AccountController is depending on the concrete type Repository; not on the IRepository interface. Because of this registration, Repository can only be resolved through its IRepository interface, but not directly (that's by MS.DI's design).
The solution, therefore, is to change AccountController's constructor to the following:
AccountController(IRepository repository)
The issue is that DI cannot create an instance of Repository because there is no parameterless constructor. Take a look at the docs for injecting settings rather than requiring a string in the constructor. Add your connection string to your appsettings.json file:
{
"AppSettings": {
"ConnectionString": "<connection_string>"
}
}
In ConfigureServices register your settings class:
public class AppSettings
{
public string ConnectionString;
}
public void ConfigureServices(IServiceCollection services)
{
services.Configure<AppSettings>(Configuration.GetSection(AppSettings));
...
}
Then your Repository class constructor would look like this:
public Repository(IOptions<PositionOptions> options)
{
_connectionString = options.Value.ConnectionString;
}
You also need to inject the interface IRepository, not the concrete class into your controller.
public class BaseAPIController
{
private readonly IRepository _repository;
public BaseAPIController(IRepository repository)
{
_repository = repository;
}
// some common functions and properties are declared here
}

My ApplicationDbContext is null for some reason

I get the error System.NullReferenceException. Object reference not set to an instance of an object. At Club.BLL.MaintenanceBLL.Maintenance.DoMaintenance()
But the context code is the same as on scaffolded pages, so not sure what I need to do. Any help is appreciated.
The code is:
namespace Club.BLL.MaintenanceBLL
{
public class Maintenance
{
private readonly Club.Data.ApplicationDbContext Context;
public Maintenance(ApplicationDbContext context)
IN DEBUG MODE context IS INDICATED
AS OBJECT REFERENCE NOT SET TO AN INSTANCE OF AN OBJECT.
HOW DO I FIX THIS.
{
Context = context;
}
public Maintenance()
{
}
public void DoMaintenance()
{
//Parse Maintenance table and action those items
//where Active=True and ActionDate has passed
//==================================
//Retrieve list of rows in Maintenance table
var maintenances = Context.Maintenance; PROGRAM FAILS ON THIS LINE.
I imagine I am missing something fundamental as I am 'learning by doing'. The lines that call up the DoMaintenance routine are located in root/Pages/Index, which is a scaffolded page. The DoMaintenance routine is called from the following lines in the root/Pages/index.cshtml page:
public void OnGet()
{
Maintenance maintenance = new Maintenance();//Create new instance
maintenance.DoMaintenance();
}
AND startup.cs includes the lines
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity(options =>
options.SignIn.RequireConfirmedAccount = true)
.AddRoles()
.AddEntityFrameworkStores();
ANY HELP WOULD BE APPRECIATED.
Thanks and cheers....Alan
According to your description and codes, I found you have register the dbcontext as a service in your application that means if you want to use it, you should inject the dbcontext into your Maintenance class.
Normally, we will create a service as BLL service in asp.net core.
Then we could register the service in the startup.cs and inject it in the razor page.
I suggest you could try to modify the Maintenance class as below codes shows:
You could create a interface called IMaintenance:
public interface IMaintenance
{
public void DoMaintenance();
}
Then you could let Maintenance inherit IMaintenance as below:
public class Maintenance : IMaintenance
{
private readonly TestDbcontext Context;
public Maintenance(TestDbcontext testDbcontext ) {
Context = testDbcontext;
}
public void DoMaintenance() {
var maintenances = Context.Employees.ToList() ;
}
}
At last, you could register the Maintenance as service in startup.cs ConfigureServices method:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddDbContext<TestDbcontext>(ServiceLifetime.Transient);
services.AddTransient<IMaintenance, Maintenance>();
}
You could directly use the Maintenance in razor pages like below, the asp.net core dependency injection will inject the dbcontext automatically into Maintenance.
public class IndexModel : PageModel
{
private readonly ILogger<IndexModel> _logger;
private readonly IStringLocalizer<IndexModel> _localizer;
private readonly IMaintenance _maintance;
public CustomerModel Customer { get; set; }
public IndexModel(ILogger<IndexModel> logger, IStringLocalizer<IndexModel> localizer, IMaintenance maintance)
{
_logger = logger;
_localizer = localizer;
_maintance = maintance;
}
public void OnGet()
{
_maintance.DoMaintenance()
}
}
namespace Club.Pages
{
[AllowAnonymous]
public class IndexModel : PageModel
{
private readonly IMaintenance _maintenance;
public IndexModel (IMaintenance<IndexModel> maintenance)
{
_maintenance = maintenance;
}
public void OnGet()
{
_maintenance.DoMaintenance();
}
}
}
You might have missed to inject your context and since there's default ctor, everything works fine without setting Context property.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<Context>(() => ...));
}

inject Database Context into Custom Attribute .NET Core

I'm creating ASP.NET Core 3.1 app, using SPA for front end. So I decided to create custom Authentication & Authorization. So I created custom attributes to give out and verify JWTs.
Lets say it looks like this:
[AttributeUsage(AttributeTargets.Method)]
public class AuthLoginAttribute : Attribute, IAuthorizationFilter
{
public async void OnAuthorization(AuthorizationFilterContext filterContext)
{
//Checking Headers..
using (var EF = new DatabaseContext)
{
user = EF.User.Where(p => (p.Email == username)).FirstOrDefault();
}
filterContext.HttpContext.Response.Headers.Add(
"AccessToken",
AccessToken.CreateAccessToken(user));
}
}
Everything was Okay, but my DatabaseContext, looked like this:
public class DatabaseContext : DbContext
{
public DbSet<User> User { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseMySQL("ConnectionString");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//....
}
}
I wanted to take Connection string from Appsettings.json and maybe use Dependency injection. I
Changed Startup.cs to look like this:
//...
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddDbContext<DatabaseContext>(
options => options.UseMySQL(Configuration["ConnectionStrings:ConnectionString"]));
services.Add(new ServiceDescriptor(
typeof(HMACSHA256_Algo), new HMACSHA256_Algo(Configuration)));
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/build";
});
}
//...
Changed Database Context class to this:
public class DatabaseContext : DbContext
{
public DatabaseContext(DbContextOptions<DatabaseContext> options) : base(options) { }
public DbSet<User> User { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
///..
}
}
In Controllers I injected DB context and everything works. It looks like this:
[ApiController]
[Route("API")]
public class APIController : ControllerBase
{
private DatabaseContext EF;
public WeatherForecastController(DatabaseContext ef)
{
EF = ef;
}
[HttpGet]
[Route("/API/GetSomething")]
public async Task<IEnumerable<Something>> GetSomething()
{
using(EF){
//.. this works
}
}
}
But my custom Attribute doesn't work no more. I can't declare new Database context, because it needs DatabaseContextOptions<DatabaseContext> object to declare, so how do I inject DBContext to Attribute as I did to Controller?
This doesn't work:
public class AuthLoginAttribute : Attribute, IAuthorizationFilter
{
private DatabaseContext EF;
public AuthLoginAttribute(DatabaseContext ef)
{
EF = ef;
}
public async void OnAuthorization(AuthorizationFilterContext filterContext)
{
using(EF){
}
}
}
this works with controller, but with attribute complains about there not being constructor with 0 arguments.
What you can do is utilize the RequestServices:
[AttributeUsage(AttributeTargets.Method)]
public class AuthLoginAttribute : Attribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
var dbContext = context.HttpContext
.RequestServices
.GetService(typeof(DatabaseContext)) as DatabaseContext;
// your code
}
}
If you allow me to add two comments to your code:
Try not to use async void because in the event of an exception you will be very confused what is going on.
There is no need to wrap injected DbContext in a using statement like this using(EF) { .. }. You will dispose it early and this will lead to bugs later in the request. The DI container is managing the lifetime for you, trust it.

how to use one dbContext interface or interface<T> for multiple context and add to service collection in asp.net core 3.1

Is it possible use one interface for all dbcontext in enterprise app.I want create one interface and base context with partial and use this for all dbcontext.
public interface IComBaseDbContext : IDisposable // ComDbContext,
{
DbSet<TEntity> Set<TEntity>() where TEntity : class;
void AddRange<TEntity>(IEnumerable<TEntity> entities) where TEntity : class;
void RemoveRange<TEntity>(IEnumerable<TEntity> entities) where TEntity : class;
EntityEntry<TEntity> Entry<TEntity>(TEntity entity) where TEntity : class;...}
public partial class ComBaseDbContext:DbContext
{
public ComBaseDbContext()
{
}
public ComBaseDbContext(DbContextOptions<ComBaseDbContext> options) : base(options)
{
}
}
public partial class ComBaseDbContext : IComBaseDbContext
{
public void AddRange<TEntity>(IEnumerable<TEntity> entities) where TEntity : class
{
Set<TEntity>().AddRange(entities);
}
public void RemoveRange<TEntity>(IEnumerable<TEntity> entities) where TEntity : class
{
Set<TEntity>().RemoveRange(entities);
}...
}
and use in :
public partial class ComDbContext : ComBaseDbContext
{
public ComDbContext()
{
}
public ComDbContext(DbContextOptions<ComBaseDbContext> options) : base(options)
{
}
public virtual DbSet<Address> Address { get; set; }...}
and add ComDbContext to service :
services.AddScoped<IComBaseDbContext, ComDbContext1>();
services.AddScoped<IComBaseDbContext, ComDbContext2>();
when use this pattern ComDbContext2 replace to ComDbContext1 .
how to change code to use this.how to use interface .Is this true?
Option 1 :
If you want to define the context for each DI in controllers, create a generic interface:
public interface IComBaseDbContext<TContext> where TContext : DbContext
{
// ...
}
Implement custom generic context:
public class ComDbContext<TCotnext> : IComBaseDbContext<TContext>
where TContext : DbContext
{
private readonly TContext _context;
public ComDbContextA(TContext context)
{
_context = context ?? throw new NotImplementedException(nameof(context));
}
public void AddRange<TEntity>(IEnumerable<TEntity> entities)
where TEntity : class
{
_context.Set<TEntity>().AddRange(entities);
// ...
}
}
Register in startup:
services.AddScoped(typeof(IComBaseDbContext<>), typeof(ComDbContext<>));
Use in controllers:
public class HomeController : Controller
{
private readonly IComBaseDbContext<AppContextA> _contextA;
public HomeController(IComBaseDbContext<AppContextA> contextA)
{
_contextA = contextA;
}
public IActionResult Index()
{
_context.AddRange<..>(...);
}
}
Option 2 :
If you want to have a common default context, and optionally change it in some controllers, create one generic and one standard interface.
public interface IComBaseDbContext<TContext> : IComBaseDbContext
where TContext : DbContext
{
}
public interface IComBaseDbContext
{
// ...
}
Then register in startup:
// This will use the default AppContextA
service.AddScoped<IComBaseDbContext, ComDbContext<AppContextA>>();
// This can have a generic AppContext
service.AddScoped(typeof(IComBaseDbContext<>), typeof(ComDbContext<>));
Use default context in controllers:
public class HomeController : Controller
{
private readonly IComBaseDbContext _context;
public HomeController(IComBaseDbContext context)
{
_contextA = contextA;
}
public IActionResult Index()
{
_context.AddRange<..>(...);
}
}
Use a genric context:
public class HomeController : Controller
{
private readonly IComBaseDbContext<AppContextB> _contextB;
public HomeController(IComBaseDbContext<AppContextB> contextB)
{
_contextB = contextB;
}
public IActionResult Index()
{
_context.AddRange<..>(...);
}
}

What is the best way to wrap dbContext for DI?

I am thinking something like the following may work ok for injecting dbcontext via constructor to my service layer.... Does anyone out there have a better way?
It seems to work however _context.EntityName etc don't show up in intellisense unless I cast the object to the actual class that inherits from dbcontext.
public interface IContextFactory:IDisposable
{
DbContext Create();
}
public class ContextFactory<TContext> : IContextFactory where TContext : DbContext, new()
{
private DbContext _context;
public DbContext Create()
{
_context = new TContext();
_context.Configuration.LazyLoadingEnabled = true;
return _context;
}
public void Dispose()
{
_context.Dispose();
}
}
As Steven mentioned in his comment, you can just inject the DbContext directly from your Composition Root. Here is an example of how this could work with SimpleInjector.
container.Register<MyDbContext>(
() => new MyDbContext("name=MyDbContext"),
new WebRequestLifestyle(true));
Where MyDbContext is a subclass of DbContext:
public class MyDbContext: DbContext
{
public MyDbContext(string connectionString)
: base(connectionString)
{
this.Configuration.LazyLoadingEnabled = true;
}
/* DbSets<SomeEntity> etc */
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//etc
}
}