Browse Source

Merge branch 'abpframework:dev' into dev

pull/12869/head
Nader Javid 4 years ago
committed by GitHub
parent
commit
022d626d42
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      README.md
  2. 2
      docs/en/Modules/OpenIddict.md
  3. 14
      docs/en/Road-Map.md
  4. 2
      docs/zh-Hans/Module-Development-Basics.md
  5. 570
      docs/zh-Hans/Text-Templating-Razor.md
  6. 527
      docs/zh-Hans/Text-Templating-Scriban.md
  7. 444
      docs/zh-Hans/Text-Templating.md
  8. BIN
      docs/zh-Hans/images/abp-book.png
  9. BIN
      docs/zh-Hans/images/abp-select2-multiple.png
  10. BIN
      docs/zh-Hans/images/abp-select2-single.png
  11. BIN
      docs/zh-Hans/images/account-module-forgot-password.png
  12. BIN
      docs/zh-Hans/images/account-module-login.png
  13. BIN
      docs/zh-Hans/images/account-module-manage-account.png
  14. BIN
      docs/zh-Hans/images/account-module-register.png
  15. BIN
      docs/zh-Hans/images/add-new-propert-to-user-database-extra-properties.png
  16. BIN
      docs/zh-Hans/images/add-new-propert-to-user-database-field.png
  17. BIN
      docs/zh-Hans/images/add-new-property-enum.png
  18. BIN
      docs/zh-Hans/images/add-new-property-to-user-form-validation-error-custom.png
  19. BIN
      docs/zh-Hans/images/add-new-property-to-user-form-validation-error.png
  20. BIN
      docs/zh-Hans/images/add-new-property-to-user-form.png
  21. BIN
      docs/zh-Hans/images/add-new-property-to-user-table.png
  22. BIN
      docs/zh-Hans/images/angular-module-dev-app-project.png
  23. BIN
      docs/zh-Hans/images/angular-module-folder-structure.png
  24. BIN
      docs/zh-Hans/images/blazor-generic-exception-message.png
  25. BIN
      docs/zh-Hans/images/blazor-user-friendly-exception.png
  26. BIN
      docs/zh-Hans/images/bookstore-visual-studio-solution-v3.png
  27. BIN
      docs/zh-Hans/images/cmskit-module-blog-post-edit.png
  28. BIN
      docs/zh-Hans/images/cmskit-module-blog-posts-page.png
  29. BIN
      docs/zh-Hans/images/cmskit-module-blogs-edit.png
  30. BIN
      docs/zh-Hans/images/cmskit-module-blogs-feature-action.png
  31. BIN
      docs/zh-Hans/images/cmskit-module-blogs-page.png
  32. BIN
      docs/zh-Hans/images/cmskit-module-comment-page.png
  33. BIN
      docs/zh-Hans/images/cmskit-module-comments-detail.png
  34. BIN
      docs/zh-Hans/images/cmskit-module-features-dialog-2.png
  35. BIN
      docs/zh-Hans/images/cmskit-module-features-dialog.png
  36. BIN
      docs/zh-Hans/images/cmskit-module-features-scroll-index.png
  37. BIN
      docs/zh-Hans/images/cmskit-module-global-resources-page.png
  38. BIN
      docs/zh-Hans/images/cmskit-module-menus-page.png
  39. BIN
      docs/zh-Hans/images/cmskit-module-menus-public.png
  40. BIN
      docs/zh-Hans/images/cmskit-module-pages-edit.png
  41. BIN
      docs/zh-Hans/images/cmskit-module-pages-page.png
  42. BIN
      docs/zh-Hans/images/cmskit-module-ratings.png
  43. BIN
      docs/zh-Hans/images/cmskit-module-reactions.png
  44. BIN
      docs/zh-Hans/images/cmskit-module-tag-edit.png
  45. BIN
      docs/zh-Hans/images/cmskit-module-tags-page.png
  46. BIN
      docs/zh-Hans/images/data-table-colum-extension-blazor-component-render-solution.png
  47. BIN
      docs/zh-Hans/images/data-table-colum-extension-blazor-component-render.png
  48. BIN
      docs/zh-Hans/images/docs-create-project-v4.4.0.png
  49. BIN
      docs/zh-Hans/images/docs-module_solution-explorer.png
  50. BIN
      docs/zh-Hans/images/extension-navigation-property-form.png
  51. BIN
      docs/zh-Hans/images/extension-navigation-property-table.png
  52. BIN
      docs/zh-Hans/images/features-modal.png
  53. BIN
      docs/zh-Hans/images/features-module-opening.png
  54. BIN
      docs/zh-Hans/images/generated-static-client-proxies.png
  55. BIN
      docs/zh-Hans/images/hello-template-razor.png
  56. BIN
      docs/zh-Hans/images/identity-module-menu.png
  57. BIN
      docs/zh-Hans/images/identity-module-permissions.png
  58. BIN
      docs/zh-Hans/images/identity-module-roles.png
  59. BIN
      docs/zh-Hans/images/identity-module-users.png
  60. BIN
      docs/zh-Hans/images/implementing-domain-driven-design-book.png
  61. BIN
      docs/zh-Hans/images/module-tenant-management-actions.png
  62. BIN
      docs/zh-Hans/images/module-tenant-management-new-tenant.png
  63. BIN
      docs/zh-Hans/images/module-tenant-management-page.png
  64. BIN
      docs/zh-Hans/images/multiple-file-template-razor.png
  65. BIN
      docs/zh-Hans/images/optimistic-concurrency.png
  66. BIN
      docs/zh-Hans/images/page-header-toolbar-blazor.png
  67. BIN
      docs/zh-Hans/images/page-toolbar-button-blazor.png
  68. BIN
      docs/zh-Hans/images/page-toolbar-button.png
  69. BIN
      docs/zh-Hans/images/page-toolbar-custom-component-blazor.png
  70. BIN
      docs/zh-Hans/images/page-toolbar-custom-component.png
  71. BIN
      docs/zh-Hans/images/permissions-module-dialog.png
  72. BIN
      docs/zh-Hans/images/permissions-module-open-dialog.png
  73. BIN
      docs/zh-Hans/images/replace-email-layout-razor.png
  74. BIN
      docs/zh-Hans/images/static-js-proxy-example.png
  75. BIN
      docs/zh-Hans/images/table-column-extension-example-blazor.png
  76. BIN
      docs/zh-Hans/images/table-column-extension-example.png
  77. BIN
      docs/zh-Hans/images/upgrade-diff-empty-folders.png
  78. BIN
      docs/zh-Hans/images/user-action-blazor-extension-click-me.png
  79. BIN
      docs/zh-Hans/images/user-action-extension-click-me.png
  80. BIN
      docs/zh-Hans/images/user-action-extension-on-blazor-project.png
  81. BIN
      docs/zh-Hans/images/user-action-extension-on-solution.png
  82. BIN
      docs/zh-Hans/images/winmerge-comparison-result.png
  83. BIN
      docs/zh-Hans/images/winmerge-file-diff.png
  84. BIN
      docs/zh-Hans/images/winmerge-open-folders.png
  85. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ServiceProxying/Angular/AngularServiceProxyGenerator.cs
  86. 24
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Values/ValueObject.cs
  87. 20
      framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/PropertyApiDescriptionModel.cs
  88. 2
      framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Urls/AppUrlProvider.cs
  89. 10
      framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Values/Address.cs
  90. 10
      framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Values/ValueObject_Tests.cs
  91. 11
      modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.cshtml.cs
  92. 22
      modules/openiddict/app/OpenIddict.Demo.Client.Mvc/Pages/Index.cshtml
  93. 2
      modules/openiddict/app/OpenIddict.Demo.Client.Mvc/Program.cs
  94. 1
      modules/openiddict/app/OpenIddict.Demo.Server/OpenIddict.Demo.Server.csproj
  95. 13
      modules/openiddict/app/OpenIddict.Demo.Server/OpenIddictServerModule.cs
  96. 10
      modules/openiddict/app/OpenIddict.Demo.Server/Program.cs
  97. 1
      modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo.Abp.OpenIddict.AspNetCore.csproj
  98. 122
      modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictAspNetCoreModule.cs
  99. 2
      modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictOptions.cs
  100. 0
      modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/WildcardDomains/AbpOpenIddictWildcardDomainBase.cs

2
README.md

@ -7,7 +7,7 @@
[![MyGet (nightly builds)](https://img.shields.io/myget/abp-nightly/vpre/Volo.Abp.svg?style=flat-square)](https://docs.abp.io/en/abp/latest/Nightly-Builds)
[![NuGet Download](https://img.shields.io/nuget/dt/Volo.Abp.Core.svg?style=flat-square)](https://www.nuget.org/packages/Volo.Abp.Core)
ABP Framework is a complete **infrastructure** based on the **ASP.NET Core** to create **modern web applications** and **APIs** by following the software development **best practices** and the **latest technologies**.
ABP Framework is a complete **infrastructure** based on the **ASP.NET Core** to create **modern web applications** and **APIs** by following the software development **best practices** and the **latest technologies**. Check out https://abp.io
## Getting Started

2
docs/en/Modules/OpenIddict.md

@ -81,7 +81,7 @@ PreConfigure<OpenIddictServerBuilder>(builder =>
});
```
#### AbpOpenIddictOptions
#### AbpOpenIddictAspNetCoreOptions
`UpdateAbpClaimTypes(default: true)`: Updates AbpClaimTypes to be compatible with identity server claims.
`AddDevelopmentEncryptionAndSigningCertificate(default: true)`: Registers (and generates if necessary) a user-specific development encryption/development signing certificate.

14
docs/en/Road-Map.md

@ -4,15 +4,18 @@ This document provides a road map, release schedule and planned features for the
## Next Versions
### v5.3
### v5.4
In [5.3 milestone](https://github.com/abpframework/abp/milestone/65), we will be mostly working on the following topics:
In [5.4 milestone](https://github.com/abpframework/abp/milestone/67), we will be mostly working on the following topics:
* Providing an OpenIddict integration to replace current IdentityServer4 integration.
* Maturing and documenting the [eShopOnAbp](https://github.com/abpframework/eShopOnAbp) project, writing a free e-book that explains the solution.
* Working on the [LeptonX](https://blog.abp.io/abp/LeptonX-Theme-for-ABP-Framework-Alpha-Release) theme and making it as the default theme for the ABP Framework UI options.
* Working on more examples and guides.
* Improvements on the existing features and providing more guides.
The planned release date for v5.3 is **May, 2022**.
The planned release date for v5.4 is **July, 2022**.
> After the version 5.4, we will be working for 6.0 which will be released in the end of 2022 based on .NET 7.0.
## Backlog Items
@ -20,8 +23,8 @@ The *Next Versions* section above shows the main focus of the planned versions.
Here, a list of major items in the backlog we are considering to work on in the next versions.
* [#7221](https://github.com/abpframework/abp/issues/7221) / Alternative to IdentityServer4
* [#2183](https://github.com/abpframework/abp/issues/2183) / Dapr integration
* [#6655](https://github.com/abpframework/abp/pull/6655) / Use Typescript for the MVC UI
* [#236](https://github.com/abpframework/abp/issues/236) / Resource based authorization system
* [#2882](https://github.com/abpframework/abp/issues/2882) / Providing a gRPC integration infrastructure (while it is [already possible](https://github.com/abpframework/abp-samples/tree/master/GrpcDemo) to create or consume gRPC endpoints for your application, we plan to create endpoints for the [standard application modules](https://docs.abp.io/en/abp/latest/Modules/Index))
* [#1754](https://github.com/abpframework/abp/issues/1754) / Multi-lingual entities
@ -31,7 +34,6 @@ Here, a list of major items in the backlog we are considering to work on in the
* [#4223](https://github.com/abpframework/abp/issues/4223) / WebHook system
* [#162](https://github.com/abpframework/abp/issues/162) / Azure ElasticDB Integration for multitenancy
* [#2296](https://github.com/abpframework/abp/issues/2296) / Feature toggling infrastructure
* [#6655](https://github.com/abpframework/abp/pull/6655) / Use Typescript for the MVC UI
You can always check the milestone planning and the prioritized backlog issues on [the GitHub repository](https://github.com/abpframework/abp/milestones) for a detailed road map. The backlog items are subject to change. We are adding new items and changing priorities based on the community feedbacks and goals of the project.

2
docs/zh-Hans/Module-Development-Basics.md

@ -49,7 +49,7 @@ public class BlogModule : AbpModule
}
}
````
有关配置系统的更多信息,请参阅配置(TODO:link)文档.
有关配置系统的更多信息,请参阅[配置](Configuration.md)文档.
##### 配置服务前和后

570
docs/zh-Hans/Text-Templating-Razor.md

@ -0,0 +1,570 @@
# Razor 集成
Razor模板是标准的C#类, 所以你可以使用任何C#的功能, 例如`依赖注入`, 使用`LINQ`, 自定义方法甚至使用`仓储`
## 安装
建议使用[ABP CLI](CLI.md)安装此包.
### 使用ABP CLI
在项目文件夹(.csproj 文件)中打开命令行窗口并输入以下命令:
````bash
abp add-package Volo.Abp.TextTemplating.Razor
````
### 手动安装
如果你想要手动安装:
1. 添加 [Volo.Abp.TextTemplating.Razor](https://www.nuget.org/packages/Volo.Abp.TextTemplating.Razor) NuGet 包到你的项目:
````
Install-Package Volo.Abp.TextTemplating.Razor
````
2.添加 `AbpTextTemplatingRazorModule` 到你的模块的依赖列表:
````csharp
[DependsOn(
//...other dependencies
typeof(AbpTextTemplatingRazorModule) //Add the new module dependency
)]
public class YourModule : AbpModule
{
}
````
## 添加 MetadataReference到CSharpCompilerOptions
你需要将添加`MetadataReference`模板中使用的类型添加到 `CSharpCompilerOptions` 的 `References`.
```csharp
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpRazorTemplateCSharpCompilerOptions>(options =>
{
options.References.Add(MetadataReference.CreateFromFile(typeof(YourModule).Assembly.Location));
});
}
```
## 添加MetadataReference到模板
你可以添加一些`MetadataReference`到模板
```csharp
public override void ConfigureServices(ServiceConfigurationContext context)
{
services.Configure<AbpCompiledViewProviderOptions>(options =>
{
//Hello is template name.
options.TemplateReferences.Add("Hello", new List<Assembly>()
{
Assembly.Load("Microsoft.Extensions.Logging.Abstractions"),
Assembly.Load("Microsoft.Extensions.Logging")
}
.Select(x => MetadataReference.CreateFromFile(x.Location))
.ToList());
});
}
```
## 定义模板
在渲染模板之前,需要定义它. 创建一个继承自 `TemplateDefinitionProvider` 的类:
````csharp
public class DemoTemplateDefinitionProvider : TemplateDefinitionProvider
{
public override void Define(ITemplateDefinitionContext context)
{
context.Add(
new TemplateDefinition("Hello") //template name: "Hello"
.WithRazorEngine()
.WithVirtualFilePath(
"/Demos/Hello/Hello.cshtml", //template content path
isInlineLocalized: true
)
);
}
}
````
* `context` 对象用于添加新模板或获取依赖模块定义的模板. 使用 `context.Add(...)` 定义新模板.
* `TemplateDefinition` 是代表模板的类,每个模板必须有唯一的名称(在渲染模板时使用).
* `/Demos/Hello/Hello.cshtml` 是模板文件的路径.
* `isInlineLocalized` 声明针对所有语言使用一个模板(`true` 还是针对每种语言使用不同的模板(`false`). 更多内容参阅下面的本地化部分.
* `WithRenderEngine` 方法为模板设置渲染引擎.
### 模板基类
每个 `cshtml` 模板页面都需要继承`RazorTemplatePageBase` 或 `RazorTemplatePageBase<Model>`. 基类提供了一些使用实用的属性可以在模板中使用. 例如: `Localizer`, `ServiceProvider`.
### 模板内容
`WithVirtualFilePath` 表示我们使用[虚拟文件系统](Virtual-File-System.md)存储模板内容. 在项目内创建一个 `Hello.cshtml` 文件,并在属性窗口中将其标记为"**嵌入式资源**":
![hello-template-razor](images/hello-template-razor.png)
示例 `Hello.cshtml` 内容如下所示:
```csharp
namespace HelloModelNamespace
{
public class HelloModel
{
public string Name { get; set; }
}
}
```
[虚拟文件系统](Virtual-File-System.md) 需要在[模块](Module-Development-Basics.md)类的 `ConfigureServices` 方法添加你的文件:
````csharp
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<TextTemplateDemoModule>("TextTemplateDemo");
});
````
* `TextTemplateDemoModule`是模块类.
* `TextTemplateDemo` 是你的项目的根命名空间.
## 渲染模板
`ITemplateRenderer` 服务用于渲染模板内容.
### 示例: 渲染一个简单的模板
````csharp
public class HelloDemo : ITransientDependency
{
private readonly ITemplateRenderer _templateRenderer;
public HelloDemo(ITemplateRenderer templateRenderer)
{
_templateRenderer = templateRenderer;
}
public async Task RunAsync()
{
var result = await _templateRenderer.RenderAsync(
"Hello", //the template name
new HelloModel
{
Name = "John"
}
);
Console.WriteLine(result);
}
}
````
* `HelloDemo` 是一个简单的类,在构造函数注入了 `ITemplateRenderer` 并在 `RunAsync` 方法中使用它.
* `RenderAsync` 有两个基本参数:
* `templateName`: 要渲染的模板名称 (本示例中是 `Hello`).
* `model`: 在模板内部用做 `model` 的对象 (本示例中是 `HelloModel` 对象).
示例会返回以下结果:
````csharp
Hello John :)
````
## 本地化
可以基于当前文化对模板内容进行本地化. 以下部分描述了两种类型的本地化选项.
### 内联本地化
内联本地化使用[本地化系统](Localization.md)本地化模板内的文本.
#### 示例: 重置密码链接
假设你需要向用户发送电子邮件重置密码. 模板内容:
```csharp
namespace ResetMyPasswordModelNamespace
{
public class ResetMyPasswordModel
{
public string Link { get; set; }
public string Name { get; set; }
}
}
```
```csharp
@inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase<ResetMyPasswordModelNamespace.ResetMyPasswordModel>
<a title="@Localizer["ResetMyPasswordTitle"]" href="@Model.Link">@Localizer["ResetMyPassword", Model.Name]</a>
```
`Localizer` 函数用于根据当前用户的文化来定位给定的Key,你需要在本地化文件中定义 `ResetMyPassword` 键:
````json
"ResetMyPasswordTitle": "Reset my password",
"ResetMyPassword": "Hi {0}, Click here to reset your password"
````
你还需要在模板定义提供程序类中声明要与此模板一起使用的本地化资源:
````csharp
context.Add(
new TemplateDefinition(
"PasswordReset", //Template name
typeof(DemoResource) //LOCALIZATION RESOURCE
)
.WithRazorEngine()
.WithVirtualFilePath(
"/Demos/PasswordReset/PasswordReset.cshtml", //template content path
isInlineLocalized: true
)
);
````
当你这样渲染模板时:
````csharp
var result = await _templateRenderer.RenderAsync(
"PasswordReset", //the template name
new PasswordResetModel
{
Name = "john",
Link = "https://abp.io/example-link?userId=123&token=ABC"
}
);
````
你可以看到以下本地化结果:
````csharp
<a title="Reset my password" href="https://abp.io/example-link?userId=123&token=ABC">Hi john, Click here to reset your password</a>
````
> 如果你为应用程序定义了 [默认本地化资源](Localization.md), 则无需声明模板定义的资源类型.
### 多个内容本地化
你可能希望为每种语言创建不同的模板文件,而不是使用本地化系统本地化单个模板. 如果模板对于特定的文化(而不是简单的文本本地化)应该是完全不同的,则可能需要使用它.
#### 示例: 欢迎电子邮件模板
假设你要发送电子邮件欢迎用户,但要定义基于用户的文化完全不同的模板.
首先创建一个文件夹,将模板放在里面,像 `en.cshtml`, `tr.cshtml` 每一个你支持的文化:
![multiple-file-template-razor](images/multiple-file-template-razor.png)
然后在模板定义提供程序类中添加模板定义:
````csharp
context.Add(
new TemplateDefinition(
name: "WelcomeEmail",
defaultCultureName: "en"
)
.WithRazorEngine()
.WithVirtualFilePath(
"/Demos/WelcomeEmail/Templates", //template content folder
isInlineLocalized: false
)
);
````
* 设置 **默认文化名称**, 当没有所需的文化模板,回退到缺省文化.
* 指定 **模板文件夹** 而不是单个模板文件.
* 设置 `isInlineLocalized` 为 `false`.
就这些,你可以渲染当前文化的模板:
````csharp
var result = await _templateRenderer.RenderAsync("WelcomeEmail");
````
> 为了简单我们跳过了模型,但是你可以使用前面所述的模型.
### 指定文化
`ITemplateRenderer` 服务如果没有指定则使用当前文化 (`CultureInfo.CurrentUICulture`). 如果你需要你可以使用 `cultureName` 参数指定文化.
````csharp
var result = await _templateRenderer.RenderAsync(
"WelcomeEmail",
cultureName: "en"
);
````
## 布局模板
布局模板用于在其他模板之间创建共享布局. 它类似于ASP.NET Core MVC / Razor Pages中的布局系统.
### 示例: 邮件HTML布局模板
例如,你想为所有电子邮件模板创建一个布局.
首先像之前一样创建一个模板文件:
```csharp
@inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
</head>
<body>
@Body
</body>
</html>
```
* 布局模板必须具有 **Body** 部分作为渲染的子内容的占位符.
在模板定义提供程序中注册模板:
````csharp
context.Add(
new TemplateDefinition(
"EmailLayout",
isLayout: true //SET isLayout!
)
.WithRazorEngine()
.WithVirtualFilePath(
"/Demos/EmailLayout/EmailLayout.cshtml",
isInlineLocalized: true
)
);
````
现在你可以将此模板用作任何其他模板的布局:
````csharp
context.Add(
new TemplateDefinition(
name: "WelcomeEmail",
defaultCultureName: "en",
layout: "EmailLayout" //Set the LAYOUT
)
.WithRazorEngine()
.WithVirtualFilePath(
"/Demos/WelcomeEmail/Templates",
isInlineLocalized: false
)
);
````
## 全局上下文
ABP传递 `model`,可用于访问模板内的模型. 如果需要,可以传递更多的全局变量.
示例模板内容:
````csharp
@inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase
A global object value: @GlobalContext["myGlobalObject"]
````
模板假定它渲染上下文中的 `myGlobalObject` 对象. 你可以如下所示提供它:
````csharp
var result = await _templateRenderer.RenderAsync(
"GlobalContextUsage",
globalContext: new Dictionary<string, object>
{
{"myGlobalObject", "TEST VALUE"}
}
);
````
渲染的结果将是:
````
A global object value: TEST VALUE
````
## 替换存在的模板
通过替换应用程序中使用的模块定义的模板. 这样你可以根据自己的需求自定义模板,而无需更改模块代码.
### 选项-1: 使用虚拟文件系统
[虚拟文件系统](Virtual-File-System.md)允许你通过将相同文件放入项目中的相同路径来覆盖任何文件.
#### 示例: 替换标准电子邮件布局模板
ABP框架提供了一个[邮件发送系统](Emailing.md), 它在内部使用文本模板来渲染邮件内容. 它在 `/Volo/Abp/Emailing/Templates/Layout.cshtml` 路径定义了一个标准邮件布局模板. 模板的唯一名称是 `Abp.StandardEmailTemplates.Layout` 并且这个字符中`Volo.Abp.Emailing.Templates.StandardEmailTemplates`静态类上定义为常量.
执行以下步骤将替换模板替换成你自定义的;
**1)** 在你的项目中相同的路径添加一个新文件 (`/Volo/Abp/Emailing/Templates/Layout.cshtml`):
![replace-email-layout-razor](images/replace-email-layout-razor.png)
**2)** 准备你的邮件布局模板:
````html
@inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
</head>
<body>
<h1>This my header</h1>
@Body
<footer>
This is my footer...
</footer>
</body>
</html>
````
此示例只是向模板添加页眉和页脚并呈现它们之间的内容(请参阅上面的布局模板部分).
**3)** 在`.csproj`文件配置嵌入式资源e
* 添加 [Microsoft.Extensions.FileProviders.Embedded](https://www.nuget.org/packages/Microsoft.Extensions.FileProviders.Embedded) NuGet 包到你的项目.
* 在 `.csproj` 中添加 `<GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>` 到 `<PropertyGroup>...</PropertyGroup>` 部分.
* 添加以下代码到你的 `.csproj` 文件:
````xml
<ItemGroup>
<None Remove="Volo\Abp\Emailing\Templates\*.cshtml" />
<EmbeddedResource Include="Volo\Abp\Emailing\Templates\*.cshtml" />
</ItemGroup>
````
这将模板文件做为"嵌入式资源".
**4)** 配置虚拟文件系统
在[模块](Module-Development-Basics.md) `ConfigureServices` 方法配置 `AbpVirtualFileSystemOptions` 将嵌入的文件添加到虚拟文件系统中:
```csharp
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<BookStoreDomainModule>();
});
```
`BookStoreDomainModule` 应该是你的模块名称
> 确保你的模块(支持或间接)[依赖](Module-Development-Basics.md) `AbpEmailingModule`. 因为VFS可以基于依赖顺序覆盖文件.
现在渲染邮件布局模板时会使用你的模板.
### 选项-2: 使用模板定义提供者
你可以创建一个模板定义提供者类来获取邮件布局模板来更改它的虚拟文件路径.
**示例: 使用 `/MyTemplates/EmailLayout.cshtml` 文件而不是标准模板**
```csharp
using Volo.Abp.DependencyInjection;
using Volo.Abp.Emailing.Templates;
using Volo.Abp.TextTemplating;
namespace MyProject
{
public class MyTemplateDefinitionProvider
: TemplateDefinitionProvider, ITransientDependency
{
public override void Define(ITemplateDefinitionContext context)
{
var emailLayoutTemplate = context.GetOrNull(StandardEmailTemplates.Layout);
emailLayoutTemplate
.WithVirtualFilePath(
"/MyTemplates/EmailLayout.cshtml",
isInlineLocalized: true
);
}
}
}
```
你依然应该添加 `/MyTemplates/EmailLayout.cshtml` 到虚拟文件系统. 这种方法允许你在任何文件夹中找到模板,而不是在依赖模块定义的文件夹中.
除了模板内容之外你还可以操作模板定义属性, 例如`DisplayName`, `Layout`或`LocalizationSource`.
## 高级功能
本节介绍文本模板系统的一些内部知识和高级用法.
### 模板内容Provider
`TemplateRenderer` 用于渲染模板,这是大多数情况下所需的模板. 但是你可以使用 `ITemplateContentProvider` 获取原始(未渲染的)模板内容.
> `ITemplateRenderer` 内部使用 `ITemplateContentProvider` 获取原始模板内容.
示例:
````csharp
public class TemplateContentDemo : ITransientDependency
{
private readonly ITemplateContentProvider _templateContentProvider;
public TemplateContentDemo(ITemplateContentProvider templateContentProvider)
{
_templateContentProvider = templateContentProvider;
}
public async Task RunAsync()
{
var result = await _templateContentProvider
.GetContentOrNullAsync("Hello");
Console.WriteLine(result);
}
}
````
结果是原始模板内容:
````
@inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase<HelloModelNamespace.HelloModel>
Hello @Model.Name
````
* `GetContentOrNullAsync` 如果没有为请求的模板定义任何内容,则返回 `null`.
* 它可以获取 `cultureName` 参数,如果模板针对不同的文化具有不同的文件,则可以使用该参数(请参见上面的"多内容本地化"部分).
### 模板内容贡献者
`ITemplateContentProvider` 服务使用 `ITemplateContentContributor` 实现来查找模板内容. 有一个预实现的内容贡献者 `VirtualFileTemplateContentContributor`,它从上面描述的虚拟文件系统中获取模板内容.
你可以实现 `ITemplateContentContributor` 从另一个源读取原始模板内容.
示例:
````csharp
public class MyTemplateContentProvider
: ITemplateContentContributor, ITransientDependency
{
public async Task<string> GetOrNullAsync(TemplateContentContributorContext context)
{
var templateName = context.TemplateDefinition.Name;
//TODO: Try to find content from another source
return null;
}
}
````
如果源无法找到内容, 则返回 `null`, `ITemplateContentProvider` 将回退到下一个贡献者.
### Template Definition Manager
`ITemplateDefinitionManager` 服务可用于获取模板定义(由模板定义提供程序创建).
## 另请参阅
* 本文开发和引用的[应用程序示例源码](https://github.com/abpframework/abp-samples/tree/master/TextTemplateDemo).
* [本地化系统](Localization.md).
* [虚拟文件系统](Virtual-File-System.md).

527
docs/zh-Hans/Text-Templating-Scriban.md

@ -0,0 +1,527 @@
# Scriban 集成
## 安装
建议使用[ABP CLI](CLI.md)安装此包.
### 使用ABP CLI
在项目文件夹(.csproj 文件)中打开命令行窗口并输入以下命令:
````bash
abp add-package Volo.Abp.TextTemplating.Scriban
````
### 手动安装
如果你想要手动安装:
1. 添加 [Volo.Abp.TextTemplating.Scriban](https://www.nuget.org/packages/Volo.Abp.TextTemplating.Scriban) NuGet 包到你的项目:
````
Install-Package Volo.Abp.TextTemplating.Scriban
````
2.添加 `AbpTextTemplatingScribanModule` 到你的模块的依赖列表:
````csharp
[DependsOn(
//...other dependencies
typeof(AbpTextTemplatingScribanModule) //Add the new module dependency
)]
public class YourModule : AbpModule
{
}
````
## 定义模板
在渲染模板之前,需要定义它. 创建一个继承自 `TemplateDefinitionProvider` 的类:
````csharp
public class DemoTemplateDefinitionProvider : TemplateDefinitionProvider
{
public override void Define(ITemplateDefinitionContext context)
{
context.Add(
new TemplateDefinition("Hello") //template name: "Hello"
.WithVirtualFilePath(
"/Demos/Hello/Hello.tpl", //template content path
isInlineLocalized: true
)
.WithScribanEngine()
);
}
}
````
* `context` 对象用于添加新模板或获取依赖模块定义的模板. 使用 `context.Add(...)` 定义新模板.
* `TemplateDefinition` 是代表模板的类,每个模板必须有唯一的名称(在渲染模板时使用).
* `/Demos/Hello/Hello.tpl` 是模板文件的路径.
* `isInlineLocalized` 声明针对所有语言使用一个模板(`true` 还是针对每种语言使用不同的模板(`false`). 更多内容参阅下面的本地化部分.
* `WithScribanEngine` 方法为模板设置渲染引擎.
### 模板内容
`WithVirtualFilePath` 表示我们使用[虚拟文件系统](Virtual-File-System.md)存储模板内容. 在项目内创建一个 `Hello.tpl` 文件,并在属性窗口中将其标记为"**嵌入式资源**":
![hello-template](images/hello-template.png)
示例 `Hello.tpl` 内容如下所示:
````
Hello {%{{{model.name}}}%} :)
````
[虚拟文件系统](Virtual-File-System.md) 需要在[模块](Module-Development-Basics.md)类的 `ConfigureServices` 方法添加你的文件:
````csharp
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<TextTemplateDemoModule>("TextTemplateDemo");
});
````
* `TextTemplateDemoModule`是模块类.
* `TextTemplateDemo` 是你的项目的根命名空间.
## 渲染模板
`ITemplateRenderer` 服务用于渲染模板内容.
### 示例: 渲染一个简单的模板
````csharp
public class HelloDemo : ITransientDependency
{
private readonly ITemplateRenderer _templateRenderer;
public HelloDemo(ITemplateRenderer templateRenderer)
{
_templateRenderer = templateRenderer;
}
public async Task RunAsync()
{
var result = await _templateRenderer.RenderAsync(
"Hello", //the template name
new HelloModel
{
Name = "John"
}
);
Console.WriteLine(result);
}
}
````
* `HelloDemo` 是一个简单的类,在构造函数注入了 `ITemplateRenderer` 并在 `RunAsync` 方法中使用它.
* `RenderAsync` 有两个基本参数:
* `templateName`: 要渲染的模板名称 (本示例中是 `Hello`).
* `model`: 在模板内部用做 `model` 的对象 (本示例中是 `HelloModel` 对象).
示例会返回以下结果:
````csharp
Hello John :)
````
### 匿名模型
虽然建议为模板创建模型类,但在简单情况下使用匿名对象也是可行的:
````csharp
var result = await _templateRenderer.RenderAsync(
"Hello",
new
{
Name = "John"
}
);
````
示例中我们并没有创建模型类,但是创建了一个匿名对象模型.
### PascalCase 与 snake_case
PascalCase 属性名(如 `UserName`) 在模板中使用蛇形命名(如 `user_name`).
## 本地化
可以基于当前文化对模板内容进行本地化. 以下部分描述了两种类型的本地化选项.
### 内联本地化
内联本地化使用[本地化系统](Localization.md)本地化模板内的文本.
#### 示例: 重置密码链接
假设你需要向用户发送电子邮件重置密码. 模板内容:
````
<a title="{%{{{L "ResetMyPasswordTitle"}}}%}" href="{%{{{model.link}}}%}">{%{{{L "ResetMyPassword" model.name}}}%}</a>
````
`L` 函数用于根据当前用户的文化来定位给定的Key,你需要在本地化文件中定义 `ResetMyPassword` 键:
````json
"ResetMyPasswordTitle": "Reset my password",
"ResetMyPassword": "Hi {0}, Click here to reset your password"
````
你还需要在模板定义提供程序类中声明要与此模板一起使用的本地化资源:
````csharp
context.Add(
new TemplateDefinition(
"PasswordReset", //Template name
typeof(DemoResource) //LOCALIZATION RESOURCE
)
.WithScribanEngine()
.WithVirtualFilePath(
"/Demos/PasswordReset/PasswordReset.tpl", //template content path
isInlineLocalized: true
)
);
````
当你这样渲染模板时:
````csharp
var result = await _templateRenderer.RenderAsync(
"PasswordReset", //the template name
new PasswordResetModel
{
Name = "john",
Link = "https://abp.io/example-link?userId=123&token=ABC"
}
);
````
你可以看到以下本地化结果:
````csharp
<a title="Reset my password" href="https://abp.io/example-link?userId=123&token=ABC">Hi john, Click here to reset your password</a>
````
> 如果你为应用程序定义了 [默认本地化资源](Localization.md), 则无需声明模板定义的资源类型.
### 多个内容本地化
你可能希望为每种语言创建不同的模板文件,而不是使用本地化系统本地化单个模板. 如果模板对于特定的文化(而不是简单的文本本地化)应该是完全不同的,则可能需要使用它.
#### 示例: 欢迎电子邮件模板
假设你要发送电子邮件欢迎用户,但要定义基于用户的文化完全不同的模板.
首先创建一个文件夹,将模板放在里面,像 `en.tpl`, `tr.tpl` 每一个你支持的文化:
![multiple-file-template](images/multiple-file-template.png)
然后在模板定义提供程序类中添加模板定义:
````csharp
context.Add(
new TemplateDefinition(
name: "WelcomeEmail",
defaultCultureName: "en"
)
.WithScribanEngine()
.WithVirtualFilePath(
"/Demos/WelcomeEmail/Templates", //template content folder
isInlineLocalized: false
)
);
````
* 设置 **默认文化名称**, 当没有所需的文化模板,回退到缺省文化.
* 指定 **模板文件夹** 而不是单个模板文件.
* 设置 `isInlineLocalized` 为 `false`.
就这些,你可以渲染当前文化的模板:
````csharp
var result = await _templateRenderer.RenderAsync("WelcomeEmail");
````
> 为了简单我们跳过了模型,但是你可以使用前面所述的模型.
### 指定文化
`ITemplateRenderer` 服务如果没有指定则使用当前文化 (`CultureInfo.CurrentUICulture`). 如果你需要你可以使用 `cultureName` 参数指定文化.
````csharp
var result = await _templateRenderer.RenderAsync(
"WelcomeEmail",
cultureName: "en"
);
````
## 布局模板
布局模板用于在其他模板之间创建共享布局. 它类似于ASP.NET Core MVC / Razor Pages中的布局系统.
### 示例: 邮件HTML布局模板
例如,你想为所有电子邮件模板创建一个布局.
首先像之前一样创建一个模板文件:
````xml
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
</head>
<body>
{%{{{content}}}%}
</body>
</html>
````
* 布局模板必须具有 **{%{{{content}}}%}** 部分作为渲染的子内容的占位符.
在模板定义提供程序中注册模板:
````csharp
context.Add(
new TemplateDefinition(
"EmailLayout",
isLayout: true //SET isLayout!
)
.WithScribanEngine()
.WithVirtualFilePath(
"/Demos/EmailLayout/EmailLayout.tpl",
isInlineLocalized: true
)
);
````
现在你可以将此模板用作任何其他模板的布局:
````csharp
context.Add(
new TemplateDefinition(
name: "WelcomeEmail",
defaultCultureName: "en",
layout: "EmailLayout" //Set the LAYOUT
)
.WithScribanEngine()
.WithVirtualFilePath(
"/Demos/WelcomeEmail/Templates",
isInlineLocalized: false
)
);
````
## 全局上下文
ABP传递 `model`,可用于访问模板内的模型. 如果需要,可以传递更多的全局变量.
示例模板内容:
````
A global object value: {%{{{myGlobalObject}}}%}
````
模板假定它渲染上下文中的 `myGlobalObject` 对象. 你可以如下所示提供它:
````csharp
var result = await _templateRenderer.RenderAsync(
"GlobalContextUsage",
globalContext: new Dictionary<string, object>
{
{"myGlobalObject", "TEST VALUE"}
}
);
````
渲染的结果将是:
````
A global object value: TEST VALUE
````
## 替换存在的模板
通过替换应用程序中使用的模块定义的模板. 这样你可以根据自己的需求自定义模板,而无需更改模块代码.
### 选项-1: 使用虚拟文件系统
[虚拟文件系统](Virtual-File-System.md)允许你通过将相同文件放入项目中的相同路径来覆盖任何文件.
#### 示例: 替换标准电子邮件布局模板
ABP框架提供了一个[邮件发送系统](Emailing.md), 它在内部使用文本模板来渲染邮件内容. 它在 `/Volo/Abp/Emailing/Templates/Layout.tp` 路径定义了一个标准邮件布局模板. 模板的唯一名称是 `Abp.StandardEmailTemplates.Layout` 并且这个字符中`Volo.Abp.Emailing.Templates.StandardEmailTemplates`静态类上定义为常量.
执行以下步骤将替换模板替换成你自定义的;
**1)** 在你的项目中相同的路径添加一个新文件 (`/Volo/Abp/Emailing/Templates/Layout.tpl`):
![replace-email-layout](images/replace-email-layout.png)
**2)** 准备你的邮件布局模板:
````html
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
</head>
<body>
<h1>This my header</h1>
{%{{{content}}}%}
<footer>
This is my footer...
</footer>
</body>
</html>
````
此示例只是向模板添加页眉和页脚并呈现它们之间的内容(请参阅上面的布局模板部分).
**3)** 在`.csproj`文件配置嵌入式资源e
* 添加 [Microsoft.Extensions.FileProviders.Embedded](https://www.nuget.org/packages/Microsoft.Extensions.FileProviders.Embedded) NuGet 包到你的项目.
* 在 `.csproj` 中添加 `<GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>` 到 `<PropertyGroup>...</PropertyGroup>` 部分.
* 添加以下代码到你的 `.csproj` 文件:
````xml
<ItemGroup>
<None Remove="Volo\Abp\Emailing\Templates\*.tpl" />
<EmbeddedResource Include="Volo\Abp\Emailing\Templates\*.tpl" />
</ItemGroup>
````
这将模板文件做为"嵌入式资源".
**4)** 配置虚拟文件系统
在[模块](Module-Development-Basics.md) `ConfigureServices` 方法配置 `AbpVirtualFileSystemOptions` 将嵌入的文件添加到虚拟文件系统中:
```csharp
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<BookStoreDomainModule>();
});
```
`BookStoreDomainModule` 应该是你的模块名称
> 确保你的模块(支持或间接)[依赖](Module-Development-Basics.md) `AbpEmailingModule`. 因为VFS可以基于依赖顺序覆盖文件.
现在渲染邮件布局模板时会使用你的模板.
### 选项-2: 使用模板定义提供者
你可以创建一个模板定义提供者类来获取邮件布局模板来更改它的虚拟文件路径.
**示例: 使用 `/MyTemplates/EmailLayout.tpl` 文件而不是标准模板**
```csharp
using Volo.Abp.DependencyInjection;
using Volo.Abp.Emailing.Templates;
using Volo.Abp.TextTemplating;
namespace MyProject
{
public class MyTemplateDefinitionProvider
: TemplateDefinitionProvider, ITransientDependency
{
public override void Define(ITemplateDefinitionContext context)
{
var emailLayoutTemplate = context.GetOrNull(StandardEmailTemplates.Layout);
emailLayoutTemplate
.WithVirtualFilePath(
"/MyTemplates/EmailLayout.tpl",
isInlineLocalized: true
);
}
}
}
```
你依然应该添加 `/MyTemplates/EmailLayout.tpl` 到虚拟文件系统. 这种方法允许你在任何文件夹中找到模板,而不是在依赖模块定义的文件夹中.
除了模板内容之外你还可以操作模板定义属性, 例如`DisplayName`, `Layout`或`LocalizationSource`.
## 高级功能
本节介绍文本模板系统的一些内部知识和高级用法.
### 模板内容Provider
`TemplateRenderer` 用于渲染模板,这是大多数情况下所需的模板. 但是你可以使用 `ITemplateContentProvider` 获取原始(未渲染的)模板内容.
> `ITemplateRenderer` 内部使用 `ITemplateContentProvider` 获取原始模板内容.
示例:
````csharp
public class TemplateContentDemo : ITransientDependency
{
private readonly ITemplateContentProvider _templateContentProvider;
public TemplateContentDemo(ITemplateContentProvider templateContentProvider)
{
_templateContentProvider = templateContentProvider;
}
public async Task RunAsync()
{
var result = await _templateContentProvider
.GetContentOrNullAsync("Hello");
Console.WriteLine(result);
}
}
````
结果是原始模板内容:
````
Hello {%{{{model.name}}}%} :)
````
* `GetContentOrNullAsync` 如果没有为请求的模板定义任何内容,则返回 `null`.
* 它可以获取 `cultureName` 参数,如果模板针对不同的文化具有不同的文件,则可以使用该参数(请参见上面的"多内容本地化"部分).
### 模板内容贡献者
`ITemplateContentProvider` 服务使用 `ITemplateContentContributor` 实现来查找模板内容. 有一个预实现的内容贡献者 `VirtualFileTemplateContentContributor`,它从上面描述的虚拟文件系统中获取模板内容.
你可以实现 `ITemplateContentContributor` 从另一个源读取原始模板内容.
示例:
````csharp
public class MyTemplateContentProvider
: ITemplateContentContributor, ITransientDependency
{
public async Task<string> GetOrNullAsync(TemplateContentContributorContext context)
{
var templateName = context.TemplateDefinition.Name;
//TODO: Try to find content from another source
return null;
}
}
````
如果源无法找到内容, 则返回 `null`, `ITemplateContentProvider` 将回退到下一个贡献者.
### Template Definition Manager
`ITemplateDefinitionManager` 服务可用于获取模板定义(由模板定义提供程序创建).
## 另请参阅
* 本文开发和引用的[应用程序示例源码](https://github.com/abpframework/abp-samples/tree/master/TextTemplateDemo).
* [本地化系统](Localization.md).
* [虚拟文件系统](Virtual-File-System.md).

444
docs/zh-Hans/Text-Templating.md

@ -8,450 +8,30 @@ Template + Model =renderer=> Rendered Content
它非常类似于 ASP.NET Core Razor View (或 Page):
*RAZOR VIEW (or PAGE) + MODEL ==render==> HTML CONTENT*
*RAZOR VIEW (或 PAGE) + MODEL ==render==> HTML CONTENT*
你可以将渲染的输出用于任何目的,例如发送电子邮件或准备一些报告.
### 示例
模板渲染引擎非常强大:
Here, a simple template:
````
Hello {%{{{model.name}}}%} :)
````
你可以定义一个含有 `Name` 属性的类来渲染这个模板:
````csharp
public class HelloModel
{
public string Name { get; set; }
}
````
如果你使用 `Name` 为 `John` 的 `HelloModel` 渲染模板,输出为:
````
Hello John :)
````
模板渲染引擎非常强大;
* 它基于 [Scriban](https://github.com/lunet-io/scriban) 库, 所以它支持 **条件逻辑**, **循环** 等.
* 模板内容 **可以本地化**.
* 你可以定义 **布局模板** 在渲染其他模板中用做布局.
* 它支持**条件逻辑**, **循环**等等.
* 模板内容**可以本地化**.
* 你可以为其他渲染模板定义**布局模板**。
* 对于高级场景,你可以传递任何对象到模板上下文.
### 源码
这里是本文开发和引用的[示例应用程序源码](https://github.com/abpframework/abp-samples/tree/master/TextTemplateDemo).
## 安装
推荐使用 [ABP CLI](CLI.md) 安装包.
### 使用 ABP CLI
在项目目录(.csproj file)打开命令行窗口运行以下命令:
````bash
abp add-package Volo.Abp.TextTemplating
````
### 手动安装
如果你想要手动安装;
1. 添加 [Volo.Abp.TextTemplating](https://www.nuget.org/packages/Volo.Abp.TextTemplating) NuGet包到你的项目:
````
Install-Package Volo.Abp.TextTemplating
````
2. 添加 `AbpTextTemplatingModule` 到你的模块依赖列表:
````csharp
[DependsOn(
//...other dependencies
typeof(AbpTextTemplatingModule) //Add the new module dependency
)]
public class YourModule : AbpModule
{
}
````
## 定义模板
在渲染模板之前,需要定义它. 创建一个继承自 `TemplateDefinitionProvider` 的类:
````csharp
public class DemoTemplateDefinitionProvider : TemplateDefinitionProvider
{
public override void Define(ITemplateDefinitionContext context)
{
context.Add(
new TemplateDefinition("Hello") //template name: "Hello"
.WithVirtualFilePath(
"/Demos/Hello/Hello.tpl", //template content path
isInlineLocalized: true
)
);
}
}
````
* `context` 对象用于添加新模板或获取依赖模块定义的模板. 使用 `context.Add(...)` 定义新模板.
* `TemplateDefinition` 是代表模板的类,每个模板必须有唯一的名称(在渲染模板时使用).
* `/Demos/Hello/Hello.tpl` 是模板文件的路径.
* `isInlineLocalized` 声明针对所有语言使用一个模板(`true` 还是针对每种语言使用不同的模板(`false`). 更多内容参阅下面的本地化部分.
### 模板内容
`WithVirtualFilePath` 表示我们使用[虚拟文件系统](Virtual-File-System.md)存储模板内容. 在项目内创建一个 `Hello.tpl` 文件,并在属性窗口中将其标记为"**嵌入式资源**":
![hello-template](images/hello-template.png)
示例 `Hello.tpl` 内容如下所示:
````
Hello {%{{{model.name}}}%} :)
````
[虚拟文件系统](Virtual-File-System.md) 需要在[模块](Module-Development-Basics.md)类的 `ConfigureServices` 方法添加你的文件:
````csharp
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<TextTemplateDemoModule>("TextTemplateDemo");
});
````
* `TextTemplateDemoModule`是模块类.
* `TextTemplateDemo` 是你的项目的根命名空间.
## 渲染模板
`ITemplateRenderer` 服务用于渲染模板内容.
### 示例: 渲染一个简单的模板
````csharp
public class HelloDemo : ITransientDependency
{
private readonly ITemplateRenderer _templateRenderer;
public HelloDemo(ITemplateRenderer templateRenderer)
{
_templateRenderer = templateRenderer;
}
public async Task RunAsync()
{
var result = await _templateRenderer.RenderAsync(
"Hello", //the template name
new HelloModel
{
Name = "John"
}
);
Console.WriteLine(result);
}
}
````
* `HelloDemo` 是一个简单的类,在构造函数注入了 `ITemplateRenderer` 并在 `RunAsync` 方法中使用它.
* `RenderAsync` 有两个基本参数:
* `templateName`: 要渲染的模板名称 (本示例中是 `Hello`).
* `model`: 在模板内部用做 `model` 的对象 (本示例中是 `HelloModel` 对象).
示例会返回以下结果:
````csharp
Hello John :)
````
### 匿名模型
虽然建议为模板创建模型类,但在简单情况下使用匿名对象也是可行的:
````csharp
var result = await _templateRenderer.RenderAsync(
"Hello",
new
{
Name = "John"
}
);
````
示例中我们并没有创建模型类,但是创建了一个匿名对象模型.
### PascalCase 与 snake_case
PascalCase 属性名(如 `UserName`) 在模板中使用蛇形命名(如 `user_name`).
## 本地化
可以基于当前文化对模板内容进行本地化. 以下部分描述了两种类型的本地化选项.
### 内联本地化
内联本地化使用[本地化系统](Localization.md)本地化模板内的文本.
#### 示例: 重置密码链接
假设你需要向用户发送电子邮件重置密码. 模板内容:
````
<a title="{%{{{L "ResetMyPasswordTitle"}}}%}" href="{%{{{model.link}}}%}">{%{{{L "ResetMyPassword" model.name}}}%}</a>
````
`L` 函数用于根据当前用户的文化来定位给定的Key,你需要在本地化文件中定义 `ResetMyPassword` 键:
````json
"ResetMyPasswordTitle": "Reset my password",
"ResetMyPassword": "Hi {0}, Click here to reset your password"
````
你还需要在模板定义提供程序类中声明要与此模板一起使用的本地化资源:
````csharp
context.Add(
new TemplateDefinition(
"PasswordReset", //Template name
typeof(DemoResource) //LOCALIZATION RESOURCE
).WithVirtualFilePath(
"/Demos/PasswordReset/PasswordReset.tpl", //template content path
isInlineLocalized: true
)
);
````
当你这样渲染模板时:
````csharp
var result = await _templateRenderer.RenderAsync(
"PasswordReset", //the template name
new PasswordResetModel
{
Name = "john",
Link = "https://abp.io/example-link?userId=123&token=ABC"
}
);
````
你可以看到以下本地化结果:
````csharp
<a title="Reset my password" href="https://abp.io/example-link?userId=123&token=ABC">Hi john, Click here to reset your password</a>
````
> 如果你为应用程序定义了 [默认本地化资源](Localization.md), 则无需声明模板定义的资源类型.
### 多个内容本地化
你可能希望为每种语言创建不同的模板文件,而不是使用本地化系统本地化单个模板. 如果模板对于特定的文化(而不是简单的文本本地化)应该是完全不同的,则可能需要使用它.
#### 示例: 欢迎电子邮件模板
假设你要发送电子邮件欢迎用户,但要定义基于用户的文化完全不同的模板.
首先创建一个文件夹,将模板放在里面,像 `en.tpl`, `tr.tpl` 每一个你支持的文化:
![multiple-file-template](images/multiple-file-template.png)
然后在模板定义提供程序类中添加模板定义:
````csharp
context.Add(
new TemplateDefinition(
name: "WelcomeEmail",
defaultCultureName: "en"
)
.WithVirtualFilePath(
"/Demos/WelcomeEmail/Templates", //template content folder
isInlineLocalized: false
)
);
````
* 设置 **默认文化名称**, 当没有所需的文化模板,回退到缺省文化.
* 指定 **模板文件夹** 而不是单个模板文件.
* 设置 `isInlineLocalized` 为 `false`.
就这些,你可以渲染当前文化的模板:
````csharp
var result = await _templateRenderer.RenderAsync("WelcomeEmail");
````
> 为了简单我们跳过了模型,但是你可以使用前面所述的模型.
### 指定文化
`ITemplateRenderer` 服务如果没有指定则使用当前文化 (`CultureInfo.CurrentUICulture`). 如果你需要你可以使用 `cultureName` 参数指定文化.
````csharp
var result = await _templateRenderer.RenderAsync(
"WelcomeEmail",
cultureName: "en"
);
````
## 布局模板
布局模板用于在其他模板之间创建共享布局. 它类似于ASP.NET Core MVC / Razor Pages中的布局系统.
### 示例: 邮件HTML布局模板
例如,你想为所有电子邮件模板创建一个布局.
首先像之前一样创建一个模板文件:
````xml
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
</head>
<body>
{%{{{content}}}%}
</body>
</html>
````
* 布局模板必须具有 **{%{{{content}}}%}** 部分作为渲染的子内容的占位符.
在模板定义提供程序中注册模板:
````csharp
context.Add(
new TemplateDefinition(
"EmailLayout",
isLayout: true //SET isLayout!
).WithVirtualFilePath(
"/Demos/EmailLayout/EmailLayout.tpl",
isInlineLocalized: true
)
);
````
现在你可以将此模板用作任何其他模板的布局:
````csharp
context.Add(
new TemplateDefinition(
name: "WelcomeEmail",
defaultCultureName: "en",
layout: "EmailLayout" //Set the LAYOUT
).WithVirtualFilePath(
"/Demos/WelcomeEmail/Templates",
isInlineLocalized: false
)
);
````
## 全局上下文
ABP传递 `model`,可用于访问模板内的模型. 如果需要,可以传递更多的全局变量.
示例模板内容:
````
A global object value: {%{{{myGlobalObject}}}%}
````
模板假定它渲染上下文中的 `myGlobalObject` 对象. 你可以如下所示提供它:
````csharp
var result = await _templateRenderer.RenderAsync(
"GlobalContextUsage",
globalContext: new Dictionary<string, object>
{
{"myGlobalObject", "TEST VALUE"}
}
);
````
渲染的结果将是:
````
A global object value: TEST VALUE
````
## 高级功能
本节介绍文本模板系统的一些内部知识和高级用法.
### 模板内容Provider
`TemplateRenderer` 用于渲染模板,这是大多数情况下所需的模板. 但是你可以使用 `ITemplateContentProvider` 获取原始(未渲染的)模板内容.
> `ITemplateRenderer` 内部使用 `ITemplateContentProvider` 获取原始模板内容.
示例:
````csharp
public class TemplateContentDemo : ITransientDependency
{
private readonly ITemplateContentProvider _templateContentProvider;
public TemplateContentDemo(ITemplateContentProvider templateContentProvider)
{
_templateContentProvider = templateContentProvider;
}
public async Task RunAsync()
{
var result = await _templateContentProvider
.GetContentOrNullAsync("Hello");
Console.WriteLine(result);
}
}
````
结果是原始模板内容:
````
Hello {%{{{model.name}}}%} :)
````
* `GetContentOrNullAsync` 如果没有为请求的模板定义任何内容,则返回 `null`.
* 它可以获取 `cultureName` 参数,如果模板针对不同的文化具有不同的文件,则可以使用该参数(请参见上面的"多内容本地化"部分).
### 模板内容贡献者
`ITemplateContentProvider` 服务使用 `ITemplateContentContributor` 实现来查找模板内容. 有一个预实现的内容贡献者 `VirtualFileTemplateContentContributor`,它从上面描述的虚拟文件系统中获取模板内容.
你可以实现 `ITemplateContentContributor` 从另一个源读取原始模板内容.
示例:
````csharp
public class MyTemplateContentProvider
: ITemplateContentContributor, ITransientDependency
{
public async Task<string> GetOrNullAsync(TemplateContentContributorContext context)
{
var templateName = context.TemplateDefinition.Name;
//TODO: Try to find content from another source
return null;
}
}
ABP框架提供了两个模板引擎:
````
* **[Razor](Text-Templating-Razor.md)**
* **[Scriban](Text-Templating-Scriban.md)**
如果源无法找到内容, 则返回 `null`, `ITemplateContentProvider` 将回退到下一个贡献者.
你可以在同一个应用应用程序中使用不同的模板引擎, 或者创建一个新的自定义模板引擎.
### Template Definition Manager
## 源码
`ITemplateDefinitionManager` 服务可用于获取模板定义(由模板定义提供程序创建).
查看开发和引用的[应用程序示例源码](https://github.com/abpframework/abp-samples/tree/master/TextTemplateDemo).
## 另请参阅
* 本文开发和引用的[应用程序示例源码](https://github.com/abpframework/abp-samples/tree/master/TextTemplateDemo).
* [本地化系统](Localization.md).
* [虚拟文件系统](Virtual-File-System.md).
* [虚拟文件系统](Virtual-File-System.md).

BIN
docs/zh-Hans/images/abp-book.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

BIN
docs/zh-Hans/images/abp-select2-multiple.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

BIN
docs/zh-Hans/images/abp-select2-single.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

BIN
docs/zh-Hans/images/account-module-forgot-password.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
docs/zh-Hans/images/account-module-login.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
docs/zh-Hans/images/account-module-manage-account.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
docs/zh-Hans/images/account-module-register.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
docs/zh-Hans/images/add-new-propert-to-user-database-extra-properties.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

BIN
docs/zh-Hans/images/add-new-propert-to-user-database-field.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

BIN
docs/zh-Hans/images/add-new-property-enum.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
docs/zh-Hans/images/add-new-property-to-user-form-validation-error-custom.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

BIN
docs/zh-Hans/images/add-new-property-to-user-form-validation-error.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

BIN
docs/zh-Hans/images/add-new-property-to-user-form.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

BIN
docs/zh-Hans/images/add-new-property-to-user-table.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

BIN
docs/zh-Hans/images/angular-module-dev-app-project.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

BIN
docs/zh-Hans/images/angular-module-folder-structure.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

BIN
docs/zh-Hans/images/blazor-generic-exception-message.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
docs/zh-Hans/images/blazor-user-friendly-exception.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
docs/zh-Hans/images/bookstore-visual-studio-solution-v3.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 11 KiB

BIN
docs/zh-Hans/images/cmskit-module-blog-post-edit.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

BIN
docs/zh-Hans/images/cmskit-module-blog-posts-page.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
docs/zh-Hans/images/cmskit-module-blogs-edit.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
docs/zh-Hans/images/cmskit-module-blogs-feature-action.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
docs/zh-Hans/images/cmskit-module-blogs-page.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
docs/zh-Hans/images/cmskit-module-comment-page.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
docs/zh-Hans/images/cmskit-module-comments-detail.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

BIN
docs/zh-Hans/images/cmskit-module-features-dialog-2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
docs/zh-Hans/images/cmskit-module-features-dialog.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
docs/zh-Hans/images/cmskit-module-features-scroll-index.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

BIN
docs/zh-Hans/images/cmskit-module-global-resources-page.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
docs/zh-Hans/images/cmskit-module-menus-page.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
docs/zh-Hans/images/cmskit-module-menus-public.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
docs/zh-Hans/images/cmskit-module-pages-edit.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

BIN
docs/zh-Hans/images/cmskit-module-pages-page.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
docs/zh-Hans/images/cmskit-module-ratings.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
docs/zh-Hans/images/cmskit-module-reactions.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

BIN
docs/zh-Hans/images/cmskit-module-tag-edit.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

BIN
docs/zh-Hans/images/cmskit-module-tags-page.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
docs/zh-Hans/images/data-table-colum-extension-blazor-component-render-solution.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

BIN
docs/zh-Hans/images/data-table-colum-extension-blazor-component-render.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
docs/zh-Hans/images/docs-create-project-v4.4.0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
docs/zh-Hans/images/docs-module_solution-explorer.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 11 KiB

BIN
docs/zh-Hans/images/extension-navigation-property-form.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
docs/zh-Hans/images/extension-navigation-property-table.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

BIN
docs/zh-Hans/images/features-modal.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 35 KiB

BIN
docs/zh-Hans/images/features-module-opening.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 24 KiB

BIN
docs/zh-Hans/images/generated-static-client-proxies.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

BIN
docs/zh-Hans/images/hello-template-razor.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

BIN
docs/zh-Hans/images/identity-module-menu.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

BIN
docs/zh-Hans/images/identity-module-permissions.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 30 KiB

BIN
docs/zh-Hans/images/identity-module-roles.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 18 KiB

BIN
docs/zh-Hans/images/identity-module-users.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 18 KiB

BIN
docs/zh-Hans/images/implementing-domain-driven-design-book.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

BIN
docs/zh-Hans/images/module-tenant-management-actions.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

BIN
docs/zh-Hans/images/module-tenant-management-new-tenant.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 15 KiB

BIN
docs/zh-Hans/images/module-tenant-management-page.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 21 KiB

BIN
docs/zh-Hans/images/multiple-file-template-razor.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

BIN
docs/zh-Hans/images/optimistic-concurrency.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
docs/zh-Hans/images/page-header-toolbar-blazor.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
docs/zh-Hans/images/page-toolbar-button-blazor.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
docs/zh-Hans/images/page-toolbar-button.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

BIN
docs/zh-Hans/images/page-toolbar-custom-component-blazor.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

BIN
docs/zh-Hans/images/page-toolbar-custom-component.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
docs/zh-Hans/images/permissions-module-dialog.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 31 KiB

BIN
docs/zh-Hans/images/permissions-module-open-dialog.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 25 KiB

BIN
docs/zh-Hans/images/replace-email-layout-razor.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

BIN
docs/zh-Hans/images/static-js-proxy-example.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

BIN
docs/zh-Hans/images/table-column-extension-example-blazor.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
docs/zh-Hans/images/table-column-extension-example.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

BIN
docs/zh-Hans/images/upgrade-diff-empty-folders.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 984 B

BIN
docs/zh-Hans/images/user-action-blazor-extension-click-me.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
docs/zh-Hans/images/user-action-extension-click-me.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

BIN
docs/zh-Hans/images/user-action-extension-on-blazor-project.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

BIN
docs/zh-Hans/images/user-action-extension-on-solution.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
docs/zh-Hans/images/winmerge-comparison-result.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

BIN
docs/zh-Hans/images/winmerge-file-diff.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

BIN
docs/zh-Hans/images/winmerge-open-folders.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

2
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ServiceProxying/Angular/AngularServiceProxyGenerator.cs

@ -48,7 +48,7 @@ public class AngularServiceProxyGenerator : ServiceProxyGeneratorBase<AngularSer
var apiName = args.ApiName ?? defaultValue;
var source = args.Source ?? defaultValue;
var target = args.Target ?? defaultValue;
var url = args.Url ?? string.Empty;
var url = args.Url ?? "''";
var commandBuilder = new StringBuilder("npx ng g @abp/ng.schematics:" + schematicsCommandName);
if (module != null)

24
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Values/ValueObject.cs

@ -16,21 +16,29 @@ public abstract class ValueObject
return false;
}
ValueObject other = (ValueObject)obj;
var other = (ValueObject)obj;
IEnumerator<object> thisValues = GetAtomicValues().GetEnumerator();
IEnumerator<object> otherValues = other.GetAtomicValues().GetEnumerator();
var thisValues = GetAtomicValues().GetEnumerator();
var otherValues = other.GetAtomicValues().GetEnumerator();
while (thisValues.MoveNext() && otherValues.MoveNext())
var thisMoveNext = thisValues.MoveNext();
var otherMoveNext = otherValues.MoveNext();
while (thisMoveNext && otherMoveNext)
{
if (ReferenceEquals(thisValues.Current, null) ^
ReferenceEquals(otherValues.Current, null))
if (ReferenceEquals(thisValues.Current, null) ^ ReferenceEquals(otherValues.Current, null))
{
return false;
}
if (thisValues.Current != null &&
!thisValues.Current.Equals(otherValues.Current))
if (thisValues.Current != null && !thisValues.Current.Equals(otherValues.Current))
{
return false;
}
thisMoveNext = thisValues.MoveNext();
otherMoveNext = otherValues.MoveNext();
if (thisMoveNext != otherMoveNext)
{
return false;
}

20
framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/PropertyApiDescriptionModel.cs

@ -1,5 +1,6 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Reflection;
using Volo.Abp.Http.ProxyScripting.Configuration;
@ -18,16 +19,31 @@ public class PropertyApiDescriptionModel
public bool IsRequired { get; set; }
//TODO: Validation rules for this property
public int? MinLength { get; set; }
public int? MaxLength { get; set; }
public string Minimum { get; set; }
public string Maximum { get; set; }
public string Regex { get; set; }
public static PropertyApiDescriptionModel Create(PropertyInfo propertyInfo)
{
var customAttributes = propertyInfo.GetCustomAttributes(true);
return new PropertyApiDescriptionModel
{
Name = propertyInfo.Name,
JsonName = AbpApiProxyScriptingConfiguration.PropertyNameGenerator.Invoke(propertyInfo),
Type = ApiTypeNameHelper.GetTypeName(propertyInfo.PropertyType),
TypeSimple = ApiTypeNameHelper.GetSimpleTypeName(propertyInfo.PropertyType),
IsRequired = propertyInfo.IsDefined(typeof(RequiredAttribute), true)
IsRequired = customAttributes.OfType<RequiredAttribute>().Any(),
Minimum = customAttributes.OfType<RangeAttribute>().Select(x => x.Minimum).FirstOrDefault()?.ToString(),
Maximum = customAttributes.OfType<RangeAttribute>().Select(x => x.Maximum).FirstOrDefault()?.ToString(),
MinLength = customAttributes.OfType<MinLengthAttribute>().FirstOrDefault()?.Length ?? customAttributes.OfType<StringLengthAttribute>().FirstOrDefault()?.MinimumLength,
MaxLength = customAttributes.OfType<MaxLengthAttribute>().FirstOrDefault()?.Length ?? customAttributes.OfType<StringLengthAttribute>().FirstOrDefault()?.MaximumLength,
Regex= customAttributes.OfType<RegularExpressionAttribute>().Select(x => x.Pattern).FirstOrDefault()
};
}
}

2
framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Urls/AppUrlProvider.cs

@ -45,7 +45,7 @@ public class AppUrlProvider : IAppUrlProvider, ITransientDependency
public bool IsRedirectAllowedUrl(string url)
{
var allow = Options.RedirectAllowedUrls.Any(url.StartsWith);
var allow = Options.RedirectAllowedUrls.Any(x => url.StartsWith(x, StringComparison.CurrentCultureIgnoreCase));
if (!allow)
{
Logger.LogError($"Invalid RedirectUrl: {url}, Use {nameof(AppUrlProvider)} to configure it!");

10
framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Values/Address.cs

@ -11,6 +11,8 @@ public class Address : ValueObject
public int Number { get; }
public string[] Tags { get; }
private Address()
{
}
@ -18,11 +20,13 @@ public class Address : ValueObject
public Address(
Guid cityId,
string street,
int number)
int number,
params string[] tags)
{
CityId = cityId;
Street = street;
Number = number;
Tags = tags;
}
//Requires to implement this method to return properties.
@ -31,5 +35,9 @@ public class Address : ValueObject
yield return Street;
yield return CityId;
yield return Number;
foreach (var tag in Tags)
{
yield return tag;
}
}
}

10
framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Values/ValueObject_Tests.cs

@ -10,8 +10,9 @@ public class ValueObject_Tests
public void ValueObjects_With_Same_Properties_Should_Be_Equals()
{
var cityId = Guid.NewGuid();
var address1 = new Address(cityId, "Baris Manco", 42);
var address2 = new Address(cityId, "Baris Manco", 42);
var address1 = new Address(cityId, "Baris Manco", 42, "home", "office");
var address2 = new Address(cityId, "Baris Manco", 42, "home", "office");
address1.ValueEquals(address2).ShouldBeTrue();
}
@ -25,5 +26,10 @@ public class ValueObject_Tests
var address2 = new Address(cityId, "Baris Manco", 43);
address1.ValueEquals(address2).ShouldBeFalse();
address1 = new Address(cityId, "Baris Manco", 42, "home", "office");
address2 = new Address(cityId, "Baris Manco", 42, "home");
address1.ValueEquals(address2).ShouldBeFalse();
}
}

11
modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.cshtml.cs

@ -1,5 +1,7 @@
using System.ComponentModel.DataAnnotations;
using System;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Extensions;
using Volo.Abp.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
@ -32,6 +34,13 @@ public class ManageModel : AccountPageModel
await contributor.ConfigureAsync(ProfileManagementPageCreationContext);
}
if (!Url.IsLocalUrl(ReturnUrl) &&
!ReturnUrl.StartsWith(UriHelper.BuildAbsolute(Request.Scheme, Request.Host, Request.PathBase).RemovePostFix("/")) &&
!AppUrlProvider.IsRedirectAllowedUrl(ReturnUrl))
{
ReturnUrl = null;
}
return Page();
}

22
modules/openiddict/app/OpenIddict.Demo.Client.Mvc/Pages/Index.cshtml

@ -1,14 +1,13 @@
@page
@using Microsoft.AspNetCore.Authentication
@using System.Net.Http.Headers
@using System.Text.Json
@model IndexModel
@{
ViewData["Title"] = "Home page";
}
<div class="text-center">
<h1 class="display-4">Welcome</h1>
<p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
<a class="btn btn-primary" href="/Login">Login</a>
<a class="btn btn-warning" href="/Logout">Loout</a>
@ -25,5 +24,22 @@
<br/>
@await HttpContext.GetTokenAsync("access_token")
</p>
var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://localhost:44303/api/claims");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await HttpContext.GetTokenAsync("access_token"));
var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
<code style="display: block; white-space: pre-wrap; text-align: left">
@{
var apiResponse = JsonSerializer.Serialize(JsonDocument.Parse(await response.Content.ReadAsStringAsync()), new JsonSerializerOptions
{
WriteIndented = true
});
}
@apiResponse;
</code>
}
</div>

2
modules/openiddict/app/OpenIddict.Demo.Client.Mvc/Program.cs

@ -35,6 +35,8 @@ builder.Services.AddAuthentication(options =>
options.Scope.Add("email");
options.Scope.Add("roles");
options.Scope.Add("phone");
options.Scope.Add("AbpAPI");
});
var app = builder.Build();

1
modules/openiddict/app/OpenIddict.Demo.Server/OpenIddict.Demo.Server.csproj

@ -67,7 +67,6 @@
<ItemGroup>
<PackageReference Include="OpenIddict.Validation.AspNetCore" Version="3.1.1" />
<PackageReference Include="OpenIddict.Validation.ServerIntegration" Version="3.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>

13
modules/openiddict/app/OpenIddict.Demo.Server/OpenIddictServerModule.cs

@ -1,9 +1,7 @@
using System.Text;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using OpenIddict.Demo.Server.EntityFrameworkCore;
using OpenIddict.Validation.AspNetCore;
using Volo.Abp;
using Volo.Abp.Account;
using Volo.Abp.Account.Web;
@ -109,16 +107,7 @@ public class OpenIddictServerModule : AbpModule
public override void ConfigureServices(ServiceConfigurationContext context)
{
// This is work for the OpenIddictServerBuilder.AddValidation()
context.Services.ConfigureApplicationCookie(options =>
{
options.ForwardDefaultSelector = ctx => ctx.Request.Path.StartsWithSegments("/api")
? OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme
: null;
});
Configure<AbpOpenIddictOptions>(options =>
Configure<AbpOpenIddictAspNetCoreOptions>(options =>
{
options.AddDevelopmentEncryptionAndSigningCertificate = false;
});

10
modules/openiddict/app/OpenIddict.Demo.Server/Program.cs

@ -1,9 +1,6 @@
using System.Text;
using Microsoft.IdentityModel.Tokens;
using OpenIddict.Demo.Server;
using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared;
using Volo.Abp.Localization;
using Volo.Abp.OpenIddict.Jwt;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
@ -44,6 +41,13 @@ builder.Services.Configure<AbpLocalizationOptions>(options =>
// options.TokenValidationParameters.TokenDecryptionKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("Abp_OpenIddict_Demo_87E33FC57D80"));
// });
// builder.Services.ConfigureApplicationCookie(options =>
// {
// options.ForwardDefaultSelector = ctx => ctx.Request.Path.StartsWithSegments("/api")
// ? OtherScheme
// : null;
// });
await builder.AddApplicationAsync<OpenIddictServerModule>();
var app = builder.Build();

1
modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo.Abp.OpenIddict.AspNetCore.csproj

@ -14,7 +14,6 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.AspNetCore.Mvc\Volo.Abp.AspNetCore.Mvc.csproj" />
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.AspNetCore.MultiTenancy\Volo.Abp.AspNetCore.MultiTenancy.csproj" />
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared\Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.csproj" />
</ItemGroup>

122
modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictAspNetCoreModule.cs

@ -1,36 +1,26 @@
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.Extensions.DependencyInjection;
using OpenIddict.Abstractions;
using OpenIddict.Server;
using Volo.Abp.AspNetCore.MultiTenancy;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared;
using Volo.Abp.Modularity;
using Volo.Abp.OpenIddict.WildcardDomains;
using Volo.Abp.Security.Claims;
namespace Volo.Abp.OpenIddict;
[DependsOn(
typeof(AbpAspNetCoreMvcModule),
typeof(AbpAspNetCoreMvcUiThemeSharedModule),
typeof(AbpAspNetCoreMultiTenancyModule),
typeof(AbpOpenIddictDomainModule)
)]
public class AbpOpenIddictAspNetCoreModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
PreConfigure<OpenIddictServerBuilder>(builder =>
{
builder.UseAspNetCore()
.EnableAuthorizationEndpointPassthrough()
.EnableTokenEndpointPassthrough()
.EnableUserinfoEndpointPassthrough()
.EnableLogoutEndpointPassthrough()
.EnableVerificationEndpointPassthrough()
.EnableStatusCodePagesIntegration();
});
}
public override void ConfigureServices(ServiceConfigurationContext context)
{
AddOpenIddictServer(context.Services);
Configure<AbpOpenIddictClaimDestinationsOptions>(options =>
{
options.ClaimDestinationsProvider.Add<AbpDefaultOpenIddictClaimDestinationsProvider>();
@ -41,4 +31,104 @@ public class AbpOpenIddictAspNetCoreModule : AbpModule
options.ViewLocationFormats.Add("/Volo/Abp/OpenIddict/Views/{1}/{0}.cshtml");
});
}
private void AddOpenIddictServer(IServiceCollection services)
{
var builderOptions = services.ExecutePreConfiguredActions<AbpOpenIddictAspNetCoreOptions>();
if (builderOptions.UpdateAbpClaimTypes)
{
AbpClaimTypes.UserId = OpenIddictConstants.Claims.Subject;
AbpClaimTypes.Role = OpenIddictConstants.Claims.Role;
AbpClaimTypes.UserName = OpenIddictConstants.Claims.Name;
AbpClaimTypes.Name = OpenIddictConstants.Claims.GivenName;
AbpClaimTypes.SurName = OpenIddictConstants.Claims.FamilyName;
AbpClaimTypes.PhoneNumber = OpenIddictConstants.Claims.PhoneNumber;
AbpClaimTypes.PhoneNumberVerified = OpenIddictConstants.Claims.PhoneNumberVerified;
AbpClaimTypes.Email = OpenIddictConstants.Claims.Email;
AbpClaimTypes.EmailVerified = OpenIddictConstants.Claims.EmailVerified;
}
var openIddictBuilder = services.AddOpenIddict()
.AddServer(builder =>
{
builder
.SetAuthorizationEndpointUris("/connect/authorize")
// /.well-known/oauth-authorization-server
// /.well-known/openid-configuration
//.SetConfigurationEndpointUris()
// /.well-known/jwks
//.SetCryptographyEndpointUris()
.SetDeviceEndpointUris("/connect/device")
.SetIntrospectionEndpointUris("/connect/introspect")
.SetLogoutEndpointUris("/connect/logout")
.SetRevocationEndpointUris("/connect/revocat")
.SetTokenEndpointUris("/connect/token")
.SetUserinfoEndpointUris("/connect/userinfo")
.SetVerificationEndpointUris("/connect/verify");
builder
.AllowAuthorizationCodeFlow()
.AllowHybridFlow()
.AllowImplicitFlow()
.AllowPasswordFlow()
.AllowClientCredentialsFlow()
.AllowRefreshTokenFlow()
.AllowDeviceCodeFlow()
.AllowNoneFlow();
builder.RegisterScopes(new[]
{
OpenIddictConstants.Scopes.OpenId,
OpenIddictConstants.Scopes.Email,
OpenIddictConstants.Scopes.Profile,
OpenIddictConstants.Scopes.Phone,
OpenIddictConstants.Scopes.Roles,
OpenIddictConstants.Scopes.Address,
OpenIddictConstants.Scopes.OfflineAccess
});
builder.UseAspNetCore()
.EnableAuthorizationEndpointPassthrough()
.EnableTokenEndpointPassthrough()
.EnableUserinfoEndpointPassthrough()
.EnableLogoutEndpointPassthrough()
.EnableVerificationEndpointPassthrough()
.EnableStatusCodePagesIntegration();
if (builderOptions.AddDevelopmentEncryptionAndSigningCertificate)
{
builder
.AddDevelopmentEncryptionCertificate()
.AddDevelopmentSigningCertificate();
}
var wildcardDomainsOptions = services.ExecutePreConfiguredActions<AbpOpenIddictWildcardDomainOptions>();
if (wildcardDomainsOptions.EnableWildcardDomainSupport)
{
var preActions = services.GetPreConfigureActions<AbpOpenIddictWildcardDomainOptions>();
Configure<AbpOpenIddictWildcardDomainOptions>(options =>
{
preActions.Configure(options);
});
builder.RemoveEventHandler(OpenIddictServerHandlers.Authentication.ValidateClientRedirectUri.Descriptor);
builder.AddEventHandler(AbpValidateClientRedirectUri.Descriptor);
builder.RemoveEventHandler(OpenIddictServerHandlers.Authentication.ValidateRedirectUriParameter.Descriptor);
builder.AddEventHandler(AbpValidateRedirectUriParameter.Descriptor);
builder.RemoveEventHandler(OpenIddictServerHandlers.Session.ValidateClientPostLogoutRedirectUri.Descriptor);
builder.AddEventHandler(AbpValidateClientPostLogoutRedirectUri.Descriptor);
builder.RemoveEventHandler(OpenIddictServerHandlers.Session.ValidatePostLogoutRedirectUriParameter.Descriptor);
builder.AddEventHandler(AbpValidatePostLogoutRedirectUriParameter.Descriptor);
}
services.ExecutePreConfiguredActions(builder);
});
services.ExecutePreConfiguredActions(openIddictBuilder);
}
}

2
modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/AbpOpenIddictOptions.cs → modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictOptions.cs

@ -2,7 +2,7 @@
namespace Volo.Abp.OpenIddict;
public class AbpOpenIddictOptions
public class AbpOpenIddictAspNetCoreOptions
{
/// <summary>
/// Updates <see cref="AbpClaimTypes"/> to be compatible with OpenIddict claims.

0
modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/WildcardDomains/AbpOpenIddictWildcardDomainBase.cs → modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/WildcardDomains/AbpOpenIddictWildcardDomainBase.cs

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save