Clean up the identity project and make it use services common
This commit is contained in:
parent
7027967568
commit
acd9a6d04b
@ -1,7 +1,11 @@
|
|||||||
global using Azure.Core;
|
global using System;
|
||||||
global using Azure.Identity;
|
global using System.Collections.Generic;
|
||||||
global using HealthChecks.UI.Client;
|
global using System.ComponentModel.DataAnnotations;
|
||||||
global using IdentityModel;
|
global using System.IdentityModel.Tokens.Jwt;
|
||||||
|
global using System.Linq;
|
||||||
|
global using System.Security.Claims;
|
||||||
|
global using System.Text.RegularExpressions;
|
||||||
|
global using System.Threading.Tasks;
|
||||||
global using Duende.IdentityServer;
|
global using Duende.IdentityServer;
|
||||||
global using Duende.IdentityServer.Configuration;
|
global using Duende.IdentityServer.Configuration;
|
||||||
global using Duende.IdentityServer.Events;
|
global using Duende.IdentityServer.Events;
|
||||||
@ -10,53 +14,33 @@ global using Duende.IdentityServer.Models;
|
|||||||
global using Duende.IdentityServer.Services;
|
global using Duende.IdentityServer.Services;
|
||||||
global using Duende.IdentityServer.Stores;
|
global using Duende.IdentityServer.Stores;
|
||||||
global using Duende.IdentityServer.Validation;
|
global using Duende.IdentityServer.Validation;
|
||||||
|
global using IdentityModel;
|
||||||
global using Microsoft.AspNetCore.Authentication;
|
global using Microsoft.AspNetCore.Authentication;
|
||||||
global using Microsoft.AspNetCore.Authorization;
|
global using Microsoft.AspNetCore.Authorization;
|
||||||
global using Microsoft.AspNetCore.Builder;
|
global using Microsoft.AspNetCore.Builder;
|
||||||
global using Microsoft.AspNetCore.Diagnostics.HealthChecks;
|
|
||||||
global using Microsoft.AspNetCore.Hosting;
|
global using Microsoft.AspNetCore.Hosting;
|
||||||
global using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
global using Microsoft.AspNetCore.Http;
|
||||||
global using Microsoft.AspNetCore.Identity;
|
global using Microsoft.AspNetCore.Identity;
|
||||||
global using Microsoft.AspNetCore.Mvc.Rendering;
|
global using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||||
global using Microsoft.AspNetCore.Mvc;
|
global using Microsoft.AspNetCore.Mvc;
|
||||||
global using Microsoft.AspNetCore.Mvc.Filters;
|
global using Microsoft.AspNetCore.Mvc.Filters;
|
||||||
|
global using Microsoft.AspNetCore.Mvc.Rendering;
|
||||||
|
global using Microsoft.EntityFrameworkCore;
|
||||||
global using Microsoft.EntityFrameworkCore.Infrastructure;
|
global using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
global using Microsoft.EntityFrameworkCore.Metadata;
|
global using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
global using Microsoft.EntityFrameworkCore.Migrations;
|
global using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
global using Microsoft.EntityFrameworkCore;
|
|
||||||
|
|
||||||
global using Microsoft.eShopOnContainers.Services.Identity.API;
|
global using Microsoft.eShopOnContainers.Services.Identity.API;
|
||||||
global using Microsoft.eShopOnContainers.Services.Identity.API.Data;
|
|
||||||
global using Microsoft.eShopOnContainers.Services.Identity.API.Configuration;
|
global using Microsoft.eShopOnContainers.Services.Identity.API.Configuration;
|
||||||
|
global using Microsoft.eShopOnContainers.Services.Identity.API.Data;
|
||||||
global using Microsoft.eShopOnContainers.Services.Identity.API.Models;
|
global using Microsoft.eShopOnContainers.Services.Identity.API.Models;
|
||||||
global using Microsoft.eShopOnContainers.Services.Identity.API.Services;
|
global using Microsoft.eShopOnContainers.Services.Identity.API.Services;
|
||||||
global using Microsoft.Extensions.Configuration;
|
global using Microsoft.Extensions.Configuration;
|
||||||
global using Microsoft.Extensions.DependencyInjection;
|
global using Microsoft.Extensions.DependencyInjection;
|
||||||
global using Microsoft.Extensions.Diagnostics.HealthChecks;
|
|
||||||
global using Microsoft.Extensions.Hosting;
|
global using Microsoft.Extensions.Hosting;
|
||||||
global using Microsoft.Extensions.Logging;
|
global using Microsoft.Extensions.Logging;
|
||||||
global using Microsoft.Extensions.Options;
|
global using Microsoft.Extensions.Options;
|
||||||
global using Polly;
|
global using Polly;
|
||||||
global using System.Collections.Generic;
|
global using Services.Common;
|
||||||
global using System.ComponentModel.DataAnnotations;
|
|
||||||
global using System.Data.SqlClient;
|
|
||||||
global using System.IdentityModel.Tokens.Jwt;
|
|
||||||
global using System.Linq;
|
|
||||||
global using System.Security.Claims;
|
|
||||||
global using System.Text.RegularExpressions;
|
|
||||||
global using System.Threading.Tasks;
|
|
||||||
global using System;
|
|
||||||
global using Microsoft.AspNetCore.Http;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,69 +0,0 @@
|
|||||||
namespace Microsoft.AspNetCore.Hosting
|
|
||||||
{
|
|
||||||
public static class IWebHostExtensions
|
|
||||||
{
|
|
||||||
public static bool IsInKubernetes(this IWebHost webHost)
|
|
||||||
{
|
|
||||||
var cfg = webHost.Services.GetService<IConfiguration>();
|
|
||||||
var orchestratorType = cfg.GetValue<string>("OrchestratorType");
|
|
||||||
return orchestratorType?.ToUpper() == "K8S";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IWebHost MigrateDbContext<TContext>(this IWebHost webHost, Action<TContext, IServiceProvider> seeder) where TContext : DbContext
|
|
||||||
{
|
|
||||||
var underK8s = webHost.IsInKubernetes();
|
|
||||||
|
|
||||||
using var scope = webHost.Services.CreateScope();
|
|
||||||
var services = scope.ServiceProvider;
|
|
||||||
var logger = services.GetRequiredService<ILogger<TContext>>();
|
|
||||||
var context = services.GetService<TContext>();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
logger.LogInformation("Migrating database associated with context {DbContextName}", typeof(TContext).Name);
|
|
||||||
|
|
||||||
if (underK8s)
|
|
||||||
{
|
|
||||||
InvokeSeeder(seeder, context, services);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var retries = 10;
|
|
||||||
var retry = Policy.Handle<SqlException>()
|
|
||||||
.WaitAndRetry(
|
|
||||||
retryCount: retries,
|
|
||||||
sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
|
|
||||||
onRetry: (exception, timeSpan, retry, ctx) =>
|
|
||||||
{
|
|
||||||
logger.LogWarning(exception, "[{prefix}] Error seeding database (attempt {retry} of {retries})", nameof(TContext), retry, retries);
|
|
||||||
});
|
|
||||||
|
|
||||||
//if the sql server container is not created on run docker compose this
|
|
||||||
//migration can't fail for network related exception. The retry options for DbContext only
|
|
||||||
//apply to transient exceptions
|
|
||||||
// Note that this is NOT applied when running some orchestrators (let the orchestrator to recreate the failing service)
|
|
||||||
retry.Execute(() => InvokeSeeder(seeder, context, services));
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.LogInformation("Migrated database associated with context {DbContextName}", typeof(TContext).Name);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
logger.LogError(ex, "An error occurred while migrating the database used on context {DbContextName}", typeof(TContext).Name);
|
|
||||||
if (underK8s)
|
|
||||||
{
|
|
||||||
throw; // Rethrow under k8s because we rely on k8s to re-run the pod
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return webHost;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void InvokeSeeder<TContext>(Action<TContext, IServiceProvider> seeder, TContext context, IServiceProvider services)
|
|
||||||
where TContext : DbContext
|
|
||||||
{
|
|
||||||
context.Database.Migrate();
|
|
||||||
seeder(context, services);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,43 +4,25 @@
|
|||||||
<TargetFramework>net7.0</TargetFramework>
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
<UserSecretsId>aspnet-eShopOnContainers.Identity-90487118-103c-4ff0-b9da-e5e26f7ab0c5</UserSecretsId>
|
<UserSecretsId>aspnet-eShopOnContainers.Identity-90487118-103c-4ff0-b9da-e5e26f7ab0c5</UserSecretsId>
|
||||||
<DockerComposeProjectPath>..\..\..\..\docker-compose.dcproj</DockerComposeProjectPath>
|
<DockerComposeProjectPath>..\..\..\..\docker-compose.dcproj</DockerComposeProjectPath>
|
||||||
<GenerateErrorForMissingTargetingPacks>false</GenerateErrorForMissingTargetingPacks>
|
|
||||||
<IsTransformWebConfigDisabled>true</IsTransformWebConfigDisabled>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="AspNetCore.HealthChecks.SqlServer" />
|
|
||||||
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" />
|
|
||||||
<PackageReference Include="Duende.IdentityServer.AspNetIdentity" />
|
<PackageReference Include="Duende.IdentityServer.AspNetIdentity" />
|
||||||
<PackageReference Include="Duende.IdentityServer.EntityFramework.Storage" />
|
<PackageReference Include="Duende.IdentityServer.EntityFramework.Storage" />
|
||||||
<PackageReference Include="Duende.IdentityServer.EntityFramework" />
|
<PackageReference Include="Duende.IdentityServer.EntityFramework" />
|
||||||
<PackageReference Include="Duende.IdentityServer.Storage" />
|
<PackageReference Include="Duende.IdentityServer.Storage" />
|
||||||
<PackageReference Include="Duende.IdentityServer" />
|
<PackageReference Include="Duende.IdentityServer" />
|
||||||
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" />
|
|
||||||
<PackageReference Include="Microsoft.ApplicationInsights.DependencyCollector" />
|
|
||||||
<PackageReference Include="Microsoft.ApplicationInsights.Kubernetes" />
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.DataProtection.StackExchangeRedis" />
|
<PackageReference Include="Microsoft.AspNetCore.DataProtection.StackExchangeRedis" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" />
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.HealthChecks" />
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.HealthChecks" />
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" />
|
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" />
|
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" />
|
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" />
|
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.AzureAppServices" />
|
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" />
|
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" />
|
||||||
<PackageReference Include="Microsoft.Web.LibraryManager.Build" />
|
<PackageReference Include="Microsoft.Web.LibraryManager.Build" />
|
||||||
<PackageReference Include="Polly" />
|
<PackageReference Include="Polly" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" />
|
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore.Newtonsoft" />
|
|
||||||
<PackageReference Include="System.Data.SqlClient" />
|
|
||||||
<PackageReference Include="Azure.Extensions.AspNetCore.Configuration.Secrets" />
|
|
||||||
<PackageReference Include="Azure.Identity" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@ -63,6 +45,10 @@
|
|||||||
<None Include="Views\_ViewImports.cshtml" />
|
<None Include="Views\_ViewImports.cshtml" />
|
||||||
<None Include="Views\_ViewStart.cshtml" />
|
<None Include="Views\_ViewStart.cshtml" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\Services.Common\Services.Common.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
<ProjectExtensions>
|
<ProjectExtensions>
|
||||||
<VisualStudio>
|
<VisualStudio>
|
||||||
<UserProperties appsettings_1json__JsonSchema="" />
|
<UserProperties appsettings_1json__JsonSchema="" />
|
||||||
|
@ -1,19 +1,12 @@
|
|||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
if (builder.Configuration.GetValue<bool>("UseVault", false))
|
builder.AddServiceDefaults();
|
||||||
{
|
|
||||||
TokenCredential credential = new ClientSecretCredential(
|
|
||||||
builder.Configuration["Vault:TenantId"],
|
|
||||||
builder.Configuration["Vault:ClientId"],
|
|
||||||
builder.Configuration["Vault:ClientSecret"]);
|
|
||||||
builder.Configuration.AddAzureKeyVault(new Uri($"https://{builder.Configuration["Vault:Name"]}.vault.azure.net/"), credential);
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.Services.AddControllersWithViews();
|
builder.Services.AddControllersWithViews();
|
||||||
builder.Services.AddControllers();
|
|
||||||
builder.Services.AddRazorPages();
|
|
||||||
|
|
||||||
builder.Services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("IdentityDb")));
|
builder.Services.AddDbContext<ApplicationDbContext>(options =>
|
||||||
|
options.UseSqlServer(builder.Configuration.GetConnectionString("IdentityDb")));
|
||||||
|
|
||||||
builder.Services.AddIdentity<ApplicationUser, IdentityRole>()
|
builder.Services.AddIdentity<ApplicationUser, IdentityRole>()
|
||||||
.AddEntityFrameworkStores<ApplicationDbContext>()
|
.AddEntityFrameworkStores<ApplicationDbContext>()
|
||||||
.AddDefaultTokenProviders();
|
.AddDefaultTokenProviders();
|
||||||
@ -35,23 +28,25 @@ builder.Services.AddIdentityServer(options =>
|
|||||||
.AddAspNetIdentity<ApplicationUser>()
|
.AddAspNetIdentity<ApplicationUser>()
|
||||||
.AddDeveloperSigningCredential(); // Not recommended for production - you need to store your key material somewhere secure
|
.AddDeveloperSigningCredential(); // Not recommended for production - you need to store your key material somewhere secure
|
||||||
|
|
||||||
builder.Services.AddAuthentication();
|
|
||||||
builder.Services.AddHealthChecks()
|
builder.Services.AddHealthChecks()
|
||||||
.AddCheck("self", () => HealthCheckResult.Healthy())
|
.AddSqlServer(_ =>
|
||||||
.AddSqlServer(builder.Configuration.GetConnectionString("IdentityDb"),
|
builder.Configuration.GetRequiredConnectionString("IdentityDb"),
|
||||||
name: "IdentityDB-check",
|
name: "IdentityDB-check",
|
||||||
tags: new string[] { "IdentityDB" });
|
tags: new string[] { "IdentityDB" });
|
||||||
|
|
||||||
builder.Services.AddTransient<IProfileService, ProfileService>();
|
builder.Services.AddTransient<IProfileService, ProfileService>();
|
||||||
builder.Services.AddTransient<ILoginService<ApplicationUser>, EFLoginService>();
|
builder.Services.AddTransient<ILoginService<ApplicationUser>, EFLoginService>();
|
||||||
builder.Services.AddTransient<IRedirectService, RedirectService>();
|
builder.Services.AddTransient<IRedirectService, RedirectService>();
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
var pathBase = builder.Configuration["PATH_BASE"];
|
if (!await app.CheckHealthAsync())
|
||||||
if (!string.IsNullOrEmpty(pathBase))
|
|
||||||
{
|
{
|
||||||
app.UsePathBase(pathBase);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
app.UseServiceDefaults();
|
||||||
|
|
||||||
app.UseStaticFiles();
|
app.UseStaticFiles();
|
||||||
|
|
||||||
// This cookie policy fixes login issues with Chrome 80+ using HHTP
|
// This cookie policy fixes login issues with Chrome 80+ using HHTP
|
||||||
@ -61,15 +56,6 @@ app.UseIdentityServer();
|
|||||||
app.UseAuthorization();
|
app.UseAuthorization();
|
||||||
|
|
||||||
app.MapDefaultControllerRoute();
|
app.MapDefaultControllerRoute();
|
||||||
app.MapHealthChecks("/hc", new HealthCheckOptions()
|
|
||||||
{
|
|
||||||
Predicate = _ => true,
|
|
||||||
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
|
|
||||||
});
|
|
||||||
app.MapHealthChecks("/liveness", new HealthCheckOptions
|
|
||||||
{
|
|
||||||
Predicate = r => r.Name.Contains("self")
|
|
||||||
});
|
|
||||||
|
|
||||||
// Apply database migration automatically. Note that this approach is not
|
// Apply database migration automatically. Note that this approach is not
|
||||||
// recommended for production scenarios. Consider generating SQL scripts from
|
// recommended for production scenarios. Consider generating SQL scripts from
|
||||||
|
@ -1,4 +1,10 @@
|
|||||||
{
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
"IsClusterEnv": "False",
|
"IsClusterEnv": "False",
|
||||||
"MvcClient": "http://localhost:5100",
|
"MvcClient": "http://localhost:5100",
|
||||||
"SpaClient": "http://localhost:5104",
|
"SpaClient": "http://localhost:5104",
|
||||||
@ -7,12 +13,6 @@
|
|||||||
"ApplicationInsights": {
|
"ApplicationInsights": {
|
||||||
"InstrumentationKey": ""
|
"InstrumentationKey": ""
|
||||||
},
|
},
|
||||||
"UseVault": false,
|
|
||||||
"Vault": {
|
|
||||||
"Name": "eshop",
|
|
||||||
"ClientId": "your-client-id",
|
|
||||||
"ClientSecret": "your-client-secret"
|
|
||||||
},
|
|
||||||
"TokenLifetimeMinutes": 120,
|
"TokenLifetimeMinutes": 120,
|
||||||
"PermanentTokenLifetimeDays": 365
|
"PermanentTokenLifetimeDays": 365
|
||||||
}
|
}
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<configuration>
|
|
||||||
<!--
|
|
||||||
Configure your application settings in appsettings.json. Learn more at http://go.microsoft.com/fwlink/?LinkId=786380
|
|
||||||
-->
|
|
||||||
<system.webServer>
|
|
||||||
<handlers>
|
|
||||||
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
|
|
||||||
</handlers>
|
|
||||||
<aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="false" hostingModel="InProcess">
|
|
||||||
<environmentVariables>
|
|
||||||
<environmentVariable name="COMPLUS_ForceENC" value="1" />
|
|
||||||
<environmentVariable name="ASPNETCORE_ENVIRONMENT" value="Development" />
|
|
||||||
</environmentVariables>
|
|
||||||
</aspNetCore>
|
|
||||||
</system.webServer>
|
|
||||||
</configuration>
|
|
@ -292,7 +292,12 @@ public static class CommonExtensions
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
var eventBusSection = configuration.GetRequiredSection("EventBus");
|
var eventBusSection = configuration.GetSection("EventBus");
|
||||||
|
|
||||||
|
if (!eventBusSection.Exists())
|
||||||
|
{
|
||||||
|
return hcBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
return eventBusSection["ProviderName"]?.ToLowerInvariant() switch
|
return eventBusSection["ProviderName"]?.ToLowerInvariant() switch
|
||||||
{
|
{
|
||||||
@ -340,7 +345,13 @@ public static class CommonExtensions
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
var eventBusSection = configuration.GetRequiredSection("EventBus");
|
var eventBusSection = configuration.GetSection("EventBus");
|
||||||
|
|
||||||
|
if (eventBusSection.Exists())
|
||||||
|
{
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
|
||||||
if (string.Equals(eventBusSection["ProviderName"], "ServiceBus", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(eventBusSection["ProviderName"], "ServiceBus", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
services.AddSingleton<IServiceBusPersisterConnection>(sp =>
|
services.AddSingleton<IServiceBusPersisterConnection>(sp =>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user