I'm developing .Net 6 API.
My project includes controllers, services and repositories (using dependency injection).
I also added a permission check via middleware:
Program.cs
app.UseMiddleware<AuthMiddleware>();
AuthMiddleware.cs
public class AuthMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<EcmAuthMiddleware> _logger;
private readonly IConfiguration _config;
public AuthMiddleware(RequestDelegate next,
ILogger<AuthMiddleware> logger, IConfiguration config)
{
_next = next;
_logger = logger;
_config = config;
}
public async Task Invoke(HttpContext context, IUserApiService
userApiService)
{
...
context.Items["Instance"] = instance;
await _next(context);
}
}
From here I get the customer (and database) to run the APIs on.
Now I need to get some license information (via external API) from the newly obtained client and store it somewhere.
I tried invoking the call from the controller but would have to repeat it for almost all controllers. So I thought about transferring the call to middleware.
From the call I will have various information that I would like to store for use by the underlying levels: controllers, services and repositories. I'd rather not use the session or coookie.
Can I use only httpcontext or are there other solutions?
context.Items["LicenseInfo"] = licenseInfo;
This information is valid only for the call of an api then it should not be stored (eg Application).
EDIT: GetLicenseInfo() must contains an external call:
string result = await _userApiService.GetUserApiResponseAsString("users/token", HttpMethod.Get, applicationId, token);
CodePudding user response:
This is only an alternative to MiddleWare which might be a better option, might not, it depends on a lot of things (as in most software dev questions).
This could be the factory:
public class LicenseInfoFactory
{
public LicenseInfoFactory(IHttpContextAccessor context)
{
_context = context;
}
private readonly IHttpContextAccessor _context;
public LicenseInfo Build()
{
_context...
// api call to get/build/return LicenseInfo
}
}
And then in your startup:
services.AddHttpContextAccessor();
services.AddSingleton<LicenseInfoFactory>();
services.AddScoped<LicenseInfo>(provider => {
var factory = provider.GetRequiredService<LicenseInfoFactory>();
return factory.Build();
});
Then just inject LicenseInfo
in your controllers/repositories constructors etc:
public MyController(LicenseInfo licenseInfo)
This should only make one api call per scoped request. (this is from memory, syntax might not be exact)
CodePudding user response:
Can I use only httpcontext or are there other solutions?
There's nothing wrong with using HttpContext.Items
for this. It's exactly what HttpContext.Items
is for: attaching contextual data to an HTTP request. With this kind of "dictionary of objects" API, I do like to wrap my own APIs around it for type safety and simplicity:
public static class HttpContextLicenseInfoExtensions
{
public static void SetLicenceInfo(this HttpContext context, LicenseInfo licenseInfo) =>
context.Items[key] = licenseInfo;
public static LicenseInfo? TryGetLicenseInfo(this HttpContext context) =>
context.Items[key] as LicenseInfo;
public static LicenseInfo GetLicenseInfo(this HttpContext context) =>
context.TryGetLicenseInfo() ?? throw new InvalidOperationException("No license info.");
private static readonly string key = Guid.NewGuid().ToString("N");
}
// Example middleware
app.Use(async (context, next) =>
{
context.SetLicenseInfo(licenseInfo);
await next.Invoke();
});
// Example usage
var licenseInfo = HttpContext.GetLicenseInfo();
But if you really want to avoid HttpContext.Items
, you can use AsyncLocal<T>
. You just want to structure the API so that you set the value for a specific scope (I like to return IDisposable
to un-set the value), and then you usually inject an "accessor" to read the current value. Something like this should work (using Disposable
from my disposables library):
public static class AsyncLocalLicenseInfo
{
public static IDisposable Set(LicenseInfo licenseInfo)
{
var originalValue = local.Value;
local.Value = licenseInfo;
return new Disposable(() => local.Value = originalValue);
}
public static LicenseInfo? TryGet() => local.Value;
public static LicenseInfo LicenseInfo => TryGet() ?? throw new InvalidOperationException("No license info.");
private static readonly AsyncLocal<LicenseInfo> local = new();
}
// Example middleware
app.Use(async (context, next) =>
{
using var localValue = AsyncLocalLicenseInfo.Set(licenseInfo);
await next.Invoke();
});
// Example usage
var licenseInfo = AsyncLocalLicenseInfo.LicenseInfo;
If you don't like the static
API, you can hide it behind an "accessor":
// Inject this into downstream types
public interface ILicenseInfoAccessor
{
LicenseInfo LicenseInfo { get; }
}
public sealed class LicenseInfoAccessor : ILicenseInfoAccessor
{
public LicenseInfo LicenseInfo => AsyncLocalLicenseInfo.LicenseInfo;
}
// Example usage
var licenseInfo = licenseInfoAccessor.LicenseInfo;