diff --git a/docs/en/Startup-Templates/Module.md b/docs/en/Startup-Templates/Module.md index 2dc0896c65..84b7525944 100644 --- a/docs/en/Startup-Templates/Module.md +++ b/docs/en/Startup-Templates/Module.md @@ -1,4 +1,4 @@ -# MVC Module Startup Template +# Module Startup Template This template can be used to create a **reusable [application module](../Modules/Index.md)** based on the [module development best practices & conventions](../Best-Practices/Index.md). It is also suitable for creating **microservices** (with or without UI). @@ -20,6 +20,20 @@ abp new Acme.IssueManagement -t module - `Acme.IssueManagement` is the solution name, like *YourCompany.YourProduct*. You can use single level, two-levels or three-levels naming. +### Specify the UI Framework + +This template provides multiple UI frameworks: + +* `mvc`: ASP.NET Core MVC UI with Razor Pages (default) +* `blazor`: Blazor UI +* `angular`: Angular UI + +Use `-u` or `--ui` option to specify the UI framework: + +````bash +abp new Acme.IssueManagement -t module -u angular +```` + ### Without User Interface The template comes with an MVC UI by default. You can use `--no-ui` option to not include the UI layer. @@ -160,3 +174,91 @@ You should run the application with the given order: - First, run the `.IdentityServer` since other applications depends on it. - Then run the `.HttpApi.Host` since it is used by the `.Web.Host` application. - Finally, you can run the `.Web.Host` project and login to the application using `admin` as the username and `1q2w3E*` as the password. + +## UI + +### Angular UI + +If you choose `Angular` as the UI framework (using the `-u angular` option), the solution will have a folder called `angular` in it. This is where the client-side code is located. When you open that folder in an IDE, the folder structure will look like below: + +![Folder structure of ABP Angular module project](../images/angular-module-folder-structure.png) + +* _angular/projects/issue-management_ folder contains the Angular module project. +* _angular/projects/dev-app_ folder contains a development application that runs your module. + +The server-side is similar to the solution described above. `*.HttpApi.Host` project serves the API and the `Angular` demo application consumes it. You will not need to run the `.Web.Host` project though. + +#### How to Run the Angular Development App + +For module development, you will need the `dev-app` project up and running. So, here is how we can start the development server. + +First, we need to install dependencies: + +1. Open your terminal at the root folder, i.e. `angular`. +2. Run `yarn` or `npm install`. + +The dependencies will be installed and some of them are ABP modules published as NPM packages. To see all ABP packages, you can run the following command in the `angular` folder: + +```bash +yarn list --pattern abp +``` + +> There is no equivalent of this command in npm. + +The module you will develop depends on two of these ABP packages: _@abp/ng.core_ and _@abp/ng.theme.shared_. Rest of the ABP modules are included in _package.json_ because of the `dev-app` project. + +Once all dependencies are installed, follow the steps below to serve your development app: + +1. Make sure `.IdentityServer` and `*.HttpApi.Host` projects are up and running. +2. Open your terminal at the root folder, i.e. `angular`. +3. Run `yarn start` or `npm start`. + +![ABP Angular module dev-app project](../images/angular-module-dev-app-project.png) + +The issue management page is empty in the beginning. You may change the content in `IssueManagementComponent` at the _angular/projects/issue-management/src/lib/issue-management.component.ts_ path and observe that the view changes accordingly. + +Now, let's have a closer look at some key elements of your project. + +#### Main Module + +`IssueManagementModule` at the _angular/projects/issue-management/src/lib/issue-management.module.ts_ path is the main module of your module project. There are a few things worth mentioning in it: + +- Essential ABP modules, i.e. `CoreModule` and `ThemeSharedModule`, are imported. +- `IssueManagementRoutingModule` is imported. +- `IssueManagementComponent` is declared. +- It is prepared for configurability. The `forLazy` static method enables [a configuration to be passed to the module when it is loaded by the router](https://volosoft.com/blog/how-to-configure-angular-modules-loaded-by-the-router). + + +#### Main Routing Module + +`IssueManagementRoutingModule` at the _angular/projects/issue-management/src/lib/issue-management-routing.module.ts_ path is the main routing module of your module project. It currently does two things: + +- Loads `DynamicLayoutComponent` at base path it is given. +- Loads `IssueManagementComponent` as child to the layout, again at the given base path. + +You can rearrange this module to load more than one component at different routes, but you need to update the route provider at _angular/projects/issue-management/config/src/providers/route.provider.ts_ to match the new routing structure with the routes in the menu. Please check [Modifying the Menu](../UI/Angular/Modifying-the-Menu.md) to see how route providers work. + +#### Config Module + +There is a config module at the _angular/projects/issue-management/config/src/issue-management-config.module.ts_ path. The static `forRoot` method of this module is supposed to be called at the route level. So, you may assume the following will take place: + +```js +@NgModule({ + imports: [ + /* other imports */ + + IssueManagementConfigModule.forRoot(), + ], + + /* rest of the module meta data */ +}) +export class AppModule {} +``` + +You can use this static method to configure an application that uses your module project. An example of such configuration is already implemented and the `ISSUE_MANAGEMENT_ROUTE_PROVIDERS` token is provided here. The method can take options which enables further configuration possibilities. + +The difference between the `forRoot` method of the config module and the `forLazy` method of the main module is that, for smallest bundle size, the former should only be used when you have to configure an app before your module is even loaded. + +#### Testing Angular UI + +Please see the [testing document](../UI/Angular/Testing.md). diff --git a/docs/en/images/angular-module-dev-app-project.png b/docs/en/images/angular-module-dev-app-project.png new file mode 100644 index 0000000000..08e0e9fd6c Binary files /dev/null and b/docs/en/images/angular-module-dev-app-project.png differ diff --git a/docs/en/images/angular-module-folder-structure.png b/docs/en/images/angular-module-folder-structure.png new file mode 100644 index 0000000000..ae2333b295 Binary files /dev/null and b/docs/en/images/angular-module-folder-structure.png differ diff --git a/docs/zh-Hans/Domain-Driven-Design-Implementation-Guide.md b/docs/zh-Hans/Domain-Driven-Design-Implementation-Guide.md index 81755fbf0d..47cab8c02e 100644 --- a/docs/zh-Hans/Domain-Driven-Design-Implementation-Guide.md +++ b/docs/zh-Hans/Domain-Driven-Design-Implementation-Guide.md @@ -1315,7 +1315,7 @@ namespace IssueTracking.Users #### 输出DTO最佳实践 -* 保持**数量较少**的输出DTO,尽可能**重用输入DTO**(例外:不要将输入DTO作为输出DTO). +* 保持**数量较少**的输出DTO,尽可能**重用输出DTO**(例外:不要将输入DTO作为输出DTO). * 输出DTO可以包含比用例需要的属性**更多**的属性. * 针对 **Create** 和 **Update** 方法,返回实体的DTO. @@ -1976,4 +1976,4 @@ public class IssueAppService * "*Domain Driven Design*" by Eric Evans * "*Implementing Domain Driven Design*" by Vaughn Vernon -* "*Clean Architecture*" by Robert C. Martin \ No newline at end of file +* "*Clean Architecture*" by Robert C. Martin diff --git a/docs/zh-Hans/PlugIn-Modules.md b/docs/zh-Hans/PlugIn-Modules.md new file mode 100644 index 0000000000..48a507ac7d --- /dev/null +++ b/docs/zh-Hans/PlugIn-Modules.md @@ -0,0 +1,233 @@ +# 模块化插件 + +可以将[模块](Module-Development-Basics.md)加载为插件.这意味着你可能不需要在解决方案中引用模块的程序集,就可以像其它模块一样在启动应用时加载该模块. + +## 基本用法 + +`IServiceCollection.AddApplication()` 扩展方法可以获取配置插件源的选项. + +**示例: 从文件夹加载插件** + +````csharp +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Modularity.PlugIns; + +namespace MyPlugInDemo.Web +{ + public class Startup + { + public void ConfigureServices(IServiceCollection services) + { + services.AddApplication(options => + { + options.PlugInSources.AddFolder(@"D:\Temp\MyPlugIns"); + }); + } + + public void Configure(IApplicationBuilder app) + { + app.InitializeApplication(); + } + } +} +```` + +* 这是典型的ASP.NET Core应用程序的`Startup`类. +* `PlugInSources.AddFolder`从指定的目录中加载程序集(通常为dll). + +就这样.ABP将在这个目录中发现这些模块,像其它常规一样配置和初始化它们. + +### 插件源 + +`options.PlugInSources`类实际上是`IPlugInSource`接口的一系列实现并且 `AddFolder`方法仅仅是以下表达式的便捷方法: + +````csharp +options.PlugInSources.Add(new FolderPlugInSource(@"D:\Temp\MyPlugIns")); +```` + +> `AddFolder()`方法仅在给定目录下查找程序集文件,而不在子目录中查找.你可以传递一个`SearchOption.AllDirectories`参数作为第二个参数,来递归地查找它的子目录. + +这里有两个内置插件源的示例: + +* `PlugInSources.AddFiles()`方法获取程序集(通常是dll)文件列表.这是使用`FilePlugInSource`类的快捷方式. +* `PlugInSources.AddTypes()`方法获取模块类类型的列表.如果实用化此方法,则需要自己加载模块的程序集,但是在需要时它提供了灵活性.这是使用`TypePlugInSource`类的快捷方式. + +如果需要,你可以创建自己的`IPlugInSource`的接口实现,并像其它方法一样添加到`options.PlugInSources`中. + +## 示例:创建一个简单的插件 + +在一个解决方案中创建一个简单的**类库项目** + +![简单插件库](images/simple-plugin-library.png) + +你可以在模块中添加需要使用的ABP框架包.至少,你应该为这个项目添加包`Volo.Abp.Core`: + +```` +Install-Package Volo.Abp.Core +```` + +每个[模块](Module-Development-Basics.md)必须声明为一个继承自`AbpModule`的类.这里是一个简单的模块类,用于解析一个服务并在应用启动时对其初始化: + +````csharp +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp; +using Volo.Abp.Modularity; + +namespace MyPlugIn +{ + public class MyPlungInModule : AbpModule + { + public override void OnApplicationInitialization(ApplicationInitializationContext context) + { + var myService = context.ServiceProvider + .GetRequiredService(); + + myService.Initialize(); + } + } +} +```` + +`MyService`可以是注册在[依赖注入](Dependency-Injection.md)系统中的任意类,如下所示: + +````csharp +using Microsoft.Extensions.Logging; +using Volo.Abp.DependencyInjection; + +namespace MyPlugIn +{ + public class MyService : ITransientDependency + { + private readonly ILogger _logger; + + public MyService(ILogger logger) + { + _logger = logger; + } + + public void Initialize() + { + _logger.LogInformation("MyService has been initialized"); + } + } +} +```` + +编译这个项目,打开build目录,找到`MyPlugIn.dll`: + +![简单dll插件](images/simple-plug-in-dll-file.png) + +将`MyPlugIn.dll`复制到到插件目录中(此实例为`D:\Temp\MyPlugIns`). + +如果你已经按照上述方式配置了主应用程序(参见“基础用法”部分),那么在应用程序启动时,你可以看到“MyService has been initialized(MyService已经初始化)的日志. + +## 示例:创建一个Razor Pages插件 + +创建内部带视图的插件需要更多的注意. + +> 这个示例假设你已经使用应用程序启动模板和MVC / Razor Pages UI[创建了一个新的Web应用程序](https://abp.io/get-started). + +在解决方案中创建一个新的**类库**项目: + +![简单razor插件](images/simple-razor-plugin.png) + +编辑这个`.csproj`文件内容: + +````xml + + + + net5.0 + Library + true + + + + + + + +```` + +* 将`Sdk`修改为`Microsoft.NET.Sdk.Web`. +* 添加了`OutputType`和`IsPackable`属性. +* 添加了`Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared`NuGet包. + +> 不需要[Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared](https://www.nuget.org/packages/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared) 包.你可以引用更基础的程序包,例如[Volo.Abp.AspNetCore.Mvc](https://www.nuget.org/packages/Volo.Abp.AspNetCore.Mvc/). 但是,如果需要构建一个UI视图/组件,建议参考[Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared](https://www.nuget.org/packages/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared)程序包,因为它是最高级的程序包,不依赖于特定[theme](UI/AspNetCore/Theming.md).如果依赖特定主题没有问题,则可以直接引用该主题的程序包,以便能够使用插件中特定于主题的功能. + +接下来在插件中创建模块类: + +````csharp +using System.IO; +using System.Reflection; +using Microsoft.AspNetCore.Mvc.ApplicationParts; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared; +using Volo.Abp.Modularity; + +namespace MyMvcUIPlugIn +{ + [DependsOn(typeof(AbpAspNetCoreMvcUiThemeSharedModule))] + public class MyMvcUIPlugInModule : AbpModule + { + public override void PreConfigureServices(ServiceConfigurationContext context) + { + PreConfigure(mvcBuilder => + { + // 添加插件程序集 + mvcBuilder.PartManager.ApplicationParts.Add(new AssemblyPart(typeof(MyMvcUIPlugInModule).Assembly)); + + // 添加视图程序集 + var viewDllPath = Path.Combine(Path.GetDirectoryName(typeof(MyMvcUIPlugInModule).Assembly.Location), "MyMvcUIPlugIn.Views.dll"); + var viewAssembly = new CompiledRazorAssemblyPart(Assembly.LoadFrom(viewDllPath)); + mvcBuilder.PartManager.ApplicationParts.Add(viewAssembly); + }); + } + } +} +```` + +* 由于我们添加了相关的NuGet包,因此取决于`AbpAspNetCoreMvcUiThemeSharedModule`. +* 添加插件程序集到ASP.NET Core MVC的`PartManager`中.这是ASP.NET Core所必需的.否则,你插件中的控制器将无法正常工作. +* 添加插件的视图程序集到ASP.NET Core MVC的`PartManager`中.这是ASP.NET Core所必需的.否则,你在插件中的视图将不起作用. + +现在,你可以在`Pages`目录下添加一个razor页面,例如`MyPlugInPage.cshtml`: + +````html +@page +@model MyMvcUIPlugIn.Pages.MyPlugInPage +

Welcome to my plug-in page

+

This page is located inside a plug-in module! :)

+```` + +现在,你可以构建插件项目.它将产生以下输出: + +![simple-razor-plug-in-dll-file](images/simple-razor-plug-in-dll-file.png) + +将`MyMvcUIPlugIn.dll`和`MyMvcUIPlugIn.Views.dll`复制到到插件目录下(此示例中为`D:\Temp\MyPlugIns`). + +如果你已经按照上述方式配置了主应用程序(参见“基础用法”部分),那么在应用程序启动的时候,你应该能够访问`/MyPlugInPage`URL: + +![simple-plugin-output](images/simple-plugin-output.png) + +## 讨论 + +在现实世界中,你的插件可能具有一些外部依赖性.另外,你的应用程序可能被设计为支持插件.所有这些都是你自己的系统要求.ABP做的仅仅是在应用程序启动时加载模块.你在这些模块中执行什么操作由你决定. + +但是,我们可以为一些常见情况提供一些建议. + +### 库依赖 + +对于包/dll依赖,你可以将相关的dll复制到插件目录下.ABP会自动将所有程序集加载到该目录下,并且你的插件将按预期工作. + +> 请参见[Microsoft文档](https://docs.microsoft.com/zh-cn/dotnet/core/tutorials/creating-app-with-plugin-support#plugin-with-library-dependencies). + +### 数据库模式 + +如果你的模块使用关系型数据库和[Entity Framework Core](Entity-Framework-Core.md), 那么它需要在数据库中提供表.有多种不同的方法可确保在应用程序使用插件时创建表.一些例子; + +1. 插件可以检查数据库表是否存在,并在应用程序启动时创建表,或者如果插件已更新且需要进行某些架构更改时,则会迁移它们.你可以使用EF Core的迁移API来做到这一点. +2. 你可以改进`DbMigrator`应用程序,用于查找插件的迁移并执行它们. + +可能还有其它解决方案.例如,如果你的数据库管理员不允许你在应用程序代码中更改数据库模式,则可能需要手动将SQL文件发送给数据库管理员,以将其应用于数据库. diff --git a/docs/zh-Hans/docs-nav.json b/docs/zh-Hans/docs-nav.json index 5d231099c9..4f47dd2760 100644 --- a/docs/zh-Hans/docs-nav.json +++ b/docs/zh-Hans/docs-nav.json @@ -313,7 +313,8 @@ "path": "Module-Development-Basics.md" }, { - "text": "模块插件" + "text": "模块插件", + "path": "PlugIn-Modules.md" }, { "text": "自定义应用模块", diff --git a/docs/zh-Hans/images/simple-plug-in-dll-file.png b/docs/zh-Hans/images/simple-plug-in-dll-file.png new file mode 100644 index 0000000000..3155708d68 Binary files /dev/null and b/docs/zh-Hans/images/simple-plug-in-dll-file.png differ diff --git a/docs/zh-Hans/images/simple-plugin-library.png b/docs/zh-Hans/images/simple-plugin-library.png new file mode 100644 index 0000000000..9fefd57dda Binary files /dev/null and b/docs/zh-Hans/images/simple-plugin-library.png differ diff --git a/docs/zh-Hans/images/simple-plugin-output.png b/docs/zh-Hans/images/simple-plugin-output.png new file mode 100644 index 0000000000..71f6a78c0e Binary files /dev/null and b/docs/zh-Hans/images/simple-plugin-output.png differ diff --git a/docs/zh-Hans/images/simple-razor-plug-in-dll-file.png b/docs/zh-Hans/images/simple-razor-plug-in-dll-file.png new file mode 100644 index 0000000000..06b7a565fe Binary files /dev/null and b/docs/zh-Hans/images/simple-razor-plug-in-dll-file.png differ diff --git a/docs/zh-Hans/images/simple-razor-plugin.png b/docs/zh-Hans/images/simple-razor-plugin.png new file mode 100644 index 0000000000..92e0e00d29 Binary files /dev/null and b/docs/zh-Hans/images/simple-razor-plugin.png differ diff --git a/framework/src/Volo.Abp.AutoMapper/Volo/Abp/AutoMapper/AbpAutoMapperModule.cs b/framework/src/Volo.Abp.AutoMapper/Volo/Abp/AutoMapper/AbpAutoMapperModule.cs index 25707e3899..cb576ba1de 100644 --- a/framework/src/Volo.Abp.AutoMapper/Volo/Abp/AutoMapper/AbpAutoMapperModule.cs +++ b/framework/src/Volo.Abp.AutoMapper/Volo/Abp/AutoMapper/AbpAutoMapperModule.cs @@ -25,7 +25,7 @@ namespace Volo.Abp.AutoMapper { context.Services.AddAutoMapperObjectMapper(); - context.Services.AddSingleton(provider => CreateMappings(provider)); + context.Services.AddSingleton(CreateMappings); context.Services.AddSingleton(provider => provider.GetRequiredService()); } @@ -43,6 +43,8 @@ namespace Volo.Abp.AutoMapper } } + options.Configurators.Insert(0, ctx => ctx.MapperConfiguration.ConstructServicesUsing(serviceProvider.GetService)); + void ValidateAll(IConfigurationProvider config) { foreach (var profileType in options.ValidatingProfiles) @@ -60,7 +62,7 @@ namespace Volo.Abp.AutoMapper return new MapperAccessor { - Mapper = new Mapper(mapperConfiguration, serviceProvider.GetService) + Mapper = new Mapper(mapperConfiguration) }; } } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LoginCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LoginCommand.cs index 12810aebb9..f03c101c88 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LoginCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LoginCommand.cs @@ -22,13 +22,17 @@ namespace Volo.Abp.Cli.Commands public ICancellationTokenProvider CancellationTokenProvider { get; } public IRemoteServiceExceptionHandler RemoteServiceExceptionHandler { get; } + private readonly CliHttpClientFactory _cliHttpClientFactory; + public LoginCommand(AuthService authService, ICancellationTokenProvider cancellationTokenProvider, - IRemoteServiceExceptionHandler remoteServiceExceptionHandler) + IRemoteServiceExceptionHandler remoteServiceExceptionHandler, + CliHttpClientFactory cliHttpClientFactory) { AuthService = authService; CancellationTokenProvider = cancellationTokenProvider; RemoteServiceExceptionHandler = remoteServiceExceptionHandler; + _cliHttpClientFactory = cliHttpClientFactory; Logger = NullLogger.Instance; } @@ -79,20 +83,19 @@ namespace Volo.Abp.Cli.Commands { var url = $"{CliUrls.WwwAbpIo}api/license/check-multiple-organizations?username={username}"; - using (var client = new CliHttpClient()) + var client = _cliHttpClientFactory.CreateClient(); + + using (var response = await client.GetHttpResponseMessageWithRetryAsync(url, CancellationTokenProvider.Token, Logger)) { - using (var response = await client.GetHttpResponseMessageWithRetryAsync(url, CancellationTokenProvider.Token, Logger)) + if (!response.IsSuccessStatusCode) { - if (!response.IsSuccessStatusCode) - { - throw new Exception($"ERROR: Remote server returns '{response.StatusCode}'"); - } + throw new Exception($"ERROR: Remote server returns '{response.StatusCode}'"); + } - await RemoteServiceExceptionHandler.EnsureSuccessfulHttpResponseAsync(response); + await RemoteServiceExceptionHandler.EnsureSuccessfulHttpResponseAsync(response); - var responseContent = await response.Content.ReadAsStringAsync(); - return JsonSerializer.Deserialize(responseContent); - } + var responseContent = await response.Content.ReadAsStringAsync(); + return JsonSerializer.Deserialize(responseContent); } } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Http/CliHttpClient.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Http/CliHttpClient.cs index d853067abf..5aaf2ae2d3 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Http/CliHttpClient.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Http/CliHttpClient.cs @@ -22,7 +22,7 @@ namespace Volo.Abp.Cli.Http { Timeout = timeout ?? DefaultTimeout; - AddAuthentication(this); + AddAuthentication(); } public CliHttpClient(bool setBearerToken) : base(new CliHttpClientHandler()) @@ -31,11 +31,11 @@ namespace Volo.Abp.Cli.Http if (setBearerToken) { - AddAuthentication(this); + AddAuthentication(); } } - private static void AddAuthentication(HttpClient client) + public void AddAuthentication() { if (!AuthService.IsLoggedIn()) { @@ -45,7 +45,7 @@ namespace Volo.Abp.Cli.Http var accessToken = File.ReadAllText(CliPaths.AccessToken, Encoding.UTF8); if (!accessToken.IsNullOrEmpty()) { - client.SetBearerToken(accessToken); + this.SetBearerToken(accessToken); } } @@ -67,7 +67,12 @@ namespace Volo.Abp.Cli.Http }; } - cancellationToken ??= CancellationToken.None; + if (cancellationToken == null) + { + var cancellationTokenSource = new CancellationTokenSource(); + cancellationTokenSource.CancelAfter(DefaultTimeout); + cancellationToken = cancellationTokenSource.Token; + } return await HttpPolicyExtensions .HandleTransientHttpError() @@ -95,5 +100,6 @@ namespace Volo.Abp.Cli.Http .ExecuteAsync(async () => await this.GetAsync(url, cancellationToken.Value)); } + } } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Http/CliHttpClientFactory.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Http/CliHttpClientFactory.cs new file mode 100644 index 0000000000..b509a1614d --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Http/CliHttpClientFactory.cs @@ -0,0 +1,84 @@ +using System; +using System.Threading; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Threading; + +namespace Volo.Abp.Cli.Http +{ + public class CliHttpClientFactory : ISingletonDependency, IDisposable + { + private static CliHttpClient _authenticatedHttpClient; + private static CliHttpClient _unauthenticatedHttpClient; + private readonly ICancellationTokenProvider _cancellationTokenProvider; + + public CliHttpClientFactory(ICancellationTokenProvider cancellationTokenProvider) + { + _cancellationTokenProvider = cancellationTokenProvider; + } + + public CliHttpClient CreateClient(bool needsAuthentication = true, TimeSpan? timeout = null) + { + if (needsAuthentication) + { + return CreateAuthenticatedHttpClient(timeout); + } + + return CreateUnAuthenticatedHttpClient(timeout); + } + + public CancellationToken GetCancellationToken(TimeSpan? timeout = null) + { + if (timeout == null) + { + if (_cancellationTokenProvider == null) + { + var cancellationTokenSource = new CancellationTokenSource(); + cancellationTokenSource.CancelAfter(CliHttpClient.DefaultTimeout); + return cancellationTokenSource.Token; + } + else + { + return _cancellationTokenProvider.Token; + } + } + else + { + var cancellationTokenSource = new CancellationTokenSource(); + cancellationTokenSource.CancelAfter(Convert.ToInt32(timeout.Value.TotalMilliseconds)); + return cancellationTokenSource.Token; + } + } + + private static CliHttpClient CreateAuthenticatedHttpClient(TimeSpan? timeout = null) + { + if (_authenticatedHttpClient == null) + { + _authenticatedHttpClient = new CliHttpClient(setBearerToken: true) + { + Timeout = System.Threading.Timeout.InfiniteTimeSpan + }; + } + + return _authenticatedHttpClient; + } + + private static CliHttpClient CreateUnAuthenticatedHttpClient(TimeSpan? timeout = null) + { + if (_unauthenticatedHttpClient == null) + { + _unauthenticatedHttpClient = new CliHttpClient(setBearerToken: false) + { + Timeout = System.Threading.Timeout.InfiniteTimeSpan + }; + } + + return _unauthenticatedHttpClient; + } + + public void Dispose() + { + _authenticatedHttpClient?.Dispose(); + _unauthenticatedHttpClient?.Dispose(); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Licensing/AbpIoApiKeyService.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Licensing/AbpIoApiKeyService.cs index 2aeb8ed90a..5ff4d39d4f 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Licensing/AbpIoApiKeyService.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Licensing/AbpIoApiKeyService.cs @@ -25,16 +25,19 @@ namespace Volo.Abp.Cli.Licensing private readonly ILogger _logger; private DeveloperApiKeyResult _apiKeyResult = null; + private readonly CliHttpClientFactory _cliHttpClientFactory; public AbpIoApiKeyService( IJsonSerializer jsonSerializer, ICancellationTokenProvider cancellationTokenProvider, IRemoteServiceExceptionHandler remoteServiceExceptionHandler, - ILogger logger) + ILogger logger, + CliHttpClientFactory cliHttpClientFactory) { JsonSerializer = jsonSerializer; RemoteServiceExceptionHandler = remoteServiceExceptionHandler; _logger = logger; + _cliHttpClientFactory = cliHttpClientFactory; CancellationTokenProvider = cancellationTokenProvider; } @@ -56,22 +59,21 @@ namespace Volo.Abp.Cli.Licensing } var url = $"{CliUrls.WwwAbpIo}api/license/api-key"; + var client = _cliHttpClientFactory.CreateClient(); - using (var client = new CliHttpClient()) + using (var response = await client.GetHttpResponseMessageWithRetryAsync(url, CancellationTokenProvider.Token, _logger)) { - using (var response = await client.GetHttpResponseMessageWithRetryAsync(url, CancellationTokenProvider.Token, _logger)) + if (!response.IsSuccessStatusCode) { - if (!response.IsSuccessStatusCode) - { - throw new Exception($"ERROR: Remote server returns '{response.StatusCode}'"); - } + throw new Exception($"ERROR: Remote server returns '{response.StatusCode}'"); + } - await RemoteServiceExceptionHandler.EnsureSuccessfulHttpResponseAsync(response); + await RemoteServiceExceptionHandler.EnsureSuccessfulHttpResponseAsync(response); - var responseContent = await response.Content.ReadAsStringAsync(); - return JsonSerializer.Deserialize(responseContent); - } + var responseContent = await response.Content.ReadAsStringAsync(); + return JsonSerializer.Deserialize(responseContent); } + } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/NuGet/NuGetService.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/NuGet/NuGetService.cs index 057ffcc02d..7907baaa2d 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/NuGet/NuGetService.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/NuGet/NuGetService.cs @@ -23,6 +23,7 @@ namespace Volo.Abp.Cli.NuGet protected ICancellationTokenProvider CancellationTokenProvider { get; } protected IRemoteServiceExceptionHandler RemoteServiceExceptionHandler { get; } private readonly IApiKeyService _apiKeyService; + private readonly CliHttpClientFactory _cliHttpClientFactory; private List _proPackageList; private DeveloperApiKeyResult _apiKeyResult; @@ -30,12 +31,14 @@ namespace Volo.Abp.Cli.NuGet IJsonSerializer jsonSerializer, IRemoteServiceExceptionHandler remoteServiceExceptionHandler, ICancellationTokenProvider cancellationTokenProvider, - IApiKeyService apiKeyService) + IApiKeyService apiKeyService, + CliHttpClientFactory cliHttpClientFactory) { JsonSerializer = jsonSerializer; RemoteServiceExceptionHandler = remoteServiceExceptionHandler; CancellationTokenProvider = cancellationTokenProvider; _apiKeyService = apiKeyService; + _cliHttpClientFactory = cliHttpClientFactory; Logger = NullLogger.Instance; } @@ -93,18 +96,17 @@ namespace Volo.Abp.Cli.NuGet url = $"https://api.nuget.org/v3-flatcontainer/{packageId.ToLowerInvariant()}/index.json"; } - using (var client = new CliHttpClient(setBearerToken: false)) + var client = _cliHttpClientFactory.CreateClient(needsAuthentication: false); + + using (var responseMessage = await client.GetHttpResponseMessageWithRetryAsync( + url, + cancellationToken: CancellationTokenProvider.Token, + logger: Logger + )) { - using (var responseMessage = await client.GetHttpResponseMessageWithRetryAsync( - url, - cancellationToken: CancellationTokenProvider.Token, - logger: Logger - )) - { - await RemoteServiceExceptionHandler.EnsureSuccessfulHttpResponseAsync(responseMessage); - var responseContent = await responseMessage.Content.ReadAsStringAsync(); - return JsonSerializer.Deserialize(responseContent).Versions; - } + await RemoteServiceExceptionHandler.EnsureSuccessfulHttpResponseAsync(responseMessage); + var responseContent = await responseMessage.Content.ReadAsStringAsync(); + return JsonSerializer.Deserialize(responseContent).Versions; } } @@ -120,9 +122,8 @@ namespace Volo.Abp.Cli.NuGet private async Task> GetProPackageListAsync() { - using var client = new CliHttpClient(); - var url = $"{CliUrls.WwwAbpIo}api/app/nugetPackage/proPackageNames"; + var client = _cliHttpClientFactory.CreateClient(needsAuthentication: true); using (var responseMessage = await client.GetHttpResponseMessageWithRetryAsync( url: url, diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs index 0462920fbb..bebe44542d 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs @@ -9,6 +9,7 @@ using System.Net.Http; using System.Reflection; using System.Text; using System.Text.RegularExpressions; +using System.Threading; using System.Threading.Tasks; using Volo.Abp.Cli.Http; using Volo.Abp.Cli.ProjectBuilding.Templates.App; @@ -28,22 +29,23 @@ namespace Volo.Abp.Cli.ProjectBuilding public ILogger Logger { get; set; } protected AbpCliOptions Options { get; } - protected IJsonSerializer JsonSerializer { get; } - protected IRemoteServiceExceptionHandler RemoteServiceExceptionHandler { get; } - protected ICancellationTokenProvider CancellationTokenProvider { get; } + private readonly CliHttpClientFactory _cliHttpClientFactory; + public AbpIoSourceCodeStore( IOptions options, IJsonSerializer jsonSerializer, IRemoteServiceExceptionHandler remoteServiceExceptionHandler, - ICancellationTokenProvider cancellationTokenProvider) + ICancellationTokenProvider cancellationTokenProvider, + CliHttpClientFactory cliHttpClientFactory) { JsonSerializer = jsonSerializer; RemoteServiceExceptionHandler = remoteServiceExceptionHandler; CancellationTokenProvider = cancellationTokenProvider; + _cliHttpClientFactory = cliHttpClientFactory; Options = options.Value; Logger = NullLogger.Instance; @@ -134,24 +136,17 @@ namespace Volo.Abp.Cli.ProjectBuilding try { - using (var client = new CliHttpClient(TimeSpan.FromMinutes(10))) + var client = _cliHttpClientFactory.CreateClient(); + var stringContent = new StringContent( + JsonSerializer.Serialize(new GetLatestSourceCodeVersionDto { Name = name, IncludePreReleases = includePreReleases }), + Encoding.UTF8, + MimeTypes.Application.Json + ); + + using (var response = await client.PostAsync(url, stringContent, _cliHttpClientFactory.GetCancellationToken(TimeSpan.FromMinutes(10)))) { - var response = await client.PostAsync( - url, - new StringContent( - JsonSerializer.Serialize( - new GetLatestSourceCodeVersionDto { Name = name, IncludePreReleases = includePreReleases } - ), - Encoding.UTF8, - MimeTypes.Application.Json - ), - CancellationTokenProvider.Token - ); - await RemoteServiceExceptionHandler.EnsureSuccessfulHttpResponseAsync(response); - var result = await response.Content.ReadAsStringAsync(); - return JsonSerializer.Deserialize(result).Version; } } @@ -164,32 +159,25 @@ namespace Volo.Abp.Cli.ProjectBuilding private async Task GetTemplateNugetVersionAsync(string name, string type, string version) { - var url = $"{CliUrls.WwwAbpIo}api/download/{type}/get-nuget-version/"; - try { - using (var client = new CliHttpClient(TimeSpan.FromMinutes(10))) - { - var response = await client.PostAsync( - url, - new StringContent( - JsonSerializer.Serialize( - new GetTemplateNugetVersionDto { Name = name, Version = version} - ), - Encoding.UTF8, - MimeTypes.Application.Json - ), - CancellationTokenProvider.Token - ); + var url = $"{CliUrls.WwwAbpIo}api/download/{type}/get-nuget-version/"; + var client = _cliHttpClientFactory.CreateClient(); - await RemoteServiceExceptionHandler.EnsureSuccessfulHttpResponseAsync(response); + var stringContent = new StringContent( + JsonSerializer.Serialize(new GetTemplateNugetVersionDto { Name = name, Version = version }), + Encoding.UTF8, + MimeTypes.Application.Json + ); + using (var response = await client.PostAsync(url, stringContent, _cliHttpClientFactory.GetCancellationToken(TimeSpan.FromMinutes(10)))) + { + await RemoteServiceExceptionHandler.EnsureSuccessfulHttpResponseAsync(response); var result = await response.Content.ReadAsStringAsync(); - return JsonSerializer.Deserialize(result).Version; } } - catch (Exception ex) + catch (Exception) { return null; } @@ -199,38 +187,39 @@ namespace Volo.Abp.Cli.ProjectBuilding { var url = $"{CliUrls.WwwAbpIo}api/download/{input.Type}/"; + HttpResponseMessage responseMessage = null; + try { - using (var client = new CliHttpClient(TimeSpan.FromMinutes(10))) - { - HttpResponseMessage responseMessage; + var client = _cliHttpClientFactory.CreateClient(); - if (input.TemplateSource.IsNullOrWhiteSpace()) - { - responseMessage = await client.PostAsync( - url, - new StringContent(JsonSerializer.Serialize(input), Encoding.UTF8, MimeTypes.Application.Json), - CancellationTokenProvider.Token - ); - } - else - { - responseMessage = await client.GetAsync(input.TemplateSource, CancellationTokenProvider.Token); - } + if (input.TemplateSource.IsNullOrWhiteSpace()) + { + responseMessage = await client.PostAsync( + url, + new StringContent(JsonSerializer.Serialize(input), Encoding.UTF8, MimeTypes.Application.Json), + _cliHttpClientFactory.GetCancellationToken(TimeSpan.FromMinutes(10)) + ); + } + else + { + responseMessage = await client.GetAsync(input.TemplateSource, _cliHttpClientFactory.GetCancellationToken()); + } - await RemoteServiceExceptionHandler.EnsureSuccessfulHttpResponseAsync(responseMessage); + await RemoteServiceExceptionHandler.EnsureSuccessfulHttpResponseAsync(responseMessage); + var resultAsBytes = await responseMessage.Content.ReadAsByteArrayAsync(); + responseMessage.Dispose(); - return await responseMessage.Content.ReadAsByteArrayAsync(); - } + return resultAsBytes; } catch (Exception ex) { - Console.WriteLine("Error occured while downloading source-code from {0} : {1}", url, ex.Message); + Console.WriteLine("Error occured while downloading source-code from {0} : {1}{2}{3}", url, responseMessage?.ToString(), Environment.NewLine, ex.Message); throw; } } - private bool IsNetworkSource(string source) + private static bool IsNetworkSource(string source) { return source.ToLower().StartsWith("http"); } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Analyticses/CliAnalyticsCollect.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Analyticses/CliAnalyticsCollect.cs index 621631af2a..e97d7443d4 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Analyticses/CliAnalyticsCollect.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Analyticses/CliAnalyticsCollect.cs @@ -18,15 +18,18 @@ namespace Volo.Abp.Cli.ProjectBuilding.Analyticses private readonly IJsonSerializer _jsonSerializer; private readonly ILogger _logger; private readonly IRemoteServiceExceptionHandler _remoteServiceExceptionHandler; + private readonly CliHttpClientFactory _cliHttpClientFactory; public CliAnalyticsCollect( ICancellationTokenProvider cancellationTokenProvider, IJsonSerializer jsonSerializer, - IRemoteServiceExceptionHandler remoteServiceExceptionHandler) + IRemoteServiceExceptionHandler remoteServiceExceptionHandler, + CliHttpClientFactory cliHttpClientFactory) { _cancellationTokenProvider = cancellationTokenProvider; _jsonSerializer = jsonSerializer; _remoteServiceExceptionHandler = remoteServiceExceptionHandler; + _cliHttpClientFactory = cliHttpClientFactory; _logger = NullLogger.Instance; } @@ -34,32 +37,31 @@ namespace Volo.Abp.Cli.ProjectBuilding.Analyticses { var postData = _jsonSerializer.Serialize(input); var url = $"{CliUrls.WwwAbpIo}api/clianalytics/collect"; - + try { - using (var client = new CliHttpClient()) - { - var responseMessage = await client.PostAsync( - url, - new StringContent(postData, Encoding.UTF8, MimeTypes.Application.Json), - _cancellationTokenProvider.Token - ); + var client = _cliHttpClientFactory.CreateClient(); - if (!responseMessage.IsSuccessStatusCode) - { - var exceptionMessage = "Remote server returns '" + (int)responseMessage.StatusCode + "-" + responseMessage.ReasonPhrase + "'. "; - var remoteServiceErrorMessage = await _remoteServiceExceptionHandler.GetAbpRemoteServiceErrorAsync(responseMessage); + var responseMessage = await client.PostAsync( + url, + new StringContent(postData, Encoding.UTF8, MimeTypes.Application.Json), + _cancellationTokenProvider.Token + ); - if (remoteServiceErrorMessage != null) - { - exceptionMessage += remoteServiceErrorMessage; - } + if (!responseMessage.IsSuccessStatusCode) + { + var exceptionMessage = "Remote server returns '" + (int)responseMessage.StatusCode + "-" + responseMessage.ReasonPhrase + "'. "; + var remoteServiceErrorMessage = await _remoteServiceExceptionHandler.GetAbpRemoteServiceErrorAsync(responseMessage); - _logger.LogInformation(exceptionMessage); + if (remoteServiceErrorMessage != null) + { + exceptionMessage += remoteServiceErrorMessage; } + + _logger.LogInformation(exceptionMessage); } } - catch (Exception ex) + catch (Exception) { // ignored } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ModuleInfo.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ModuleInfo.cs index 260fbb745f..1d8da0e3b1 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ModuleInfo.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ModuleInfo.cs @@ -23,5 +23,7 @@ public bool MvcUi { get; set; } public bool BlazorUi { get; set; } + + public bool IsFreeToActiveLicenseOwners { get; set; } } } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/SolutionRenameStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/SolutionRenameStep.cs index 611d4d5266..ce2c8c3b2a 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/SolutionRenameStep.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/SolutionRenameStep.cs @@ -55,11 +55,13 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps { RenameHelper.RenameAll(_entries, _companyNamePlaceHolder, _companyName); RenameHelper.RenameAll(_entries, _companyNamePlaceHolder.ToCamelCase(), _companyName.ToCamelCase()); + RenameHelper.RenameAll(_entries, _companyNamePlaceHolder.ToKebabCase(), _companyName.ToKebabCase()); } else { RenameHelper.RenameAll(_entries, _companyNamePlaceHolder + "." + _projectNamePlaceHolder, _projectNamePlaceHolder); RenameHelper.RenameAll(_entries, _companyNamePlaceHolder.ToCamelCase() + "." + _projectNamePlaceHolder.ToCamelCase(), _projectNamePlaceHolder.ToCamelCase()); + RenameHelper.RenameAll(_entries, _companyNamePlaceHolder.ToKebabCase() + "/" + _projectNamePlaceHolder.ToKebabCase(), _projectNamePlaceHolder.ToKebabCase()); } } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ModuleInfoProvider.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ModuleInfoProvider.cs index 81bc704151..92bbe728c0 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ModuleInfoProvider.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ModuleInfoProvider.cs @@ -16,14 +16,18 @@ namespace Volo.Abp.Cli.ProjectBuilding public ICancellationTokenProvider CancellationTokenProvider { get; } public IRemoteServiceExceptionHandler RemoteServiceExceptionHandler { get; } + private readonly CliHttpClientFactory _cliHttpClientFactory; + public ModuleInfoProvider( IJsonSerializer jsonSerializer, ICancellationTokenProvider cancellationTokenProvider, - IRemoteServiceExceptionHandler remoteServiceExceptionHandler) + IRemoteServiceExceptionHandler remoteServiceExceptionHandler, + CliHttpClientFactory cliHttpClientFactory) { JsonSerializer = jsonSerializer; CancellationTokenProvider = cancellationTokenProvider; RemoteServiceExceptionHandler = remoteServiceExceptionHandler; + _cliHttpClientFactory = cliHttpClientFactory; } public async Task GetAsync(string name) @@ -47,17 +51,16 @@ namespace Volo.Abp.Cli.ProjectBuilding private async Task> GetModuleListInternalAsync() { - using (var client = new CliHttpClient()) + var client = _cliHttpClientFactory.CreateClient(); + + using (var responseMessage = await client.GetAsync( + $"{CliUrls.WwwAbpIo}api/download/modules/", + CancellationTokenProvider.Token + )) { - using (var responseMessage = await client.GetAsync( - $"{CliUrls.WwwAbpIo}api/download/modules/", - CancellationTokenProvider.Token - )) - { - await RemoteServiceExceptionHandler.EnsureSuccessfulHttpResponseAsync(responseMessage); - var result = await responseMessage.Content.ReadAsStringAsync(); - return JsonSerializer.Deserialize>(result); - } + await RemoteServiceExceptionHandler.EnsureSuccessfulHttpResponseAsync(responseMessage); + var result = await responseMessage.Content.ReadAsStringAsync(); + return JsonSerializer.Deserialize>(result); } } } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/TemplateInfoProvider.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/TemplateInfoProvider.cs index b9308dd707..1a4bf3c05a 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/TemplateInfoProvider.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/TemplateInfoProvider.cs @@ -24,13 +24,17 @@ namespace Volo.Abp.Cli.ProjectBuilding public IRemoteServiceExceptionHandler RemoteServiceExceptionHandler { get; } public AuthService AuthService { get; } + private readonly CliHttpClientFactory _cliHttpClientFactory; + public TemplateInfoProvider(ICancellationTokenProvider cancellationTokenProvider, IRemoteServiceExceptionHandler remoteServiceExceptionHandler, - AuthService authService) + AuthService authService, + CliHttpClientFactory cliHttpClientFactory) { CancellationTokenProvider = cancellationTokenProvider; RemoteServiceExceptionHandler = remoteServiceExceptionHandler; AuthService = authService; + _cliHttpClientFactory = cliHttpClientFactory; Logger = NullLogger.Instance; } @@ -76,21 +80,19 @@ namespace Volo.Abp.Cli.ProjectBuilding try { var url = $"{CliUrls.WwwAbpIo}api/license/check-user"; + var client = _cliHttpClientFactory.CreateClient(); - using (var client = new CliHttpClient()) + using (var response = await client.GetHttpResponseMessageWithRetryAsync(url, CancellationTokenProvider.Token, Logger)) { - using (var response = await client.GetHttpResponseMessageWithRetryAsync(url, CancellationTokenProvider.Token, Logger)) + if (!response.IsSuccessStatusCode) { - if (!response.IsSuccessStatusCode) - { - throw new Exception($"ERROR: Remote server returns '{response.StatusCode}'"); - } + throw new Exception($"ERROR: Remote server returns '{response.StatusCode}'"); + } - await RemoteServiceExceptionHandler.EnsureSuccessfulHttpResponseAsync(response); + await RemoteServiceExceptionHandler.EnsureSuccessfulHttpResponseAsync(response); - var responseContent = await response.Content.ReadAsStringAsync(); - return JsonSerializer.Deserialize(responseContent); - } + var responseContent = await response.Content.ReadAsStringAsync(); + return JsonSerializer.Deserialize(responseContent); } } catch (Exception) diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/MyGetPackageListFinder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/MyGetPackageListFinder.cs index 75e9156ce0..5434ede83f 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/MyGetPackageListFinder.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/MyGetPackageListFinder.cs @@ -6,17 +6,23 @@ using Microsoft.Extensions.Logging.Abstractions; using Newtonsoft.Json; using Volo.Abp.Cli.Http; using Volo.Abp.DependencyInjection; +using Volo.Abp.Threading; namespace Volo.Abp.Cli.ProjectModification { public class MyGetPackageListFinder : ISingletonDependency { - private MyGetApiResponse _response; - public ILogger Logger { get; set; } - public MyGetPackageListFinder() + private MyGetApiResponse _response; + private readonly CliHttpClientFactory _cliHttpClientFactory; + protected ICancellationTokenProvider CancellationTokenProvider { get; } + + public MyGetPackageListFinder(CliHttpClientFactory cliHttpClientFactory, + ICancellationTokenProvider cancellationTokenProvider) { + _cliHttpClientFactory = cliHttpClientFactory; + CancellationTokenProvider = cancellationTokenProvider; Logger = NullLogger.Instance; } @@ -29,18 +35,20 @@ namespace Volo.Abp.Cli.ProjectModification try { - using (var client = new CliHttpClient(TimeSpan.FromMinutes(10))) + var client = _cliHttpClientFactory.CreateClient(); + + using (var responseMessage = await client.GetAsync( + $"{CliUrls.WwwAbpIo}api/myget/packages/", + _cliHttpClientFactory.GetCancellationToken(TimeSpan.FromMinutes(10)))) { - var responseMessage = await client.GetAsync( - $"{CliUrls.WwwAbpIo}api/myget/packages/" + _response = JsonConvert.DeserializeObject( + Encoding.Default.GetString(await responseMessage.Content.ReadAsByteArrayAsync()) ); - - _response = JsonConvert.DeserializeObject(Encoding.Default.GetString(await responseMessage.Content.ReadAsByteArrayAsync())); } } - catch (Exception) + catch (Exception ex) { - Logger.LogError("Unable to get latest preview version."); + Logger.LogError("Unable to get latest preview version. Error: " + ex.Message); throw; } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmPackagesUpdater.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmPackagesUpdater.cs index 8fc800c146..bf23bc3786 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmPackagesUpdater.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmPackagesUpdater.cs @@ -27,15 +27,18 @@ namespace Volo.Abp.Cli.ProjectModification private readonly PackageJsonFileFinder _packageJsonFileFinder; private readonly NpmGlobalPackagesChecker _npmGlobalPackagesChecker; private readonly Dictionary _fileVersionStorage = new Dictionary(); + private readonly CliHttpClientFactory _cliHttpClientFactory; public NpmPackagesUpdater( PackageJsonFileFinder packageJsonFileFinder, NpmGlobalPackagesChecker npmGlobalPackagesChecker, - ICancellationTokenProvider cancellationTokenProvider) + ICancellationTokenProvider cancellationTokenProvider, + CliHttpClientFactory cliHttpClientFactory) { _packageJsonFileFinder = packageJsonFileFinder; _npmGlobalPackagesChecker = npmGlobalPackagesChecker; CancellationTokenProvider = cancellationTokenProvider; + _cliHttpClientFactory = cliHttpClientFactory; Logger = NullLogger.Instance; } @@ -151,16 +154,14 @@ namespace Volo.Abp.Cli.ProjectModification { try { - using (var client = new CliHttpClient(TimeSpan.FromMinutes(1))) + var client = _cliHttpClientFactory.CreateClient(); + using (var response = await client.GetHttpResponseMessageWithRetryAsync( + url: $"{CliUrls.WwwAbpIo}api/myget/apikey/", + cancellationToken: CancellationTokenProvider.Token, + logger: Logger + )) { - using (var response = await client.GetHttpResponseMessageWithRetryAsync( - url: $"{CliUrls.WwwAbpIo}api/myget/apikey/", - cancellationToken: CancellationTokenProvider.Token, - logger: Logger - )) - { - return Encoding.Default.GetString(await response.Content.ReadAsByteArrayAsync()); - } + return Encoding.Default.GetString(await response.Content.ReadAsByteArrayAsync()); } } catch (Exception) @@ -356,7 +357,7 @@ namespace Volo.Abp.Cli.ProjectModification protected virtual string ExtractVersions(string output) { var arrayStart = output.IndexOf('['); - return output.Substring(arrayStart, output.IndexOf(']') - arrayStart + 1); + return output.Substring(arrayStart, output.IndexOf(']') - arrayStart + 1); } protected virtual bool SpecifiedVersionExists(string version, JProperty package) diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ProjectNugetPackageAdder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ProjectNugetPackageAdder.cs index ee736f3aa0..6ebcad2c08 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ProjectNugetPackageAdder.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ProjectNugetPackageAdder.cs @@ -21,13 +21,15 @@ namespace Volo.Abp.Cli.ProjectModification public class ProjectNugetPackageAdder : ITransientDependency { public ILogger Logger { get; set; } + public BundleCommand BundleCommand { get; } protected IJsonSerializer JsonSerializer { get; } protected ProjectNpmPackageAdder NpmPackageAdder { get; } protected DerivedClassFinder ModuleClassFinder { get; } protected ModuleClassDependcyAdder ModuleClassDependcyAdder { get; } protected IRemoteServiceExceptionHandler RemoteServiceExceptionHandler { get; } - public BundleCommand BundleCommand { get; } + + private readonly CliHttpClientFactory _cliHttpClientFactory; public ProjectNugetPackageAdder( IJsonSerializer jsonSerializer, @@ -35,7 +37,8 @@ namespace Volo.Abp.Cli.ProjectModification DerivedClassFinder moduleClassFinder, ModuleClassDependcyAdder moduleClassDependcyAdder, IRemoteServiceExceptionHandler remoteServiceExceptionHandler, - BundleCommand bundleCommand) + BundleCommand bundleCommand, + CliHttpClientFactory cliHttpClientFactory) { JsonSerializer = jsonSerializer; NpmPackageAdder = npmPackageAdder; @@ -43,6 +46,7 @@ namespace Volo.Abp.Cli.ProjectModification ModuleClassDependcyAdder = moduleClassDependcyAdder; RemoteServiceExceptionHandler = remoteServiceExceptionHandler; BundleCommand = bundleCommand; + _cliHttpClientFactory = cliHttpClientFactory; Logger = NullLogger.Instance; } @@ -121,7 +125,7 @@ namespace Volo.Abp.Cli.ProjectModification private Task AddToCsprojManuallyAsync(string projectFile, NugetPackageInfo package, string version = null) { var projectFileContent = File.ReadAllText(projectFile); - var doc = new XmlDocument() {PreserveWhitespace = true}; + var doc = new XmlDocument() { PreserveWhitespace = true }; doc.Load(StreamHelper.GenerateStreamFromString(projectFileContent)); var itemGroupNodes = doc.SelectNodes("/Project/ItemGroup"); @@ -162,7 +166,7 @@ namespace Volo.Abp.Cli.ProjectModification private string GetAbpVersionOrNull(string projectFileContent) { - var doc = new XmlDocument() {PreserveWhitespace = true}; + var doc = new XmlDocument() { PreserveWhitespace = true }; doc.Load(StreamHelper.GenerateStreamFromString(projectFileContent)); @@ -173,12 +177,11 @@ namespace Volo.Abp.Cli.ProjectModification protected virtual async Task FindNugetPackageInfoAsync(string packageName) { - using (var client = new CliHttpClient()) - { - var url = $"{CliUrls.WwwAbpIo}api/app/nugetPackage/byName/?name=" + packageName; - - var response = await client.GetAsync(url); + var url = $"{CliUrls.WwwAbpIo}api/app/nugetPackage/byName/?name=" + packageName; + var client = _cliHttpClientFactory.CreateClient(); + using (var response = await client.GetAsync(url, _cliHttpClientFactory.GetCancellationToken())) + { if (!response.IsSuccessStatusCode) { if (response.StatusCode == HttpStatusCode.NotFound) diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionModuleAdder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionModuleAdder.cs index 90f4ff5d8f..c84713ffcb 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionModuleAdder.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionModuleAdder.cs @@ -7,7 +7,6 @@ using System.Linq; using System.Net; using System.Threading.Tasks; using Volo.Abp.Cli.Args; -using Volo.Abp.Cli.Bundling; using Volo.Abp.Cli.Commands; using Volo.Abp.Cli.Commands.Services; using Volo.Abp.Cli.Http; @@ -22,6 +21,12 @@ namespace Volo.Abp.Cli.ProjectModification public class SolutionModuleAdder : ITransientDependency { public ILogger Logger { get; set; } + public SourceCodeDownloadService SourceCodeDownloadService { get; } + public SolutionFileModifier SolutionFileModifier { get; } + public NugetPackageToLocalReferenceConverter NugetPackageToLocalReferenceConverter { get; } + public AngularModuleSourceCodeAdder AngularModuleSourceCodeAdder { get; } + public NewCommand NewCommand { get; } + public BundleCommand BundleCommand { get; } protected IJsonSerializer JsonSerializer { get; } protected ProjectNugetPackageAdder ProjectNugetPackageAdder { get; } @@ -31,12 +36,9 @@ namespace Volo.Abp.Cli.ProjectModification protected ProjectNpmPackageAdder ProjectNpmPackageAdder { get; } protected NpmGlobalPackagesChecker NpmGlobalPackagesChecker { get; } protected IRemoteServiceExceptionHandler RemoteServiceExceptionHandler { get; } - public SourceCodeDownloadService SourceCodeDownloadService { get; } - public SolutionFileModifier SolutionFileModifier { get; } - public NugetPackageToLocalReferenceConverter NugetPackageToLocalReferenceConverter { get; } - public AngularModuleSourceCodeAdder AngularModuleSourceCodeAdder { get; } - public NewCommand NewCommand { get; } - public BundleCommand BundleCommand { get; } + + private readonly CliHttpClientFactory _cliHttpClientFactory; + public SolutionModuleAdder( IJsonSerializer jsonSerializer, @@ -52,7 +54,8 @@ namespace Volo.Abp.Cli.ProjectModification NugetPackageToLocalReferenceConverter nugetPackageToLocalReferenceConverter, AngularModuleSourceCodeAdder angularModuleSourceCodeAdder, NewCommand newCommand, - BundleCommand bundleCommand) + BundleCommand bundleCommand, + CliHttpClientFactory cliHttpClientFactory) { JsonSerializer = jsonSerializer; ProjectNugetPackageAdder = projectNugetPackageAdder; @@ -68,6 +71,7 @@ namespace Volo.Abp.Cli.ProjectModification AngularModuleSourceCodeAdder = angularModuleSourceCodeAdder; NewCommand = newCommand; BundleCommand = bundleCommand; + _cliHttpClientFactory = cliHttpClientFactory; Logger = NullLogger.Instance; } @@ -131,7 +135,7 @@ namespace Volo.Abp.Cli.ProjectModification { var blazorProject = projectFiles.FirstOrDefault(f => f.EndsWith(".Blazor.csproj")); - if (blazorProject == null || !module.NugetPackages.Any(np=> np.Target == NuGetPackageTarget.Blazor)) + if (blazorProject == null || !module.NugetPackages.Any(np => np.Target == NuGetPackageTarget.Blazor)) { return; } @@ -171,7 +175,7 @@ namespace Volo.Abp.Cli.ProjectModification { await RemoveProjectByTarget(module, moduleSolutionFile, NuGetPackageTarget.EntityFrameworkCore, isProjectTiered); await RemoveProjectByPostFix(module, moduleSolutionFile, "test", ".EntityFrameworkCore.Tests"); - await ChangeDomainTestReferenceToMongoDB(module, moduleSolutionFile); + ChangeDomainTestReferenceToMongoDB(module, moduleSolutionFile); } } @@ -211,7 +215,7 @@ namespace Volo.Abp.Cli.ProjectModification return; } - var projectFolderPath = Directory.GetDirectories(srcPath).FirstOrDefault(d=> d.EndsWith(postFix)); + var projectFolderPath = Directory.GetDirectories(srcPath).FirstOrDefault(d => d.EndsWith(postFix)); if (projectFolderPath == null) { @@ -226,7 +230,7 @@ namespace Volo.Abp.Cli.ProjectModification } } - private async Task ChangeDomainTestReferenceToMongoDB(ModuleWithMastersInfo module, string moduleSolutionFile) + private void ChangeDomainTestReferenceToMongoDB(ModuleWithMastersInfo module, string moduleSolutionFile) { var testPath = Path.Combine(Path.GetDirectoryName(moduleSolutionFile), "test"); @@ -235,7 +239,7 @@ namespace Volo.Abp.Cli.ProjectModification return; } - var projectFolderPath = Directory.GetDirectories(testPath).FirstOrDefault(d=> d.EndsWith("Domain.Tests")); + var projectFolderPath = Directory.GetDirectories(testPath).FirstOrDefault(d => d.EndsWith("Domain.Tests")); if (projectFolderPath == null) { @@ -250,10 +254,10 @@ namespace Volo.Abp.Cli.ProjectModification return; } - File.WriteAllText(csprojFile, File.ReadAllText(csprojFile).Replace("EntityFrameworkCore","MongoDB")); + File.WriteAllText(csprojFile, File.ReadAllText(csprojFile).Replace("EntityFrameworkCore", "MongoDB")); File.WriteAllText(moduleFile, File.ReadAllText(moduleFile) - .Replace(".EntityFrameworkCore;",".MongoDB;") - .Replace("EntityFrameworkCoreTestModule","MongoDbTestModule")); + .Replace(".EntityFrameworkCore;", ".MongoDB;") + .Replace("EntityFrameworkCoreTestModule", "MongoDbTestModule")); } private async Task AddAngularPackages(string solutionFilePath, ModuleWithMastersInfo module) @@ -355,9 +359,9 @@ namespace Volo.Abp.Cli.ProjectModification ); } - await DeleteRedundantHostProjects(targetModuleFolder,"app"); - await DeleteRedundantHostProjects(targetModuleFolder,"demo"); - await DeleteRedundantHostProjects(targetModuleFolder,"host"); + await DeleteRedundantHostProjects(targetModuleFolder, "app"); + await DeleteRedundantHostProjects(targetModuleFolder, "demo"); + await DeleteRedundantHostProjects(targetModuleFolder, "host"); if (module.MasterModuleInfos == null) { @@ -501,7 +505,7 @@ namespace Volo.Abp.Cli.ProjectModification } } - protected virtual async Task RunMigrator(string[] projectFiles) + protected virtual void RunMigrator(string[] projectFiles) { var dbMigratorProject = projectFiles.FirstOrDefault(p => p.EndsWith(".DbMigrator.csproj")); @@ -516,15 +520,14 @@ namespace Volo.Abp.Cli.ProjectModification { if (newTemplate || newProTemplate) { - return await GetEmptyModuleProjectInfoAsync(moduleName, newProTemplate); + return GetEmptyModuleProjectInfo(moduleName, newProTemplate); } - using (var client = new CliHttpClient()) - { - var url = $"{CliUrls.WwwAbpIo}api/app/module/byNameWithDetails/?name=" + moduleName; - - var response = await client.GetAsync(url); + var url = $"{CliUrls.WwwAbpIo}api/app/module/byNameWithDetails/?name=" + moduleName; + var client = _cliHttpClientFactory.CreateClient(); + using (var response = await client.GetAsync(url, _cliHttpClientFactory.GetCancellationToken())) + { if (!response.IsSuccessStatusCode) { if (response.StatusCode == HttpStatusCode.NotFound) @@ -540,7 +543,7 @@ namespace Volo.Abp.Cli.ProjectModification } } - private async Task GetEmptyModuleProjectInfoAsync(string moduleName, + private ModuleWithMastersInfo GetEmptyModuleProjectInfo(string moduleName, bool newProTemplate = false) { var module = new ModuleWithMastersInfo @@ -625,7 +628,7 @@ namespace Volo.Abp.Cli.ProjectModification protected virtual async Task IsProjectTiered(string[] projectFiles) { return projectFiles.Select(ProjectFileNameHelper.GetAssemblyNameFromProjectPath) - .Any(p =>p.EndsWith(".HttpApi.Host")) + .Any(p => p.EndsWith(".HttpApi.Host")) && projectFiles.Select(ProjectFileNameHelper.GetAssemblyNameFromProjectPath) .Any(p => p.EndsWith(".IdentityServer")); } diff --git a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/AbpKafkaOptions.cs b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/AbpKafkaOptions.cs index 1769d8a076..26d15ce818 100644 --- a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/AbpKafkaOptions.cs +++ b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/AbpKafkaOptions.cs @@ -9,11 +9,13 @@ namespace Volo.Abp.Kafka public KafkaConnections Connections { get; } public Action ConfigureProducer { get; set; } - + public Action ConfigureConsumer { get; set; } public Action ConfigureTopic { get; set; } + public bool ReQueue { get; set; } = true; + public AbpKafkaOptions() { Connections = new KafkaConnections(); diff --git a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/KafkaMessageConsumer.cs b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/KafkaMessageConsumer.cs index bb7e2d66a3..3b8f022012 100644 --- a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/KafkaMessageConsumer.cs +++ b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/KafkaMessageConsumer.cs @@ -20,6 +20,8 @@ namespace Volo.Abp.Kafka protected IConsumerPool ConsumerPool { get; } + protected IProducerPool ProducerPool { get; } + protected IExceptionNotifier ExceptionNotifier { get; } protected AbpKafkaOptions Options { get; } @@ -37,10 +39,12 @@ namespace Volo.Abp.Kafka public KafkaMessageConsumer( IConsumerPool consumerPool, IExceptionNotifier exceptionNotifier, - IOptions options) + IOptions options, + IProducerPool producerPool) { ConsumerPool = consumerPool; ExceptionNotifier = exceptionNotifier; + ProducerPool = producerPool; Options = options.Value; Logger = NullLogger.Instance; @@ -132,14 +136,29 @@ namespace Volo.Abp.Kafka { await callback(consumeResult.Message); } - - Consumer.Commit(consumeResult); } catch (Exception ex) { + await RequeueAsync(consumeResult); + Logger.LogException(ex); await ExceptionNotifier.NotifyAsync(ex); } + finally + { + Consumer.Commit(consumeResult); + } + } + + protected virtual async Task RequeueAsync(ConsumeResult consumeResult) + { + if (!Options.ReQueue) + { + return; + } + + var producer = ProducerPool.Get(ConnectionName); + await producer.ProduceAsync(consumeResult.Topic, consumeResult.Message); } public virtual void Dispose() diff --git a/framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/MultiTenantConnectionStringResolver.cs b/framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/MultiTenantConnectionStringResolver.cs index 0d82419013..959c910901 100644 --- a/framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/MultiTenantConnectionStringResolver.cs +++ b/framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/MultiTenantConnectionStringResolver.cs @@ -26,105 +26,146 @@ namespace Volo.Abp.MultiTenancy public override async Task ResolveAsync(string connectionStringName = null) { - //No current tenant, fallback to default logic if (_currentTenant.Id == null) { + //No current tenant, fallback to default logic return await base.ResolveAsync(connectionStringName); } - using (var serviceScope = _serviceProvider.CreateScope()) + var tenant = await FindTenantConfigurationAsync(_currentTenant.Id.Value); + + if (tenant == null || tenant.ConnectionStrings.IsNullOrEmpty()) { - var tenantStore = serviceScope - .ServiceProvider - .GetRequiredService(); + //Tenant has not defined any connection string, fallback to default logic + return await base.ResolveAsync(connectionStringName); + } + + var tenantDefaultConnectionString = tenant.ConnectionStrings.Default; + + //Requesting default connection string... + if (connectionStringName == null || + connectionStringName == ConnectionStrings.DefaultConnectionStringName) + { + //Return tenant's default or global default + return !tenantDefaultConnectionString.IsNullOrWhiteSpace() + ? tenantDefaultConnectionString + : Options.ConnectionStrings.Default; + } + + //Requesting specific connection string... + var connString = tenant.ConnectionStrings.GetOrDefault(connectionStringName); + if (!connString.IsNullOrWhiteSpace()) + { + //Found for the tenant + return connString; + } - var tenant = await tenantStore.FindAsync(_currentTenant.Id.Value); - - if (tenant?.ConnectionStrings == null) - { - return await base.ResolveAsync(connectionStringName); - } - - //Requesting default connection string - if (connectionStringName == null) - { - return tenant.ConnectionStrings.Default ?? - Options.ConnectionStrings.Default; - } - - //Requesting specific connection string - var connString = tenant.ConnectionStrings.GetOrDefault(connectionStringName); - if (connString != null) - { - return connString; - } - - /* Requested a specific connection string, but it's not specified for the tenant. - * - If it's specified in options, use it. - * - If not, use tenant's default conn string. - */ - - var connStringInOptions = Options.ConnectionStrings.GetOrDefault(connectionStringName); - if (connStringInOptions != null) - { - return connStringInOptions; - } - - return tenant.ConnectionStrings.Default ?? - Options.ConnectionStrings.Default; + //Fallback to tenant's default connection string if available + if (!tenantDefaultConnectionString.IsNullOrWhiteSpace()) + { + return tenantDefaultConnectionString; + } + + //Try to find the specific connection string for given name + var connStringInOptions = Options.ConnectionStrings.GetOrDefault(connectionStringName); + if (!connStringInOptions.IsNullOrWhiteSpace()) + { + return connStringInOptions; + } + + //Fallback to the global default connection string + var defaultConnectionString = Options.ConnectionStrings.Default; + if (!defaultConnectionString.IsNullOrWhiteSpace()) + { + return defaultConnectionString; } + + throw new AbpException("No connection string defined!"); } [Obsolete("Use ResolveAsync method.")] public override string Resolve(string connectionStringName = null) { - //No current tenant, fallback to default logic if (_currentTenant.Id == null) { + //No current tenant, fallback to default logic return base.Resolve(connectionStringName); } + var tenant = FindTenantConfiguration(_currentTenant.Id.Value); + + if (tenant == null || tenant.ConnectionStrings.IsNullOrEmpty()) + { + //Tenant has not defined any connection string, fallback to default logic + return base.Resolve(connectionStringName); + } + + var tenantDefaultConnectionString = tenant.ConnectionStrings.Default; + + //Requesting default connection string... + if (connectionStringName == null || + connectionStringName == ConnectionStrings.DefaultConnectionStringName) + { + //Return tenant's default or global default + return !tenantDefaultConnectionString.IsNullOrWhiteSpace() + ? tenantDefaultConnectionString + : Options.ConnectionStrings.Default; + } + + //Requesting specific connection string... + var connString = tenant.ConnectionStrings.GetOrDefault(connectionStringName); + if (!connString.IsNullOrWhiteSpace()) + { + //Found for the tenant + return connString; + } + + //Fallback to tenant's default connection string if available + if (!tenantDefaultConnectionString.IsNullOrWhiteSpace()) + { + return tenantDefaultConnectionString; + } + + //Try to find the specific connection string for given name + var connStringInOptions = Options.ConnectionStrings.GetOrDefault(connectionStringName); + if (!connStringInOptions.IsNullOrWhiteSpace()) + { + return connStringInOptions; + } + + //Fallback to the global default connection string + var defaultConnectionString = Options.ConnectionStrings.Default; + if (!defaultConnectionString.IsNullOrWhiteSpace()) + { + return defaultConnectionString; + } + + throw new AbpException("No connection string defined!"); + } + + protected virtual async Task FindTenantConfigurationAsync(Guid tenantId) + { + using (var serviceScope = _serviceProvider.CreateScope()) + { + var tenantStore = serviceScope + .ServiceProvider + .GetRequiredService(); + + return await tenantStore.FindAsync(tenantId); + } + } + + [Obsolete("Use FindTenantConfigurationAsync method.")] + protected virtual TenantConfiguration FindTenantConfiguration(Guid tenantId) + { using (var serviceScope = _serviceProvider.CreateScope()) { var tenantStore = serviceScope .ServiceProvider .GetRequiredService(); - var tenant = tenantStore.Find(_currentTenant.Id.Value); - - if (tenant?.ConnectionStrings == null) - { - return base.Resolve(connectionStringName); - } - - //Requesting default connection string - if (connectionStringName == null) - { - return tenant.ConnectionStrings.Default ?? - Options.ConnectionStrings.Default; - } - - //Requesting specific connection string - var connString = tenant.ConnectionStrings.GetOrDefault(connectionStringName); - if (connString != null) - { - return connString; - } - - /* Requested a specific connection string, but it's not specified for the tenant. - * - If it's specified in options, use it. - * - If not, use tenant's default conn string. - */ - - var connStringInOptions = Options.ConnectionStrings.GetOrDefault(connectionStringName); - if (connStringInOptions != null) - { - return connStringInOptions; - } - - return tenant.ConnectionStrings.Default ?? - Options.ConnectionStrings.Default; + return tenantStore.Find(tenantId); } } } -} +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/RabbitMqMessageConsumer.cs b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/RabbitMqMessageConsumer.cs index b94663d886..f757efab64 100644 --- a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/RabbitMqMessageConsumer.cs +++ b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/RabbitMqMessageConsumer.cs @@ -191,6 +191,16 @@ namespace Volo.Abp.RabbitMQ } catch (Exception ex) { + try + { + Channel.BasicNack( + basicDeliverEventArgs.DeliveryTag, + multiple: false, + requeue: true + ); + } + catch { } + Logger.LogException(ex); await ExceptionNotifier.NotifyAsync(ex); } diff --git a/framework/test/Volo.Abp.AutoMapper.Tests/Volo/Abp/AutoMapper/AutoMapper_CustomServiceConstruction_Tests.cs b/framework/test/Volo.Abp.AutoMapper.Tests/Volo/Abp/AutoMapper/AutoMapper_CustomServiceConstruction_Tests.cs new file mode 100644 index 0000000000..c1995970b5 --- /dev/null +++ b/framework/test/Volo.Abp.AutoMapper.Tests/Volo/Abp/AutoMapper/AutoMapper_CustomServiceConstruction_Tests.cs @@ -0,0 +1,83 @@ +using System; +using AutoMapper; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Volo.Abp.Modularity; +using Volo.Abp.Testing; +using Xunit; +using IObjectMapper = Volo.Abp.ObjectMapping.IObjectMapper; + +namespace Volo.Abp.AutoMapper +{ + public class AutoMapper_CustomServiceConstruction_Tests : AbpIntegratedTest + { + private readonly IObjectMapper _objectMapper; + + public AutoMapper_CustomServiceConstruction_Tests() + { + _objectMapper = ServiceProvider.GetRequiredService(); + } + + [Fact] + public void Should_Custom_Service_Construction() + { + var source = new SourceModel + { + Name = nameof(SourceModel) + }; + _objectMapper.Map(source).Name.ShouldBe(nameof(CustomMappingAction)); + } + + [DependsOn(typeof(AbpAutoMapperModule))] + public class TestModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.AddMaps(); + options.Configurators.Add(configurationContext => + { + configurationContext.MapperConfiguration.ConstructServicesUsing(type => + type.Name.Contains(nameof(CustomMappingAction)) + ? new CustomMappingAction(nameof(CustomMappingAction)) + : Activator.CreateInstance(type)); + }); + }); + } + } + + public class SourceModel + { + public string Name { get; set; } + } + + public class DestModel + { + public string Name { get; set; } + } + + public class MapperActionProfile : Profile + { + public MapperActionProfile() + { + CreateMap().AfterMap(); + } + } + + public class CustomMappingAction : IMappingAction + { + private readonly string _name; + + public CustomMappingAction(string name) + { + _name = name; + } + + public void Process(SourceModel source, DestModel destination, ResolutionContext context) + { + destination.Name = _name; + } + } + } +} diff --git a/framework/test/Volo.Abp.AutoMapper.Tests/Volo/Abp/AutoMapper/AutoMapper_Dependency_Injection_Tests.cs b/framework/test/Volo.Abp.AutoMapper.Tests/Volo/Abp/AutoMapper/AutoMapper_Dependency_Injection_Tests.cs index 56c5e6f061..7edf32eb23 100644 --- a/framework/test/Volo.Abp.AutoMapper.Tests/Volo/Abp/AutoMapper/AutoMapper_Dependency_Injection_Tests.cs +++ b/framework/test/Volo.Abp.AutoMapper.Tests/Volo/Abp/AutoMapper/AutoMapper_Dependency_Injection_Tests.cs @@ -19,7 +19,7 @@ namespace Volo.Abp.AutoMapper [Fact] public void Should_Registered_AutoMapper_Service() { - GetService().ShouldNotBeNull(); + GetService().ShouldNotBeNull(); } [Fact] @@ -47,15 +47,30 @@ namespace Volo.Abp.AutoMapper { public MapperActionProfile() { - CreateMap().AfterMap(); + CreateMap().AfterMap(); } } - public class CustomMappingActionService : IMappingAction + public class CustomMappingAction : IMappingAction { + private readonly CustomMappingActionService _customMappingActionService; + + public CustomMappingAction(CustomMappingActionService customMappingActionService) + { + _customMappingActionService = customMappingActionService; + } + public void Process(SourceModel source, DestModel destination, ResolutionContext context) { - destination.Name = nameof(CustomMappingActionService); + destination.Name = _customMappingActionService.GetName(); + } + } + + public class CustomMappingActionService : ITransientDependency + { + public string GetName() + { + return nameof(CustomMappingActionService); } } } diff --git a/test/AbpPerfTest/_jmeter/SimpleTestPlan.jmx b/test/AbpPerfTest/_jmeter/SimpleTestPlan.jmx index 0c08c60f20..5c1c5dbced 100644 --- a/test/AbpPerfTest/_jmeter/SimpleTestPlan.jmx +++ b/test/AbpPerfTest/_jmeter/SimpleTestPlan.jmx @@ -1,5 +1,5 @@ - + @@ -16,10 +16,10 @@ continue false - 20 + 40 2000 - 10 + 20 false diff --git a/test/AbpPerfTest/_jmeter/SimpleTestPlanWithoutAbp.jmx b/test/AbpPerfTest/_jmeter/SimpleTestPlanWithoutAbp.jmx index c7d6b3292c..5ceb318e6b 100644 --- a/test/AbpPerfTest/_jmeter/SimpleTestPlanWithoutAbp.jmx +++ b/test/AbpPerfTest/_jmeter/SimpleTestPlanWithoutAbp.jmx @@ -1,5 +1,5 @@ - + @@ -16,10 +16,10 @@ continue false - 20 + 40 - 500 - 10 + 2000 + 20 false