我们 demo 应用程序的每个租户都将通过不同的域名进行连接。
ASP.NET Core 将检查传入请求并在 tenants
表中查找域。
您还可以按子域(或您想要的任何其他 scheme)查找租户。
注意 tenant_id
是如何存储在 questions
表中的(通过外键关联)。
ASP.NET Core 项目
使用 dotnet new
从 MVC 模板创建一个新项目:
-
dotnet new mvc -o QuestionExchange
-
cd QuestionExchange
首先使用NuGet安装下面的包:
Microsoft.EntityFrameworkCore.SqlServe
Microsoft.EntityFrameworkCore.Tools
SaasKit.Multitenancy
在appsettings.json添加如下的配置
打开 Startup.cs 文件并将这些行添加到 ConfigureServices 方法中:
services.AddEntityFrameworkSqlServer()
.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("SaasKitContext")));
添加 Tenancy(租赁) 到 App
定义 Entity Framework Core 上下文和模型
在 Models 目录中创建一个 Tenant.cs
文件:
namespace QuestionExchange.Models
{
public class Tenant
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
public string Domain { get; set; }
[Required]
public string Name { get; set; }
public string Description { get; set; }
public DateTimeOffset CreatedAt { get; set; }
public DateTimeOffset UpdatedAt { get; set; }
}
}
紧接着创建一个 Question.cs
文件:
namespace QuestionExchange.Models
{
public class Question
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public Tenant Tenant { get; set; }
public string Title { get; set; }
public int Votes { get; set; }
public DateTimeOffset CreatedAt { get; set; }
public DateTimeOffset UpdatedAt { get; set; }
}
}
接下来,您需要定义一个数据库上下文。在Models文件夹下创建一个名为 AppDbContext.cs
的文件,并添加以下代码:
namespace QuestionExchange.Models
{
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
{
}
public DbSet<Tenant> Tenants { get; set; }
public DbSet<Question> Questions { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
}
然后打开程序包控制台,执行下面的命令:
Add-Migration Init
update-database
我们看到Sql Server中生成了如下的数据库和表:
我们分别在Tenants、Questions表中添加如下数据:
INSERT INTO tenants VALUES ( 'bufferoverflow.local', 'Buffer Overflow', 'Ask anything code-related!', getdate(), getdate()); INSERT INTO tenants VALUES ( 'dboverflow.local', 'Database Questions', 'Figure out why your connection string is broken.', getdate(), getdate());
INSERT INTO questions VALUES (
1,
'How do you build apps in ASP.NET Core?',
1,
getdate(),
getdate());
INSERT INTO questions VALUES (
2,
'Using postgresql for multitenant data?',
2,
getdate(),
getdate());
这样就完成了数据库结构和示例数据和ASP.NET Core设置 ,下一步是向 ASP.NET Core 管道添加多租户支持。
安装 SaasKit
SaasKit 是一款优秀的开源 ASP.NET Core 中间件。
该软件包使您的 Startup
请求管道 租户感知(tenant-aware) 变得容易,
并且足够灵活以处理许多不同的多租户用例。
使用NuGet安装下面的包:
SaasKit.Multitenancy
SaasKit 需要两件事才能工作:租户模型(tenant model)和租户解析器(tenant resolver)。
您已经有了前者(您之前创建的 Tenant
类),因此在项目根目录中创建一个名为 CachingTenantResolver.cs
的新文件:
namespace QuestionExchange
{
public class CachingTenantResolver : MemoryCacheTenantResolver<Tenant>
{
private readonly AppDbContext _context;
public CachingTenantResolver(AppDbContext context, IMemoryCache cache, ILoggerFactory loggerFactory)
:base(cache, loggerFactory)
{
_context = context;
}
protected override async Task<TenantContext<Tenant>> ResolveAsync(HttpContext context)
{
var subdomain = context.Request.Host.Host.ToLower();
var tenant = await _context.Tenants
.FirstOrDefaultAsync(t => t.Domain == subdomain);
if (tenant == null) return null;
return new TenantContext<Tenant>(tenant);
}
protected override MemoryCacheEntryOptions CreateCacheEntryOptions()
=> new MemoryCacheEntryOptions().SetSize(1024).SetAbsoluteExpiration(TimeSpan.FromHours(2));
protected override string GetContextIdentifier(HttpContext context)
=> context.Request.Host.Host.ToLower();
protected override IEnumerable<string> GetTenantIdentifiers(TenantContext<Tenant> context)
=> new string[] { context.Tenant.Domain };
}
}
准备好租户模型(tenant model)和租户解析器(tenant resolver)后,在 ConfigureServices
方法中添加下面的代码:
services.AddMultitenancy<Tenant, CachingTenantResolver>();
接下来,将此行添加到 Configure
方法中,在 UseStaticFiles
下方但在 UseRouting
上方:
Configure
方法代表您的实际请求管道,因此顺序很重要!
更新视图
现在所有部分都已就绪,您可以开始在代码和视图中引用当前租户。
在 Models 目录中创建 QuestionListViewModel.cs
文件:
namespace QuestionExchange.Models
{
public class QuestionListViewModel
{
public IEnumerable<Question> Questions { get; set; }
}
}
打开 Views/Home/Index.cshtml
视图并用这个标记替换整个文件:
@inject Tenant Tenant @model QuestionListViewModel @{ ViewData["Title"] = "Home Page"; } <div class="row"> <div class="col-md-12"> <h1>Welcome to <strong>@Tenant.Name</strong></h1> <h3>@Tenant.Description</h3> </div> </div> <div class="row"> <div class="col-md-12"> <h4>Popular questions</h4> <ul> @foreach (var question in Model.Questions) { <li>@question.Title</li> } </ul> </div> </div>
@inject
指令从 SaasKit 获取当前租户,并且@model
指令告诉 ASP.NET Core,此视图将由模型类QuestionListViewModel支持。
查询数据库
HomeController
负责渲染您刚刚编辑的索引视图。打开它并用下面的代码替换构造函数和Index()
方法:
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
private readonly AppDbContext _context;
private readonly Tenant _currentTenant;
public HomeController(ILogger<HomeController> logger, AppDbContext context, Tenant currentTenant)
{
_logger = logger;
_context = context;
_currentTenant = currentTenant;
}
public async Task<IActionResult> Index()
{
var topQuestions = await _context.Questions.Where(q => q.Tenant.Id == _currentTenant.Id)
.OrderByDescending(q => q.UpdatedAt)
.Take(5).ToArrayAsync();
var viewModel = new QuestionListViewModel
{
Questions = topQuestions
};
return View(viewModel);
}
测试应用程序
您添加到数据库的测试租户与(fake)域 bufferoverflow.local
和 dboverflow.local
相关联。
首先需要修改本地Hosts文件,添加:
127.0.0.1 bufferoverflow.local 127.0.0.1 dboverflow.local
运行cmd(命令行),输入以下命令,刷新DNS:
ipconfig /flushdns
使用 dotnet run
或单击 Visual Studio 中的 Start 启动当前项目,
应用程序将开始侦听 localhost:5000
之类的 URL。
如果您直接访问该 URL,您将看到一个错误,因为您尚未设置任何 默认租户行为。
访问 http://bufferoverflow.local:5000, http://dboverflow.local:5000 可以看到之前插入的测试数据在不同租户下显示不同。
添加更多租户现在只需在 tenants
表中添加更多行即可。