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.
Related
I'm trying to leverage AddDBContextPool in my ASP Core API project that uses IdentityServer for authentication. The problem is, to use PersistedGrants you need to have the DBContext set up this way:
private readonly IOptions<OperationalStoreOptions> _operationalStoreOptions;
public ApplicationDbContext(
DbContextOptions options,
IOptions<OperationalStoreOptions> operationalStoreOptions) : base(options)
{
_operationalStoreOptions = operationalStoreOptions;
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.ConfigurePersistedGrantContext(_operationalStoreOptions.Value);
}
AddDBContextPool requires only a single constructor that takes only DbContextOptions as a parameter. So I can't inject operationalStoreOptions anymore.
I tried using AddOperationalStore() from the Startup class, but I got the following error:
The entity type 'DeviceFlowCodes' requires a primary key to be
defined. If you intended to use a keyless entity type call
'HasNoKey()'.
Any idea what am I missing?
I ended up manually implementing the IPersistedGrantDbContext interface in my class. The code is below in case this could help anyone else.
public class ApplicationDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, int>, IPersistedGrantDbContext
{
public ApplicationDbContext(DbContextOptions options) : base(options)
{
// No tracking needed as all transactions are disconnected
ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
}
public DbSet<PersistedGrant> PersistedGrants { get; set; } = null!;
public DbSet<DeviceFlowCodes> DeviceFlowCodes { get; set; } = null!;
public Task<int> SaveChangesAsync()
{
return base.SaveChangesAsync();
}
protected override void OnModelCreating(ModelBuilder builder)
{
OperationalStoreOptions options = new OperationalStoreOptions
{
DeviceFlowCodes = new TableConfiguration("DeviceCodes"),
PersistedGrants = new TableConfiguration("PersistedGrants")
};
base.OnModelCreating(builder);
builder.ConfigurePersistedGrantContext(options);
// ... Rest of configuration here
}
}
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>(() => ...));
}
I have created a custom attribute and I am trying to retrieve the value of this custom attribute in asp.net action filter but it seems to be unavailable. What am I doing wrong?
[AttributeUsage(AttributeTargets.Method, Inherited = true)]
public sealed class MyCustomAttribute : Attribute
{
MyCustomAttribute(string param)
{
}
}
public class MyCustomActionFilter : IActionFilter
{
public void OnActionExecuted(ActionExecutedContext context)
{
throw new NotImplementedException();
}
public void OnActionExecuting(ActionExecutingContext context)
{
// unable to find my custom attribute here under context.Filters or anywhere else.
}
}
[HttpGet]
[MyCustomAttribute ("test123")]
public async Task<Details> GetDetails(
{
}
What you want to achieve is a little more complicated if you want to do it yourself (ie. reflecting attribute value from method of Controller).
I would recommend using built-in attribute filters from ASP.NET Core (more in ASP.NET Core documentation), in your example:
public class MyCustomActionAttribute : ActionFilterAttribute
{
private readonly string param;
public MyCustomActionAttribute(string param)
{
this.param = param;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
var paramValue = param;
base.OnActionExecuting(context);
}
}
and annotating your controller action like this:
[HttpGet]
[MyCustomAction("test123")]
public async Task<Details> GetDetails()
{
}
I have read through the documentation on the different ways to setup and access configuration in .Net Core 2.1 and also the options pattern that seems to be recommended (https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-2.1). However, I can't seem to get what I want working:
I have done the following:
AppSettings:
{
"ConnectionStrings": {
"DefaultConnStr": "Server=(localdb)\\MSSQLLocalDB;Database=_CHANGE_ME;Trusted_Connection=True;MultipleActiveResultSets=true;Integrated Security=true",
"AW2012ConnStr": "Server=localhost;Database=AW2012;Trusted_Connection=True;MultipleActiveResultSets=true;Integrated Security=true"
}
}
MyConfig:
public class MyConfig
{
public string AWConnStr { get; }
public string DefaultConnStr { get; }
}
Startup:
public class Startup
{
public IConfiguration _config { get; set; }
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);
_config = builder.Build();
}
public void ConfigureServices(IServiceCollection services)
{
services.AddOptions();
//add config to services for dependency injection
//services.AddTransient<IMyConfig, MyConfig>();
//services.AddScoped<IMyConfig, MyConfig>();
var section = _config.GetSection("ConnectionStrings");
services.Configure<MyConfig>(section);
}
private static void HandleGetData(IApplicationBuilder app)
{
//DataHelper dataHelper = new DataHelper(_dataHelper);
var _dataHelper = app.ApplicationServices.GetService<DataHelper>();
app.Run(async context =>
{
//await context.Response.WriteAsync("<b>Get Data</b>");
//await context.Response.WriteAsync(dataHelper.GetCompetitions(context.Request.QueryString.ToString()));
await context.Response.WriteAsync(_dataHelper.GetCompetitions(context.Request.QueryString.ToString()));
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.Map("/Route1", HandleRoute1);
app.Map("/Route2", HandleRoute2);
app.Map("/GetData", HandleGetData);
app.Run(async (context) =>
{
await context.Response.WriteAsync("Non Mapped Default");
});
}
}
I would like to then access the configuration in any class anywhere in my code. So for example I have the following class where I would like to just read the configuration information:
public interface IDataHelper
{
string GetCompetitions(string val);
}
public class DataHelper : IDataHelper
{
private readonly MyConfig _settings;
public DataHelper(IOptions<MyConfig> options)
{
_settings = options.Value;
}
public string GetCompetitions( string queryStringVals)
{
return _settings.AWConnStr;
}
}
As shown above in my Startup class I then want to access/call something in the HandleGetData function in my startup, so that when I browse to the following route: http://localhost:xxxxx/getdata I get back the response from the Something.GetData function.
Is this correct? The problem I'm having is that when I create an instance of class Something, it is requiring me to pass in the configuration object, but doesn't that defeat the purpose of injecting it. How should I be setting this up to work similar to how DBContext gets the context injected with the configuration options. And what's the difference between services.AddTransient and services.AddScoped? I've seen both as a way to register the service.
I would say that in .Net Core application you shouldn't pass instance of IConfiguration to your controllers or other classes. You should use strongly typed settings injected through IOtions<T> instead. Applying it to your case, modify MyConfig class (also property names should match names in config, so you have to rename either config (DefaultConnection->DefaultConnStr, AW2012ConnStr->AWConnStr or properies vice versa):
public class MyConfig
{
public string AWConnStr { get; set; }
public string DefaultConnStr { get; set; }
}
Register it:
public void ConfigureServices(IServiceCollection services)
{
// in case config properties specified at root level of config file
// services.Configure<MyConfig>(Configuration);
// in case there are in some section (seems to be your case)
var section = Configuration.GetSection("ConnectionStrings");
services.Configure<MyConfig>(section);
}
Inject it to required service:
public class MyService
{
private readonly MyConfig _settings;
public MyService(IOptions<MyConfig> options)
{
_settings = options.Value;
}
}
And what's the difference between services.AddTransient and
services.AddScoped? I've seen both as a way to register the service.
Transient lifetime services are created each time they're requested.
Scoped lifetime services are created once per request.
You have to do the same thing for the Something as you did for MyConfig like:
public interface ISomething
{
string GetSomeData();
}
Then:
public class Something : ISomething
{
public IConfiguration _config { get; set; }
public Something(IConfiguration configuration)
{
_config = configuration;
}
public string GetSomeData()
{
return _config["DefaultConnStr"];
}
}
Then in the ConfigureService method of the Startup class as follows:
services.AddScoped<ISomething,Something>();
Then call the GetSomeData() as follows:
public class CallerClass
{
public ISomething _something { get; set; }
public CallerClass(ISomething something)
{
_something = something;
}
public string CallerMethod()
{
return _something.GetSomeData();
}
}
Then:
And what's the difference between services.AddTransient and services.AddScoped? I've seen both as a way to register the service.
Here is the details about this from microsoft:
Service Lifetime details in ASP.NET Core
For Razor Pages using ASP.NET Core is there any way to create a catch-all handler for all verbs instead of having separate OnGet(), OnPost(). The handler will need access to HttpContext and Request objects (not provided in the constructor)
Instead of
public class ExampleModel : PageModel
{
public void OnGet()
{
//do something
}
public void OnPost()
{
//do something
}
}
Something like the following
public class ExampleModel : PageModel
{
public void OnAll()
{
//code executes for POST, PUT, GET, ... VERBS
}
}
Also would work is just something generic that would execute before or after (with context) each request
Also would work is just something generic that would execute before or after (with context) each request
Taking the above into account you probably want to use filters. Declaration:
public class DefaultFilterAttribute : ResultFilterAttribute
{
public override void OnResultExecuted(ResultExecutedContext context)
{
Console.WriteLine("Here we go");
base.OnResultExecuted(context);
}
}
In case you want to see this behavior on a single page only:
[DefaultFilter]
public class IndexModel : PageModel
{
}
In case you need this filter to be applied on all pages (Startup.cs):
services.AddMvcOptions(options =>
{
options.Filters.Add(typeof(DefaultFilterAttribute));
});
If you want to execute a group of commands for all request methods, you can use the constractor of the PageModel:
public class IndexModel : PageModel
{
public IndexModel()
{
// This will be executed first
}
public void OnGet()
{
}
}
New solution
I have an other solution for you. Create a class which will inherit from PageModel where you will catch all the different request methods and call a new virtual method.
public class MyPageModel : PageModel
{
public virtual void OnAll()
{
}
public void OnGet()
{
OnAll();
}
public void OnPost()
{
OnAll();
}
}
Now change your PageModel class so that it will inherit from the new class that you created. In your class you can overrice the OnAll method in order to execute your common code.
public class TestModel : MyPageModel
{
public override void OnAll()
{
// Write your code here
}
}