@ -1,425 +0,0 @@ |
|||||
# Deploy Abp Webapp to Azure App Service |
|
||||
|
|
||||
> In this document, you'll learn how to create and deploy your first abp web app to [Azure App Service](https://docs.microsoft.com/en-us/azure/app-service/overview). The App Service supports various versions of .NET apps, and provides a highly scalable, self-patching web hosting service. Abp web apps are cross-platform and can be hosted on Linux and Windows. |
|
||||
|
|
||||
****Prerequisites**** |
|
||||
|
|
||||
- An Azure account with an active subscription. [Create an account for free](https://azure.microsoft.com/free/dotnet). |
|
||||
- A GitHub account [Create an account for free](http://github.com/). |
|
||||
|
|
||||
|
|
||||
## Create a new ABP Framework application |
|
||||
|
|
||||
Create a repository on [GitHub.com](https://github.com/) (keep all the default settings) |
|
||||
|
|
||||
Open the command prompt and clone the repository into a folder on your computer |
|
||||
|
|
||||
```bash |
|
||||
git clone https://github.com/your-username/your-repository-name.git |
|
||||
``` |
|
||||
Check your dotnet version. It should be at least 3.1.x |
|
||||
```bash |
|
||||
dotnet --version |
|
||||
``` |
|
||||
Install or update the *ABP CLI* using a command line window |
|
||||
|
|
||||
```bash |
|
||||
dotnet tool install -g Volo.Abp.Cli || dotnet tool update -g Volo.Abp.Cli |
|
||||
``` |
|
||||
|
|
||||
Open the command prompt in the *GitHub repository folder* and create a *new abp Blazor solution* with the command below |
|
||||
|
|
||||
```bash |
|
||||
abp new YourAppName -u blazor |
|
||||
``` |
|
||||
|
|
||||
Open the command prompt in the *[YourAppName].DbMigrator* project and enter the command below to apply the database migrations |
|
||||
|
|
||||
```bash |
|
||||
dotnet run |
|
||||
``` |
|
||||
|
|
||||
Open the command prompt in the *[YourAppName].HttpApi.Host* project to run the API project |
|
||||
|
|
||||
```bash |
|
||||
dotnet run |
|
||||
``` |
|
||||
|
|
||||
Navigate to the *applicationUrl* specified in *the launchSettings.json* file of the *[YourAppName].HttpApi.Host project*. You should get the *Swagger window* |
|
||||
|
|
||||
Open the command prompt in the *[YourAppName].Blazor* folder and enter the command below to run the Blazor project |
|
||||
|
|
||||
```bash |
|
||||
dotnet run |
|
||||
``` |
|
||||
|
|
||||
Navigate to the *applicationUrl* specified in the *launchSettings.json* file of the *[YourAppName].Blazor* project. You should get the *ABP Framework Welcome window* |
|
||||
|
|
||||
Stop both the *API* and the *Blazor* project by pressing **CTRL+C** |
|
||||
|
|
||||
Before the github commit, you have to delete "**/wwwroot/libs/*" at *.gitignore* file. |
|
||||
|
|
||||
 |
|
||||
|
|
||||
Open the command prompt in the root folder of your project and *add, commit and push* all your changes to your GitHub repository |
|
||||
|
|
||||
```bash |
|
||||
git add . |
|
||||
git commit -m initialcommit |
|
||||
git push |
|
||||
``` |
|
||||
|
|
||||
|
|
||||
## Create a SQL Database on Azure and change the connection string in the appsettings.json files |
|
||||
|
|
||||
* Login into [Azure Portal](https://portal.azure.com/) |
|
||||
|
|
||||
* Click **Create a resource** |
|
||||
|
|
||||
* Search for *SQL Database* |
|
||||
|
|
||||
* Click the **Create** button in the *SQL Database window* |
|
||||
|
|
||||
* Create a new resource group. Name it *rg[YourAppName]* |
|
||||
|
|
||||
* Enter *[YourAppName]Db* as database name |
|
||||
|
|
||||
* Create a new Server and name it *[yourappname]server* |
|
||||
|
|
||||
* Enter a serveradmin login and passwords. Click the **OK** button |
|
||||
|
|
||||
* Select your *Location* |
|
||||
|
|
||||
* Check *Allow Azure services to access server* |
|
||||
|
|
||||
* Click **Configure database**. Go to the *Basic* version and click the **Apply** button |
|
||||
|
|
||||
* Click the **Review + create** button. Click **Create** |
|
||||
|
|
||||
* Click **Go to resource** and click **SQL server** when the SQL Database is created |
|
||||
|
|
||||
* Click **Networking** under Security left side menu |
|
||||
|
|
||||
* Select **Selected networks** and click **Add your client IP$ address** at the Firewall rules |
|
||||
|
|
||||
* Select **Allow Azure and resources to access this seerver** and save |
|
||||
|
|
||||
* Go to your **SQL database**, click **Connection strings** and copy the connection string |
|
||||
|
|
||||
* Copy/paste the appsettings.json files of the [YourAppName].HttpApi.Host and the [YourAppName].DbMigrator project |
|
||||
|
|
||||
* Do not forget to replace {your_password} with the correct server password you entered in Azure SQL Database |
|
||||
|
|
||||
Open the command prompt in the [YourAppName].DbMigrator project again and enter the command below to apply the database migrations |
|
||||
|
|
||||
```bash |
|
||||
dotnet run |
|
||||
``` |
|
||||
Open the command prompt in the [YourAppName].HttpApi.Host project and enter the command below to check your API is working |
|
||||
|
|
||||
```bash |
|
||||
dotnet run |
|
||||
``` |
|
||||
|
|
||||
Stop the [YourAppName].HttpApi.Host by entering CTRL+C |
|
||||
|
|
||||
Open the command prompt in the root folder of your project and add, commit and push all your changes to your GitHub repository |
|
||||
```bash |
|
||||
git add . |
|
||||
git commit -m initialcommit |
|
||||
git push |
|
||||
``` |
|
||||
|
|
||||
|
|
||||
## Set up the Build pipeline in AzureDevops and publish the Build Artifacts |
|
||||
|
|
||||
* Sign in into Azure DevOps |
|
||||
|
|
||||
* Click **New organization** and follow the steps to create a new organisation. Name it [YourAppName]org |
|
||||
|
|
||||
* Enter [YourAppName]Proj as project name in the ***Create a project to get started*** window |
|
||||
|
|
||||
* Select **Public visibility** and click the **Create project** button |
|
||||
|
|
||||
* Click the **Pipelines** button to continue |
|
||||
|
|
||||
* Click the **Create Pipeline** button |
|
||||
|
|
||||
Select GitHub in the Select your repository window |
|
||||
|
|
||||
 |
|
||||
|
|
||||
* Enter the Connection name. [YourAppName]GitHubConnection and click **Authorize using OAuth** |
|
||||
|
|
||||
* Select your **GitHub** [YourAppName]repo and click Continue |
|
||||
|
|
||||
* Search for **ASP.NET** in the ***Select a template*** window |
|
||||
|
|
||||
 |
|
||||
|
|
||||
* Select the ASP.NET Core template and click the **Apply** button |
|
||||
|
|
||||
* Add the below commands block as a first step in the pipeline |
|
||||
|
|
||||
``` |
|
||||
- task: UseDotNet@2 |
|
||||
inputs: |
|
||||
packageType: 'sdk' |
|
||||
version: '6.0.106' |
|
||||
``` |
|
||||
|
|
||||
 |
|
||||
|
|
||||
* Select **Settings** on the second task(Nugetcommand@2) in the pipeline |
|
||||
|
|
||||
* Select **Feeds in my Nuget.config** and type **Nuget.config** in the text box |
|
||||
|
|
||||
 |
|
||||
|
|
||||
* Add the below commands block to the end of the pipeline |
|
||||
|
|
||||
``` |
|
||||
- task: PublishBuildArtifacts@1 |
|
||||
displayName: 'Publish Artifact' |
|
||||
inputs: |
|
||||
PathtoPublish: '$(build.artifactstagingdirectory)' |
|
||||
ArtifactName: '$(Parameters.ArtifactName)' |
|
||||
condition: succeededOrFailed() |
|
||||
``` |
|
||||
 |
|
||||
|
|
||||
``` |
|
||||
# ASP.NET |
|
||||
# Build and test ASP.NET projects. |
|
||||
# Add steps that publish symbols, save build artifacts, deploy, and more: |
|
||||
# https://docs.microsoft.com/azure/devops/pipelines/apps/aspnet/build-aspnet-4 |
|
||||
|
|
||||
trigger: |
|
||||
- main |
|
||||
|
|
||||
pool: |
|
||||
vmImage: 'windows-latest' |
|
||||
|
|
||||
variables: |
|
||||
solution: '**/*.sln' |
|
||||
buildPlatform: 'Any CPU' |
|
||||
buildConfiguration: 'Release' |
|
||||
|
|
||||
steps: |
|
||||
- task: UseDotNet@2 |
|
||||
inputs: |
|
||||
packageType: 'sdk' |
|
||||
version: '6.0.106' |
|
||||
|
|
||||
- task: NuGetToolInstaller@1 |
|
||||
|
|
||||
- task: NuGetCommand@2 |
|
||||
inputs: |
|
||||
command: 'restore' |
|
||||
restoreSolution: '$(solution)' |
|
||||
feedsToUse: 'config' |
|
||||
nugetConfigPath: 'NuGet.config' |
|
||||
|
|
||||
- task: VSBuild@1 |
|
||||
inputs: |
|
||||
solution: '$(solution)' |
|
||||
msbuildArgs: '/p:DeployOnBuild=true /p:WebPublishMethod=Package /p:PackageAsSingleFile=true /p:SkipInvalidConfigurations=true /p:PackageLocation="$(build.artifactStagingDirectory)"' |
|
||||
platform: '$(buildPlatform)' |
|
||||
configuration: '$(buildConfiguration)' |
|
||||
|
|
||||
- task: VSTest@2 |
|
||||
inputs: |
|
||||
platform: '$(buildPlatform)' |
|
||||
configuration: '$(buildConfiguration)' |
|
||||
|
|
||||
- task: PublishBuildArtifacts@1 |
|
||||
displayName: 'Publish Artifact' |
|
||||
inputs: |
|
||||
PathtoPublish: '$(build.artifactstagingdirectory)' |
|
||||
ArtifactName: '$(Parameters.ArtifactName)' |
|
||||
publishLocation: 'Container' |
|
||||
condition: succeededOrFailed() |
|
||||
``` |
|
||||
|
|
||||
* Click **Save & queue** in the top menu. Click **Save & queue** again and click **Save and run** to run the Build pipeline |
|
||||
|
|
||||
* When the Build pipeline has finished. Click **1 published; 1 consumed** |
|
||||
|
|
||||
|
|
||||
|
|
||||
## Create a Web App in the Azure Portal to deploy [YourAppName].HttpApi.Host project |
|
||||
|
|
||||
* Search for Web App in the *Search the Marketplace* field |
|
||||
|
|
||||
* Click the **Create** button in the Web App window |
|
||||
|
|
||||
* Select rg[YourAppName] in the *Resource Group* dropdown |
|
||||
|
|
||||
* Enter [YourAppName]API in the *Name input* field |
|
||||
|
|
||||
* Select code, .NET Core 3.1 (LTS) and windows as *Operating System* |
|
||||
|
|
||||
* Enter [YourAppName]API in the *Name input* field |
|
||||
|
|
||||
* Select .NET Core 3.1 (LTS) in the *Runtime stack* dropdown |
|
||||
|
|
||||
* Select Windows as *Operating System* |
|
||||
|
|
||||
* Select the same *Region* as in the SQL server you created in Part 3 |
|
||||
|
|
||||
 |
|
||||
|
|
||||
* Click **Create new** in the Windows Plan. Name it [YourAppName]ApiWinPlan |
|
||||
|
|
||||
* Click **Change size** in Sku and size. Go to the Dev/Test Free F1 version and click the **Apply** button |
|
||||
|
|
||||
 |
|
||||
|
|
||||
* Click the **Review + create** button. Click the **Create** button |
|
||||
|
|
||||
* Click **Go to resource** when the Web App has been created |
|
||||
|
|
||||
|
|
||||
|
|
||||
## Create a Release pipeline in the AzureDevops and deploy [YourAppName].HttpApi.Host project |
|
||||
|
|
||||
* Sign in into [Azure DevOps](https://azure.microsoft.com/en-us/services/devops/) |
|
||||
|
|
||||
* Click [YourAppName]Proj and click **Releases** in the *Pipelines* menu |
|
||||
|
|
||||
* Click the **New pipeline** button in the *No release pipelines found* window |
|
||||
|
|
||||
* Select *Azure App Service deployment* and click the **Apply** button |
|
||||
|
|
||||
 |
|
||||
|
|
||||
* Enter *[YourAppName]staging* in the *Stage name* field in the *Stage* window. And close the window |
|
||||
|
|
||||
* Click **+ Add an artifact** in the *Pipeline* tab |
|
||||
|
|
||||
* Select the **Build** icon as *Source type* in the *Add an artifact* window |
|
||||
|
|
||||
* Select Build pipeline in the *Source (build pipeline)* dropdown and click the **Add** button |
|
||||
|
|
||||
 |
|
||||
|
|
||||
* Click the **Continuous deployment trigger (thunderbolt icon)** |
|
||||
|
|
||||
* Set the toggle to **Enabled** in the the *Continuous deployment trigger* window |
|
||||
|
|
||||
* Click **+ Add** in *No filters added*. Select **Include** in the *Type* dropdown. Select your branch in the *Build branch* dropdown and close the window |
|
||||
|
|
||||
 |
|
||||
|
|
||||
* Click **the little red circle with the exclamation mark** in the *Tasks* tab menu |
|
||||
|
|
||||
* Select your subscription in the *Azure subscription* dropdown. |
|
||||
|
|
||||
 |
|
||||
|
|
||||
* Click **Authorize** and enter your credentials in the next screens |
|
||||
|
|
||||
* After Authorization, select the **[YourAppName]API** in the *App service name* dropdown |
|
||||
|
|
||||
* Click the **Deploy Azure App Service** task |
|
||||
|
|
||||
* Select **[YourAppName].HttpApi.Host.zip** in the *Package or folder* input field |
|
||||
|
|
||||
 |
|
||||
|
|
||||
* Click the **Save** icon in the top menu and click **OK** |
|
||||
|
|
||||
* Click **Create release** in the top menu. Click **Create** to create a release |
|
||||
|
|
||||
* Click the *Pipeline* tab and wait until the Deployment succeeds |
|
||||
|
|
||||
 |
|
||||
|
|
||||
* Open a browser and navigate to the URL of your Web App |
|
||||
|
|
||||
``` |
|
||||
https://[YourAppName]api.azurewebsites.net |
|
||||
``` |
|
||||
|
|
||||
 |
|
||||
|
|
||||
|
|
||||
|
|
||||
## Create a Web App in Azure Portal to deploy [YourAppName].Blazor project |
|
||||
|
|
||||
* Login into [Azure Portal](https://portal.azure.com/) |
|
||||
|
|
||||
* Click **Create a resource** |
|
||||
|
|
||||
* Search for *Web App* in the *Search the Marketplace* field |
|
||||
|
|
||||
* Click the **Create** button in the *Web App* window |
|
||||
|
|
||||
* Select *rg[YourAppName]* in the *Resource Group* dropdown |
|
||||
|
|
||||
* Enter *[YourAppName]Blazor* in the *Name* input field |
|
||||
|
|
||||
* Select *.NET Core 3.1 (LTS)* in the *Runtime stack* dropdown |
|
||||
|
|
||||
* Select *Windows* as *Operating System* |
|
||||
|
|
||||
* Select the same region as the SQL server you created in Part 3 |
|
||||
|
|
||||
* Select the [YourAppName]ApiWinPlan in the *Windows Plan* dropdown |
|
||||
|
|
||||
 |
|
||||
|
|
||||
* Click the **Review + create** button. Click **Create** button |
|
||||
|
|
||||
* Click **Go to resource** when the Web App has been created |
|
||||
|
|
||||
* Copy the URL of the Blazor Web App for later use |
|
||||
|
|
||||
``` |
|
||||
https://[YourAppName]blazor.azurewebsites.net |
|
||||
``` |
|
||||
|
|
||||
|
|
||||
## Change the Web App configuration for the Azure App Service |
|
||||
|
|
||||
Copy the URL of the Api Host and Blazor Web App. Change appsettings.json files in the Web App as follows images. |
|
||||
|
|
||||
 |
|
||||
|
|
||||
 |
|
||||
|
|
||||
 |
|
||||
|
|
||||
|
|
||||
|
|
||||
## Add an extra Stage in the Release pipeline in the AzureDevops to deploy [YourAppName].Blazor project |
|
||||
|
|
||||
* Go to the *Release* pipeline in [Azure DevOps](https://azure.microsoft.com/en-us/services/devops/) and click **Edit** |
|
||||
|
|
||||
* Click the **+ Add** link and add a **New Stage** |
|
||||
|
|
||||
 |
|
||||
|
|
||||
* Select *Azure App Service deployment* and click the **Apply** button |
|
||||
|
|
||||
* Enter *BlazorDeployment* in the *Stage name* input field and close the *Stage* window |
|
||||
|
|
||||
* Click the **little red circle with the exclamation mark** in the BlazorDeployment stage |
|
||||
|
|
||||
* Select your subscription in the *Azure subscription* dropdown |
|
||||
|
|
||||
* Select your Blazor Web App in the *App service name* dropdown |
|
||||
|
|
||||
* Click the **Deploy Azure App Service task** |
|
||||
|
|
||||
* Select *[YourAppName].Blazor.zip* in the *Package or folder* input field |
|
||||
|
|
||||
 |
|
||||
|
|
||||
* Click **Save** in the top menu and click the **OK** button after |
|
||||
|
|
||||
* Click **Create release** in the top menu and click the **Create** button |
|
||||
|
|
||||
 |
|
||||
|
|
||||
 |
|
||||
@ -0,0 +1,20 @@ |
|||||
|
# Angular UI: Current User |
||||
|
|
||||
|
The current user information stored in Config State. |
||||
|
|
||||
|
### How to Get a Current User Information Configuration |
||||
|
|
||||
|
You can use the `getOne` or `getOne$` method of `ConfigStateService` to get a specific configuration property. For that, the property name should be passed to the method as parameter. |
||||
|
|
||||
|
```js |
||||
|
// this.config is an instance of ConfigStateService |
||||
|
|
||||
|
const currentUser = this.config.getOne("currentUser"); |
||||
|
|
||||
|
// or |
||||
|
this.config.getOne$("currentUser").subscribe(currentUser => { |
||||
|
// use currentUser here |
||||
|
}) |
||||
|
``` |
||||
|
|
||||
|
> See the [ConfigStateService](./Config-State-Service) for more information. |
||||
|
Before Width: | Height: | Size: 252 KiB |
|
Before Width: | Height: | Size: 220 KiB |
|
Before Width: | Height: | Size: 487 KiB |
|
Before Width: | Height: | Size: 139 KiB |
|
Before Width: | Height: | Size: 368 KiB |
|
Before Width: | Height: | Size: 281 KiB |
|
Before Width: | Height: | Size: 115 KiB |
|
Before Width: | Height: | Size: 418 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 291 KiB |
|
Before Width: | Height: | Size: 491 KiB |
|
Before Width: | Height: | Size: 205 KiB |
|
Before Width: | Height: | Size: 539 KiB |
|
Before Width: | Height: | Size: 415 KiB |
|
Before Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 237 KiB |
|
Before Width: | Height: | Size: 352 KiB |
|
Before Width: | Height: | Size: 400 KiB |
|
Before Width: | Height: | Size: 295 KiB |
|
Before Width: | Height: | Size: 219 KiB |
|
Before Width: | Height: | Size: 288 KiB |
|
Before Width: | Height: | Size: 309 KiB |
|
Before Width: | Height: | Size: 304 KiB |
|
After Width: | Height: | Size: 234 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 24 KiB |
@ -1,3 +1,182 @@ |
|||||
## Validation |
# Validação |
||||
|
|
||||
Façam |
O sistema de validação é utilizado para validar a entrada do usuário ou a requisição do cliente para uma ação de um controller ou por um serviço. |
||||
|
|
||||
|
O ABP é compatível com o sistema de Validação de Modelos do ASP.NET Core e tudo escrito na [sua documentação](https://docs.microsoft.com/en-us/aspnet/core/mvc/models/validation) é válido para aplicações baseadas no ABP. Logo, esse documento foca nas funcionalidades do ABP ao invés de repetir a documentação da Microsoft. |
||||
|
|
||||
|
Além disso, o ABP adiciona os seguintes benefícios: |
||||
|
|
||||
|
* Define `IValidationEnabled` para adicionar validação automática para uma classe qualquer. Como todos os [serviços de aplicação](Application-Services.md) já o implementam, eles também são validados automaticamente. |
||||
|
* Automaticamente traduz os erros de validação para os atributos de anotação de dados. |
||||
|
* Provê serviços extensíveis para validar a chamada de um método ou o estado de um objeto. |
||||
|
* Provê integração com o [FluentValidation](https://fluentvalidation.net/) |
||||
|
|
||||
|
## Validando DTOs |
||||
|
|
||||
|
Essa seção introduz brevemente o sistema de validação. Para mais detalhes, veja a [Documentação da Validação de Modelo em ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/mvc/models/validation). |
||||
|
|
||||
|
### Atributos de anotação de dados |
||||
|
|
||||
|
Utilizar anotações de dados é uma maneira simples de implementar uma validação formal para um [DTO](Data-Transfer-Objects.md) de uma forma declarativa. Exemplo: |
||||
|
|
||||
|
````csharp |
||||
|
public class CreateBookDto |
||||
|
{ |
||||
|
[Required] |
||||
|
[StringLength(100)] |
||||
|
public string Name { get; set; } |
||||
|
|
||||
|
[Required] |
||||
|
[StringLength(1000)] |
||||
|
public string Description { get; set; } |
||||
|
|
||||
|
[Range(0, 999.99)] |
||||
|
public decimal Price { get; set; } |
||||
|
} |
||||
|
```` |
||||
|
Quando você utilizar essa classe como parâmetro para um [serviço da aplicação](Application-Services.md) ou um controller, ele será automaticamente validado e a validação traduzida será lançada ([e tratada](Exception-Handling.md) pelo ABP framework). |
||||
|
|
||||
|
### IValidatableObject |
||||
|
|
||||
|
`IValidatableObject` pode ser implementado por um DTO para executar uma lógica customizada de validação. O `CreateBookDto` no exemplo a seguir implementa essa interface e verifica se o `Name` é igual a `Description` e retorna um erro de validação nesse caso. |
||||
|
|
||||
|
````csharp |
||||
|
using System.Collections.Generic; |
||||
|
using System.ComponentModel.DataAnnotations; |
||||
|
|
||||
|
namespace Acme.BookStore |
||||
|
{ |
||||
|
public class CreateBookDto : IValidatableObject |
||||
|
{ |
||||
|
[Required] |
||||
|
[StringLength(100)] |
||||
|
public string Name { get; set; } |
||||
|
|
||||
|
[Required] |
||||
|
[StringLength(1000)] |
||||
|
public string Description { get; set; } |
||||
|
|
||||
|
[Range(0, 999.99)] |
||||
|
public decimal Price { get; set; } |
||||
|
|
||||
|
public IEnumerable<ValidationResult> Validate( |
||||
|
ValidationContext validationContext) |
||||
|
{ |
||||
|
if (Name == Description) |
||||
|
{ |
||||
|
yield return new ValidationResult( |
||||
|
"Name and Description can not be the same!", |
||||
|
new[] { "Name", "Description" } |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
#### Resolvendo um serviço. |
||||
|
|
||||
|
Se você precisar resolver um serviço do [sistema de injeção de dependências](Dependency-Injection.md), você pode utilizar o objeto `ValidationContext`. |
||||
|
|
||||
|
````csharp |
||||
|
var myService = validationContext.GetRequiredService<IMyService>(); |
||||
|
```` |
||||
|
|
||||
|
> Enquanto resolver os serviços no método `Validate` permite várias possibilidades, não é um boa prática implementar sua lógica de validação do domínio nos DTOs. Mantenha os DTOs simples. Seu propósito é transferir dados (DTO: Data Transfer Object, ou Objeto de Transferência de Dados). |
||||
|
|
||||
|
## Infraestrutura de Validação. |
||||
|
|
||||
|
Essa seção explica alguns serviços adicionais fornecidos pelo ABP Framework. |
||||
|
|
||||
|
### Interface IValidationEnabled |
||||
|
|
||||
|
`IValidationEnabled` é um marcador vazio de interface que pode ser implementado por qualquer classe (registrada e resolvida a partir do [DI](Dependency-Injection.md)) para permitir que o ABP framework realize o sistema de validação para os métodos da classe. Por exemplo: |
||||
|
|
||||
|
````csharp |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Validation; |
||||
|
|
||||
|
namespace Acme.BookStore |
||||
|
{ |
||||
|
public class MyService : ITransientDependency, IValidationEnabled |
||||
|
{ |
||||
|
public virtual async Task DoItAsync(MyInput input) |
||||
|
{ |
||||
|
//... |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
> O ABP framework utiliza o sistema de [Proxying Dinâmico / Interceptadores](Dynamic-Proxying-Interceptors.md) para realizar a validação. Para fazê-lo funcionar, seu método deve ser **virtual** ou seu serviço deve ser injetado e utilizado através de uma **interface** (como `IMyService`). |
||||
|
|
||||
|
#### Habilitando e Desabilitando Validações |
||||
|
|
||||
|
Você pode utilizar o `[DisableValidation]` e desabilitar a validação para métodos, classes e propriedades. |
||||
|
|
||||
|
````csharp |
||||
|
[DisableValidation] |
||||
|
public Void MyMethod() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
[DisableValidation] |
||||
|
public class InputClass |
||||
|
{ |
||||
|
public string MyProperty { get; set; } |
||||
|
} |
||||
|
|
||||
|
public class InputClass |
||||
|
{ |
||||
|
[DisableValidation] |
||||
|
public string MyProperty { get; set; } |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
### AbpValidationException |
||||
|
|
||||
|
Uma vez que o ABP determina um erro de validação, é lançada uma validação do tipo `AbpValidationException`. O código da sua aplicação poderá lançar o `AbpValidationException`, mas na maioria das vezes não será necessário. |
||||
|
|
||||
|
* A propriedade `ValidationErrors` do `AbpValidationException` contem a lista com os erros de validação. |
||||
|
* O nível de log do `AbpValidationException` é definido como `Warning`. Todos os erros de validação são logados no [Sistema de Logging](Logging.md). |
||||
|
* `AbpValidationException` é tratado automaticamente pelo ABP framework e é convertido em um erro utilizável com o código de status HTTP 400. Veja a documentação de [Manipulação de Exceção](Exception-Handling.md) para mais informações. |
||||
|
|
||||
|
## Tópicos Avançados |
||||
|
|
||||
|
### IObjectValidator |
||||
|
|
||||
|
Além da validação automática, você pode querer validar um objeto manualmente. Nesse caso, [injete](Dependency-Injection.md) e use o serviço `IObjectValidator`: |
||||
|
|
||||
|
* O método `ValidateAsync` valida o objeto informado baseado nas regras de validação e lança uma `AbpValidationException` se não estiver em um estado válido. |
||||
|
|
||||
|
* `GetErrorsAsync` não lança uma exceção, somente retorna os erros de validação. |
||||
|
|
||||
|
`IObjectValidator` é implementado pelo `ObjectValidator` por padrão. `ObjectValidator` é extensível; você pode implementar a interface `IObjectValidationContributor` para contribuir com uma lógica customizada. Exemplo: |
||||
|
|
||||
|
````csharp |
||||
|
public class MyObjectValidationContributor |
||||
|
: IObjectValidationContributor, ITransientDependency |
||||
|
{ |
||||
|
public Task AddErrorsAsync(ObjectValidationContext context) |
||||
|
{ |
||||
|
//Get the validating object |
||||
|
var obj = context.ValidatingObject; |
||||
|
|
||||
|
//Add the validation errors if available |
||||
|
context.Errors.Add(...); |
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
* Lembre-se de registrar sua classe no [DI](Dependency-Injection.md) (implementar `ITransientDependency` faz isso no exemplo anterior) |
||||
|
* ABP vai automaticamente descobrir sua classe e utilizá-la em qualquer tipo de validação de objetos (incluindo chamadas de métodos de validação automáticas). |
||||
|
|
||||
|
### IMethodInvocationValidator |
||||
|
|
||||
|
`IMethodInvocationValidator` é utilizado para validar a chamada de um método. Ele utiliza internamente o `IObjectValidator` para validar os objetos passados na chamada do método. Você normalmente não precisa deste serviço, já que ele é utilizado automaticamente pelo framework, mas você pode querer reutilizar ou substituir na sua aplicação em alguns casos raros. |
||||
|
|
||||
|
## Integração com FluentValidation |
||||
|
|
||||
|
O pacote Volo.Abp.FluentValidation integra a biblioteca FluentValidation com o sistema de validação (implementando o `IObjectValidationContributor`). Veja o [documento de Integração com o FluentValidation](FluentValidation.md) para mais informações. |
||||
|
|||||
@ -0,0 +1,3 @@ |
|||||
|
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
||||
|
<ConfigureAwait ContinueOnCapturedContext="false" /> |
||||
|
</Weavers> |
||||
@ -0,0 +1,30 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
||||
|
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
||||
|
<xs:element name="Weavers"> |
||||
|
<xs:complexType> |
||||
|
<xs:all> |
||||
|
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
||||
|
<xs:complexType> |
||||
|
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:all> |
||||
|
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:schema> |
||||
@ -0,0 +1,18 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net6.0</TargetFramework> |
||||
|
<ImplicitUsings>enable</ImplicitUsings> |
||||
|
<Nullable>enable</Nullable> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\Volo.Abp.AspNetCore.Mvc.Dapr\Volo.Abp.AspNetCore.Mvc.Dapr.csproj" /> |
||||
|
<ProjectReference Include="..\Volo.Abp.EventBus.Dapr\Volo.Abp.EventBus.Dapr.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,29 @@ |
|||||
|
using Microsoft.AspNetCore.Http.Json; |
||||
|
using Volo.Abp.AspNetCore.Mvc.Dapr.EventBus.SystemTextJson; |
||||
|
using Volo.Abp.EventBus.Dapr; |
||||
|
using Volo.Abp.Json.SystemTextJson; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace Volo.Abp.AspNetCore.Mvc.Dapr.EventBus; |
||||
|
|
||||
|
[DependsOn( |
||||
|
typeof(AbpAspNetCoreMvcDaprModule), |
||||
|
typeof(AbpEventBusDaprModule) |
||||
|
)] |
||||
|
public class AbpAspNetCoreMvcDaprEventBusModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
// TODO: Add NewtonsoftJson json converter.
|
||||
|
|
||||
|
Configure<JsonOptions>(options => |
||||
|
{ |
||||
|
options.SerializerOptions.Converters.Add(new AbpAspNetCoreMvcDaprSubscriptionDefinitionConverter()); |
||||
|
}); |
||||
|
|
||||
|
Configure<AbpSystemTextJsonSerializerOptions>(options => |
||||
|
{ |
||||
|
options.JsonSerializerOptions.Converters.Add(new AbpAspNetCoreMvcDaprSubscriptionDefinitionConverter()); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
namespace Volo.Abp.AspNetCore.Mvc.Dapr.EventBus; |
||||
|
|
||||
|
public class AbpAspNetCoreMvcDaprEventBusOptions |
||||
|
{ |
||||
|
public List<IAbpAspNetCoreMvcDaprPubSubProviderContributor> Contributors { get; } |
||||
|
|
||||
|
public AbpAspNetCoreMvcDaprEventBusOptions() |
||||
|
{ |
||||
|
Contributors = new List<IAbpAspNetCoreMvcDaprPubSubProviderContributor>(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
namespace Volo.Abp.AspNetCore.Mvc.Dapr.EventBus; |
||||
|
|
||||
|
public class AbpAspNetCoreMvcDaprPubSubConsts |
||||
|
{ |
||||
|
public const string DaprSubscribeUrl = "dapr/subscribe"; |
||||
|
|
||||
|
public const string DaprEventCallbackUrl = "api/abp/dapr/event"; |
||||
|
} |
||||
@ -0,0 +1,63 @@ |
|||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using Volo.Abp.AspNetCore.Mvc.Dapr.EventBus.Models; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.EventBus; |
||||
|
using Volo.Abp.EventBus.Dapr; |
||||
|
using Volo.Abp.EventBus.Distributed; |
||||
|
|
||||
|
namespace Volo.Abp.AspNetCore.Mvc.Dapr.EventBus; |
||||
|
|
||||
|
public class AbpAspNetCoreMvcDaprPubSubProvider : ITransientDependency |
||||
|
{ |
||||
|
protected IServiceProvider ServiceProvider { get; } |
||||
|
protected AbpAspNetCoreMvcDaprEventBusOptions AspNetCoreMvcDaprEventBusOptions { get; } |
||||
|
protected AbpDaprEventBusOptions DaprEventBusOptions { get; } |
||||
|
protected AbpDistributedEventBusOptions DistributedEventBusOptions { get; } |
||||
|
|
||||
|
public AbpAspNetCoreMvcDaprPubSubProvider( |
||||
|
IServiceProvider serviceProvider, |
||||
|
IOptions<AbpAspNetCoreMvcDaprEventBusOptions> aspNetCoreDaprEventBusOptions, |
||||
|
IOptions<AbpDaprEventBusOptions> daprEventBusOptions, |
||||
|
IOptions<AbpDistributedEventBusOptions> distributedEventBusOptions) |
||||
|
{ |
||||
|
ServiceProvider = serviceProvider; |
||||
|
AspNetCoreMvcDaprEventBusOptions = aspNetCoreDaprEventBusOptions.Value; |
||||
|
DaprEventBusOptions = daprEventBusOptions.Value; |
||||
|
DistributedEventBusOptions = distributedEventBusOptions.Value; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<List<AbpAspNetCoreMvcDaprSubscriptionDefinition>> GetSubscriptionsAsync() |
||||
|
{ |
||||
|
var subscriptions = new List<AbpAspNetCoreMvcDaprSubscriptionDefinition>(); |
||||
|
foreach (var handler in DistributedEventBusOptions.Handlers) |
||||
|
{ |
||||
|
foreach (var @interface in handler.GetInterfaces().Where(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IDistributedEventHandler<>))) |
||||
|
{ |
||||
|
var eventType = @interface.GetGenericArguments()[0]; |
||||
|
var eventName = EventNameAttribute.GetNameOrDefault(eventType); |
||||
|
|
||||
|
subscriptions.Add(new AbpAspNetCoreMvcDaprSubscriptionDefinition() |
||||
|
{ |
||||
|
PubSubName = DaprEventBusOptions.PubSubName, |
||||
|
Topic = eventName, |
||||
|
Route = AbpAspNetCoreMvcDaprPubSubConsts.DaprEventCallbackUrl |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (AspNetCoreMvcDaprEventBusOptions.Contributors.Any()) |
||||
|
{ |
||||
|
using (var scope = ServiceProvider.CreateScope()) |
||||
|
{ |
||||
|
var context = new AbpAspNetCoreMvcDaprPubSubProviderContributorContext(scope.ServiceProvider, subscriptions); |
||||
|
foreach (var contributor in AspNetCoreMvcDaprEventBusOptions.Contributors) |
||||
|
{ |
||||
|
await contributor.ContributeAsync(context); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return subscriptions; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
using Volo.Abp.AspNetCore.Mvc.Dapr.EventBus.Models; |
||||
|
|
||||
|
namespace Volo.Abp.AspNetCore.Mvc.Dapr.EventBus; |
||||
|
|
||||
|
public class AbpAspNetCoreMvcDaprPubSubProviderContributorContext |
||||
|
{ |
||||
|
public IServiceProvider ServiceProvider { get; } |
||||
|
|
||||
|
public List<AbpAspNetCoreMvcDaprSubscriptionDefinition> Subscriptions { get; } |
||||
|
|
||||
|
public AbpAspNetCoreMvcDaprPubSubProviderContributorContext(IServiceProvider serviceProvider, List<AbpAspNetCoreMvcDaprSubscriptionDefinition> daprSubscriptionModels) |
||||
|
{ |
||||
|
ServiceProvider = serviceProvider; |
||||
|
Subscriptions = daprSubscriptionModels; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,36 @@ |
|||||
|
using System.Text.Json; |
||||
|
using Microsoft.AspNetCore.Mvc; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using Volo.Abp.AspNetCore.Mvc.Dapr.EventBus.Models; |
||||
|
using Volo.Abp.Dapr; |
||||
|
using Volo.Abp.EventBus.Dapr; |
||||
|
|
||||
|
namespace Volo.Abp.AspNetCore.Mvc.Dapr.EventBus.Controllers; |
||||
|
|
||||
|
[Area("abp")] |
||||
|
[RemoteService(Name = "abp")] |
||||
|
public class AbpAspNetCoreMvcDaprPubSubController : AbpController |
||||
|
{ |
||||
|
[HttpGet(AbpAspNetCoreMvcDaprPubSubConsts.DaprSubscribeUrl)] |
||||
|
public virtual async Task<List<AbpAspNetCoreMvcDaprSubscriptionDefinition>> SubscribeAsync() |
||||
|
{ |
||||
|
return await HttpContext.RequestServices.GetRequiredService<AbpAspNetCoreMvcDaprPubSubProvider>().GetSubscriptionsAsync(); |
||||
|
} |
||||
|
|
||||
|
[HttpPost(AbpAspNetCoreMvcDaprPubSubConsts.DaprEventCallbackUrl)] |
||||
|
public virtual async Task<IActionResult> EventsAsync() |
||||
|
{ |
||||
|
var bodyJsonDocument = await JsonDocument.ParseAsync(HttpContext.Request.Body); |
||||
|
var request = JsonSerializer.Deserialize<AbpAspNetCoreMvcDaprSubscriptionRequest>(bodyJsonDocument.RootElement.GetRawText(), |
||||
|
HttpContext.RequestServices.GetRequiredService<IOptions<JsonOptions>>().Value.JsonSerializerOptions); |
||||
|
|
||||
|
var distributedEventBus = HttpContext.RequestServices.GetRequiredService<DaprDistributedEventBus>(); |
||||
|
var daprSerializer = HttpContext.RequestServices.GetRequiredService<IDaprSerializer>(); |
||||
|
|
||||
|
var eventData = daprSerializer.Deserialize(bodyJsonDocument.RootElement.GetProperty("data").GetRawText(), distributedEventBus.GetEventType(request.Topic)); |
||||
|
await distributedEventBus.TriggerHandlersAsync(distributedEventBus.GetEventType(request.Topic), eventData); |
||||
|
|
||||
|
return Ok(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,6 @@ |
|||||
|
namespace Volo.Abp.AspNetCore.Mvc.Dapr.EventBus; |
||||
|
|
||||
|
public interface IAbpAspNetCoreMvcDaprPubSubProviderContributor |
||||
|
{ |
||||
|
Task ContributeAsync(AbpAspNetCoreMvcDaprPubSubProviderContributorContext context); |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
namespace Volo.Abp.AspNetCore.Mvc.Dapr.EventBus.Models; |
||||
|
|
||||
|
public class AbpAspNetCoreMvcDaprSubscriptionDefinition |
||||
|
{ |
||||
|
public string PubSubName { get; set; } |
||||
|
|
||||
|
public string Topic { get; set; } |
||||
|
|
||||
|
public string Route { get; set; } |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
namespace Volo.Abp.AspNetCore.Mvc.Dapr.EventBus.Models; |
||||
|
|
||||
|
public class AbpAspNetCoreMvcDaprSubscriptionRequest |
||||
|
{ |
||||
|
public string PubSubName { get; set; } |
||||
|
|
||||
|
public string Topic { get; set; } |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
using System.Text.Json; |
||||
|
|
||||
|
namespace Volo.Abp.AspNetCore.Mvc.Dapr.EventBus.SystemTextJson; |
||||
|
|
||||
|
public class AbpAspNetCoreMvcDaprPubSubJsonNamingPolicy : JsonNamingPolicy |
||||
|
{ |
||||
|
public override string ConvertName(string name) |
||||
|
{ |
||||
|
return name.ToLower(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
using System.Text.Json; |
||||
|
using System.Text.Json.Serialization; |
||||
|
using Volo.Abp.AspNetCore.Mvc.Dapr.EventBus.Models; |
||||
|
|
||||
|
namespace Volo.Abp.AspNetCore.Mvc.Dapr.EventBus.SystemTextJson; |
||||
|
|
||||
|
public class AbpAspNetCoreMvcDaprSubscriptionDefinitionConverter : JsonConverter<AbpAspNetCoreMvcDaprSubscriptionDefinition> |
||||
|
{ |
||||
|
private JsonSerializerOptions _writeJsonSerializerOptions; |
||||
|
|
||||
|
public override AbpAspNetCoreMvcDaprSubscriptionDefinition Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) |
||||
|
{ |
||||
|
throw new NotSupportedException(); |
||||
|
} |
||||
|
|
||||
|
public override void Write(Utf8JsonWriter writer, AbpAspNetCoreMvcDaprSubscriptionDefinition value, JsonSerializerOptions options) |
||||
|
{ |
||||
|
_writeJsonSerializerOptions ??= JsonSerializerOptionsHelper.Create(new JsonSerializerOptions(options) |
||||
|
{ |
||||
|
PropertyNamingPolicy = new AbpAspNetCoreMvcDaprPubSubJsonNamingPolicy() |
||||
|
}, x => x == this); |
||||
|
|
||||
|
JsonSerializer.Serialize(writer, value, _writeJsonSerializerOptions); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,3 @@ |
|||||
|
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
||||
|
<ConfigureAwait ContinueOnCapturedContext="false" /> |
||||
|
</Weavers> |
||||
@ -0,0 +1,30 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
||||
|
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
||||
|
<xs:element name="Weavers"> |
||||
|
<xs:complexType> |
||||
|
<xs:all> |
||||
|
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
||||
|
<xs:complexType> |
||||
|
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:all> |
||||
|
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:schema> |
||||
@ -0,0 +1,22 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net6.0</TargetFramework> |
||||
|
<ImplicitUsings>enable</ImplicitUsings> |
||||
|
<Nullable>enable</Nullable> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\Volo.Abp.AspNetCore.Mvc\Volo.Abp.AspNetCore.Mvc.csproj" /> |
||||
|
<ProjectReference Include="..\Volo.Abp.Dapr\Volo.Abp.Dapr.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Dapr.AspNetCore" Version="1.8.0" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,13 @@ |
|||||
|
using Volo.Abp.Dapr; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace Volo.Abp.AspNetCore.Mvc.Dapr; |
||||
|
|
||||
|
[DependsOn( |
||||
|
typeof(AbpAspNetCoreMvcModule), |
||||
|
typeof(AbpDaprModule) |
||||
|
)] |
||||
|
public class AbpAspNetCoreMvcDaprModule : AbpModule |
||||
|
{ |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using System.Reflection; |
||||
|
using Autofac.Core; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace Volo.Abp.Autofac; |
||||
|
|
||||
|
public class AbpPropertySelector : DefaultPropertySelector |
||||
|
{ |
||||
|
public AbpPropertySelector(bool preserveSetValues) |
||||
|
: base(preserveSetValues) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public override bool InjectProperty(PropertyInfo propertyInfo, object instance) |
||||
|
{ |
||||
|
return propertyInfo.GetCustomAttributes(typeof(DisablePropertyInjectionAttribute), true).IsNullOrEmpty() && |
||||
|
base.InjectProperty(propertyInfo, instance); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property)] |
||||
|
public class DisablePropertyInjectionAttribute : Attribute |
||||
|
{ |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,3 @@ |
|||||
|
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
||||
|
<ConfigureAwait ContinueOnCapturedContext="false" /> |
||||
|
</Weavers> |
||||
@ -0,0 +1,30 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
||||
|
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
||||
|
<xs:element name="Weavers"> |
||||
|
<xs:complexType> |
||||
|
<xs:all> |
||||
|
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
||||
|
<xs:complexType> |
||||
|
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:all> |
||||
|
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:schema> |
||||
@ -0,0 +1,21 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net6.0</TargetFramework> |
||||
|
<ImplicitUsings>enable</ImplicitUsings> |
||||
|
<Nullable>enable</Nullable> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\Volo.Abp.Json\Volo.Abp.Json.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Dapr.Client" Version="1.8.0" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,48 @@ |
|||||
|
using System.Collections.Concurrent; |
||||
|
using System.Text.Json; |
||||
|
using Dapr.Client; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Json.SystemTextJson; |
||||
|
|
||||
|
namespace Volo.Abp.Dapr; |
||||
|
|
||||
|
public class AbpDaprClientFactory : ITransientDependency |
||||
|
{ |
||||
|
protected AbpDaprOptions Options { get; } |
||||
|
protected AbpSystemTextJsonSerializerOptions SystemTextJsonSerializerOptions { get; } |
||||
|
|
||||
|
public AbpDaprClientFactory( |
||||
|
IOptions<AbpDaprOptions> options, |
||||
|
IOptions<AbpSystemTextJsonSerializerOptions> systemTextJsonSerializerOptions) |
||||
|
{ |
||||
|
Options = options.Value; |
||||
|
SystemTextJsonSerializerOptions = systemTextJsonSerializerOptions.Value; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<DaprClient> CreateAsync() |
||||
|
{ |
||||
|
var builder = new DaprClientBuilder() |
||||
|
.UseJsonSerializationOptions(await CreateJsonSerializerOptions()); |
||||
|
|
||||
|
if (!Options.HttpEndpoint.IsNullOrWhiteSpace()) |
||||
|
{ |
||||
|
builder.UseHttpEndpoint(Options.HttpEndpoint); |
||||
|
} |
||||
|
|
||||
|
if (!Options.GrpcEndpoint.IsNullOrWhiteSpace()) |
||||
|
{ |
||||
|
builder.UseGrpcEndpoint(Options.GrpcEndpoint); |
||||
|
} |
||||
|
|
||||
|
return builder.Build(); |
||||
|
} |
||||
|
|
||||
|
private readonly static ConcurrentDictionary<string, JsonSerializerOptions> JsonSerializerOptionsCache = new ConcurrentDictionary<string, JsonSerializerOptions>(); |
||||
|
|
||||
|
protected virtual Task<JsonSerializerOptions> CreateJsonSerializerOptions() |
||||
|
{ |
||||
|
return Task.FromResult(JsonSerializerOptionsCache.GetOrAdd(nameof(AbpDaprClientFactory), |
||||
|
_ => new JsonSerializerOptions(SystemTextJsonSerializerOptions.JsonSerializerOptions))); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Volo.Abp.Json; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace Volo.Abp.Dapr; |
||||
|
|
||||
|
[DependsOn(typeof(AbpJsonModule))] |
||||
|
public class AbpDaprModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
var configuration = context.Services.GetConfiguration(); |
||||
|
Configure<AbpDaprOptions>(configuration.GetSection("Dapr")); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
namespace Volo.Abp.Dapr; |
||||
|
|
||||
|
public class AbpDaprOptions |
||||
|
{ |
||||
|
public string AppId { get; set; } |
||||
|
|
||||
|
public string HttpEndpoint { get; set; } |
||||
|
|
||||
|
public string GrpcEndpoint { get; set; } |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
namespace Volo.Abp.Dapr; |
||||
|
|
||||
|
public interface IDaprSerializer |
||||
|
{ |
||||
|
byte[] Serialize(object obj); |
||||
|
|
||||
|
object Deserialize(byte[] value, Type type); |
||||
|
|
||||
|
T Deserialize<T>(byte[] value); |
||||
|
|
||||
|
string SerializeToString(object obj); |
||||
|
|
||||
|
object Deserialize(string value, Type type); |
||||
|
|
||||
|
T Deserialize<T>(string value); |
||||
|
} |
||||
@ -0,0 +1,45 @@ |
|||||
|
using System.Text; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Json; |
||||
|
|
||||
|
namespace Volo.Abp.Dapr; |
||||
|
|
||||
|
public class Utf8JsonDaprSerializer : IDaprSerializer, ITransientDependency |
||||
|
{ |
||||
|
private readonly IJsonSerializer _jsonSerializer; |
||||
|
|
||||
|
public Utf8JsonDaprSerializer(IJsonSerializer jsonSerializer) |
||||
|
{ |
||||
|
_jsonSerializer = jsonSerializer; |
||||
|
} |
||||
|
|
||||
|
public byte[] Serialize(object obj) |
||||
|
{ |
||||
|
return Encoding.UTF8.GetBytes(_jsonSerializer.Serialize(obj)); |
||||
|
} |
||||
|
|
||||
|
public object Deserialize(byte[] value, Type type) |
||||
|
{ |
||||
|
return _jsonSerializer.Deserialize(type, Encoding.UTF8.GetString(value)); |
||||
|
} |
||||
|
|
||||
|
public T Deserialize<T>(byte[] value) |
||||
|
{ |
||||
|
return _jsonSerializer.Deserialize<T>(Encoding.UTF8.GetString(value)); |
||||
|
} |
||||
|
|
||||
|
public string SerializeToString(object obj) |
||||
|
{ |
||||
|
return _jsonSerializer.Serialize(obj); |
||||
|
} |
||||
|
|
||||
|
public object Deserialize(string value, Type type) |
||||
|
{ |
||||
|
return _jsonSerializer.Deserialize(type, value); |
||||
|
} |
||||
|
|
||||
|
public T Deserialize<T>(string value) |
||||
|
{ |
||||
|
return _jsonSerializer.Deserialize<T>(value); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,3 @@ |
|||||
|
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
||||
|
<ConfigureAwait ContinueOnCapturedContext="false" /> |
||||
|
</Weavers> |
||||
@ -0,0 +1,30 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
||||
|
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
||||
|
<xs:element name="Weavers"> |
||||
|
<xs:complexType> |
||||
|
<xs:all> |
||||
|
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
||||
|
<xs:complexType> |
||||
|
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:all> |
||||
|
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:schema> |
||||
@ -0,0 +1,18 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net6.0</TargetFramework> |
||||
|
<ImplicitUsings>enable</ImplicitUsings> |
||||
|
<Nullable>enable</Nullable> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\Volo.Abp.Dapr\Volo.Abp.Dapr.csproj" /> |
||||
|
<ProjectReference Include="..\Volo.Abp.DistributedLocking.Abstractions\Volo.Abp.DistributedLocking.Abstractions.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,13 @@ |
|||||
|
namespace Volo.Abp.DistributedLocking.Dapr; |
||||
|
|
||||
|
public class AbpDistributedLockDaprOptions |
||||
|
{ |
||||
|
public string StoreName { get; set; } |
||||
|
|
||||
|
public TimeSpan DefaultTimeout { get; set; } |
||||
|
|
||||
|
public AbpDistributedLockDaprOptions() |
||||
|
{ |
||||
|
DefaultTimeout = TimeSpan.FromSeconds(30); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
using Volo.Abp.Dapr; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace Volo.Abp.DistributedLocking.Dapr; |
||||
|
|
||||
|
[DependsOn( |
||||
|
typeof(AbpDistributedLockingAbstractionsModule), |
||||
|
typeof(AbpDaprModule))] |
||||
|
public class AbpDistributedLockingDaprModule : AbpModule |
||||
|
{ |
||||
|
} |
||||
@ -0,0 +1,50 @@ |
|||||
|
using Microsoft.Extensions.Options; |
||||
|
using Volo.Abp.Dapr; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace Volo.Abp.DistributedLocking.Dapr; |
||||
|
|
||||
|
[Dependency(ReplaceServices = true)] |
||||
|
public class DaprAbpDistributedLock : IAbpDistributedLock, ITransientDependency |
||||
|
{ |
||||
|
protected AbpDaprClientFactory DaprClientFactory { get; } |
||||
|
protected AbpDistributedLockDaprOptions DistributedLockDaprOptions { get; } |
||||
|
protected AbpDaprOptions DaprOptions { get; } |
||||
|
|
||||
|
public DaprAbpDistributedLock( |
||||
|
AbpDaprClientFactory daprClientFactory, |
||||
|
IOptions<AbpDistributedLockDaprOptions> distributedLockDaprOptions, |
||||
|
IOptions<AbpDaprOptions> daprOptions) |
||||
|
{ |
||||
|
DaprClientFactory = daprClientFactory; |
||||
|
DaprOptions = daprOptions.Value; |
||||
|
DistributedLockDaprOptions = distributedLockDaprOptions.Value; |
||||
|
} |
||||
|
|
||||
|
public async Task<IAbpDistributedLockHandle> TryAcquireAsync( |
||||
|
string name, |
||||
|
TimeSpan timeout = default, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
if (timeout == default) |
||||
|
{ |
||||
|
timeout = DistributedLockDaprOptions.DefaultTimeout; |
||||
|
} |
||||
|
|
||||
|
var daprClient = await DaprClientFactory.CreateAsync(); |
||||
|
|
||||
|
var lockResponse = await daprClient.Lock( |
||||
|
DistributedLockDaprOptions.StoreName, |
||||
|
name, |
||||
|
DaprOptions.AppId, |
||||
|
(int)timeout.TotalSeconds, |
||||
|
cancellationToken); |
||||
|
|
||||
|
if (lockResponse == null || !lockResponse.Success) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
return new DaprAbpDistributedLockHandle(lockResponse); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
using Dapr.Client; |
||||
|
|
||||
|
namespace Volo.Abp.DistributedLocking.Dapr; |
||||
|
|
||||
|
public class DaprAbpDistributedLockHandle : IAbpDistributedLockHandle |
||||
|
{ |
||||
|
protected TryLockResponse LockResponse { get; } |
||||
|
|
||||
|
public DaprAbpDistributedLockHandle(TryLockResponse lockResponse) |
||||
|
{ |
||||
|
LockResponse = lockResponse; |
||||
|
} |
||||
|
|
||||
|
public async ValueTask DisposeAsync() |
||||
|
{ |
||||
|
await LockResponse.DisposeAsync(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,3 @@ |
|||||
|
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
||||
|
<ConfigureAwait ContinueOnCapturedContext="false" /> |
||||
|
</Weavers> |
||||
@ -0,0 +1,30 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
||||
|
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
||||
|
<xs:element name="Weavers"> |
||||
|
<xs:complexType> |
||||
|
<xs:all> |
||||
|
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
||||
|
<xs:complexType> |
||||
|
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:all> |
||||
|
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:schema> |
||||
@ -0,0 +1,18 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net6.0</TargetFramework> |
||||
|
<ImplicitUsings>enable</ImplicitUsings> |
||||
|
<Nullable>enable</Nullable> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\Volo.Abp.EventBus\Volo.Abp.EventBus.csproj" /> |
||||
|
<ProjectReference Include="..\Volo.Abp.Dapr\Volo.Abp.Dapr.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,11 @@ |
|||||
|
namespace Volo.Abp.EventBus.Dapr; |
||||
|
|
||||
|
public class AbpDaprEventBusOptions |
||||
|
{ |
||||
|
public string PubSubName { get; set; } |
||||
|
|
||||
|
public AbpDaprEventBusOptions() |
||||
|
{ |
||||
|
PubSubName = "pubsub"; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Volo.Abp.Dapr; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace Volo.Abp.EventBus.Dapr; |
||||
|
|
||||
|
[DependsOn( |
||||
|
typeof(AbpEventBusModule), |
||||
|
typeof(AbpDaprModule) |
||||
|
)] |
||||
|
public class AbpEventBusDaprModule : AbpModule |
||||
|
{ |
||||
|
public override void OnApplicationInitialization(ApplicationInitializationContext context) |
||||
|
{ |
||||
|
context |
||||
|
.ServiceProvider |
||||
|
.GetRequiredService<DaprDistributedEventBus>() |
||||
|
.Initialize(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,221 @@ |
|||||
|
using System.Collections.Concurrent; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using Volo.Abp.Dapr; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.EventBus.Distributed; |
||||
|
using Volo.Abp.Guids; |
||||
|
using Volo.Abp.MultiTenancy; |
||||
|
using Volo.Abp.Threading; |
||||
|
using Volo.Abp.Timing; |
||||
|
using Volo.Abp.Uow; |
||||
|
|
||||
|
namespace Volo.Abp.EventBus.Dapr; |
||||
|
|
||||
|
[Dependency(ReplaceServices = true)] |
||||
|
[ExposeServices(typeof(IDistributedEventBus), typeof(DaprDistributedEventBus))] |
||||
|
public class DaprDistributedEventBus : DistributedEventBusBase, ISingletonDependency |
||||
|
{ |
||||
|
protected IDaprSerializer Serializer { get; } |
||||
|
protected AbpDaprEventBusOptions DaprEventBusOptions { get; } |
||||
|
protected AbpDaprClientFactory DaprClientFactory { get; } |
||||
|
|
||||
|
protected ConcurrentDictionary<Type, List<IEventHandlerFactory>> HandlerFactories { get; } |
||||
|
protected ConcurrentDictionary<string, Type> EventTypes { get; } |
||||
|
|
||||
|
public DaprDistributedEventBus( |
||||
|
IServiceScopeFactory serviceScopeFactory, |
||||
|
ICurrentTenant currentTenant, |
||||
|
IUnitOfWorkManager unitOfWorkManager, |
||||
|
IOptions<AbpDistributedEventBusOptions> abpDistributedEventBusOptions, |
||||
|
IGuidGenerator guidGenerator, |
||||
|
IClock clock, |
||||
|
IEventHandlerInvoker eventHandlerInvoker, |
||||
|
IDaprSerializer serializer, |
||||
|
IOptions<AbpDaprEventBusOptions> daprEventBusOptions, |
||||
|
AbpDaprClientFactory daprClientFactory) |
||||
|
: base(serviceScopeFactory, currentTenant, unitOfWorkManager, abpDistributedEventBusOptions, guidGenerator, clock, eventHandlerInvoker) |
||||
|
{ |
||||
|
Serializer = serializer; |
||||
|
DaprEventBusOptions = daprEventBusOptions.Value; |
||||
|
DaprClientFactory = daprClientFactory; |
||||
|
|
||||
|
HandlerFactories = new ConcurrentDictionary<Type, List<IEventHandlerFactory>>(); |
||||
|
EventTypes = new ConcurrentDictionary<string, Type>(); |
||||
|
} |
||||
|
|
||||
|
public void Initialize() |
||||
|
{ |
||||
|
SubscribeHandlers(AbpDistributedEventBusOptions.Handlers); |
||||
|
} |
||||
|
|
||||
|
public override IDisposable Subscribe(Type eventType, IEventHandlerFactory factory) |
||||
|
{ |
||||
|
var handlerFactories = GetOrCreateHandlerFactories(eventType); |
||||
|
|
||||
|
if (factory.IsInFactories(handlerFactories)) |
||||
|
{ |
||||
|
return NullDisposable.Instance; |
||||
|
} |
||||
|
|
||||
|
handlerFactories.Add(factory); |
||||
|
|
||||
|
return new EventHandlerFactoryUnregistrar(this, eventType, factory); |
||||
|
} |
||||
|
|
||||
|
public override void Unsubscribe<TEvent>(Func<TEvent, Task> action) |
||||
|
{ |
||||
|
Check.NotNull(action, nameof(action)); |
||||
|
|
||||
|
GetOrCreateHandlerFactories(typeof(TEvent)) |
||||
|
.Locking(factories => |
||||
|
{ |
||||
|
factories.RemoveAll( |
||||
|
factory => |
||||
|
{ |
||||
|
var singleInstanceFactory = factory as SingleInstanceHandlerFactory; |
||||
|
if (singleInstanceFactory == null) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
var actionHandler = singleInstanceFactory.HandlerInstance as ActionEventHandler<TEvent>; |
||||
|
if (actionHandler == null) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
return actionHandler.Action == action; |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public override void Unsubscribe(Type eventType, IEventHandler handler) |
||||
|
{ |
||||
|
GetOrCreateHandlerFactories(eventType) |
||||
|
.Locking(factories => |
||||
|
{ |
||||
|
factories.RemoveAll( |
||||
|
factory => |
||||
|
factory is SingleInstanceHandlerFactory && |
||||
|
(factory as SingleInstanceHandlerFactory).HandlerInstance == handler |
||||
|
); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public override void Unsubscribe(Type eventType, IEventHandlerFactory factory) |
||||
|
{ |
||||
|
GetOrCreateHandlerFactories(eventType).Locking(factories => factories.Remove(factory)); |
||||
|
} |
||||
|
|
||||
|
public override void UnsubscribeAll(Type eventType) |
||||
|
{ |
||||
|
GetOrCreateHandlerFactories(eventType).Locking(factories => factories.Clear()); |
||||
|
} |
||||
|
|
||||
|
protected async override Task PublishToEventBusAsync(Type eventType, object eventData) |
||||
|
{ |
||||
|
await PublishToDaprAsync(eventType, eventData); |
||||
|
} |
||||
|
|
||||
|
protected override void AddToUnitOfWork(IUnitOfWork unitOfWork, UnitOfWorkEventRecord eventRecord) |
||||
|
{ |
||||
|
unitOfWork.AddOrReplaceDistributedEvent(eventRecord); |
||||
|
} |
||||
|
|
||||
|
protected override IEnumerable<EventTypeWithEventHandlerFactories> GetHandlerFactories(Type eventType) |
||||
|
{ |
||||
|
var handlerFactoryList = new List<EventTypeWithEventHandlerFactories>(); |
||||
|
|
||||
|
foreach (var handlerFactory in HandlerFactories.Where(hf => ShouldTriggerEventForHandler(eventType, hf.Key))) |
||||
|
{ |
||||
|
handlerFactoryList.Add(new EventTypeWithEventHandlerFactories(handlerFactory.Key, handlerFactory.Value)); |
||||
|
} |
||||
|
|
||||
|
return handlerFactoryList.ToArray(); |
||||
|
} |
||||
|
|
||||
|
public async override Task PublishFromOutboxAsync(OutgoingEventInfo outgoingEvent, OutboxConfig outboxConfig) |
||||
|
{ |
||||
|
await PublishToDaprAsync(outgoingEvent.EventName, Serializer.Deserialize(outgoingEvent.EventData, GetEventType(outgoingEvent.EventName))); |
||||
|
} |
||||
|
|
||||
|
public async override Task PublishManyFromOutboxAsync(IEnumerable<OutgoingEventInfo> outgoingEvents, OutboxConfig outboxConfig) |
||||
|
{ |
||||
|
var outgoingEventArray = outgoingEvents.ToArray(); |
||||
|
|
||||
|
foreach (var outgoingEvent in outgoingEventArray) |
||||
|
{ |
||||
|
await PublishToDaprAsync(outgoingEvent.EventName, Serializer.Deserialize(outgoingEvent.EventData, GetEventType(outgoingEvent.EventName))); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public async override Task ProcessFromInboxAsync(IncomingEventInfo incomingEvent, InboxConfig inboxConfig) |
||||
|
{ |
||||
|
var eventType = EventTypes.GetOrDefault(incomingEvent.EventName); |
||||
|
if (eventType == null) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var eventData = Serializer.Deserialize(incomingEvent.EventData, eventType); |
||||
|
var exceptions = new List<Exception>(); |
||||
|
await TriggerHandlersAsync(eventType, eventData, exceptions, inboxConfig); |
||||
|
if (exceptions.Any()) |
||||
|
{ |
||||
|
ThrowOriginalExceptions(eventType, exceptions); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected override byte[] Serialize(object eventData) |
||||
|
{ |
||||
|
return Serializer.Serialize(eventData); |
||||
|
} |
||||
|
|
||||
|
private List<IEventHandlerFactory> GetOrCreateHandlerFactories(Type eventType) |
||||
|
{ |
||||
|
return HandlerFactories.GetOrAdd( |
||||
|
eventType, |
||||
|
type => |
||||
|
{ |
||||
|
var eventName = EventNameAttribute.GetNameOrDefault(type); |
||||
|
EventTypes[eventName] = type; |
||||
|
return new List<IEventHandlerFactory>(); |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
public Type GetEventType(string eventName) |
||||
|
{ |
||||
|
return EventTypes.GetOrDefault(eventName); |
||||
|
} |
||||
|
|
||||
|
protected virtual async Task PublishToDaprAsync(Type eventType, object eventData) |
||||
|
{ |
||||
|
await PublishToDaprAsync(EventNameAttribute.GetNameOrDefault(eventType), eventData); |
||||
|
} |
||||
|
|
||||
|
protected virtual async Task PublishToDaprAsync(string eventName, object eventData) |
||||
|
{ |
||||
|
var client = await DaprClientFactory.CreateAsync(); |
||||
|
await client.PublishEventAsync(pubsubName: DaprEventBusOptions.PubSubName, topicName: eventName, data: eventData); |
||||
|
} |
||||
|
|
||||
|
private static bool ShouldTriggerEventForHandler(Type targetEventType, Type handlerEventType) |
||||
|
{ |
||||
|
//Should trigger same type
|
||||
|
if (handlerEventType == targetEventType) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
//TODO: Support inheritance? But it does not support on subscription to RabbitMq!
|
||||
|
//Should trigger for inherited types
|
||||
|
if (handlerEventType.IsAssignableFrom(targetEventType)) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,3 @@ |
|||||
|
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
||||
|
<ConfigureAwait ContinueOnCapturedContext="false" /> |
||||
|
</Weavers> |
||||
@ -0,0 +1,30 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
||||
|
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
||||
|
<xs:element name="Weavers"> |
||||
|
<xs:complexType> |
||||
|
<xs:all> |
||||
|
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
||||
|
<xs:complexType> |
||||
|
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:all> |
||||
|
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:schema> |
||||
@ -0,0 +1,18 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net6.0</TargetFramework> |
||||
|
<ImplicitUsings>enable</ImplicitUsings> |
||||
|
<Nullable>enable</Nullable> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\Volo.Abp.Http.Client\Volo.Abp.Http.Client.csproj" /> |
||||
|
<ProjectReference Include="..\Volo.Abp.Dapr\Volo.Abp.Dapr.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||