diff --git a/src/BuildingBlocks/Isolation/Masa.BuildingBlocks.Isolation/IParserProvider.cs b/src/BuildingBlocks/Isolation/Masa.BuildingBlocks.Isolation/IParserProvider.cs index de646ff37..5670e0ee1 100644 --- a/src/BuildingBlocks/Isolation/Masa.BuildingBlocks.Isolation/IParserProvider.cs +++ b/src/BuildingBlocks/Isolation/Masa.BuildingBlocks.Isolation/IParserProvider.cs @@ -7,5 +7,5 @@ public interface IParserProvider { string Name { get; } - Task ResolveAsync(IServiceProvider serviceProvider, string key, Action action); + Task ResolveAsync(HttpContext? httpContext, string key, Action action); } diff --git a/src/BuildingBlocks/Isolation/Masa.BuildingBlocks.Isolation/Middleware/IIsolationMiddleware.cs b/src/BuildingBlocks/Isolation/Masa.BuildingBlocks.Isolation/Middleware/IIsolationMiddleware.cs index 5549b97ef..4c5a77de0 100644 --- a/src/BuildingBlocks/Isolation/Masa.BuildingBlocks.Isolation/Middleware/IIsolationMiddleware.cs +++ b/src/BuildingBlocks/Isolation/Masa.BuildingBlocks.Isolation/Middleware/IIsolationMiddleware.cs @@ -7,5 +7,5 @@ namespace Masa.BuildingBlocks.Isolation; public interface IIsolationMiddleware { - Task HandleAsync(); + Task HandleAsync(HttpContext? httpContext); } diff --git a/src/BuildingBlocks/Isolation/Masa.BuildingBlocks.Isolation/Parser/CookieParserProvider.cs b/src/BuildingBlocks/Isolation/Masa.BuildingBlocks.Isolation/Parser/CookieParserProvider.cs index e27f005e4..8973f42fa 100644 --- a/src/BuildingBlocks/Isolation/Masa.BuildingBlocks.Isolation/Parser/CookieParserProvider.cs +++ b/src/BuildingBlocks/Isolation/Masa.BuildingBlocks.Isolation/Parser/CookieParserProvider.cs @@ -7,9 +7,8 @@ public class CookieParserProvider : IParserProvider { public string Name => "Cookie"; - public Task ResolveAsync(IServiceProvider serviceProvider, string key, Action action) + public Task ResolveAsync(HttpContext? httpContext, string key, Action action) { - var httpContext = serviceProvider.GetRequiredService().HttpContext; if (httpContext?.Request.Cookies.ContainsKey(key) ?? false) { var value = httpContext.Request.Cookies[key]; diff --git a/src/BuildingBlocks/Isolation/Masa.BuildingBlocks.Isolation/Parser/EnvironmentVariablesParserProvider.cs b/src/BuildingBlocks/Isolation/Masa.BuildingBlocks.Isolation/Parser/EnvironmentVariablesParserProvider.cs index 7e50d1da0..7f2070af8 100644 --- a/src/BuildingBlocks/Isolation/Masa.BuildingBlocks.Isolation/Parser/EnvironmentVariablesParserProvider.cs +++ b/src/BuildingBlocks/Isolation/Masa.BuildingBlocks.Isolation/Parser/EnvironmentVariablesParserProvider.cs @@ -7,9 +7,9 @@ public class EnvironmentVariablesParserProvider : IParserProvider { public string Name { get; } = "EnvironmentVariables"; - public Task ResolveAsync(IServiceProvider serviceProvider, string key, Action action) + public Task ResolveAsync(HttpContext? httpContext, string key, Action action) { - string? value = System.Environment.GetEnvironmentVariable(key); + string? value = Environment.GetEnvironmentVariable(key); if (!string.IsNullOrEmpty(value)) { action.Invoke(value); diff --git a/src/BuildingBlocks/Isolation/Masa.BuildingBlocks.Isolation/Parser/FormParserProvider.cs b/src/BuildingBlocks/Isolation/Masa.BuildingBlocks.Isolation/Parser/FormParserProvider.cs index c471328c9..f35bb419c 100644 --- a/src/BuildingBlocks/Isolation/Masa.BuildingBlocks.Isolation/Parser/FormParserProvider.cs +++ b/src/BuildingBlocks/Isolation/Masa.BuildingBlocks.Isolation/Parser/FormParserProvider.cs @@ -7,9 +7,8 @@ public class FormParserProvider : IParserProvider { public string Name => "Form"; - public Task ResolveAsync(IServiceProvider serviceProvider, string key, Action action) + public Task ResolveAsync(HttpContext? httpContext, string key, Action action) { - var httpContext = serviceProvider.GetRequiredService().HttpContext; if (!(httpContext?.Request.HasFormContentType ?? false)) return Task.FromResult(false); diff --git a/src/BuildingBlocks/Isolation/Masa.BuildingBlocks.Isolation/Parser/HeaderParserProvider.cs b/src/BuildingBlocks/Isolation/Masa.BuildingBlocks.Isolation/Parser/HeaderParserProvider.cs index 4149c3165..28870b84e 100644 --- a/src/BuildingBlocks/Isolation/Masa.BuildingBlocks.Isolation/Parser/HeaderParserProvider.cs +++ b/src/BuildingBlocks/Isolation/Masa.BuildingBlocks.Isolation/Parser/HeaderParserProvider.cs @@ -7,9 +7,8 @@ public class HeaderParserProvider : IParserProvider { public string Name => "Header"; - public Task ResolveAsync(IServiceProvider serviceProvider, string key, Action action) + public Task ResolveAsync(HttpContext? httpContext, string key, Action action) { - var httpContext = serviceProvider.GetRequiredService().HttpContext; if (httpContext?.Request.Headers.ContainsKey(key) ?? false) { var value = httpContext.Request.Headers[key].ToString(); diff --git a/src/BuildingBlocks/Isolation/Masa.BuildingBlocks.Isolation/Parser/HttpContextItemParserProvider.cs b/src/BuildingBlocks/Isolation/Masa.BuildingBlocks.Isolation/Parser/HttpContextItemParserProvider.cs index 76dbbfd9a..62c6174e5 100644 --- a/src/BuildingBlocks/Isolation/Masa.BuildingBlocks.Isolation/Parser/HttpContextItemParserProvider.cs +++ b/src/BuildingBlocks/Isolation/Masa.BuildingBlocks.Isolation/Parser/HttpContextItemParserProvider.cs @@ -7,9 +7,8 @@ public class HttpContextItemParserProvider : IParserProvider { public string Name => "Items"; - public Task ResolveAsync(IServiceProvider serviceProvider, string key, Action action) + public Task ResolveAsync(HttpContext? httpContext, string key, Action action) { - var httpContext = serviceProvider.GetRequiredService().HttpContext; if (httpContext?.Items.ContainsKey(key) ?? false) { var value = httpContext.Items[key]?.ToString() ?? string.Empty; diff --git a/src/BuildingBlocks/Isolation/Masa.BuildingBlocks.Isolation/Parser/QueryStringParserProvider.cs b/src/BuildingBlocks/Isolation/Masa.BuildingBlocks.Isolation/Parser/QueryStringParserProvider.cs index c76c2b904..0dd1ad977 100644 --- a/src/BuildingBlocks/Isolation/Masa.BuildingBlocks.Isolation/Parser/QueryStringParserProvider.cs +++ b/src/BuildingBlocks/Isolation/Masa.BuildingBlocks.Isolation/Parser/QueryStringParserProvider.cs @@ -7,9 +7,8 @@ public class QueryStringParserProvider : IParserProvider { public string Name => "QueryString"; - public Task ResolveAsync(IServiceProvider serviceProvider, string key, Action action) + public Task ResolveAsync(HttpContext? httpContext, string key, Action action) { - var httpContext = serviceProvider.GetRequiredService().HttpContext; if (httpContext?.Request.Query.ContainsKey(key) ?? false) { var value = httpContext.Request.Query[key].ToString(); diff --git a/src/BuildingBlocks/Isolation/Masa.BuildingBlocks.Isolation/Parser/RouteParserProvider.cs b/src/BuildingBlocks/Isolation/Masa.BuildingBlocks.Isolation/Parser/RouteParserProvider.cs index 34b637282..3862fb795 100644 --- a/src/BuildingBlocks/Isolation/Masa.BuildingBlocks.Isolation/Parser/RouteParserProvider.cs +++ b/src/BuildingBlocks/Isolation/Masa.BuildingBlocks.Isolation/Parser/RouteParserProvider.cs @@ -7,9 +7,8 @@ public class RouteParserProvider : IParserProvider { public string Name => "Route"; - public Task ResolveAsync(IServiceProvider serviceProvider, string key, Action action) + public Task ResolveAsync(HttpContext? httpContext, string key, Action action) { - var httpContext = serviceProvider.GetRequiredService().HttpContext; var value = httpContext?.GetRouteValue(key)?.ToString() ?? string.Empty; if (!string.IsNullOrEmpty(value)) { diff --git a/src/Contrib/Data/Orm/EFCore/Masa.Contrib.Data.EFCore.Tests/CustomDbContext.cs b/src/Contrib/Data/Orm/EFCore/Masa.Contrib.Data.EFCore.Tests/CustomDbContext.cs index c70cafedd..6e9c04398 100644 --- a/src/Contrib/Data/Orm/EFCore/Masa.Contrib.Data.EFCore.Tests/CustomDbContext.cs +++ b/src/Contrib/Data/Orm/EFCore/Masa.Contrib.Data.EFCore.Tests/CustomDbContext.cs @@ -16,3 +16,17 @@ protected override void OnModelCreatingExecuting(ModelBuilder modelBuilder) modelBuilder.Entity().OwnsMany(t => t.Hobbies); } } + +public class CustomQueryDbContext : MasaDbContext +{ + public CustomQueryDbContext(MasaDbContextOptions options) : base(options) + { + } + + protected override void OnModelCreatingExecuting(ModelBuilder modelBuilder) + { + modelBuilder.Entity(); + modelBuilder.Entity().OwnsOne(x => x.Address); + modelBuilder.Entity().OwnsMany(t => t.Hobbies); + } +} diff --git a/src/Contrib/Data/Orm/EFCore/Masa.Contrib.Data.EFCore.Tests/DbContextTest.cs b/src/Contrib/Data/Orm/EFCore/Masa.Contrib.Data.EFCore.Tests/DbContextTest.cs index 4830c35dc..c21a1a85b 100644 --- a/src/Contrib/Data/Orm/EFCore/Masa.Contrib.Data.EFCore.Tests/DbContextTest.cs +++ b/src/Contrib/Data/Orm/EFCore/Masa.Contrib.Data.EFCore.Tests/DbContextTest.cs @@ -81,12 +81,83 @@ public async Task TestSoftDeleteAsync() dbContext.Set().Remove(student); await dbContext.SaveChangesAsync(); - Assert.IsTrue(await dbContext.Set().CountAsync() == 0); + Assert.IsFalse(await dbContext.Set().AnyAsync()); + + var dataFilter = serviceProvider.GetRequiredService(); + using (dataFilter.Disable()) + { + Assert.IsTrue(await dbContext.Set().CountAsync() == 1); + + student = (await dbContext.Set().Include(s => s.Address).FirstOrDefaultAsync())!; + Assert.IsTrue(student.Id == 1); + Assert.IsTrue(student.Name == "Jim"); + Assert.IsTrue(student.Age == 18); + Assert.IsTrue(student.IsDeleted); + Assert.IsTrue(student.Address.City == "ShangHai"); + Assert.IsTrue(student.Address.Street == "PuDong"); + + Assert.IsTrue(student.Hobbies.Count == 2); + Assert.IsTrue(student.Hobbies.Any(h => h.Name == "Sing")); + Assert.IsTrue(student.Hobbies.Any(h => h.Name == "Game")); + } + } + + [TestMethod] + public async Task TestAddMultiDbContextAsync() + { + var services = new ServiceCollection(); + string connectionString = $"data source=test-{Guid.NewGuid()}"; + string connectionStringByQuery = connectionString; + services.AddMasaDbContext(options => options.UseTestSqlite(connectionStringByQuery).UseFilter()); + services.AddMasaDbContext(options => options.UseTestSqlite(connectionString).UseFilter()); + var serviceProvider = services.BuildServiceProvider(); + var dbContext = serviceProvider.GetRequiredService(); + var queryDbContext = serviceProvider.GetRequiredService(); + await dbContext.Database.EnsureCreatedAsync(); + await queryDbContext.Database.EnsureCreatedAsync(); + + var student = new Student() + { + Id = 1, + Name = "Jim", + Age = 18, + Address = new Address() + { + City = "ShangHai", + Street = "PuDong", + }, + Hobbies = new List() + { + new() + { + Name = "Sing", + Description = "loves singing" + }, + new() + { + Name = "Game", + Description = "mobile game" + } + } + }; + await dbContext.Set().AddAsync(student); + await dbContext.SaveChangesAsync(); + Assert.IsTrue(await dbContext.Set().CountAsync() == 1); + + Assert.IsTrue(await queryDbContext.Set().AnyAsync()); + + student = await dbContext.Set().Include(s => s.Address).Include(s => s.Hobbies).FirstAsync(); + dbContext.Set().Remove(student); + await dbContext.SaveChangesAsync(); + + Assert.IsFalse(await dbContext.Set().AnyAsync()); + Assert.IsFalse(await queryDbContext.Set().AnyAsync()); var dataFilter = serviceProvider.GetRequiredService(); using (dataFilter.Disable()) { Assert.IsTrue(await dbContext.Set().CountAsync() == 1); + Assert.IsTrue(await queryDbContext.Set().CountAsync() == 1); student = (await dbContext.Set().Include(s => s.Address).FirstOrDefaultAsync())!; Assert.IsTrue(student.Id == 1); @@ -146,7 +217,7 @@ public void TestAddMultiMasaDbContextReturnSaveChangeFilterEqual1() .AddMasaDbContext(); var serviceProvider = services.BuildServiceProvider(); - Assert.IsTrue(serviceProvider.GetServices().Count() == 2); + Assert.IsTrue(serviceProvider.GetServices>().Count() == 2); } [TestMethod] @@ -160,7 +231,7 @@ public void TestAddMasaDbContextReturnSaveChangeFilterEqual2() var serviceProvider = services.BuildServiceProvider(); - var filters = serviceProvider.GetServices(); + var filters = serviceProvider.GetServices>(); Assert.IsTrue(filters.Count() == 2); } diff --git a/src/Contrib/Data/Orm/EFCore/Masa.Contrib.Data.EFCore.Tests/TestBase.cs b/src/Contrib/Data/Orm/EFCore/Masa.Contrib.Data.EFCore.Tests/TestBase.cs index 071a824be..9cad67ad9 100644 --- a/src/Contrib/Data/Orm/EFCore/Masa.Contrib.Data.EFCore.Tests/TestBase.cs +++ b/src/Contrib/Data/Orm/EFCore/Masa.Contrib.Data.EFCore.Tests/TestBase.cs @@ -31,4 +31,19 @@ protected CustomDbContext CreateDbContext(bool enableSoftDelete, out IServicePro dbContext.Database.EnsureCreated(); return dbContext; } + + protected CustomQueryDbContext CreateQueryDbContext(bool enableSoftDelete, out IServiceProvider serviceProvider) + { + Services.AddMasaDbContext(options => + { + if (enableSoftDelete) + options.UseFilter(); + + options.UseTestSqlite($"data source=test2-{Guid.NewGuid()}"); + }); + serviceProvider = Services.BuildServiceProvider(); + var dbContext = serviceProvider.GetRequiredService(); + dbContext.Database.EnsureCreated(); + return dbContext; + } } diff --git a/src/Contrib/Data/Orm/EFCore/Masa.Contrib.Data.EFCore/Filters/ISaveChangesFilterOfT.cs b/src/Contrib/Data/Orm/EFCore/Masa.Contrib.Data.EFCore/Filters/ISaveChangesFilterOfT.cs new file mode 100644 index 000000000..b77710d72 --- /dev/null +++ b/src/Contrib/Data/Orm/EFCore/Masa.Contrib.Data.EFCore/Filters/ISaveChangesFilterOfT.cs @@ -0,0 +1,12 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +// ReSharper disable once CheckNamespace + +namespace Microsoft.EntityFrameworkCore; + +public interface ISaveChangesFilter : ISaveChangesFilter + where TDbContext : MasaDbContext, IMasaDbContext +{ + +} diff --git a/src/Contrib/Data/Orm/EFCore/Masa.Contrib.Data.EFCore/Filters/SaveChangeFilter.cs b/src/Contrib/Data/Orm/EFCore/Masa.Contrib.Data.EFCore/Filters/SaveChangeFilter.cs index ea79c4a81..ef56ddd4a 100644 --- a/src/Contrib/Data/Orm/EFCore/Masa.Contrib.Data.EFCore/Filters/SaveChangeFilter.cs +++ b/src/Contrib/Data/Orm/EFCore/Masa.Contrib.Data.EFCore/Filters/SaveChangeFilter.cs @@ -5,8 +5,8 @@ namespace Microsoft.EntityFrameworkCore; -public class SaveChangeFilter : ISaveChangesFilter - where TDbContext : DbContext +public class SaveChangeFilter : ISaveChangesFilter + where TDbContext : MasaDbContext, IMasaDbContext { private readonly Type _userIdType; private readonly IUserContext? _userContext; diff --git a/src/Contrib/Data/Orm/EFCore/Masa.Contrib.Data.EFCore/Filters/SoftDeleteSaveChangesFilter.cs b/src/Contrib/Data/Orm/EFCore/Masa.Contrib.Data.EFCore/Filters/SoftDeleteSaveChangesFilter.cs index f59835cbb..9a1eba817 100644 --- a/src/Contrib/Data/Orm/EFCore/Masa.Contrib.Data.EFCore/Filters/SoftDeleteSaveChangesFilter.cs +++ b/src/Contrib/Data/Orm/EFCore/Masa.Contrib.Data.EFCore/Filters/SoftDeleteSaveChangesFilter.cs @@ -5,8 +5,8 @@ namespace Microsoft.EntityFrameworkCore; -public sealed class SoftDeleteSaveChangesFilter : ISaveChangesFilter - where TDbContext : DbContext, IMasaDbContext +public sealed class SoftDeleteSaveChangesFilter : ISaveChangesFilter + where TDbContext : MasaDbContext, IMasaDbContext where TUserId : IComparable { private readonly Type _userIdType; diff --git a/src/Contrib/Data/Orm/EFCore/Masa.Contrib.Data.EFCore/MasaDbContext.cs b/src/Contrib/Data/Orm/EFCore/Masa.Contrib.Data.EFCore/MasaDbContext.cs index 7b90a73c3..8cd443c76 100644 --- a/src/Contrib/Data/Orm/EFCore/Masa.Contrib.Data.EFCore/MasaDbContext.cs +++ b/src/Contrib/Data/Orm/EFCore/Masa.Contrib.Data.EFCore/MasaDbContext.cs @@ -86,7 +86,7 @@ protected virtual void OnModelCreatingExecuting(ModelBuilder modelBuilder) protected virtual void OnModelCreatingConfigureGlobalFilters(ModelBuilder modelBuilder) { - var methodInfo = typeof(MasaDbContext).GetMethod(nameof(ConfigureGlobalFilters), BindingFlags.NonPublic | BindingFlags.Instance); + var methodInfo = GetType().GetMethod(nameof(ConfigureGlobalFilters), BindingFlags.NonPublic | BindingFlags.Instance); foreach (var entityType in modelBuilder.Model.GetEntityTypes()) { @@ -221,20 +221,9 @@ public sealed override async Task SaveChangesAsync(bool acceptAllChangesOnS } public abstract class MasaDbContext : MasaDbContext - where TDbContext : DbContext, IMasaDbContext + where TDbContext : MasaDbContext, IMasaDbContext { protected MasaDbContext(MasaDbContextOptions options) : base(options) { } - - protected override void OnModelCreatingConfigureGlobalFilters(ModelBuilder modelBuilder) - { - var methodInfo = - typeof(MasaDbContext).GetMethod(nameof(ConfigureGlobalFilters), BindingFlags.NonPublic | BindingFlags.Instance); - - foreach (var entityType in modelBuilder.Model.GetEntityTypes()) - { - methodInfo!.MakeGenericMethod(entityType.ClrType).Invoke(this, new object?[] { modelBuilder, entityType }); - } - } } diff --git a/src/Contrib/Data/Orm/EFCore/Masa.Contrib.Data.EFCore/MasaDbContextOptions`.cs b/src/Contrib/Data/Orm/EFCore/Masa.Contrib.Data.EFCore/MasaDbContextOptions`.cs index 6ba9d52ca..1537ab016 100644 --- a/src/Contrib/Data/Orm/EFCore/Masa.Contrib.Data.EFCore/MasaDbContextOptions`.cs +++ b/src/Contrib/Data/Orm/EFCore/Masa.Contrib.Data.EFCore/MasaDbContextOptions`.cs @@ -5,8 +5,8 @@ namespace Microsoft.EntityFrameworkCore; -public class MasaDbContextOptions : MasaDbContextOptions - where TContext : DbContext, IMasaDbContext +public class MasaDbContextOptions : MasaDbContextOptions + where TDbContext : MasaDbContext, IMasaDbContext { private readonly DbContextOptions _originOptions; @@ -23,13 +23,13 @@ public MasaDbContextOptions( public override IEnumerable ModelCreatingProviders => _modelCreatingProviders ??= ServiceProvider?.GetServices() ?? new List(); - private IEnumerable? _saveChangesFilters; + private IEnumerable>? _saveChangesFilters; /// /// Can be used to intercept SaveChanges(Async) method /// public override IEnumerable SaveChangesFilters - => _saveChangesFilters ??= ServiceProvider?.GetServices() ?? new List(); + => _saveChangesFilters ??= ServiceProvider?.GetServices>() ?? new List>(); /// /// diff --git a/src/Contrib/Data/Orm/EFCore/Masa.Contrib.Data.EFCore/ServiceCollectionExtensions.cs b/src/Contrib/Data/Orm/EFCore/Masa.Contrib.Data.EFCore/ServiceCollectionExtensions.cs index 865b1cd34..b398b7210 100644 --- a/src/Contrib/Data/Orm/EFCore/Masa.Contrib.Data.EFCore/ServiceCollectionExtensions.cs +++ b/src/Contrib/Data/Orm/EFCore/Masa.Contrib.Data.EFCore/ServiceCollectionExtensions.cs @@ -69,9 +69,9 @@ private static IServiceCollection AddCoreServices serviceProvider.GetRequiredService>(), optionsLifetime)); - services.TryAddEnumerable(new ServiceDescriptor(typeof(ISaveChangesFilter), + services.TryAddEnumerable(new ServiceDescriptor(typeof(ISaveChangesFilter), typeof(SaveChangeFilter), optionsLifetime)); - services.TryAddEnumerable(new ServiceDescriptor(typeof(ISaveChangesFilter), + services.TryAddEnumerable(new ServiceDescriptor(typeof(ISaveChangesFilter), typeof(SoftDeleteSaveChangesFilter), optionsLifetime)); return services; } diff --git a/src/Contrib/Data/UoW/Masa.Contrib.Data.UoW.EFCore/DispatcherOptionsExtensions.cs b/src/Contrib/Data/UoW/Masa.Contrib.Data.UoW.EFCore/DispatcherOptionsExtensions.cs index a8b29dbb5..3ce547e9f 100644 --- a/src/Contrib/Data/UoW/Masa.Contrib.Data.UoW.EFCore/DispatcherOptionsExtensions.cs +++ b/src/Contrib/Data/UoW/Masa.Contrib.Data.UoW.EFCore/DispatcherOptionsExtensions.cs @@ -21,7 +21,10 @@ public static IEventBusBuilder UseUoW( where TDbContext : MasaDbContext, IMasaDbContext where TUserId : IComparable { - eventBusBuilder.Services.UseUoW(nameof(eventBusBuilder.Services), optionsBuilder, disableRollbackOnFailure, + eventBusBuilder.Services.UseUoW( + nameof(eventBusBuilder.Services), + optionsBuilder, + disableRollbackOnFailure, useTransaction); return eventBusBuilder; } diff --git a/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n.AspNetCore/README.md b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n.AspNetCore/README.md index bb745ec80..4ff78065d 100644 --- a/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n.AspNetCore/README.md +++ b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n.AspNetCore/README.md @@ -3,8 +3,15 @@ Provides the ability to parse and obtain Culture, and use it with [I18n](../Masa.Contrib.Globalization.I18n/README.md). Currently, there are three ways to switch languages: * URL parameter method: ?culture=en-US, this method has the highest priority, the format is: culture=region code -* Cookies method: call L.SetCulture (region code) method to switch -* Client browser language automatic matching: If neither of the previous two methods are set, it supports automatic matching according to the client browser language. +* Cookies method: the cookie format is c=%LANGCODE%|uic=%LANGCODE%, where c is Culture and uic is UICulture, for example: + +``` cookie +c=en-UK|uic=en-US +``` + +* Client browser language automatic matching: If the previous two methods are not set, it supports automatic matching according to the client browser language. + +> Detailed [reference](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/localization?view=aspnetcore-7.0#localization-middleware) ## Masa.Contrib.Globalization.I18n.AspNetCore diff --git a/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n.AspNetCore/README.zh-CN.md b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n.AspNetCore/README.zh-CN.md index ef54926b6..b58087d45 100644 --- a/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n.AspNetCore/README.zh-CN.md +++ b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n.AspNetCore/README.zh-CN.md @@ -3,9 +3,16 @@ 提供解析获取Culture的能力,配合[I18n](../Masa.Contrib.Globalization.I18n/README.zh-CN.md)来使用,目前支持三种方式进行切换语言: * URL 参数 方式: ?culture=en-US,此方式优先级最高,格式为:culture=区域码 -* Cookies 方式:调用 L.SetCulture(区域码) 方式切换 +* Cookies 方式:cookie 格式为 c=%LANGCODE%|uic=%LANGCODE%,其中 c 是 Culture,uic 是 UICulture, 例如: + +``` cookie +c=en-UK|uic=en-US +``` + * 客户端浏览器语言自动匹配:如果前面两种方式都没有设置,支持自动根据客户端浏览器语言进行匹配。 +> 详细[可参考](https://learn.microsoft.com/zh-cn/aspnet/core/fundamentals/localization?view=aspnetcore-7.0#localization-middleware) + ## Masa.Contrib.Globalization.I18n.AspNetCore 用例: diff --git a/src/Contrib/Isolation/Masa.Contrib.Isolation.MultiEnvironment/Middleware/MultiEnvironmentMiddleware.cs b/src/Contrib/Isolation/Masa.Contrib.Isolation.MultiEnvironment/Middleware/MultiEnvironmentMiddleware.cs index 165eb3bb3..47ee080f2 100644 --- a/src/Contrib/Isolation/Masa.Contrib.Isolation.MultiEnvironment/Middleware/MultiEnvironmentMiddleware.cs +++ b/src/Contrib/Isolation/Masa.Contrib.Isolation.MultiEnvironment/Middleware/MultiEnvironmentMiddleware.cs @@ -6,7 +6,6 @@ namespace Masa.Contrib.Isolation.MultiEnvironment.Middleware; public class MultiEnvironmentMiddleware : IIsolationMiddleware { private const string DEFAULT_ENVIRONMENT_NAME = "ASPNETCORE_ENVIRONMENT"; - private readonly IServiceProvider _serviceProvider; private readonly ILogger? _logger; private readonly IEnumerable _parserProviders; private readonly IMultiEnvironmentContext _environmentContext; @@ -21,18 +20,17 @@ public MultiEnvironmentMiddleware( IEnumerable? parserProviders, IOptions? masaAppConfigureOptions = null) { - _serviceProvider = serviceProvider; _environmentKey = environmentKey ?? masaAppConfigureOptions?.Value.GetVariable(nameof(MasaAppConfigureOptions.Environment))?.Variable ?? DEFAULT_ENVIRONMENT_NAME; _parserProviders = parserProviders ?? GetDefaultParserProviders(); - _logger = _serviceProvider.GetService>(); - _environmentContext = _serviceProvider.GetRequiredService(); - _environmentSetter = _serviceProvider.GetRequiredService(); - _environmentUserContext = _serviceProvider.GetService(); + _logger = serviceProvider.GetService>(); + _environmentContext = serviceProvider.GetRequiredService(); + _environmentSetter = serviceProvider.GetRequiredService(); + _environmentUserContext = serviceProvider.GetService(); } - public async Task HandleAsync() + public async Task HandleAsync(HttpContext? httpContext) { if (_handled) return; @@ -53,7 +51,7 @@ public async Task HandleAsync() foreach (var environmentParserProvider in _parserProviders) { parsers.Add(environmentParserProvider.Name); - if (await environmentParserProvider.ResolveAsync(_serviceProvider, _environmentKey, + if (await environmentParserProvider.ResolveAsync(httpContext, _environmentKey, environment => _environmentSetter.SetEnvironment(environment))) { _logger?.LogDebug("The environment is successfully resolved, and the resolver is: {Resolvers}", string.Join("、 ", parsers)); diff --git a/src/Contrib/Isolation/Masa.Contrib.Isolation.MultiEnvironment/Parser/CurrentUserEnvironmentParseProvider.cs b/src/Contrib/Isolation/Masa.Contrib.Isolation.MultiEnvironment/Parser/CurrentUserEnvironmentParseProvider.cs index 48e97d6dc..1851ae4fa 100644 --- a/src/Contrib/Isolation/Masa.Contrib.Isolation.MultiEnvironment/Parser/CurrentUserEnvironmentParseProvider.cs +++ b/src/Contrib/Isolation/Masa.Contrib.Isolation.MultiEnvironment/Parser/CurrentUserEnvironmentParseProvider.cs @@ -9,9 +9,9 @@ public class CurrentUserEnvironmentParseProvider : IParserProvider { public string Name => "CurrentUser"; - public Task ResolveAsync(IServiceProvider serviceProvider, string key, Action action) + public Task ResolveAsync(HttpContext? httpContext, string key, Action action) { - var multiEnvironmentUserContext = serviceProvider.GetService(); + var multiEnvironmentUserContext = httpContext?.RequestServices?.GetService(); var environment = multiEnvironmentUserContext?.Environment; if (!string.IsNullOrWhiteSpace(environment)) { diff --git a/src/Contrib/Isolation/Masa.Contrib.Isolation.MultiEnvironment/Parser/MasaAppConfigureParserProvider.cs b/src/Contrib/Isolation/Masa.Contrib.Isolation.MultiEnvironment/Parser/MasaAppConfigureParserProvider.cs index 098e2db90..7cf1f25d2 100644 --- a/src/Contrib/Isolation/Masa.Contrib.Isolation.MultiEnvironment/Parser/MasaAppConfigureParserProvider.cs +++ b/src/Contrib/Isolation/Masa.Contrib.Isolation.MultiEnvironment/Parser/MasaAppConfigureParserProvider.cs @@ -9,9 +9,9 @@ public class MasaAppConfigureParserProvider : IParserProvider { public string Name => "MasaAppConfigure"; - public Task ResolveAsync(IServiceProvider serviceProvider, string key, Action action) + public Task ResolveAsync(HttpContext? httpContext, string key, Action action) { - var environment = serviceProvider.GetService>()?.Value?.Environment; + var environment = httpContext?.RequestServices?.GetService>()?.Value?.Environment; if (!string.IsNullOrWhiteSpace(environment)) { action.Invoke(environment); diff --git a/src/Contrib/Isolation/Masa.Contrib.Isolation.MultiEnvironment/_Imports.cs b/src/Contrib/Isolation/Masa.Contrib.Isolation.MultiEnvironment/_Imports.cs index 20beeef93..7ef96cbdb 100644 --- a/src/Contrib/Isolation/Masa.Contrib.Isolation.MultiEnvironment/_Imports.cs +++ b/src/Contrib/Isolation/Masa.Contrib.Isolation.MultiEnvironment/_Imports.cs @@ -6,6 +6,7 @@ global using Masa.BuildingBlocks.Isolation; global using Masa.BuildingBlocks.Isolation.Parser; global using Masa.Contrib.Isolation.MultiEnvironment.Middleware; +global using Microsoft.AspNetCore.Http; global using Microsoft.Extensions.DependencyInjection; global using Microsoft.Extensions.DependencyInjection.Extensions; global using Microsoft.Extensions.Logging; diff --git a/src/Contrib/Isolation/Masa.Contrib.Isolation.MultiTenant/Middleware/MultiTenantMiddleware.cs b/src/Contrib/Isolation/Masa.Contrib.Isolation.MultiTenant/Middleware/MultiTenantMiddleware.cs index d1d26a33b..6b1ba7367 100644 --- a/src/Contrib/Isolation/Masa.Contrib.Isolation.MultiTenant/Middleware/MultiTenantMiddleware.cs +++ b/src/Contrib/Isolation/Masa.Contrib.Isolation.MultiTenant/Middleware/MultiTenantMiddleware.cs @@ -5,7 +5,6 @@ namespace Masa.Contrib.Isolation.MultiTenant.Middleware; public class MultiTenantMiddleware : IIsolationMiddleware { - private readonly IServiceProvider _serviceProvider; private readonly ILogger? _logger; private readonly IEnumerable _parserProviders; private readonly IMultiTenantContext _tenantContext; @@ -19,16 +18,15 @@ public MultiTenantMiddleware( string tenantKey, IEnumerable? parserProviders) { - _serviceProvider = serviceProvider; _tenantKey = tenantKey; _parserProviders = parserProviders ?? GetDefaultParserProviders(); - _logger = _serviceProvider.GetService>(); - _tenantContext = _serviceProvider.GetRequiredService(); - _tenantSetter = _serviceProvider.GetRequiredService(); - _tenantUserContext = _serviceProvider.GetService(); + _logger = serviceProvider.GetService>(); + _tenantContext = serviceProvider.GetRequiredService(); + _tenantSetter = serviceProvider.GetRequiredService(); + _tenantUserContext = serviceProvider.GetService(); } - public async Task HandleAsync() + public async Task HandleAsync(HttpContext? httpContext) { if (_handled) return; @@ -50,7 +48,7 @@ public async Task HandleAsync() foreach (var tenantParserProvider in _parserProviders) { parsers.Add(tenantParserProvider.Name); - if (await tenantParserProvider.ResolveAsync(_serviceProvider, _tenantKey, + if (await tenantParserProvider.ResolveAsync(httpContext, _tenantKey, tenantId => _tenantSetter.SetTenant(new Tenant(tenantId)))) { _logger?.LogDebug("The tenant is successfully resolved, and the resolver is: {Resolvers}", string.Join("、 ", parsers)); diff --git a/src/Contrib/Isolation/Masa.Contrib.Isolation.MultiTenant/Parser/CurrentUserTenantParseProvider.cs b/src/Contrib/Isolation/Masa.Contrib.Isolation.MultiTenant/Parser/CurrentUserTenantParseProvider.cs index 4f2d0fea4..7e42671b7 100644 --- a/src/Contrib/Isolation/Masa.Contrib.Isolation.MultiTenant/Parser/CurrentUserTenantParseProvider.cs +++ b/src/Contrib/Isolation/Masa.Contrib.Isolation.MultiTenant/Parser/CurrentUserTenantParseProvider.cs @@ -9,9 +9,9 @@ public class CurrentUserTenantParseProvider : IParserProvider { public string Name => "CurrentUser"; - public Task ResolveAsync(IServiceProvider serviceProvider, string key, Action action) + public Task ResolveAsync(HttpContext? httpContext, string key, Action action) { - var multiTenantUserContext = serviceProvider.GetService(); + var multiTenantUserContext = httpContext?.RequestServices?.GetService(); var tenantId = multiTenantUserContext?.TenantId; if (!string.IsNullOrWhiteSpace(tenantId)) { diff --git a/src/Contrib/Isolation/Masa.Contrib.Isolation.MultiTenant/_Imports.cs b/src/Contrib/Isolation/Masa.Contrib.Isolation.MultiTenant/_Imports.cs index 5f9b38688..0eb518fd8 100644 --- a/src/Contrib/Isolation/Masa.Contrib.Isolation.MultiTenant/_Imports.cs +++ b/src/Contrib/Isolation/Masa.Contrib.Isolation.MultiTenant/_Imports.cs @@ -5,6 +5,7 @@ global using Masa.BuildingBlocks.Isolation; global using Masa.BuildingBlocks.Isolation.Parser; global using Masa.Contrib.Isolation.MultiTenant.Middleware; +global using Microsoft.AspNetCore.Http; global using Microsoft.Extensions.DependencyInjection; global using Microsoft.Extensions.DependencyInjection.Extensions; global using Microsoft.Extensions.Logging; diff --git a/src/Contrib/Isolation/Masa.Contrib.Isolation/Middleware/IsolationMiddleware.cs b/src/Contrib/Isolation/Masa.Contrib.Isolation/Middleware/IsolationMiddleware.cs index 20ca0c518..ab82c067e 100644 --- a/src/Contrib/Isolation/Masa.Contrib.Isolation/Middleware/IsolationMiddleware.cs +++ b/src/Contrib/Isolation/Masa.Contrib.Isolation/Middleware/IsolationMiddleware.cs @@ -6,17 +6,19 @@ namespace Masa.Contrib.Isolation.Middleware; public class IsolationMiddleware : Middleware where TEvent : IEvent { private readonly IEnumerable _middlewares; + private readonly IHttpContextAccessor _httpContextAccessor; - public IsolationMiddleware(IEnumerable middlewares) + public IsolationMiddleware(IEnumerable middlewares, IHttpContextAccessor httpContextAccessor) { _middlewares = middlewares; + _httpContextAccessor = httpContextAccessor; } public override async Task HandleAsync(TEvent @event, EventHandlerDelegate next) { foreach (var middleware in _middlewares) { - await middleware.HandleAsync(); + await middleware.HandleAsync(_httpContextAccessor.HttpContext); } await next(); @@ -36,7 +38,7 @@ public async Task InvokeAsync(HttpContext httpContext, IEnumerable parserProvider = new(); parserProvider.Setup(provider - => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>())); + => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>())); List parserProviders = new List { parserProvider.Object }; string environmentKey = "env"; var middleware = new MultiEnvironmentMiddleware(services.BuildServiceProvider(), environmentKey, parserProviders); - await middleware.HandleAsync(); + await middleware.HandleAsync(null); parserProvider.Verify( - provider => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>()), Times.Once); + provider => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>()), Times.Once); } [TestMethod] @@ -47,15 +47,15 @@ public async Task TestMultiEnvironmentMiddleware2Async() services.AddScoped(_ => environmentSetter.Object); Mock parserProvider = new(); - parserProvider.Setup(provider => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>())).Verifiable(); + parserProvider.Setup(provider => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>())).Verifiable(); List parserProviders = new List { parserProvider.Object }; string environmentKey = "env"; var middleware = new MultiEnvironmentMiddleware(services.BuildServiceProvider(), environmentKey, parserProviders); - await middleware.HandleAsync(); - parserProvider.Verify(provider => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>()), Times.Once); + await middleware.HandleAsync(null); + parserProvider.Verify(provider => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>()), Times.Once); } [TestMethod] @@ -72,16 +72,16 @@ public async Task TestMultiEnvironmentMiddleware3Async() Mock parserProvider = new(); parserProvider.Setup(provider - => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>())); + => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>())); List parserProviders = new List { parserProvider.Object }; string environmentKey = "env"; var middleware = new MultiEnvironmentMiddleware(services.BuildServiceProvider(), environmentKey, parserProviders); - await middleware.HandleAsync(); + await middleware.HandleAsync(null); parserProvider.Verify( - provider => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>()), Times.Never); + provider => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>()), Times.Never); } [TestMethod] @@ -106,11 +106,12 @@ public async Task TestMultiEnvironmentMiddleware4Async() Items = new Dictionary { { environmentKey, "dev" } - } + }, + RequestServices = services.BuildServiceProvider() } }; var middleware = new MultiEnvironmentMiddleware(services.BuildServiceProvider(), environmentKey, null); - await middleware.HandleAsync(); + await middleware.HandleAsync(httpContextAccessor.HttpContext); environmentSetter.Verify(setter => setter.SetEnvironment(It.IsAny()), Times.Once); } @@ -137,11 +138,12 @@ public async Task TestMultiEnvironmentMiddleware5Async() Items = new Dictionary { { environmentKey, "dev" } - } + }, + RequestServices = services.BuildServiceProvider() } }; var middleware = new MultiEnvironmentMiddleware(services.BuildServiceProvider(), environmentKey, null); - await middleware.HandleAsync(); + await middleware.HandleAsync(httpContextAccessor.HttpContext); environmentSetter.Verify(setter => setter.SetEnvironment(It.IsAny()), Times.Once); } } diff --git a/src/Contrib/Isolation/Tests/Masa.Contrib.Isolation.MultiTenant.Tests/MiddlewareTest.cs b/src/Contrib/Isolation/Tests/Masa.Contrib.Isolation.MultiTenant.Tests/MiddlewareTest.cs index e91d1baaa..c39cf5674 100644 --- a/src/Contrib/Isolation/Tests/Masa.Contrib.Isolation.MultiTenant.Tests/MiddlewareTest.cs +++ b/src/Contrib/Isolation/Tests/Masa.Contrib.Isolation.MultiTenant.Tests/MiddlewareTest.cs @@ -21,15 +21,15 @@ public async Task TestMultiTenantMiddlewareAsync() Mock parserProvider = new(); parserProvider.Setup(provider - => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>())); + => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>())); List parserProviders = new List { parserProvider.Object }; string tenantKey = "tenant"; var middleware = new MultiTenantMiddleware(services.BuildServiceProvider(), tenantKey, parserProviders); - await middleware.HandleAsync(); - parserProvider.Verify(provider => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>()), Times.Once); + await middleware.HandleAsync(null); + parserProvider.Verify(provider => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>()), Times.Once); } [TestMethod] @@ -46,15 +46,15 @@ public async Task TestMultiTenantMiddleware2Async() Mock parserProvider = new(); parserProvider.Setup(provider - => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>())); + => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>())); List parserProviders = new List { parserProvider.Object }; string tenantKey = "tenant"; var middleware = new MultiTenantMiddleware(services.BuildServiceProvider(), tenantKey, parserProviders); - await middleware.HandleAsync(); - parserProvider.Verify(provider => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>()), Times.Never); + await middleware.HandleAsync(null); + parserProvider.Verify(provider => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>()), Times.Never); } [TestMethod] @@ -71,7 +71,7 @@ public async Task TestMultiTenantMiddleware3Async() services.AddScoped(_ => tenantSetter.Object); Mock parserProvider = new(); - parserProvider.Setup(provider => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>())); + parserProvider.Setup(provider => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>())); services.AddHttpContextAccessor(); string tenantKey = "tenant"; @@ -83,11 +83,12 @@ public async Task TestMultiTenantMiddleware3Async() Items = new Dictionary { { tenantKey, "1" } - } + }, + RequestServices = services.BuildServiceProvider() } }; var middleware = new MultiTenantMiddleware(services.BuildServiceProvider(), tenantKey, null); - await middleware.HandleAsync(); + await middleware.HandleAsync(httpContextAccessor.HttpContext); tenantSetter.Verify(setter => setter.SetTenant(It.IsAny()), Times.Once); } @@ -106,7 +107,7 @@ public async Task TestMultiTenantMiddleware4Async() isolationBuilder.Object.UseMultiTenant(tenantKey); Mock parserProvider = new(); - parserProvider.Setup(provider => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>())); + parserProvider.Setup(provider => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>())); services.AddHttpContextAccessor(); var httpContextAccessor = services.BuildServiceProvider().GetRequiredService()!; @@ -117,15 +118,16 @@ public async Task TestMultiTenantMiddleware4Async() Items = new Dictionary { { tenantKey, "1" } - } + }, + RequestServices = services.BuildServiceProvider() } }; var middleware = services.BuildServiceProvider().GetRequiredService(); - await middleware.HandleAsync(); + await middleware.HandleAsync(httpContextAccessor.HttpContext); tenantSetter.Verify(setter => setter.SetTenant(It.IsAny()), Times.Once); - await middleware.HandleAsync(); + await middleware.HandleAsync(httpContextAccessor.HttpContext); tenantSetter.Verify(setter => setter.SetTenant(It.IsAny()), Times.Once); } @@ -143,7 +145,7 @@ public async Task TestMultiTenantMiddleware5Async() isolationBuilder.Object.UseMultiTenant(); Mock parserProvider = new(); - parserProvider.Setup(provider => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>())); + parserProvider.Setup(provider => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>())); services.AddHttpContextAccessor(); var httpContextAccessor = services.BuildServiceProvider().GetRequiredService()!; @@ -154,15 +156,16 @@ public async Task TestMultiTenantMiddleware5Async() Items = new Dictionary { { "__tenant", "1" } - } + }, + RequestServices = services.BuildServiceProvider() } }; var middleware = services.BuildServiceProvider().GetRequiredService(); - await middleware.HandleAsync(); + await middleware.HandleAsync(httpContextAccessor.HttpContext); tenantSetter.Verify(setter => setter.SetTenant(It.IsAny()), Times.Once); - await middleware.HandleAsync(); + await middleware.HandleAsync(httpContextAccessor.HttpContext); tenantSetter.Verify(setter => setter.SetTenant(It.IsAny()), Times.Once); } @@ -180,7 +183,7 @@ public async Task TestMultiTenantMiddleware6Async() isolationBuilder.Object.UseMultiTenant(new List()); Mock parserProvider = new(); - parserProvider.Setup(provider => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>())); + parserProvider.Setup(provider => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>())); services.AddHttpContextAccessor(); var httpContextAccessor = services.BuildServiceProvider().GetRequiredService()!; @@ -191,12 +194,13 @@ public async Task TestMultiTenantMiddleware6Async() Items = new Dictionary { { "__tenant", "1" } - } + }, + RequestServices = services.BuildServiceProvider() } }; var middleware = services.BuildServiceProvider().GetRequiredService(); - await middleware.HandleAsync(); + await middleware.HandleAsync(httpContextAccessor.HttpContext); tenantSetter.Verify(setter => setter.SetTenant(It.IsAny()), Times.Never); } @@ -216,15 +220,15 @@ public async Task TestMultiTenantMiddleware7Async() Mock parserProvider = new(); parserProvider.Setup(provider - => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>())); + => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>())); List parserProviders = new List { parserProvider.Object }; string tenantKey = "tenant"; var middleware = new MultiTenantMiddleware(services.BuildServiceProvider(), tenantKey, parserProviders); - await middleware.HandleAsync(); - parserProvider.Verify(provider => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>()), Times.Once); + await middleware.HandleAsync(null); + parserProvider.Verify(provider => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>()), Times.Once); } [TestMethod] @@ -241,7 +245,7 @@ public async Task TestMultiTenantMiddleware8Async() services.AddScoped(_ => tenantSetter.Object); Mock parserProvider = new(); - parserProvider.Setup(provider => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>())); + parserProvider.Setup(provider => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>())); services.AddHttpContextAccessor(); string tenantKey = "tenant"; @@ -253,11 +257,12 @@ public async Task TestMultiTenantMiddleware8Async() Items = new Dictionary { { tenantKey, "1" } - } + }, + RequestServices = services.BuildServiceProvider() } }; var middleware = new MultiTenantMiddleware(services.BuildServiceProvider(), tenantKey, null); - await middleware.HandleAsync(); + await middleware.HandleAsync(httpContextAccessor.HttpContext); tenantSetter.Verify(setter => setter.SetTenant(It.IsAny()), Times.Once); } @@ -276,7 +281,7 @@ public async Task TestMultiTenantMiddleware9Async() isolationBuilder.Object.UseMultiTenant(new List()); Mock parserProvider = new(); - parserProvider.Setup(provider => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>())); + parserProvider.Setup(provider => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>())); services.AddHttpContextAccessor(); var httpContextAccessor = services.BuildServiceProvider().GetRequiredService()!; @@ -287,12 +292,13 @@ public async Task TestMultiTenantMiddleware9Async() Items = new Dictionary { { "__tenant", "1" } - } + }, + RequestServices = services.BuildServiceProvider() } }; var middleware = services.BuildServiceProvider().GetRequiredService(); - await middleware.HandleAsync(); + await middleware.HandleAsync(httpContextAccessor.HttpContext); tenantSetter.Verify(setter => setter.SetTenant(It.IsAny()), Times.Never); } @@ -311,7 +317,7 @@ public async Task TestMultiTenantMiddleware10Async() services.AddScoped(_ => tenantSetter.Object); Mock parserProvider = new(); - parserProvider.Setup(provider => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>())); + parserProvider.Setup(provider => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>())); services.AddHttpContextAccessor(); string tenantKey = "tenant"; @@ -323,11 +329,12 @@ public async Task TestMultiTenantMiddleware10Async() Items = new Dictionary { { tenantKey, "1" } - } + }, + RequestServices = services.BuildServiceProvider() } }; var middleware = new MultiTenantMiddleware(services.BuildServiceProvider(), tenantKey, null); - await middleware.HandleAsync(); + await middleware.HandleAsync(httpContextAccessor.HttpContext); tenantSetter.Verify(setter => setter.SetTenant(It.IsAny()), Times.Once); } @@ -346,14 +353,14 @@ public async Task TestMultiTenantMiddleware11Async() Mock parserProvider = new(); parserProvider.Setup(provider - => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>())); + => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>())); List parserProviders = new List { parserProvider.Object }; string tenantKey = "tenant"; var middleware = new MultiTenantMiddleware(services.BuildServiceProvider(), tenantKey, parserProviders); - await middleware.HandleAsync(); - parserProvider.Verify(provider => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>()), Times.Never); + await middleware.HandleAsync(null); + parserProvider.Verify(provider => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>()), Times.Never); } } diff --git a/src/Contrib/Isolation/Tests/Masa.Contrib.Isolation.Tests/ParserProviderTest.cs b/src/Contrib/Isolation/Tests/Masa.Contrib.Isolation.Tests/ParserProviderTest.cs index c32629878..d6516134f 100644 --- a/src/Contrib/Isolation/Tests/Masa.Contrib.Isolation.Tests/ParserProviderTest.cs +++ b/src/Contrib/Isolation/Tests/Masa.Contrib.Isolation.Tests/ParserProviderTest.cs @@ -24,14 +24,15 @@ public async Task TestCookieParserAsync() tenantKey, "1" } } - } + }, + RequestServices = serviceProvider }; var provider = new CookieParserProvider(); Assert.IsTrue(provider.Name == "Cookie"); - var handler = await provider.ResolveAsync(services.BuildServiceProvider(), tenantKey, tenantId => - { - Assert.IsTrue(tenantId == "1"); - }); + var handler = await provider.ResolveAsync(httpContextAccessor.HttpContext, tenantKey, tenantId => + { + Assert.IsTrue(tenantId == "1"); + }); Assert.IsTrue(handler); } @@ -48,10 +49,13 @@ public async Task TestCookieParser2Async() Request = { Cookies = new RequestCookieCollection() - } + }, + RequestServices = serviceProvider }; var provider = new CookieParserProvider(); - var handler = await provider.ResolveAsync(services.BuildServiceProvider(), tenantKey, _ => { }); + var handler = await provider.ResolveAsync(httpContextAccessor.HttpContext, tenantKey, _ => + { + }); Assert.IsFalse(handler); } @@ -62,7 +66,9 @@ public async Task TestCookieParser3Async() services.AddHttpContextAccessor(); string tenantKey = "tenant"; var provider = new CookieParserProvider(); - var handler = await provider.ResolveAsync(services.BuildServiceProvider(), tenantKey, _ => { }); + var handler = await provider.ResolveAsync(services.BuildServiceProvider().GetRequiredService().HttpContext, tenantKey, _ => + { + }); Assert.IsFalse(handler); } @@ -83,14 +89,15 @@ public async Task TestFormParserAsync() { tenantKey, "1" } } ) - } + }, + RequestServices = serviceProvider }; var provider = new FormParserProvider(); Assert.IsTrue(provider.Name == "Form"); - var handler = await provider.ResolveAsync(services.BuildServiceProvider(), tenantKey, tenantId => - { - Assert.IsTrue(tenantId == "1"); - }); + var handler = await provider.ResolveAsync(httpContextAccessor.HttpContext, tenantKey, tenantId => + { + Assert.IsTrue(tenantId == "1"); + }); Assert.IsTrue(handler); } @@ -107,12 +114,13 @@ public async Task TestFormParser2Async() Request = { Form = new FormCollection(new Dictionary()) - } + }, + RequestServices = serviceProvider }; var provider = new FormParserProvider(); - var handler = await provider.ResolveAsync(services.BuildServiceProvider(), tenantKey, _ => - { - }); + var handler = await provider.ResolveAsync(httpContextAccessor.HttpContext, tenantKey, _ => + { + }); Assert.IsFalse(handler); } @@ -129,13 +137,14 @@ public async Task TestFormParser3Async() Request = { QueryString = QueryString.Create(tenantKey, "1") - } + }, + RequestServices = serviceProvider }; var provider = new FormParserProvider(); Assert.IsTrue(provider.Name == "Form"); - var handler = await provider.ResolveAsync(services.BuildServiceProvider(), tenantKey, _ => - { - }); + var handler = await provider.ResolveAsync(httpContextAccessor.HttpContext, tenantKey, _ => + { + }); Assert.IsFalse(handler); } @@ -155,14 +164,15 @@ public async Task TestHeaderParserAsync() { { tenantKey, "1" } } - } + }, + RequestServices = serviceProvider }; var provider = new HeaderParserProvider(); Assert.IsTrue(provider.Name == "Header"); - var handler = await provider.ResolveAsync(services.BuildServiceProvider(), tenantKey, tenantId => - { - Assert.IsTrue(tenantId == "1"); - }); + var handler = await provider.ResolveAsync(httpContextAccessor.HttpContext, tenantKey, tenantId => + { + Assert.IsTrue(tenantId == "1"); + }); Assert.IsTrue(handler); } @@ -179,12 +189,13 @@ public async Task TestHeaderParser2Async() Request = { Headers = { } - } + }, + RequestServices = serviceProvider }; var provider = new HeaderParserProvider(); - var handler = await provider.ResolveAsync(services.BuildServiceProvider(), tenantKey, _ => - { - }); + var handler = await provider.ResolveAsync(httpContextAccessor.HttpContext, tenantKey, _ => + { + }); Assert.IsFalse(handler); } @@ -201,14 +212,15 @@ public async Task TestHttpContextItemParserAsync() Items = new Dictionary { { tenantKey, "1" } - } + }, + RequestServices = serviceProvider }; var provider = new HttpContextItemParserProvider(); Assert.IsTrue(provider.Name == "Items"); - var handler = await provider.ResolveAsync(services.BuildServiceProvider(), tenantKey, tenantId => - { - Assert.IsTrue(tenantId == "1"); - }); + var handler = await provider.ResolveAsync(httpContextAccessor.HttpContext, tenantKey, tenantId => + { + Assert.IsTrue(tenantId == "1"); + }); Assert.IsTrue(handler); } @@ -225,9 +237,9 @@ public async Task TestHttpContextItemParser2Async() Items = new Dictionary() }; var provider = new HttpContextItemParserProvider(); - var handler = await provider.ResolveAsync(services.BuildServiceProvider(), tenantKey, _ => - { - }); + var handler = await provider.ResolveAsync(httpContextAccessor.HttpContext, tenantKey, _ => + { + }); Assert.IsFalse(handler); } @@ -238,9 +250,9 @@ public async Task TestHttpContextItemParser3Async() services.AddHttpContextAccessor(); string tenantKey = "tenant"; var provider = new HttpContextItemParserProvider(); - var handler = await provider.ResolveAsync(services.BuildServiceProvider(), tenantKey, _ => - { - }); + var handler = await provider.ResolveAsync(services.BuildServiceProvider().GetRequiredService().HttpContext, tenantKey, _ => + { + }); Assert.IsFalse(handler); } @@ -254,14 +266,15 @@ public async Task TestQueryStringParserAsync() var httpContextAccessor = serviceProvider.GetRequiredService(); httpContextAccessor.HttpContext = new DefaultHttpContext { - Request = { QueryString = QueryString.Create(tenantKey, "1") } + Request = { QueryString = QueryString.Create(tenantKey, "1") }, + RequestServices = serviceProvider }; var provider = new QueryStringParserProvider(); Assert.IsTrue(provider.Name == "QueryString"); - var handler = await provider.ResolveAsync(services.BuildServiceProvider(), tenantKey, tenantId => - { - Assert.IsTrue(tenantId == "1"); - }); + var handler = await provider.ResolveAsync(httpContextAccessor.HttpContext, tenantKey, tenantId => + { + Assert.IsTrue(tenantId == "1"); + }); Assert.IsTrue(handler); } @@ -275,12 +288,13 @@ public async Task TestQueryStringParser2Async() var httpContextAccessor = serviceProvider.GetRequiredService(); httpContextAccessor.HttpContext = new DefaultHttpContext { - Request = { QueryString = new QueryString() } + Request = { QueryString = new QueryString() }, + RequestServices = serviceProvider }; var provider = new QueryStringParserProvider(); - var handler = await provider.ResolveAsync(services.BuildServiceProvider(), tenantKey, _ => - { - }); + var handler = await provider.ResolveAsync(httpContextAccessor.HttpContext, tenantKey, _ => + { + }); Assert.IsFalse(handler); } @@ -291,9 +305,9 @@ public async Task TestQueryStringParser3Async() services.AddHttpContextAccessor(); string tenantKey = "tenant"; var provider = new QueryStringParserProvider(); - var handler = await provider.ResolveAsync(services.BuildServiceProvider(), tenantKey, _ => - { - }); + var handler = await provider.ResolveAsync(services.BuildServiceProvider().GetRequiredService().HttpContext, tenantKey, _ => + { + }); Assert.IsFalse(handler); } @@ -313,14 +327,15 @@ public async Task TestRouteParserAsync() { { tenantKey, "1" } } - } + }, + RequestServices = serviceProvider }; var provider = new RouteParserProvider(); Assert.IsTrue(provider.Name == "Route"); - var handler = await provider.ResolveAsync(services.BuildServiceProvider(), tenantKey, tenantId => - { - Assert.IsTrue(tenantId == "1"); - }); + var handler = await provider.ResolveAsync(httpContextAccessor.HttpContext, tenantKey, tenantId => + { + Assert.IsTrue(tenantId == "1"); + }); Assert.IsTrue(handler); } @@ -337,12 +352,13 @@ public async Task TestRouteParser2Async() Request = { RouteValues = new RouteValueDictionary() - } + }, + RequestServices = serviceProvider }; var provider = new RouteParserProvider(); - var handler = await provider.ResolveAsync(services.BuildServiceProvider(), tenantKey, _ => - { - }); + var handler = await provider.ResolveAsync(httpContextAccessor.HttpContext, tenantKey, _ => + { + }); Assert.IsFalse(handler); } @@ -353,9 +369,9 @@ public async Task TestRouteParser3Async() services.AddHttpContextAccessor(); string tenantKey = "tenant"; var provider = new RouteParserProvider(); - var handler = await provider.ResolveAsync(services.BuildServiceProvider(), tenantKey, _ => - { - }); + var handler = await provider.ResolveAsync(services.BuildServiceProvider().GetRequiredService().HttpContext, tenantKey, _ => + { + }); Assert.IsFalse(handler); } @@ -365,12 +381,11 @@ public async Task TestEnvironmentVariablesParserAsync() var services = new ServiceCollection(); string environmentKey = "env"; System.Environment.SetEnvironmentVariable(environmentKey, "dev"); - var serviceProvider = services.BuildServiceProvider(); var environmentVariablesParserProvider = new EnvironmentVariablesParserProvider(); - var handler = await environmentVariablesParserProvider.ResolveAsync(serviceProvider, environmentKey, environment => - { - Assert.IsTrue(environment == "dev"); - }); + var handler = await environmentVariablesParserProvider.ResolveAsync(null, environmentKey, environment => + { + Assert.IsTrue(environment == "dev"); + }); Assert.IsTrue(environmentVariablesParserProvider.Name == "EnvironmentVariables"); Assert.IsTrue(handler); } diff --git a/src/Contrib/Isolation/UoW/Masa.Contrib.Isolation.UoW.EFCore/DispatcherOptionsExtensions.cs b/src/Contrib/Isolation/UoW/Masa.Contrib.Isolation.UoW.EFCore/DispatcherOptionsExtensions.cs index e5a2e9340..034a0bdbb 100644 --- a/src/Contrib/Isolation/UoW/Masa.Contrib.Isolation.UoW.EFCore/DispatcherOptionsExtensions.cs +++ b/src/Contrib/Isolation/UoW/Masa.Contrib.Isolation.UoW.EFCore/DispatcherOptionsExtensions.cs @@ -22,7 +22,10 @@ public static IEventBusBuilder UseIsolationUoW( bool useTransaction = true) where TDbContext : MasaDbContext, IMasaDbContext where TTenantId : IComparable - => eventBusBuilder.UseIsolationUoW(isolationBuilder, optionsBuilder, disableRollbackOnFailure, + => eventBusBuilder.UseIsolationUoW( + isolationBuilder, + optionsBuilder, + disableRollbackOnFailure, useTransaction); public static IEventBusBuilder UseIsolationUoW( @@ -35,7 +38,7 @@ public static IEventBusBuilder UseIsolationUoW( where TTenantId : IComparable where TUserId : IComparable { - eventBusBuilder.Services.UseIsolationUoW(); + eventBusBuilder.Services.UseIsolationUoW(); return eventBusBuilder.UseIsolation(isolationBuilder) .UseUoW(optionsBuilder, disableRollbackOnFailure, useTransaction); } @@ -70,12 +73,15 @@ public static IDispatcherOptions UseIsolationUoW where TTenantId : IComparable where TUserId : IComparable { - options.Services.UseIsolationUoW(); + options.Services.UseIsolationUoW(); return options.UseIsolation(isolationBuilder) .UseUoW(optionsBuilder, disableRollbackOnFailure, useTransaction); } - private static void UseIsolationUoW(this IServiceCollection services) where TTenantId : IComparable - => services.TryAddEnumerable(new ServiceDescriptor(typeof(ISaveChangesFilter), typeof(IsolationSaveChangesFilter), + private static void UseIsolationUoW(this IServiceCollection services) + where TTenantId : IComparable + where TDbContext : MasaDbContext, IMasaDbContext + => services.TryAddEnumerable(new ServiceDescriptor(typeof(ISaveChangesFilter), + typeof(IsolationSaveChangesFilter), ServiceLifetime.Scoped)); } diff --git a/src/Contrib/Isolation/UoW/Masa.Contrib.Isolation.UoW.EFCore/IsolationDbContext.cs b/src/Contrib/Isolation/UoW/Masa.Contrib.Isolation.UoW.EFCore/IsolationDbContext.cs index e45c93a2c..520b328eb 100644 --- a/src/Contrib/Isolation/UoW/Masa.Contrib.Isolation.UoW.EFCore/IsolationDbContext.cs +++ b/src/Contrib/Isolation/UoW/Masa.Contrib.Isolation.UoW.EFCore/IsolationDbContext.cs @@ -10,7 +10,7 @@ namespace Masa.Contrib.Isolation.UoW.EFCore; /// public abstract class IsolationDbContext : IsolationDbContext where TKey : IComparable - where TDbContext : DbContext, IMasaDbContext + where TDbContext : MasaDbContext, IMasaDbContext { protected IsolationDbContext(MasaDbContextOptions options) : base(options) { @@ -20,7 +20,6 @@ protected IsolationDbContext(MasaDbContextOptions options) : base(op /// /// DbContext providing isolation /// -/// tenant id type public abstract class IsolationDbContext : IsolationDbContext { protected IsolationDbContext(MasaDbContextOptions options) : base(options) @@ -38,7 +37,7 @@ public abstract class IsolationDbContext : MasaDbContext private readonly IMultiEnvironmentContext? _environmentContext; private readonly IMultiTenantContext? _tenantContext; - public IsolationDbContext(MasaDbContextOptions options) : base(options) + protected IsolationDbContext(MasaDbContextOptions options) : base(options) { _environmentContext = options.ServiceProvider?.GetService(); _tenantContext = options.ServiceProvider?.GetService(); @@ -53,23 +52,23 @@ public IsolationDbContext(MasaDbContextOptions options) : base(options) { string defaultTenantId = default(TKey)?.ToString() ?? string.Empty; Expression> tenantFilter = entity => !IsTenantFilterEnabled || - (Microsoft.EntityFrameworkCore.EF.Property(entity, nameof(IMultiTenant.TenantId)).ToString() ?? string.Empty) + (EF.Property(entity, nameof(IMultiTenant.TenantId)).ToString() ?? string.Empty) .Equals(_tenantContext.CurrentTenant != null ? _tenantContext.CurrentTenant.Id : defaultTenantId); - expression = ExpressionExtensions.And(tenantFilter, expression != null, expression); + expression = tenantFilter.And(expression != null, expression); } if (typeof(IMultiEnvironment).IsAssignableFrom(typeof(TEntity)) && _environmentContext != null) { Expression> envFilter = entity => !IsEnvironmentFilterEnabled || - Microsoft.EntityFrameworkCore.EF.Property(entity, nameof(IMultiEnvironment.Environment)) + EF.Property(entity, nameof(IMultiEnvironment.Environment)) .Equals(_environmentContext != null ? _environmentContext.CurrentEnvironment : default); - expression = ExpressionExtensions.And(envFilter, expression != null, expression); + expression = envFilter.And(expression != null, expression); } var secondExpression = base.CreateFilterExpression(); if (secondExpression != null) - expression = ExpressionExtensions.And(secondExpression, expression != null, expression); + expression = secondExpression.And(expression != null, expression); return expression; } diff --git a/src/Contrib/Isolation/UoW/Masa.Contrib.Isolation.UoW.EFCore/IsolationSaveChangesFilter.cs b/src/Contrib/Isolation/UoW/Masa.Contrib.Isolation.UoW.EFCore/IsolationSaveChangesFilter.cs index d561cd437..895c21b15 100644 --- a/src/Contrib/Isolation/UoW/Masa.Contrib.Isolation.UoW.EFCore/IsolationSaveChangesFilter.cs +++ b/src/Contrib/Isolation/UoW/Masa.Contrib.Isolation.UoW.EFCore/IsolationSaveChangesFilter.cs @@ -3,7 +3,9 @@ namespace Masa.Contrib.Isolation.UoW.EFCore; -public class IsolationSaveChangesFilter : ISaveChangesFilter where TTenantId : IComparable +public class IsolationSaveChangesFilter : ISaveChangesFilter + where TDbContext : MasaDbContext, IMasaDbContext + where TTenantId : IComparable { private readonly IMultiTenantContext? _tenantContext; private readonly IConvertProvider? _convertProvider; diff --git a/src/Contrib/Isolation/UoW/Tests/Masa.Contrib.Isolation.UoW.EFCore.Web.Tests/EdgeDriverTest.cs b/src/Contrib/Isolation/UoW/Tests/Masa.Contrib.Isolation.UoW.EFCore.Web.Tests/EdgeDriverTest.cs index a90eb6fe8..e626090a9 100644 --- a/src/Contrib/Isolation/UoW/Tests/Masa.Contrib.Isolation.UoW.EFCore.Web.Tests/EdgeDriverTest.cs +++ b/src/Contrib/Isolation/UoW/Tests/Masa.Contrib.Isolation.UoW.EFCore.Web.Tests/EdgeDriverTest.cs @@ -19,7 +19,7 @@ public void Initialize() _services.AddSingleton(configurationRoot); _services.AddEventBus(eventBusBuilder => eventBusBuilder.UseIsolationUoW( isolationBuilder => isolationBuilder.UseMultiTenant("tenant").UseMultiEnvironment("env"), dbOptions => dbOptions.UseSqlite().UseFilter())); - System.Environment.SetEnvironmentVariable("env", "pro"); + Environment.SetEnvironmentVariable("env", "pro"); } [TestMethod] @@ -39,7 +39,7 @@ public async Task TestTenantAsync() #endregion var registerUserEvent = new RegisterUserEvent("jim", "123456"); - var eventBus = serviceProvider.GetRequiredService(); + var eventBus = serviceProvider.CreateScope().ServiceProvider.GetRequiredService(); await eventBus.PublishAsync(registerUserEvent); } diff --git a/src/Contrib/Isolation/UoW/Tests/Masa.Contrib.Isolation.UoW.EFCore.Web.Tests/EventHandlers/RegisterUserEventHandler.cs b/src/Contrib/Isolation/UoW/Tests/Masa.Contrib.Isolation.UoW.EFCore.Web.Tests/EventHandlers/RegisterUserEventHandler.cs index 5c094c007..940e05fb5 100644 --- a/src/Contrib/Isolation/UoW/Tests/Masa.Contrib.Isolation.UoW.EFCore.Web.Tests/EventHandlers/RegisterUserEventHandler.cs +++ b/src/Contrib/Isolation/UoW/Tests/Masa.Contrib.Isolation.UoW.EFCore.Web.Tests/EventHandlers/RegisterUserEventHandler.cs @@ -10,7 +10,9 @@ public class RegisterUserEventHandler private readonly IMultiEnvironmentSetter _environmentSetter; private readonly IMultiEnvironmentContext _environmentContext; - public RegisterUserEventHandler(CustomDbContext customDbContext, IDataFilter dataFilter, IMultiEnvironmentSetter environmentSetter, + public RegisterUserEventHandler(CustomDbContext customDbContext, + IDataFilter dataFilter, + IMultiEnvironmentSetter environmentSetter, IMultiEnvironmentContext environmentContext) { _customDbContext = customDbContext;