Home > Enterprise >  Where to store license information that I will need at multiple levels?
Where to store license information that I will need at multiple levels?

Time:04-07

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;
  • Related