pull/11147/head
Alper Ebicoglu 4 years ago
commit ff58c79591

@ -1 +1,238 @@
TODO..
# Web应用程序开发教程 - 第七章: 数据库集成
````json
//[doc-params]
{
"UI": ["MVC","Blazor","BlazorServer","NG"],
"DB": ["EF","Mongo"]
}
````
## 关于本教程
在本系列教程中, 你将构建一个名为 `Acme.BookStore` 的用于管理书籍及其作者列表的基于ABP的应用程序. 它是使用以下技术开发的:
* **{{DB_Text}}** 做为ORM提供程序.
* **{{UI_Value}}** 做为UI框架.
本教程分为以下部分:
- [Part 1: 创建服务端](Part-1.md)
- [Part 2: 图书列表页面](Part-2.md)
- [Part 3: 创建,更新和删除图书](Part-2.md)
- [Part 4: 集成测试](Part-4.md)
- [Part 5: 授权](Part-5.md)
- [Part 6: 作者: 领域层](Part-6.md)
- **Part 7: 数据库集成**(本章)
- [Part 8: 作者: 应用服务层](Part-8.md)
- [Part 9: 作者: 用户页面](Part-9.md)
- [Part 10: 图书到作者的关系](Part-10.md)
## 下载源码
本教程根据你的**UI** 和 **数据库**偏好有多个版本,我们准备了几种可供下载的源码组合:
* [MVC (Razor Pages) UI 与 EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore)
* [Blazor UI 与 EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Blazor-EfCore)
* [Angular UI 与 MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb)
> 如果你在Windows中遇到 "文件名太长" or "解压错误", 很可能与Windows最大文件路径限制有关. Windows文件路径的最大长度为250字符. 为了解决这个问题,参阅 [在Windows 10中启用长路径](https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=cmd#enable-long-paths-in-windows-10-version-1607-and-later).
> 如果你遇到与Git相关的长路径错误, 尝试使用下面的命令在Windows中启用长路径. 参阅 https://github.com/msysgit/msysgit/wiki/Git-cannot-create-a-file-or-directory-with-a-long-path
> `git config --system core.longpaths true`
## 简介
这章阐述如何为前一章介绍的 `作者` 实体配置数据库集成.
{{if DB=="EF"}}
## DB Context
打开 `Acme.BookStore.EntityFrameworkCore` 项目中的 `BookStoreDbContext` 加入 `DbSet` 属性:
````csharp
public DbSet<Author> Authors { get; set; }
````
定位到相同项目中的 `BookStoreDbContext` 类中的 `OnModelCreating` 方法, 加入以下代码到方法的结尾:
````csharp
builder.Entity<Author>(b =>
{
b.ToTable(BookStoreConsts.DbTablePrefix + "Authors",
BookStoreConsts.DbSchema);
b.ConfigureByConvention();
b.Property(x => x.Name)
.IsRequired()
.HasMaxLength(AuthorConsts.MaxNameLength);
b.HasIndex(x => x.Name);
});
````
这和前面的 `Book` 实体做的一样, 所以不再赘述.
## 创建数据库迁移
配置启动解决方案为使用 [Entity Framework Core Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/). 因为我们还没有修改数据库映射配置,所以需要创建一个新的迁移并对数据库应用变更.
打开命令行终端, 切换当前目录为 `Acme.BookStore.EntityFrameworkCore` 项目目录, 输入以下命令:
````bash
dotnet ef migrations add Added_Authors
````
这会在项目中添加一个迁移类:
![bookstore-efcore-migration-authors](./images/bookstore-efcore-migration-authors.png)
你可以在同一个命令行终端中使用以下命令对数据库应用更改:
````bash
dotnet ef database update
````
> 如果你使用 Visual Studio, 可能希望在 *Package Manager Console (PMC)* 使用 `Add-Migration Added_Authors -c BookStoreMigrationsDbContext``Update-Database -c BookStoreMigrationsDbContext` 命令. 如果这样, 保证 {{if UI=="MVC"}}`Acme.BookStore.Web`{{else if UI=="BlazorServer"}}`Acme.BookStore.Blazor`{{else if UI=="Blazor" || UI=="NG"}}`Acme.BookStore.HttpApi.Host`{{end}} 是启动项目并且在PMC中 `Acme.BookStore.EntityFrameworkCore`*默认项目* .
{{else if DB=="Mongo"}}
## DB Context
打开 `Acme.BookStore.MongoDB` 项目 `MongoDb 文件夹`中的 `BookStoreMongoDbContext`, 在类中加入以下属性:
````csharp
public IMongoCollection<Author> Authors => Collection<Author>();
````
{{end}}
## 实现 IAuthorRepository
{{if DB=="EF"}}
`Acme.BookStore.EntityFrameworkCore` 项目 (`Authors` 文件夹)中创建一个新类 `EfCoreAuthorRepository`, 粘贴以下代码:
````csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Threading.Tasks;
using Acme.BookStore.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Volo.Abp.Domain.Repositories.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore;
namespace Acme.BookStore.Authors
{
public class EfCoreAuthorRepository
: EfCoreRepository<BookStoreDbContext, Author, Guid>,
IAuthorRepository
{
public EfCoreAuthorRepository(
IDbContextProvider<BookStoreDbContext> dbContextProvider)
: base(dbContextProvider)
{
}
public async Task<Author> FindByNameAsync(string name)
{
var dbSet = await GetDbSetAsync();
return await dbSet.FirstOrDefaultAsync(author => author.Name == name);
}
public async Task<List<Author>> GetListAsync(
int skipCount,
int maxResultCount,
string sorting,
string filter = null)
{
var dbSet = await GetDbSetAsync();
return await dbSet
.WhereIf(
!filter.IsNullOrWhiteSpace(),
author => author.Name.Contains(filter)
)
.OrderBy(sorting)
.Skip(skipCount)
.Take(maxResultCount)
.ToListAsync();
}
}
}
````
* 继承自 `EfCoreRepository`, 所以继承了标准repository的方法实现.
* `WhereIf` 是ABP 框架的快捷扩展方法. 它仅当第一个条件满足时, 执行 `Where` 查询. (根据名字查询, 仅当 filter 不为空). 你可以不使用这个方法, 但这些快捷方法可以提高效率.
* `sorting` 可以是一个字符串, 如 `Name`, `Name ASC``Name DESC`. 通过使用 [System.Linq.Dynamic.Core](https://www.nuget.org/packages/System.Linq.Dynamic.Core) NuGet 包是可能的.
> 参阅 [EF Core 集成文档](../Entity-Framework-Core.md) 获得基于EF Core的repositories的更多信息.
{{else if DB=="Mongo"}}
`Acme.BookStore.MongoDB` 项目 (`Authors` 文件夹)中创建一个新类 `MongoDbAuthorRepository`, 粘贴以下代码:
```csharp
using System;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Collections.Generic;
using System.Threading.Tasks;
using Acme.BookStore.MongoDB;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
using Volo.Abp.Domain.Repositories.MongoDB;
using Volo.Abp.MongoDB;
namespace Acme.BookStore.Authors
{
public class MongoDbAuthorRepository
: MongoDbRepository<BookStoreMongoDbContext, Author, Guid>,
IAuthorRepository
{
public MongoDbAuthorRepository(
IMongoDbContextProvider<BookStoreMongoDbContext> dbContextProvider
) : base(dbContextProvider)
{
}
public async Task<Author> FindByNameAsync(string name)
{
var queryable = await GetMongoQueryableAsync();
return await queryable.FirstOrDefaultAsync(author => author.Name == name);
}
public async Task<List<Author>> GetListAsync(
int skipCount,
int maxResultCount,
string sorting,
string filter = null)
{
var queryable = await GetMongoQueryableAsync();
return await queryable
.WhereIf<Author, IMongoQueryable<Author>>(
!filter.IsNullOrWhiteSpace(),
author => author.Name.Contains(filter)
)
.OrderBy(sorting)
.As<IMongoQueryable<Author>>()
.Skip(skipCount)
.Take(maxResultCount)
.ToListAsync();
}
}
}
```
* 继承自 `MongoDbRepository`, 所以继承了标准repository的方法实现.
* `WhereIf` 是ABP 框架的快捷扩展方法. 它仅当第一个条件满足时, 执行 `Where` 查询. (根据名字查询, 仅当 filter 不为空). 你可以不使用这个方法, 但这些快捷方法可以提高效率.
* `sorting` 可以是一个字符串, 如 `Name`, `Name ASC``Name DESC`. 通过使用 [System.Linq.Dynamic.Core](https://www.nuget.org/packages/System.Linq.Dynamic.Core) NuGet 包是可能的.
> 参阅 [MongoDB 集成文档](../MongoDB.md) 获得基于MongoDB的repositories的更多信息.
{{end}}
## 下一章
查看本教程的[下一章](Part-8.md).

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

@ -10,7 +10,7 @@ namespace Volo.Abp.BackgroundWorkers.Hangfire;
[DependsOn(
typeof(AbpBackgroundWorkersModule),
typeof(AbpHangfireModule))]
public class AbpBackgroundWorkerHangfireModule : AbpModule
public class AbpBackgroundWorkersHangfireModule : AbpModule
{
public override void OnPreApplicationInitialization(ApplicationInitializationContext context)
{

@ -21,7 +21,7 @@ public class HangfireBackgroundWorkerManager : IBackgroundWorkerManager, ISingle
return Task.CompletedTask;
}
public async Task AddAsync(IBackgroundWorker worker)
public Task AddAsync(IBackgroundWorker worker)
{
if (worker is IHangfireBackgroundWorker hangfireBackgroundWorker)
{
@ -42,18 +42,26 @@ public class HangfireBackgroundWorkerManager : IBackgroundWorkerManager, ISingle
if (worker is AsyncPeriodicBackgroundWorkerBase or PeriodicBackgroundWorkerBase)
{
var timer = (AbpAsyncTimer) worker.GetType()
var timer = worker.GetType()
.GetProperty("Timer", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(worker);
period = timer?.Period;
if (worker is AsyncPeriodicBackgroundWorkerBase)
{
period = ((AbpAsyncTimer)timer)?.Period;
}
else
{
period = ((AbpTimer)timer)?.Period;
}
}
else
{
return;
return Task.CompletedTask;
}
if (period == null)
{
return;
return Task.CompletedTask;
}
var adapterType = typeof(HangfirePeriodicBackgroundWorkerAdapter<>).MakeGenericType(worker.GetType());
@ -61,6 +69,8 @@ public class HangfireBackgroundWorkerManager : IBackgroundWorkerManager, ISingle
RecurringJob.AddOrUpdate(() => workerAdapter.DoWorkAsync(), GetCron(period.Value));
}
return Task.CompletedTask;
}
protected virtual string GetCron(int period)
@ -82,7 +92,7 @@ public class HangfireBackgroundWorkerManager : IBackgroundWorkerManager, ISingle
}
else
{
cron = $"0 0 */{time.TotalDays} * *";
throw new AbpException($"Cannot convert period: {period} to cron expression, use HangfireBackgroundWorkerBase to define worker");
}
return cron;

@ -28,15 +28,23 @@ public class QuartzPeriodicBackgroundWorkerAdapter<TWorker> : QuartzBackgroundWo
int? period;
var workerType = worker.GetType();
if (worker is AsyncPeriodicBackgroundWorkerBase || worker is PeriodicBackgroundWorkerBase)
if (worker is AsyncPeriodicBackgroundWorkerBase or PeriodicBackgroundWorkerBase)
{
if (typeof(TWorker) != worker.GetType())
{
throw new ArgumentException($"{nameof(worker)} type is different from the generic type");
}
var timer = (AbpAsyncTimer)worker.GetType().GetProperty("Timer", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(worker);
period = timer?.Period;
var timer = worker.GetType().GetProperty("Timer", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(worker);
if (worker is AsyncPeriodicBackgroundWorkerBase)
{
period = ((AbpAsyncTimer)timer)?.Period;
}
else
{
period = ((AbpTimer)timer)?.Period;
}
}
else
{
@ -60,26 +68,26 @@ public class QuartzPeriodicBackgroundWorkerAdapter<TWorker> : QuartzBackgroundWo
public override async Task Execute(IJobExecutionContext context)
{
var worker = (IBackgroundWorker)ServiceProvider.GetService(typeof(TWorker));
var worker = (IBackgroundWorker) ServiceProvider.GetService(typeof(TWorker));
var workerContext = new PeriodicBackgroundWorkerContext(ServiceProvider);
switch (worker)
{
case AsyncPeriodicBackgroundWorkerBase asyncWorker:
{
if (_doWorkAsyncMethod != null)
{
if (_doWorkAsyncMethod != null)
{
await (Task)_doWorkAsyncMethod.Invoke(asyncWorker, new object[] { workerContext });
}
break;
await (Task) _doWorkAsyncMethod.Invoke(asyncWorker, new object[] {workerContext});
}
break;
}
case PeriodicBackgroundWorkerBase syncWorker:
{
_doWorkMethod?.Invoke(syncWorker, new object[] { workerContext });
{
_doWorkMethod?.Invoke(syncWorker, new object[] {workerContext});
break;
}
break;
}
}
}
}

@ -12,6 +12,7 @@ public static class ModuleProjectBuildPipelineBuilder
pipeline.Steps.Add(new FileEntryListReadStep());
pipeline.Steps.Add(new ProjectReferenceReplaceStep());
pipeline.Steps.Add(new ReplaceCommonPropsStep());
pipeline.Steps.Add(new MakeProxyJsonFileEmbeddedStep());
pipeline.Steps.Add(new ReplaceConfigureAwaitPropsStep());
pipeline.Steps.Add(new UpdateNuGetConfigStep("/NuGet.Config"));
pipeline.Steps.Add(new CreateProjectResultZipStep());

@ -0,0 +1,38 @@
using System.Linq;
using System.Xml.Linq;
using Volo.Abp.Cli.Utils;
namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps;
public class MakeProxyJsonFileEmbeddedStep : ProjectBuildPipelineStep
{
public override void Execute(ProjectBuildContext context)
{
foreach (var file in context.Files.Where(x => x.Name.EndsWith(".HttpApi.Client.csproj")))
{
using (var stream = StreamHelper.GenerateStreamFromString(file.Content))
{
var doc = XDocument.Load(stream);
if (doc.Root == null)
{
continue;
}
var itemGroupNode =
new XElement("ItemGroup",
new XElement("EmbeddedResource",
new XAttribute("Include", @"**\*generate-proxy.json")
),
new XElement("Content",
new XAttribute("Remove", @"**\*generate-proxy.json")
)
);
doc.Root.Add(itemGroupNode);
file.SetContent(doc.ToString());
}
}
}
}

@ -12,4 +12,11 @@ public class PreConfigureActionList<TOptions> : List<Action<TOptions>>
action(options);
}
}
public TOptions Configure()
{
var options = Activator.CreateInstance<TOptions>();
Configure(options);
return options;
}
}

@ -11,18 +11,17 @@ public class AbpEventBusRebusModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
var options = context.Services.ExecutePreConfiguredActions<AbpRebusEventBusOptions>(); ;
context.Services.AddTransient(typeof(IHandleMessages<>), typeof(RebusDistributedEventHandlerAdapter<>));
var preActions = context.Services.GetPreConfigureActions<AbpRebusEventBusOptions>();
Configure<AbpRebusEventBusOptions>(rebusOptions =>
{
context.Services.ExecutePreConfiguredActions(rebusOptions);
preActions.Configure(rebusOptions);
});
context.Services.AddRebus(configure =>
{
options.Configurer?.Invoke(configure);
preActions.Configure().Configurer?.Invoke(configure);
return configure;
});
}
@ -36,4 +35,4 @@ public class AbpEventBusRebusModule : AbpModule
.GetRequiredService<RebusDistributedEventBus>()
.Initialize();
}
}
}

@ -1,4 +1,4 @@
using System;
using System;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
@ -6,6 +6,7 @@ using Microsoft.Extensions.Options;
using Volo.Abp.Collections;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus.Local;
using Volo.Abp.Uow;
namespace Volo.Abp.EventBus.Distributed;
@ -15,16 +16,19 @@ public class LocalDistributedEventBus : IDistributedEventBus, ISingletonDependen
{
private readonly ILocalEventBus _localEventBus;
protected IUnitOfWorkManager UnitOfWorkManager { get; }
protected IServiceScopeFactory ServiceScopeFactory { get; }
protected AbpDistributedEventBusOptions AbpDistributedEventBusOptions { get; }
public LocalDistributedEventBus(
ILocalEventBus localEventBus,
IUnitOfWorkManager unitOfWorkManager,
IServiceScopeFactory serviceScopeFactory,
IOptions<AbpDistributedEventBusOptions> distributedEventBusOptions)
{
_localEventBus = localEventBus;
UnitOfWorkManager = unitOfWorkManager;
ServiceScopeFactory = serviceScopeFactory;
AbpDistributedEventBusOptions = distributedEventBusOptions.Value;
Subscribe(distributedEventBusOptions.Value.Handlers);
@ -122,24 +126,56 @@ public class LocalDistributedEventBus : IDistributedEventBus, ISingletonDependen
_localEventBus.UnsubscribeAll(eventType);
}
public Task PublishAsync<TEvent>(TEvent eventData, bool onUnitOfWorkComplete = true)
public async Task PublishAsync<TEvent>(TEvent eventData, bool onUnitOfWorkComplete = true)
where TEvent : class
{
return _localEventBus.PublishAsync(eventData, onUnitOfWorkComplete);
await PublishAsync(typeof(TEvent), eventData, onUnitOfWorkComplete);
}
public Task PublishAsync(Type eventType, object eventData, bool onUnitOfWorkComplete = true)
public async Task PublishAsync(Type eventType, object eventData, bool onUnitOfWorkComplete = true)
{
return _localEventBus.PublishAsync(eventType, eventData, onUnitOfWorkComplete);
if (onUnitOfWorkComplete && UnitOfWorkManager.Current != null)
{
AddToUnitOfWork(
UnitOfWorkManager.Current,
new UnitOfWorkEventRecord(eventType, eventData, EventOrderGenerator.GetNext())
);
return;
}
await _localEventBus.PublishAsync(eventType, eventData, onUnitOfWorkComplete: false);
}
public async Task PublishAsync<TEvent>(TEvent eventData, bool onUnitOfWorkComplete = true, bool useOutbox = true)
where TEvent : class
{
await PublishAsync(typeof(TEvent), eventData, onUnitOfWorkComplete, useOutbox);
}
public Task PublishAsync<TEvent>(TEvent eventData, bool onUnitOfWorkComplete = true, bool useOutbox = true) where TEvent : class
public async Task PublishAsync(Type eventType, object eventData, bool onUnitOfWorkComplete = true, bool useOutbox = true)
{
return _localEventBus.PublishAsync(eventData, onUnitOfWorkComplete);
if (onUnitOfWorkComplete && UnitOfWorkManager.Current != null)
{
AddToUnitOfWork(
UnitOfWorkManager.Current,
new UnitOfWorkEventRecord(eventType, eventData, EventOrderGenerator.GetNext(), useOutbox)
);
return;
}
if (useOutbox && UnitOfWorkManager.Current != null)
{
UnitOfWorkManager.Current.OnCompleted(async() => {
await _localEventBus.PublishAsync(eventType, eventData, onUnitOfWorkComplete: false);
});
return;
}
await _localEventBus.PublishAsync(eventType, eventData, onUnitOfWorkComplete: false);
}
public Task PublishAsync(Type eventType, object eventData, bool onUnitOfWorkComplete = true, bool useOutbox = true)
protected virtual void AddToUnitOfWork(IUnitOfWork unitOfWork, UnitOfWorkEventRecord eventRecord)
{
return _localEventBus.PublishAsync(eventType, eventData, onUnitOfWorkComplete);
unitOfWork.AddOrReplaceDistributedEvent(eventRecord);
}
}

@ -13,9 +13,10 @@ public class AbpHangfireModule : AbpModule
public override void ConfigureServices(ServiceConfigurationContext context)
{
var preActions = context.Services.GetPreConfigureActions<IGlobalConfiguration>();
context.Services.AddHangfire(configuration =>
{
context.Services.ExecutePreConfiguredActions(configuration);
preActions.Configure(configuration);
});
}

@ -21,6 +21,7 @@ namespace Volo.Abp.Http.Client.Web.Conventions;
public class AbpHttpClientProxyServiceConvention : AbpServiceConvention
{
protected readonly IClientProxyApiDescriptionFinder ClientProxyApiDescriptionFinder;
protected readonly List<ControllerModel> ControllerWithAttributeRoute;
protected readonly List<ActionModel> ActionWithAttributeRoute;
public AbpHttpClientProxyServiceConvention(
@ -30,6 +31,7 @@ public class AbpHttpClientProxyServiceConvention : AbpServiceConvention
: base(options, conventionalRouteBuilder)
{
ClientProxyApiDescriptionFinder = clientProxyApiDescriptionFinder;
ControllerWithAttributeRoute = new List<ControllerModel>();
ActionWithAttributeRoute = new List<ActionModel>();
}
@ -51,15 +53,6 @@ public class AbpHttpClientProxyServiceConvention : AbpServiceConvention
{
controller.ControllerName = controller.ControllerName.RemovePostFix("ClientProxy");
var moduleApiDescription = FindModuleApiDescriptionModel(controller);
if (moduleApiDescription != null && !moduleApiDescription.RootPath.IsNullOrWhiteSpace())
{
var selector = controller.Selectors.FirstOrDefault();
selector?.EndpointMetadata.Add(new AreaAttribute(moduleApiDescription.RootPath));
controller.RouteValues.Add(new KeyValuePair<string, string>("area", moduleApiDescription.RootPath));
}
var controllerApiDescription = FindControllerApiDescriptionModel(controller);
if (controllerApiDescription != null &&
!controllerApiDescription.ControllerGroupName.IsNullOrWhiteSpace())
@ -77,6 +70,14 @@ public class AbpHttpClientProxyServiceConvention : AbpServiceConvention
{
RemoveEmptySelectors(controller.Selectors);
var moduleApiDescription = FindModuleApiDescriptionModel(controller);
if (moduleApiDescription != null && !moduleApiDescription.RootPath.IsNullOrWhiteSpace())
{
var selector = controller.Selectors.FirstOrDefault();
selector?.EndpointMetadata.Add(new AreaAttribute(moduleApiDescription.RootPath));
controller.RouteValues.Add(new KeyValuePair<string, string>("area", moduleApiDescription.RootPath));
}
var controllerType = controller.ControllerType.AsType();
var remoteServiceAtt = ReflectionHelper.GetSingleAttributeOrDefault<RemoteServiceAttribute>(controllerType.GetTypeInfo());
if (remoteServiceAtt != null && !remoteServiceAtt.IsEnabledFor(controllerType))
@ -111,15 +112,14 @@ public class AbpHttpClientProxyServiceConvention : AbpServiceConvention
return;;
}
ControllerWithAttributeRoute.Add(controller);
ActionWithAttributeRoute.Add(action);
if (!action.Selectors.Any())
{
var abpServiceSelectorModel = new SelectorModel
{
AttributeRouteModel = new AttributeRouteModel(
new RouteAttribute(template: actionApiDescriptionModel.Url)
),
AttributeRouteModel = new AttributeRouteModel(new RouteAttribute(template: actionApiDescriptionModel.Url)),
ActionConstraints = { new HttpMethodActionConstraint(new[] { actionApiDescriptionModel.HttpMethod }) }
};
@ -142,9 +142,7 @@ public class AbpHttpClientProxyServiceConvention : AbpServiceConvention
if (selector.AttributeRouteModel == null)
{
selector.AttributeRouteModel = new AttributeRouteModel(
new RouteAttribute(template: actionApiDescriptionModel.Url)
);
selector.AttributeRouteModel = new AttributeRouteModel(new RouteAttribute(template: actionApiDescriptionModel.Url));
}
if (!selector.ActionConstraints.OfType<HttpMethodActionConstraint>().Any())
@ -157,14 +155,17 @@ public class AbpHttpClientProxyServiceConvention : AbpServiceConvention
protected virtual void ConfigureClientProxyApiExplorer(ControllerModel controller)
{
if (controller.ApiExplorer.GroupName.IsNullOrEmpty())
if (ControllerWithAttributeRoute.Contains(controller))
{
controller.ApiExplorer.GroupName = controller.ControllerName;
}
if (controller.ApiExplorer.GroupName.IsNullOrEmpty())
{
controller.ApiExplorer.GroupName = controller.ControllerName;
}
if (controller.ApiExplorer.IsVisible == null)
{
controller.ApiExplorer.IsVisible = IsVisibleRemoteService(controller.ControllerType);
if (controller.ApiExplorer.IsVisible == null)
{
controller.ApiExplorer.IsVisible = IsVisibleRemoteService(controller.ControllerType);
}
}
foreach (var action in controller.Actions)

@ -16,12 +16,11 @@ public class AbpJsonModule : AbpModule
context.Services.TryAddEnumerable(ServiceDescriptor
.Transient<IConfigureOptions<AbpSystemTextJsonSerializerOptions>, AbpSystemTextJsonSerializerOptionsSetup>());
var preActions = context.Services.GetPreConfigureActions<AbpJsonOptions>();
Configure<AbpJsonOptions>(options =>
{
options.Providers.Add<AbpNewtonsoftJsonSerializerProvider>();
var abpJsonOptions = context.Services.ExecutePreConfiguredActions<AbpJsonOptions>();
if (abpJsonOptions.UseHybridSerializer)
if (preActions.Configure().UseHybridSerializer)
{
options.Providers.Add<AbpSystemTextJsonSerializerProvider>();
}

@ -38,9 +38,10 @@ public class AbpAspNetCoreMvcVersioningTestModule : AbpModule
public override void ConfigureServices(ServiceConfigurationContext context)
{
var preActions = context.Services.GetPreConfigureActions<AbpAspNetCoreMvcOptions>();
Configure<AbpAspNetCoreMvcOptions>(options =>
{
context.Services.ExecutePreConfiguredActions(options);
preActions.Configure(options);
});
context.Services.AddAbpApiVersioning(options =>
@ -48,11 +49,10 @@ public class AbpAspNetCoreMvcVersioningTestModule : AbpModule
options.ReportApiVersions = true;
options.AssumeDefaultVersionWhenUnspecified = true;
//options.ApiVersionReader = new HeaderApiVersionReader("api-version"); //Supports header too
//options.ApiVersionReader = new MediaTypeApiVersionReader(); //Supports accept header too
//options.ApiVersionReader = new HeaderApiVersionReader("api-version"); //Supports header too
//options.ApiVersionReader = new MediaTypeApiVersionReader(); //Supports accept header too
var mvcOptions = context.Services.ExecutePreConfiguredActions<AbpAspNetCoreMvcOptions>();
options.ConfigureAbp(mvcOptions);
options.ConfigureAbp(preActions.Configure());
});
context.Services.AddHttpClientProxies(typeof(AbpAspNetCoreMvcVersioningTestModule).Assembly);

@ -1,7 +1,9 @@
using System;
using System.Threading.Tasks;
using Shouldly;
using Volo.Abp.Domain.Entities.Events.Distributed;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Uow;
using Xunit;
namespace Volo.Abp.EventBus.Distributed;
@ -55,11 +57,75 @@ public class LocalDistributedEventBus_Test : LocalDistributedEventBusTestBase
await DistributedEventBus.PublishAsync(new MySimpleEto
{
Properties =
{
{"TenantId", tenantId.ToString()}
}
{
{"TenantId", tenantId.ToString()}
}
});
Assert.Equal(tenantId, MySimpleDistributedSingleInstanceEventHandler.TenantId);
}
[Fact]
public async Task Event_Should_Published_On_UnitOfWorkComplete()
{
var id = 0;
DistributedEventBus.Subscribe<MySimpleEventData>(data =>
{
id = data.Value;
return Task.CompletedTask;
});
var unitOfWorkManager = GetRequiredService<IUnitOfWorkManager>();
using (var uow = unitOfWorkManager.Begin())
{
await DistributedEventBus.PublishAsync(new MySimpleEventData(3), onUnitOfWorkComplete: true, useOutbox: false);
}
id.ShouldBe(0);
using (var uow = unitOfWorkManager.Begin())
{
await DistributedEventBus.PublishAsync(new MySimpleEventData(3), onUnitOfWorkComplete: true, useOutbox: false);
await uow.CompleteAsync();
}
id.ShouldBe(3);
id = 0;
using (var uow = unitOfWorkManager.Begin())
{
await DistributedEventBus.PublishAsync(new MySimpleEventData(3), onUnitOfWorkComplete: false, useOutbox: false);
}
id.ShouldBe(3);
}
[Fact]
public async Task Event_Should_Published_On_UnitOfWorkComplete_UseOutbox()
{
var id = 0;
DistributedEventBus.Subscribe<MySimpleEventData>(data =>
{
id = data.Value;
return Task.CompletedTask;
});
var unitOfWorkManager = GetRequiredService<IUnitOfWorkManager>();
using (var uow = unitOfWorkManager.Begin())
{
await DistributedEventBus.PublishAsync(new MySimpleEventData(3), onUnitOfWorkComplete: false, useOutbox: true);
}
id.ShouldBe(0);
using (var uow = unitOfWorkManager.Begin())
{
await DistributedEventBus.PublishAsync(new MySimpleEventData(3), onUnitOfWorkComplete: false, useOutbox: true);
await uow.CompleteAsync();
}
id.ShouldBe(3);
id = 0;
using (var uow = unitOfWorkManager.Begin())
{
await DistributedEventBus.PublishAsync(new MySimpleEventData(3), onUnitOfWorkComplete: false, useOutbox: false);
}
id.ShouldBe(3);
}
}

@ -56,6 +56,17 @@
"@angular/platform-browser": "12.2.13",
"@angular/platform-browser-dynamic": "12.2.13",
"@angular/router": "12.2.13",
"@abp/ng.account": "~5.0.1",
"@abp/ng.account.core": "~5.0.1",
"@abp/ng.core": "~5.0.1",
"@abp/ng.feature-management": "~5.0.1",
"@abp/ng.identity": "~5.0.1",
"@abp/ng.permission-management": "~5.0.1",
"@abp/ng.schematics": "~5.0.1",
"@abp/ng.setting-management": "~5.0.1",
"@abp/ng.tenant-management": "~5.0.1",
"@abp/ng.theme.basic": "~5.0.1",
"@abp/ng.theme.shared": "~5.0.1",
"@fortawesome/fontawesome-free": "^5.15.4",
"@ng-bootstrap/ng-bootstrap": "11.0.0-beta.2",
"@ngneat/spectator": "^8.0.3",

@ -198,6 +198,5 @@ describe('DynamicLayoutComponent', () => {
spectator.detectComponentChanges();
expect(spectator.query('abp-layout-empty')).toBeFalsy();
expect(spectator.query('abp-dynamic-layout').children[0].tagName).toEqual('ROUTER-OUTLET');
});
});

Loading…
Cancel
Save