Storing and retrieving values from IDistributedCache (Redis) in .NET Core 2 - asp.net-core

I have an ASP.NET Core2 application. I am using both builtin and Autofac IoC containers. I am setting up all the component registrations in my Startup.cs file. While doing this, I am also setting up my DBContext which inherits from a custom DataContext which in turn inherits from DbContext and implements a custom IDataContextAsync. This DbContext expects a connection string as a constructor parameter.
My problem is that the connection string is stored in the Redis Cache which is an IDistributedCache. The cache is setup in the startup.cs file. The Connection String also is required in the same ConfigureServices method in Startup.cs. So, I don't seem to have access to this cache at this point.
Everything was working when I was using the HttpContext Session to store the connection string. Now that the application is being deployed to a Web farm, I can't use in proc session. We are using Redis for state management. This is where I am having a problem with.
Here is my ConfigureServices method from startup.cs file (unnecessary code removed for brevity).
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMvc()
.AddJsonOptions(op => op.SerializerSettings.ContractResolver = new DefaultContractResolver());
services.AddSession(opt =>
{
opt.IdleTimeout = TimeSpan.FromMinutes(20);
opt.Cookie.Name = "apexportal.RulesSession";
opt.Cookie.HttpOnly = true;
});
services.AddDistributedRedisCache(o =>
{
var host = Configuration.GetValue<string>($"{AppConstants.REDIS}:{AppConstants.REDISHOST}");
var port = Configuration.GetValue<string>($"{AppConstants.REDIS}:{AppConstants.REDISPORT}");
o.Configuration = $"{host}";
o.InstanceName = Configuration.GetValue<string>($"{AppConstants.REDIS}:{AppConstants.REDISNAME}");
});
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
//services.AddTransient<IConnectionStringProvider, ConnectionStringProvider>();
services.AddTransient<IDataContextAsync>(s => new PortalEFContext(GetPortalConnectionString()));
services.AddAuthentication(IISDefaults.AuthenticationScheme);
ContainerBuilder builder = new ContainerBuilder();
builder.Populate( services );
var container = builder.Build();
return container.Resolve<IServiceProvider>();
}
and here is my GetPortalConnectionString() method which is also in the startup.cs file. I want to replace the line accessor.HttpContext.Session.Get() with an injected RedisCache.Get().
private string GetPortalConnectionString()
{
IHttpContextAccessor accessor = new HttpContextAccessor();
//this is where I need to access the RedisCache and access the stored properties
// instead of using HttpContext.Session. But I don't know how to inject the IDistributedCache
// to this spot.
var connString = accessor.HttpContext.Session.Get<string>(AppConstants.SPCONNSTRING);
return connString ?? Configuration.GetConnectionString("PortalEFContext");
}
Later when the user has selected a database to use in the application, I am storing the connectionstring to that database in Redis Cache like so.
Here is my BaseController class which does that.
public abstract class BaseController : Controller
{
//private readonly IRulesEngineService reService;
protected readonly IHttpContextAccessor httpCtxAccessor;
protected readonly IConfiguration config;
private readonly IAuthService authService;
protected readonly IDistributedCache redisCache;
public BaseController(IHttpContextAccessor _httpContext, IConfiguration _config, IAuthService _authService, IDistributedCache _redisCache)
{
//reService = _reService;
httpCtxAccessor = _httpContext;
config = _config;
authService = _authService;
redisCache = _redisCache;
//SetupCurrentWindowsUserAsync();
}
protected async Task<string> SetCurrentDBConnString( int dbId )
{
var currDbId = await GetCurrentDBId();
if ( currDbId == 0 || currDbId != dbId )
{
var envConnStr = config.GetConnectionString( AppConstants.ENVCONNSTRING );
var connStr = await AppHelper.SetCurrentDBConnectionString( dbId, envConnStr );
//httpCtxAccessor.HttpContext.Session.Set<string>( AppConstants.SPCONNSTRING, connStr );
//httpCtxAccessor.HttpContext.Session.Set<int>( AppConstants.CURRDBID, dbId );
await redisCache.SetAsync<string>( AppConstants.SPCONNSTRING, connStr );
await redisCache.SetAsync<int>( AppConstants.CURRDBID, dbId );
await SetupCurrentWindowsUserAsync();
return connStr;
}
return null;
}
}
Can someone please tell me how I can access the Redis cache in my startup.cs file? Thanks.

It's actually very simple. You were almost there already.
Take a closer look at this line in your startup:
services.AddTransient<IDataContextAsync>(s => new PortalEFContext(GetPortalConnectionString()));
See the s parameter in the lambda? This is the DI container of .NET Core called IServiceProvider. This is what you were looking for. Just pass it down into your function and use it there to resolve anything you want.
So, the code will be the following:
public IServiceProvider ConfigureServices(IServiceCollection services)
...
services.AddTransient<IDataContextAsync>(s => new PortalEFContext(GetPortalConnectionString(s))); // <-- pass the container to the function
...
}
private string GetPortalConnectionString(IServiceProvider container)
{
// Here you go:
var cache = container.GetService<IDistributedCache>();
// and now do whatever you want with it.
var connString = cache.Get<string>(AppConstants.SPCONNSTRING);
// BTW, configuration can be resolved from container as well in order to avoid hard dependency on global Configuration object:
var config = container.GetService<IConfiguration>();
return connString ?? config.GetConnectionString("PortalEFContext");
}

Related

Adding console app project to a solution with .Net Core web api

I added reference to the web api project. The added the following to Program.cs
class Program
{
static void Main(string[] args)
{
var builder = new ConfigurationBuilder();
BuildConfig(builder);
var host = Host.CreateDefaultBuilder()
.ConfigureServices((context, services) =>
{
services.AddDbContext<ModelContext>(
options => options.UseOracle(connectionString)); // dbcontext from web api project
services.AddTransient<IEmailReminderService, EmailReminderService>();
}).Build();
var svc = ActivatorUtilities.CreateInstance<EmailReminderService>();
var er = new EmailReminderService();
er.OpenIncidentReminder();
}
static void BuildConfig(IConfigurationBuilder builder)
{
builder.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")}.json", optional: true)
.AddEnvironmentVariables();
_configuration = builder.Build();
}
}
I get System.NullReferenceException: 'Object reference not set to an instance of an object.'
_context was null.
Edit
public class EmailReminderService : IEmailReminderService
{
private ModelContext _context;
//private readonly IExceptionLogService _exceptionLogService;
public EmailReminderService(ModelContext con)
{
_context = con;
//_exceptionLogService = exceptionLogService;
}
public void OpenIncidentReminder()
{
var openIncidents = _context.Incidents.ToList();
}
}
#terodaktil is right about the configuration of the DbContext. You need to configure it properly to specify the provider. See Microsoft's docs about this topic.
var host = Host.CreateDefaultBuilder(args)
.ConfigureServices((context, services) => {
// here i'm using in-memory DB, you'd use SqlServer, Oracle, etc.
services.AddDbContext<AppDbContext>(o => o.UseInMemoryDatabase("db"));
services.AddTransient<IEmailReminderService, EmailReminderService>();
}).Build();
That said, you need to create a scope to resolve a scoped service like DbContext.
Then you can use the scope's service provider directly to resolve IEmailReminderService, or ActivatorUtilities.CreateInstance to create an instance of the concrete implementation EmailReminderService.
using var scope = host.Services.CreateScope();
// now you can resolve the dbcontext
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
// or any service that depends on a scoped service
var reminder = scope.ServiceProvider.GetRequiredService<IEmailReminderService>();
// var reminder = ActivatorUtilities.CreateInstance<EmailReminderService>(scope.ServiceProvider);
reminder.OpenIncidentReminder();
References:
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-5.0#call-services-from-main

How to extract ClaimsPrincipal from AuthenticationStateProvider in Transient middleware service

I have a blazor server web application and a .NET Core worker process, these both use a common class for data access (generic unit of work / generic repository).
In the database I would like to log the user names that are inserting or editing records. To do this I want to inject a ClaimsPrincipal to the shared UoW and Repo classes).
So, I would like to be able to extract the current ClaimsPrincipal in a transient service via dependency injection.
For the worker I can inject a ClaimsPrincipal via the following code;
public static IServiceCollection CreateWorkerClaimsPrincipal(this IServiceCollection services, string workerName)
{
Claim workerNameClaim = new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", workerName);
ClaimsIdentity identity = new ClaimsIdentity(
new System.Security.Claims.Claim[] { workerNameClaim },
"My-Worker-Authentication-Type",
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name",
"role");
ClaimsPrincipal principal = new ClaimsPrincipal(identity);
services.AddTransient<ClaimsPrincipal>(s => principal);
return services;
}
This is working and meets my needs.
For the blazor server web application I need to do something similar.
I believe that the correct way to extract the ClaimsPrincipal is via the AuthenticationStateProvider, however this needs a call to an async method GetAuthenticationStateAsync.
NOTE: I cannot user IHttpContextAccessor as this doesn't work with Azure App Service.
I want something like;
public void ConfigureServices(IServiceCollection services)
{
/// ...
services.AddTransient<ClaimsPrincipal>(); // I think I need to do something here?
/// ...
}
So when I request a ClaimsPrincipal via dependency injection I want to return the user from;
var authState = await AUthenticationStateProvider.GetAuthenticationStateAsync();
return authState.User;
Is this possible?
As is often the way, by working this through into a simple example for a SO post I have found a workable (I think) solution from https://learn.microsoft.com/en-us/aspnet/core/blazor/security/?view=aspnetcore-5.0#implement-a-custom-authenticationstateprovider
NOTE: I'm still not 100% sure if the async init pattern will always resolve the AuthenticationState before the Repository property is called, but its hanging together so far... Just beware of this if you choose to use this code.
I have changed the approach, and instead of trying to resolve ClaimsPrincipal via DI (because AuthenticationStateProvider is not available for a worker process), I have created a custom AuthenticationStateProvider in the worker.
public class WorkerAuthStateProvider : AuthenticationStateProvider
{
private readonly string _workerName;
public WorkerAuthStateProvider(string workerName)
{
_workerName = workerName;
}
public override Task<AuthenticationState> GetAuthenticationStateAsync()
{
var identity = new ClaimsIdentity(new[] {
new Claim(ClaimTypes.Name, _workerName),
}, "My-Worker-Authentication-Type");
ClaimsPrincipal user = new ClaimsPrincipal(identity);
return Task.FromResult(new AuthenticationState(user));
}
}
and then register this in configureServices to resolve for instances of AuthenticationStateProvider in the worker program.cs file (also passing a custom worker process name, so I can use this on all my workers);
services.AddScoped<AuthenticationStateProvider, WorkerAuthStateProvider>(serviceProvider =>
{
return new WorkerAuthStateProvider(Constants.Logging.RoleNames.MYWORKERNAME);
});
The AuthenticationStateProvider already works in the blazor web apps so this allows me to resolve this correctly, in the constructor for my GenericUnitOfWork pattern for data access on both Web and Workers, for example;
private TDbContext _dbContext;
private readonly ILogger<TEntity> _logger;
private GenericRepository<TEntity, TDbContext> _repository;
private ClaimsPrincipal _user;
private readonly AuthenticationStateProvider _authenticationStateProvider;
public GenericUnitOfWork(TDbContext context, ILogger<TEntity> logger, AuthenticationStateProvider authenticationStateProvider)
{
_dbContext = context;
_logger = logger;
_authenticationStateProvider = authenticationStateProvider;
UserInit = InitUserAsync();
}
/// <summary>
/// Async initialisation pattern from https://blog.stephencleary.com/2013/01/async-oop-2-constructors.html
/// </summary>
public Task UserInit { get; private set; }
private async Task InitUserAsync()
{
var authState = await _authenticationStateProvider.GetAuthenticationStateAsync();
_user = authState.User;
}
public IGenericRepository<TEntity, TDbContext> Repository
{
get
{
if (_repository == null)
{
// when accessing the repository, we are expecting to pass the current application claims principal
// however the ClaimsPrincipal is resolved using an Async method from the AuthenticationStateProvider.
// In the event that the Async method has not yet completed we need to throw an exception so we can determine
// if a further async code fix is required.
if (_user == null)
{
throw new InvalidOperationException("Async ClaimsPrincipal has not been loaded from the AuthenticationStateProvider");
}
_repository = new GenericRepository<TEntity, TDbContext>(_dbContext, _logger, _user);
}
return _repository;
}
}

How do you resolve a per-request item using service location in ASP.NET Core 3.1 and Autofac?

I have used this snippet to setup my application:
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
.ConfigureContainer<ContainerBuilder>(Startup.Register)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
public static void Register(ContainerBuilder builder)
{
builder.RegisterType<UserService>().As<IUserServcice>().InstancePerLifetimeScope();
}
and I have utilized it in the manner mentioned below:
public interface IUserServcice
{
public long Tick { get; }
}
public class UserService : IUserServcice
{
private long _tick;
public UserService()
{
_tick = DateTime.Now.Ticks;
}
public long Tick => _tick;
}
public WeatherForecastController(IUserServcice userServcice)
{
// _logger = logger;
iUserServcice = userServcice;
var g = Startup.AutofacContainer.Resolve<IUserServcice>();
tick2 = g.Tick;
}
private async Task Get1()
{
var list = new List<long>();
list.Add(iUserServcice.Tick);
var g=Startup.AutofacContainer.Resolve<IUserServcice>();
list.Add(g.Tick);
list.Add(tick2);
//using (var scope= SorviceLocator.Container.BeginLifetimeScope("t1"))
// {
for (int i = 0; i < 3; i++)
{
await Task.Factory.StartNew(() =>
{
var sr = Startup.AutofacContainer.Resolve<IUserServcice>();
list.Add(sr.Tick);
});
}
// }
}
[HttpGet]
public async Task<IEnumerable<WeatherForecast>> Get()
{
await Get1();
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
})
.ToArray();
}
}
and unfortunately the result of debug is like image uploaded below:
as you can see in the picture item in the top is the result of controller constructor injection and, other items, are inside the controller and my question is that how can I have all these items with the same value.
When you use ASP.NET Core, while you can have Autofac be the backing container, for the most part you give up working with Autofac directly when outside the Startup class. You register your stuff in Startup, but in controllers and elsewhere, it's all standard dependency injection (no Autofac references) and the Microsoft dependency injection abstraction.
This is important because it'll help you Google for answers. Don't look for "How do I do this with Autofac?" - look for "How do I do this in ASP.NET Core?"
First, avoid service location. I see what you're doing, I see what you're getting at... but the fact you need to use service location to demonstrate the issue seems like a red flag.
Now that's out of the way:
What you want is HttpContext.RequestServices. When you have a controller, you'll have the HttpContext and the RequestServices object there is the request lifetime scope. It's backed by Autofac but the interface is the Microsoft interface.
You can read about RequestServices in the Microsoft docs.
private readonly IUserService injected;
public WeatherForecastController(IUserService userService)
{
this.injected = userService;
}
public async Task Get()
{
var located = this.HttpContext.RequestServices.GetService<IUserService>();
// located and injected will be the same instance.
}
If you need to begin a child lifetime scope, again, that's an MS DI thing. You'll need an IServiceScopeFactory. That can be a constructor dependency or you can use service location like you were doing before.
var scopeFactory = this.HttpContext.RequestServices.GetService<IServiceScopeFactory>();
using(var scope = scopeFactory.CreateScope())
{
// Now you have a scope to work with.
}
If you absolutely must get the Autofac lifetime from an IServiceProvider for whatever reason, you can resolve one. Resolving a lifetime scope from a lifetime scope returns itself.
var requestScope = this.HttpContext.RequestServices.GetService<ILifetimeScope>();
But, again, you'll notice everything we're doing here is working with the Microsoft DI abstraction, so when you're looking for answers, I'd recommend looking more broadly and not limiting your search to Autofac. This answer is basically the same regardless of the backing container you use.

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/

Using Ninject with ORMLite

I want to use Ninject with ServiceStack ORMLite but I'm not sure how to configure it.
I have the following in my Repository:
private readonly IDbConnectionFactory _dbFactory;
public TaskRepository(IDbConnectionFactory dbFactory)
{
_dbFactory = dbFactory;
}
public IEnumerable<Task> GetAll()
{
using (IDbConnection _db = _dbFactory.OpenDbConnection())
{
return _db.Select<Task>();
}
}
I'm just not sure how to register it to use my connection string. I have bound OrmLiteConnectionFactory to IDbConnectionFactory like so:
kernel.Bind<IDbConnectionFactory>().To<OrmLiteConnectionFactory>().InScope(x => x.Request);
I have also created a new instance of OrmLiteConnectionFactory in the Configure method in AppHost like so:
var ormLite = new OrmLiteConnectionFactory(
ConfigurationManager.ConnectionStrings["DefaultConnection"]
.ConnectionString, SqlServerDialect.Provider);
But when I try to use a service I get: ConnectionString must be set
Edit
Here is how I have registered OrmLiteConnectionFactory:
Inside Configure I have:
var ormLite = new OrmLiteConnectionFactory(
ConfigurationManager.ConnectionStrings["AngularApp"]
.ConnectionString, SqlServerDialect.Provider);
// Create Tables and Seed Data
CreateSeedData(ormLite);
IKernel kernel = new StandardKernel();
// Register dependencies in method
RegisterDependencies(kernel);
RegisterDependencies looks like:
private void RegisterDependencies(IKernel kernel)
{
kernel.Bind<IDbConnectionFactory>().To<OrmLiteConnectionFactory>()
.InSingletonScope();
kernel.Bind<ITaskRepository>().To<TaskRepository>();
}
IDbConnectionFactory is a db connection factory so it should be a sigleton, i.e. you want to inject the configured instance not create a new one per request, e.g:
kernel.Bind<IDbConnectionFactory>().ToMethod(c =>
new OrmLiteConnectionFactory(
ConfigurationManager.ConnectionStrings["DefaultConnection"]
.ConnectionString, SqlServerDialect.Provider))
.InSingletonScope();