公司网站海报怎么做,1688黄页网品种大全2021,广东百度推广的代理商,外贸网站怎样做推广介绍本系列博客文章探讨了如何在ASP.NET Core Web应用程序中实现多租户。这里有很多代码段#xff0c;因此您可以按照自己的示例应用程序进行操作。在此过程的最后#xff0c;没有对应的NuGet程序包#xff0c;但这是一个很好的学习和练习。它涉及到框架的一些“核心”部分。… 介绍本系列博客文章探讨了如何在ASP.NET Core Web应用程序中实现多租户。这里有很多代码段因此您可以按照自己的示例应用程序进行操作。在此过程的最后没有对应的NuGet程序包但这是一个很好的学习和练习。它涉及到框架的一些“核心”部分。在本系列的改篇中我们将解析对租户的请求并介绍访问该租户信息的能力。系列目录第1部分租户解析本篇第2部分租户containers第3部分每个租户的选项配置第4部分每个租户的身份验证附加升级到.NET Core 3.1LTS什么是多租户应用程序它是一个单一的代码库根据访问它的“租户”不同而做出不同的响应您可以使用几种不同的模式例如应用程序级别隔离为每个租户启动一个新网站和相关的依存关系多租户应用都拥有自己的数据库租户使用相同的网站但是拥有自己的数据库多租户应用程序使用多租户数据库租户使用相同的网站和相同的数据库需要注意不要将数据暴露给错误的租户这里有关于每种模式的非常深入的指南。在本系列中我们将探讨多租户应用程序选项。https://docs.microsoft.com/zh-cn/azure/sql-database/saas-tenancy-app-design-patterns多租户应用程序需要什么多租户应用程序需要满足几个核心要求。租户解析从HTTP请求中我们将需要能够确定在哪个租户上下文中运行请求。这会影响诸如访问哪个数据库或使用哪种配置等问题。租户应用程序配置根据加载的租户上下文可能会对应用程序进行不同的配置例如OAuth提供程序的身份验证密钥连接字符串等。租户数据隔离租户将需要能够访问他们的数据以及仅仅访问他们自己的数据。这可以通过在单个数据存储中对数据进行分区或通过使用每个租户的数据存储来实现。无论我们使用哪种模式我们都应该使开发人员在跨租户场景中难以公开数据以避免编码错误。租户解析对于任何多租户应用程序我们都需要能够识别请求在哪个租户下运行但是在我们太兴奋之前我们需要确定查找租户所需的数据。在此阶段我们实际上只需要一个信息即租户标识符。/// summary
/// Tenant information
/// /summary
public class Tenant
{/// summary/// The tenant Id/// /summarypublic string Id { get; set; }/// summary/// The tenant identifier/// /summarypublic string Identifier { get; set; }/// summary/// Tenant items/// /summarypublic Dictionarystring, object Items { get; private set; } new Dictionarystring, object();
}
我们将Identifier根据解析方案策略使用来匹配租户可能是租户的域名例如https://{tenant}.myapplication.com。我们将使用它Id作为对租户的持久引用Identifier可能会更改例如主机域更改。该属性Items仅用于让开发人员在请求管道期间向租户添加其他内容如果他们需要特定的属性或方法他们还可以扩展该类。常见的租户解决策略我们将使用解决方案策略将请求匹配到租户该策略不应依赖任何外部数据来使其变得美观快速。主机头将根据浏览器发送的主机头来推断租户如果所有租户都具有不同的域例如https://host1.example.comhttps://host2.example.com或者https://host3.com您支持自定义域则这是完美的选择。例如如果主机标头是https://host1.example.com我们将Tenant使用Identifier持有值加载host1.example.com。请求路径可以根据路线推断租户例如 https://example.com/host1/...标头值可以根据标头值来推断承租人例如x-tenant: host1如果所有承租人都可以在核心api上访问https://api.example.com并且客户端可以指定要与特定标头一起使用的承租人则这可能很有用。定义租户解析策略为了让应用程序知道使用哪种策略我们应该能够实现ITenantResolutionStrategy将请求解析为租户标识符的服务。public interface ITenantResolutionStrategy
{Taskstring GetTenantIdentifierAsync();
}
在这篇文章中我们将实现一个策略从主机头那里解析租户。/// summary
/// Resolve the host to a tenant identifier
/// /summary
public class HostResolutionStrategy : ITenantResolutionStrategy
{private readonly IHttpContextAccessor _httpContextAccessor;public HostResolutionStrategy(IHttpContextAccessor httpContextAccessor){_httpContextAccessor httpContextAccessor;}/// summary/// Get the tenant identifier/// /summary/// param namecontext/param/// returns/returnspublic async Taskstring GetTenantIdentifierAsync(){return await Task.FromResult(_httpContextAccessor.HttpContext.Request.Host.Host);}
}
租户存储现在我们知道要加载哪个租户该从哪里获取那将需要某种租户存储。我们将需要实现一个ITenantStore接受承租人标识符并返回Tenant信息的。public interface ITenantStoreT where T : Tenant
{TaskT GetTenantAsync(string identifier);
}
我为什么要使泛型存储万一我们想在使用我们库的项目中获得更多特定于应用程序的租户信息我们可以扩展租户使其具有应用程序级别所需的任何其他属性并适当地配置存储如果要针对租户存储连接字符串之类的内容则需要将其放置在安全的地方并且最好使用每个租户模式的选项配置并从诸如Azure Key Vault之类的安全地方加载这些字符串。在这篇文章中为了简单起见我们将为租户存储执行一个硬编码的内存中模拟。/// summary
/// In memory store for testing
/// /summary
public class InMemoryTenantStore : ITenantStoreTenant
{/// summary/// Get a tenant for a given identifier/// /summary/// param nameidentifier/param/// returns/returnspublic async TaskTenant GetTenantAsync(string identifier){var tenant new[]{new Tenant{ Id 80fdb3c0-5888-4295-bf40-ebee0e3cd8f3, Identifier localhost }}.SingleOrDefault(t t.Identifier identifier);return await Task.FromResult(tenant);}
}
与ASP.NET Core管道集成有两个主要组成部分注册你的服务以便可以解析它们重新注册一些中间件以便您可以HttpContext在请求管道中将租户信息添加到当前信息中从而使下游消费者可以使用它注册服务现在我们有一个获取租户的策略以及一个使租户脱离的位置我们需要在应用程序容器中注册这些服务。我们希望该库易于使用因此我们将使用构建器模式来提供积极的服务注册体验。首先我们添加一点扩展以支持.AddMultiTenancy()语法。/// summary
/// Nice method to create the tenant builder
/// /summary
public static class ServiceCollectionExtensions
{/// summary/// Add the services (application specific tenant class)/// /summary/// param nameservices/param/// returns/returnspublic static TenantBuilderT AddMultiTenancyT(this IServiceCollection services) where T : Tenant new TenantBuilderT(services);/// summary/// Add the services (default tenant class)/// /summary/// param nameservices/param/// returns/returnspublic static TenantBuilderTenant AddMultiTenancy(this IServiceCollection services) new TenantBuilderTenant(services);
}
然后我们将让构建器提供“流畅的”扩展。/// summary
/// Configure tenant services
/// /summary
public class TenantBuilderT where T : Tenant
{private readonly IServiceCollection _services;public TenantBuilder(IServiceCollection services){_services services;}/// summary/// Register the tenant resolver implementation/// /summary/// typeparam nameV/typeparam/// param namelifetime/param/// returns/returnspublic TenantBuilderT WithResolutionStrategyV(ServiceLifetime lifetime ServiceLifetime.Transient) where V : class, ITenantResolutionStrategy{_services.TryAddSingletonIHttpContextAccessor, HttpContextAccessor();_services.Add(ServiceDescriptor.Describe(typeof(ITenantResolutionStrategy), typeof(V), lifetime));return this;}/// summary/// Register the tenant store implementation/// /summary/// typeparam nameV/typeparam/// param namelifetime/param/// returns/returnspublic TenantBuilderT WithStoreV(ServiceLifetime lifetime ServiceLifetime.Transient) where V : class, ITenantStoreT{_services.Add(ServiceDescriptor.Describe(typeof(ITenantStoreT), typeof(V), lifetime));return this;}
}
现在在.NET Core Web应用程序ConfigureServices中的StartUp类部分中您可以添加以下内容。services.AddMultiTenancy().WithResolutionStrategyHostResolutionStrategy().WithStoreInMemoryTenantStore();
这是一个很好的开始但接下来您可能会希望支持传递选项例如如果不使用整个域可能会有一个模式从主机中提取tenantId等但它现在可以完成任务。此时您将能够将存储或解析方案策略注入到控制器中但这有点低级。您不想在要访问租户的任何地方都必须执行这些解决步骤。接下来让我们创建一个服务以允许我们访问当前的租户对象。/// summary
/// Tenant access service
/// /summary
/// typeparam nameT/typeparam
public class TenantAccessServiceT where T : Tenant
{private readonly ITenantResolutionStrategy _tenantResolutionStrategy;private readonly ITenantStoreT _tenantStore;public TenantAccessService(ITenantResolutionStrategy tenantResolutionStrategy, ITenantStoreT tenantStore){_tenantResolutionStrategy tenantResolutionStrategy;_tenantStore tenantStore;}/// summary/// Get the current tenant/// /summary/// returns/returnspublic async TaskT GetTenantAsync(){var tenantIdentifier await _tenantResolutionStrategy.GetTenantIdentifierAsync();return await _tenantStore.GetTenantAsync(tenantIdentifier);}
}
并更新构建器以也注册此服务public TenantBuilder(IServiceCollection services)
{services.AddTransientTenantAccessServiceT();_services services;
}
酷酷酷酷。现在您可以通过将服务注入控制器来访问当前租户/// summary
/// A controller that returns a value
/// /summary
[Route(api/values)]
[ApiController]
public class Values : Controller
{private readonly TenantAccessServiceTenant _tenantService;/// summary/// Constructor with required services/// /summary/// param nametenantService/parampublic Values(TenantAccessServiceTenant tenantService){_tenantService tenantService;}/// summary/// Get the value/// /summary/// param namedefinitionId/param/// returns/returns[HttpGet()]public async Taskstring GetValue(Guid definitionId){return (await _tenantService.GetTenantAsync()).Id;}
}
运行您应该会看到根据URL返回的租户ID。接下来我们可以添加一些中间件以将当前的Tenant注入到HttpContext中这意味着我们可以在可以访问HttpContext的任何地方获取Tenant从而更加方便。这将意味着我们不再需要大量地注入TenantAccessService。注册中间件ASP.NET Core中的中间件使您可以将一些逻辑放入请求处理管道中。在本例中我们应该在需要访问Tenant信息的任何内容例如MVC中间件之前注册中间件。这很可能需要处理请求的控制器中的租户上下文。首先让我们创建我们的中间件类这将处理请求并将其注入Tenant当前HttpContext-超级简单。internal class TenantMiddlewareT where T : Tenant
{private readonly RequestDelegate next;public TenantMiddleware(RequestDelegate next){this.next next;}public async Task Invoke(HttpContext context){if (!context.Items.ContainsKey(Constants.HttpContextTenantKey)){var tenantService context.RequestServices.GetService(typeof(TenantAccessServiceT)) as TenantAccessServiceT;context.Items.Add(Constants.HttpContextTenantKey, await tenantService.GetTenantAsync());}//Continue processingif (next ! null)await next(context);}
}
接下来我们创建一个扩展类使用它。/// summary
/// Nice method to register our middleware
/// /summary
public static class IApplicationBuilderExtensions
{/// summary/// Use the Teanant Middleware to process the request/// /summary/// typeparam nameT/typeparam/// param namebuilder/param/// returns/returnspublic static IApplicationBuilder UseMultiTenancyT(this IApplicationBuilder builder) where T : Tenant builder.UseMiddlewareTenantMiddlewareT();/// summary/// Use the Teanant Middleware to process the request/// /summary/// typeparam nameT/typeparam/// param namebuilder/param/// returns/returnspublic static IApplicationBuilder UseMultiTenancy(this IApplicationBuilder builder) builder.UseMiddlewareTenantMiddlewareTenant();
}
最后我们可以注册我们的中间件这样做的最佳位置是在中间件之前例如MVC可能需要访问Tenant信息的地方。app.UseMultiTenancy();
app.UseMvc()
现在Tenant它将位于items集合中但我们并不是真的要强迫开发人员找出将其存储在哪里记住类型需要对其进行转换等。因此我们将创建一个不错的扩展方法来提取列出当前的租户信息。/// summary
/// Extensions to HttpContext to make multi-tenancy easier to use
/// /summary
public static class HttpContextExtensions
{/// summary/// Returns the current tenant/// /summary/// typeparam nameT/typeparam/// param namecontext/param/// returns/returnspublic static T GetTenantT(this HttpContext context) where T : Tenant{if (!context.Items.ContainsKey(Constants.HttpContextTenantKey))return null;return context.Items[Constants.HttpContextTenantKey] as T;}/// summary/// Returns the current Tenant/// /summary/// param namecontext/param/// returns/returnspublic static Tenant GetTenant(this HttpContext context){return context.GetTenantTenant();}
}
现在我们可以修改我们的Values控制器演示使用当前的HttpContext而不是注入服务。/// summary
/// A controller that returns a value
/// /summary
[Route(api/values)]
[ApiController]
public class Values : Controller
{/// summary/// Get the value/// /summary/// param namedefinitionId/param/// returns/returns[HttpGet()]public async Taskstring GetValue(Guid definitionId){return await Task.FromResult(HttpContext.GetTenant().Id);}
}
如果运行您将得到相同的结果????我们的应用程序是“租户感知”的。这是一个重大的里程碑。‘加个餐’租户上下文访问者在ASP.NET Core中可以使用IHttpContextAccessor访问服务内的HttpContext为了开发人员提供对租户信息的熟悉访问模式我们可以创建ITenantAccessor服务。首先定义一个接口public interface ITenantAccessorT where T : Tenant
{T Tenant { get; }
}
然后实现public class TenantAccessorT : ITenantAccessorT where T : Tenant
{private readonly IHttpContextAccessor _httpContextAccessor;public TenantAccessor(IHttpContextAccessor httpContextAccessor){_httpContextAccessor httpContextAccessor;}public T Tenant _httpContextAccessor.HttpContext.GetTenantT();
}
现在如果下游开发人员想要向您的应用程序添加一个需要访问当前租户上下文的服务他们只需以与使用IHttpContextAccessor完全相同的方式注入ITenantAccessorT⚡⚡只需将该TenantAccessServiceT类标记为内部类这样就不会在我们的程序集之外错误地使用它。小结在这篇文章中我们研究了如何将请求映射到租户。我们将应用程序容器配置为能够解析我们的租户服务甚至创建了ITenantAccessor服务以允许在其他服务如IHttpContextAccessor内部访问该租赁者。我们还编写了自定义中间件将当前的租户信息注入到HttpContext中以便下游中间件可以轻松访问它并创建了一个不错的扩展方法以便您可以像HttpContext.GetTenant一样轻松地获取当前的Tenant。在下一篇文章中我们将研究按租户隔离数据访问。在本系列的下一篇文章中我们将介绍如何在每个租户的基础上配置服务以便我们可以根据活动的租户解析不同的实现。