Merge branch 'dev' of https://github.com/volosoft/abp into dev
	
		
	
				
					
				
			| @ -0,0 +1,60 @@ | |||||||
|  | ## Guia de Contribuição | ||||||
|  | 
 | ||||||
|  | O ABP é um projeto de [código aberto](https://github.com/abpframework) e orientado à comunidade. Este guia tem como objetivo ajudar alguém que queira contribuir com o projeto. | ||||||
|  | 
 | ||||||
|  | ### Contribuição de código | ||||||
|  | 
 | ||||||
|  | Você sempre pode enviar solicitações pull ao repositório do Github. | ||||||
|  | 
 | ||||||
|  | - Clone o [repositório ABP](https://github.com/abpframework/abp/) do Github. | ||||||
|  | - Faça as alterações necessárias. | ||||||
|  | - Envie uma solicitação de recebimento. | ||||||
|  | 
 | ||||||
|  | Antes de fazer qualquer alteração, discuta-a sobre os [problemas](https://github.com/abpframework/abp/issues) do [Github](https://github.com/abpframework/abp/issues) . Dessa forma, nenhum outro desenvolvedor trabalhará no mesmo problema e seu PR terá uma chance melhor de ser aceito. | ||||||
|  | 
 | ||||||
|  | #### Correções de bugs e aprimoramentos | ||||||
|  | 
 | ||||||
|  | Você pode corrigir um bug conhecido ou trabalhar em uma melhoria planejada. Veja [a lista de problemas](https://github.com/abpframework/abp/issues) no Github. | ||||||
|  | 
 | ||||||
|  | #### Solicitações de recursos | ||||||
|  | 
 | ||||||
|  | Se você tem uma ideia de recurso para a estrutura ou módulos, [crie um problema](https://github.com/abpframework/abp/issues/new) no Github ou participe de uma discussão existente. Então você pode implementá-lo se for adotado pela comunidade. | ||||||
|  | 
 | ||||||
|  | ### Tradução de documentos | ||||||
|  | 
 | ||||||
|  | Você pode traduzir a [documentação](https://abp.io/documents/) completa (incluindo esta) para o idioma materno. Nesse caso, siga estas etapas: | ||||||
|  | 
 | ||||||
|  | - Clone o [repositório ABP](https://github.com/abpframework/abp/) do Github. | ||||||
|  | - Para adicionar um novo idioma, crie uma nova pasta dentro da pasta [docs](https://github.com/abpframework/abp/tree/master/docs) . Os nomes das pastas podem ser "en", "es", "fr", "tr" e assim por diante, com base no idioma (consulte [todos os códigos de cultura](https://msdn.microsoft.com/en-us/library/hh441729.aspx) ). | ||||||
|  | - Obtenha a [pasta "en"](https://github.com/abpframework/abp/tree/master/docs/en) como uma referência para os nomes de arquivos e a estrutura de pastas. Mantenha o mesmo nome se estiver traduzindo a mesma documentação. | ||||||
|  | - Envie uma solicitação de recebimento (PR) depois de traduzir qualquer documento. Traduza documentos e envie PRs um por um. Não espere para terminar as traduções de todos os documentos. | ||||||
|  | 
 | ||||||
|  | Alguns documentos fundamentais precisam ser traduzidos antes da publicação de um idioma no [site de documentação](https://docs.abp.io/) da [ABP](https://docs.abp.io/) : | ||||||
|  | 
 | ||||||
|  | - Documentos de introdução | ||||||
|  | - Tutoriais | ||||||
|  | - CLI | ||||||
|  | 
 | ||||||
|  | Um novo idioma é publicado após a conclusão dessas traduções mínimas. | ||||||
|  | 
 | ||||||
|  | ### Localização de Recursos | ||||||
|  | 
 | ||||||
|  | A estrutura ABP possui um [sistema de localização](../Localization.md) flexível . Você pode criar interfaces de usuário localizadas para seu próprio aplicativo. | ||||||
|  | 
 | ||||||
|  | Além disso, os módulos de estrutura e pré-construção já localizaram textos. Como exemplo, veja [os textos de localização para o pacote Volo.Abp.UI](https://github.com/abpframework/abp/blob/master/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json) . Você pode criar um novo arquivo na [mesma pasta](https://github.com/abpframework/abp/tree/master/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi) para traduzi-lo. | ||||||
|  | 
 | ||||||
|  | - Clone o [repositório ABP](https://github.com/abpframework/abp/) do Github. | ||||||
|  | - Crie um novo arquivo para o idioma de destino para um arquivo de texto de localização (json) (próximo ao arquivo en.json). | ||||||
|  | - Copie todos os textos do arquivo en.json. | ||||||
|  | - Traduzir os textos. | ||||||
|  | - Enviar solicitação de recebimento no Github. | ||||||
|  | 
 | ||||||
|  | ABP é uma estrutura modular. Portanto, existem muitos recursos de texto de localização, um por módulo. Para encontrar todos os arquivos .json, você pode procurar por "en.json" após clonar o repositório. Você também pode verificar [esta lista](https://docs.abp.io/en/abp/latest/Contribution/Localization-Text-Files) para obter uma lista de arquivos de texto de localização. | ||||||
|  | 
 | ||||||
|  | ### Posts e tutoriais do blog | ||||||
|  | 
 | ||||||
|  | Se você decidir criar alguns tutoriais ou postagens de blog no ABP, informe-nos (criando um [problema no Github](https://github.com/abpframework/abp/issues) ), para que possamos adicionar um link ao seu tutorial / publicação na documentação oficial e podemos anunciá-lo em nossa [conta do Twitter](https://twitter.com/abpframework) . | ||||||
|  | 
 | ||||||
|  | ### Relatório de erro | ||||||
|  | 
 | ||||||
|  | Se você encontrar algum erro, [crie um problema no repositório do Github](https://github.com/abpframework/abp/issues/new) . | ||||||
| @ -0,0 +1,126 @@ | |||||||
|  | ## Introdução ao modelo de aplicativo angular | ||||||
|  | 
 | ||||||
|  | Este tutorial explica como criar um novo aplicativo Angular usando o modelo de inicialização, configurar e executá-lo. | ||||||
|  | 
 | ||||||
|  | ### Criando um novo projeto | ||||||
|  | 
 | ||||||
|  | Este tutorial usa o **ABP CLI** para criar um novo projeto. Consulte a página [Introdução](https://abp.io/get-started) para outras opções. | ||||||
|  | 
 | ||||||
|  | Instale a CLI ABP usando uma janela de linha de comando, se você não tiver instalado antes: | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | dotnet tool install -g Volo.Abp.Cli | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Use o `abp new`comando em uma pasta vazia para criar seu projeto: | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | abp new Acme.BookStore -u angular | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | > Você pode usar diferentes níveis de namespaces; por exemplo, BookStore, Acme.BookStore ou Acme.Retail.BookStore. | ||||||
|  | 
 | ||||||
|  | `-u angular`A opção especifica que a estrutura da interface do usuário seja Angular. O provedor de banco de dados padrão é o EF Core. Consulte a [documentação](CLI.md) da [CLI](CLI.md) para todas as opções disponíveis. | ||||||
|  | 
 | ||||||
|  | #### Pré requisitos | ||||||
|  | 
 | ||||||
|  | A solução criada requer; | ||||||
|  | 
 | ||||||
|  | - [Visual Studio 2017 (v15.9.0 +)](https://visualstudio.microsoft.com/tr/downloads/) | ||||||
|  | - [.NET Core 2.2 ou superior](https://www.microsoft.com/net/download/dotnet-core/) | ||||||
|  | - [Node v10.16 +](https://nodejs.org/) | ||||||
|  | - [Yarn v1.17 +](https://yarnpkg.com/) | ||||||
|  | 
 | ||||||
|  | ### A Estrutura da Solução | ||||||
|  | 
 | ||||||
|  | Abra a solução no **Visual Studio** : | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | 
 | ||||||
|  | A solução possui uma estrutura em camadas (baseada no [Domain Driven Design](Domain-Driven-Design.md) ) e contém projetos de teste de unidade e integração adequadamente configurados para trabalhar com o **banco de** dados de **memória** **EF Core** e **SQLite** . | ||||||
|  | 
 | ||||||
|  | > Consulte o [documento do modelo do aplicativo](Startup-Templates/Application.md)  para entender a estrutura da solução em detalhes. | ||||||
|  | 
 | ||||||
|  | ### Cadeia de Conexão de Banco de Dados | ||||||
|  | 
 | ||||||
|  | Verifique a **cadeia de conexão** no `appsettings.json`arquivo no `.HttpApi.Host`projeto: | ||||||
|  | 
 | ||||||
|  | ```json | ||||||
|  | { | ||||||
|  |   "ConnectionStrings": { | ||||||
|  |     "Default": "Server=localhost;Database=BookStore;Trusted_Connection=True" | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | A solução está configurada para usar o **Entity Framework Core** com o **MS SQL Server** . O EF Core suporta [vários](https://docs.microsoft.com/en-us/ef/core/providers/) provedores de banco de dados, para que você possa usar outro DBMS, se desejar. Mude a cadeia de conexão, se necessário. | ||||||
|  | 
 | ||||||
|  | ### Criar banco de dados e aplicar migrações de banco de dados | ||||||
|  | 
 | ||||||
|  | Você tem duas opções para criar o banco de dados. | ||||||
|  | 
 | ||||||
|  | #### Usando o aplicativo DbMigrator | ||||||
|  | 
 | ||||||
|  | A solução contém um aplicativo de console (nomeado `Acme.BookStore.DbMigrator`nesta amostra) que pode criar banco de dados, aplicar migrações e propagar dados iniciais. É útil no desenvolvimento e no ambiente de produção. | ||||||
|  | 
 | ||||||
|  | > `.DbMigrator`projeto tem o seu próprio `appsettings.json`. Portanto, se você alterou a cadeia de conexão acima, também deve alterar esta. | ||||||
|  | 
 | ||||||
|  | Clique com o botão direito do mouse no `.DbMigrator`projeto e selecione **Definir como Projeto de Inicialização** : | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | 
 | ||||||
|  | Pressione F5 (ou Ctrl + F5) para executar o aplicativo. Terá uma saída como mostrado abaixo: | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | 
 | ||||||
|  | #### Usando o comando EF Core Update-Database | ||||||
|  | 
 | ||||||
|  | O Ef Core possui um `Update-Database`comando que cria banco de dados, se necessário, e aplica migrações pendentes. Clique com o botão direito do mouse no `.Web`projeto e selecione **Definir como Projeto de Inicialização** : | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | 
 | ||||||
|  | Abra o **Console do Gerenciador de Pacotes** , selecione o `.EntityFrameworkCore.DbMigrations`projeto como **Projeto Padrão** e execute o `Update-Database`comando: | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | 
 | ||||||
|  | Isso criará um novo banco de dados com base na cadeia de conexão configurada. | ||||||
|  | 
 | ||||||
|  | > O uso da `.Migrator`ferramenta é a maneira sugerida, porque também semeia os dados iniciais para poder executar corretamente o aplicativo Web. | ||||||
|  | 
 | ||||||
|  | ### Executando o aplicativo | ||||||
|  | 
 | ||||||
|  | #### Execute o host da API (lado do servidor) | ||||||
|  | 
 | ||||||
|  | Verifique se o `.HttpApi.Host`projeto é o projeto de inicialização e o aplicativo que abrirá uma interface do usuário do Swagger: | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | 
 | ||||||
|  | Você pode ver as APIs do aplicativo e testá-las aqui. Obtenha [mais informações](https://swagger.io/tools/swagger-ui/) sobre a interface do usuário do Swagger. | ||||||
|  | 
 | ||||||
|  | ##### Autorização para a interface do usuário do Swagger | ||||||
|  | 
 | ||||||
|  | A maioria das APIs de aplicativos requer autenticação e autorização. Se você deseja testar APIs autorizadas, vá manualmente para a `/Account/Login`página, digite `admin`como o nome de usuário e `1q2w3E*`a senha para efetuar login no aplicativo. Você também poderá executar APIs autorizadas. | ||||||
|  | 
 | ||||||
|  | #### Execute o aplicativo angular (lado do cliente) | ||||||
|  | 
 | ||||||
|  | Vá para a `angular`pasta, abra um terminal de linha de comando, digite o `yarn`comando (sugerimos ao gerenciador de pacotes do [yarn](https://yarnpkg.com/) enquanto o npm install também funcionará na maioria dos casos): | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | yarn | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Depois que todos os módulos do nó estiverem carregados, execute `yarn start`ou `npm start`comando: | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | yarn start | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Abra seu navegador favorito e vá para `localhost:4200`URL. Nome de usuário inicial é `admin`e senha é `1q2w3E*`. | ||||||
|  | 
 | ||||||
|  | O modelo de inicialização inclui os módulos de **gerenciamento de** **identidade** e **gerenciamento de inquilino** . Após o login, o menu Administração estará disponível, onde você poderá gerenciar **inquilinos** , **funções** , **usuários** e suas **permissões** . | ||||||
|  | 
 | ||||||
|  | > Recomendamos o [Visual Studio Code](https://code.visualstudio.com/) como editor do projeto Angular, mas você pode usar seu editor favorito. | ||||||
|  | 
 | ||||||
|  | ### Qual é o próximo? | ||||||
|  | 
 | ||||||
|  | - [Tutorial de desenvolvimento de aplicativos](Tutorials/Angular/Part-I.md) | ||||||
| @ -0,0 +1,183 @@ | |||||||
|  | # Introdução ao ABP com o aplicativo Web MVC AspNet Core | ||||||
|  | 
 | ||||||
|  | Este tutorial explica como iniciar o ABP do zero com dependências mínimas. Você geralmente deseja começar com o **modelo de inicialização** . | ||||||
|  | 
 | ||||||
|  | ## Criar um novo projeto | ||||||
|  | 
 | ||||||
|  | 1. Crie um novo aplicativo da Web vazio do AspNet Core no Visual Studio: | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | 
 | ||||||
|  | 1. Selecionar modelo vazio | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | 
 | ||||||
|  | Você pode selecionar outro modelo, mas quero mostrá-lo em um projeto claro. | ||||||
|  | 
 | ||||||
|  | ## Instale o pacote Volo.Abp.AspNetCore.Mvc | ||||||
|  | 
 | ||||||
|  | Volo.Abp.AspNetCore.Mvc é um pacote de integração do AspNet Core MVC para ABP. Então, instale-o no seu projeto: | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | Install-Package Volo.Abp.AspNetCore.Mvc | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ## Criar o primeiro módulo ABP | ||||||
|  | 
 | ||||||
|  | O ABP é uma estrutura modular e requer uma classe de **módulo de inicialização (raiz)** derivada de `AbpModule`: | ||||||
|  | 
 | ||||||
|  | ```csharp | ||||||
|  | using Microsoft.AspNetCore.Builder; | ||||||
|  | using Microsoft.AspNetCore.Hosting; | ||||||
|  | using Microsoft.Extensions.DependencyInjection; | ||||||
|  | using Volo.Abp; | ||||||
|  | using Volo.Abp.AspNetCore.Modularity; | ||||||
|  | using Volo.Abp.AspNetCore.Mvc; | ||||||
|  | using Volo.Abp.Modularity; | ||||||
|  | 
 | ||||||
|  | namespace BasicAspNetCoreApplication | ||||||
|  | { | ||||||
|  |     [DependsOn(typeof(AbpAspNetCoreMvcModule))] | ||||||
|  |     public class AppModule : AbpModule | ||||||
|  |     { | ||||||
|  |         public override void OnApplicationInitialization(ApplicationInitializationContext context) | ||||||
|  |         { | ||||||
|  |             var app = context.GetApplicationBuilder(); | ||||||
|  |             var env = context.GetEnvironment(); | ||||||
|  | 
 | ||||||
|  |             if (env.IsDevelopment()) | ||||||
|  |             { | ||||||
|  |                 app.UseDeveloperExceptionPage(); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             app.UseMvcWithDefaultRoute(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | `AppModule` é um bom nome para o módulo de inicialização de um aplicativo. | ||||||
|  | 
 | ||||||
|  | Os pacotes ABP definem as classes do módulo e um módulo pode depender de outro módulo. No código acima, nosso `AppModule`depende `AbpAspNetCoreMvcModule`(definido pelo pacote Volo.Abp.AspNetCore.Mvc). É comum adicionar um `DependsOn`atributo após a instalação de um novo pacote de nuget ABP. | ||||||
|  | 
 | ||||||
|  | Em vez da classe Startup, estamos configurando o pipeline do ASP.NET Core nesta classe de módulo. | ||||||
|  | 
 | ||||||
|  | ## A classe de inicialização | ||||||
|  | 
 | ||||||
|  | O próximo passo é modificar a classe Startup para integrar ao sistema do módulo ABP: | ||||||
|  | 
 | ||||||
|  | ```csharp | ||||||
|  | using System; | ||||||
|  | using Microsoft.AspNetCore.Builder; | ||||||
|  | using Microsoft.Extensions.DependencyInjection; | ||||||
|  | 
 | ||||||
|  | namespace BasicAspNetCoreApplication | ||||||
|  | { | ||||||
|  |     public class Startup | ||||||
|  |     { | ||||||
|  |         public IServiceProvider ConfigureServices(IServiceCollection services) | ||||||
|  |         { | ||||||
|  |             services.AddApplication<AppModule>(); | ||||||
|  | 
 | ||||||
|  |             return services.BuildServiceProviderFromFactory(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public void Configure(IApplicationBuilder app) | ||||||
|  |         { | ||||||
|  |             app.InitializeApplication(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | `ConfigureServices`Método alterado para retornar em `IServiceProvider`vez de `void`. Essa alteração nos permite substituir a injeção de dependência do AspNet Core por outra estrutura (consulte a seção de integração com Autofac abaixo). `services.AddApplication<AppModule>()`adiciona todos os serviços definidos em todos os módulos a partir do `AppModule`. | ||||||
|  | 
 | ||||||
|  | `app.InitializeApplication()`O `Configure`método call in inicializa e inicia o aplicativo. | ||||||
|  | 
 | ||||||
|  | ## Olá Mundo! | ||||||
|  | 
 | ||||||
|  | O aplicativo acima não faz nada. Vamos criar um controlador MVC que faz algo: | ||||||
|  | 
 | ||||||
|  | ```csharp | ||||||
|  | using Microsoft.AspNetCore.Mvc; | ||||||
|  | using Volo.Abp.AspNetCore.Mvc; | ||||||
|  | 
 | ||||||
|  | namespace BasicAspNetCoreApplication.Controllers | ||||||
|  | { | ||||||
|  |     public class HomeController : AbpController | ||||||
|  |     { | ||||||
|  |         public IActionResult Index() | ||||||
|  |         { | ||||||
|  |             return Content("Hello World!"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Se você executar o aplicativo, verá um "Olá, mundo!" mensagem na página. | ||||||
|  | 
 | ||||||
|  | Derivado `HomeController`de em `AbpController`vez de `Controller`classe padrão . Isso não é necessário, mas a `AbpController`classe possui propriedades e métodos base úteis para facilitar seu desenvolvimento. | ||||||
|  | 
 | ||||||
|  | ## Usando Autofac como a estrutura de injeção de dependência | ||||||
|  | 
 | ||||||
|  | Embora o sistema de Injeção de Dependência (DI) do AspNet Core seja adequado para requisitos básicos, o Autofac fornece recursos avançados, como Injeção de Propriedade e Interceptação de Método, exigidos pela ABP para executar recursos avançados da estrutura de aplicativos. | ||||||
|  | 
 | ||||||
|  | Substituir o sistema DI do AspNet Core pelo Autofac e integrar ao ABP é bastante fácil. | ||||||
|  | 
 | ||||||
|  | 1. Instale o pacote [Volo.Abp.Autofac](https://www.nuget.org/packages/Volo.Abp.Autofac) | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | Install-Package Volo.Abp.Autofac | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | 1. Adicionar `AbpAutofacModule`dependência | ||||||
|  | 
 | ||||||
|  | ```csharp | ||||||
|  | [DependsOn(typeof(AbpAspNetCoreMvcModule))] | ||||||
|  | [DependsOn(typeof(AbpAutofacModule))] //Add dependency to ABP Autofac module | ||||||
|  | public class AppModule : AbpModule | ||||||
|  | { | ||||||
|  |     ... | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | 1. Altere a `services.AddApplication<AppModule>();`linha na `Startup`classe, como mostrado abaixo: | ||||||
|  | 
 | ||||||
|  | ```csharp | ||||||
|  | services.AddApplication<AppModule>(options => | ||||||
|  | { | ||||||
|  |     options.UseAutofac(); //Integrate to Autofac | ||||||
|  | }); | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | 1. Atualize `Program.cs`para não usar o `WebHost.CreateDefaultBuilder()`método, pois ele usa o contêiner DI padrão: | ||||||
|  | 
 | ||||||
|  | ```csharp | ||||||
|  | public class Program | ||||||
|  | { | ||||||
|  |     public static void Main(string[] args) | ||||||
|  |     { | ||||||
|  |         /* | ||||||
|  |             https://github.com/aspnet/AspNetCore/issues/4206#issuecomment-445612167 | ||||||
|  |             CurrentDirectoryHelpers exists in: \framework\src\Volo.Abp.AspNetCore.Mvc\Microsoft\AspNetCore\InProcess\CurrentDirectoryHelpers.cs | ||||||
|  |             Will remove CurrentDirectoryHelpers.cs when upgrade to ASP.NET Core 3.0. | ||||||
|  |         */ | ||||||
|  |         CurrentDirectoryHelpers.SetCurrentDirectory(); | ||||||
|  | 
 | ||||||
|  |         BuildWebHostInternal(args).Run(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static IWebHost BuildWebHostInternal(string[] args) => | ||||||
|  |         new WebHostBuilder() | ||||||
|  |             .UseKestrel() | ||||||
|  |             .UseContentRoot(Directory.GetCurrentDirectory()) | ||||||
|  |             .UseIIS() | ||||||
|  |             .UseIISIntegration() | ||||||
|  |             .UseStartup<Startup>() | ||||||
|  |             .Build(); | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ## Código fonte | ||||||
|  | 
 | ||||||
|  | Obter código-fonte do projeto de exemplo criada neste tutorial a partir de [aqui](https://github.com/abpframework/abp/tree/master/samples/BasicAspNetCoreApplication) . | ||||||
| @ -0,0 +1,102 @@ | |||||||
|  | ## Introdução ao modelo ASP.NET Core MVC | ||||||
|  | 
 | ||||||
|  | Este tutorial explica como criar um novo aplicativo Web ASP.NET Core MVC usando o modelo de inicialização, configurá-lo e executá-lo. | ||||||
|  | 
 | ||||||
|  | ### Criando um novo projeto | ||||||
|  | 
 | ||||||
|  | Este tutorial usa o **ABP CLI** para criar um novo projeto. Consulte a página [Introdução](https://abp.io/get-started) para outras opções. | ||||||
|  | 
 | ||||||
|  | Instale a CLI ABP usando uma janela de linha de comando, se você não tiver instalado antes: | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | dotnet tool install -g Volo.Abp.Cli | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Use o `abp new`comando em uma pasta vazia para criar seu projeto: | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | abp new Acme.BookStore | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | > Você pode usar diferentes níveis de namespaces; por exemplo, BookStore, Acme.BookStore ou Acme.Retail.BookStore. | ||||||
|  | 
 | ||||||
|  | `new`O comando cria um **aplicativo MVC em camadas** com o **Entity Framework Core** como o provedor de banco de dados. No entanto, possui opções adicionais. Consulte a [documentação](CLI.md) da [CLI](CLI.md) para todas as opções disponíveis. | ||||||
|  | 
 | ||||||
|  | #### Pré requisitos | ||||||
|  | 
 | ||||||
|  | A solução criada requer; | ||||||
|  | 
 | ||||||
|  | - [Visual Studio 2017 (v15.9.0 +)](https://visualstudio.microsoft.com/tr/downloads/) | ||||||
|  | - [.NET Core 2.2 ou superior](https://www.microsoft.com/net/download/dotnet-core/) | ||||||
|  | 
 | ||||||
|  | ### A Estrutura da Solução | ||||||
|  | 
 | ||||||
|  | Abra a solução no **Visual Studio** : | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | 
 | ||||||
|  | A solução possui uma estrutura em camadas (baseada no [Domain Driven Design](Domain-Driven-Design.md) ) e contém projetos de teste de unidade e integração adequadamente configurados para trabalhar com o **banco de** dados de **memória** **EF Core** e **SQLite** . | ||||||
|  | 
 | ||||||
|  | > Consulte o [documento do modelo de aplicativo](Startup-Templates/Application.md) para entender a estrutura da solução em detalhes. | ||||||
|  | 
 | ||||||
|  | ### Cadeia de Conexão de Banco de Dados | ||||||
|  | 
 | ||||||
|  | Verifique a **cadeia de conexão** no `appsettings.json`arquivo no `.Web`projeto: | ||||||
|  | 
 | ||||||
|  | ```json | ||||||
|  | { | ||||||
|  |   "ConnectionStrings": { | ||||||
|  |     "Default": "Server=localhost;Database=BookStore;Trusted_Connection=True" | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | A solução está configurada para usar o **Entity Framework Core** com o **MS SQL Server** . O EF Core suporta [vários](https://docs.microsoft.com/en-us/ef/core/providers/) provedores de banco de dados, para que você possa usar outro DBMS, se desejar. Mude a cadeia de conexão, se necessário. | ||||||
|  | 
 | ||||||
|  | ### Criar banco de dados e aplicar migrações de banco de dados | ||||||
|  | 
 | ||||||
|  | Você tem duas opções para criar o banco de dados. | ||||||
|  | 
 | ||||||
|  | #### Usando o aplicativo DbMigrator | ||||||
|  | 
 | ||||||
|  | A solução contém um aplicativo de console (nomeado `Acme.BookStore.DbMigrator`nesta amostra) que pode criar banco de dados, aplicar migrações e propagar dados iniciais. É útil no desenvolvimento e no ambiente de produção. | ||||||
|  | 
 | ||||||
|  | > `.DbMigrator`projeto tem o seu próprio `appsettings.json`. Portanto, se você alterou a cadeia de conexão acima, também deve alterar esta. | ||||||
|  | 
 | ||||||
|  | Clique com o botão direito do mouse no `.DbMigrator`projeto e selecione **Definir como Projeto de Inicialização** : | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | 
 | ||||||
|  | Pressione F5 (ou Ctrl + F5) para executar o aplicativo. Terá uma saída como mostrado abaixo: | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | 
 | ||||||
|  | #### Usando o comando EF Core Update-Database | ||||||
|  | 
 | ||||||
|  | O Ef Core possui um `Update-Database`comando que cria banco de dados, se necessário, e aplica migrações pendentes. Clique com o botão direito do mouse no `.Web`projeto e selecione **Definir como Projeto de Inicialização** : | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | 
 | ||||||
|  | Abra o **Console do Gerenciador de Pacotes** , selecione o `.EntityFrameworkCore.DbMigrations`projeto como **Projeto Padrão** e execute o `Update-Database`comando: | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | 
 | ||||||
|  | Isso criará um novo banco de dados com base na cadeia de conexão configurada. | ||||||
|  | 
 | ||||||
|  | > O uso da `.Migrator`ferramenta é a maneira sugerida, porque também semeia os dados iniciais para poder executar corretamente o aplicativo Web. | ||||||
|  | 
 | ||||||
|  | ### Executando o aplicativo | ||||||
|  | 
 | ||||||
|  | Verifique se o `.Web`projeto é o projeto de inicialização. Execute o aplicativo que abrirá a página **inicial** no seu navegador: | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | 
 | ||||||
|  | Clique no botão **Login** , insira `admin`como nome de usuário e `1q2w3E*`senha para acessar o aplicativo. | ||||||
|  | 
 | ||||||
|  | O modelo de inicialização inclui os módulos de **gerenciamento de** **identidade** e **gerenciamento de inquilino** . Após o login, o menu Administração estará disponível, onde você poderá gerenciar **inquilinos** , **funções** , **usuários** e suas **permissões** . A página de gerenciamento de usuários é mostrada abaixo: | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | 
 | ||||||
|  | ### Qual é o próximo? | ||||||
|  | 
 | ||||||
|  | - [Tutorial de desenvolvimento de aplicativos](Tutorials/AspNetCore-Mvc/Part-I.md) | ||||||
| @ -0,0 +1,182 @@ | |||||||
|  | # Introdução ao ABP com aplicativo de console | ||||||
|  | 
 | ||||||
|  | Este tutorial explica como iniciar o ABP do zero com dependências mínimas. Você geralmente deseja começar com um **modelo de inicialização** . | ||||||
|  | 
 | ||||||
|  | ## Criar um novo projeto | ||||||
|  | 
 | ||||||
|  | Crie um novo aplicativo regular .Net Core Console do Visual Studio: | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | 
 | ||||||
|  | ## Instale o pacote Volo.Abp | ||||||
|  | 
 | ||||||
|  | Volo.Abp.Core é o pacote principal de pepitas para criar aplicativos baseados em ABP. Então, instale-o no seu projeto: | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | Install-Package Volo.Abp.Core | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ## Criar o primeiro módulo ABP | ||||||
|  | 
 | ||||||
|  | O ABP é uma estrutura modular e requer uma classe de **módulo de inicialização (raiz)** derivada de `AbpModule`: | ||||||
|  | 
 | ||||||
|  | ```csharp | ||||||
|  | using Microsoft.Extensions.DependencyInjection; | ||||||
|  | using Volo.Abp.Modularity; | ||||||
|  | 
 | ||||||
|  | namespace AbpConsoleDemo | ||||||
|  | { | ||||||
|  |     public class AppModule : AbpModule | ||||||
|  |     { | ||||||
|  |          | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | `AppModule` é um bom nome para o módulo de inicialização de um aplicativo. | ||||||
|  | 
 | ||||||
|  | ## Inicializar o aplicativo | ||||||
|  | 
 | ||||||
|  | A próxima etapa é inicializar o aplicativo usando o módulo de inicialização criado acima: | ||||||
|  | 
 | ||||||
|  | ```csharp | ||||||
|  | using System; | ||||||
|  | using Volo.Abp; | ||||||
|  | 
 | ||||||
|  | namespace AbpConsoleDemo | ||||||
|  | { | ||||||
|  |     class Program | ||||||
|  |     { | ||||||
|  |         static void Main(string[] args) | ||||||
|  |         { | ||||||
|  |             using (var application = AbpApplicationFactory.Create<AppModule>()) | ||||||
|  |             { | ||||||
|  |                 application.Initialize(); | ||||||
|  | 
 | ||||||
|  |                 Console.WriteLine("Press ENTER to stop application..."); | ||||||
|  |                 Console.ReadLine(); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | `AbpApplicationFactory`é usado para criar o aplicativo e carregar todos os módulos que tomam `AppModule`como módulo de inicialização. `Initialize()`O método inicia o aplicativo. | ||||||
|  | 
 | ||||||
|  | ## Olá Mundo! | ||||||
|  | 
 | ||||||
|  | O aplicativo acima não faz nada. Vamos criar um serviço que faça algo: | ||||||
|  | 
 | ||||||
|  | ```csharp | ||||||
|  | using System; | ||||||
|  | using Volo.Abp.DependencyInjection; | ||||||
|  | 
 | ||||||
|  | namespace AbpConsoleDemo | ||||||
|  | { | ||||||
|  |     public class HelloWorldService : ITransientDependency | ||||||
|  |     { | ||||||
|  |         public void SayHello() | ||||||
|  |         { | ||||||
|  |             Console.WriteLine("Hello World!"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | `ITransientDependency`é uma interface especial do ABP que registra automaticamente o serviço como transitório (consulte o [documento de injeção de dependência](Dependency-Injection.md) ). | ||||||
|  | 
 | ||||||
|  | Agora, podemos resolver o problema `HelloWorldService`e dizer olá. Altere o Program.cs como mostrado abaixo: | ||||||
|  | 
 | ||||||
|  | ```csharp | ||||||
|  | using System; | ||||||
|  | using Microsoft.Extensions.DependencyInjection; | ||||||
|  | using Volo.Abp; | ||||||
|  | 
 | ||||||
|  | namespace AbpConsoleDemo | ||||||
|  | { | ||||||
|  |     class Program | ||||||
|  |     { | ||||||
|  |         static void Main(string[] args) | ||||||
|  |         { | ||||||
|  |             using (var application = AbpApplicationFactory.Create<AppModule>()) | ||||||
|  |             { | ||||||
|  |                 application.Initialize(); | ||||||
|  | 
 | ||||||
|  |                 //Resolve a service and use it | ||||||
|  |                 var helloWorldService =  | ||||||
|  |                     application.ServiceProvider.GetService<HelloWorldService>(); | ||||||
|  |                 helloWorldService.SayHello(); | ||||||
|  | 
 | ||||||
|  |                 Console.WriteLine("Press ENTER to stop application..."); | ||||||
|  |                 Console.ReadLine(); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Embora seja suficiente para este exemplo de código simples, é sempre recomendável criar escopos no caso de resolver diretamente dependências de `IServiceProvider`(consulte a [documentação de Injeção de Dependências](Dependency-Injection.md)). | ||||||
|  | 
 | ||||||
|  | ## Usando Autofac como a estrutura de injeção de dependência | ||||||
|  | 
 | ||||||
|  | Embora o sistema de Injeção de Dependência (DI) do AspNet Core seja adequado para requisitos básicos, o Autofac fornece recursos avançados, como Injeção de Propriedade e Interceptação de Método, exigidos pela ABP para executar recursos avançados da estrutura de aplicativos. | ||||||
|  | 
 | ||||||
|  | Substituir o sistema DI do AspNet Core pelo Autofac e integrar ao ABP é bastante fácil. | ||||||
|  | 
 | ||||||
|  | 1. Instale o pacote [Volo.Abp.Autofac](https://www.nuget.org/packages/Volo.Abp.Autofac) | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | Install-Package Volo.Abp.Autofac | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | 1. Adicionar `AbpAutofacModule`dependência | ||||||
|  | 
 | ||||||
|  | ```csharp | ||||||
|  | [DependsOn(typeof(AbpAutofacModule))] //Add dependency to the AbpAutofacModule | ||||||
|  | public class AppModule : AbpModule | ||||||
|  | { | ||||||
|  |      | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | 1. Mude o `Program.cs`arquivo como mostrado abaixo: | ||||||
|  | 
 | ||||||
|  | ```csharp | ||||||
|  | using System; | ||||||
|  | using Microsoft.Extensions.DependencyInjection; | ||||||
|  | using Volo.Abp; | ||||||
|  | 
 | ||||||
|  | namespace AbpConsoleDemo | ||||||
|  | { | ||||||
|  |     class Program | ||||||
|  |     { | ||||||
|  |         static void Main(string[] args) | ||||||
|  |         { | ||||||
|  |             using (var application = AbpApplicationFactory.Create<AppModule>(options => | ||||||
|  |             { | ||||||
|  |                 options.UseAutofac(); //Autofac integration | ||||||
|  |             })) | ||||||
|  |             { | ||||||
|  |                 application.Initialize(); | ||||||
|  | 
 | ||||||
|  |                 //Resolve a service and use it | ||||||
|  |                 var helloWorldService =  | ||||||
|  |                     application.ServiceProvider.GetService<HelloWorldService>(); | ||||||
|  |                 helloWorldService.SayHello(); | ||||||
|  | 
 | ||||||
|  |                 Console.WriteLine("Press ENTER to stop application..."); | ||||||
|  |                 Console.ReadLine(); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Apenas chamado `options.UseAutofac()`método nas `AbpApplicationFactory.Create`opções. | ||||||
|  | 
 | ||||||
|  | ## Código fonte | ||||||
|  | 
 | ||||||
|  | Obter código-fonte do projeto de exemplo criada neste tutorial a partir de [aqui](https://github.com/abpframework/abp/tree/master/samples/BasicConsoleApplication) . | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |    | ||||||
| @ -0,0 +1,31 @@ | |||||||
|  | # Documentação ABP | ||||||
|  | 
 | ||||||
|  | O ABP é uma **estrutura de aplicativos de código aberto** focada no desenvolvimento de aplicativos da Web baseado no ASP.NET Core, mas também suporta o desenvolvimento de outros tipos de aplicativos. | ||||||
|  | 
 | ||||||
|  | Explore o menu de navegação esquerdo para mergulhar fundo na documentação. | ||||||
|  | 
 | ||||||
|  | ## Status do projeto | ||||||
|  | 
 | ||||||
|  | ABP é a **próxima geração** da estrutura de código aberto [ASP.NET Boilerplate](https://aspnetboilerplate.com/) . Atualmente, está em fase de visualização e não está pronto para uso na produção. A documentação ainda está em andamento e está longe de estar completa. | ||||||
|  | 
 | ||||||
|  | Para aplicativos de curto prazo e em nível de produção, é sugerido o uso da estrutura do [ASP.NET Boilerplate](https://aspnetboilerplate.com/) , que possui um rico conjunto de recursos, maduro, mantido ativamente e atualizado. | ||||||
|  | 
 | ||||||
|  | ## Começando | ||||||
|  | 
 | ||||||
|  | A maneira mais fácil de iniciar um novo projeto com o ABP é usar os modelos de inicialização: | ||||||
|  | 
 | ||||||
|  | - [Modelo de interface do usuário do ASP.NET Core MVC (Razor Pages)](Getting-Started-AspNetCore-MVC-Template.md) | ||||||
|  | - [Modelo de interface do usuário angular](Getting-Started-Angular-Template.md) | ||||||
|  | 
 | ||||||
|  | Se você deseja começar do zero (com um projeto vazio), instale manualmente o ABP Framework e use os seguintes tutoriais: | ||||||
|  | 
 | ||||||
|  | - [Aplicação de console](Getting-Started-Console-Application.md) | ||||||
|  | - [Aplicativo da Web principal do ASP.NET](Getting-Started-AspNetCore-Application.md) | ||||||
|  | 
 | ||||||
|  | ## Código fonte | ||||||
|  | 
 | ||||||
|  | ABP está hospedado no GitHub. Veja [o código fonte](https://github.com/abpframework/abp) . | ||||||
|  | 
 | ||||||
|  | ## Deseja contribuir? | ||||||
|  | 
 | ||||||
|  | O ABP é um projeto de código aberto orientado pela comunidade. Consulte [o guia de contribuição](Contribution/Index.md) se você quiser fazer parte deste projeto. | ||||||
| @ -0,0 +1,462 @@ | |||||||
|  | ## Tutorial do ASP.NET Core MVC - Parte I | ||||||
|  | 
 | ||||||
|  | ### Sobre este tutorial | ||||||
|  | 
 | ||||||
|  | Nesta série de tutoriais, você criará um aplicativo usado para gerenciar uma lista de livros e seus autores. **O Entity Framework Core** (EF Core) será usado como o provedor ORM, pois é o provedor de banco de dados padrão. | ||||||
|  | 
 | ||||||
|  | Esta é a primeira parte da série de tutoriais do ASP.NET Core MVC. 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.md) | ||||||
|  | - [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/master/samples/BookStore) . | ||||||
|  | 
 | ||||||
|  | > Você também pode assistir a [este curso em vídeo](https://amazingsolutions.teachable.com/p/lets-build-the-bookstore-application) preparado por um membro da comunidade ABP, com base neste tutorial. | ||||||
|  | 
 | ||||||
|  | ### Criando o projeto | ||||||
|  | 
 | ||||||
|  | Crie um novo projeto chamado `Acme.BookStore`, crie o banco de dados e execute o aplicativo seguindo o [documento Introdução](Getting-Started-AspNetCore-MVC-Template.md). | ||||||
|  | 
 | ||||||
|  | ### Estrutura da solução | ||||||
|  | 
 | ||||||
|  | É assim que a estrutura da solução em camadas cuida da criação: | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | 
 | ||||||
|  | > Você pode ver o [documento do modelo de aplicativo](https://docs.abp.io/en/abp/latest/Startup-Templates/Application) 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](https://docs.abp.io/en/abp/latest/Entities.md) , [serviços de domínio](https://docs.abp.io/en/abp/latest/Domain-Services) 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](https://docs.abp.io/en/abp/latest/Entities) 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](https://docs.abp.io/en/abp/latest/Entities) 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 | ||||||
|  | 
 | ||||||
|  | O EF Core exige que você relacione entidades com seu DbContext. A maneira mais fácil de fazer isso é adicionar uma `DbSet`propriedade à `BookStoreDbContext`classe no `Acme.BookStore.EntityFrameworkCore`projeto, conforme mostrado abaixo: | ||||||
|  | 
 | ||||||
|  | ```csharp | ||||||
|  |     public class BookStoreDbContext : AbpDbContext<BookStoreDbContext> | ||||||
|  |     { | ||||||
|  |         public DbSet<Book> Books { get; set; } | ||||||
|  | 		... | ||||||
|  |     } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | #### Configure sua entidade do livro | ||||||
|  | 
 | ||||||
|  | Abra o `BookStoreDbContextModelCreatingExtensions.cs`arquivo no `Acme.BookStore.EntityFrameworkCore`projeto e adicione o seguinte código ao final do `ConfigureBookStore`método para configurar a entidade Livro: | ||||||
|  | 
 | ||||||
|  | ```csharp | ||||||
|  | builder.Entity<Book>(b => | ||||||
|  | { | ||||||
|  |     b.ToTable(BookStoreConsts.DbTablePrefix + "Books", BookStoreConsts.DbSchema); | ||||||
|  |     b.ConfigureByConvention(); //auto configure for the base class props | ||||||
|  |     b.Property(x => x.Name).IsRequired().HasMaxLength(128); | ||||||
|  | }); | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | #### Adicionar nova migração e atualizar o banco de dados | ||||||
|  | 
 | ||||||
|  | O modelo de inicialização usa [as primeiras migrações do código principal EF](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/) para criar e manter o esquema do banco de dados. Abra o **Gerenciador de Console Package (PMC)** (sob as *Ferramentas / Gerente Nuget Package* menu), selecione o `Acme.BookStore.EntityFrameworkCore.DbMigrations`como o **projeto padrão** e execute o seguinte comando: | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | 
 | ||||||
|  | Isso criará uma nova classe de migração dentro da `Migrations`pasta. Em seguida, execute o `Update-Database`comando para atualizar o esquema do banco de dados: | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | PM> Update-Database | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | #### Adicionar dados de amostra | ||||||
|  | 
 | ||||||
|  | `Update-Database`O comando criou a `AppBooks`tabela no banco de dados. Abra seu banco de dados e insira algumas linhas de amostra, para que você possa mostrá-las na página: | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | 
 | ||||||
|  | ### Crie o serviço de aplicativo | ||||||
|  | 
 | ||||||
|  | O próximo passo é criar um [serviço de aplicativo](https://docs.abp.io/en/abp/latest/Application-Services) 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](https://docs.abp.io/en/abp/latest/Data-Transfer-Objects) 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](https://automapper.org/) 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](https://docs.abp.io/en/abp/latest/Validation) 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](https://docs.abp.io/en/abp/latest/Repositories) do [repositório](https://docs.abp.io/en/abp/latest/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**](https://docs.abp.io/en/abp/latest/AspNetCore/Auto-API-Controllers) 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](https://swagger.io/tools/swagger-ui/) do [swagger](https://swagger.io/tools/swagger-ui/) usando a biblioteca [Swashbuckle.AspNetCore](https://github.com/domaindrivendev/Swashbuckle.AspNetCore) . Execute o 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. | ||||||
|  | 
 | ||||||
|  | ### Proxies dinâmicos de JavaScript | ||||||
|  | 
 | ||||||
|  | É comum chamar pontos de extremidade da API HTTP via AJAX do lado do **JavaScript** . Você pode usar `$.ajax`ou outra ferramenta para chamar os pontos de extremidade. No entanto, o ABP oferece uma maneira melhor. | ||||||
|  | 
 | ||||||
|  | O ABP cria **dinamicamente** **proxies** JavaScript para todos os pontos de extremidade da API. Portanto, você pode usar qualquer **terminal,** assim como chamar uma **função JavaScript** . | ||||||
|  | 
 | ||||||
|  | #### Testando no console do desenvolvedor do navegador | ||||||
|  | 
 | ||||||
|  | Você pode testar facilmente os proxies JavaScript usando o **Console** do **desenvolvedor** do seu navegador favorito agora. Execute o aplicativo, abra as **ferramentas de desenvolvedor** do navegador (atalho: F12), vá para a guia **Console** , digite o seguinte código e pressione enter: | ||||||
|  | 
 | ||||||
|  | ```js | ||||||
|  | acme.bookStore.book.getList({}).done(function (result) { console.log(result); }); | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | - `acme.bookStore`é o espaço para nome do `BookAppService`convertido em [camelCase](https://en.wikipedia.org/wiki/Camel_case) . | ||||||
|  | - `book`é o nome convencional para o `BookAppService`(postfix do AppService removido e convertido em camelCase). | ||||||
|  | - `getList`é o nome convencional para o `GetListAsync`método definido na `AsyncCrudAppService`classe base (postfix assíncrono removido e convertido em camelCase). | ||||||
|  | - `{}`O argumento é usado para enviar um objeto vazio ao `GetListAsync`método que normalmente espera um objeto do tipo `PagedAndSortedResultRequestDto`usado para enviar opções de paginação e classificação ao servidor (todas as propriedades são opcionais, para que você possa enviar um objeto vazio). | ||||||
|  | - `getList`A função retorna a `promise`. Portanto, você pode passar um retorno de chamada para a função `done`(ou `then`) para obter o resultado do servidor. | ||||||
|  | 
 | ||||||
|  | A execução desse código produz a seguinte saída: | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | 
 | ||||||
|  | Você pode ver a **lista de livros** retornada do servidor. Você também pode verificar a guia de **rede** das ferramentas do desenvolvedor para ver a comunicação do cliente com o servidor: | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | 
 | ||||||
|  | Vamos **criar um novo livro** usando a `create`função: | ||||||
|  | 
 | ||||||
|  | ```js | ||||||
|  | acme.bookStore.book.create({ name: 'Foundation', type: 7, publishDate: '1951-05-24', price: 21.5 }).done(function (result) { console.log('successfully created the book with id: ' + result.id); }); | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Você deve ver uma mensagem no console, algo assim: | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | successfully created the book with id: f3f03580-c1aa-d6a9-072d-39e75c69f5c7 | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Verifique a `Books`tabela no banco de dados para ver a nova linha do livro. Você pode tentar `get`, `update`e `delete`funciona mesmo. | ||||||
|  | 
 | ||||||
|  | ### Crie a página de livros | ||||||
|  | 
 | ||||||
|  | É hora de criar algo visível e utilizável! Em vez do MVC clássico, usaremos a nova abordagem de [interface do usuário do Razor Pages,](https://docs.microsoft.com/en-us/aspnet/core/tutorials/razor-pages/razor-pages-start) recomendada pela Microsoft. | ||||||
|  | 
 | ||||||
|  | Crie uma nova `Books`pasta na `Pages`pasta do `Acme.BookStore.Web`projeto e adicione uma nova página Razor denominada `Index.cshtml`: | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | 
 | ||||||
|  | Abra `Index.cshtml`e altere o conteúdo, como mostrado abaixo: | ||||||
|  | 
 | ||||||
|  | ```html | ||||||
|  | @page | ||||||
|  | @using Acme.BookStore.Web.Pages.Books | ||||||
|  | @inherits Acme.BookStore.Web.Pages.BookStorePage | ||||||
|  | @model IndexModel | ||||||
|  | 
 | ||||||
|  | <h2>Books</h2> | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | - Esse código altera a herança padrão do Razor View Page Model para que ele **herda** da `BookStorePage`classe (em vez de `PageModel`). A `BookStorePage`classe que acompanha o modelo de inicialização e fornece algumas propriedades / métodos compartilhados usados por todas as páginas. | ||||||
|  | - Verifique se o `IndexModel`( *Index.cshtml.cs)* possui o `Acme.BookStore.Pages.Books`espaço para nome ou atualize-o no `Index.cshtml`. | ||||||
|  | 
 | ||||||
|  | #### Adicionar página de livros ao menu principal | ||||||
|  | 
 | ||||||
|  | Abra a `BookStoreMenuContributor`classe na `Menus`pasta e adicione o seguinte código ao final do `ConfigureMainMenuAsync`método: | ||||||
|  | 
 | ||||||
|  | ```csharp | ||||||
|  | context.Menu.AddItem( | ||||||
|  |     new ApplicationMenuItem("BooksStore", l["Menu:BookStore"]) | ||||||
|  |         .AddItem(new ApplicationMenuItem("BooksStore.Books", l["Menu:Books"], url: "/Books")) | ||||||
|  | ); | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | #### Localizando os itens de menu | ||||||
|  | 
 | ||||||
|  | Os textos de localização estão localizados na `Localization/BookStore`pasta do `Acme.BookStore.Domain.Shared`projeto: | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | 
 | ||||||
|  | Abra o `en.json`arquivo e adicione textos de localização `Menu:BookStore`e `Menu:Books`chaves ao final do arquivo: | ||||||
|  | 
 | ||||||
|  | ```json | ||||||
|  | { | ||||||
|  |   "culture": "en", | ||||||
|  |   "texts": { | ||||||
|  |     "Menu:BookStore": "Book Store", | ||||||
|  |     "Menu:Books": "Books" | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | - O sistema de localização da ABP é construído no sistema de [localização padrão do ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/localization) e o estende de várias maneiras. Consulte o [documento de localização](https://docs.abp.io/en/abp/latest/Localization) para obter detalhes. | ||||||
|  | - Os nomes das chaves de localização são arbitrários. Você pode definir qualquer nome. Preferimos adicionar `Menu:`prefixo aos itens de menu para distinguir de outros textos. Se um texto não estiver definido no arquivo de localização, ele **recuará** para a chave de localização (comportamento padrão do ASP.NET Core). | ||||||
|  | 
 | ||||||
|  | Execute o aplicativo e veja se o novo item de menu foi adicionado à barra superior: | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | 
 | ||||||
|  | Quando você clica no item de menu Livros, você é redirecionado para a nova página Livros. | ||||||
|  | 
 | ||||||
|  | #### Lista de livros | ||||||
|  | 
 | ||||||
|  | Usaremos o plug-in [Datatables.net](https://datatables.net/) JQuery para mostrar a lista de tabelas na página. As tabelas de dados podem funcionar completamente via AJAX, são rápidas e oferecem uma boa experiência ao usuário. O plug-in Datatables está configurado no modelo de inicialização, para que você possa usá-lo diretamente em qualquer página sem incluir nenhum estilo ou arquivo de script em sua página. | ||||||
|  | 
 | ||||||
|  | ##### Index.cshtml | ||||||
|  | 
 | ||||||
|  | Altere o `Pages/Books/Index.cshtml`seguinte: | ||||||
|  | 
 | ||||||
|  | ```html | ||||||
|  | @page | ||||||
|  | @inherits Acme.BookStore.Web.Pages.BookStorePage | ||||||
|  | @model Acme.BookStore.Web.Pages.Books.IndexModel | ||||||
|  | @section scripts | ||||||
|  | { | ||||||
|  |     <abp-script src="/Pages/Books/index.js" /> | ||||||
|  | } | ||||||
|  | <abp-card> | ||||||
|  |     <abp-card-header> | ||||||
|  |         <h2>@L["Books"]</h2> | ||||||
|  |     </abp-card-header> | ||||||
|  |     <abp-card-body> | ||||||
|  |         <abp-table striped-rows="true" id="BooksTable"> | ||||||
|  |             <thead> | ||||||
|  |                 <tr> | ||||||
|  |                     <th>@L["Name"]</th> | ||||||
|  |                     <th>@L["Type"]</th> | ||||||
|  |                     <th>@L["PublishDate"]</th> | ||||||
|  |                     <th>@L["Price"]</th> | ||||||
|  |                     <th>@L["CreationTime"]</th> | ||||||
|  |                 </tr> | ||||||
|  |             </thead> | ||||||
|  |         </abp-table> | ||||||
|  |     </abp-card-body> | ||||||
|  | </abp-card> | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | - `abp-script` [O auxiliar de marca](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/tag-helpers/intro) é usado para adicionar **scripts** externos à página. Possui muitos recursos adicionais em comparação com a `script`tag padrão . Ele lida com **minificação** e **controle** de **versão,** por exemplo. Consulte o [documento de compactação e redução](https://docs.abp.io/en/abp/latest/AspNetCore/Bundling-Minification) para obter detalhes. | ||||||
|  | - `abp-card`e `abp-table`são **auxiliares de tags** para o [componente de cartão](http://getbootstrap.com/docs/4.1/components/card/) do Twitter Bootstrap . Existem muitos auxiliares de tag no ABP para usar facilmente a maioria dos componentes de [autoinicialização](https://getbootstrap.com/) . Você também pode usar tags HTML regulares em vez desses auxiliares de tag, mas o uso de tag reduz o código HTML e evita erros com a ajuda do intellisense e da verificação do tipo de tempo de compilação. Consulte o [documento auxiliares](https://docs.abp.io/en/abp/latest/AspNetCore/Tag-Helpers) da [tag](https://docs.abp.io/en/abp/latest/AspNetCore/Tag-Helpers) . | ||||||
|  | - Você pode **localizar** os nomes das colunas no arquivo de localização, como fez nos itens de menu acima. | ||||||
|  | 
 | ||||||
|  | ##### Adicionar um arquivo de script | ||||||
|  | 
 | ||||||
|  | Crie um `index.js`arquivo JavaScript na `Pages/Books/`pasta: | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | 
 | ||||||
|  | `index.js` o conteúdo é mostrado abaixo: | ||||||
|  | 
 | ||||||
|  | ```js | ||||||
|  | $(function () { | ||||||
|  |     var dataTable = $('#BooksTable').DataTable(abp.libs.datatables.normalizeConfiguration({ | ||||||
|  |         ajax: abp.libs.datatables.createAjax(acme.bookStore.book.getList), | ||||||
|  |         columnDefs: [ | ||||||
|  |             { data: "name" }, | ||||||
|  |             { data: "type" }, | ||||||
|  |             { data: "publishDate" }, | ||||||
|  |             { data: "price" }, | ||||||
|  |             { data: "creationTime" } | ||||||
|  |         ] | ||||||
|  |     })); | ||||||
|  | }); | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | - `abp.libs.datatables.createAjax` é uma função auxiliar para adaptar os proxies dinâmicos da API JavaScript da ABP ao formato do Datatable. | ||||||
|  | - `abp.libs.datatables.normalizeConfiguration`é outra função auxiliar. Não há necessidade de usá-lo, mas simplifica a configuração das tabelas de dados, fornecendo valores convencionais para as opções ausentes. | ||||||
|  | - `acme.bookStore.book.getList` é a função para obter a lista de livros (você já viu isso antes). | ||||||
|  | - Consulte [a documentação do Datatable](https://datatables.net/manual/) para obter mais opções de configuração. | ||||||
|  | 
 | ||||||
|  | A interface do usuário final é mostrada abaixo: | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | 
 | ||||||
|  | ### Próxima parte | ||||||
|  | 
 | ||||||
|  | Veja a [próxima parte](https://docs.abp.io/en/abp/latest/Tutorials/AspNetCore-Mvc/Part-II) deste tutorial. | ||||||
| @ -0,0 +1,466 @@ | |||||||
|  | ## Tutorial do ASP.NET Core MVC - Parte II | ||||||
|  | 
 | ||||||
|  | ### Sobre este tutorial | ||||||
|  | 
 | ||||||
|  | Esta é a segunda parte da série de tutoriais do ASP.NET Core MVC. Veja todas as peças: | ||||||
|  | 
 | ||||||
|  | - [Parte I: Crie o projeto e uma página da lista de livros](https://docs.abp.io/en/abp/latest/Tutorials/AspNetCore-Mvc/Part-I) | ||||||
|  | - **Parte II: Criar, atualizar e excluir livros (este tutorial)** | ||||||
|  | - [Parte III: Testes de Integração](https://docs.abp.io/en/abp/latest/Tutorials/AspNetCore-Mvc/Part-III) | ||||||
|  | 
 | ||||||
|  | Você pode acessar o **código fonte** do aplicativo [no repositório GitHub](https://github.com/volosoft/abp/tree/master/samples/BookStore) . | ||||||
|  | 
 | ||||||
|  | > Você também pode assistir a [este curso em vídeo](https://amazingsolutions.teachable.com/p/lets-build-the-bookstore-application) preparado por um membro da comunidade ABP, com base neste tutorial. | ||||||
|  | 
 | ||||||
|  | ### Criando um novo livro | ||||||
|  | 
 | ||||||
|  | Nesta seção, você aprenderá como criar um novo formulário de diálogo modal para criar um novo livro. A caixa de diálogo do resultado será assim: | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | 
 | ||||||
|  | #### Crie o formulário modal | ||||||
|  | 
 | ||||||
|  | Crie uma nova página de navalha, nomeada `CreateModal.cshtml`sob a `Pages/Books`pasta do `Acme.BookStore.Web`projeto: | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | 
 | ||||||
|  | ##### CreateModal.cshtml.cs | ||||||
|  | 
 | ||||||
|  | Abra o `CreateModal.cshtml.cs`arquivo ( `CreateModalModel`classe) e substitua pelo seguinte código: | ||||||
|  | 
 | ||||||
|  | ```csharp | ||||||
|  | using System.Threading.Tasks; | ||||||
|  | using Microsoft.AspNetCore.Mvc; | ||||||
|  | 
 | ||||||
|  | namespace Acme.BookStore.Web.Pages.Books | ||||||
|  | { | ||||||
|  |     public class CreateModalModel : BookStorePageModel | ||||||
|  |     { | ||||||
|  |         [BindProperty] | ||||||
|  |         public CreateUpdateBookDto Book { get; set; } | ||||||
|  | 
 | ||||||
|  |         private readonly IBookAppService _bookAppService; | ||||||
|  | 
 | ||||||
|  |         public CreateModalModel(IBookAppService bookAppService) | ||||||
|  |         { | ||||||
|  |             _bookAppService = bookAppService; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public async Task<IActionResult> OnPostAsync() | ||||||
|  |         { | ||||||
|  |             await _bookAppService.CreateAsync(Book); | ||||||
|  |             return NoContent(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | - Esta classe é derivada do em `BookStorePageModel`vez do padrão `PageModel`. `BookStorePageModel`herda o `PageModel`e adiciona algumas propriedades / métodos comuns que podem ser usados pelas classes de modelo de página. | ||||||
|  | - `[BindProperty]`O atributo na `Book`propriedade vincula os dados de solicitação posterior a essa propriedade. | ||||||
|  | - Essa classe simplesmente injeta o `IBookAppService`em seu construtor e chama o `CreateAsync`método no `OnPostAsync`manipulador. | ||||||
|  | 
 | ||||||
|  | ##### CreateModal.cshtml | ||||||
|  | 
 | ||||||
|  | Abra o `CreateModal.cshtml`arquivo e cole o código abaixo: | ||||||
|  | 
 | ||||||
|  | ```html | ||||||
|  | @page | ||||||
|  | @inherits Acme.BookStore.Web.Pages.BookStorePage | ||||||
|  | @using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal | ||||||
|  | @model Acme.BookStore.Web.Pages.Books.CreateModalModel | ||||||
|  | @{ | ||||||
|  |     Layout = null; | ||||||
|  | } | ||||||
|  | <abp-dynamic-form abp-model="Book" data-ajaxForm="true" asp-page="/Books/CreateModal"> | ||||||
|  |     <abp-modal> | ||||||
|  |         <abp-modal-header title="@L["NewBook"].Value"></abp-modal-header> | ||||||
|  |         <abp-modal-body> | ||||||
|  |             <abp-form-content /> | ||||||
|  |         </abp-modal-body> | ||||||
|  |         <abp-modal-footer buttons="@(AbpModalButtons.Cancel|AbpModalButtons.Save)"></abp-modal-footer> | ||||||
|  |     </abp-modal> | ||||||
|  | </abp-dynamic-form> | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | - Este modal usa o  | ||||||
|  | 
 | ||||||
|  |   ``` | ||||||
|  |   abp-dynamic-form | ||||||
|  |   ``` | ||||||
|  | 
 | ||||||
|  |   auxiliar de marca para criar automaticamente o formulário a partir da  | ||||||
|  | 
 | ||||||
|  |   ``` | ||||||
|  |   CreateBookViewModel | ||||||
|  |   ``` | ||||||
|  | 
 | ||||||
|  |   classe. | ||||||
|  | 
 | ||||||
|  |   - `abp-model`O atributo indica o objeto do modelo, a `Book`propriedade neste caso. | ||||||
|  |   - `data-ajaxForm` O atributo faz com que o formulário seja enviado via AJAX, em vez de uma postagem de página clássica. | ||||||
|  |   - `abp-form-content`O auxiliar de marca é um espaço reservado para renderizar os controles do formulário (isso é opcional e necessário apenas se você tiver adicionado outro conteúdo à `abp-dynamic-form`marca, como nesta página). | ||||||
|  | 
 | ||||||
|  | #### Adicione o botão "Novo livro" | ||||||
|  | 
 | ||||||
|  | Abra `Pages/Books/Index.cshtml`e altere a `abp-card-header`tag, como mostrado abaixo: | ||||||
|  | 
 | ||||||
|  | ```html | ||||||
|  | <abp-card-header> | ||||||
|  |     <abp-row> | ||||||
|  |         <abp-column size-md="_6"> | ||||||
|  |             <h2>@L["Books"]</h2> | ||||||
|  |         </abp-column> | ||||||
|  |         <abp-column size-md="_6" class="text-right"> | ||||||
|  |             <abp-button id="NewBookButton" | ||||||
|  |                         text="@L["NewBook"].Value" | ||||||
|  |                         icon="plus" | ||||||
|  |                         button-type="Primary" /> | ||||||
|  |         </abp-column> | ||||||
|  |     </abp-row> | ||||||
|  | </abp-card-header> | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | Acabei de adicionar um botão **Novo livro** no canto **superior direito** da tabela: | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | 
 | ||||||
|  | Abra o `pages/books/index.js`e adicione o seguinte código logo após a configuração da tabela de dados: | ||||||
|  | 
 | ||||||
|  | ```js | ||||||
|  | var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal'); | ||||||
|  | 
 | ||||||
|  | createModal.onResult(function () { | ||||||
|  |     dataTable.ajax.reload(); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | $('#NewBookButton').click(function (e) { | ||||||
|  |     e.preventDefault(); | ||||||
|  |     createModal.open(); | ||||||
|  | }); | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | - `abp.ModalManager`é uma classe auxiliar para abrir e gerenciar modais no lado do cliente. Ele usa internamente o modal padrão do Twitter Bootstrap, mas abstrai muitos detalhes, fornecendo uma API simples. | ||||||
|  | 
 | ||||||
|  | Agora, você pode **executar o aplicativo** e adicionar novos livros usando o novo formulário modal. | ||||||
|  | 
 | ||||||
|  | ### Atualizando um livro existente | ||||||
|  | 
 | ||||||
|  | Crie uma nova página de navalha, nomeada `EditModal.cshtml`sob a `Pages/Books`pasta do `Acme.BookStore.Web`projeto: | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | 
 | ||||||
|  | #### EditModal.cshtml.cs | ||||||
|  | 
 | ||||||
|  | Abra o `EditModal.cshtml.cs`arquivo ( `EditModalModel`classe) e substitua pelo seguinte código: | ||||||
|  | 
 | ||||||
|  | ```csharp | ||||||
|  | using System; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  | using Microsoft.AspNetCore.Mvc; | ||||||
|  | 
 | ||||||
|  | namespace Acme.BookStore.Web.Pages.Books | ||||||
|  | { | ||||||
|  |     public class EditModalModel : BookStorePageModel | ||||||
|  |     { | ||||||
|  |         [HiddenInput] | ||||||
|  |         [BindProperty(SupportsGet = true)] | ||||||
|  |         public Guid Id { get; set; } | ||||||
|  | 
 | ||||||
|  |         [BindProperty] | ||||||
|  |         public CreateUpdateBookDto Book { get; set; } | ||||||
|  | 
 | ||||||
|  |         private readonly IBookAppService _bookAppService; | ||||||
|  | 
 | ||||||
|  |         public EditModalModel(IBookAppService bookAppService) | ||||||
|  |         { | ||||||
|  |             _bookAppService = bookAppService; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public async Task OnGetAsync() | ||||||
|  |         { | ||||||
|  |             var bookDto = await _bookAppService.GetAsync(Id); | ||||||
|  |             Book = ObjectMapper.Map<BookDto, CreateUpdateBookDto>(bookDto); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public async Task<IActionResult> OnPostAsync() | ||||||
|  |         { | ||||||
|  |             await _bookAppService.UpdateAsync(Id, Book); | ||||||
|  |             return NoContent(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | - `[HiddenInput]`e `[BindProperty]`são atributos padrão do ASP.NET Core MVC. Utilizado `SupportsGet`para obter o valor do ID a partir do parâmetro da string de consulta da solicitação. | ||||||
|  | - Mapeado `BookDto`(recebido de `BookAppService.GetAsync`) para `CreateUpdateBookDto`no `GetAsync`método | ||||||
|  | - O `OnPostAsync`simplesmente usa `BookAppService.UpdateAsync`para atualizar a entidade. | ||||||
|  | 
 | ||||||
|  | #### Mapeamento de BookDto para CreateUpdateBookDto | ||||||
|  | 
 | ||||||
|  | A fim de executar `BookDto`a `CreateUpdateBookDto`opor mapeamento, abrir o `BookStoreWebAutoMapperProfile.cs`no `Acme.BookStore.Web`projecto e alterá-lo como se mostra abaixo: | ||||||
|  | 
 | ||||||
|  | ```csharp | ||||||
|  | using AutoMapper; | ||||||
|  | 
 | ||||||
|  | namespace Acme.BookStore.Web | ||||||
|  | { | ||||||
|  |     public class BookStoreWebAutoMapperProfile : Profile | ||||||
|  |     { | ||||||
|  |         public BookStoreWebAutoMapperProfile() | ||||||
|  |         { | ||||||
|  |             CreateMap<BookDto, CreateUpdateBookDto>(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | - Apenas adicionado `CreateMap<BookDto, CreateUpdateBookDto>();`como a definição de mapeamento. | ||||||
|  | 
 | ||||||
|  | #### EditModal.cshtml | ||||||
|  | 
 | ||||||
|  | Substitua o `EditModal.cshtml`conteúdo pelo seguinte: | ||||||
|  | 
 | ||||||
|  | ```html | ||||||
|  | @page | ||||||
|  | @inherits Acme.BookStore.Web.Pages.BookStorePage | ||||||
|  | @using Acme.BookStore.Web.Pages.Books | ||||||
|  | @using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal | ||||||
|  | @model EditModalModel | ||||||
|  | @{ | ||||||
|  |     Layout = null; | ||||||
|  | } | ||||||
|  | <abp-dynamic-form abp-model="Book" data-ajaxForm="true" asp-page="/Books/EditModal"> | ||||||
|  |     <abp-modal> | ||||||
|  |         <abp-modal-header title="@L["Update"].Value"></abp-modal-header> | ||||||
|  |         <abp-modal-body> | ||||||
|  |             <abp-input asp-for="Id" /> | ||||||
|  |             <abp-form-content /> | ||||||
|  |         </abp-modal-body> | ||||||
|  |         <abp-modal-footer buttons="@(AbpModalButtons.Cancel|AbpModalButtons.Save)"></abp-modal-footer> | ||||||
|  |     </abp-modal> | ||||||
|  | </abp-dynamic-form> | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | Esta página é muito semelhante à `CreateModal.cshtml`exceção; | ||||||
|  | 
 | ||||||
|  | - Ele inclui um `abp-input`para a `Id`propriedade armazenar o ID do livro de edição (que é uma entrada oculta). | ||||||
|  | - Ele usa `Books/EditModal`como URL de postagem e texto de *atualização* como cabeçalho modal. | ||||||
|  | 
 | ||||||
|  | #### Adicione o menu suspenso "Ações" à tabela | ||||||
|  | 
 | ||||||
|  | Adicionaremos um botão suspenso ("Ações") para cada linha da tabela. A interface do usuário final é assim: | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | 
 | ||||||
|  | Abra a `Pages/Books/Index.cshtml`página e altere a seção da tabela como mostrado abaixo: | ||||||
|  | 
 | ||||||
|  | ```html | ||||||
|  | <abp-table striped-rows="true" id="BooksTable"> | ||||||
|  |     <thead> | ||||||
|  |         <tr> | ||||||
|  |             <th>@L["Actions"]</th> | ||||||
|  |             <th>@L["Name"]</th> | ||||||
|  |             <th>@L["Type"]</th> | ||||||
|  |             <th>@L["PublishDate"]</th> | ||||||
|  |             <th>@L["Price"]</th> | ||||||
|  |             <th>@L["CreationTime"]</th> | ||||||
|  |         </tr> | ||||||
|  |     </thead> | ||||||
|  | </abp-table> | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | - Acabei de adicionar uma nova `th`tag para as "Ações". | ||||||
|  | 
 | ||||||
|  | Abra `pages/books/index.js`e substitua o conteúdo como abaixo: | ||||||
|  | 
 | ||||||
|  | ```js | ||||||
|  | $(function () { | ||||||
|  | 
 | ||||||
|  |     var l = abp.localization.getResource('BookStore'); | ||||||
|  | 
 | ||||||
|  |     var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal'); | ||||||
|  |     var editModal = new abp.ModalManager(abp.appPath + 'Books/EditModal'); | ||||||
|  | 
 | ||||||
|  |     var dataTable = $('#BooksTable').DataTable(abp.libs.datatables.normalizeConfiguration({ | ||||||
|  |         processing: true, | ||||||
|  |         serverSide: true, | ||||||
|  |         paging: true, | ||||||
|  |         searching: false, | ||||||
|  |         autoWidth: false, | ||||||
|  |         scrollCollapse: true, | ||||||
|  |         order: [[1, "asc"]], | ||||||
|  |         ajax: abp.libs.datatables.createAjax(acme.bookStore.book.getList), | ||||||
|  |         columnDefs: [ | ||||||
|  |             { | ||||||
|  |                 rowAction: { | ||||||
|  |                     items: | ||||||
|  |                         [ | ||||||
|  |                             { | ||||||
|  |                                 text: l('Edit'), | ||||||
|  |                                 action: function (data) { | ||||||
|  |                                     editModal.open({ id: data.record.id }); | ||||||
|  |                                 } | ||||||
|  |                             } | ||||||
|  |                         ] | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|  |             { data: "name" }, | ||||||
|  |             { data: "type" }, | ||||||
|  |             { data: "publishDate" }, | ||||||
|  |             { data: "price" }, | ||||||
|  |             { data: "creationTime" } | ||||||
|  |         ] | ||||||
|  |     })); | ||||||
|  | 
 | ||||||
|  |     createModal.onResult(function () { | ||||||
|  |         dataTable.ajax.reload(); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     editModal.onResult(function () { | ||||||
|  |         dataTable.ajax.reload(); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     $('#NewBookButton').click(function (e) { | ||||||
|  |         e.preventDefault(); | ||||||
|  |         createModal.open(); | ||||||
|  |     }); | ||||||
|  | }); | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | - Utilizado `abp.localization.getResource('BookStore')`para poder usar os mesmos textos de localização definidos no lado do servidor. | ||||||
|  | - Adicionado um novo `ModalManager`nome `createModal`para abrir a caixa de diálogo criar modal. | ||||||
|  | - Adicionado um novo `ModalManager`nome `editModal`para abrir a caixa de diálogo modal de edição. | ||||||
|  | - Adicionada uma nova coluna no início da `columnDefs`seção. Esta coluna é usada para o botão suspenso "Ações". | ||||||
|  | - A ação "Novo livro" simplesmente chama `createModal.open`para abrir a caixa de diálogo Criar. | ||||||
|  | - A ação "Editar" simplesmente chama `editModal.open`para abrir a caixa de diálogo de edição. `Você pode executar o aplicativo e editar qualquer livro selecionando a ação de edição. | ||||||
|  | 
 | ||||||
|  | ### Exclusão de um livro existente | ||||||
|  | 
 | ||||||
|  | Abra o `pages/books/index.js`e adicione um novo item ao `rowAction` `items`: | ||||||
|  | 
 | ||||||
|  | ```js | ||||||
|  | { | ||||||
|  |     text: l('Delete'), | ||||||
|  |     confirmMessage: function (data) { | ||||||
|  |         return l('BookDeletionConfirmationMessage', data.record.name); | ||||||
|  |     }, | ||||||
|  |     action: function (data) { | ||||||
|  |         acme.bookStore.book | ||||||
|  |             .delete(data.record.id) | ||||||
|  |             .then(function() { | ||||||
|  |                 abp.notify.info(l('SuccessfullyDeleted')); | ||||||
|  |                 dataTable.ajax.reload(); | ||||||
|  |             }); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | - `confirmMessage`A opção é usada para fazer uma pergunta de confirmação antes de executar o `action`. | ||||||
|  | - Utilizou a `acme.bookStore.book.delete`função de proxy javascript para executar uma solicitação AJAX para excluir um livro. | ||||||
|  | - `abp.notify.info` é usado para mostrar uma notificação toastr logo após a exclusão. | ||||||
|  | 
 | ||||||
|  | O `index.js`conteúdo final é mostrado abaixo: | ||||||
|  | 
 | ||||||
|  | ```js | ||||||
|  | $(function () { | ||||||
|  | 
 | ||||||
|  |     var l = abp.localization.getResource('BookStore'); | ||||||
|  | 
 | ||||||
|  |     var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal'); | ||||||
|  |     var editModal = new abp.ModalManager(abp.appPath + 'Books/EditModal'); | ||||||
|  | 
 | ||||||
|  |     var dataTable = $('#BooksTable').DataTable(abp.libs.datatables.normalizeConfiguration({ | ||||||
|  |         processing: true, | ||||||
|  |         serverSide: true, | ||||||
|  |         paging: true, | ||||||
|  |         searching: false, | ||||||
|  |         autoWidth: false, | ||||||
|  |         scrollCollapse: true, | ||||||
|  |         order: [[1, "asc"]], | ||||||
|  |         ajax: abp.libs.datatables.createAjax(acme.bookStore.book.getList), | ||||||
|  |         columnDefs: [ | ||||||
|  |             { | ||||||
|  |                 rowAction: { | ||||||
|  |                     items: | ||||||
|  |                     [ | ||||||
|  |                         { | ||||||
|  |                             text: l('Edit'), | ||||||
|  |                             action: function (data) { | ||||||
|  |                                 editModal.open({ id: data.record.id }); | ||||||
|  |                             } | ||||||
|  |                         }, | ||||||
|  |                         { | ||||||
|  |                             text: l('Delete'), | ||||||
|  |                             confirmMessage: function (data) { | ||||||
|  |                                 return l('BookDeletionConfirmationMessage', data.record.name); | ||||||
|  |                             }, | ||||||
|  |                             action: function (data) { | ||||||
|  |                                 acme.bookStore.book | ||||||
|  |                                     .delete(data.record.id) | ||||||
|  |                                     .then(function() { | ||||||
|  |                                         abp.notify.info(l('SuccessfullyDeleted')); | ||||||
|  |                                         dataTable.ajax.reload(); | ||||||
|  |                                     }); | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                     ] | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|  |             { data: "name" }, | ||||||
|  |             { data: "type" }, | ||||||
|  |             { data: "publishDate" }, | ||||||
|  |             { data: "price" }, | ||||||
|  |             { data: "creationTime" } | ||||||
|  |         ] | ||||||
|  |     })); | ||||||
|  | 
 | ||||||
|  |     createModal.onResult(function () { | ||||||
|  |         dataTable.ajax.reload(); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     editModal.onResult(function () { | ||||||
|  |         dataTable.ajax.reload(); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     $('#NewBookButton').click(function (e) { | ||||||
|  |         e.preventDefault(); | ||||||
|  |         createModal.open(); | ||||||
|  |     }); | ||||||
|  | }); | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | Abra o `en.json`no `Acme.BookStore.Domain.Shared`projeto e adicione a seguinte linha: | ||||||
|  | 
 | ||||||
|  | ```json | ||||||
|  | "BookDeletionConfirmationMessage": "Are you sure to delete the book {0}?"  | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | Execute o aplicativo e tente excluir um livro. | ||||||
|  | 
 | ||||||
|  | ### Próxima parte | ||||||
|  | 
 | ||||||
|  | Veja a [próxima parte](https://docs.abp.io/en/abp/latest/Tutorials/AspNetCore-Mvc/Part-III) deste tutorial. | ||||||
| @ -0,0 +1,182 @@ | |||||||
|  | ## Tutorial do ASP.NET Core MVC - Parte III | ||||||
|  | 
 | ||||||
|  | ### Sobre este tutorial | ||||||
|  | 
 | ||||||
|  | Esta é a terceira parte da série de tutoriais do ASP.NET Core MVC. Veja todas as peças: | ||||||
|  | 
 | ||||||
|  | - [Parte I: Crie o projeto e uma página da lista de livros](https://docs.abp.io/en/abp/latest/Tutorials/AspNetCore-Mvc/Part-I) | ||||||
|  | - [Parte II: Criar, atualizar e excluir livros](https://docs.abp.io/en/abp/latest/Tutorials/AspNetCore-Mvc/Part-II) | ||||||
|  | - **Parte III: Testes de Integração (este tutorial)** | ||||||
|  | 
 | ||||||
|  | Você pode acessar o **código fonte** do aplicativo [no repositório GitHub](https://github.com/volosoft/abp/tree/master/samples/BookStore) . | ||||||
|  | 
 | ||||||
|  | > Você também pode assistir a [este curso em vídeo](https://amazingsolutions.teachable.com/p/lets-build-the-bookstore-application) preparado por um membro da comunidade ABP, com base neste tutorial. | ||||||
|  | 
 | ||||||
|  | ### 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. | ||||||
|  | - [Altamente](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](https://docs.abp.io/en/abp/latest/Guid-Generation) do [Guid](https://docs.abp.io/en/abp/latest/Guid-Generation) 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: 3.1 KiB | 
| After Width: | Height: | Size: 3.9 KiB | 
| After Width: | Height: | Size: 7.9 KiB | 
| After Width: | Height: | Size: 16 KiB | 
| After Width: | Height: | Size: 33 KiB | 
| After Width: | Height: | Size: 33 KiB | 
| After Width: | Height: | Size: 9.1 KiB | 
| After Width: | Height: | Size: 13 KiB | 
| After Width: | Height: | Size: 39 KiB | 
| After Width: | Height: | Size: 34 KiB | 
| After Width: | Height: | Size: 32 KiB | 
| After Width: | Height: | Size: 11 KiB | 
| After Width: | Height: | Size: 2.9 KiB | 
| After Width: | Height: | Size: 6.2 KiB | 
| After Width: | Height: | Size: 10 KiB | 
| After Width: | Height: | Size: 34 KiB | 
| After Width: | Height: | Size: 7.5 KiB | 
| After Width: | Height: | Size: 7.9 KiB | 
| After Width: | Height: | Size: 34 KiB | 
| After Width: | Height: | Size: 22 KiB | 
| After Width: | Height: | Size: 21 KiB | 
| After Width: | Height: | Size: 6.7 KiB | 
| After Width: | Height: | Size: 19 KiB | 
| After Width: | Height: | Size: 13 KiB | 
| After Width: | Height: | Size: 6.3 KiB | 
| After Width: | Height: | Size: 17 KiB | 
| After Width: | Height: | Size: 32 KiB | 
| After Width: | Height: | Size: 11 KiB | 
| After Width: | Height: | Size: 59 KiB | 
| After Width: | Height: | Size: 18 KiB | 
| After Width: | Height: | Size: 44 KiB | 
| After Width: | Height: | Size: 20 KiB | 
| After Width: | Height: | Size: 13 KiB | 
| After Width: | Height: | Size: 6.7 KiB | 
| After Width: | Height: | Size: 33 KiB | 
| After Width: | Height: | Size: 35 KiB | 
| After Width: | Height: | Size: 43 KiB | 
| After Width: | Height: | Size: 12 KiB | 
| After Width: | Height: | Size: 58 KiB | 
| After Width: | Height: | Size: 20 KiB | 
| After Width: | Height: | Size: 12 KiB | 
| After Width: | Height: | Size: 11 KiB | 
| After Width: | Height: | Size: 153 KiB | 
| After Width: | Height: | Size: 146 KiB | 
| After Width: | Height: | Size: 144 KiB | 
| After Width: | Height: | Size: 25 KiB | 
| After Width: | Height: | Size: 38 KiB | 
| After Width: | Height: | Size: 31 KiB | 
| After Width: | Height: | Size: 2.7 KiB | 
| After Width: | Height: | Size: 16 KiB | 
| After Width: | Height: | Size: 12 KiB | 
| After Width: | Height: | Size: 40 KiB | 
| After Width: | Height: | Size: 45 KiB | 
| After Width: | Height: | Size: 3.2 KiB | 
| After Width: | Height: | Size: 154 KiB | 
| After Width: | Height: | Size: 69 KiB | 
| After Width: | Height: | Size: 77 KiB | 
| After Width: | Height: | Size: 53 KiB | 
| After Width: | Height: | Size: 12 KiB | 
| After Width: | Height: | Size: 28 KiB | 
| After Width: | Height: | Size: 10 KiB | 
| After Width: | Height: | Size: 6.4 KiB | 
| After Width: | Height: | Size: 6.3 KiB | 
| After Width: | Height: | Size: 26 KiB | 
| After Width: | Height: | Size: 23 KiB | 
| After Width: | Height: | Size: 29 KiB | 
| After Width: | Height: | Size: 6.3 KiB | 
| After Width: | Height: | Size: 6.3 KiB | 
| After Width: | Height: | Size: 26 KiB | 
| After Width: | Height: | Size: 3.8 KiB | 
| After Width: | Height: | Size: 14 KiB | 
| After Width: | Height: | Size: 33 KiB | 
| After Width: | Height: | Size: 14 KiB | 
| After Width: | Height: | Size: 5.9 KiB | 
| After Width: | Height: | Size: 9.9 KiB | 
| @ -1,29 +0,0 @@ | |||||||
| /** |  | ||||||
|  * @fileoverview added by tsickle |  | ||||||
|  * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc |  | ||||||
|  */ |  | ||||||
| import { NgModule } from '@angular/core'; |  | ||||||
| import { ACCOUNT_OPTIONS, optionsFactory } from './tokens/options.token'; |  | ||||||
| export class RootAccountModule { |  | ||||||
|     /** |  | ||||||
|      * @param {?=} options |  | ||||||
|      * @return {?} |  | ||||||
|      */ |  | ||||||
|     static forRoot(options = (/** @type {?} */ ({}))) { |  | ||||||
|         return { |  | ||||||
|             ngModule: RootAccountModule, |  | ||||||
|             providers: [ |  | ||||||
|                 { provide: ACCOUNT_OPTIONS, useValue: options }, |  | ||||||
|                 { |  | ||||||
|                     provide: 'ACCOUNT_OPTIONS', |  | ||||||
|                     useFactory: optionsFactory, |  | ||||||
|                     deps: [ACCOUNT_OPTIONS], |  | ||||||
|                 }, |  | ||||||
|             ], |  | ||||||
|         }; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| RootAccountModule.decorators = [ |  | ||||||
|     { type: NgModule, args: [{},] } |  | ||||||
| ]; |  | ||||||
| //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm9vdC1hY2NvdW50Lm1vZHVsZS5qcyIsInNvdXJjZVJvb3QiOiJuZzovL0BhYnAvbmcuYWNjb3VudC8iLCJzb3VyY2VzIjpbImxpYi9yb290LWFjY291bnQubW9kdWxlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7QUFBQSxPQUFPLEVBQXVCLFFBQVEsRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUU5RCxPQUFPLEVBQUUsZUFBZSxFQUFFLGNBQWMsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBR3pFLE1BQU0sT0FBTyxpQkFBaUI7Ozs7O0lBQzVCLE1BQU0sQ0FBQyxPQUFPLENBQUMsT0FBTyxHQUFHLG1CQUFBLEVBQUUsRUFBVztRQUNwQyxPQUFPO1lBQ0wsUUFBUSxFQUFFLGlCQUFpQjtZQUMzQixTQUFTLEVBQUU7Z0JBQ1QsRUFBRSxPQUFPLEVBQUUsZUFBZSxFQUFFLFFBQVEsRUFBRSxPQUFPLEVBQUU7Z0JBQy9DO29CQUNFLE9BQU8sRUFBRSxpQkFBaUI7b0JBQzFCLFVBQVUsRUFBRSxjQUFjO29CQUMxQixJQUFJLEVBQUUsQ0FBQyxlQUFlLENBQUM7aUJBQ3hCO2FBQ0Y7U0FDRixDQUFDO0lBQ0osQ0FBQzs7O1lBZEYsUUFBUSxTQUFDLEVBQUUiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBNb2R1bGVXaXRoUHJvdmlkZXJzLCBOZ01vZHVsZSB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuaW1wb3J0IHsgT3B0aW9ucyB9IGZyb20gJy4vbW9kZWxzL29wdGlvbnMnO1xuaW1wb3J0IHsgQUNDT1VOVF9PUFRJT05TLCBvcHRpb25zRmFjdG9yeSB9IGZyb20gJy4vdG9rZW5zL29wdGlvbnMudG9rZW4nO1xuXG5ATmdNb2R1bGUoe30pXG5leHBvcnQgY2xhc3MgUm9vdEFjY291bnRNb2R1bGUge1xuICBzdGF0aWMgZm9yUm9vdChvcHRpb25zID0ge30gYXMgT3B0aW9ucyk6IE1vZHVsZVdpdGhQcm92aWRlcnMge1xuICAgIHJldHVybiB7XG4gICAgICBuZ01vZHVsZTogUm9vdEFjY291bnRNb2R1bGUsXG4gICAgICBwcm92aWRlcnM6IFtcbiAgICAgICAgeyBwcm92aWRlOiBBQ0NPVU5UX09QVElPTlMsIHVzZVZhbHVlOiBvcHRpb25zIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBwcm92aWRlOiAnQUNDT1VOVF9PUFRJT05TJyxcbiAgICAgICAgICB1c2VGYWN0b3J5OiBvcHRpb25zRmFjdG9yeSxcbiAgICAgICAgICBkZXBzOiBbQUNDT1VOVF9PUFRJT05TXSxcbiAgICAgICAgfSxcbiAgICAgIF0sXG4gICAgfTtcbiAgfVxufVxuIl19
 |  | ||||||
 Alper Ebicoglu
						Alper Ebicoglu