C
C#3mo ago
Mango

Need to make an IO call to register a service

using System.Net.Http.Headers;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace Erp.Core.Extensions;

public static class ServiceCollectionExtensions
{
/// <summary>
/// Adds the Tenant API for retrieving the <see cref="TenantContext"/>.
/// <br />
/// This is required by the ASP.Net application to run. Requires the following value set in appsettings.json in the Configuration section.
/// - TenantEndpoint
/// </summary>
/// <param name="services"></param>
/// <exception cref="HttpRequestException"></exception>
public static void AddTenantContext(this IServiceCollection services)
{
services.AddScoped(sp =>
{
var cache = sp.GetRequiredService<IMemoryCache>();
var configuration = sp.GetRequiredService<IConfiguration>();
var factory = sp.GetRequiredService<IHttpClientFactory>();
var accessor = sp.GetRequiredService<IHttpContextAccessor>();

var host = accessor.HttpContext!.Request.Host.Host;

return cache.GetOrCreate($"tenant.{host}", cacheEntry =>
{
cacheEntry.SlidingExpiration = TimeSpan.FromHours(8);

var endpoint = configuration.GetValue<string>("Configuration:TenantEndpoint");
ArgumentException.ThrowIfNullOrEmpty(endpoint);

using var client = factory.CreateClient();
var request = new HttpRequestMessage(HttpMethod.Get, $"{endpoint}/api/retrieve-tenant/{host}");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "");

var response = client.Send(request);

if (!response.IsSuccessStatusCode)
throw new HttpRequestException(response.StatusCode.ToString());

var content = response.Content.ToString();
ArgumentException.ThrowIfNullOrEmpty(content);

return new Tenant(Guid.Parse(content));
});
});
}
}
using System.Net.Http.Headers;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace Erp.Core.Extensions;

public static class ServiceCollectionExtensions
{
/// <summary>
/// Adds the Tenant API for retrieving the <see cref="TenantContext"/>.
/// <br />
/// This is required by the ASP.Net application to run. Requires the following value set in appsettings.json in the Configuration section.
/// - TenantEndpoint
/// </summary>
/// <param name="services"></param>
/// <exception cref="HttpRequestException"></exception>
public static void AddTenantContext(this IServiceCollection services)
{
services.AddScoped(sp =>
{
var cache = sp.GetRequiredService<IMemoryCache>();
var configuration = sp.GetRequiredService<IConfiguration>();
var factory = sp.GetRequiredService<IHttpClientFactory>();
var accessor = sp.GetRequiredService<IHttpContextAccessor>();

var host = accessor.HttpContext!.Request.Host.Host;

return cache.GetOrCreate($"tenant.{host}", cacheEntry =>
{
cacheEntry.SlidingExpiration = TimeSpan.FromHours(8);

var endpoint = configuration.GetValue<string>("Configuration:TenantEndpoint");
ArgumentException.ThrowIfNullOrEmpty(endpoint);

using var client = factory.CreateClient();
var request = new HttpRequestMessage(HttpMethod.Get, $"{endpoint}/api/retrieve-tenant/{host}");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "");

var response = client.Send(request);

if (!response.IsSuccessStatusCode)
throw new HttpRequestException(response.StatusCode.ToString());

var content = response.Content.ToString();
ArgumentException.ThrowIfNullOrEmpty(content);

return new Tenant(Guid.Parse(content));
});
});
}
}
15 Replies
Mango
Mango3mo ago
@viceroypenguin | 🦋🐧🌟 here need to get tenantid from a separate service in order to setup the correct context
viceroypenguin
viceroypenguin3mo ago
oh, create a singleton cache wrapper. the wrapper has an async call to get the id. that async method can call cache.getorcreate(), and have the async lambda to pass to getorcreate. also... for the love of all that is holy, use refit instead of whatever that mess is
Mango
Mango3mo ago
straight paste into discord whats refit
viceroypenguin
viceroypenguin3mo ago
https://github.com/reactiveui/refit make your life infinitely easier when making calls to a rest api
Mango
Mango3mo ago
this sounds weird but ill try it
public class CacheWrapper(IMemoryCache cache)
{
public async ValueTask<T> GetOrCreateAsync<T>(object key, Func<T> factory)
{
return cache.GetOrCreate(key, cacheEntry =>
{
cacheEntry.SlidingExpiration = TimeSpan.FromHours(8);

return factory();
})!;
}
}
public class CacheWrapper(IMemoryCache cache)
{
public async ValueTask<T> GetOrCreateAsync<T>(object key, Func<T> factory)
{
return cache.GetOrCreate(key, cacheEntry =>
{
cacheEntry.SlidingExpiration = TimeSpan.FromHours(8);

return factory();
})!;
}
}
that?
viceroypenguin
viceroypenguin3mo ago
yes
Mango
Mango3mo ago
So it’ll be the same CacheWrapper with a new scoped IMemoryCache And I call that inside of AddScoped?
viceroypenguin
viceroypenguin3mo ago
except that factory shouldn't be part of the public api. make the public api just take in the key, and use a protected abstract method that gets the value and then implement TenantContextFactory : CacheWrapper<string> tenantcontextfactory can also take an IServiceScopeFactory, to create a temporary scope in it's Factory() method the temporary scope can give you things like the ihttpclientfactory, to go make api calls
Mango
Mango3mo ago
I don’t know what any of that looks like but I’ll see what I come up with lmao You lost me at make a protected abstract method All I know is the key part is on ever request the host needs to resolve a Guid value The api call is to get it if it is not in cache And since it’s a low volatility data it can stand to stay stale for a long time Is this how one would do subdomain driven multi tenancy?
viceroypenguin
viceroypenguin3mo ago
public class CacheWrapper<T>(IMemoryCache cache)
{
public async ValueTask<T> GetOrCreateAsync(object key)
{
return cache.GetOrCreate(key, cacheEntry =>
{
cacheEntry.SlidingExpiration = TimeSpan.FromHours(8);

// i think you can pass cacheEntry.Key or something here?
return Factory(key);
})!;
}

protected abstract ValueTask<T> Factory(object key);
}

public class TenantContextFactory(
IMemoryCache cache,
IServiceScopeFactory serviceScopeFactory
) : CacheWrapper<string>(cache)
{
public new async ValueTask<T> GetOrCreateAsync(string host)
=> base.GetOrCreateAsync($"tenant.{host}");

public override async ValueTask<string> Factory(object key)
{
using var scope = serviceScopeFactory.CreateScope();
var sp = scope.ServiceProvider;

// note these two lines will change with Refit
var ihcf = sp.GetRequiredService<IHttpClientFactory>();
var client = ihcf.CreateClient();

var value = await client.GetAsync(...<key>...);

return value;
}
}
public class CacheWrapper<T>(IMemoryCache cache)
{
public async ValueTask<T> GetOrCreateAsync(object key)
{
return cache.GetOrCreate(key, cacheEntry =>
{
cacheEntry.SlidingExpiration = TimeSpan.FromHours(8);

// i think you can pass cacheEntry.Key or something here?
return Factory(key);
})!;
}

protected abstract ValueTask<T> Factory(object key);
}

public class TenantContextFactory(
IMemoryCache cache,
IServiceScopeFactory serviceScopeFactory
) : CacheWrapper<string>(cache)
{
public new async ValueTask<T> GetOrCreateAsync(string host)
=> base.GetOrCreateAsync($"tenant.{host}");

public override async ValueTask<string> Factory(object key)
{
using var scope = serviceScopeFactory.CreateScope();
var sp = scope.ServiceProvider;

// note these two lines will change with Refit
var ihcf = sp.GetRequiredService<IHttpClientFactory>();
var client = ihcf.CreateClient();

var value = await client.GetAsync(...<key>...);

return value;
}
}
Mango
Mango3mo ago
Oh I was gonna give it a shot and show you my cursed interpretation of what you said But I’ll try this lol wait why abstract and not virtual for Factory()?
viceroypenguin
viceroypenguin3mo ago
so that CacheWrapper<> doesn't have to implement the method
Mango
Mango3mo ago
oh, i was not aware thats what it did TIl inheritors do implement nice anyone know of a way to get .editorconfig to apply to an entire solution? is it as simple as putting it at the solution level?
viceroypenguin
viceroypenguin3mo ago
yes
Mango
Mango3mo ago
of course its that easy I just realized I was not in chat when I posted that