@ -0,0 +1,3 @@
|
||||
## Dynamic Proxying / Interceptors
|
||||
|
||||
Façam
|
||||
@ -0,0 +1,3 @@
|
||||
# Audit Logging
|
||||
|
||||
Façam
|
||||
@ -0,0 +1,3 @@
|
||||
## Authorization
|
||||
|
||||
Façam
|
||||
@ -0,0 +1,3 @@
|
||||
## AutoMapper Integration
|
||||
|
||||
Façam
|
||||
@ -0,0 +1,3 @@
|
||||
# Hangfire Background Job Manager
|
||||
|
||||
Façam
|
||||
@ -0,0 +1,3 @@
|
||||
# RabbitMQ Background Job Manager
|
||||
|
||||
TODO
|
||||
@ -0,0 +1,149 @@
|
||||
# ABP CLI
|
||||
|
||||
O ABP CLI (Command Line Interface) é uma ferramenta de linha de comando para executar algumas operações comuns para soluções baseadas em ABP.
|
||||
|
||||
## Instalação
|
||||
|
||||
O ABP CLI é uma [ferramenta global dotnet](https://docs.microsoft.com/en-us/dotnet/core/tools/global-tools) . Instale-o usando uma janela de linha de comando:
|
||||
|
||||
````bash
|
||||
dotnet tool install -g Volo.Abp.Cli
|
||||
````
|
||||
|
||||
Para atualizar uma instalação existente:
|
||||
|
||||
````bash
|
||||
dotnet tool update -g Volo.Abp.Cli
|
||||
````
|
||||
|
||||
## Comandos
|
||||
|
||||
### Novo
|
||||
|
||||
Gera uma nova solução baseada nos [modelos de inicialização](Startup-Templates/Index.md) do ABP .
|
||||
|
||||
Uso básico:
|
||||
|
||||
````bash
|
||||
abp new <solution-name> [options]
|
||||
````
|
||||
|
||||
Examplo:
|
||||
|
||||
````bash
|
||||
abp new Acme.BookStore
|
||||
````
|
||||
|
||||
* `Acme.BookStore` é o nome da solução aqui.
|
||||
* A convenção comum é nomear uma solução como *YourCompany.YourProject* . No entanto, você pode usar nomes diferentes, como *YourProject* (namespacing de nível único) ou *YourCompany.YourProduct.YourModule* (namespacing de três níveis).
|
||||
|
||||
#### Opções
|
||||
|
||||
* `--template`ou `-t`: especifica o nome do modelo. O nome do modelo padrão é `app`, que gera um aplicativo da web. Modelos disponíveis:
|
||||
* `app`(padrão): [modelo de aplicativo](https://docs.abp.io/en/abp/latest/Startup-Templates/Application) . Opções adicionais:
|
||||
* `--ui`ou `-u`: Especifica a UI framework. Framework padrão é `mvc`. Framework disponíveis:
|
||||
* `mvc`: ASP.NET Core MVC. Existem algumas opções adicionais para este modelo:
|
||||
* `--tiered`: Cria uma solução em camadas em que as camadas da Web e da API HTTP são fisicamente separadas. Se não especificado, ele cria uma solução em camadas que é menos complexa e adequada para a maioria dos cenários.
|
||||
* `angular`: Angular. Existem algumas opções adicionais para este modelo:
|
||||
* `--separate-identity-server`: Separa o aplicativo do servidor de identidade do aplicativo host da API. Se não especificado, você terá um único ponto de extremidade no lado do servidor.
|
||||
* `--database-provider` Ou `-d`: especifica o provedor de banco de dados. O provedor padrão é `ef`. Fornecedores disponíveis:
|
||||
* `ef`: Entity Framework Core.
|
||||
* `mongodb`: MongoDB.
|
||||
* `module`: [Exemplo de Módulo](Startup-Templates/Module.md). Opções adicionais:
|
||||
* `--no-ui`: Especifica para não incluir a UI. Isso possibilita a criação de módulos somente de serviço (também conhecidos como microsserviços - sem interface do usuário).
|
||||
* `--output-folder` ou `-o`: especifica a pasta de saída. O valor padrão é o diretório atual.
|
||||
* `--version` ou `-v`: Especifica a ABP & versão de exemplo . Pode ser uma [release tag](https://github.com/abpframework/abp/releases) ou um [branch name](https://github.com/abpframework/abp/branches). Usa a versão mais recente, se não especificado. Na maioria das vezes, você desejará usar a versão mais recente.
|
||||
|
||||
### add-package
|
||||
|
||||
Adiciona um pacote ABP a um projeto por,
|
||||
|
||||
- Adicionando pacote de nuget relacionado como uma dependência ao projeto.
|
||||
- Adicionando `[DependsOn(...)]`atributo à classe de módulo no projeto (consulte o [documento de desenvolvimento](https://docs.abp.io/en/abp/latest/Module-Development-Basics) do [módulo](https://docs.abp.io/en/abp/latest/Module-Development-Basics) ).
|
||||
|
||||
> Observe que o módulo adicionado pode exigir uma configuração adicional, geralmente indicada na documentação do pacote relacionado.
|
||||
|
||||
Uso básico:
|
||||
|
||||
```bash
|
||||
abp add-package <package-name> [options]
|
||||
```
|
||||
|
||||
Bater
|
||||
|
||||
cópia de
|
||||
|
||||
Exemplo:
|
||||
|
||||
```
|
||||
abp add-package Volo.Abp.MongoDB
|
||||
```
|
||||
|
||||
- Este exemplo adiciona o pacote Volo.Abp.MongoDB ao projeto.
|
||||
|
||||
#### Opções
|
||||
|
||||
- `--project`ou `-p`: especifica o caminho do arquivo do projeto (.csproj). Se não especificado, a CLI tenta encontrar um arquivo .csproj no diretório atual.
|
||||
|
||||
### add-module
|
||||
|
||||
Adiciona um [módulo de aplicativo com vários pacotes](Modules/Index.md) a uma solução, localizando todos os pacotes do módulo, localizando projetos relacionados na solução e adicionando cada pacote ao projeto correspondente na solução.
|
||||
|
||||
> Um módulo de negócios geralmente consiste em vários pacotes (devido a camadas, diferentes opções de provedor de banco de dados ou outros motivos). O uso do `add-module`comando simplifica drasticamente a adição de um módulo a uma solução. No entanto, cada módulo pode exigir algumas configurações adicionais, geralmente indicadas na documentação do módulo relacionado.
|
||||
|
||||
Uso básico:
|
||||
|
||||
```bash
|
||||
abp add-module <module-name> [options]
|
||||
```
|
||||
|
||||
Exemplo:
|
||||
|
||||
```bash
|
||||
abp add-module Volo.Blogging
|
||||
```
|
||||
|
||||
- Este exemplo adiciona o módulo Volo.Blogging à solução.
|
||||
|
||||
#### Opções
|
||||
|
||||
- `--solution`ou `-s`: especifica o caminho do arquivo da solução (.sln). Se não especificado, a CLI tenta encontrar um arquivo .sln no diretório atual.
|
||||
- `--skip-db-migrations`: Para o provedor de banco de dados EF Core, ele adiciona automaticamente um novo código à primeira migração ( `Add-Migration`) e atualiza o banco de dados ( `Update-Database`), se necessário. Especifique esta opção para pular esta operação.
|
||||
- `-sp`ou `--startup-project`: caminho relativo para a pasta do projeto de inicialização. O valor padrão é a pasta atual.
|
||||
|
||||
### atualizar
|
||||
|
||||
A atualização de todos os pacotes relacionados ao ABP pode ser entediante, pois existem muitos pacotes da estrutura e dos módulos. Este comando atualiza automaticamente todos os pacotes NuGet e NPM relacionados ao ABP em uma solução ou projeto para as versões mais recentes.
|
||||
|
||||
Uso:
|
||||
|
||||
```bash
|
||||
abp update [options]
|
||||
```
|
||||
|
||||
- Se você executar em um diretório com um arquivo .sln, ele atualizará todos os pacotes relacionados ao ABP de todos os projetos da solução para as versões mais recentes.
|
||||
- Se você executar em um diretório com um arquivo .csproj, ele atualizará todos os pacotes relacionados ao ABP do projeto para as versões mais recentes.
|
||||
|
||||
#### Opções
|
||||
|
||||
- `--include-previews`ou `-p`: inclui pacotes de visualização, beta e rc enquanto verifica as versões mais recentes.
|
||||
|
||||
### Socorro
|
||||
|
||||
Grava informações básicas de uso da CLI.
|
||||
|
||||
Uso:
|
||||
|
||||
```bash
|
||||
abp help [command-name]
|
||||
```
|
||||
|
||||
Exemplos:
|
||||
|
||||
```bash
|
||||
abp help # Shows a general help.
|
||||
abp help new # Shows help about the "new" command.
|
||||
```
|
||||
|
||||
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
# Caching
|
||||
|
||||
Façam
|
||||
@ -0,0 +1,3 @@
|
||||
# Correlation ID
|
||||
|
||||
Façam
|
||||
@ -0,0 +1,3 @@
|
||||
# Data Filtering
|
||||
|
||||
Façam
|
||||
@ -0,0 +1,3 @@
|
||||
# Data Seeding
|
||||
|
||||
Façam
|
||||
@ -0,0 +1,3 @@
|
||||
## Data Transfer Objects
|
||||
|
||||
Façam
|
||||
@ -0,0 +1,3 @@
|
||||
# Distributed Event Bus
|
||||
|
||||
Façam
|
||||
@ -0,0 +1,3 @@
|
||||
# ABP Documentation
|
||||
|
||||
Façam!
|
||||
@ -0,0 +1,3 @@
|
||||
## Dynamic Proxying / Interceptors
|
||||
|
||||
Façam
|
||||
@ -0,0 +1,3 @@
|
||||
# Emailing
|
||||
|
||||
Façam!
|
||||
@ -0,0 +1,3 @@
|
||||
# Event Bus
|
||||
|
||||
Façam
|
||||
@ -0,0 +1,3 @@
|
||||
# Extension Methods & Helpers
|
||||
|
||||
Façam
|
||||
@ -0,0 +1,3 @@
|
||||
## Guid Generation
|
||||
|
||||
Façam
|
||||
@ -0,0 +1,3 @@
|
||||
# Integration Tests
|
||||
|
||||
Façam!
|
||||
@ -0,0 +1,3 @@
|
||||
# Local Event Bus
|
||||
|
||||
Façam
|
||||
@ -0,0 +1,195 @@
|
||||
# Localização
|
||||
|
||||
O sistema de localização da ABP é perfeitamente integrado ao `Microsoft.Extensions.Localization`pacote e compatível com a [documentação de localização da Microsoft](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/localization) . Ele adiciona alguns recursos e aprimoramentos úteis para facilitar o uso em cenários de aplicativos da vida real.
|
||||
|
||||
## Pacote Volo.Abp.Localization
|
||||
|
||||
> Este pacote já está instalado por padrão com o modelo de inicialização. Portanto, na maioria das vezes, você não precisa instalá-lo manualmente.
|
||||
|
||||
Volo.Abp.Localization é o pacote principal do sistema de localização. Instale-o no seu projeto usando o console do gerenciador de pacotes (PMC):
|
||||
|
||||
```
|
||||
Install-Package Volo.Abp.Localization
|
||||
```
|
||||
|
||||
Em seguida, você pode adicionar a dependência **AbpLocalizationModule** ao seu módulo:
|
||||
|
||||
```csharp
|
||||
using Volo.Abp.Modularity;
|
||||
using Volo.Abp.Localization;
|
||||
|
||||
namespace MyCompany.MyProject
|
||||
{
|
||||
[DependsOn(typeof(AbpLocalizationModule))]
|
||||
public class MyModule : AbpModule
|
||||
{
|
||||
//...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Criando um recurso de localização
|
||||
|
||||
Um recurso de localização é usado para agrupar cadeias de localização relacionadas e separá-las de outras cadeias de localização do aplicativo. Um [módulo](Module-Development-Basics.md) geralmente define seu próprio recurso de localização. O recurso de localização é apenas uma classe simples. Exemplo:
|
||||
|
||||
```csharp
|
||||
public class TestResource
|
||||
{
|
||||
}
|
||||
```
|
||||
|
||||
Em seguida, deve ser adicionado usando `AbpLocalizationOptions`como mostrado abaixo:
|
||||
|
||||
```csharp
|
||||
[DependsOn(typeof(AbpLocalizationModule))]
|
||||
public class MyModule : AbpModule
|
||||
{
|
||||
public override void ConfigureServices(ServiceConfigurationContext context)
|
||||
{
|
||||
Configure<VirtualFileSystemOptions>(options =>
|
||||
{
|
||||
options.FileSets.AddEmbedded<MyModule>();
|
||||
});
|
||||
|
||||
Configure<AbpLocalizationOptions>(options =>
|
||||
{
|
||||
//Define a new localization resource (TestResource)
|
||||
options.Resources
|
||||
.Add<TestResource>("en")
|
||||
.AddVirtualJson("/Localization/Resources/Test");
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Neste exemplo;
|
||||
|
||||
- Adicionado um novo recurso de localização com "en" (inglês) como a cultura padrão.
|
||||
- Arquivos JSON usados para armazenar as sequências de localização.
|
||||
- Os arquivos JSON são incorporados ao assembly usando `VirtualFileSystemOptions`(consulte [sistema de arquivos virtual](Virtual-File-System.md) ).
|
||||
|
||||
Os arquivos JSON estão localizados na pasta do projeto "/ Localização / Recursos / Teste", como mostrado abaixo:
|
||||
|
||||

|
||||
|
||||
Um conteúdo do arquivo de localização JSON é mostrado abaixo:
|
||||
|
||||
```json
|
||||
{
|
||||
"culture": "en",
|
||||
"texts": {
|
||||
"HelloWorld": "Hello World!"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- Todo arquivo de localização deve definir o `culture`código para o arquivo (como "en" ou "en-US").
|
||||
- `texts` A seção contém apenas a coleção de valores-chave das sequências de localização (as chaves também podem ter espaços).
|
||||
|
||||
### Nome Curto do Recurso de Localização
|
||||
|
||||
Os recursos de localização também estão disponíveis no lado do cliente (JavaScript). Portanto, definir um nome abreviado para o recurso de localização facilita o uso de textos de localização. Exemplo:
|
||||
|
||||
```csharp
|
||||
[LocalizationResourceName("Test")]
|
||||
public class TestResource
|
||||
{
|
||||
}
|
||||
```
|
||||
|
||||
Consulte a seção Obtendo teste localizado / lado do cliente abaixo.
|
||||
|
||||
### Herdar de outros recursos
|
||||
|
||||
Um recurso pode herdar de outros recursos, o que possibilita reutilizar cadeias de localização existentes sem fazer referência ao recurso existente. Exemplo:
|
||||
|
||||
```csharp
|
||||
[InheritResource(typeof(AbpValidationResource))]
|
||||
public class TestResource
|
||||
{
|
||||
}
|
||||
```
|
||||
|
||||
Herança alternativa configurando o `AbpLocalizationOptions`:
|
||||
|
||||
```csharp
|
||||
services.Configure<AbpLocalizationOptions>(options =>
|
||||
{
|
||||
options.Resources
|
||||
.Add<TestResource>("en") //Define the resource by "en" default culture
|
||||
.AddVirtualJson("/Localization/Resources/Test") //Add strings from virtual json files
|
||||
.AddBaseTypes(typeof(AbpValidationResource)); //Inherit from an existing resource
|
||||
});
|
||||
```
|
||||
|
||||
- Um recurso pode herdar de vários recursos.
|
||||
- Se o novo recurso definir a mesma sequência localizada, ele substituirá a sequência.
|
||||
|
||||
### Estendendo o Recurso Existente
|
||||
|
||||
Herdar de um recurso cria um novo recurso sem modificar o existente. Em alguns casos, convém não criar um novo recurso, mas estender diretamente um recurso existente. Exemplo:
|
||||
|
||||
```csharp
|
||||
services.Configure<AbpLocalizationOptions>(options =>
|
||||
{
|
||||
options.Resources
|
||||
.Get<TestResource>()
|
||||
.AddVirtualJson("/Localization/Resources/Test/Extensions");
|
||||
});
|
||||
```
|
||||
|
||||
- Se um arquivo de extensão define a mesma sequência localizada, ele substitui a sequência.
|
||||
|
||||
## Obtendo textos localizados
|
||||
|
||||
### Lado do servidor
|
||||
|
||||
Obter o texto localizado no lado do servidor é bastante padrão.
|
||||
|
||||
#### Uso mais simples de uma classe
|
||||
|
||||
```csharp
|
||||
public class MyService
|
||||
{
|
||||
private readonly IStringLocalizer<TestResource> _localizer;
|
||||
|
||||
public MyService(IStringLocalizer<TestResource> localizer)
|
||||
{
|
||||
_localizer = localizer;
|
||||
}
|
||||
|
||||
public void Foo()
|
||||
{
|
||||
var str = _localizer["HelloWorld"];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Uso mais simples em uma vista / página do Razor
|
||||
|
||||
```csharp
|
||||
@inject IHtmlLocalizer<TestResource> Localizer
|
||||
|
||||
<h1>@Localizer["HelloWorld"]</h1>
|
||||
```
|
||||
|
||||
Consulte a [documentação de localização da Microsoft](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/localization) para obter detalhes sobre o uso da localização no lado do servidor.
|
||||
|
||||
### Lado do Cliente
|
||||
|
||||
A ABP fornece serviços JavaScript para usar os mesmos textos localizados no lado do cliente.
|
||||
|
||||
Obtenha um recurso de localização:
|
||||
|
||||
```js
|
||||
var testResource = abp.localization.getResource('Test');
|
||||
```
|
||||
|
||||
Localize uma sequência:
|
||||
|
||||
```js
|
||||
var str = testResource('HelloWorld');
|
||||
```
|
||||
|
||||
|
||||
|
||||
@ -0,0 +1,27 @@
|
||||
# Compilações Noturnas
|
||||
|
||||
Todos os pacotes de estrutura e módulo são implantados no MyGet todas as noites durante a semana. Portanto, você pode usar ou testar o código mais recente sem aguardar o próximo lançamento.
|
||||
|
||||
## Configurar o Visual Studio
|
||||
|
||||
> Requer Visual Studio 2017 ou superior
|
||||
|
||||
1. Vá para `Tools > Options > NuGet Package Manager > Package Source`.
|
||||
2. Clique no `+` ícone verde .
|
||||
3. Defina `ABP Nightly`como *Nome* e `https://www.myget.org/F/abp-nightly/api/v3/index.json`como a *Fonte,* como mostrado abaixo: 
|
||||
4. Clique no `Update` botão
|
||||
5. Clique no `OK` botão para salvar as alterações.
|
||||
|
||||
## Instalar pacote
|
||||
|
||||
Agora, você pode instalar pacotes noturnos / de visualização no seu projeto a partir do Nuget Browser ou do Package Manager Console.
|
||||
|
||||

|
||||
|
||||
1. No Nuget Browser, selecione "Incluir pré-lançamentos".
|
||||
2. Altere a fonte do pacote para "Todos".
|
||||
3. Pesquise um pacote. Você verá as pré *-liberações* do pacote formatadas como `(VERSION)-preview(DATE)`(como *v0.16.0-preview20190401* neste exemplo).
|
||||
4. Você pode clicar no `Install`botão para adicionar um pacote ao seu projeto.
|
||||
|
||||
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
## Object To Object Mapping
|
||||
|
||||
Façam
|
||||
@ -0,0 +1,3 @@
|
||||
# Emailing
|
||||
|
||||
Façam!
|
||||
@ -0,0 +1,3 @@
|
||||
# Settings
|
||||
|
||||
Façam!
|
||||
@ -0,0 +1,3 @@
|
||||
# Specifications
|
||||
|
||||
Façam!
|
||||
@ -0,0 +1,275 @@
|
||||
# Modelo de inicialização do aplicativo
|
||||
|
||||
## Introdução
|
||||
|
||||
Este modelo fornece uma estrutura de aplicativo em camadas com base nas práticas DDD ([Domain Driven Design](../Domain-Driven-Design.md)). Este documento explica a estrutura da solução e os projetos em detalhes. Se você deseja iniciar rapidamente, siga os guias abaixo:
|
||||
|
||||
- Consulte [Introdução ao modelo do ASP.NET Core MVC](../Getting-Started-AspNetCore-MVC-Template.md) para criar uma nova solução e executá-la para este modelo (usa o MVC como a estrutura da interface do usuário e o Entity Framework Core como o provedor de banco de dados).
|
||||
- Consulte o [Tutorial de desenvolvimento de aplicativos do ASP.NET Core MVC](../Tutorials/AspNetCore-Mvc/Part-I.md) para aprender como desenvolver aplicativos usando este modelo (usa o MVC como a estrutura da interface do usuário e o Entity Framework Core como o provedor de banco de dados).
|
||||
- Consulte o [Tutorial de desenvolvimento de aplicativos Angular](../Tutorials/Angular/Part-I.md) para aprender como desenvolver aplicativos usando este modelo (usa Angular como a estrutura da interface do usuário e MongoDB como o provedor de banco de dados).
|
||||
|
||||
## Como começar?
|
||||
|
||||
Você pode usar a [ABP CLI](../CLI.md) para criar um novo projeto usando este modelo de inicialização. Como alternativa, você pode criar e fazer o download diretamente na página [Introdução](https://abp.io/get-started) . A abordagem CLI é usada aqui.
|
||||
|
||||
Primeiro, instale a ABP CLI se você não tiver instalado antes:
|
||||
|
||||
```bash
|
||||
dotnet tool install -g Volo.Abp.Cli
|
||||
```
|
||||
|
||||
Em seguida, use o `abp new`comando em uma pasta vazia para criar uma nova solução:
|
||||
|
||||
```bash
|
||||
abp new Acme.BookStore -t app
|
||||
```
|
||||
|
||||
- `Acme.BookStore`é o nome da solução, como *YourCompany.YourProduct* . Você pode usar nomes de nível único, dois ou três níveis.
|
||||
- Este exemplo especificou o nome do modelo ( `-t`ou `--template`opção). No entanto, `app`já é o modelo padrão se você não o especificar.
|
||||
|
||||
### Especifique a estrutura da interface do usuário
|
||||
|
||||
Este modelo fornece várias estruturas de interface do usuário:
|
||||
|
||||
- `mvc`: Interface do usuário do ASP.NET Core MVC com Razor Pages (padrão)
|
||||
- `angular`: UI angular
|
||||
|
||||
Use `-u`ou `--ui`opção para especificar a estrutura da interface do usuário:
|
||||
|
||||
```bash
|
||||
abp new Acme.BookStore -u angular
|
||||
```
|
||||
|
||||
### Especifique o provedor de banco de dados
|
||||
|
||||
Este modelo suporta os seguintes provedores de banco de dados:
|
||||
|
||||
- `ef`: Entity Framework Core (padrão)
|
||||
- `mongodb`: MongoDB
|
||||
|
||||
Use a opção `-d`(ou `--database-provider`) para especificar o provedor de banco de dados:
|
||||
|
||||
```bash
|
||||
abp new Acme.BookStore -d mongodb
|
||||
```
|
||||
|
||||
## Estrutura da solução
|
||||
|
||||
Com base nas opções especificadas, você obterá uma estrutura de solução ligeiramente diferente.
|
||||
|
||||
### Estrutura padrão
|
||||
|
||||
Se você não especificar nenhuma opção adicional, terá uma solução como a mostrada abaixo:
|
||||
|
||||

|
||||
|
||||
Os projetos são organizados em `src`e `test`pastas. `src`A pasta contém o aplicativo real que está em camadas com base nos princípios [DDD](https://docs.abp.io/en/abp/latest/Domain-Driven-Design) , como mencionado anteriormente.
|
||||
|
||||
O diagrama abaixo mostra as camadas e dependências do projeto do aplicativo:
|
||||
|
||||

|
||||
|
||||
Cada seção abaixo explicará o projeto relacionado e suas dependências.
|
||||
|
||||
#### Projeto .Domain.Shared
|
||||
|
||||
Este projeto contém constantes, enumerações e outros objetos. Na verdade, eles fazem parte da camada de domínio, mas precisam ser usados por todas as camadas / projetos da solução.
|
||||
|
||||
Um `BookType`enum e uma `BookConsts`classe (que podem ter alguns campos constantes para a `Book`entidade, como `MaxNameLength`) são bons candidatos para este projeto.
|
||||
|
||||
- Este projeto não depende de outros projetos na solução. Todos os outros projetos dependem disso direta ou indiretamente.
|
||||
|
||||
#### .Domain Project
|
||||
|
||||
Essa é a camada de domínio da solução. Ele contém principalmente [entidades, raízes agregadas](../Entities.md) , [serviços de domínio](../Domain-Services.md) , [tipos de valor](../Value-Types.md) , [interfaces de repositório](../Repositories) e outros objetos de domínio.
|
||||
|
||||
Uma `Book`entidade, um `BookManager`serviço de domínio e uma `IBookRepository`interface são bons candidatos para este projeto.
|
||||
|
||||
- Depende do `.Domain.Shared`porque usa constantes, enumerações e outros objetos definidos nesse projeto.
|
||||
|
||||
#### .Application.Contracts Project
|
||||
|
||||
Este projeto contém principalmente **interfaces de** [serviço de aplicativo](../Application-Services.md) e DTO ( [Data Transfer Objects](../Data-Transfer-Objects.md) ) da camada de aplicativo. Existe para separar a interface e a implementação da camada de aplicação. Dessa forma, o projeto de interface pode ser compartilhado com os clientes como um pacote de contrato.
|
||||
|
||||
Uma `IBookAppService`interface e uma `BookCreationDto`classe são boas candidatas para este projeto.
|
||||
|
||||
- Depende do `.Domain.Shared`porque ele pode usar constantes, enumerações e outros objetos compartilhados deste projeto nas interfaces de serviço de aplicativo e DTOs.
|
||||
|
||||
#### Projeto de Aplicação
|
||||
|
||||
Este projeto contém as **implementações** de [serviço de aplicativo](../Application-Services.md) das interfaces definidas no projeto.`.Application.Contracts`
|
||||
|
||||
Uma `BookAppService`turma é uma boa candidata para este projeto.
|
||||
|
||||
- Depende do `.Application.Contracts`projeto para poder implementar as interfaces e usar os DTOs.
|
||||
- Depende do `.Domain`projeto para poder usar objetos de domínio (entidades, interfaces de repositório ... etc.) para executar a lógica do aplicativo.
|
||||
|
||||
#### Projeto .EntityFrameworkCore
|
||||
|
||||
Este é o projeto de integração para o EF Core. Ele define `DbContext`e implementa as interfaces de repositório definidas no `.Domain`projeto.
|
||||
|
||||
- Depende do `.Domain`projeto para poder fazer referência a entidades e interfaces de repositório.
|
||||
|
||||
> Este projeto está disponível apenas se você estiver usando o EF Core como provedor de banco de dados. Se você selecionar outro provedor de banco de dados, seu nome será diferente.
|
||||
|
||||
#### Projeto .EntityFrameworkCore.DbMigrations
|
||||
|
||||
Contém migrações de banco de dados EF Core para a solução. Ele foi separado `DbContext`para dedicado a gerenciar migrações.
|
||||
|
||||
ABP é uma estrutura modular e com um design ideal, cada módulo tem sua própria `DbContext`classe. É aqui que a migração `DbContext`entra em ação e unifica todas as `DbContext`configurações em um único modelo para manter um único esquema de banco de dados. Para cenários mais avançados, você pode ter vários bancos de dados (cada um contém uma única ou algumas tabelas de módulos) e várias migrações `DbContext`(cada uma mantém um esquema de banco de dados diferente).
|
||||
|
||||
Observe que a migração `DbContext`é usada apenas para migrações de banco de dados e *não em tempo de execução* .
|
||||
|
||||
- Depende do `.EntityFrameworkCore`projeto, pois reutiliza a configuração definida para `DbContext`o aplicativo.
|
||||
|
||||
> Este projeto está disponível apenas se você estiver usando o EF Core como provedor de banco de dados.
|
||||
|
||||
#### Projeto .DbMigrator
|
||||
|
||||
Este é um aplicativo de console que simplifica a execução de migrações de banco de dados em ambientes de desenvolvimento e produção. Quando você executa este aplicativo, ele;
|
||||
|
||||
- Cria o banco de dados, se necessário.
|
||||
- Aplica as migrações de banco de dados pendentes.
|
||||
- Semeia os dados iniciais, se necessário.
|
||||
|
||||
> Este projeto possui seu próprio `appsettings.json`arquivo. Portanto, se você deseja alterar a cadeia de conexão do banco de dados, lembre-se de alterar também esse arquivo.
|
||||
|
||||
Especialmente, semear dados iniciais é importante neste momento. A ABP possui uma infraestrutura modular de semente de dados. Consulte [a documentação](../Data-Seeding.md) para obter mais informações sobre a propagação de dados.
|
||||
|
||||
Embora a criação de banco de dados e a aplicação de migrações pareça necessária apenas para bancos de dados relacionais, esse projeto ocorre mesmo que você escolha um provedor de banco de dados NoSQL (como o MongoDB). Nesse caso, ele ainda semeia os dados iniciais necessários para a aplicação.
|
||||
|
||||
- Depende do `.EntityFrameworkCore.DbMigrations`projeto (para EF Core), pois ele precisa acessar as migrações.
|
||||
- Depende do `.Application.Contracts`projeto para poder acessar as definições de permissão, porque o semeador de dados inicial concede todas as permissões para a função de administrador por padrão.
|
||||
|
||||
#### Projeto .HttpApi
|
||||
|
||||
Este projeto é usado para definir seus controladores de API.
|
||||
|
||||
Na maioria das vezes, você não precisa definir manualmente os controladores de API, pois o recurso de [controladores de API automática](../AspNetCore/Auto-API-Controllers.md) da ABP os cria automaticamente, com base na sua camada de aplicação. No entanto, no caso de você precisar escrever controladores de API, este é o melhor lugar para fazê-lo.
|
||||
|
||||
- Depende do `.Application.Contracts`projeto para poder injetar as interfaces de serviço do aplicativo.
|
||||
|
||||
#### Projeto .HttpApi.Client
|
||||
|
||||
Este é um projeto que define os proxies do cliente C # para usar as APIs HTTP da solução. Você pode compartilhar essa biblioteca com clientes de terceiros, para que eles consumam facilmente suas APIs HTTP em seus aplicativos Dotnet (para outros tipos de aplicativos, eles ainda podem usar suas APIs, manualmente ou usando uma ferramenta em sua própria plataforma)
|
||||
|
||||
Na maioria das vezes, você não precisa criar proxies de clientes C # manualmente, graças ao recurso [Dynamic C # API Clients](../AspNetCore/Dynamic-CSharp-API-Clients.md) da ABP .
|
||||
|
||||
`.HttpApi.Client.ConsoleTestApp` project é um aplicativo de console criado para demonstrar o uso dos proxies do cliente.
|
||||
|
||||
- Depende do `.Application.Contracts`projeto para poder compartilhar as mesmas interfaces de serviço de aplicativo e DTOs com o serviço remoto.
|
||||
|
||||
> Você pode excluir este projeto e dependências se não precisar criar proxies de cliente C # para suas APIs.
|
||||
|
||||
#### Projeto .Web
|
||||
|
||||
Este projeto contém a interface do usuário (UI) do aplicativo se você estiver usando a interface do usuário do ASP.NET Core MVC. Ele contém páginas Razor, arquivos JavaScript, arquivos CSS, imagens e assim por diante ...
|
||||
|
||||
Este projeto contém o `appsettings.json`arquivo principal que contém a cadeia de conexão e outras configurações do aplicativo.
|
||||
|
||||
- Depende da `.HttpApi`camada de interface do usuário que precisa usar APIs e interfaces de serviço de aplicativo da solução.
|
||||
|
||||
> Se você verificar o código fonte do `.Web.csproj`arquivo, verá as referências aos `.Application`e aos `.EntityFrameworkCore.DbMigrations`projetos.
|
||||
>
|
||||
> Na verdade, essas referências não são necessárias durante a codificação da camada da interface do usuário, porque a camada da interface do usuário normalmente não depende da implementação do EF Core ou da camada do Aplicativo. Esses modelos de inicialização estão prontos para a implantação em camadas, em que a camada da API está hospedada em um servidor separado da camada da interface do usuário.
|
||||
>
|
||||
> No entanto, se você não escolher a opção `--tiered`, essas referências estarão no projeto .Web para poder hospedar as camadas da Web, API e aplicativos em um único ponto de extremidade do aplicativo.
|
||||
>
|
||||
> Isso permite que você use entidades e repositórios de domínio em sua camada de apresentação. No entanto, isso é considerado uma má prática de acordo com o DDD.
|
||||
|
||||
#### Projetos de teste
|
||||
|
||||
A solução possui vários projetos de teste, um para cada camada:
|
||||
|
||||
- `.Domain.Tests` é usado para testar a camada de domínio.
|
||||
- `.Application.Tests` é usado para testar a camada de aplicativo.
|
||||
- `.EntityFrameworkCore.Tests` é usado para testar a configuração do EF Core e os repositórios personalizados.
|
||||
- `.Web.Tests` é usado para testar a interface do usuário (se você estiver usando a interface do ASP.NET Core MVC).
|
||||
- `.TestBase` é um projeto básico (compartilhado) para todos os testes.
|
||||
|
||||
Além disso, `.HttpApi.Client.ConsoleTestApp`é um aplicativo de console (não um projeto de teste automatizado) que demonstra o uso de APIs HTTP de um aplicativo .NET.
|
||||
|
||||
Projetos de teste são preparados para testes de integração;
|
||||
|
||||
- É totalmente integrado à estrutura ABP e a todos os serviços em sua aplicação.
|
||||
- Ele usa o banco de dados SQLite na memória para o EF Core. Para o MongoDB, ele usa a biblioteca [Mongo2Go](https://github.com/Mongo2Go/Mongo2Go) .
|
||||
- A autorização está desabilitada, portanto, qualquer serviço de aplicativo pode ser facilmente usado em testes.
|
||||
|
||||
Você ainda pode criar testes de unidade para suas classes, que serão mais difíceis de escrever (porque você precisará preparar objetos simulados / falsos), mas mais rápidos de executar (porque apenas testa uma única classe e ignora todo o processo de inicialização).
|
||||
|
||||
#### Como correr?
|
||||
|
||||
Defina `.Web`como o projeto de inicialização e execute o aplicativo. O nome de usuário padrão é `admin`e a senha é `1q2w3E*`.
|
||||
|
||||
Consulte [Introdução ao modelo ASP.NET Core MVC](../Getting-Started-AspNetCore-MVC-Template.md) para obter mais informações.
|
||||
|
||||
### Estrutura em camadas
|
||||
|
||||
Se você selecionou a interface do usuário do ASP.NET Core e especificou a `--tiered`opção, a solução criada será uma solução em camadas. O objetivo da estrutura em camadas é poder **implantar aplicativos da Web e API HTTP em diferentes servidores** :
|
||||
|
||||

|
||||
|
||||
- O navegador executa sua interface do usuário executando HTML, CSS e JavaScript.
|
||||
- Os servidores da Web hospedam arquivos de interface do usuário estáticos (CSS, JavaScript, imagem ... etc.) e componentes dinâmicos (por exemplo, páginas Razor). Ele executa solicitações HTTP para o servidor da API para executar a lógica de negócios do aplicativo.
|
||||
- O API Server hospeda as APIs HTTP que, em seguida, usam as camadas de aplicativo e domínio do aplicativo para executar a lógica de negócios.
|
||||
- Finalmente, o servidor de banco de dados hospeda seu banco de dados.
|
||||
|
||||
Portanto, a solução resultante permite uma implantação em quatro camadas, comparando com a implantação em três camadas da estrutura padrão explicada anteriormente.
|
||||
|
||||
> A menos que você realmente precise de uma implantação em quatro camadas, é recomendável seguir a estrutura padrão que é mais simples de desenvolver, implantar e manter.
|
||||
|
||||
A estrutura da solução é mostrada abaixo:
|
||||
|
||||

|
||||
|
||||
Diferente da estrutura padrão, dois novos projetos entram em jogo: `.IdentityServer`& `.HttpApi.Host`.
|
||||
|
||||
#### Projeto .IdentityServer
|
||||
|
||||
Este projeto é usado como um servidor de autenticação para outros projetos. `.Web`O projeto usa a autenticação do OpenId Connect para obter tokens de identidade e acesso para o usuário atual do IdentityServer. Em seguida, usa o token de acesso para chamar o servidor da API HTTP. O servidor HTTP API usa autenticação de token de portador para obter declarações do token de acesso para autorizar o usuário atual.
|
||||
|
||||

|
||||
|
||||
O ABP usa a estrutura [IdentityServer4 de](https://identityserver.io/) código aberto para a autenticação entre aplicativos. Consulte a [documentação do IdentityServer4](http://docs.identityserver.io/) para obter detalhes sobre o protocolo IdentityServer4 e OpenID Connect.
|
||||
|
||||
Ele possui seu próprio `appsettings.json`que contém conexão com o banco de dados e outras configurações.
|
||||
|
||||
#### Projeto .HttpApi.Host
|
||||
|
||||
Este projeto é um aplicativo que hospeda a API da solução. Ele possui seu próprio `appsettings.json`que contém conexão com o banco de dados e outras configurações.
|
||||
|
||||
#### Projeto .Web
|
||||
|
||||
Assim como a estrutura padrão, este projeto contém a interface do usuário (UI) do aplicativo. Ele contém páginas de barbear, arquivos JavaScript, arquivos de estilo, imagens e assim por diante ...
|
||||
|
||||
Este projeto contém um `appsettings.json`arquivo, mas desta vez não possui uma cadeia de conexão porque nunca se conecta ao banco de dados. Em vez disso, ele contém principalmente o terminal do servidor de API remoto e o servidor de autenticação.
|
||||
|
||||
#### Pré requisitos
|
||||
|
||||
- [Redis](https://redis.io/) : os aplicativos usam Redis como cache distribuído. Então, você precisa ter o Redis instalado e funcionando.
|
||||
|
||||
#### Como correr?
|
||||
|
||||
Você deve executar o aplicativo com a ordem especificada:
|
||||
|
||||
- Primeiro, execute o `.IdentityServer`aplicativo, pois outros aplicativos dependem dele.
|
||||
- Em seguida, execute o `.HttpApi.Host`que é usado pelo `.Web`aplicativo.
|
||||
- Por fim, você pode executar o `.Web`projeto e efetuar login no aplicativo (usando `admin`como nome de usuário e `1q2w3E*`senha).
|
||||
|
||||
### UI angular
|
||||
|
||||
Se você escolher Angular como a estrutura da interface do usuário (usando a `-u angular`opção), a solução será separada em duas pastas:
|
||||
|
||||
- `angular` A pasta contém a solução Angular UI, do lado do cliente.
|
||||
- `aspnet-core` A pasta contém a solução ASP.NET Core, do lado do servidor.
|
||||
|
||||
O lado do servidor é muito semelhante à solução descrita acima. `.HttpApi.Host`projeto serve a API, para que o aplicativo Angular possa consumi-lo.
|
||||
|
||||
Os arquivos na `angular/src/environments`pasta têm a configuração essencial do aplicativo.
|
||||
|
||||
## Qual é o próximo?
|
||||
|
||||
- Consulte [Introdução ao modelo ASP.NET Core MVC](../Getting-Started-AspNetCore-MVC-Template.md) para criar uma nova solução e executá-la para este modelo.
|
||||
- Consulte o [Tutorial](../Tutorials/AspNetCore-Mvc/Part-I.md) do [ASP.NET Core MVC](../Tutorials/AspNetCore-Mvc/Part-I.md) para aprender como desenvolver aplicativos usando este modelo.
|
||||
|
||||
|
||||
@ -0,0 +1,7 @@
|
||||
# Modelos de inicialização
|
||||
|
||||
Embora você possa começar com um projeto vazio e adicionar os pacotes necessários manualmente, os modelos de inicialização facilitam e são confortáveis para iniciar uma nova solução com a estrutura ABP. Clique no nome da lista abaixo para ver a documentação do modelo de inicialização relacionado:
|
||||
|
||||
- [**app**](Application.md) : modelo de aplicativo.
|
||||
- [**módulo**](Module.md) : Módulo / modelo de serviço.
|
||||
|
||||
@ -0,0 +1,161 @@
|
||||
# Modelo de inicialização do módulo MVC
|
||||
|
||||
Este modelo pode ser usado para criar um **módulo de aplicativo** **reutilizável com** base nas [melhores práticas e convenções de desenvolvimento do módulo](../Best-Practices/Index.md). Também é adequado para criar **microsserviços** (com ou sem interface do usuário).
|
||||
|
||||
## Como começar?
|
||||
|
||||
Você pode usar a [ABP CLI](../CLI.md) para criar um novo projeto usando este modelo de inicialização. Como alternativa, você pode criar e fazer o download diretamente na página [Introdução](https://abp.io/get-started) . A abordagem CLI é usada aqui.
|
||||
|
||||
Primeiro, instale a ABP CLI se você não tiver instalado antes:
|
||||
|
||||
```bash
|
||||
dotnet tool install -g Volo.Abp.Cli
|
||||
```
|
||||
|
||||
Em seguida, use o `abp new`comando em uma pasta vazia para criar uma nova solução:
|
||||
|
||||
```bash
|
||||
abp new Acme.IssueManagement -t module
|
||||
```
|
||||
|
||||
- `Acme.IssueManagement`é o nome da solução, como *YourCompany.YourProduct* . Você pode usar nomes de nível único, dois ou três níveis.
|
||||
|
||||
### Sem interface de usuário
|
||||
|
||||
O modelo vem com uma interface do usuário do MVC por padrão. Você pode usar a `--no-ui`opção para não incluir a camada da interface do usuário.
|
||||
|
||||
```bash
|
||||
abp new Acme.IssueManagement -t mvc-module --no-ui
|
||||
```
|
||||
|
||||
## Estrutura da solução
|
||||
|
||||
Com base nas opções especificadas, você obterá uma estrutura de solução ligeiramente diferente. Se você não especificar nenhuma opção, terá uma solução como a mostrada abaixo:
|
||||
|
||||

|
||||
|
||||
Projetos são organizados como `src`, `test`e `host`pastas:
|
||||
|
||||
- `src`A pasta contém o módulo real, que é estratificado com base nos princípios [DDD](../Domain-Driven-Design.md) .
|
||||
- `test` pasta contém testes de unidade e integração.
|
||||
- `host`A pasta contém aplicativos com configurações diferentes para demonstrar como hospedar o módulo em um aplicativo. Isso não faz parte do módulo, mas é útil no desenvolvimento.
|
||||
|
||||
O diagrama abaixo mostra as camadas e dependências do projeto do módulo:
|
||||
|
||||

|
||||
|
||||
Cada seção abaixo explicará o projeto relacionado e suas dependências.
|
||||
|
||||
### Projeto .Domain.Shared
|
||||
|
||||
Este projeto contém constantes, enumerações e outros objetos. Na verdade, eles fazem parte da camada de domínio, mas precisam ser usados por todas as camadas / projetos da solução.
|
||||
|
||||
Um `IssueType`enum e uma `IssueConsts`classe (que podem ter alguns campos constantes para a `Issue`entidade, como `MaxTitleLength`) são bons candidatos para este projeto.
|
||||
|
||||
- Este projeto não depende de outros projetos na solução. Todos os outros projetos dependem disso direta ou indiretamente.
|
||||
|
||||
### .Domain Project
|
||||
|
||||
Essa é a camada de domínio da solução. Ele contém principalmente [entidades, raízes agregadas](../Entities.md) , [serviços de domínio](../Domain-Services.md) , [tipos de valor](../Value-Types.md) , [interfaces de repositório](../Repositories.md) e outros objetos de domínio.
|
||||
|
||||
Uma `Issue`entidade, um `IssueManager`serviço de domínio e uma `IIssueRepository`interface são bons candidatos para este projeto.
|
||||
|
||||
- Depende do `.Domain.Shared`porque usa constantes, enumerações e outros objetos definidos nesse projeto.
|
||||
|
||||
### .Application.Contracts Project
|
||||
|
||||
Este projeto contém principalmente **interfaces de** [serviço de aplicativo](../Application-Services.md) e DTO ( [Data Transfer Objects](../Data-Transfer-Objects.md) ) da camada de aplicativo. Existe para separar a interface e a implementação da camada de aplicação. Dessa forma, o projeto de interface pode ser compartilhado com os clientes como um pacote de contrato.
|
||||
|
||||
Uma `IIssueAppService`interface e uma `IssueCreationDto`classe são boas candidatas para este projeto.
|
||||
|
||||
- Depende do `.Domain.Shared`porque ele pode usar constantes, enumerações e outros objetos compartilhados deste projeto nas interfaces de serviço de aplicativo e DTOs.
|
||||
|
||||
### Projeto de Aplicação
|
||||
|
||||
Este projeto contém as **implementações** de [serviço de aplicativo](../Application-Services.md) das interfaces definidas no projeto.`.Application.Contracts`
|
||||
|
||||
Uma `IssueAppService`turma é uma boa candidata para este projeto.
|
||||
|
||||
- Depende do `.Application.Contracts`projeto para poder implementar as interfaces e usar os DTOs.
|
||||
- Depende do `.Domain`projeto para poder usar objetos de domínio (entidades, interfaces de repositório ... etc.) para executar a lógica do aplicativo.
|
||||
|
||||
### Projeto .EntityFrameworkCore
|
||||
|
||||
Este é o projeto de integração do EF Core. Ele define `DbContext`e implementa as interfaces de repositório definidas no `.Domain`projeto.
|
||||
|
||||
- Depende do `.Domain`projeto para poder fazer referência a entidades e interfaces de repositório.
|
||||
|
||||
> Você pode excluir este projeto se não desejar dar suporte ao EF Core para o seu módulo.
|
||||
|
||||
### Projeto .MongoDB
|
||||
|
||||
Este é o projeto de integração do MongoDB.
|
||||
|
||||
- Depende do `.Domain`projeto para poder fazer referência a entidades e interfaces de repositório.
|
||||
|
||||
> Você pode excluir este projeto se não quiser dar suporte ao MongoDB para o seu módulo.
|
||||
|
||||
### Projetos de teste
|
||||
|
||||
A solução possui vários projetos de teste, um para cada camada:
|
||||
|
||||
- `.Domain.Tests` é usado para testar a camada de domínio.
|
||||
- `.Application.Tests` é usado para testar a camada de aplicativo.
|
||||
- `.EntityFrameworkCore.Tests` é usado para testar a configuração do EF Core e os repositórios personalizados.
|
||||
- `.MongoDB.Tests` é usado para testar a configuração do MongoDB e os repositórios personalizados.
|
||||
- `.TestBase` é um projeto básico (compartilhado) para todos os testes.
|
||||
|
||||
Além disso, `.HttpApi.Client.ConsoleTestApp`é um aplicativo de console (não um projeto de teste automatizado) que demonstra o uso de APIs HTTP de um aplicativo Dotnet.
|
||||
|
||||
Projetos de teste são preparados para testes de integração;
|
||||
|
||||
- É totalmente integrado à estrutura ABP e a todos os serviços em sua aplicação.
|
||||
- Ele usa o banco de dados SQLite na memória para o EF Core. Para o MongoDB, ele usa a biblioteca [Mongo2Go](https://github.com/Mongo2Go/Mongo2Go) .
|
||||
- A autorização está desabilitada, portanto, qualquer serviço de aplicativo pode ser facilmente usado em testes.
|
||||
|
||||
Você ainda pode criar testes de unidade para suas classes, que serão mais difíceis de escrever (porque você precisará preparar objetos simulados / falsos), mas mais rápidos de executar (porque apenas testa uma única classe e ignora todo o processo de inicialização).
|
||||
|
||||
> Os testes de domínio e aplicativos estão usando o EF Core. Se você remover a integração do EF Core ou desejar usar o MongoDB para testar essas camadas, altere manualmente as referências do projeto e as dependências do módulo.
|
||||
|
||||
### Projetos Anfitriões
|
||||
|
||||
A solução possui alguns aplicativos host para executar seu módulo. Aplicativos host são usados para executar seu módulo em um aplicativo totalmente configurado. É útil no desenvolvimento. Os aplicativos host incluem alguns outros módulos além do módulo que está sendo desenvolvido:
|
||||
|
||||
Os aplicativos host oferecem suporte a dois tipos de cenários.
|
||||
|
||||
#### Cenário de aplicativo único (unificado)
|
||||
|
||||
Se o seu módulo tiver uma interface do usuário, o `.Web.Unified`aplicativo será usado para hospedar a interface do usuário e a API em um único ponto. Ele possui seu próprio `appsettings.json`arquivo (que inclui a cadeia de conexão do banco de dados) e as migrações do banco de dados EF Core.
|
||||
|
||||
Para o `.Web.Unified`aplicativo, há um único banco de dados chamado `YourProjectName_Unified`(como *IssueManagement_Unified* para esta amostra).
|
||||
|
||||
> Se você selecionou a `--no-ui`opção, este projeto não estará na sua solução.
|
||||
|
||||
##### Como correr?
|
||||
|
||||
Defina-o como o projeto de inicialização, execute o `Update-Database`comando para o EF Core no Package Manager Console e execute seu aplicativo. O nome de usuário padrão é `admin`e a senha é `1q2w3E*`.
|
||||
|
||||
#### Implantação separada e cenário de bancos de dados
|
||||
|
||||
Nesse cenário, há três aplicativos;
|
||||
|
||||
- `.IdentityServer`application é um servidor de autenticação usado por outros aplicativos. Ele possui seu próprio `appsettings.json`que contém conexão com o banco de dados e outras configurações.
|
||||
- `.HttpApi.Host`hospeda a API HTTP do módulo. Ele possui seu próprio `appsettings.json`que contém conexões com o banco de dados e outras configurações.
|
||||
- `.Web.Host`hospedar a interface do usuário do módulo. Este projeto contém um `appsettings.json`arquivo, mas não possui uma cadeia de conexão porque nunca se conecta ao banco de dados. Em vez disso, ele contém principalmente o terminal do servidor de API remoto e o servidor de autenticação.
|
||||
|
||||
O diagrama abaixo mostra a relação dos aplicativos:
|
||||
|
||||

|
||||
|
||||
`.Web.Host`O projeto usa a autenticação OpenId Connect para obter tokens de identidade e acesso para o usuário atual do `.IdentityServer`. Em seguida, usa o token de acesso para chamar o `.HttpApi.Host`. O servidor HTTP API usa autenticação de token de portador para obter declarações do token de acesso para autorizar o usuário atual.
|
||||
|
||||
##### Como correr?
|
||||
|
||||
Você deve executar o aplicativo com a ordem especificada:
|
||||
|
||||
- Primeiro, execute o `.IdentityServer`aplicativo, pois outros aplicativos dependem dele.
|
||||
- Em seguida, execute o `.HttpApi.Host`que é usado pelo `.Web.Host`aplicativo.
|
||||
- Por fim, você pode executar o `.Web.Host`projeto e efetuar login no aplicativo usando `admin`como nome de usuário e `1q2w3E*`senha.
|
||||
|
||||
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
# Testing
|
||||
|
||||
Façam!
|
||||
@ -0,0 +1,661 @@
|
||||
## Tutorial Angular - Parte I
|
||||
|
||||
### Sobre este tutorial
|
||||
|
||||
Nesta série de tutoriais, você criará um aplicativo usado para gerenciar uma lista de livros e seus autores. **Angular** será usado como estrutura da interface do usuário e **MongoDB** será usado como provedor de banco de dados.
|
||||
|
||||
Esta é a primeira parte da série de tutoriais angulares. Veja todas as peças:
|
||||
|
||||
- **Parte I: Crie o projeto e uma página de lista de livros (este tutorial)**
|
||||
- [Parte II: Criar, atualizar e excluir livros](Part-II)
|
||||
- [Parte III: Testes de Integração](Part-III)
|
||||
|
||||
Você pode acessar o **código fonte** do aplicativo no [repositório GitHub](https://github.com/abpframework/abp/tree/dev/samples/BookStore-Angular-MongoDb) .
|
||||
|
||||
### Criando o projeto
|
||||
|
||||
Crie um novo projeto nomeado `Acme.BookStore`selecionando Angular como a estrutura da interface do usuário e MongoDB como o provedor de banco de dados, crie o banco de dados e execute o aplicativo seguindo o [documento Introdução](../../Getting-Started-Angular-Template.md) .
|
||||
|
||||
### Estrutura da solução (back-end)
|
||||
|
||||
É assim que a estrutura da solução em camadas cuida da criação:
|
||||
|
||||

|
||||
|
||||
> Você pode ver o [documento do modelo de aplicativo](../../Startup-Templates/Application.md) para entender a estrutura da solução em detalhes. No entanto, você entenderá o básico com este tutorial.
|
||||
|
||||
### Criar a entidade do livro
|
||||
|
||||
A camada de domínio no modelo de inicialização é separada em dois projetos:
|
||||
|
||||
- `Acme.BookStore.Domain`contém suas [entidades](../../Entities.md) , [serviços de domínio](../../Domain-Services.md) e outros objetos principais de domínio.
|
||||
- `Acme.BookStore.Domain.Shared` contém constantes, enumerações ou outros objetos relacionados ao domínio que podem ser compartilhados com os clientes.
|
||||
|
||||
Defina [entidades](../../Entities.md) na **camada de domínio** ( `Acme.BookStore.Domain`projeto) da solução. A entidade principal do aplicativo é a `Book`. Crie uma classe, chamada `Book`, no `Acme.BookStore.Domain`projeto, como mostrado abaixo:
|
||||
|
||||
```csharp
|
||||
using System;
|
||||
using Volo.Abp.Domain.Entities.Auditing;
|
||||
|
||||
namespace Acme.BookStore
|
||||
{
|
||||
public class Book : AuditedAggregateRoot<Guid>
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public BookType Type { get; set; }
|
||||
|
||||
public DateTime PublishDate { get; set; }
|
||||
|
||||
public float Price { get; set; }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- O ABP possui duas classes base fundamentais para entidades: `AggregateRoot`e `Entity`. **A raiz agregada** é um dos conceitos de **DDD (Domain Driven Design)** . Consulte o [documento da entidade](../../Entities.md) para obter detalhes e melhores práticas.
|
||||
- `Book`entidade herda `AuditedAggregateRoot`que adiciona algumas propriedades de auditoria ( `CreationTime`, `CreatorId`, `LastModificationTime`... etc.) no topo da `AggregateRoot`classe.
|
||||
- `Guid`é o **tipo** de **chave primária** da `Book`entidade.
|
||||
|
||||
#### BookType Enum
|
||||
|
||||
Defina a `BookType`enumeração no `Acme.BookStore.Domain.Shared`projeto:
|
||||
|
||||
```csharp
|
||||
namespace Acme.BookStore
|
||||
{
|
||||
public enum BookType
|
||||
{
|
||||
Undefined,
|
||||
Adventure,
|
||||
Biography,
|
||||
Dystopia,
|
||||
Fantastic,
|
||||
Horror,
|
||||
Science,
|
||||
ScienceFiction,
|
||||
Poetry
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Adicionar entidade de livro ao seu DbContext
|
||||
|
||||
Adicione uma `IMongoCollection`propriedade ao `BookStoreMongoDbContext`interior do `Acme.BookStore.MongoDB`projeto:
|
||||
|
||||
```csharp
|
||||
public class BookStoreMongoDbContext : AbpMongoDbContext
|
||||
{
|
||||
public IMongoCollection<Book> Books => Collection<Book>();
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
#### Adicionar dados de semente (amostra)
|
||||
|
||||
Esta seção é opcional, mas seria bom ter um dado inicial no banco de dados na primeira execução. O ABP fornece um [sistema de semente de dados](../../Data-Seeding.md) . Crie uma classe derivada de `IDataSeedContributor`no `.Domain`projeto:
|
||||
|
||||
```csharp
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Volo.Abp.Data;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Volo.Abp.Domain.Repositories;
|
||||
|
||||
namespace Acme.BookStore
|
||||
{
|
||||
public class BookStoreDataSeederContributor
|
||||
: IDataSeedContributor, ITransientDependency
|
||||
{
|
||||
private readonly IRepository<Book, Guid> _bookRepository;
|
||||
|
||||
public BookStoreDataSeederContributor(IRepository<Book, Guid> bookRepository)
|
||||
{
|
||||
_bookRepository = bookRepository;
|
||||
}
|
||||
|
||||
public async Task SeedAsync(DataSeedContext context)
|
||||
{
|
||||
if (await _bookRepository.GetCountAsync() > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await _bookRepository.InsertAsync(
|
||||
new Book
|
||||
{
|
||||
Name = "1984",
|
||||
Type = BookType.Dystopia,
|
||||
PublishDate = new DateTime(1949, 6, 8),
|
||||
Price = 19.84f
|
||||
}
|
||||
);
|
||||
|
||||
await _bookRepository.InsertAsync(
|
||||
new Book
|
||||
{
|
||||
Name = "The Hitchhiker's Guide to the Galaxy",
|
||||
Type = BookType.ScienceFiction,
|
||||
PublishDate = new DateTime(1995, 9, 27),
|
||||
Price = 42.0f
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`BookStoreDataSeederContributor`simplesmente insere dois livros no banco de dados se não houver nenhum livro adicionado antes. O ABP descobre e executa automaticamente essa classe quando você propaga o banco de dados executando o `Acme.BookStore.DbMigrator`projeto.
|
||||
|
||||
### Crie o serviço de aplicativo
|
||||
|
||||
O próximo passo é criar um [serviço de aplicativo](../../Application-Services.md) para gerenciar (criar, listar, atualizar, excluir ...) os livros. A camada de aplicativo no modelo de inicialização é separada em dois projetos:
|
||||
|
||||
- `Acme.BookStore.Application.Contracts` contém principalmente seus DTOs e interfaces de serviço de aplicativo.
|
||||
- `Acme.BookStore.Application` contém as implementações dos seus serviços de aplicativo.
|
||||
|
||||
#### BookDto
|
||||
|
||||
Crie uma classe DTO denominada `BookDto`no `Acme.BookStore.Application.Contracts`projeto:
|
||||
|
||||
```csharp
|
||||
using System;
|
||||
using Volo.Abp.Application.Dtos;
|
||||
|
||||
namespace Acme.BookStore
|
||||
{
|
||||
public class BookDto : AuditedEntityDto<Guid>
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public BookType Type { get; set; }
|
||||
|
||||
public DateTime PublishDate { get; set; }
|
||||
|
||||
public float Price { get; set; }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- **As** classes **DTO** são usadas para **transferir dados** entre a *camada de apresentação* e a *camada de aplicativo* . Consulte o [documento Objetos de transferência de dados](../../Data-Transfer-Objects.md) para obter mais detalhes.
|
||||
- `BookDto` é usado para transferir dados do livro para a camada de apresentação para mostrar as informações do livro na interface do usuário.
|
||||
- `BookDto`é derivado do `AuditedEntityDto<Guid>`que possui propriedades de auditoria exatamente como a `Book`classe definida acima.
|
||||
|
||||
Será necessário converter `Book`entidades em `BookDto`objetos enquanto retorna os livros para a camada de apresentação. A biblioteca do [AutoMapper](https://automapper.org/) pode automatizar essa conversão quando você define o mapeamento adequado. O modelo de inicialização é fornecido com o AutoMapper configurado, para que você possa definir o mapeamento na `BookStoreApplicationAutoMapperProfile`classe no `Acme.BookStore.Application`projeto:
|
||||
|
||||
```csharp
|
||||
using AutoMapper;
|
||||
|
||||
namespace Acme.BookStore
|
||||
{
|
||||
public class BookStoreApplicationAutoMapperProfile : Profile
|
||||
{
|
||||
public BookStoreApplicationAutoMapperProfile()
|
||||
{
|
||||
CreateMap<Book, BookDto>();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### CreateUpdateBookDto
|
||||
|
||||
Crie uma classe DTO denominada `CreateUpdateBookDto`no `Acme.BookStore.Application.Contracts`projeto:
|
||||
|
||||
```csharp
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Acme.BookStore
|
||||
{
|
||||
public class CreateUpdateBookDto
|
||||
{
|
||||
[Required]
|
||||
[StringLength(128)]
|
||||
public string Name { get; set; }
|
||||
|
||||
[Required]
|
||||
public BookType Type { get; set; } = BookType.Undefined;
|
||||
|
||||
[Required]
|
||||
public DateTime PublishDate { get; set; }
|
||||
|
||||
[Required]
|
||||
public float Price { get; set; }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- Essa classe DTO é usada para obter informações do livro a partir da interface do usuário ao criar ou atualizar um livro.
|
||||
- Ele define atributos de anotação de dados (como `[Required]`) para definir validações para as propriedades. Os DTOs são [validados automaticamente](../../Validation.md) pela estrutura ABP.
|
||||
|
||||
Em seguida, adicione um mapeamento `BookStoreApplicationAutoMapperProfile`do `CreateUpdateBookDto`objeto à `Book`entidade:
|
||||
|
||||
```csharp
|
||||
CreateMap<CreateUpdateBookDto, Book>();
|
||||
```
|
||||
|
||||
#### IBookAppService
|
||||
|
||||
Defina uma interface nomeada `IBookAppService`no `Acme.BookStore.Application.Contracts`projeto:
|
||||
|
||||
```csharp
|
||||
using System;
|
||||
using Volo.Abp.Application.Dtos;
|
||||
using Volo.Abp.Application.Services;
|
||||
|
||||
namespace Acme.BookStore
|
||||
{
|
||||
public interface IBookAppService :
|
||||
ICrudAppService< //Defines CRUD methods
|
||||
BookDto, //Used to show books
|
||||
Guid, //Primary key of the book entity
|
||||
PagedAndSortedResultRequestDto, //Used for paging/sorting on getting a list of books
|
||||
CreateUpdateBookDto, //Used to create a new book
|
||||
CreateUpdateBookDto> //Used to update a book
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- A definição de interfaces para serviços de aplicativos não é requerida pela estrutura. No entanto, é sugerido como uma prática recomendada.
|
||||
- `ICrudAppService`define comuns **CRUD** métodos: `GetAsync`, `GetListAsync`, `CreateAsync`, `UpdateAsync`e `DeleteAsync`. Não é necessário estendê-lo. Em vez disso, você pode herdar da `IApplicationService`interface vazia e definir seus próprios métodos manualmente.
|
||||
- Existem algumas variações de `ICrudAppService`onde você pode usar DTOs separados para cada método.
|
||||
|
||||
#### BookAppService
|
||||
|
||||
Implemente `IBookAppService`como nomeado `BookAppService`no `Acme.BookStore.Application`projeto:
|
||||
|
||||
```csharp
|
||||
using System;
|
||||
using Volo.Abp.Application.Dtos;
|
||||
using Volo.Abp.Application.Services;
|
||||
using Volo.Abp.Domain.Repositories;
|
||||
|
||||
namespace Acme.BookStore
|
||||
{
|
||||
public class BookAppService :
|
||||
CrudAppService<Book, BookDto, Guid, PagedAndSortedResultRequestDto,
|
||||
CreateUpdateBookDto, CreateUpdateBookDto>,
|
||||
IBookAppService
|
||||
{
|
||||
public BookAppService(IRepository<Book, Guid> repository)
|
||||
: base(repository)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- `BookAppService`é derivado do `CrudAppService<...>`qual implementa todos os métodos CRUD definidos acima.
|
||||
- `BookAppService`injeta `IRepository<Book, Guid>`qual é o repositório padrão da `Book`entidade. O ABP cria automaticamente repositórios padrão para cada raiz (ou entidade) agregada. Veja o [documento do repositório](../../Repositories) .
|
||||
- `BookAppService`usa `IObjectMapper`para converter `Book`objetos em `BookDto`objetos e `CreateUpdateBookDto`objetos em `Book`objetos. O modelo de inicialização usa a biblioteca [AutoMapper](http://automapper.org/) como o provedor de mapeamento de objetos. Você definiu os mapeamentos antes, para que funcionem conforme o esperado.
|
||||
|
||||
### Controladores de API automática
|
||||
|
||||
Você normalmente cria **controladores** para expor serviços de aplicativos como pontos de extremidade da **API HTTP** . Assim, permite que navegadores ou clientes de terceiros os chamem via AJAX. O ABP pode configurar [**automaticamente**](../../AspNetCore/Auto-API-Controllers.md) seus serviços de aplicativo como controladores de API MVC por convenção.
|
||||
|
||||
#### UI do Swagger
|
||||
|
||||
O modelo de inicialização está configurado para executar a [interface do usuário do swagger](https://swagger.io/tools/swagger-ui/) usando a biblioteca [Swashbuckle.AspNetCore](https://github.com/domaindrivendev/Swashbuckle.AspNetCore) . Execute o `Acme.BookStore.HttpApi.Host`aplicativo e insira `https://localhost:XXXX/swagger/`(substitua XXXX por sua própria porta) como URL no seu navegador.
|
||||
|
||||
Você verá alguns pontos de extremidade de serviço internos, bem como o `Book`serviço e seus pontos de extremidade no estilo REST:
|
||||
|
||||

|
||||
|
||||
O Swagger tem uma ótima interface para testar APIs. Você pode tentar executar a `[GET] /api/app/book`API para obter uma lista de livros.
|
||||
|
||||
### Crie a página de livros
|
||||
|
||||
Neste tutorial;
|
||||
|
||||
- [A CLI angular](https://angular.io/cli) será usada para criar módulos, componentes e serviços
|
||||
- [NGXS](https://ngxs.gitbook.io/ngxs/) será usado como a biblioteca de gerenciamento de estado
|
||||
- [O Bootstrap](https://ng-bootstrap.github.io/#/home) será usado como a biblioteca de componentes da interface do usuário.
|
||||
- [O Visual Studio Code](https://code.visualstudio.com/) será usado como editor de código (você pode usar seu editor favorito).
|
||||
|
||||
#### Instalar pacotes NPM
|
||||
|
||||
Abra uma janela do terminal, vá para a `angular`pasta e execute o `yarn` comando para instalar os pacotes NPM:
|
||||
|
||||
```
|
||||
yarn
|
||||
```
|
||||
|
||||
#### BooksModule
|
||||
|
||||
Execute a seguinte linha de comando para criar um novo módulo, denominado `BooksModule`:
|
||||
|
||||
```bash
|
||||
yarn ng generate module books --route books --module app.module
|
||||
```
|
||||
|
||||

|
||||
|
||||
Execute `yarn start`, aguarde Angular para executar o aplicativo e abra `http://localhost:4200/books`em um navegador:
|
||||
|
||||

|
||||
|
||||
#### Encaminhamento
|
||||
|
||||
Abra `app-routing.module.ts`e substitua `books`conforme mostrado abaixo:
|
||||
|
||||
```js
|
||||
import { ApplicationLayoutComponent } from '@abp/ng.theme.basic';-
|
||||
|
||||
//...
|
||||
{
|
||||
path: 'books',
|
||||
component: ApplicationLayoutComponent,
|
||||
loadChildren: () => import('./books/books.module').then(m => m.BooksModule),
|
||||
data: {
|
||||
routes: {
|
||||
name: 'Books',
|
||||
} as ABP.Route,
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
`ApplicationLayoutComponent`configuração define o layout do aplicativo para a nova página. Se você deseja ver sua rota na barra de navegação (menu principal), também deve adicionar o `data`objeto com `name`propriedade à sua rota.
|
||||
|
||||

|
||||
|
||||
#### Componente da lista de livros
|
||||
|
||||
Primeiro, substitua pela `books.component.html`seguinte linha para colocar a saída do roteador:
|
||||
|
||||
```html
|
||||
<router-outlet></router-outlet>
|
||||
```
|
||||
|
||||
Em seguida, execute o comando abaixo no terminal na pasta raiz para gerar um novo componente, chamado book-list:
|
||||
|
||||
```bash
|
||||
yarn ng generate component books/book-list
|
||||
```
|
||||
|
||||

|
||||
|
||||
Importe `SharedModule`para `BooksModule`para reutilizar alguns componentes e serviços definidos em:
|
||||
|
||||
```js
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
|
||||
@NgModule({
|
||||
//...
|
||||
imports: [
|
||||
//...
|
||||
SharedModule,
|
||||
],
|
||||
})
|
||||
export class BooksModule {}
|
||||
```
|
||||
|
||||
Em seguida, atualize o `routes`no `books-routing.module.ts`para adicionar o novo componente book-list:
|
||||
|
||||
```js
|
||||
import { BookListComponent } from './book-list/book-list.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: BooksComponent,
|
||||
children: [{ path: '', component: BookListComponent }],
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class BooksRoutingModule {}
|
||||
```
|
||||
|
||||

|
||||
|
||||
#### Criar BooksState
|
||||
|
||||
Execute o seguinte comando no terminal para criar um novo estado, denominado `BooksState`:
|
||||
|
||||
```shell
|
||||
yarn ng generate ngxs-schematic:state books
|
||||
```
|
||||
|
||||
Este comando cria vários novos arquivos e edições `app.modules.ts`para importar o `NgxsModule`com o novo estado:
|
||||
|
||||
```js
|
||||
// app.module.ts
|
||||
|
||||
import { BooksState } from './store/states/books.state';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
//...
|
||||
NgxsModule.forRoot([BooksState]),
|
||||
],
|
||||
//...
|
||||
})
|
||||
export class AppModule {}
|
||||
```
|
||||
|
||||
#### Obter dados de livros do back-end
|
||||
|
||||
Primeiro, crie tipos de dados para mapear os dados que retornam do back-end (você pode verificar a interface do swagger ou a API do back-end para conhecer o formato dos dados).
|
||||
|
||||
Modifique o `books.ts`como mostrado abaixo:
|
||||
|
||||
```js
|
||||
export namespace Books {
|
||||
export interface State {
|
||||
books: Response;
|
||||
}
|
||||
|
||||
export interface Response {
|
||||
items: Book[];
|
||||
totalCount: number;
|
||||
}
|
||||
|
||||
export interface Book {
|
||||
name: string;
|
||||
type: BookType;
|
||||
publishDate: string;
|
||||
price: number;
|
||||
lastModificationTime: string;
|
||||
lastModifierId: string;
|
||||
creationTime: string;
|
||||
creatorId: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
export enum BookType {
|
||||
Undefined,
|
||||
Adventure,
|
||||
Biography,
|
||||
Dystopia,
|
||||
Fantastic,
|
||||
Horror,
|
||||
Science,
|
||||
ScienceFiction,
|
||||
Poetry,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Adicionada `Book`interface que representa um objeto de livro e `BookType`enum representa uma categoria de livro.
|
||||
|
||||
#### BooksService
|
||||
|
||||
Agora, crie um novo serviço, nomeado `BooksService`para executar chamadas HTTP para o servidor:
|
||||
|
||||
```bash
|
||||
yarn ng generate service books/shared/books
|
||||
```
|
||||
|
||||

|
||||
|
||||
Modifique `books.service.ts`como mostrado abaixo:
|
||||
|
||||
```js
|
||||
import { Injectable } from '@angular/core';
|
||||
import { RestService } from '@abp/ng.core';
|
||||
import { Books } from '../../store/models';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class BooksService {
|
||||
constructor(private restService: RestService) {}
|
||||
|
||||
get(): Observable<Books.Response> {
|
||||
return this.restService.request<void, Books.Response>({
|
||||
method: 'GET',
|
||||
url: '/api/app/book'
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Adicionado o `get`método para obter a lista de livros executando uma solicitação HTTP no terminal relacionado.
|
||||
|
||||
Substitua o `books.actions.ts`conteúdo conforme mostrado abaixo:
|
||||
|
||||
```js
|
||||
export class GetBooks {
|
||||
static readonly type = '[Books] Get';
|
||||
}
|
||||
```
|
||||
|
||||
#### Implementar o BooksState
|
||||
|
||||
Abra o `books.state.ts`e altere o arquivo, como mostrado abaixo:
|
||||
|
||||
```js
|
||||
import { State, Action, StateContext, Selector } from '@ngxs/store';
|
||||
import { GetBooks } from '../actions/books.actions';
|
||||
import { Books } from '../models/books';
|
||||
import { BooksService } from '../../books/shared/books.service';
|
||||
import { tap } from 'rxjs/operators';
|
||||
|
||||
@State<Books.State>({
|
||||
name: 'BooksState',
|
||||
defaults: { books: {} } as Books.State,
|
||||
})
|
||||
export class BooksState {
|
||||
@Selector()
|
||||
static getBooks(state: Books.State) {
|
||||
return state.books.items || [];
|
||||
}
|
||||
|
||||
constructor(private booksService: BooksService) {}
|
||||
|
||||
@Action(GetBooks)
|
||||
get(ctx: StateContext<Books.State>) {
|
||||
return this.booksService.get().pipe(
|
||||
tap(booksResponse => {
|
||||
ctx.patchState({
|
||||
books: booksResponse,
|
||||
});
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Adicionada a `GetBooks`ação que usa o `BookService`definido acima para obter os livros e corrigir o estado.
|
||||
|
||||
> O NGXS exige retornar o observável sem assiná-lo, conforme feito nesta amostra (na função get).
|
||||
|
||||
#### BookListComponent
|
||||
|
||||
Modifique o `book-list.component.ts`como mostrado abaixo:
|
||||
|
||||
```js
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Store, Select } from '@ngxs/store';
|
||||
import { BooksState } from '../../store/states';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Books } from '../../store/models';
|
||||
import { GetBooks } from '../../store/actions';
|
||||
|
||||
@Component({
|
||||
selector: 'app-book-list',
|
||||
templateUrl: './book-list.component.html',
|
||||
styleUrls: ['./book-list.component.scss'],
|
||||
})
|
||||
export class BookListComponent implements OnInit {
|
||||
@Select(BooksState.getBooks)
|
||||
books$: Observable<Books.Book[]>;
|
||||
|
||||
booksType = Books.BookType;
|
||||
|
||||
loading = false;
|
||||
|
||||
constructor(private store: Store) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.loading = true;
|
||||
this.store.dispatch(new GetBooks()).subscribe(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> Consulte as [ações de despacho](https://ngxs.gitbook.io/ngxs/concepts/store#dispatching-actions) e [selecione](https://ngxs.gitbook.io/ngxs/concepts/select) na documentação do NGXS para obter mais informações sobre esses recursos do NGXS.
|
||||
|
||||
Substitua o `book-list.component.html`conteúdo conforme mostrado abaixo:
|
||||
|
||||
```html
|
||||
<div id="wrapper" class="card">
|
||||
<div class="card-header">
|
||||
<div class="row">
|
||||
<div class="col col-md-6">
|
||||
<h5 class="card-title">
|
||||
Books
|
||||
</h5>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p-table [value]="books$ | async" [loading]="loading" [paginator]="true" [rows]="10">
|
||||
<ng-template pTemplate="header">
|
||||
<tr>
|
||||
<th>Book name</th>
|
||||
<th>Book type</th>
|
||||
<th>Publish date</th>
|
||||
<th>Price</th>
|
||||
</tr>
|
||||
</ng-template>
|
||||
<ng-template pTemplate="body" let-data>
|
||||
<tr>
|
||||
<td>{{ data.name }}</td>
|
||||
<td>{{ booksType[data.type] }}</td>
|
||||
<td>{{ data.publishDate | date }}</td>
|
||||
<td>{{ data.price }}</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
</p-table>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
> Usamos a [tabela PrimeNG](https://www.primefaces.org/primeng/#/table) neste componente.
|
||||
|
||||
A página de livros resultante é mostrada abaixo:
|
||||
|
||||

|
||||
|
||||
E esta é a estrutura de pastas e arquivos no final deste tutorial:
|
||||
|
||||

|
||||
|
||||
> Este tutorial segue o [Guia de estilo angular](https://angular.io/guide/styleguide#file-tree) .
|
||||
|
||||
### Próxima parte
|
||||
|
||||
Veja a [próxima parte](Part-II.md) deste tutorial.
|
||||
|
||||
|
||||
|
||||
@ -0,0 +1,582 @@
|
||||
## Tutorial Angular - Parte II
|
||||
|
||||
### Sobre este tutorial
|
||||
|
||||
Esta é a segunda parte da série de tutoriais angulares. Veja todas as peças:
|
||||
|
||||
- [Parte I: Crie o projeto e uma página da lista de livros](Part-I.md)
|
||||
- **Parte II: Criar, atualizar e excluir livros (este tutorial)**
|
||||
- [Parte III: Testes de Integração](Part-III.md)
|
||||
|
||||
Você pode acessar o **código fonte** do aplicativo no [repositório GitHub](https://github.com/abpframework/abp/tree/dev/samples/BookStore-Angular-MongoDb) .
|
||||
|
||||
### Criando um novo livro
|
||||
|
||||
Nesta seção, você aprenderá como criar um novo formulário de diálogo modal para criar um novo livro.
|
||||
|
||||
#### Definição do tipo
|
||||
|
||||
Criar uma interface, com o nome `CreateUpdateBookInput`no `books.ts`como mostrado abaixo:
|
||||
|
||||
```js
|
||||
export namespace Books {
|
||||
//...
|
||||
export interface CreateUpdateBookInput {
|
||||
name: string;
|
||||
type: BookType;
|
||||
publishDate: string;
|
||||
price: number;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`CreateUpdateBookInput`interface corresponde ao `CreateUpdateBookDto`no back-end.
|
||||
|
||||
#### Método de Serviço
|
||||
|
||||
Abra o `books.service.ts`e adicione um novo método, nomeado `create`para executar uma solicitação HTTP POST no servidor:
|
||||
|
||||
```js
|
||||
create(createBookInput: Books.CreateUpdateBookInput): Observable<Books.Book> {
|
||||
return this.restService.request<Books.CreateUpdateBookInput, Books.Book>({
|
||||
method: 'POST',
|
||||
url: '/api/app/book',
|
||||
body: createBookInput
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
- `restService.request`A função obtém parâmetros genéricos para os tipos enviados e recebidos do servidor. Este exemplo envia um `CreateUpdateBookInput`objeto e recebe um `Book`objeto (você pode definir o tipo `void`de solicitação ou retorno, se não for usado).
|
||||
|
||||
#### Definições de estado
|
||||
|
||||
Adicione a `CreateUpdateBook`ação ao `books.actions.ts`conforme mostrado abaixo:
|
||||
|
||||
```js
|
||||
import { Books } from '../models';
|
||||
|
||||
export class CreateUpdateBook {
|
||||
static readonly type = '[Books] Create Update Book';
|
||||
constructor(public payload: Books.CreateUpdateBookInput) {}
|
||||
}
|
||||
```
|
||||
|
||||
Abra `books.state.ts`e defina o `save`método que ouvirá uma `CreateUpdateBook`ação para criar um livro:
|
||||
|
||||
```js
|
||||
import { ... , CreateUpdateBook } from '../actions/books.actions';
|
||||
import { ... , switchMap } from 'rxjs/operators';
|
||||
//...
|
||||
@Action(CreateUpdateBook)
|
||||
save(ctx: StateContext<Books.State>, action: CreateUpdateBook) {
|
||||
return this.booksService
|
||||
.create(action.payload)
|
||||
.pipe(switchMap(() => ctx.dispatch(new GetBooks())));
|
||||
}
|
||||
```
|
||||
|
||||
Quando a `SaveBook`ação é despachada, o método save é executado. Ele chama o `create`método do `BooksService`definido anteriormente. Após a chamada de serviço, `BooksState`despacha a `GetBooks`ação para obter livros novamente do servidor para atualizar a página.
|
||||
|
||||
#### Adicionar um modal ao BookListComponent
|
||||
|
||||
Abra o `book-list.component.html`e adicione o `abp-modal`para mostrar / ocultar o modal para criar um novo livro.
|
||||
|
||||
```html
|
||||
<abp-modal [(visible)]="isModalOpen">
|
||||
<ng-template #abpHeader>
|
||||
<h3>New Book</h3>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #abpBody> </ng-template>
|
||||
|
||||
<ng-template #abpFooter>
|
||||
<button type="button" class="btn btn-secondary" #abpClose>
|
||||
Cancel
|
||||
</button>
|
||||
</ng-template>
|
||||
</abp-modal>
|
||||
```
|
||||
|
||||
`abp-modal`é um componente pré-construído para mostrar os modais. Embora você possa usar outra abordagem para mostrar um modal, `abp-modal`fornece benefícios adicionais.
|
||||
|
||||
Adicione um botão rotulado `New book`para mostrar o modal:
|
||||
|
||||
```html
|
||||
<div class="row">
|
||||
<div class="col col-md-6">
|
||||
<h5 class="card-title">
|
||||
Books
|
||||
</h5>
|
||||
</div>
|
||||
<div class="text-right col col-md-6">
|
||||
<button id="create-role" class="btn btn-primary" type="button" (click)="createBook()">
|
||||
<i class="fa fa-plus mr-1"></i> <span>New book</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
Abra a variável `book-list.component.ts`e adicione `isModalOpen`e `createBook`método para mostrar / ocultar o modal.
|
||||
|
||||
```js
|
||||
isModalOpen = false;
|
||||
|
||||
//...
|
||||
|
||||
createBook() {
|
||||
this.isModalOpen = true;
|
||||
}
|
||||
```
|
||||
|
||||

|
||||
|
||||
#### Criar um formulário reativo
|
||||
|
||||
> [Os formulários reativos](https://angular.io/guide/reactive-forms) fornecem uma abordagem orientada a modelo para lidar com entradas de formulário cujos valores mudam ao longo do tempo.
|
||||
|
||||
Adicione uma `form`variável e injete um `FormBuilder`serviço `book-list.component.ts`como mostrado abaixo (lembre-se de adicionar a instrução de importação).
|
||||
|
||||
```js
|
||||
import { FormGroup, FormBuilder } from '@angular/forms';
|
||||
|
||||
form: FormGroup;
|
||||
|
||||
constructor(
|
||||
//...
|
||||
private fb: FormBuilder
|
||||
) {}
|
||||
```
|
||||
|
||||
> O serviço [FormBuilder](https://angular.io/api/forms/FormBuilder) fornece métodos convenientes para gerar controles. Reduz a quantidade de clichê necessária para criar formulários complexos.
|
||||
|
||||
Adicione o `buildForm`método para criar um formulário de livro.
|
||||
|
||||
```js
|
||||
buildForm() {
|
||||
this.form = this.fb.group({
|
||||
name: ['', Validators.required],
|
||||
type: [null, Validators.required],
|
||||
publishDate: [null, Validators.required],
|
||||
price: [null, Validators.required],
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
- O `group`método de `FormBuilder`( `fb`) cria a `FormGroup`.
|
||||
- Adicionado `Validators.required`método estático que valida o elemento de formulário relacionado.
|
||||
|
||||
Modifique o `createBook`método como mostrado abaixo:
|
||||
|
||||
```js
|
||||
createBook() {
|
||||
this.buildForm();
|
||||
this.isModalOpen = true;
|
||||
}
|
||||
```
|
||||
|
||||
#### Crie os elementos DOM do formulário
|
||||
|
||||
Abra `book-list.component.html`e adicione o formulário no modelo de corpo do modal.
|
||||
|
||||
```html
|
||||
<ng-template #abpBody>
|
||||
<form [formGroup]="form">
|
||||
<div class="form-group">
|
||||
<label for="book-name">Name</label><span> * </span>
|
||||
<input type="text" id="book-name" class="form-control" formControlName="name" autofocus />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="book-price">Price</label><span> * </span>
|
||||
<input type="number" id="book-price" class="form-control" formControlName="price" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="book-type">Type</label><span> * </span>
|
||||
<select class="form-control" id="book-type" formControlName="type">
|
||||
<option [ngValue]="null">Select a book type</option>
|
||||
<option [ngValue]="booksType[type]" *ngFor="let type of bookTypeArr"> {{ type }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Publish date</label><span> * </span>
|
||||
<input
|
||||
#datepicker="ngbDatepicker"
|
||||
class="form-control"
|
||||
name="datepicker"
|
||||
formControlName="publishDate"
|
||||
ngbDatepicker
|
||||
(click)="datepicker.toggle()"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</ng-template>
|
||||
```
|
||||
|
||||
- Este modelo cria um formulário com os campos Nome, Preço, Tipo e Data de publicação.
|
||||
|
||||
> Usamos o [datepicker do NgBootstrap](https://ng-bootstrap.github.io/#/components/datepicker/overview) neste componente.
|
||||
|
||||
Abra o `book-list.component.ts`e crie uma matriz chamada `bookTypeArr`:
|
||||
|
||||
```js
|
||||
//...
|
||||
form: FormGroup;
|
||||
|
||||
bookTypeArr = Object.keys(Books.BookType).filter(
|
||||
bookType => typeof this.booksType[bookType] === 'number'
|
||||
);
|
||||
```
|
||||
|
||||
O `bookTypeArr`contém os campos da `BookType`enumeração. A matriz resultante é mostrada abaixo:
|
||||
|
||||
```js
|
||||
['Adventure', 'Biography', 'Dystopia', 'Fantastic' ...]
|
||||
```
|
||||
|
||||
Essa matriz foi usada no modelo de formulário anterior (no `ngFor`loop).
|
||||
|
||||
#### Requisitos do Datepicker
|
||||
|
||||
Você precisa importar `NgbDatepickerModule`para o `books.module.ts`:
|
||||
|
||||
```js
|
||||
import { NgbDatepickerModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
// ...
|
||||
NgbDatepickerModule,
|
||||
],
|
||||
})
|
||||
export class BooksModule {}
|
||||
```
|
||||
|
||||
Abra o `book-list.component.ts`e adicione `providers`como mostrado abaixo:
|
||||
|
||||
```js
|
||||
import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap';
|
||||
|
||||
@Component({
|
||||
// ...
|
||||
providers: [{ provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }],
|
||||
})
|
||||
export class BookListComponent implements OnInit {
|
||||
// ...
|
||||
```
|
||||
|
||||
> O `NgbDateAdapter`valor do Datepicker converte em `Date`tipo. Consulte os [adaptadores datepicker](https://ng-bootstrap.github.io/#/components/datepicker/overview) para obter mais detalhes.
|
||||
|
||||

|
||||
|
||||
#### Salvando o livro
|
||||
|
||||
Abra o `book-list.component.html`e adicione um `abp-button`para salvar o formulário.
|
||||
|
||||
```html
|
||||
<ng-template #abpFooter>
|
||||
<button type="button" class="btn btn-secondary" #abpClose>
|
||||
Cancel
|
||||
</button>
|
||||
<button class="btn btn-primary" (click)="save()">
|
||||
<i class="fa fa-check mr-1"></i>
|
||||
Save
|
||||
</button>
|
||||
</ng-template>
|
||||
```
|
||||
|
||||
Isso adiciona um botão Salvar à área inferior do modal:
|
||||
|
||||

|
||||
|
||||
Em seguida, defina um `save`método no `BookListComponent`:
|
||||
|
||||
```js
|
||||
save() {
|
||||
if (this.form.invalid) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.store.dispatch(new CreateUpdateBook(this.form.value)).subscribe(() => {
|
||||
this.isModalOpen = false;
|
||||
this.form.reset();
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Atualizando um livro existente
|
||||
|
||||
#### BooksService
|
||||
|
||||
Abra o `books.service.ts`e adicione os métodos `getById`e `update`.
|
||||
|
||||
```js
|
||||
getById(id: string): Observable<Books.Book> {
|
||||
return this.restService.request<void, Books.Book>({
|
||||
method: 'GET',
|
||||
url: `/api/app/book/${id}`
|
||||
});
|
||||
}
|
||||
|
||||
update(updateBookInput: Books.CreateUpdateBookInput, id: string): Observable<Books.Book> {
|
||||
return this.restService.request<Books.CreateUpdateBookInput, Books.Book>({
|
||||
method: 'PUT',
|
||||
url: `/api/app/book/${id}`,
|
||||
body: updateBookInput
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
#### Ação CreateUpdateBook
|
||||
|
||||
Abra o parâmetro `books.actins.ts`e adicione `id`à `CreateUpdateBook`ação:
|
||||
|
||||
```js
|
||||
export class CreateUpdateBook {
|
||||
static readonly type = '[Books] Create Update Book';
|
||||
constructor(public payload: Books.CreateUpdateBookInput, public id?: string) {}
|
||||
}
|
||||
```
|
||||
|
||||
Abra `books.state.ts`e modifique o `save`método conforme mostrado abaixo:
|
||||
|
||||
```js
|
||||
@Action(CreateUpdateBook)
|
||||
save(ctx: StateContext<Books.State>, action: CreateUpdateBook) {
|
||||
let request;
|
||||
|
||||
if (action.id) {
|
||||
request = this.booksService.update(action.payload, action.id);
|
||||
} else {
|
||||
request = this.booksService.create(action.payload);
|
||||
}
|
||||
|
||||
return request.pipe(switchMap(() => ctx.dispatch(new GetBooks())));
|
||||
}
|
||||
```
|
||||
|
||||
#### BookListComponent
|
||||
|
||||
Injectar `BooksService`dependência, adicionando-o ao `book-list.component.ts`construtor e adicione uma variável chamada `selectedBook`.
|
||||
|
||||
```js
|
||||
import { BooksService } from '../shared/books.service';
|
||||
//...
|
||||
selectedBook = {} as Books.Book;
|
||||
|
||||
constructor(
|
||||
//...
|
||||
private booksService: BooksService
|
||||
)
|
||||
```
|
||||
|
||||
`booksService`é usado para obter o livro de edição para preparar o formulário. Modifique o `buildForm`método para reutilizar o mesmo formulário ao editar um livro.
|
||||
|
||||
```js
|
||||
buildForm() {
|
||||
this.form = this.fb.group({
|
||||
name: [this.selectedBook.name || '', Validators.required],
|
||||
type: this.selectedBook.type || null,
|
||||
publishDate: this.selectedBook.publishDate ? new Date(this.selectedBook.publishDate) : null,
|
||||
price: this.selectedBook.price || null,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
Adicione o `editBook`método como mostrado abaixo:
|
||||
|
||||
```js
|
||||
editBook(id: string) {
|
||||
this.booksService.getById(id).subscribe(book => {
|
||||
this.selectedBook = book;
|
||||
this.buildForm();
|
||||
this.isModalOpen = true;
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
Adicionado `editBook`método para obter o livro de edição, criar o formulário e mostrar o modal.
|
||||
|
||||
Agora, adicione a `selectedBook`definição ao `createBook`método para reutilizar o mesmo formulário ao criar um novo livro:
|
||||
|
||||
```js
|
||||
createBook() {
|
||||
this.selectedBook = {} as Books.Book;
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
Modifique o `save`método para passar o ID do livro selecionado, como mostrado abaixo:
|
||||
|
||||
```js
|
||||
save() {
|
||||
if (this.form.invalid) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.store.dispatch(new CreateUpdateBook(this.form.value, this.selectedBook.id))
|
||||
.subscribe(() => {
|
||||
this.isModalOpen = false;
|
||||
this.form.reset();
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
#### Adicione o menu suspenso "Ações" à tabela
|
||||
|
||||
Abra o `book-list.component.html` e adicione modifique o `p-table` como mostrado abaixo:
|
||||
|
||||
```html
|
||||
<p-table [value]="books$ | async" [loading]="loading" [paginator]="true" [rows]="10">
|
||||
<ng-template pTemplate="header">
|
||||
<tr>
|
||||
<th>Actions</th>
|
||||
<th>Book name</th>
|
||||
<th>Book type</th>
|
||||
<th>Publish date</th>
|
||||
<th>Price</th>
|
||||
</tr>
|
||||
</ng-template>
|
||||
<ng-template pTemplate="body" let-data>
|
||||
<tr>
|
||||
<td>
|
||||
<div ngbDropdown class="d-inline-block">
|
||||
<button
|
||||
class="btn btn-primary btn-sm dropdown-toggle"
|
||||
data-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
ngbDropdownToggle
|
||||
>
|
||||
<i class="fa fa-cog mr-1"></i>Actions
|
||||
</button>
|
||||
<div ngbDropdownMenu>
|
||||
<button ngbDropdownItem (click)="editBook(data.id)">Edit</button>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>{{ data.name }}</td>
|
||||
<td>{{ booksType[data.type] }}</td>
|
||||
<td>{{ data.publishDate | date }}</td>
|
||||
<td>{{ data.price }}</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
</p-table>
|
||||
```
|
||||
|
||||
- Adicionado um `th`para a coluna "Ações".
|
||||
- Adicionado `button`com `ngbDropdownToggle`para abrir ações quando clicamos no botão.
|
||||
|
||||
> Nós costumávamos usar o [NgbDropdown](https://ng-bootstrap.github.io/#/components/dropdown/examples) no menu suspenso de ações.
|
||||
|
||||
A interface do usuário final é semelhante a:
|
||||
|
||||

|
||||
|
||||
Atualize o cabeçalho modal para alterar o título com base na operação atual:
|
||||
|
||||
```html
|
||||
<ng-template #abpHeader>
|
||||
<h3>{{ selectedBook.id ? 'Edit' : 'New Book' }}</h3>
|
||||
</ng-template>
|
||||
```
|
||||
|
||||

|
||||
|
||||
### Exclusão de um livro existente
|
||||
|
||||
#### BooksService
|
||||
|
||||
Abra `books.service.ts`e inclua um `delete`método para excluir um livro com o `id`, executando uma solicitação HTTP no nó de extremidade relacionado:
|
||||
|
||||
```js
|
||||
delete(id: string): Observable<void> {
|
||||
return this.restService.request<void, void>({
|
||||
method: 'DELETE',
|
||||
url: `/api/app/book/${id}`
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
#### Ação DeleteBook
|
||||
|
||||
Adicione uma ação chamada `DeleteBook`para `books.actions.ts`:
|
||||
|
||||
```js
|
||||
export class DeleteBook {
|
||||
static readonly type = '[Books] Delete';
|
||||
constructor(public id: string) {}
|
||||
}
|
||||
```
|
||||
|
||||
Abra o `books.state.ts`e adicione o `delete`método que ouvirá a `DeleteBook`ação para excluir um livro:
|
||||
|
||||
```js
|
||||
import { ... , DeleteBook } from '../actions/books.actions';
|
||||
//...
|
||||
@Action(DeleteBook)
|
||||
delete(ctx: StateContext<Books.State>, action: DeleteBook) {
|
||||
return this.booksService.delete(action.id).pipe(switchMap(() => ctx.dispatch(new GetBooks())));
|
||||
}
|
||||
```
|
||||
|
||||
- Adicionado `DeleteBook`à lista de importação.
|
||||
- Usa `bookService`para excluir o livro.
|
||||
|
||||
\#### Adicionar um botão Excluir
|
||||
|
||||
Abra `book-list.component.html`e modifique `ngbDropdownMenu`para adicionar o botão excluir, como mostrado abaixo:
|
||||
|
||||
```html
|
||||
<div ngbDropdownMenu>
|
||||
...
|
||||
<button ngbDropdownItem (click)="delete(data.id, data.name)">
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
```
|
||||
|
||||
A interface do usuário suspensa de ações finais é semelhante a abaixo:
|
||||
|
||||

|
||||
|
||||
\#### Caixa de diálogo Excluir confirmação
|
||||
|
||||
Abra `book-list.component.ts`e injete o `ConfirmationService`.
|
||||
|
||||
```js
|
||||
import { ConfirmationService } from '@abp/ng.theme.shared';
|
||||
//...
|
||||
constructor(
|
||||
//...
|
||||
private confirmationService: ConfirmationService
|
||||
)
|
||||
```
|
||||
|
||||
> `ConfirmationService` é um serviço simples fornecido pela estrutura ABP que usa internamente o PrimeNG.
|
||||
|
||||
Adicione um método de exclusão ao `BookListComponent`:
|
||||
|
||||
```js
|
||||
import { ... , DeleteBook } from '../../store/actions';
|
||||
import { ... , Toaster } from '@abp/ng.theme.shared';
|
||||
//...
|
||||
delete(id: string, name: string) {
|
||||
this.confirmationService
|
||||
.error(`${name} will be deleted. Do you confirm that?`, 'Are you sure?')
|
||||
.subscribe(status => {
|
||||
if (status === Toaster.Status.confirm) {
|
||||
this.store.dispatch(new DeleteBook(id));
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
O `delete`método mostra um pop-up de confirmação e assina a resposta do usuário. `DeleteBook`ação despachada somente se o usuário clicar no `Yes`botão O pop-up de confirmação é exibido abaixo:
|
||||
|
||||

|
||||
|
||||
### Próxima parte
|
||||
|
||||
Veja a [próxima parte](Part-III.md) deste tutorial.
|
||||
|
||||
@ -0,0 +1,181 @@
|
||||
## Tutorial do ASP.NET Core MVC - Parte III
|
||||
|
||||
### Sobre este tutorial
|
||||
|
||||
Esta é a terceira parte da série de tutoriais Angular. Veja todas as peças:
|
||||
|
||||
- [Parte I: Crie o projeto e uma página da lista de livros](Part-I.md)
|
||||
- [Parte II: Criar, atualizar e excluir livros](Part-II.md)
|
||||
- **Parte III: Testes de Integração (este tutorial)**
|
||||
|
||||
Esta parte abrange os testes do **lado** do **servidor** . Você pode acessar o **código fonte** do aplicativo no [repositório GitHub](https://github.com/abpframework/abp/tree/dev/samples/BookStore-Angular-MongoDb) .
|
||||
|
||||
### Testar projetos na solução
|
||||
|
||||
Existem vários projetos de teste na solução:
|
||||
|
||||

|
||||
|
||||
Cada projeto é usado para testar o projeto de aplicativo relacionado. Os projetos de teste usam as seguintes bibliotecas para teste:
|
||||
|
||||
- [xunit](https://xunit.github.io/) como a principal estrutura de teste.
|
||||
- [Shouldly](http://shouldly.readthedocs.io/en/latest/) como uma biblioteca de asserções.
|
||||
- [NSubstitute](http://nsubstitute.github.io/) como uma biblioteca de zombaria.
|
||||
|
||||
### Adicionando dados de teste
|
||||
|
||||
O modelo de inicialização contém a `BookStoreTestDataSeedContributor`classe no `Acme.BookStore.TestBase`projeto que cria alguns dados para executar os testes.
|
||||
|
||||
Mude a `BookStoreTestDataSeedContributor`classe como mostrado abaixo:
|
||||
|
||||
```csharp
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Volo.Abp.Data;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Volo.Abp.Domain.Repositories;
|
||||
using Volo.Abp.Guids;
|
||||
|
||||
namespace Acme.BookStore
|
||||
{
|
||||
public class BookStoreTestDataSeedContributor
|
||||
: IDataSeedContributor, ITransientDependency
|
||||
{
|
||||
private readonly IRepository<Book, Guid> _bookRepository;
|
||||
private readonly IGuidGenerator _guidGenerator;
|
||||
|
||||
public BookStoreTestDataSeedContributor(
|
||||
IRepository<Book, Guid> bookRepository,
|
||||
IGuidGenerator guidGenerator)
|
||||
{
|
||||
_bookRepository = bookRepository;
|
||||
_guidGenerator = guidGenerator;
|
||||
}
|
||||
|
||||
public async Task SeedAsync(DataSeedContext context)
|
||||
{
|
||||
await _bookRepository.InsertAsync(
|
||||
new Book
|
||||
{
|
||||
Id = _guidGenerator.Create(),
|
||||
Name = "Test book 1",
|
||||
Type = BookType.Fantastic,
|
||||
PublishDate = new DateTime(2015, 05, 24),
|
||||
Price = 21
|
||||
}
|
||||
);
|
||||
|
||||
await _bookRepository.InsertAsync(
|
||||
new Book
|
||||
{
|
||||
Id = _guidGenerator.Create(),
|
||||
Name = "Test book 2",
|
||||
Type = BookType.Science,
|
||||
PublishDate = new DateTime(2014, 02, 11),
|
||||
Price = 15
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- Injetado `IRepository<Book, Guid>`e usado no `SeedAsync`para criar duas entidades de livro como dados de teste.
|
||||
- `IGuidGenerator`Serviço usado para criar GUIDs. Embora `Guid.NewGuid()`funcionasse perfeitamente para testes, `IGuidGenerator`possui recursos adicionais especialmente importantes ao usar bancos de dados reais (consulte o documento de geração do [Guid](../../Guid-Generation.md) para obter mais informações).
|
||||
|
||||
### Testando o BookAppService
|
||||
|
||||
Crie uma classe de teste denominada `BookAppService_Tests`no `Acme.BookStore.Application.Tests`projeto:
|
||||
|
||||
```csharp
|
||||
using System.Threading.Tasks;
|
||||
using Shouldly;
|
||||
using Volo.Abp.Application.Dtos;
|
||||
using Xunit;
|
||||
|
||||
namespace Acme.BookStore
|
||||
{
|
||||
public class BookAppService_Tests : BookStoreApplicationTestBase
|
||||
{
|
||||
private readonly IBookAppService _bookAppService;
|
||||
|
||||
public BookAppService_Tests()
|
||||
{
|
||||
_bookAppService = GetRequiredService<IBookAppService>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Should_Get_List_Of_Books()
|
||||
{
|
||||
//Act
|
||||
var result = await _bookAppService.GetListAsync(
|
||||
new PagedAndSortedResultRequestDto()
|
||||
);
|
||||
|
||||
//Assert
|
||||
result.TotalCount.ShouldBeGreaterThan(0);
|
||||
result.Items.ShouldContain(b => b.Name == "Test book 1");
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- `Should_Get_List_Of_Books`O teste simplesmente usa o `BookAppService.GetListAsync`método para obter e verificar a lista de usuários.
|
||||
|
||||
Adicione um novo teste que crie um novo livro válido:
|
||||
|
||||
```csharp
|
||||
[Fact]
|
||||
public async Task Should_Create_A_Valid_Book()
|
||||
{
|
||||
//Act
|
||||
var result = await _bookAppService.CreateAsync(
|
||||
new CreateUpdateBookDto
|
||||
{
|
||||
Name = "New test book 42",
|
||||
Price = 10,
|
||||
PublishDate = DateTime.Now,
|
||||
Type = BookType.ScienceFiction
|
||||
}
|
||||
);
|
||||
|
||||
//Assert
|
||||
result.Id.ShouldNotBe(Guid.Empty);
|
||||
result.Name.ShouldBe("New test book 42");
|
||||
}
|
||||
```
|
||||
|
||||
Adicione um novo teste que tente criar um livro inválido e falhe:
|
||||
|
||||
```csharp
|
||||
[Fact]
|
||||
public async Task Should_Not_Create_A_Book_Without_Name()
|
||||
{
|
||||
var exception = await Assert.ThrowsAsync<AbpValidationException>(async () =>
|
||||
{
|
||||
await _bookAppService.CreateAsync(
|
||||
new CreateUpdateBookDto
|
||||
{
|
||||
Name = "",
|
||||
Price = 10,
|
||||
PublishDate = DateTime.Now,
|
||||
Type = BookType.ScienceFiction
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
exception.ValidationErrors
|
||||
.ShouldContain(err => err.MemberNames.Any(mem => mem == "Name"));
|
||||
}
|
||||
```
|
||||
|
||||
- Como o `Name`está vazio, o ABP lança um `AbpValidationException`.
|
||||
|
||||
Abra a **janela Test Explorer** (use o menu Test -> Windows -> Test Explorer, se não estiver visível) e **execute Todos os** testes:
|
||||
|
||||

|
||||
|
||||
Parabéns, ícones verdes mostram que os testes foram aprovados com sucesso!
|
||||
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 101 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 97 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 102 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 143 KiB |
|
After Width: | Height: | Size: 132 KiB |
|
After Width: | Height: | Size: 99 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 18 KiB |
@ -0,0 +1,3 @@
|
||||
## Unit of Work
|
||||
|
||||
Façam
|
||||
@ -0,0 +1,3 @@
|
||||
## Validation
|
||||
|
||||
Façam
|
||||
@ -0,0 +1,3 @@
|
||||
## Value Objects
|
||||
|
||||
Façam
|
||||
@ -0,0 +1,328 @@
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"text": "Começando",
|
||||
"items": [
|
||||
{
|
||||
"text": "De Modelo de Inicialização",
|
||||
"items": [
|
||||
{
|
||||
"text": "Aplicativo com MVC (Razor Pages) UI",
|
||||
"path": "Getting-Started-AspNetCore-MVC-Template.md"
|
||||
},
|
||||
{
|
||||
"text": "Aplicativo com Angular UI",
|
||||
"path": "Getting-Started-Angular-Template.md"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "De Projetos Vazios",
|
||||
"items": [
|
||||
{
|
||||
"text": "Com Aplicativo ASP.NET Core Web",
|
||||
"path": "Getting-Started-AspNetCore-Application.md"
|
||||
},
|
||||
{
|
||||
"text": "Com Aplicativo Console",
|
||||
"path": "Getting-Started-Console-Application.md"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "Tutoriais",
|
||||
"items": [
|
||||
{
|
||||
"text": "Desenvolvimento de Aplicações",
|
||||
"items": [
|
||||
{
|
||||
"text": "Com ASP.NET Core MVC UI",
|
||||
"path": "Tutorials/AspNetCore-Mvc/Part-I.md"
|
||||
},
|
||||
{
|
||||
"text": "Com Angular UI",
|
||||
"path": "Tutorials/Angular/Part-I.md"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "CLI",
|
||||
"path": "CLI.md"
|
||||
},
|
||||
{
|
||||
"text": "Fundamentos",
|
||||
"items": [
|
||||
{
|
||||
"text": "Injeção de Dependência",
|
||||
"path": "Dependency-Injection.md",
|
||||
"items": [
|
||||
{
|
||||
"text": "Integração com Autofac",
|
||||
"path": "Autofac-Integration.md"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "Sistema de Arquivos Virtual",
|
||||
"path": "Virtual-File-System.md"
|
||||
},
|
||||
{
|
||||
"text": "Localização",
|
||||
"path": "Localization.md"
|
||||
},
|
||||
{
|
||||
"text": "Manipulação de Exceção",
|
||||
"path": "Exception-Handling.md"
|
||||
},
|
||||
{
|
||||
"text": "Validação"
|
||||
},
|
||||
{
|
||||
"text": "Autorização"
|
||||
},
|
||||
{
|
||||
"text": "Armazenamento em Cache"
|
||||
},
|
||||
{
|
||||
"text": "Auditoria"
|
||||
},
|
||||
{
|
||||
"text": "Gerenciamento de Configurações"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "Eventos",
|
||||
"items": [
|
||||
{
|
||||
"text": "Barramento de Eventos (local)"
|
||||
},
|
||||
{
|
||||
"text": "Barramento de Eventos Distribuídos",
|
||||
"items": [
|
||||
{
|
||||
"text": "Integração RabbitMQ"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "Serviços",
|
||||
"items": [
|
||||
{
|
||||
"text": "Serialização de Objetos"
|
||||
},
|
||||
{
|
||||
"text": "Serialização JSON"
|
||||
},
|
||||
{
|
||||
"text": "Emailing"
|
||||
},
|
||||
{
|
||||
"text": "GUIDs"
|
||||
},
|
||||
{
|
||||
"text": "Threading"
|
||||
},
|
||||
{
|
||||
"text": "Timing"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "Múltiplos Inquilinos",
|
||||
"path": "Multi-Tenancy.md"
|
||||
},
|
||||
{
|
||||
"text": "Desenvolvimento de Módulos",
|
||||
"items": [
|
||||
{
|
||||
"text": "Fundamentos",
|
||||
"path": "Module-Development-Basics.md"
|
||||
},
|
||||
{
|
||||
"text": "Módulos de plug-in"
|
||||
},
|
||||
{
|
||||
"text": "Melhores práticas",
|
||||
"path": "Best-Practices/Index.md"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "Design Orientado a Domínio",
|
||||
"path": "Domain-Driven-Design.md",
|
||||
"items": [
|
||||
{
|
||||
"text": "Camada de Domínio",
|
||||
"items": [
|
||||
{
|
||||
"text": "Entidades & Raízes Agregadas",
|
||||
"path": "Entities.md"
|
||||
},
|
||||
{
|
||||
"text": "Value Objects"
|
||||
},
|
||||
{
|
||||
"text": "Repositórios",
|
||||
"path": "Repositories.md"
|
||||
},
|
||||
{
|
||||
"text": "Serviços de Domínio"
|
||||
},
|
||||
{
|
||||
"text": "Especificações"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "Camada de Aplicação",
|
||||
"items": [
|
||||
{
|
||||
"text": "Serviços de Aplicação",
|
||||
"path": "Application-Services.md"
|
||||
},
|
||||
{
|
||||
"text": "Objetos de Transferência de Dados"
|
||||
},
|
||||
{
|
||||
"text": "Unidade de Trabalho"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "ASP.NET Core",
|
||||
"items": [
|
||||
{
|
||||
"text": "API",
|
||||
"items": [
|
||||
{
|
||||
"text": "Controladores de API Automática",
|
||||
"path": "AspNetCore/Auto-API-Controllers.md"
|
||||
},
|
||||
{
|
||||
"text": "Clientes da API C # Dinâmica",
|
||||
"path": "AspNetCore/Dynamic-CSharp-API-Clients.md"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "Interface de Usuário",
|
||||
"items": [
|
||||
{
|
||||
"text": "Gerenciamento de Pacotes do Lado do Cliente",
|
||||
"path": "AspNetCore/Client-Side-Package-Management.md"
|
||||
},
|
||||
{
|
||||
"text": "Compactação & Minificação",
|
||||
"path": "AspNetCore/Bundling-Minification.md"
|
||||
},
|
||||
{
|
||||
"text": "Tag Helpers",
|
||||
"path": "AspNetCore/Tag-Helpers/Index.md"
|
||||
},
|
||||
{
|
||||
"text": "Widgets",
|
||||
"path": "AspNetCore/Widgets.md"
|
||||
},
|
||||
{
|
||||
"text": "Theming",
|
||||
"path": "AspNetCore/Theming.md"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "Acesso de Dados",
|
||||
"items": [
|
||||
{
|
||||
"text": "Integração do Entity Framework Core",
|
||||
"path": "Entity-Framework-Core.md",
|
||||
"items": [
|
||||
{
|
||||
"text": "Integração do PostgreSQL",
|
||||
"path": "Best-Practices/PostgreSQL-Integration.md"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "Integração do MongoDB",
|
||||
"path": "MongoDB.md"
|
||||
},
|
||||
{
|
||||
"text": "Integração do Dapper",
|
||||
"path": "Dapper.md"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "Background",
|
||||
"items": [
|
||||
{
|
||||
"text": "Trabalhos em Segundo Plano",
|
||||
"path": "Background-Jobs.md",
|
||||
"items": [
|
||||
{
|
||||
"text": "Integração do Hangfire",
|
||||
"path": "Background-Jobs-Hangfire.md"
|
||||
},
|
||||
{
|
||||
"text": "Integração do RabbitMQ",
|
||||
"path": "Background-Jobs-RabbitMq.md"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "Modelos de Inicialização",
|
||||
"path": "Startup-Templates/Index.md",
|
||||
"items": [
|
||||
{
|
||||
"text": "Aplicativo",
|
||||
"path": "Startup-Templates/Application.md"
|
||||
},
|
||||
{
|
||||
"text": "Módulo",
|
||||
"path": "Startup-Templates/Module.md"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "Exemplos",
|
||||
"items": [
|
||||
{
|
||||
"text": "Demonstração de Microsserviços",
|
||||
"path": "Samples/Microservice-Demo.md"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "Módulos de Aplicação",
|
||||
"path": "Modules/Index.md"
|
||||
},
|
||||
{
|
||||
"text": "Arquitetura de Microsserviços",
|
||||
"path": "Microservice-Architecture.md"
|
||||
},
|
||||
{
|
||||
"text": "Testando"
|
||||
},
|
||||
{
|
||||
"text": "Compilações Noturnas",
|
||||
"path": "Nightly-Builds.md"
|
||||
},
|
||||
{
|
||||
"text": "Guia de Contribuição",
|
||||
"path": "Contribution/Index.md"
|
||||
}
|
||||
]
|
||||
}
|
||||