@ -1,5 +1,7 @@
|
||||
{
|
||||
"culture": "en",
|
||||
"texts": {
|
||||
"AbpTitle": "ABP Framework - Open Source Web Application Framework",
|
||||
"AbpDescription": "ABP is an open source application framework focused on AspNet Core based web application development. Don't repeat yourself, focus on your own business code."
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
{
|
||||
"culture": "en",
|
||||
"texts": {
|
||||
"FAQ": "FAQ"
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
{
|
||||
"culture": "pt-BR",
|
||||
"texts": {
|
||||
"FAQ": "FAQ"
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
{
|
||||
"culture": "ro-RO",
|
||||
"texts": {
|
||||
"FAQ": "FAQ"
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
{
|
||||
"culture": "sl",
|
||||
"texts": {
|
||||
"FAQ": "FAQ"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
{
|
||||
"culture": "tr",
|
||||
"texts": {
|
||||
"FAQ": "SSS"
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
{
|
||||
"culture": "zh-Hans",
|
||||
"texts": {
|
||||
"FAQ": "常问问题"
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
{
|
||||
"culture": "zh-Hant",
|
||||
"texts": {
|
||||
"FAQ": "常问问题"
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,243 @@
|
||||
# Consuming HTTP APIs from a .NET Client Using ABP's Client Proxy System
|
||||
|
||||
In this article, I will explain how to consume HTTP APIs from a .NET application using ABP's [dynamic](https://docs.abp.io/en/abp/latest/API/Dynamic-CSharp-API-Clients) and [static](https://docs.abp.io/en/abp/latest/API/Static-CSharp-API-Clients) client-side proxy systems. I will start by creating a new project and consume the HTTP APIs from a .NET console application using dynamic client proxies. Then I will switch to static client proxies. Finally, I will glance at the differences and similarities between static and dynamic generic proxies.
|
||||
|
||||
Here the main benefits of using the client-side proxy system (either dynamic or static):
|
||||
|
||||
* Automatically maps C# method calls to remote server HTTP calls by considering the HTTP method, route, query string parameters, request payload and other details.
|
||||
* Authenticates the HTTP Client by adding an access token to the HTTP header.
|
||||
* Serializes to and deserialize from JSON.
|
||||
* Handles HTTP API versioning.
|
||||
* Adds correlation id, current tenant id and the current culture to the request.
|
||||
* Properly handles the error messages sent by the server and throws proper exceptions.
|
||||
|
||||
## Create a new ABP application with the ABP CLI
|
||||
Firstly create a new solution via [ABP CLI](https://docs.abp.io/en/abp/latest/CLI):
|
||||
|
||||
```shell
|
||||
abp new Acme.BookStore
|
||||
```
|
||||
|
||||
> See ABP's [Getting Started document](https://docs.abp.io/en/abp/latest/Getting-Started-Setup-Environment?UI=MVC&DB=EF&Tiered=No) to learn how to create and run your application, if you haven't done it before.
|
||||
|
||||
## Create the application service interface
|
||||
I will start by creating an application service and exposing it as an HTTP API to be consumed by remote clients. First, define an interface for the application service; Create an `IBookAppService` interface in the `Books` folder (namespace) of the `Acme.BookStore.Application.Contracts` project:
|
||||
|
||||
````csharp
|
||||
using System.Threading.Tasks;
|
||||
using Volo.Abp.Application.Dtos;
|
||||
using Volo.Abp.Application.Services;
|
||||
|
||||
namespace Acme.BookStore.Books
|
||||
{
|
||||
public interface IBookAppService : IApplicationService
|
||||
{
|
||||
Task<PagedResultDto<BookDto>> GetListAsync();
|
||||
}
|
||||
}
|
||||
````
|
||||
|
||||
Also add a `BookDto` class inside the same `Books` folder:
|
||||
|
||||
```csharp
|
||||
using System;
|
||||
using Volo.Abp.Application.Dtos;
|
||||
|
||||
namespace Acme.BookStore.Books
|
||||
{
|
||||
public class BookDto
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string AuthorName { get; set; }
|
||||
public float Price { get; set; }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Implement the application service
|
||||
It is time to implement the `IBookAppService` interface. Create a new class named `BookAppService` in the `Books` namespace (folder) of the `Acme.BookStore.Application` project:
|
||||
|
||||
```csharp
|
||||
using Acme.BookStore.Permissions;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Volo.Abp.Application.Dtos;
|
||||
using Volo.Abp.Application.Services;
|
||||
|
||||
namespace Acme.BookStore.Books
|
||||
{
|
||||
public class BookAppService : ApplicationService, IBookAppService
|
||||
{
|
||||
public Task<PagedResultDto<BookDto>> GetListAsync()
|
||||
{
|
||||
var bookDtos = new List<BookDto>()
|
||||
{
|
||||
new BookDto(){ Name = "Hunger", AuthorName ="Knut Hamsun", Price = 50},
|
||||
new BookDto(){ Name = "Crime and Punishment", AuthorName ="Dostoevsky", Price = 60},
|
||||
new BookDto(){ Name = "For Whom the Bell Tolls", AuthorName ="Ernest Hemingway", Price = 70}
|
||||
};
|
||||
return Task.FromResult(new PagedResultDto<BookDto>(
|
||||
bookDtos.Count,
|
||||
bookDtos
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
It simply returns a list of books. You probably want to get the books from a database, but it doesn't matter for this article. If you want it, you can fully implement [this tutorial](https://docs.abp.io/en/abp/latest/Tutorials/Part-1?UI=MVC&DB=EF).
|
||||
|
||||
## Consume the app service from the console application
|
||||
The startup solution comes with an example .NET console application (`Acme.BookStore.HttpApi.Client.ConsoleTestApp`) that is fully configured to consume your HTTP APIs remotely. Change `ClientDemoService` as shown in the following `Acme.BookStore.HttpApi.Client.ConsoleTestApp` project (it is under the `test` folder).
|
||||
|
||||
```csharp
|
||||
using Acme.BookStore.Books;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Volo.Abp.Application.Dtos;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
|
||||
namespace Acme.BookStore.HttpApi.Client.ConsoleTestApp;
|
||||
|
||||
public class ClientDemoService : ITransientDependency
|
||||
{
|
||||
private readonly IBookAppService _bookAppService;
|
||||
|
||||
public ClientDemoService(IBookAppService bookAppService )
|
||||
{
|
||||
_bookAppService = bookAppService;
|
||||
}
|
||||
|
||||
public async Task RunAsync()
|
||||
{
|
||||
var listOfBooks = await _bookAppService.GetListAsync(new PagedAndSortedResultRequestDto());
|
||||
Console.WriteLine($"Books: {string.Join(", ", listOfBooks.Items.Select(p => p.Name).ToList())}");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We are basically injecting the `IBookAppService` interface to consume the remote service. ABP handles all the details (performing HTTP request, deserializing the resulting JSON object, etc) for us.
|
||||
|
||||
You can run the application to see the output:
|
||||
|
||||
```
|
||||
Books: Hunger, Crime and Punishment, For Whom the Bell Tolls
|
||||
```
|
||||
|
||||
## Convert the application to use static client proxies
|
||||
The [application startup template](https://docs.abp.io/en/abp/latest/Startup-Templates/Application) comes pre-configured for the **dynamic** client proxy generation, in the `HttpApi.Client` project. If you want to switch to the **static** client proxies, you should change `context.Services.AddHttpClientProxies` to `context.Services.AddStaticHttpClientProxies` in the module class of your `HttpApi.Client` project:
|
||||
|
||||
```csharp
|
||||
public class BookStoreHttpApiClientModule : AbpModule
|
||||
{
|
||||
public const string RemoteServiceName = "Default";
|
||||
|
||||
public override void ConfigureServices(ServiceConfigurationContext context)
|
||||
{
|
||||
// Other configurations...
|
||||
|
||||
context.Services.AddStaticHttpClientProxies(
|
||||
typeof(BookStoreApplicationContractsModule).Assembly,
|
||||
RemoteServiceName
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `AddStaticHttpClientProxies` method gets an assembly, finds all service interfaces in the given assembly, and prepares for static client proxy generation.
|
||||
|
||||
|
||||
Now you're ready to generate the client proxy code by running the following command in the root folder of your client project **while your server-side project is running**:
|
||||
|
||||
````bash
|
||||
abp generate-proxy -t csharp -u http://localhost:44397/
|
||||
````
|
||||
|
||||
> The URL (`-u` parameter's value) might be different for your application. It should be the server's root URL.
|
||||
|
||||
You should see the generated files under the selected folder:
|
||||
|
||||

|
||||
|
||||
Now you can run the console client application again. You should see the same output:
|
||||
|
||||
````
|
||||
Books: Hunger, Crime and Punishment, For Whom the Bell Tolls
|
||||
````
|
||||
|
||||
## Add authorization
|
||||
The ABP Framework provides an [authorization system](https://docs.abp.io/en/abp/latest/Authorization) based on [ASP.NET Core's authorization infrastructure](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/introduction). We can define permissions and restrict access to some of our application's functionalities, so only the allowed users/clients can use these functionalities. Here, I will define a permission to be able to get the list of books.
|
||||
|
||||
### Defining a permission
|
||||
|
||||
Under `Acme.BookStore.Application.Contracts` open `BookStorePermissions` and paste the below code:
|
||||
```csharp
|
||||
namespace Acme.BookStore.Permissions;
|
||||
|
||||
public static class BookStorePermissions
|
||||
{
|
||||
public const string GroupName = "BookStore";
|
||||
|
||||
public static class Books
|
||||
{
|
||||
public const string Default = GroupName + ".Books";
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
You also need to change `BookStorePermissionDefinitionProvider` under the same folder and project as follows:
|
||||
```csharp
|
||||
using Acme.BookStore.Localization;
|
||||
using Volo.Abp.Authorization.Permissions;
|
||||
using Volo.Abp.Localization;
|
||||
|
||||
public class BookStorePermissionDefinitionProvider : PermissionDefinitionProvider
|
||||
{
|
||||
public override void Define(IPermissionDefinitionContext context)
|
||||
{
|
||||
var bookStoreGroup = context.AddGroup(BookStorePermissions.GroupName);
|
||||
bookStoreGroup.AddPermission(BookStorePermissions.Books.Default);
|
||||
}
|
||||
}
|
||||
```
|
||||
### Authorizing the application service
|
||||
|
||||
We can now add the `[Authorize(BookStorePermissions.Books.Default)]` attribute to the `BookAppService` class:
|
||||
|
||||
```csharp
|
||||
[Authorize(BookStorePermissions.Books.Default)]
|
||||
public class BookAppService : ApplicationService, IBookAppService
|
||||
{
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
If you run the server now, then run the console client application, you will see the following error on the console application:
|
||||
|
||||
```
|
||||
Unhandled exception. Volo.Abp.Http.Client.AbpRemoteCallException: Forbidden at
|
||||
Volo.Abp.Http.Client.ClientProxying.ClientProxyBase`1
|
||||
.ThrowExceptionForResponseAsync(HttpResponseMessage response)...
|
||||
```
|
||||
|
||||
To fix the problem, we should grant permission to the admin user. We are granting permission to the admin user because the console application is configured to use the Resource Owner Password Grant Flow. That means the client application is consuming services on behalf of the admin user. You can see the configuration in the `appsettings.json` file of the console application.
|
||||
|
||||
### Granting the permission
|
||||
|
||||
Once you define the permissions, you can see them on the permission management modal.
|
||||
|
||||
Go to the Administration -> Identity -> Roles page, select the Permissions action for the admin role to open the permission management modal:
|
||||

|
||||
Grant the permissions you want and save the modal.
|
||||
|
||||
## Dynamic vs static proxies
|
||||
|
||||
Static generic proxies provide **better performance** because they don't need to run on runtime, but you should **re-generate** them once you change the API endpoint definition. Dynamic generic proxies don't need to be re-generated because they work on the runtime but they have a slight performance penalty.
|
||||
|
||||
## Further Reading
|
||||
In this tutorial, I explained how you can create an example project and apply a static client proxy instead of a dynamic client proxy. I also summarized the differences between both approaches. If you want to get more information, you can read the following documents:
|
||||
|
||||
* [Static C# API Client Proxies](https://docs.abp.io/en/abp/latest/API/Static-CSharp-API-Clients)
|
||||
* [Dynamic C# API Client Proxies](https://docs.abp.io/en/abp/latest/API/Dynamic-CSharp-API-Clients)
|
||||
* [Web Application Development Tutorial](https://docs.abp.io/en/abp/latest/Tutorials/Part-1?UI=MVC&DB=EF)
|
||||
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 19 KiB |
@ -0,0 +1,54 @@
|
||||
# How to contribute to abp.io as a frontend developer
|
||||
|
||||
## How to setup development environment
|
||||
|
||||
### Pre-requirements
|
||||
|
||||
- Dotnet core SDK https://dotnet.microsoft.com/en-us/download
|
||||
- Nodejs LTS https://nodejs.org/en/
|
||||
- Docker https://docs.docker.com/engine/install
|
||||
- Angular CLI. https://angular.io/guide/what-is-angular#angular-cli
|
||||
- Abp CLI https://docs.abp.io/en/abp/latest/cli
|
||||
- A code editor
|
||||
|
||||
Note: This arcticle prepare Windows OS. You may change the path type of your OS. an Example
|
||||
|
||||
Windows: `templates\app\aspnet-core\src\MyCompanyName.MyProjectName.DbMigrator\appsettings.json`
|
||||
|
||||
Unix: `templates/app/aspnet-core/src/MyCompanyName.MyProjectName.DbMigrator/appsettings.json`
|
||||
|
||||
### Sample docker commands
|
||||
|
||||
You need to install SQL Server and Redis. You can install these programs without docker, but my example uses docker containers. Your computer should have Docker Engine. Then open the terminal en execute the commands one by one.
|
||||
For the Sql Server
|
||||
|
||||
docker run -v sqlvolume:/var/opt/mssql -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=yourpassword -p 1433:1433 -d mcr.microsoft.com/mssql/server:2019-CU3-ubuntu-18.04
|
||||
|
||||
For the Redis
|
||||
|
||||
docker run -p 6379:6379 -d redis
|
||||
|
||||
Then we are ready to download and execute the code.
|
||||
|
||||
## Folder Structure
|
||||
|
||||
The app has a backend written in .net core (c#) and an angular app. It would help if you ran both of them.
|
||||
|
||||
### Running Backend App
|
||||
|
||||
The path of the Backend app is “templates\app\aspnet-core.” If you want to work with dockerized SQL Server, you should change connection strings for running with docker. The path of the connection string is
|
||||
`templates\app\aspnet-core\src\MyCompanyName.MyProjectName.DbMigrator\appsettings.json`.
|
||||
|
||||
Before running the backend, you should run the Db migrator project. The DbMigrator created initial tables and values. The path of DbMigrator is `templates\app\aspnet-core\src\MyCompanyName.MyProjectName.DbMigrator`. Open a terminal in the path and execute the command `dotnet run` in terminal
|
||||
|
||||
One last step before the running the backend is installing client-side libraries. Go to `templates\app\aspnet-core`. Open a terminal in the path and execute the command `abp install-libs` in terminal
|
||||
|
||||
Next step you should go to path of backend host project. The path is `templates\app\aspnet-core\src\MyCompanyName.MyProjectName.HttpApi.HostWithIds`. Open a terminal in the path and execute the command `dotnet run` in terminal
|
||||
|
||||
Your backend should be running successfully
|
||||
|
||||
### Running Frontend App
|
||||
|
||||
There is a demo app. The path of the demo app is `npm\ng-packs\apps\dev-app`. The demo app is connected to the packages with local references. Open the terminal in `npm\ng-packs\apps\dev-app` and execute `yarn` or `npm i` in terminal. After the package installed run `npm start` or `yarn start`.
|
||||
|
||||
The repo uses Nx and packages connected with `local references`. The packages path is `”npm\ng-packs\packages`
|
||||
@ -0,0 +1,215 @@
|
||||
## ABP OpenIddict Modules
|
||||
|
||||
## How to Install
|
||||
|
||||
TODO:
|
||||
|
||||
## User Interface
|
||||
|
||||
This module implements the domain logic and database integrations, but not provides any UI. Management UI is useful if you need to add applications and scopes on the fly. In this case, you may build the management UI yourself or consider to purchase the [ABP Commercial](https://commercial.abp.io/) which provides the management UI for this module.
|
||||
|
||||
## Relations to Other Modules
|
||||
|
||||
This module is based on the [Identity Module](Identity.md) and have an [integration package](https://www.nuget.org/packages/Volo.Abp.Account.Web.OpenIddict) with the [Account Module](Account.md).
|
||||
|
||||
## The module
|
||||
|
||||
### Demo projects
|
||||
|
||||
In the module's `app` directory there are six projects(including `angular`)
|
||||
|
||||
* `OpenIddict.Demo.Server`: An abp application with integrated modules (has two `clients` and a `scope`).
|
||||
* `OpenIddict.Demo.API`: ASP NET Core API application using JwtBearer authentication
|
||||
* `OpenIddict.Demo.Client.Mvc`: ASP NET Core MVC application using `OpenIdConnect` for authentication
|
||||
* `OpenIddict.Demo.Client.Console`: Use `IdentityModel` to test OpenIddict's various endpoints, and call the api of `OpenIddict.Demo.API`
|
||||
* `OpenIddict.Demo.Client.BlazorWASM:` ASP NET Core Blazor application using `OidcAuthentication` for authentication
|
||||
* `angular`: An angular application that integrates the abp ng modules and uses oauth for authentication
|
||||
|
||||
#### How to run?
|
||||
|
||||
Confirm the connection string of `appsettings.json` in the `OpenIddict.Demo.Server` project. Running the project will automatically create the database and initialize the data.
|
||||
After running the `OpenIddict.Demo.API` project, then you can run the rest of the projects to test.
|
||||
|
||||
### Domain module
|
||||
|
||||
There are four main entities included in this module.
|
||||
|
||||
* OpenIddictApplication: **Represents applications(client)**
|
||||
* OpenIddictScope: **Represents scopes**
|
||||
* OpenIddictAuthorization: **Represents authorizations, Track of logical chains of tokens and user consent..**
|
||||
* OpenIddictToken: **Represents various tokens.**
|
||||
|
||||
Domain also implements four store interfaces in OpenIddict, OpenIddict uses store to manage entities, corresponding to the above four entities, Custom entity repository is used in the store.
|
||||
|
||||
|
||||
```cs
|
||||
//Manager
|
||||
OpenIddictApplicationManager
|
||||
OpenIddictScopeManager
|
||||
OpenIddictAuthorizationManager
|
||||
OpenIddictTokenManager
|
||||
|
||||
//Store
|
||||
IOpenIddictApplicationStore
|
||||
IOpenIddictScopeStore
|
||||
IOpenIddictAuthorizationStore
|
||||
IOpenIddictTokenStore
|
||||
|
||||
//Repository
|
||||
IOpenIddictApplicationRepository
|
||||
IOpenIddictScopeRepository
|
||||
IOpenIddictAuthorizationRepository
|
||||
IOpenIddictTokenRepository
|
||||
```
|
||||
|
||||
We enabled most of OpenIddict's features in the `AddOpenIddict` method, You can change OpenIddict's related builder options via `PreConfigure`.
|
||||
|
||||
```cs
|
||||
PreConfigure<OpenIddictBuilder>(builder =>
|
||||
{
|
||||
//builder
|
||||
});
|
||||
|
||||
PreConfigure<OpenIddictCoreBuilder>(builder =>
|
||||
{
|
||||
//builder
|
||||
});
|
||||
|
||||
PreConfigure<OpenIddictServerBuilder>(builder =>
|
||||
{
|
||||
//builder
|
||||
});
|
||||
```
|
||||
|
||||
#### 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.
|
||||
|
||||
You can also change this options via `PreConfigure`.
|
||||
|
||||
#### Automatically removing orphaned tokens/authorizations
|
||||
|
||||
There is a background task in the `Domain` module (`enabled by default`) that automatically removes orphaned tokens/authorizations, you can configure `TokenCleanupOptions` to manage it.
|
||||
|
||||
### ASP NET Core module
|
||||
|
||||
This module integrates ASP NET Core, with built-in MVC controllers for four protocols. It uses OpenIddict's [Pass-through mode](https://documentation.openiddict.com/guides/index.html#pass-through-mode).
|
||||
|
||||
```cs
|
||||
AuthorizeController -> connect/authorize
|
||||
TokenController -> connect/token
|
||||
LogoutController -> connect/logout
|
||||
UserInfoController -> connect/userinfo
|
||||
```
|
||||
|
||||
> We will implement the related functions of **device flow** in the PRO module..
|
||||
|
||||
#### How to control claims in access_token and id_token
|
||||
|
||||
You can use the [Claims Principal Factory](https://docs.abp.io/en/abp/latest/Authorization#claims-principal-factory) to add/remove claims to the `ClaimsPrincipal`.
|
||||
|
||||
The `AbpDefaultOpenIddictClaimDestinationsProvider` service will add `Name`, `Email` and `Role` types of Claims to `access_token` and `id_token`, other claims are only added to `access_token` by default, and remove the `SecurityStampClaimType` secret claim of `Identity`.
|
||||
|
||||
You can create a service that inherits from `IAbpOpenIddictClaimDestinationsProvider` and add it to DI to fully control the destinations of claims
|
||||
|
||||
```cs
|
||||
public class MyClaimDestinationsProvider : IAbpOpenIddictClaimDestinationsProvider, ITransientDependency
|
||||
{
|
||||
public virtual Task SetDestinationsAsync(AbpOpenIddictClaimDestinationsProviderContext context)
|
||||
{
|
||||
// ...
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
Configure<AbpOpenIddictClaimDestinationsOptions>(options =>
|
||||
{
|
||||
options.ClaimDestinationsProvider.Add<MyClaimDestinationsProvider>();
|
||||
});
|
||||
```
|
||||
|
||||
For detailed information, please refer to: [OpenIddict claim destinations](https://documentation.openiddict.com/configuration/claim-destinations.html)
|
||||
|
||||
### EF Core module
|
||||
|
||||
Implements the above four repository interfaces.
|
||||
|
||||
### MongoDB module
|
||||
|
||||
Implements the above four repository interfaces.
|
||||
|
||||
|
||||
## OpenIddict
|
||||
|
||||
### Documentation
|
||||
|
||||
For more details about OpenIddict, please refer to its official documentation and Github.
|
||||
|
||||
https://documentation.openiddict.com
|
||||
|
||||
https://github.com/openiddict/openiddict-core#resources
|
||||
|
||||
### Disable AccessToken Encryption
|
||||
|
||||
ABP disables the `access token encryption` by default for compatibility, you can manually enable it if needed.
|
||||
|
||||
```cs
|
||||
public override void PreConfigureServices(ServiceConfigurationContext context)
|
||||
{
|
||||
PreConfigure<OpenIddictServerBuilder>(builder =>
|
||||
{
|
||||
builder.Configure(options => options.DisableAccessTokenEncryption = false);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
https://documentation.openiddict.com/configuration/token-formats.html#disabling-jwt-access-token-encryption
|
||||
|
||||
|
||||
### PKCE
|
||||
|
||||
https://documentation.openiddict.com/configuration/proof-key-for-code-exchange.html
|
||||
|
||||
### Request/Response process
|
||||
|
||||
I will briefly introduce the principle of OpenIddict so that everyone can quickly understand it.
|
||||
|
||||
The `OpenIddict.Server.AspNetCore` adds an authentication scheme(`Name: OpenIddict.Server.AspNetCore, handler: OpenIddictServerAspNetCoreHandler`) and implements the `IAuthenticationRequestHandler` interface.
|
||||
|
||||
It will be executed first in `AuthenticationMiddleware` and can short-circuit the current request. Otherwise, `DefaultAuthenticateScheme` will be called and continue to execute the pipeline.
|
||||
|
||||
`OpenIddictServerAspNetCoreHandler` will call various built-in handlers(Handling requests and responses), And the handler will process according to the context or skip logic that has nothing to do with it.
|
||||
|
||||
Example a token request:
|
||||
|
||||
```
|
||||
POST /connect/token HTTP/1.1
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
grant_type=password&
|
||||
client_id=AbpApp&
|
||||
client_secret=1q2w3e*&
|
||||
username=admin&
|
||||
password=1q2w3E*&
|
||||
scope=AbpAPI offline_access
|
||||
```
|
||||
|
||||
This request will be processed by various handlers. They will confirm the endpoint type of the request, check `http/https`, verify that the request parameters (`client. scope etc`) are valid and exist in the database, etc. Various protocol checks. And build a `OpenIddictRequest` object, If there are any errors, the response content may be set and directly short-circuit the current request.
|
||||
|
||||
If everything is ok, the request will go to our processing controller(eg `TokenController`), we can get an `OpenIddictRequest` from the http request at this time. The rest of our work will be based on this object.
|
||||
|
||||
We may check the `username` and `password` in the request. If it is correct we create a `ClaimsPrincipal` object and return a `SignInResult`, which uses the `OpenIddict.Validation.AspNetCore` authentication scheme name, will calls `OpenIddictServerAspNetCoreHandler` for processing.
|
||||
|
||||
`OpenIddictServerAspNetCoreHandler` do some checks to generate json and replace the http response content.
|
||||
|
||||
The `ForbidResult` `ChallengeResult` are all the above types of processing.
|
||||
|
||||
If you need to customize OpenIddict, you need to replace/delete/add new handlers and make it execute in the correct order.
|
||||
|
||||
Please refer to:
|
||||
https://documentation.openiddict.com/guides/index.html#events-model
|
||||
|
||||
## Sponsor
|
||||
|
||||
Please consider sponsoring this project: https://github.com/sponsors/kevinchalet
|
||||
@ -0,0 +1,47 @@
|
||||
# Angular: Global Features API
|
||||
|
||||
The `ConfigStateService.getGlobalFeatures` API allows you to get the enabled features of the [Global Features](../../Global-Features.md) on the client side.
|
||||
|
||||
> This document only explains the JavaScript API. See the [Global Features](../../Global-Features.md) document to understand the ABP Global Features system.
|
||||
|
||||
## Usage
|
||||
|
||||
````js
|
||||
|
||||
import { ConfigStateService } from '@abp/ng.core';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
/* class metadata here */
|
||||
})
|
||||
class DemoComponent implements OnInit {
|
||||
constructor(private config: ConfigStateService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
// Gets all enabled global features.
|
||||
const getGlobalFeatures = this.config.getGlobalFeatures();
|
||||
|
||||
//Example result is: `{ enabledFeatures: [ 'Shopping.Payment', 'Ecommerce.Subscription' ] }`
|
||||
|
||||
// or
|
||||
this.config.getGlobalFeatures$().subscribe(getGlobalFeatures => {
|
||||
// use getGlobalFeatures here
|
||||
})
|
||||
|
||||
// Check the global feature is enabled
|
||||
this.config.getGlobalFeatureIsEnabled('Ecommerce.Subscription')
|
||||
|
||||
//Example result is `true`
|
||||
|
||||
this.config.getGlobalFeatureIsEnabled('My.Subscription')
|
||||
|
||||
//Example result is `false`
|
||||
|
||||
// or
|
||||
this.config.getGlobalFeatureIsEnabled$('Ecommerce.Subscription').subscribe((isEnabled:boolean) => {
|
||||
// use isEnabled here
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,44 @@
|
||||
# Loading Directive
|
||||
|
||||
|
||||
You may want to block a part of the UI and show a spinner for a while; the `LoadingDirective` directive makes this for you. `LoadingDirective` has been exposed by the `@abp/ng.theme.shared` package.
|
||||
|
||||
|
||||
## Getting Started
|
||||
|
||||
In order to use the `LoadingDirective` in an HTML template, the **`ThemeSharedModule`** should be imported into your module like this:
|
||||
|
||||
```js
|
||||
// ...
|
||||
import { ThemeSharedModule } from '@abp/ng.theme.shared';
|
||||
|
||||
@NgModule({
|
||||
//...
|
||||
imports: [..., ThemeSharedModule],
|
||||
})
|
||||
export class MyFeatureModule {}
|
||||
```
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
The `LoadingDirective` is easy to use. The directive's selector is **`abpLoading`**. By adding the `abpLoading` attribute to an HTML element, you can activate the `LoadingDirectiveective` for the HTML element when the value is true.
|
||||
|
||||
See an example usage:
|
||||
|
||||
```html
|
||||
<div [abpLoading]="true">
|
||||
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Laboriosam commodi quae aspernatur,
|
||||
corporis velit et suscipit id consequuntur amet minima expedita cum reiciendis dolorum
|
||||
cupiditate? Voluptas eaque voluptatum odio deleniti quo vel illum nemo accusamus nulla ratione
|
||||
impedit dolorum expedita necessitatibus fugiat ullam beatae, optio eum cupiditate ducimus
|
||||
architecto.
|
||||
</div>
|
||||
```
|
||||
|
||||
|
||||
The `abpLoading` attribute has been added to the `<div>` element that contains very a long text inside to activate the `LoadingDirective`.
|
||||
|
||||
See the result:
|
||||
|
||||

|
||||
|
After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 160 KiB |
@ -0,0 +1,248 @@
|
||||
# ABP CLI - 新解决方案命令示例
|
||||
|
||||
`abp new`命令基于abp模板创建abp解决方案或其他组件. [ABP CLI](CLI.md)有一些参数可以用于创建新的ABP解决方案. 在本文档中, 我们将向你展示一些创建新的解决方案的命令示例. 所有的项目名称都是`Acme.BookStore`. 目前, 唯一可用的移动端项目是`React Native`移动端应用程序. 可用的数据库提供程序有`Entity Framework Core`和`MongoDB`. 所有命令都以`abp new`开头.
|
||||
|
||||
## Angular
|
||||
|
||||
以下命令用于创建Angular UI项目:
|
||||
|
||||
* 在新文件夹中创建项目, **Entity Framework Core**, 非移动端应用程序:
|
||||
|
||||
````bash
|
||||
abp new Acme.BookStore -u angular --mobile none --database-provider ef -csf
|
||||
````
|
||||
|
||||
* 在新文件夹中创建项目, **Entity Framework Core**, 默认应用程序模板, **拆分Identity Server**:
|
||||
|
||||
```bash
|
||||
abp new Acme.BookStore -t app -u angular -m none --separate-auth-server --database-provider ef -csf
|
||||
```
|
||||
|
||||
* 在新文件夹中创建项目, **Entity Framework Core**, **自定义连接字符串**:
|
||||
|
||||
```bash
|
||||
abp new Acme.BookStore -u angular -csf --connection-string Server=localhost;Database=MyDatabase;Trusted_Connection=True
|
||||
```
|
||||
|
||||
* 在`C:\MyProjects\Acme.BookStore`中创建解决方案, **MongoDB**, 默认应用程序模板, 包含移动端项目:
|
||||
|
||||
```bash
|
||||
abp new Acme.BookStore -u angular --database-provider mongodb --output-folder C:\MyProjects\Acme.BookStore
|
||||
```
|
||||
|
||||
* 在新文件夹中创建项目, **MongoDB**, 默认应用程序模板, 不创建移动端应用程序, **拆分Identity Server**:
|
||||
|
||||
```bash
|
||||
abp new Acme.BookStore -t app -u angular -m none --separate-auth-server --database-provider mongodb -csf
|
||||
```
|
||||
|
||||
## MVC
|
||||
|
||||
以下命令用于创建MVC UI项目:
|
||||
|
||||
* 在新文件夹中创建项目, **Entity Framework Core**, 不创建移动端应用程序:
|
||||
|
||||
```bash
|
||||
abp new Acme.BookStore -t app -u mvc --mobile none --database-provider ef -csf
|
||||
```
|
||||
|
||||
* 在新文件夹中创建项目, **Entity Framework Core**, **分层结构** (*Web和HTTP API层是分开的*), 不创建移动端应用程序:
|
||||
|
||||
```bash
|
||||
abp new Acme.BookStore -u mvc --mobile none --tiered --database-provider ef -csf
|
||||
```
|
||||
|
||||
* 在新文件夹中创建项目, **MongoDB**, 不创建移动端应用程序:
|
||||
|
||||
```bash
|
||||
abp new Acme.BookStore -t app -u mvc --mobile none --database-provider mongodb -csf
|
||||
```
|
||||
|
||||
* 在新文件夹中创建项目, **MongoDB**, **分层结构**:
|
||||
|
||||
```bash
|
||||
abp new Acme.BookStore -u mvc --tiered --database-provider mongodb -csf
|
||||
```
|
||||
|
||||
|
||||
## Blazor
|
||||
|
||||
以下命令用于创建Blazor项目:
|
||||
|
||||
* **Entity Framework Core**, 不创建移动端应用程序:
|
||||
|
||||
```bash
|
||||
abp new Acme.BookStore -t app -u blazor --mobile none
|
||||
```
|
||||
|
||||
* **Entity Framework Core**, **拆分Identity Server**, 包含移动端应用程序:
|
||||
|
||||
```bash
|
||||
abp new Acme.BookStore -u blazor --separate-auth-server
|
||||
```
|
||||
|
||||
* 在新文件夹中创建项目, **MongoDB**, 不创建移动端应用程序:
|
||||
|
||||
```bash
|
||||
abp new Acme.BookStore -u blazor --database-provider mongodb --mobile none -csf
|
||||
```
|
||||
|
||||
## Blazor Server
|
||||
|
||||
以下命令用于创建Blazor项目:
|
||||
|
||||
* **Entity Framework Core**, 不创建移动端应用程序:
|
||||
|
||||
```bash
|
||||
abp new Acme.BookStore -t app -u blazor-server --mobile none
|
||||
```
|
||||
|
||||
* **Entity Framework Core**, **拆分Identity Server**, **拆分API Host**, 包含移动端应用程序:
|
||||
|
||||
```bash
|
||||
abp new Acme.BookStore -u blazor-server --tiered
|
||||
```
|
||||
|
||||
* 在新文件夹中创建项目, **MongoDB**, 不创建移动端应用程序:
|
||||
|
||||
```bash
|
||||
abp new Acme.BookStore -u blazor --database-provider mongodb --mobile none -csf
|
||||
```
|
||||
|
||||
## 无UI
|
||||
|
||||
在默认应用程序模板中, 始终有一个前端项目. 在这个选项中没有前端项目. 它有一个`HttpApi.Host`项目为你的HTTP WebAPI提供服务. 这个选项适合在你想创建一个WebAPI服务时使用.
|
||||
|
||||
* 在新文件夹中创建项目, **Entity Framework Core**, 拆分Identity Server:
|
||||
|
||||
```bash
|
||||
abp new Acme.BookStore -u none --separate-auth-server -csf
|
||||
```
|
||||
* **MongoDB**, 不创建移动端应用程序:
|
||||
|
||||
```bash
|
||||
abp new Acme.BookStore -u none --mobile none --database-provider mongodb
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 控制台应用程序
|
||||
|
||||
这是一个基于.NET控制台应用程序的模板, 集成了ABP模块架构. 要创建控制台应用程序, 请使用以下命令:
|
||||
|
||||
* 项目由以下文件组成: `Acme.BookStore.csproj`, `appsettings.json`, `BookStoreHostedService.cs`, `BookStoreModule.cs`, `HelloWorldService.cs` 和 `Program.cs`.
|
||||
|
||||
```bash
|
||||
abp new Acme.BookStore -t console -csf
|
||||
```
|
||||
|
||||
## 模块
|
||||
|
||||
模块是主项目使用的可重用子应用程序. 如果你正在构建微服务解决方案, 使用ABP模块是最佳方案. 由于模块不是最终的应用程序, 每个模块都有前端UI项目和数据库提供程序. 模块模板带有MVC UI, 可以在没有最终解决方案的情况下进行开发. 但是, 如果要在最终解决方案下开发模块, 可以添加`--no-ui`参数来去除MVC UI项目.
|
||||
|
||||
* 包含前端: `MVC`, `Angular`, `Blazor`. 包含数据库提供程序: `Entity Framework Core`, `MongoDB`. 包含MVC启动项目.
|
||||
|
||||
```bash
|
||||
abp new Acme.IssueManagement -t module
|
||||
```
|
||||
* 与上面相同, 但不包括MVC启动项目.
|
||||
|
||||
```bash
|
||||
abp new Acme.IssueManagement -t module --no-ui
|
||||
```
|
||||
|
||||
* 创建模块并将其添加到解决方案中
|
||||
|
||||
```bash
|
||||
abp new Acme.IssueManagement -t module --add-to-solution-file
|
||||
```
|
||||
|
||||
## 从特定版本创建解决方案
|
||||
|
||||
创建解决方案时, 它总是使用最新版本创建. 要从旧版本创建项目, 可以使用`--version`参数.
|
||||
|
||||
* 使用v3.3.0版本创建解决方案, 包含Angular UI和Entity Framework Core.
|
||||
|
||||
```bash
|
||||
abp new Acme.BookStore -t app -u angular -m none --database-provider ef -csf --version 3.3.0
|
||||
```
|
||||
|
||||
要获取ABP版本列表, 请查看以下链接: https://www.nuget.org/packages/Volo.Abp.Core/
|
||||
|
||||
## 从自定义模板创建
|
||||
|
||||
ABP CLI使用默认的[应用程序模板](https://github.com/abpframework/abp/tree/dev/templates/app)创建项目. 如果要从自定义模板创建新的解决方案, 可以使用参数`--template-source`.
|
||||
|
||||
* 在`c:\MyProjects\templates\app`目录中使用模板, MVC UI, Entity Framework Core, 不创建移动端应用程序.
|
||||
|
||||
```bash
|
||||
abp new Acme.BookStore -t app -u mvc --mobile none --database-provider ef --template-source "c:\MyProjects\templates\app"
|
||||
```
|
||||
|
||||
* 除了此命令从URL `https://myabp.com/app-template.zip` 检索模板之外, 与上一个命令相同.
|
||||
|
||||
```bash
|
||||
abp new Acme.BookStore -t app -u mvc --mobile none --database-provider ef --template-source https://myabp.com/app-template.zip
|
||||
```
|
||||
|
||||
## 创建预览版本
|
||||
|
||||
ABP CLI始终使用最新版本. 要从预览(RC)版本创建解决方案, 请添加`--preview`参数.
|
||||
|
||||
* 在新文件夹中创建项目, Blazor UI, Entity Framework Core, 不创建移动端应用程序, **使用最新版本**:
|
||||
|
||||
```bash
|
||||
abp new Acme.BookStore -t app -u blazor --mobile none -csf --preview
|
||||
```
|
||||
|
||||
## 选择数据库管理系统
|
||||
|
||||
默认的数据库管理系统是 `Entity Framework Core` / ` SQL Server`. 你可以通过使用`--database-management-system`参数选择DBMS. [可用的值](https://github.com/abpframework/abp/blob/dev/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/DatabaseManagementSystem.cs) 包括 `SqlServer`, `MySQL`, `SQLite`, `Oracle`, `Oracle-Devart`, `PostgreSQL`. 默认值是 `SqlServer`.
|
||||
|
||||
* 在新文件夹中创建项目, Angular UI, **PostgreSQL** 数据库:
|
||||
|
||||
```bash
|
||||
abp new Acme.BookStore -u angular --database-management-system PostgreSQL -csf
|
||||
```
|
||||
|
||||
## 使用静态HTTP端口
|
||||
|
||||
ABP CLI始终为项目分配随机端口. 如果需要保留默认端口并且创建解决方案始终使用相同的HTTP端口, 请添加参数`--no-random-port`.
|
||||
|
||||
* 在新文件夹中创建项目, MVC UI, Entity Framework Core, **静态端口**:
|
||||
|
||||
```bash
|
||||
abp new Acme.BookStore --no-random-port -csf
|
||||
```
|
||||
|
||||
## 引用本地ABP框架
|
||||
|
||||
在ABP解决方案中, 默认情况下从NuGet引用ABP库. 有时, 你需要在本地将ABP库引用到你的解决方案中. 这利于调试框架本身. 本地ABP框架的根目录必须有`Volo.Abp.sln`文件. 你可以将以下目录的内容复制到你的文件系统中
|
||||
|
||||
* MVC UI, Entity Framework Core, **引用本地的ABP库**:
|
||||
|
||||
本地路径必须是ABP存储库的根目录.
|
||||
如果`C:\source\abp\framework\Volo.Abp.sln`是你的框架解决方案的路径, 那么你必须设置`--abp-path`参数值为`C:\source\abp`.
|
||||
|
||||
```bash
|
||||
abp new Acme.BookStore --local-framework-ref --abp-path C:\source\abp
|
||||
```
|
||||
|
||||
**输出**:
|
||||
|
||||
如下所示, 引用本地ABP框架库项目.
|
||||
|
||||
```xml
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="C:\source\abp\framework\src\Volo.Abp.Autofac\Volo.Abp.Autofac.csproj" />
|
||||
<ProjectReference Include="C:\source\abp\framework\src\Volo.Abp.AspNetCore.Serilog\Volo.Abp.AspNetCore.Serilog.csproj" />
|
||||
<ProjectReference Include="C:\source\abp\framework\src\Volo.Abp.AspNetCore.Authentication.JwtBearer\Volo.Abp.AspNetCore.Authentication.JwtBearer.csproj" />
|
||||
<ProjectReference Include="..\Acme.BookStore.Application\Acme.BookStore.Application.csproj" />
|
||||
<ProjectReference Include="..\Acme.BookStore.HttpApi\Acme.BookStore.HttpApi.csproj" />
|
||||
<ProjectReference Include="..\Acme.BookStore.EntityFrameworkCore\Acme.BookStore.EntityFrameworkCore.csproj" />
|
||||
</ItemGroup>
|
||||
```
|
||||
|
||||
## 另请参阅
|
||||
|
||||
* [ABP CLI文档](CLI.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.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` 每一个你支持的文化:
|
||||
|
||||

|
||||
|
||||
然后在模板定义提供程序类中添加模板定义:
|
||||
|
||||
````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`):
|
||||
|
||||

|
||||
|
||||
**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).
|
||||
@ -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.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` 每一个你支持的文化:
|
||||
|
||||

|
||||
|
||||
然后在模板定义提供程序类中添加模板定义:
|
||||
|
||||
````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`):
|
||||
|
||||

|
||||
|
||||
**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).
|
||||
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 108 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 134 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 29 KiB |