Retry policy is not working in asp.net core 6 - retrypolicy

`builder.Services.AddHttpClient<IAgentEFTReportTransactionService, AgentEFTReportTransactionService>()
.SetHandlerLifetime(TimeSpan.FromMinutes(2))
.AddTransientHttpErrorPolicy(p => p.WaitAndRetryAsync(3,retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt) * 1)));
ProcessTransaction(builder.Services);
private static void ProcessTransaction(IServiceCollection services)
{
var serviceProvider = services.BuildServiceProvider();
//Adds Agent EFT Report Transaction details
serviceProvider.GetService<IAgentEFTReportTransactionService>().AddAgentEFTReportTransactions();
}`

Related

Hangfire Dashboard Auth inside Web API project

We have an .NET 5.0 Web API project with as frontend an Angular project.
In de Web API we use Hangfire to do some jobs. I'm trying to make it work so that our admins can access the hangfire dashboard to be able to check the jobs. So I followed the documentation to do this (https://docs.hangfire.io/en/latest/configuration/using-dashboard.html). The Owin package does not seem to work with our Web API project so I've tried many other options such as added middleware, without Owen, changing the order of UseAuthentication and others.
The problem is that the HttpContext is always mostly empty and so the User is also empty.
As I see it the problem is that it is a Web API and not a MVC project as you see in many online examples. My knowledge of auth is also not that great so any help is welcome!
Some more information:
We use Azure AD as Authentication service
StartUp
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory, IServiceProvider sp)
{
UseHangfireDashboardCustom(app);
app.UseHangfireServer();
app.UseHangfireDashboard("/hangfire");
app.UseAuthentication();
app.UseAuthorization();
}
private static IApplicationBuilder UseHangfireDashboardCustom(IApplicationBuilder app, string pathMatch = "/hangfire", DashboardOptions options = null, JobStorage storage = null)
{
var services = app.ApplicationServices;
storage = storage ?? services.GetRequiredService<JobStorage>();
options = options ?? services.GetService<DashboardOptions>() ?? new DashboardOptions();
var routes = app.ApplicationServices.GetRequiredService<RouteCollection>();
app.Map(new PathString(pathMatch), x =>
x.UseMiddleware<CustomHangfireDashboardMiddleware>(storage, options, routes));
return app;
}
CustomHangfireDashboardMiddleware
public class CustomHangfireDashboardMiddleware
{
private readonly RequestDelegate _nextRequestDelegate;
private readonly JobStorage _jobStorage;
private readonly DashboardOptions _dashboardOptions;
private readonly RouteCollection _routeCollection;
public CustomHangfireDashboardMiddleware(RequestDelegate nextRequestDelegate,
JobStorage storage,
DashboardOptions options,
RouteCollection routes)
{
_nextRequestDelegate = nextRequestDelegate;
_jobStorage = storage;
_dashboardOptions = options;
_routeCollection = routes;
}
public async Task Invoke(HttpContext httpContext)
{
var aspNetCoreDashboardContext = new AspNetCoreDashboardContext(_jobStorage, _dashboardOptions, httpContext);
var findResult = _routeCollection.FindDispatcher(httpContext.Request.Path.Value);
if (findResult == null)
{
await _nextRequestDelegate.Invoke(httpContext);
return;
}
// Attempt to authenticate against Cookies scheme.
// This will attempt to authenticate using data in request, but doesn't send challenge.
var result = await httpContext.AuthenticateAsync();
if (!result.Succeeded)
{
// Request was not authenticated, send challenge and do not continue processing this request.
await httpContext.ChallengeAsync();
return;
}
if (_dashboardOptions.Authorization.Any(filter => filter.Authorize(aspNetCoreDashboardContext) == false))
{
var isAuthenticated = result.Principal?.Identity?.IsAuthenticated ?? false;
if (isAuthenticated == false)
{
httpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
}
else
{
httpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
}
return;
}
aspNetCoreDashboardContext.UriMatch = findResult.Item2;
await findResult.Item1.Dispatch(aspNetCoreDashboardContext);
}
}

InvalidOperationException: No service for type Microsoft.AspNetCore.Identity.UserManager has been registered error using ASP.NET Identity Server

I am trying to put all my data in one database, including business data and Identity data. So, I have run the migrations on my database and updated my DbContext.
I'm trying to modify my project from the New Project template for specified users in Blazor Web Assembly.
I run the app. It loads the home page. But, when I click Login, I receive the error:
An unhandled exception occurred while processing the request.
InvalidOperationException: No service for type
'Microsoft.AspNetCore.Identity.UserManager`1[MyProject.Server.Models.ApplicationUser]'
has been registered.
In my MyProject.Server Startup.cs file:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<MyContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddDatabaseDeveloperPageExceptionFilter();
services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = false)
.AddRoles<IdentityRole>()
.AddDefaultTokenProviders()
.AddUserManager<UserManager<ApplicationUser>>()
.AddSignInManager<SignInManager<ApplicationUser>>()
.AddEntityFrameworkStores<MyContext>();
services.AddIdentityServer(
options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
}
)
.AddClientStore<ClientStore>()
.AddConfigurationStore<MyContext>(options =>
{
options.ConfigureDbContext = b => b.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
})
.AddOperationalStore<MyContext>(options =>
{
options.ConfigureDbContext = b => b.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
})
.AddApiAuthorization<ApplicationUser, MyContext>();
services.AddAuthentication().AddIdentityServerJwt();
services.AddControllersWithViews();
services.AddRazorPages();
}
There's nothing special in my ApplicationUser class:
public class ApplicationUser : IdentityUser
{
}
And in my _LoginPartial.cshtml:
#using Microsoft.AspNetCore.Identity
#using MyProject.Server.Models
#inject SignInManager<ApplicationUser> SignInManager
#inject UserManager<ApplicationUser> UserManager
I didn't think I was trying to do anything special. The main thing is I am using a customer DbContext that overrides OnModelCreating. That is why I need to define so much of the IdentityServer.
If you are using .Net core 5
add this :
services.AddIdentityCore<ApplicationUser>();
according to the source code here this will register UserManager<ApplicationUser>

EF Core to call database based on parameter in API

I have an API developed in .NET Core with EF Core. I have to serve multiple clients with different data(but the same schema). This is a school application, where every school want to keep their data separately due to competition etc. So we have a database for each school. Now my challenge is, based on some parameters, I want to change the connection string of my dbContext object.
for e.g., if I call api/students/1 it should get all the students from school 1 and so on. I am not sure whether there is a better method to do it in the configure services itself. But I should be able to pass SchoolId from my client application
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<SchoolDataContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("APIConnectionString")));
services.AddScoped<IUnitOfWorkLearn, UnitOfWorkLearn>();
}
11 May 2021
namespace LearnNew
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
//Comenting to implement Mr Brownes Solution
//services.AddDbContext<SchoolDataContext>(options =>
// options.UseSqlServer(
// Configuration.GetConnectionString("APIConnectionString")));
services.AddScoped<IUnitOfWorkLearn, UnitOfWorkLearn>();
services.AddControllers();
services.AddHttpContextAccessor();
services.AddDbContext<SchoolDataContext>((sp, options) =>
{
var requestContext = sp.GetRequiredService<HttpContext>();
var constr = GetConnectionStringFromRequestContext(requestContext);
options.UseSqlServer(constr, o => o.UseRelationalNulls());
});
ConfigureSharedKernelServices(services);
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "LearnNew", Version = "v1" });
});
}
private string GetConnectionStringFromRequestContext(HttpContext requestContext)
{
//Trying to implement Mr Brownes Solution
var host = requestContext.Request.Host;
// Since I don't know how to get the connection string, I want to
//debug the host variable and see the possible way to get details of
//the host. Below line is temporary until the right method is identified
return Configuration.GetConnectionString("APIConnectionString");
}
private void ConfigureSharedKernelServices(IServiceCollection services)
{
ServiceProvider serviceProvider = services.BuildServiceProvider();
SchoolDataContext appDbContext = serviceProvider.GetService<SchoolDataContext>();
services.RegisterSharedKernel(appDbContext);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "LearnNew v1"));
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
You can access the HttpContext when configuring the DbContext like this:
services.AddControllers();
services.AddHttpContextAccessor();
services.AddDbContext<SchoolDataContext>((sp, options) =>
{
var requestContext = sp.GetRequiredService<IHttpContextAccessor>().HttpContext;
var constr = GetConnectionStringFromRequestContext(requestContext);
options.UseSqlServer(constr, o => o.UseRelationalNulls());
});
This code:
var requestContext = sp.GetRequiredService<IHttpContextAccessor>().HttpContext; var constr = GetConnectionStringFromRequestContext(requestContext);
options.UseSqlServer(constr, o => o.UseRelationalNulls());
will run for every request, configuring the connection string based on details from the HttpRequestContext.
If you need to use your DbContext on startup, don't resolve it through DI. Just configure a connection like this:
var ob = new DbContextOptionsBuilder<SchoolDataContext>();
var constr = "...";
ob.UseSqlServer(constr);
using (var db = new Db(ob.Options))
{
db.Database.EnsureCreated();
}
But in production you would normally create all your tenant databases ahead-of-time.

Set a custom SessionStore for ConfigureApplicationCookie without BuildServiceProvider()

I have a .NET Core 3 project (recently upgraded from 2.2) that uses a Redis distributed cache and cookie authentication.
It currently looks something like this:
public void ConfigureServices(IServiceCollection services)
{
// Set up Redis distributed cache
services.AddStackExchangeRedisCache(...);
...
services.ConfigureApplicationCookie(options =>
{
...
// Get a service provider to get the distributed cache set up above
var cache = services.BuildServiceProvider().GetService<IDistributedCache>();
options.SessionStore = new MyCustomStore(cache, ...);
}):
}
The problem is that BuildServiceProvider() causes a build error:
Startup.cs(...): warning ASP0000: Calling 'BuildServiceProvider' from application code results in an additional copy of singleton services being created. Consider alternatives such as dependency injecting services as parameters to 'Configure'.
This doesn't appear to be an option - ConfigureApplicationCookie is in Startup.ConfigureServices and can only configure new services, Startup.Configure can use the new services, but can't override CookieAuthenticationOptions.SessionStore to be my custom store.
I've tried adding services.AddSingleton<ITicketStore>(p => new MyCustomRedisStore(cache, ...)) before ConfigureApplicationCookie, but this is ignored.
Explicitly setting CookieAuthenticationOptions.SessionStore appears to be the only way to get it to use anything other than the local memory store.
Every example I've found online uses BuildServiceProvider();
Ideally I want to do something like:
services.ConfigureApplicationCookieStore(provider =>
{
var cache = provider.GetService<IDistributedCache>();
return new MyCustomStore(cache, ...);
});
Or
public void Configure(IApplicationBuilder app, ... IDistributedCache cache)
{
app.UseApplicationCookieStore(new MyCustomStore(cache, ...));
}
And then CookieAuthenticationOptions.SessionStore should just use whatever I've configured there.
How do I make the application cookie use an injected store?
Reference Use DI services to configure options
If all the dependencies of your custom store are injectable, then just register your store and required dependencies with the service collection and use DI services to configure options
public void ConfigureServices(IServiceCollection services) {
// Set up Redis distributed cache
services.AddStackExchangeRedisCache(...);
//register my custom store
services.AddSingleton<ITicketStore, MyCustomRedisStore>();
//...
//Use DI services to configure options
services.AddOptions<CookieAuthenticationOptions>(IdentityConstants.ApplicationScheme)
.Configure<ITicketStore>((options, store) => {
options.SessionStore = store;
});
services.ConfigureApplicationCookie(options => {
//do nothing
}):
}
If not then work around what is actually registered
For example
//Use DI services to configure options
services.AddOptions<CookieAuthenticationOptions>(IdentityConstants.ApplicationScheme)
.Configure<IDistributedCache>((options, cache) => {
options.SessionStore = new MyCustomRedisStore(cache, ...);
});
Note:
ConfigureApplicationCookie uses a named options instance. - #KirkLarkin
public static IServiceCollection ConfigureApplicationCookie(this IServiceCollection services, Action<CookieAuthenticationOptions> configure)
=> services.Configure(IdentityConstants.ApplicationScheme, configure);
The option would need to include the name when adding it to services.
To implement Redis Tickets in .NET Core 3.0 we did the following which is the above in a bit more of a final form::
services.AddSingleton<ITicketStore, RedisTicketStore>();
services.AddOptions<CookieAuthenticationOptions>(CookieAuthenticationDefaults.AuthenticationScheme)
.Configure<ITicketStore>((options, store) => {
options.SessionStore = store;
});
services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
// ...configure identity server options
}).AddCookie(CookieAuthenticationDefaults.AuthenticationScheme);
Here is a Redis implementation:
public class RedisTicketStore : ITicketStore
{
private const string KeyPrefix = "AuthSessionStore-";
private IDistributedCache cache;
public RedisTicketStore(IDistributedCache cache)
{
this.cache = cache;
}
public async Task<string> StoreAsync(AuthenticationTicket ticket)
{
var guid = Guid.NewGuid();
var key = KeyPrefix + guid.ToString();
await RenewAsync(key, ticket);
return key;
}
public Task RenewAsync(string key, AuthenticationTicket ticket)
{
var options = new DistributedCacheEntryOptions();
var expiresUtc = ticket.Properties.ExpiresUtc;
if (expiresUtc.HasValue)
{
options.SetAbsoluteExpiration(expiresUtc.Value);
}
byte[] val = SerializeToBytes(ticket);
cache.Set(key, val, options);
return Task.FromResult(0);
}
public Task<AuthenticationTicket> RetrieveAsync(string key)
{
AuthenticationTicket ticket;
byte[] bytes = null;
bytes = cache.Get(key);
ticket = DeserializeFromBytes(bytes);
return Task.FromResult(ticket);
}
public Task RemoveAsync(string key)
{
cache.Remove(key);
return Task.FromResult(0);
}
private static byte[] SerializeToBytes(AuthenticationTicket source)
{
return TicketSerializer.Default.Serialize(source);
}
private static AuthenticationTicket DeserializeFromBytes(byte[] source)
{
return source == null ? null : TicketSerializer.Default.Deserialize(source);
}
}
Redis implementation from: https://mikerussellnz.github.io/.NET-Core-Auth-Ticket-Redis/

Access EF DbContext within Google OAuth flow

I have a working Google authentication flow in ASP.NET Core 2.1.
I would like to add some authorization by checking the user's email address against a database when they sign in. How can I access the Entity Framework DbContext here?
public void ConfigureServices(IServiceCollection services)
{
services
.AddDbContext<Database>(options => options.UseSqlServer(Configuration["SqlServer"]));
services
.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(o =>
{
o.LoginPath = "/Auth/SignIn";
o.LogoutPath = "/Auth/SignOut";
o.ExpireTimeSpan = TimeSpan.FromDays(90);
})
.AddGoogle(o =>
{
o.ClientId = Configuration["Auth:Google:ClientId"];
o.ClientSecret = Configuration["Auth:Google:ClientSecret"];
o.Events = new OAuthEvents()
{
OnTicketReceived = async context =>
{
var email = context.Principal.Identity.GetEmail().ToLowerInvariant();
if (/* User not in database */) // <-- How can I access the EF DbContext here?
{
context.Response.Redirect("/Auth/Unauthorised");
context.HandleResponse();
}
}
};
});
services.AddMvc();
/* ... */
}
You can access your HttpContext from context and you can access the IServiceProvider from there.
context.HttpContext.RequestServices.GetService<Database>()