@ -0,0 +1,81 @@ |
|||
# ABP Suite: How to Add the User Entity as a Navigation Property of Another Entity |
|||
|
|||
## Introduction |
|||
|
|||
[ABP Suite](https://commercial.abp.io/tools/suite), a part of the [ABP Commercial](https://commercial.abp.io/), is a productivity tool developed by the team behind the ABP Framework. The main functionality of the ABP Suite is to generate code for you. |
|||
|
|||
In this post, I'll show you how to add the user entity as a navigation property in your new entity, by the help of the ABP Suite. |
|||
|
|||
> In the sample project MVC UI is used, but the same steps are applicable to the Angular UI as well. |
|||
|
|||
## Code Generation |
|||
|
|||
### Create a New Entity |
|||
|
|||
Open the ABP Suite ([see how](https://docs.abp.io/en/commercial/latest/abp-suite/index)). Create a new entity called `Note`, as an example entity. |
|||
|
|||
 |
|||
|
|||
Then add a string property called `Title`, as an example property. |
|||
|
|||
 |
|||
|
|||
### Create AppUserDto |
|||
|
|||
ABP Suite needs a DTO for the target entity (user, in this case) in order to define a navigation property. |
|||
|
|||
To do this, create a new folder called "Users" in `*.Application.Contracts` then add a new class called `AppUserDto` inherited from `IdentityUserDto`. |
|||
|
|||
 |
|||
|
|||
We should define the [object mapping](https://docs.abp.io/en/abp/latest/Object-To-Object-Mapping) to be able to convert the `AppUser` objects to `AppUserDto` objects. To do this, open `YourProjectApplicationAutoMapperProfile.cs` and add the below line: |
|||
|
|||
```csharp |
|||
CreateMap<AppUser, AppUserDto>().Ignore(x => x.ExtraProperties); |
|||
``` |
|||
|
|||
 |
|||
|
|||
> Creating such a DTO class may not be needed for another entity than the `AppUser`, since it will probably be already available, especially if you had created the other entity using the ABP Suite. |
|||
|
|||
### Define the Navigation Property |
|||
|
|||
Get back to ABP Suite, open the **Navigation Properties** tab of the ABP Suite, click the **Add Navigation Property** button. Browse `AppUser.cs` in `*.Domain\Users` folder. Then choose the `Name` item as display property. Browse `AppUserDto.cs` in `*.Contracts\Users` folder. Choose `Users` from Collection Names dropdown. |
|||
|
|||
 |
|||
|
|||
### Generate the Code! |
|||
|
|||
That's it! Click **Save and generate** button to create your page. You'll see the following page if everything goes well. |
|||
|
|||
 |
|||
|
|||
This is the new page that has been created by the ABP Suite. It can perform the fundamental CRUD operations. Also, it has the "App user" column that shows the related user name (you can easily change the automatically created "App user" title from the **Entity Name** field of the navigation property creation screen). |
|||
|
|||
**Picking Users from Look Up Table** |
|||
|
|||
We used dropdown element to select a user from the user list. If you have a lot of users, then it's good to pick a user from a look up table. A look up table is a modal window that lets you filter data and pick one. To do this, get back to Suite and click **Edit** button of user navigation which is set as `AppUserId` name. Choose "Modal" from the "UI Pick Type" field. Then click **Save and generate** button to recreate your page. |
|||
|
|||
 |
|||
|
|||
After successful code generation, you'll see the the user can be picked from user table. |
|||
|
|||
 |
|||
|
|||
## About the ABP Commercial RC |
|||
|
|||
This example has been implemented with **ABP Commercial 3.1.0-rc.3**. This is a RC version. If you want to install the CLI and Suite RC version follow the next steps: |
|||
|
|||
1- Uninstall the current version of the CLI and install the specific RC version: |
|||
|
|||
```bash |
|||
dotnet tool uninstall --global Volo.Abp.Cli && dotnet tool install --global Volo.Abp.Cli --version 3.1.0-rc.3 |
|||
``` |
|||
|
|||
2- Uninstall the current version of the Suite and install the specific RC version: |
|||
|
|||
```bash |
|||
dotnet tool uninstall --global Volo.Abp.Suite && dotnet tool install -g Volo.Abp.Suite --version 3.1.0-rc.3 --add-source https://nuget.abp.io/<YOUR-API-KEY>/v3/index.json |
|||
``` |
|||
|
|||
Don't forget to replace the `<YOUR-API-KEY>` with your own key! |
|||
|
After Width: | Height: | Size: 84 KiB |
|
After Width: | Height: | Size: 223 KiB |
|
After Width: | Height: | Size: 180 KiB |
|
After Width: | Height: | Size: 302 KiB |
|
After Width: | Height: | Size: 153 KiB |
|
After Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 539 KiB |
|
After Width: | Height: | Size: 175 KiB |
@ -0,0 +1,167 @@ |
|||
# Distributed Event Bus Kafka Integration |
|||
|
|||
> This document explains **how to configure the [Kafka](https://kafka.apache.org/)** as the distributed event bus provider. See the [distributed event bus document](Distributed-Event-Bus.md) to learn how to use the distributed event bus system |
|||
|
|||
## Installation |
|||
|
|||
Use the ABP CLI to add [Volo.Abp.EventBus.Kafka](https://www.nuget.org/packages/Volo.Abp.EventBus.Kafka) NuGet package to your project: |
|||
|
|||
* Install the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) if you haven't installed before. |
|||
* Open a command line (terminal) in the directory of the `.csproj` file you want to add the `Volo.Abp.EventBus.Kafka` package. |
|||
* Run `abp add-package Volo.Abp.EventBus.Kafka` command. |
|||
|
|||
If you want to do it manually, install the [Volo.Abp.EventBus.Kafka](https://www.nuget.org/packages/Volo.Abp.EventBus.Kafka) NuGet package to your project and add `[DependsOn(typeof(AbpEventBusKafkaModule))]` to the [ABP module](Module-Development-Basics.md) class inside your project. |
|||
|
|||
## Configuration |
|||
|
|||
You can configure using the standard [configuration system](Configuration.md), like using the `appsettings.json` file, or using the [options](Options.md) classes. |
|||
|
|||
### `appsettings.json` file configuration |
|||
|
|||
This is the simplest way to configure the Kafka settings. It is also very strong since you can use any other configuration source (like environment variables) that is [supported by the AspNet Core](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/). |
|||
|
|||
**Example: The minimal configuration to connect to a local kafka server with default configurations** |
|||
|
|||
````json |
|||
{ |
|||
"Kafka": { |
|||
"Connections": { |
|||
"Default": { |
|||
"BootstrapServers": "localhost:9092" |
|||
} |
|||
}, |
|||
"EventBus": { |
|||
"GroupId": "MyGroupId", |
|||
"TopicName": "MyTopicName" |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
* `MyGroupId` is the name of this application, which is used as the **GroupId** on the Kakfa. |
|||
* `MyTopicName` is the **topic name**. |
|||
|
|||
See [the Kafka document](https://docs.confluent.io/current/clients/confluent-kafka-dotnet/api/Confluent.Kafka.html) to understand these options better. |
|||
|
|||
#### Connections |
|||
|
|||
If you need to connect to another server than the localhost, you need to configure the connection properties. |
|||
|
|||
**Example: Specify the host name (as an IP address)** |
|||
|
|||
````json |
|||
{ |
|||
"Kafka": { |
|||
"Connections": { |
|||
"Default": { |
|||
"BootstrapServers": "123.123.123.123:9092" |
|||
} |
|||
}, |
|||
"EventBus": { |
|||
"GroupId": "MyGroupId", |
|||
"TopicName": "MyTopicName" |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
Defining multiple connections is allowed. In this case, you can specify the connection that is used for the event bus. |
|||
|
|||
**Example: Declare two connections and use one of them for the event bus** |
|||
|
|||
````json |
|||
{ |
|||
"Kafka": { |
|||
"Connections": { |
|||
"Default": { |
|||
"BootstrapServers": "123.123.123.123:9092" |
|||
}, |
|||
"SecondConnection": { |
|||
"BootstrapServers": "321.321.321.321:9092" |
|||
} |
|||
}, |
|||
"EventBus": { |
|||
"GroupId": "MyGroupId", |
|||
"TopicName": "MyTopicName", |
|||
"ConnectionName": "SecondConnection" |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
This allows you to use multiple RabbitMQ server in your application, but select one of them for the event bus. |
|||
|
|||
You can use any of the [ClientConfig](https://docs.confluent.io/current/clients/confluent-kafka-dotnet/api/Confluent.Kafka.ClientConfig.html) properties as the connection properties. |
|||
|
|||
**Example: Specify the socket timeout** |
|||
|
|||
````json |
|||
{ |
|||
"Kafka": { |
|||
"Connections": { |
|||
"Default": { |
|||
"BootstrapServers": "123.123.123.123:9092", |
|||
"SocketTimeoutMs": 60000 |
|||
} |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
### The Options Classes |
|||
|
|||
`AbpRabbitMqOptions` and `AbpRabbitMqEventBusOptions` classes can be used to configure the connection strings and event bus options for the RabbitMQ. |
|||
|
|||
You can configure this options inside the `ConfigureServices` of your [module](Module-Development-Basics.md). |
|||
|
|||
**Example: Configure the connection** |
|||
|
|||
````csharp |
|||
Configure<AbpKafkaOptions>(options => |
|||
{ |
|||
options.Connections.Default.BootstrapServers = "123.123.123.123:9092"; |
|||
options.Connections.Default.SaslUsername = "user"; |
|||
options.Connections.Default.SaslPassword = "pwd"; |
|||
}); |
|||
```` |
|||
|
|||
**Example: Configure the consumer config** |
|||
|
|||
````csharp |
|||
Configure<AbpKafkaOptions>(options => |
|||
{ |
|||
options.ConfigureConsumer = config => |
|||
{ |
|||
config.GroupId = "MyGroupId"; |
|||
config.EnableAutoCommit = false; |
|||
}; |
|||
}); |
|||
```` |
|||
|
|||
**Example: Configure the producer config** |
|||
|
|||
````csharp |
|||
Configure<AbpKafkaOptions>(options => |
|||
{ |
|||
options.ConfigureProducer = config => |
|||
{ |
|||
config.MessageTimeoutMs = 6000; |
|||
config.Acks = Acks.All; |
|||
}; |
|||
}); |
|||
```` |
|||
|
|||
**Example: Configure the topic specification** |
|||
|
|||
````csharp |
|||
Configure<AbpKafkaOptions>(options => |
|||
{ |
|||
options.ConfigureTopic = specification => |
|||
{ |
|||
specification.ReplicationFactor = 3; |
|||
specification.NumPartitions = 3; |
|||
}; |
|||
}); |
|||
```` |
|||
|
|||
Using these options classes can be combined with the `appsettings.json` way. Configuring an option property in the code overrides the value in the configuration file. |
|||
@ -0,0 +1,163 @@ |
|||
# 分布式事件总线Kafka集成 |
|||
|
|||
> 本文解释了**如何配置[Kafka](https://kafka.apache.org/)**做为分布式总线提供程序. 参阅[分布式事件总线文档](Distributed-Event-Bus.md)了解如何使用分布式事件总线系统. |
|||
|
|||
## 安装 |
|||
|
|||
使用ABP CLI添加[Volo.Abp.EventBus.Kafka[Volo.Abp.EventBus.Kafka](https://www.nuget.org/packages/Volo.Abp.EventBus.Kafka)NuGet包到你的项目: |
|||
|
|||
* 安装[ABP CLI](https://docs.abp.io/en/abp/latest/CLI),如果你还没有安装. |
|||
* 在你想要安装 `Volo.Abp.EventBus.Kafka` 包的 `.csproj` 文件目录打开命令行(终端). |
|||
* 运行 `abp add-package Volo.Abp.EventBus.Kafka` 命令. |
|||
|
|||
如果你想要手动安装,安装[Volo.Abp.EventBus.Kafka](https://www.nuget.org/packages/Volo.Abp.EventBus.Kafka) NuGet 包到你的项目然后添加 `[DependsOn(typeof(AbpEventBusKafkaModule))]` 到你的项目[模块](Module-Development-Basics.md)类. |
|||
|
|||
## 配置 |
|||
|
|||
可以使用配置使用标准的[配置系统](Configuration.md),如 `appsettings.json` 文件,或[选项](Options.md)类. |
|||
|
|||
### `appsettings.json` 文件配置 |
|||
|
|||
这是配置Kafka设置最简单的方法. 它也非常强大,因为你可以使用[由AspNet Core支持的](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/)的任何其他配置源(如环境变量). |
|||
|
|||
**示例:最小化配置与默认配置连接到本地的Kafka服务器** |
|||
|
|||
|
|||
````json |
|||
{ |
|||
"Kafka": { |
|||
"EventBus": { |
|||
"GroupId": "MyGroupId", |
|||
"TopicName": "MyTopicName" |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
* `MyGroupId` 是应用程序的名称,用于Kafka的**GroupId**. |
|||
* `MyTopicName` 是**topic名称**. |
|||
|
|||
参阅[Kafka文档](https://docs.confluent.io/current/clients/confluent-kafka-dotnet/api/Confluent.Kafka.html)更好的了解这些选项. |
|||
|
|||
#### 连接 |
|||
|
|||
如果需要连接到本地主机以外的另一台服务器,需要配置连接属性. |
|||
|
|||
**示例: 指定主机名 (如IP地址)** |
|||
|
|||
````json |
|||
{ |
|||
"Kafka": { |
|||
"Connections": { |
|||
"Default": { |
|||
"BootstrapServers": "123.123.123.123:9092" |
|||
} |
|||
}, |
|||
"EventBus": { |
|||
"GroupId": "MyGroupId", |
|||
"TopicName": "MyTopicName" |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
允许定义多个连接. 在这种情况下,你可以指定用于事件总线的连接. |
|||
|
|||
**示例: 声明两个连接并将其中一个用于事件总线** |
|||
|
|||
````json |
|||
{ |
|||
"Kafka": { |
|||
"Connections": { |
|||
"Default": { |
|||
"BootstrapServers": "123.123.123.123:9092" |
|||
}, |
|||
"SecondConnection": { |
|||
"BootstrapServers": "321.321.321.321:9092" |
|||
} |
|||
}, |
|||
"EventBus": { |
|||
"GroupId": "MyGroupId", |
|||
"TopicName": "MyTopicName", |
|||
"ConnectionName": "SecondConnection" |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
这允许你可以在你的应用程序使用多个Kafka服务器,但将其中一个做为事件总线. |
|||
|
|||
你可以使用任何[ClientConfig](https://docs.confluent.io/current/clients/confluent-kafka-dotnet/api/Confluent.Kafka.ClientConfig.html)属性作为连接属性. |
|||
|
|||
**示例: 指定socket超时时间** |
|||
|
|||
````json |
|||
{ |
|||
"Kafka": { |
|||
"Connections": { |
|||
"Default": { |
|||
"BootstrapServers": "123.123.123.123:9092", |
|||
"SocketTimeoutMs": 60000 |
|||
} |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
### 选项类 |
|||
|
|||
`AbpKafkaOptions` 和 `AbpKafkaEventBusOptions` 类用于配置Kafka的连接字符串和事件总线选项. |
|||
|
|||
你可以在你的[模块](Module-Development-Basics.md)的 `ConfigureServices` 方法配置选项. |
|||
|
|||
**示例: 配置连接** |
|||
|
|||
````csharp |
|||
Configure<AbpKafkaOptions>(options => |
|||
{ |
|||
options.Connections.Default.BootstrapServers = "123.123.123.123:9092"; |
|||
options.Connections.Default.SaslUsername = "user"; |
|||
options.Connections.Default.SaslPassword = "pwd"; |
|||
}); |
|||
```` |
|||
|
|||
**示例: 配置 consumer config** |
|||
|
|||
````csharp |
|||
Configure<AbpKafkaOptions>(options => |
|||
{ |
|||
options.ConfigureConsumer = config => |
|||
{ |
|||
config.GroupId = "MyGroupId"; |
|||
config.EnableAutoCommit = false; |
|||
}; |
|||
}); |
|||
```` |
|||
|
|||
**示例: 配置 producer config** |
|||
|
|||
````csharp |
|||
Configure<AbpKafkaOptions>(options => |
|||
{ |
|||
options.ConfigureProducer = config => |
|||
{ |
|||
config.MessageTimeoutMs = 6000; |
|||
config.Acks = Acks.All; |
|||
}; |
|||
}); |
|||
```` |
|||
|
|||
**示例: 配置 topic specification** |
|||
|
|||
````csharp |
|||
Configure<AbpKafkaOptions>(options => |
|||
{ |
|||
options.ConfigureTopic = specification => |
|||
{ |
|||
specification.ReplicationFactor = 3; |
|||
specification.NumPartitions = 3; |
|||
}; |
|||
}); |
|||
```` |
|||
|
|||
使用这些选项类可以与 `appsettings.json` 组合在一起. 在代码中配置选项属性会覆盖配置文件中的值. |
|||
@ -1,140 +1,11 @@ |
|||
using System; |
|||
using System.IO; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
using Newtonsoft.Json.Linq; |
|||
using Volo.Abp.Cli.Args; |
|||
using Volo.Abp.Cli.Utils; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.Cli.Commands |
|||
{ |
|||
public class GenerateProxyCommand : IConsoleCommand, ITransientDependency |
|||
public class GenerateProxyCommand : ProxyCommandBase |
|||
{ |
|||
public Task ExecuteAsync(CommandLineArgs commandLineArgs) |
|||
{ |
|||
CheckAngularJsonFile(); |
|||
CheckNgSchematics(); |
|||
|
|||
var prompt = commandLineArgs.Options.ContainsKey("p") || commandLineArgs.Options.ContainsKey("prompt"); |
|||
var defaultValue = prompt ? null : "__default"; |
|||
|
|||
var module = commandLineArgs.Options.GetOrNull(Options.Module.Short, Options.Module.Long) ?? defaultValue; |
|||
var source = commandLineArgs.Options.GetOrNull(Options.Source.Short, Options.Source.Long) ?? defaultValue; |
|||
var target = commandLineArgs.Options.GetOrNull(Options.Target.Short, Options.Target.Long) ?? defaultValue; |
|||
|
|||
var commandBuilder = new StringBuilder("npx ng g @abp/ng.schematics:proxy"); |
|||
|
|||
if (module != null) |
|||
{ |
|||
commandBuilder.Append($" --module {module}"); |
|||
} |
|||
|
|||
if (source != null) |
|||
{ |
|||
commandBuilder.Append($" --source {source}"); |
|||
} |
|||
|
|||
if (target != null) |
|||
{ |
|||
commandBuilder.Append($" --target {target}"); |
|||
} |
|||
|
|||
CmdHelper.RunCmd(commandBuilder.ToString()); |
|||
|
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
private void CheckNgSchematics() |
|||
{ |
|||
var packageJsonPath = $"package.json"; |
|||
|
|||
if (!File.Exists(packageJsonPath)) |
|||
{ |
|||
throw new CliUsageException( |
|||
"package.json file not found" + |
|||
Environment.NewLine + |
|||
GetUsageInfo() |
|||
); |
|||
} |
|||
|
|||
var schematicsPackageNode = |
|||
(string) JObject.Parse(File.ReadAllText(packageJsonPath))["devDependencies"]?["@abp/ng.schematics"]; |
|||
|
|||
if (schematicsPackageNode == null) |
|||
{ |
|||
throw new CliUsageException( |
|||
"\"@abp/ng.schematics\" NPM package should be installed to the devDependencies before running this command!" + |
|||
Environment.NewLine + |
|||
GetUsageInfo() |
|||
); |
|||
} |
|||
} |
|||
|
|||
private void CheckAngularJsonFile() |
|||
{ |
|||
var angularPath = $"angular.json"; |
|||
if (!File.Exists(angularPath)) |
|||
{ |
|||
throw new CliUsageException( |
|||
"angular.json file not found. You must run this command in the angular folder." + |
|||
Environment.NewLine + Environment.NewLine + |
|||
GetUsageInfo() |
|||
); |
|||
} |
|||
} |
|||
|
|||
public string GetUsageInfo() |
|||
{ |
|||
var sb = new StringBuilder(); |
|||
|
|||
sb.AppendLine(""); |
|||
sb.AppendLine("Usage:"); |
|||
sb.AppendLine(""); |
|||
sb.AppendLine(" abp generate-proxy"); |
|||
sb.AppendLine(""); |
|||
sb.AppendLine("Options:"); |
|||
sb.AppendLine(""); |
|||
sb.AppendLine("-m|--module <module-name> (default: 'app') The name of the backend module you wish to generate proxies for."); |
|||
sb.AppendLine("-s|--source <source-name> (default: 'defaultProject') Angular project name to resolve the root namespace & API definition URL from."); |
|||
sb.AppendLine("-t|--target <target-name> (default: 'defaultProject') Angular project name to place generated code in."); |
|||
sb.AppendLine("-p|--prompt Asks the options from the command line prompt (for the missing options)"); |
|||
sb.AppendLine(""); |
|||
sb.AppendLine("See the documentation for more info: https://docs.abp.io/en/abp/latest/CLI"); |
|||
|
|||
return sb.ToString(); |
|||
} |
|||
|
|||
public string GetShortDescription() |
|||
{ |
|||
return "Generates Angular service proxies and DTOs to consume HTTP APIs."; |
|||
} |
|||
|
|||
public static class Options |
|||
{ |
|||
public static class Module |
|||
{ |
|||
public const string Short = "m"; |
|||
public const string Long = "module"; |
|||
} |
|||
|
|||
public static class Source |
|||
{ |
|||
public const string Short = "s"; |
|||
public const string Long = "source"; |
|||
} |
|||
public const string Name = "generate-proxy"; |
|||
|
|||
public static class Target |
|||
{ |
|||
public const string Short = "t"; |
|||
public const string Long = "target"; |
|||
} |
|||
protected override string CommandName => Name; |
|||
|
|||
public static class Prompt |
|||
{ |
|||
public const string Short = "p"; |
|||
public const string Long = "prompt"; |
|||
} |
|||
} |
|||
protected override string SchematicsCommandName => "proxy-add"; |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,144 @@ |
|||
using System; |
|||
using System.IO; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
using Newtonsoft.Json.Linq; |
|||
using Volo.Abp.Cli.Args; |
|||
using Volo.Abp.Cli.Utils; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.Cli.Commands |
|||
{ |
|||
public abstract class ProxyCommandBase : IConsoleCommand, ITransientDependency |
|||
{ |
|||
protected abstract string CommandName { get; } |
|||
|
|||
protected abstract string SchematicsCommandName { get; } |
|||
|
|||
public Task ExecuteAsync(CommandLineArgs commandLineArgs) |
|||
{ |
|||
CheckAngularJsonFile(); |
|||
CheckNgSchematics(); |
|||
|
|||
var prompt = commandLineArgs.Options.ContainsKey("p") || commandLineArgs.Options.ContainsKey("prompt"); |
|||
var defaultValue = prompt ? null : "__default"; |
|||
|
|||
var module = commandLineArgs.Options.GetOrNull(Options.Module.Short, Options.Module.Long) ?? defaultValue; |
|||
var source = commandLineArgs.Options.GetOrNull(Options.Source.Short, Options.Source.Long) ?? defaultValue; |
|||
var target = commandLineArgs.Options.GetOrNull(Options.Target.Short, Options.Target.Long) ?? defaultValue; |
|||
|
|||
var commandBuilder = new StringBuilder("npx ng g @abp/ng.schematics:" + SchematicsCommandName); |
|||
|
|||
if (module != null) |
|||
{ |
|||
commandBuilder.Append($" --module {module}"); |
|||
} |
|||
|
|||
if (source != null) |
|||
{ |
|||
commandBuilder.Append($" --source {source}"); |
|||
} |
|||
|
|||
if (target != null) |
|||
{ |
|||
commandBuilder.Append($" --target {target}"); |
|||
} |
|||
|
|||
CmdHelper.RunCmd(commandBuilder.ToString()); |
|||
|
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
private void CheckNgSchematics() |
|||
{ |
|||
var packageJsonPath = $"package.json"; |
|||
|
|||
if (!File.Exists(packageJsonPath)) |
|||
{ |
|||
throw new CliUsageException( |
|||
"package.json file not found" + |
|||
Environment.NewLine + |
|||
GetUsageInfo() |
|||
); |
|||
} |
|||
|
|||
var schematicsPackageNode = |
|||
(string) JObject.Parse(File.ReadAllText(packageJsonPath))["devDependencies"]?["@abp/ng.schematics"]; |
|||
|
|||
if (schematicsPackageNode == null) |
|||
{ |
|||
throw new CliUsageException( |
|||
"\"@abp/ng.schematics\" NPM package should be installed to the devDependencies before running this command!" + |
|||
Environment.NewLine + |
|||
GetUsageInfo() |
|||
); |
|||
} |
|||
} |
|||
|
|||
private void CheckAngularJsonFile() |
|||
{ |
|||
var angularPath = $"angular.json"; |
|||
if (!File.Exists(angularPath)) |
|||
{ |
|||
throw new CliUsageException( |
|||
"angular.json file not found. You must run this command in the angular folder." + |
|||
Environment.NewLine + Environment.NewLine + |
|||
GetUsageInfo() |
|||
); |
|||
} |
|||
} |
|||
|
|||
public string GetUsageInfo() |
|||
{ |
|||
var sb = new StringBuilder(); |
|||
|
|||
sb.AppendLine(""); |
|||
sb.AppendLine("Usage:"); |
|||
sb.AppendLine(""); |
|||
sb.AppendLine($" abp {CommandName}"); |
|||
sb.AppendLine(""); |
|||
sb.AppendLine("Options:"); |
|||
sb.AppendLine(""); |
|||
sb.AppendLine("-m|--module <module-name> (default: 'app') The name of the backend module you wish to generate proxies for."); |
|||
sb.AppendLine("-s|--source <source-name> (default: 'defaultProject') Angular project name to resolve the root namespace & API definition URL from."); |
|||
sb.AppendLine("-t|--target <target-name> (default: 'defaultProject') Angular project name to place generated code in."); |
|||
sb.AppendLine("-p|--prompt Asks the options from the command line prompt (for the missing options)"); |
|||
sb.AppendLine(""); |
|||
sb.AppendLine("See the documentation for more info: https://docs.abp.io/en/abp/latest/CLI"); |
|||
|
|||
return sb.ToString(); |
|||
} |
|||
|
|||
public string GetShortDescription() |
|||
{ |
|||
return "Generates Angular service proxies and DTOs to consume HTTP APIs."; |
|||
} |
|||
|
|||
public static class Options |
|||
{ |
|||
public static class Module |
|||
{ |
|||
public const string Short = "m"; |
|||
public const string Long = "module"; |
|||
} |
|||
|
|||
public static class Source |
|||
{ |
|||
public const string Short = "s"; |
|||
public const string Long = "source"; |
|||
} |
|||
|
|||
public static class Target |
|||
{ |
|||
public const string Short = "t"; |
|||
public const string Long = "target"; |
|||
} |
|||
|
|||
public static class Prompt |
|||
{ |
|||
public const string Short = "p"; |
|||
public const string Long = "prompt"; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
namespace Volo.Abp.Cli.Commands |
|||
{ |
|||
public class RemoveProxyCommand : ProxyCommandBase |
|||
{ |
|||
public const string Name = "remove-proxy"; |
|||
|
|||
protected override string CommandName => Name; |
|||
|
|||
protected override string SchematicsCommandName => "proxy-remove"; |
|||
} |
|||
} |
|||
@ -1,16 +0,0 @@ |
|||
export interface Schema { |
|||
/** |
|||
* Backend module to generate code for |
|||
*/ |
|||
module?: string; |
|||
|
|||
/** |
|||
* Angular project to resolve root namespace & API definition URL from |
|||
*/ |
|||
source?: string; |
|||
|
|||
/** |
|||
* Angular project to generate code in |
|||
*/ |
|||
target?: string; |
|||
} |
|||
@ -0,0 +1,60 @@ |
|||
import { strings } from '@angular-devkit/core'; |
|||
import { chain, SchematicContext, Tree } from '@angular-devkit/schematics'; |
|||
import { GenerateProxySchema } from '../../models'; |
|||
import { |
|||
buildDefaultPath, |
|||
createApiDefinitionGetter, |
|||
createApisGenerator, |
|||
createProxyClearer, |
|||
createProxyConfigReader, |
|||
createProxyConfigSaver, |
|||
createProxyIndexGenerator, |
|||
createProxyWarningSaver, |
|||
mergeAndAllowDelete, |
|||
removeDefaultPlaceholders, |
|||
resolveProject, |
|||
} from '../../utils'; |
|||
|
|||
export default function(schema: GenerateProxySchema) { |
|||
const params = removeDefaultPlaceholders(schema); |
|||
const moduleName = strings.camelize(params.module || 'app'); |
|||
|
|||
return chain([ |
|||
async (host: Tree, _context: SchematicContext) => { |
|||
const target = await resolveProject(host, params.target!); |
|||
const targetPath = buildDefaultPath(target.definition); |
|||
const readProxyConfig = createProxyConfigReader(targetPath); |
|||
let generated: string[] = []; |
|||
|
|||
try { |
|||
generated = readProxyConfig(host).generated; |
|||
const index = generated.findIndex(m => m === moduleName); |
|||
if (index < 0) generated.push(moduleName); |
|||
} catch (_) { |
|||
generated.push(moduleName); |
|||
} |
|||
|
|||
const getApiDefinition = createApiDefinitionGetter(params); |
|||
const data = { generated, ...(await getApiDefinition(host)) }; |
|||
data.generated = []; |
|||
|
|||
const clearProxy = createProxyClearer(targetPath); |
|||
|
|||
const saveProxyConfig = createProxyConfigSaver(data, targetPath); |
|||
|
|||
const saveProxyWarning = createProxyWarningSaver(targetPath); |
|||
|
|||
const generateApis = createApisGenerator(schema, generated); |
|||
|
|||
const generateIndex = createProxyIndexGenerator(targetPath); |
|||
|
|||
return chain([ |
|||
mergeAndAllowDelete(host, clearProxy), |
|||
saveProxyConfig, |
|||
saveProxyWarning, |
|||
generateApis, |
|||
generateIndex, |
|||
]); |
|||
}, |
|||
]); |
|||
} |
|||
@ -0,0 +1,45 @@ |
|||
import { chain, SchematicContext, Tree } from '@angular-devkit/schematics'; |
|||
import { GenerateProxySchema } from '../../models'; |
|||
import { |
|||
buildDefaultPath, |
|||
createApiDefinitionGetter, |
|||
createApisGenerator, |
|||
createProxyClearer, |
|||
createProxyConfigReader, |
|||
createProxyConfigSaver, |
|||
createProxyIndexGenerator, |
|||
mergeAndAllowDelete, |
|||
removeDefaultPlaceholders, |
|||
resolveProject, |
|||
} from '../../utils'; |
|||
|
|||
export default function(schema: GenerateProxySchema) { |
|||
const params = removeDefaultPlaceholders(schema); |
|||
|
|||
return async (host: Tree, _context: SchematicContext) => { |
|||
const target = await resolveProject(host, params.target!); |
|||
const targetPath = buildDefaultPath(target.definition); |
|||
|
|||
const readProxyConfig = createProxyConfigReader(targetPath); |
|||
const { generated } = readProxyConfig(host); |
|||
|
|||
const getApiDefinition = createApiDefinitionGetter(params); |
|||
const data = { generated, ...(await getApiDefinition(host)) }; |
|||
data.generated = []; |
|||
|
|||
const clearProxy = createProxyClearer(targetPath); |
|||
|
|||
const saveProxyConfig = createProxyConfigSaver(data, targetPath); |
|||
|
|||
const generateApis = createApisGenerator(schema, generated); |
|||
|
|||
const generateIndex = createProxyIndexGenerator(targetPath); |
|||
|
|||
return chain([ |
|||
mergeAndAllowDelete(host, clearProxy), |
|||
saveProxyConfig, |
|||
generateApis, |
|||
generateIndex, |
|||
]); |
|||
}; |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
{ |
|||
"$schema": "http://json-schema.org/schema", |
|||
"id": "SchematicsAbpGenerateProxy", |
|||
"title": "ABP Generate Proxy Schema", |
|||
"type": "object", |
|||
"properties": { |
|||
"module": { |
|||
"alias": "m", |
|||
"description": "Backend module name", |
|||
"type": "string", |
|||
"$default": { |
|||
"$source": "argv", |
|||
"index": 0 |
|||
}, |
|||
"x-prompt": "Please enter backend module name. (default: \"app\")" |
|||
}, |
|||
"source": { |
|||
"alias": "s", |
|||
"description": "Source Angular project for API definition URL & root namespace resolution", |
|||
"type": "string", |
|||
"$default": { |
|||
"$source": "argv", |
|||
"index": 1 |
|||
}, |
|||
"x-prompt": "Plese enter source Angular project for API definition URL & root namespace resolution. (default: workspace \"defaultProject\")" |
|||
}, |
|||
"target": { |
|||
"alias": "t", |
|||
"description": "Target Angular project to place the generated code", |
|||
"type": "string", |
|||
"$default": { |
|||
"$source": "argv", |
|||
"index": 2 |
|||
}, |
|||
"x-prompt": "Plese enter target Angular project to place the generated code. (default: workspace \"defaultProject\")" |
|||
} |
|||
}, |
|||
"required": [] |
|||
} |
|||
@ -0,0 +1,51 @@ |
|||
import { strings } from '@angular-devkit/core'; |
|||
import { chain, SchematicContext, Tree } from '@angular-devkit/schematics'; |
|||
import { GenerateProxySchema } from '../../models'; |
|||
import { |
|||
buildDefaultPath, |
|||
createApiDefinitionGetter, |
|||
createApisGenerator, |
|||
createProxyClearer, |
|||
createProxyConfigReader, |
|||
createProxyConfigSaver, |
|||
createProxyIndexGenerator, |
|||
mergeAndAllowDelete, |
|||
removeDefaultPlaceholders, |
|||
resolveProject, |
|||
} from '../../utils'; |
|||
|
|||
export default function(schema: GenerateProxySchema) { |
|||
const params = removeDefaultPlaceholders(schema); |
|||
const moduleName = strings.camelize(params.module || 'app'); |
|||
|
|||
return async (host: Tree, _context: SchematicContext) => { |
|||
const target = await resolveProject(host, params.target!); |
|||
const targetPath = buildDefaultPath(target.definition); |
|||
|
|||
const readProxyConfig = createProxyConfigReader(targetPath); |
|||
const { generated } = readProxyConfig(host); |
|||
|
|||
const index = generated.findIndex(m => m === moduleName); |
|||
if (index < 0) return host; |
|||
generated.splice(index, 1); |
|||
|
|||
const getApiDefinition = createApiDefinitionGetter(params); |
|||
const data = { generated, ...(await getApiDefinition(host)) }; |
|||
data.generated = []; |
|||
|
|||
const clearProxy = createProxyClearer(targetPath); |
|||
|
|||
const saveProxyConfig = createProxyConfigSaver(data, targetPath); |
|||
|
|||
const generateApis = createApisGenerator(schema, generated); |
|||
|
|||
const generateIndex = createProxyIndexGenerator(targetPath); |
|||
|
|||
return chain([ |
|||
mergeAndAllowDelete(host, clearProxy), |
|||
saveProxyConfig, |
|||
generateApis, |
|||
generateIndex, |
|||
]); |
|||
}; |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
{ |
|||
"$schema": "http://json-schema.org/schema", |
|||
"id": "SchematicsAbpGenerateProxy", |
|||
"title": "ABP Generate Proxy Schema", |
|||
"type": "object", |
|||
"properties": { |
|||
"module": { |
|||
"alias": "m", |
|||
"description": "Backend module name", |
|||
"type": "string", |
|||
"$default": { |
|||
"$source": "argv", |
|||
"index": 0 |
|||
}, |
|||
"x-prompt": "Please enter backend module name. (default: \"app\")" |
|||
}, |
|||
"source": { |
|||
"alias": "s", |
|||
"description": "Source Angular project for API definition URL & root namespace resolution", |
|||
"type": "string", |
|||
"$default": { |
|||
"$source": "argv", |
|||
"index": 1 |
|||
}, |
|||
"x-prompt": "Plese enter source Angular project for API definition URL & root namespace resolution. (default: workspace \"defaultProject\")" |
|||
}, |
|||
"target": { |
|||
"alias": "t", |
|||
"description": "Target Angular project to place the generated code", |
|||
"type": "string", |
|||
"$default": { |
|||
"$source": "argv", |
|||
"index": 2 |
|||
}, |
|||
"x-prompt": "Plese enter target Angular project to place the generated code. (default: workspace \"defaultProject\")" |
|||
} |
|||
}, |
|||
"required": [] |
|||
} |
|||
@ -1,42 +0,0 @@ |
|||
import { strings } from '@angular-devkit/core'; |
|||
import { |
|||
branchAndMerge, |
|||
chain, |
|||
schematic, |
|||
SchematicContext, |
|||
Tree, |
|||
} from '@angular-devkit/schematics'; |
|||
import { API_DEFINITION_ENDPOINT } from '../../constants'; |
|||
import { ApiDefinition } from '../../models'; |
|||
import { |
|||
buildDefaultPath, |
|||
createApiDefinitionSaver, |
|||
getApiDefinition, |
|||
getSourceUrl, |
|||
removeDefaultPlaceholders, |
|||
resolveProject, |
|||
} from '../../utils'; |
|||
import { Schema as GenerateProxySchema } from './schema'; |
|||
|
|||
export default function(schema: GenerateProxySchema) { |
|||
const params = removeDefaultPlaceholders(schema); |
|||
const moduleName = strings.camelize(params.module || 'app'); |
|||
|
|||
return chain([ |
|||
async (tree: Tree, _context: SchematicContext) => { |
|||
const source = await resolveProject(tree, params.source!); |
|||
const target = await resolveProject(tree, params.target!); |
|||
const sourceUrl = getSourceUrl(tree, source, moduleName); |
|||
const targetPath = buildDefaultPath(target.definition); |
|||
const data: ApiDefinition = await getApiDefinition(sourceUrl + API_DEFINITION_ENDPOINT); |
|||
|
|||
const saveApiDefinition = createApiDefinitionSaver( |
|||
data, |
|||
`${targetPath}/shared/api-definition.json`, |
|||
); |
|||
const createApi = schematic('api', schema); |
|||
|
|||
return branchAndMerge(chain([saveApiDefinition, createApi])); |
|||
}, |
|||
]); |
|||
} |
|||
@ -1,16 +0,0 @@ |
|||
export interface Schema { |
|||
/** |
|||
* Backend module to generate code for |
|||
*/ |
|||
module?: string; |
|||
|
|||
/** |
|||
* Angular project to resolve root namespace & API definition URL from |
|||
*/ |
|||
source?: string; |
|||
|
|||
/** |
|||
* Angular project to generate code in |
|||
*/ |
|||
target?: string; |
|||
} |
|||
@ -1,3 +1,4 @@ |
|||
export * from './api'; |
|||
export * from './proxy'; |
|||
export * from './system-types'; |
|||
export * from './volo'; |
|||
|
|||
@ -0,0 +1,18 @@ |
|||
export const PROXY_PATH = '/proxy'; |
|||
export const PROXY_CONFIG_PATH = `${PROXY_PATH}/generate-proxy.json`; |
|||
export const PROXY_WARNING_PATH = `${PROXY_PATH}/README.md`; |
|||
|
|||
export const PROXY_WARNING = `# Proxy Generation Output
|
|||
|
|||
This directory includes the output of the latest proxy generation. |
|||
The files and folders in it will be overwritten when proxy generation is run again. |
|||
Therefore, please do not place your own content in this folder. |
|||
|
|||
In addition, \`generate-proxy.json\` works like a lock file.
|
|||
It includes information used by the proxy generator, so please do not delete or modify it. |
|||
|
|||
Finally, the name of the files and folders should not be changed for two reasons: |
|||
- Proxy generator will keep creating them at those paths and you will have multiple copies of the same content. |
|||
- ABP Suite generates files which include imports from this folder. |
|||
|
|||
`;
|
|||
@ -0,0 +1,16 @@ |
|||
export interface GenerateProxySchema { |
|||
/** |
|||
* Backend module name |
|||
*/ |
|||
module?: string; |
|||
|
|||
/** |
|||
* Source Angular project for API definition URL & root namespace resolution |
|||
*/ |
|||
source?: string; |
|||
|
|||
/** |
|||
* Target Angular project to place the generated code |
|||
*/ |
|||
target?: string; |
|||
} |
|||
@ -1,7 +1,10 @@ |
|||
export * from './api-definition'; |
|||
export * from './generate-proxy-schema'; |
|||
export * from './import'; |
|||
export * from './method'; |
|||
export * from './model'; |
|||
export * from './project'; |
|||
export * from './proxy-config'; |
|||
export * from './service'; |
|||
export * from './tree'; |
|||
export * from './util'; |
|||
|
|||
@ -0,0 +1,5 @@ |
|||
import { ApiDefinition } from './api-definition'; |
|||
|
|||
export interface ProxyConfig extends ApiDefinition { |
|||
generated: string[]; |
|||
} |
|||
@ -0,0 +1 @@ |
|||
export type WriteOp = 'create' | 'overwrite'; |
|||
@ -0,0 +1,6 @@ |
|||
import { chain, schematic } from '@angular-devkit/schematics'; |
|||
import { GenerateProxySchema } from '../models'; |
|||
|
|||
export function createApisGenerator(schema: GenerateProxySchema, generated: string[]) { |
|||
return chain(generated.map(m => schematic('api', { ...schema, module: m }))); |
|||
} |
|||
@ -0,0 +1,48 @@ |
|||
import { strings } from '@angular-devkit/core'; |
|||
import { Tree } from '@angular-devkit/schematics'; |
|||
import { PROXY_PATH } from '../constants'; |
|||
import { createFileSaver } from './file'; |
|||
|
|||
export function createProxyIndexGenerator(targetPath: string) { |
|||
return createBarrelsGenerator(targetPath + PROXY_PATH); |
|||
} |
|||
|
|||
export function createBarrelsGenerator(rootPath: string) { |
|||
return (tree: Tree) => { |
|||
generateBarrelFromPath(tree, rootPath); |
|||
return tree; |
|||
}; |
|||
} |
|||
|
|||
export function generateBarrelFromPath(tree: Tree, indexPath: string) { |
|||
const saveFile = createFileSaver(tree); |
|||
const dir = tree.getDir(indexPath); |
|||
|
|||
const _exports: string[] = []; |
|||
|
|||
dir.subfiles.forEach(fragment => { |
|||
if (!fragment.endsWith('.ts')) return; |
|||
|
|||
_exports.push(`export * from './${fragment.replace(/\.ts$/, '')}';`); |
|||
}); |
|||
|
|||
dir.subdirs.forEach(fragment => { |
|||
const subDirPath = indexPath + '/' + fragment; |
|||
const subDir = tree.getDir(subDirPath); |
|||
let hasFiles = false; |
|||
subDir.visit(() => (hasFiles = true)); |
|||
if (!hasFiles) return; |
|||
|
|||
_exports.push(`export * as ${strings.classify(fragment)} from './${fragment}';`); |
|||
generateBarrelFromPath(tree, subDirPath); |
|||
}); |
|||
|
|||
_exports.sort(); |
|||
|
|||
if (_exports.length) |
|||
saveFile( |
|||
indexPath + '/index.ts', |
|||
_exports.join(` |
|||
`),
|
|||
); |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
import { Tree } from '@angular-devkit/schematics'; |
|||
|
|||
export function createFileSaver(tree: Tree) { |
|||
return (filePath: string, fileContent: string) => |
|||
tree.exists(filePath) |
|||
? tree.overwrite(filePath, fileContent) |
|||
: tree.create(filePath, fileContent); |
|||
} |
|||