Browse Source

Merge branch 'dev' into docs/diaval-angular

pull/3072/head
TheDiaval 7 years ago
parent
commit
c409dcd6bb
  1. 2
      README.md
  2. 6
      common.props
  3. 2
      docs/cs/Getting-Started-Angular-Template.md
  4. 2
      docs/cs/Getting-Started-AspNetCore-Application.md
  5. 2
      docs/cs/Getting-Started-AspNetCore-MVC-Template.md
  6. 73
      docs/en/Background-Jobs-Quartz.md
  7. 2
      docs/en/Background-Jobs.md
  8. 3
      docs/en/Background-Worker.md
  9. 68
      docs/en/Background-Workers-Quartz.md
  10. 4
      docs/en/Blog-Posts/2019-08-16 v0_19_Release/Post.md
  11. 21
      docs/en/Entity-Framework-Core-Migrations.md
  12. 8
      docs/en/Getting-Started-Angular-Template.md
  13. 2
      docs/en/Getting-Started-AspNetCore-Application.md
  14. 4
      docs/en/Getting-Started-AspNetCore-MVC-Template.md
  15. 1
      docs/en/Multi-Tenancy.md
  16. 2
      docs/en/Samples/Microservice-Demo.md
  17. 6
      docs/en/Startup-Templates/Application.md
  18. 2
      docs/en/Startup-Templates/Module.md
  19. 661
      docs/en/Tutorials/Angular/Part-I.md
  20. 589
      docs/en/Tutorials/Angular/Part-II.md
  21. 180
      docs/en/Tutorials/Angular/Part-III.md
  22. BIN
      docs/en/Tutorials/Angular/images/bookstore-actions-buttons.png
  23. BIN
      docs/en/Tutorials/Angular/images/bookstore-angular-file-tree.png
  24. BIN
      docs/en/Tutorials/Angular/images/bookstore-backend-solution-v2.png
  25. BIN
      docs/en/Tutorials/Angular/images/bookstore-book-list.png
  26. BIN
      docs/en/Tutorials/Angular/images/bookstore-creating-book-list-terminal.png
  27. BIN
      docs/en/Tutorials/Angular/images/bookstore-creating-books-module-terminal.png
  28. BIN
      docs/en/Tutorials/Angular/images/bookstore-edit-modal.png
  29. BIN
      docs/en/Tutorials/Angular/images/bookstore-final-actions-dropdown.png
  30. BIN
      docs/en/Tutorials/Angular/images/bookstore-initial-book-list-page.png
  31. BIN
      docs/en/Tutorials/Angular/images/bookstore-initial-books-page-with-layout.png
  32. BIN
      docs/en/Tutorials/Angular/images/bookstore-initial-books-page.png
  33. BIN
      docs/en/Tutorials/Angular/images/bookstore-service-terminal-output.png
  34. BIN
      docs/en/Tutorials/Angular/images/bookstore-swagger-api.png
  35. BIN
      docs/en/Tutorials/Angular/images/bookstore-test-explorer.png
  36. BIN
      docs/en/Tutorials/Angular/images/bookstore-test-projects-v3.png
  37. 478
      docs/en/Tutorials/AspNetCore-Mvc/Part-I.md
  38. 434
      docs/en/Tutorials/AspNetCore-Mvc/Part-II.md
  39. 168
      docs/en/Tutorials/AspNetCore-Mvc/Part-III.md
  40. BIN
      docs/en/Tutorials/AspNetCore-Mvc/images/bookstore-book-list-2.png
  41. BIN
      docs/en/Tutorials/AspNetCore-Mvc/images/bookstore-book-list.png
  42. BIN
      docs/en/Tutorials/AspNetCore-Mvc/images/bookstore-create-template.png
  43. BIN
      docs/en/Tutorials/AspNetCore-Mvc/images/bookstore-localization-files-v2.png
  44. BIN
      docs/en/Tutorials/AspNetCore-Mvc/images/bookstore-new-book-button.png
  45. BIN
      docs/en/Tutorials/AspNetCore-Mvc/images/bookstore-pmc-add-book-migration-v2.png
  46. BIN
      docs/en/Tutorials/AspNetCore-Mvc/images/bookstore-swagger.png
  47. 6
      docs/en/Tutorials/Index.md
  48. 1117
      docs/en/Tutorials/Part-1.md
  49. 1447
      docs/en/Tutorials/Part-2.md
  50. BIN
      docs/en/Tutorials/images/bookstore-actions-buttons.png
  51. 0
      docs/en/Tutorials/images/bookstore-add-create-dialog-v2.png
  52. 0
      docs/en/Tutorials/images/bookstore-add-edit-dialog.png
  53. 0
      docs/en/Tutorials/images/bookstore-add-index-page-v2.png
  54. BIN
      docs/en/Tutorials/images/bookstore-angular-file-tree.png
  55. 0
      docs/en/Tutorials/images/bookstore-appservice-tests.png
  56. BIN
      docs/en/Tutorials/images/bookstore-book-list-2.png
  57. BIN
      docs/en/Tutorials/images/bookstore-book-list.png
  58. 0
      docs/en/Tutorials/images/bookstore-books-table-actions.png
  59. 0
      docs/en/Tutorials/images/bookstore-books-table.png
  60. 0
      docs/en/Tutorials/images/bookstore-confirmation-popup.png
  61. 0
      docs/en/Tutorials/images/bookstore-create-dialog-2.png
  62. 0
      docs/en/Tutorials/images/bookstore-create-dialog.png
  63. BIN
      docs/en/Tutorials/images/bookstore-create-project-angular.png
  64. BIN
      docs/en/Tutorials/images/bookstore-create-project-mvc.png
  65. BIN
      docs/en/Tutorials/images/bookstore-creating-book-list-terminal.png
  66. BIN
      docs/en/Tutorials/images/bookstore-creating-books-module-terminal.png
  67. BIN
      docs/en/Tutorials/images/bookstore-database-tables-ef.png
  68. BIN
      docs/en/Tutorials/images/bookstore-database-tables-mongodb.png
  69. BIN
      docs/en/Tutorials/images/bookstore-edit-button.png
  70. 0
      docs/en/Tutorials/images/bookstore-empty-new-book-modal.png
  71. BIN
      docs/en/Tutorials/images/bookstore-final-actions-dropdown.png
  72. BIN
      docs/en/Tutorials/images/bookstore-generate-state-books.png
  73. 0
      docs/en/Tutorials/images/bookstore-homepage.png
  74. 0
      docs/en/Tutorials/images/bookstore-index-js-file-v2.png
  75. BIN
      docs/en/Tutorials/images/bookstore-initial-book-list-page.png
  76. BIN
      docs/en/Tutorials/images/bookstore-initial-books-page-with-layout.png
  77. BIN
      docs/en/Tutorials/images/bookstore-localization-files-v2.png
  78. 0
      docs/en/Tutorials/images/bookstore-menu-items.png
  79. BIN
      docs/en/Tutorials/images/bookstore-migrations-applied-angular.png
  80. BIN
      docs/en/Tutorials/images/bookstore-migrations-applied-mvc.png
  81. BIN
      docs/en/Tutorials/images/bookstore-new-book-button.png
  82. 0
      docs/en/Tutorials/images/bookstore-new-book-form-v2.png
  83. 0
      docs/en/Tutorials/images/bookstore-new-book-form.png
  84. BIN
      docs/en/Tutorials/images/bookstore-new-menu-item.png
  85. BIN
      docs/en/Tutorials/images/bookstore-open-package-manager-console.png
  86. BIN
      docs/en/Tutorials/images/bookstore-pmc-add-book-migration-v2.png
  87. 0
      docs/en/Tutorials/images/bookstore-pmc-add-book-migration.png
  88. BIN
      docs/en/Tutorials/images/bookstore-service-terminal-output.png
  89. BIN
      docs/en/Tutorials/images/bookstore-solution-structure-angular.png
  90. 0
      docs/en/Tutorials/images/bookstore-solution-structure-mvc.png
  91. BIN
      docs/en/Tutorials/images/bookstore-start-project-angular.png
  92. BIN
      docs/en/Tutorials/images/bookstore-start-project-mvc.png
  93. BIN
      docs/en/Tutorials/images/bookstore-swagger-book-dto-properties.png
  94. BIN
      docs/en/Tutorials/images/bookstore-swagger.png
  95. 0
      docs/en/Tutorials/images/bookstore-test-js-proxy-getlist-network.png
  96. 0
      docs/en/Tutorials/images/bookstore-test-js-proxy-getlist.png
  97. BIN
      docs/en/Tutorials/images/bookstore-test-projects-angular.png
  98. 0
      docs/en/Tutorials/images/bookstore-test-projects-mvc.png
  99. BIN
      docs/en/Tutorials/images/bookstore-test-projects-v2.png
  100. BIN
      docs/en/Tutorials/images/bookstore-update-database-after-book-entity.png

2
README.md

@ -17,7 +17,7 @@ See the <a href="https://docs.abp.io/" target="_blank">documentation</a>.
#### Pre Requirements
- Visual Studio 2019 16.3.0+
- Visual Studio 2019 16.4.0+
#### Framework

6
common.props

@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<LangVersion>latest</LangVersion>
<Version>2.2.0</Version>
<LangVersion>latest</LangVersion>
<Version>2.3.0</Version>
<NoWarn>$(NoWarn);CS1591</NoWarn>
<PackageIconUrl>https://abp.io/assets/abp_nupkg.png</PackageIconUrl>
<PackageProjectUrl>https://abp.io</PackageProjectUrl>
@ -9,9 +9,7 @@
<RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/abpframework/abp/</RepositoryUrl>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="SourceLink.Create.CommandLine" Version="2.8.3" PrivateAssets="All" />
</ItemGroup>
</Project>

2
docs/cs/Getting-Started-Angular-Template.md

@ -26,7 +26,7 @@ abp new Acme.BookStore -u angular
Vytvořené řešení vyžaduje;
* [Visual Studio 2019 (v16.3+)](https://visualstudio.microsoft.com/vs/)
* [Visual Studio 2019 (v16.4.0+)](https://visualstudio.microsoft.com/vs/)
* [.NET Core 3.0+](https://www.microsoft.com/net/download/dotnet-core/)
* [Node v12+](https://nodejs.org)
* [Yarn v1.19+](https://yarnpkg.com/)

2
docs/cs/Getting-Started-AspNetCore-Application.md

@ -4,7 +4,7 @@ Tento tutoriál vysvětluje jak začít s ABP z ničeho s minimem závislostí.
## Tvorba nového projektu
1. Vytvořte novou AspNet Core Web aplikaci ve Visual Studio 2019 (16.3.0+):
1. Vytvořte novou AspNet Core Web aplikaci ve Visual Studio 2019 (16.4.0+):
![](images/create-new-aspnet-core-application-v2.png)

2
docs/cs/Getting-Started-AspNetCore-MVC-Template.md

@ -26,7 +26,7 @@ Příkaz `new` vytvoří **vrstvenou MVC aplikaci** s **Entity Framework Core**
Vytvořené řešení vyžaduje;
* [Visual Studio 2019 (v16.3+)](https://visualstudio.microsoft.com/vs/)
* [Visual Studio 2019 (v16.4.0+)](https://visualstudio.microsoft.com/vs/)
* [.NET Core 3.0+](https://www.microsoft.com/net/download/dotnet-core/)
* [Node v12+](https://nodejs.org)
* [Yarn v1.19+](https://yarnpkg.com/)

73
docs/en/Background-Jobs-Quartz.md

@ -0,0 +1,73 @@
# Quartz Background Job Manager
[Quartz](https://www.quartz-scheduler.net/) is an advanced background job manager. You can integrate Quartz with the ABP Framework to use it instead of the [default background job manager](Background-Jobs.md). In this way, you can use the same background job API for Quartz and your code will be independent of Quartz. If you like, you can directly use Quartz's API, too.
> See the [background jobs document](Background-Jobs.md) to learn how to use the background job system. This document only shows how to install and configure the Quartz integration.
## Installation
It is suggested to use the [ABP CLI](CLI.md) to install this package.
### Using the ABP CLI
Open a command line window in the folder of the project (.csproj file) and type the following command:
````bash
abp add-package Volo.Abp.BackgroundJobs.Quartz
````
### Manual Installation
If you want to manually install;
1. Add the [Volo.Abp.BackgroundJobs.Quartz](https://www.nuget.org/packages/Volo.Abp.BackgroundJobs.Quartz) NuGet package to your project:
````
Install-Package Volo.Abp.BackgroundJobs.Quartz
````
2. Add the `AbpBackgroundJobsQuartzModule` to the dependency list of your module:
````csharp
[DependsOn(
//...other dependencies
typeof(AbpBackgroundJobsQuartzModule) //Add the new module dependency
)]
public class YourModule : AbpModule
{
}
````
## Configuration
Quartz is a very configurable library,and the ABP framework provides `AbpQuartzPreOptions` for this. You can use the `PreConfigure` method in your module class to pre-configure this option. ABP will use it when initializing the Quartz module. For example:
````csharp
[DependsOn(
//...other dependencies
typeof(AbpBackgroundJobsQuartzModule) //Add the new module dependency
)]
public class YourModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
PreConfigure<AbpQuartzPreOptions>(options =>
{
options.Properties = new NameValueCollection
{
["quartz.jobStore.dataSource"] = "BackgroundJobsDemoApp",
["quartz.jobStore.type"] = "Quartz.Impl.AdoJobStore.JobStoreTX, Quartz",
["quartz.jobStore.tablePrefix"] = "QRTZ_",
["quartz.serializer.type"] = "json",
["quartz.dataSource.BackgroundJobsDemoApp.connectionString"] = configuration.GetConnectionString("Quartz"),
["quartz.dataSource.BackgroundJobsDemoApp.provider"] = "SqlServer",
["quartz.jobStore.driverDelegateType"] = "Quartz.Impl.AdoJobStore.SqlServerDelegate, Quartz",
};
});
}
}
````
Quartz stores job and scheduling information **in memory by default**. In the example, we use the pre-configuration of [options pattern](Options.md) to change it to the database. For more configuration of Quartz, please refer to the Quartz's [documentation](https://www.quartz-scheduler.net/documentation/quartz-3.x/tutorial/index.html).

2
docs/en/Background-Jobs.md

@ -11,7 +11,7 @@ Background jobs are **persistent** that means they will be **re-tried** and **ex
## Abstraction Package
ABP provides an **abstraction** module and **several implementations** for background jobs. It has a built-in/default implementation as well as Hangfire and RabbitMQ integrations.
ABP provides an **abstraction** module and **several implementations** for background jobs. It has a built-in/default implementation as well as Hangfire, RabbitMQ and Quartz integrations.
`Volo.Abp.BackgroundJobs.Abstractions` nuget package provides needed services to create background jobs and queue background job items. If your module only depend on this package, it can be independent from the actual implementation/integration.

3
docs/en/Background-Worker.md

@ -0,0 +1,3 @@
# Background Workers
TODO

68
docs/en/Background-Workers-Quartz.md

@ -0,0 +1,68 @@
# Quartz Background Worker Manager
[Quartz](https://www.quartz-scheduler.net/) is an advanced background worker manager. You can integrate Quartz with the ABP Framework to use it instead of the [default background worker manager](Background-Worker.md). ABP simply integrates quartz.
## Installation
It is suggested to use the [ABP CLI](CLI.md) to install this package.
### Using the ABP CLI
Open a command line window in the folder of the project (.csproj file) and type the following command:
````bash
abp add-package Volo.Abp.BackgroundWorkers.Quartz
````
### Manual Installation
If you want to manually install;
1. Add the [Volo.Abp.BackgroundWorkers.Quartz](https://www.nuget.org/packages/Volo.Abp.BackgroundWorkers.Quartz) NuGet package to your project:
````
Install-Package Volo.Abp.BackgroundWorkers.Quartz
````
2. Add the `AbpBackgroundWorkersQuartzModule` to the dependency list of your module:
````csharp
[DependsOn(
//...other dependencies
typeof(AbpBackgroundWorkersQuartzModule) //Add the new module dependency
)]
public class YourModule : AbpModule
{
}
````
### Configuration
See [Configuration](Background-Jobs-Quartz.md#Configuration).
### Create a Background Worker
A background work is a class that derives from the `QuartzBackgroundWorkerBase` base class. for example. A simple worker class is shown below:
```` csharp
public class MyLogWorker : QuartzBackgroundWorkerBase
{
public MyLogWorker()
{
JobDetail = JobBuilder.Create<MyLogWorker>().Build();
Trigger = TriggerBuilder.Create().StartNow().Build();
}
public override Task Execute(IJobExecutionContext context)
{
Logger.LogInformation("Executed MyLogWorker..!");
return Task.CompletedTask;
}
}
````
We simply implemented the Execute method to write a log. The background worker is a **singleton by default**. If you want, you can also implement a [dependency interface](Dependency-Injection.md#DependencyInterfaces) to register it as another life cycle.
### More
Please see Quartz's [documentation](https://www.quartz-scheduler.net/documentation/index.html) for more information.

4
docs/en/Blog-Posts/2019-08-16 v0_19_Release/Post.md

@ -12,11 +12,11 @@ Finally, ABP has a **SPA UI** option with the latest [Angular](https://angular.i
* Created Angular UI packages for the modules like account, identity and tenant-management.
* Created a minimal startup template that authenticates using IdentityServer and uses the ASP.NET Core backend. This template uses the packages mentioned above.
* Worked on the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) and the [download page](https://abp.io/get-started) to be able to generate projects with the new UI option.
* Created a [tutorial](https://docs.abp.io/en/abp/latest/Tutorials/Angular/Part-I) to jump start with the new UI option.
* Created a [tutorial](https://docs.abp.io/en/abp/latest/Tutorials/Part-1?UI=NG) to jump start with the new UI option.
We've created the template, document and infrastructure based on the latest Angular tools and trends:
* Uses [NgBootstrap](https://ng-bootstrap.github.io/) and [PrimeNG](https://www.primefaces.org/primeng/) as the UI component libraries. You can use your favorite library, no problem, but pre-built modules work with these libraries.
* Uses [NgBootstrap](https://ng-bootstrap.github.io/) as the UI component library. You can use your favorite library, but pre-built modules work with these libraries.
* Uses [NGXS](https://ngxs.gitbook.io/ngxs/) as the state management library.
Angular was the first SPA UI option, but it is not the last. After v1.0 release, we will start to work on a second UI option. Not decided yet, but candidates are Blazor, React and Vue.js. Waiting your feedback. You can thumb up using the following issues:

21
docs/en/Entity-Framework-Core-Migrations.md

@ -899,26 +899,7 @@ public class BookStoreDbMigratorModule : AbpModule
We had a reference to the `Acme.BookStore.EntityFrameworkCore.DbMigrationsForSecondDb` project from the `Acme.BookStore.Web` project, but hadn't added module dependency since we hadn't created it before. But, now we have it and we need to add `typeof(BookStoreEntityFrameworkCoreSecondDbMigrationsModule)` to the dependency list of the `BookStoreWebModule` class.
#### BookStoreDbMigrationService
You need one final touch to the `BookStoreDbMigrationService` inside the `Acme.BookStore.Domain` project. It is currently designed to work with a single `IBookStoreDbSchemaMigrator` implementation, but now we have two.
It injects `IBookStoreDbSchemaMigrator`. Replace it with an `IEnumerable<IBookStoreDbSchemaMigrator>` injection ([Dependency Injection System](Dependency-Injection.md) allows to inject multiple implementations of an interface just like that).
Now, you have **a collection of schema migrators** instead of a single one. Find the lines like:
````csharp
await _dbSchemaMigrators.MigrateAsync();
````
change them to:
````csharp
foreach (var migrator in _dbSchemaMigrators)
{
await migrator.MigrateAsync();
}
````
#### Run the Database Migrator!
You can run the `.DbMigrator` application to migrate & seed the databases. To test, you can delete both databases and run the `.DbMigrator` application again to see if it creates both of the databases.

8
docs/en/Getting-Started-Angular-Template.md

@ -1,6 +1,6 @@
## Getting Started With the Angular Application Template
This tutorial explain how to create a new Angular application using the startup template, configure and run it.
This tutorial explains how to create a new Angular application using the startup template, configure and run it.
### Creating a New Project
@ -26,7 +26,7 @@ abp new Acme.BookStore -u angular
The created solution requires;
* [Visual Studio 2019 (v16.3+)](https://visualstudio.microsoft.com/vs/)
* [Visual Studio 2019 (v16.4+)](https://visualstudio.microsoft.com/vs/)
* [.NET Core 3.0+](https://www.microsoft.com/net/download/dotnet-core/)
* [Node v12+](https://nodejs.org)
* [Yarn v1.19+](https://yarnpkg.com/)
@ -103,7 +103,7 @@ Most of the application APIs require authentication & authorization. If you want
#### Run the Angular Application (Client Side)
Go to the `angular` folder, open a command line terminal, type the `yarn` command (we suggest to the [yarn](https://yarnpkg.com) package manager while npm install will also work in most cases):
Go to the `angular` folder, open a command line terminal, type the `yarn` command (we suggest the [yarn](https://yarnpkg.com) package manager while `npm install` will also work in most cases)
````bash
yarn
@ -123,4 +123,4 @@ The startup template includes the **identity management** and **tenant managemen
### What's Next?
* [Application development tutorial](Tutorials/Angular/Part-I.md)
* [Application development tutorial](Tutorials/Part-1)

2
docs/en/Getting-Started-AspNetCore-Application.md

@ -4,7 +4,7 @@ This tutorial explains how to start ABP from scratch with minimal dependencies.
## Create A New Project
1. Create a new AspNet Core Web Application from Visual Studio 2019 (16.3.0+):
1. Create a new AspNet Core Web Application from Visual Studio 2019 (16.4.0+):
![](images/create-new-aspnet-core-application-v2.png)

4
docs/en/Getting-Started-AspNetCore-MVC-Template.md

@ -26,7 +26,7 @@ abp new Acme.BookStore
The created solution requires;
* [Visual Studio 2019 (v16.3+)](https://visualstudio.microsoft.com/vs/)
* [Visual Studio 2019 (v16.4+)](https://visualstudio.microsoft.com/vs/)
* [.NET Core 3.0+](https://www.microsoft.com/net/download/dotnet-core/)
* [Node v12+](https://nodejs.org)
* [Yarn v1.19+](https://yarnpkg.com/)
@ -101,4 +101,4 @@ The startup template includes the **identity management** and **tenant managemen
### What's Next?
* [Application development tutorial](Tutorials/AspNetCore-Mvc/Part-I.md)
* [Application development tutorial](Tutorials/Part-1.md)

1
docs/en/Multi-Tenancy.md

@ -302,6 +302,7 @@ TODO:...
Volo.Abp.AspNetCore.MultiTenancy package adds following tenant resolvers to determine current tenant from current web request (ordered by priority). These resolvers are added and work out of the box:
* **CurrentUserTenantResolveContributor**: Gets the tenant id from claims of the current user, if the current user has logged in. **This should always be stay as the first contributor for security**.
* **QueryStringTenantResolver**: Tries to find current tenant id from query string parameter. Parameter name is "__tenant" by default.
* **RouteTenantResolver**: Tries to find current tenant id from route (URL path). Variable name is "__tenant" by default. So, if you defined a route with this variable, then it can determine the current tenant from the route.
* **HeaderTenantResolver**: Tries to find current tenant id from HTTP header. Header name is "__tenant" by default.

2
docs/en/Samples/Microservice-Demo.md

@ -419,7 +419,7 @@ A screenshot from the Products page:
#### Using Microservices
Publc web site application uses the Blogging and Product microservices for all operations, over the Public Web Site Gateway (PublicWebSiteGateway.Host).
Public web site application uses the Blogging and Product microservices for all operations, over the Public Web Site Gateway (PublicWebSiteGateway.Host).
##### Remote End Point

6
docs/en/Startup-Templates/Application.md

@ -5,8 +5,8 @@
This template provides a layered application structure based on the [Domain Driven Design](../Domain-Driven-Design.md) (DDD) practices. This document explains the solution structure and projects in details. If you want to start quickly, follow the guides below:
* See [Getting Started With the ASP.NET Core MVC Template](../Getting-Started-AspNetCore-MVC-Template.md) to create a new solution and run it for this template (uses MVC as the UI framework and Entity Framework Core as the database provider).
* See the [ASP.NET Core MVC Application Development Tutorial](../Tutorials/AspNetCore-Mvc/Part-I.md) to learn how to develop applications using this template (uses MVC as the UI framework and Entity Framework Core as the database provider).
* See the [Angular Application Development Tutorial](../Tutorials/Angular/Part-I.md) to learn how to develop applications using this template (uses Angular as the UI framework and MongoDB as the database provider).
* See the [ASP.NET Core MVC Application Development Tutorial](../Tutorials/Part-1.md?UI=MVC) to learn how to develop applications using this template (uses MVC as the UI framework and Entity Framework Core as the database provider).
* See the [Angular Application Development Tutorial](../Tutorials/Part-1.md?UI=NG) to learn how to develop applications using this template (uses Angular as the UI framework and MongoDB as the database provider).
## How to Start With?
@ -270,4 +270,4 @@ The files under the `angular/src/environments` folder has the essential configur
## What's Next?
- See [Getting Started With the ASP.NET Core MVC Template](../Getting-Started-AspNetCore-MVC-Template.md) to create a new solution and run it for this template.
- See the [ASP.NET Core MVC Tutorial](../Tutorials/AspNetCore-Mvc/Part-I.md) to learn how to develop applications using this template.
- See the [ASP.NET Core MVC Tutorial](../Tutorials/Part-1.md?UI=MVC) to learn how to develop applications using this template.

2
docs/en/Startup-Templates/Module.md

@ -133,7 +133,7 @@ For the `.Web.Unified` application, there is a single database, named `YourProje
##### How to Run?
Set it as the startup project, run `Update-Database` command for the EF Core from Package Manager Console and run your application. Default username is `admin` and password is `1q2w3E*`.
Set `host/YourProjectName.Web.Unified` as the startup project, run `Update-Database` command for the EF Core from Package Manager Console and run your application. Default username is `admin` and password is `1q2w3E*`.
#### Separated Deployment & Databases Scenario

661
docs/en/Tutorials/Angular/Part-I.md

@ -1,659 +1,6 @@
## Angular Tutorial - Part I
# Tutorials
### About this Tutorial
## Application Development
In this tutorial series, you will build an application that is used to manage a list of books & their authors. **Angular** will be used as the UI framework and **MongoDB** will be used as the database provider.
This is the first part of the Angular tutorial series. See all parts:
- **Part I: Create the project and a book list page (this tutorial)**
- [Part II: Create, Update and Delete books](Part-II.md)
- [Part III: Integration Tests](Part-III.md)
You can access to the **source code** of the application from the [GitHub repository](https://github.com/abpframework/abp/tree/dev/samples/BookStore-Angular-MongoDb).
### Creating the Project
Create a new project named `Acme.BookStore` by selecting the Angular as the UI framework and MongoDB as the database provider, create the database and run the application by following the [Getting Started document](../../Getting-Started-Angular-Template.md).
### Solution Structure (Backend)
This is how the layered solution structure looks after it's created:
![bookstore-backend-solution](images/bookstore-backend-solution-v2.png)
> You can see the [Application template document](../../Startup-Templates/Application.md) to understand the solution structure in details. However, you will understand the basics with this tutorial.
### Create the Book Entity
Domain layer in the startup template is separated into two projects:
- `Acme.BookStore.Domain` contains your [entities](../../Entities.md), [domain services](../../Domain-Services.md) and other core domain objects.
- `Acme.BookStore.Domain.Shared` contains constants, enums or other domain related objects those can be shared with clients.
Define [entities](../../Entities.md) in the **domain layer** (`Acme.BookStore.Domain` project) of the solution. The main entity of the application is the `Book`. Create a class, named `Book`, in the `Acme.BookStore.Domain` project as shown below:
```C#
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; }
}
}
```
- ABP has two fundamental base classes for entities: `AggregateRoot` and `Entity`. **Aggregate Root** is one of the **Domain Driven Design (DDD)** concepts. See [entity document](../../Entities.md) for details and best practices.
- `Book` entity inherits `AuditedAggregateRoot` which adds some auditing properties (`CreationTime`, `CreatorId`, `LastModificationTime`... etc.) on top of the `AggregateRoot` class.
- `Guid` is the **primary key type** of the `Book` entity.
#### BookType Enum
Define the `BookType` enum in the `Acme.BookStore.Domain.Shared` project:
```C#
namespace Acme.BookStore
{
public enum BookType
{
Undefined,
Adventure,
Biography,
Dystopia,
Fantastic,
Horror,
Science,
ScienceFiction,
Poetry
}
}
```
#### Add Book Entity to Your DbContext
Add a `IMongoCollection` property to the `BookStoreMongoDbContext` inside the `Acme.BookStore.MongoDB` project:
```csharp
public class BookStoreMongoDbContext : AbpMongoDbContext
{
public IMongoCollection<Book> Books => Collection<Book>();
...
}
```
#### Add Seed (Sample) Data
This section is optional, but it would be good to have an initial data in the database in the first run. ABP provides a [data seed system](../../Data-Seeding.md). Create a class deriving from the `IDataSeedContributor` in the `.Domain` project:
```csharp
using System;
using System.Threading.Tasks;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;
namespace Acme.BookStore
{
public class BookStoreDataSeederContributor
: IDataSeedContributor, ITransientDependency
{
private readonly IRepository<Book, Guid> _bookRepository;
public BookStoreDataSeederContributor(IRepository<Book, Guid> bookRepository)
{
_bookRepository = bookRepository;
}
public async Task SeedAsync(DataSeedContext context)
{
if (await _bookRepository.GetCountAsync() > 0)
{
return;
}
await _bookRepository.InsertAsync(
new Book
{
Name = "1984",
Type = BookType.Dystopia,
PublishDate = new DateTime(1949, 6, 8),
Price = 19.84f
}
);
await _bookRepository.InsertAsync(
new Book
{
Name = "The Hitchhiker's Guide to the Galaxy",
Type = BookType.ScienceFiction,
PublishDate = new DateTime(1995, 9, 27),
Price = 42.0f
}
);
}
}
}
```
`BookStoreDataSeederContributor` simply inserts two books into database if there is no book added before. ABP automatically discovers and executes this class when you seed the database by running the `Acme.BookStore.DbMigrator` project.
### Create the Application Service
The next step is to create an [application service](../../Application-Services.md) to manage (create, list, update, delete...) the books. Application layer in the startup template is separated into two projects:
- `Acme.BookStore.Application.Contracts` mainly contains your DTOs and application service interfaces.
- `Acme.BookStore.Application` contains the implementations of your application services.
#### BookDto
Create a DTO class named `BookDto` into the `Acme.BookStore.Application.Contracts` project:
```C#
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; }
}
}
```
- **DTO** classes are used to **transfer data** between the _presentation layer_ and the _application layer_. See the [Data Transfer Objects document](../../Data-Transfer-Objects.md) for more details.
- `BookDto` is used to transfer book data to the presentation layer in order to show the book information on the UI.
- `BookDto` is derived from the `AuditedEntityDto<Guid>` which has audit properties just like the `Book` class defined above.
It will be needed to convert `Book` entities to `BookDto` objects while returning books to the presentation layer. [AutoMapper](https://automapper.org) library can automate this conversion when you define the proper mapping. Startup template comes with AutoMapper configured, so you can just define the mapping in the `BookStoreApplicationAutoMapperProfile` class in the `Acme.BookStore.Application` project:
```csharp
using AutoMapper;
namespace Acme.BookStore
{
public class BookStoreApplicationAutoMapperProfile : Profile
{
public BookStoreApplicationAutoMapperProfile()
{
CreateMap<Book, BookDto>();
}
}
}
```
#### CreateUpdateBookDto
Create a DTO class named `CreateUpdateBookDto` into the `Acme.BookStore.Application.Contracts` project:
```c#
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; }
}
}
```
- This DTO class is used to get book information from the user interface while creating or updating a book.
- It defines data annotation attributes (like `[Required]`) to define validations for the properties. DTOs are [automatically validated](../../Validation.md) by the ABP framework.
Next, add a mapping in `BookStoreApplicationAutoMapperProfile` from the `CreateUpdateBookDto` object to the `Book` entity:
```csharp
CreateMap<CreateUpdateBookDto, Book>();
```
#### IBookAppService
Define an interface named `IBookAppService` in the `Acme.BookStore.Application.Contracts` project:
```C#
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
{
}
}
```
- Defining interfaces for application services is <u>not required</u> by the framework. However, it's suggested as a best practice.
- `ICrudAppService` defines common **CRUD** methods: `GetAsync`, `GetListAsync`, `CreateAsync`, `UpdateAsync` and `DeleteAsync`. It's not required to extend it. Instead, you could inherit from the empty `IApplicationService` interface and define your own methods manually.
- There are some variations of the `ICrudAppService` where you can use separated DTOs for each method.
#### BookAppService
Implement the `IBookAppService` as named `BookAppService` in the `Acme.BookStore.Application` project:
```C#
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` is derived from `CrudAppService<...>` which implements all the CRUD methods defined above.
- `BookAppService` injects `IRepository<Book, Guid>` which is the default repository for the `Book` entity. ABP automatically creates default repositories for each aggregate root (or entity). See the [repository document](../../Repositories.md).
- `BookAppService` uses `IObjectMapper` to convert `Book` objects to `BookDto` objects and `CreateUpdateBookDto` objects to `Book` objects. The Startup template uses the [AutoMapper](http://automapper.org/) library as the object mapping provider. You defined the mappings before, so it will work as expected.
### Auto API Controllers
You normally create **Controllers** to expose application services as **HTTP API** endpoints. Thus allowing browser or 3rd-party clients to call them via AJAX. ABP can [**automagically**](../../AspNetCore/Auto-API-Controllers.md) configures your application services as MVC API Controllers by convention.
#### Swagger UI
The startup template is configured to run the [swagger UI](https://swagger.io/tools/swagger-ui/) using the [Swashbuckle.AspNetCore](https://github.com/domaindrivendev/Swashbuckle.AspNetCore) library. Run the `Acme.BookStore.HttpApi.Host` application and enter `https://localhost:XXXX/swagger/` (replace XXXX by your own port) as URL on your browser.
You will see some built-in service endpoints as well as the `Book` service and its REST-style endpoints:
![bookstore-swagger](images/bookstore-swagger-api.png)
Swagger has a nice UI to test APIs. You can try to execute the `[GET] /api/app/book` API to get a list of books.
### Create the Books Page
In this tutorial;
- [Angular CLI](https://angular.io/cli) will be used to create modules, components and services
- [NGXS](https://ngxs.gitbook.io/ngxs/) will be used as the state management library
- [Ng Bootstrap](https://ng-bootstrap.github.io/#/home) will be used as the UI component library.
- [Visual Studio Code](https://code.visualstudio.com/) will be used as the code editor (you can use your favorite editor).
#### Install NPM Packages
Open a terminal window and go to `angular` folder and then run `yarn` command for installing NPM packages:
```
yarn
```
#### BooksModule
Run the following command line to create a new module, named `BooksModule`:
```bash
yarn ng generate module books --route books --module app.module
```
![creating-books-module.terminal](images/bookstore-creating-books-module-terminal.png)
Run `yarn start`, wait Angular to run the application and open `http://localhost:4200/books` on a browser:
![initial-books-page](images/bookstore-initial-books-page.png)
#### Routing
Open the `app-routing.module.ts` and replace `books` as shown below:
```js
import { ApplicationLayoutComponent } from '@abp/ng.theme.basic';
//...
{
path: 'books',
component: ApplicationLayoutComponent,
loadChildren: () => import('./books/books.module').then(m => m.BooksModule),
data: {
routes: {
name: 'Books',
} as ABP.Route,
},
},
```
`ApplicationLayoutComponent` configuration sets the application layout to the new page. If you would like to see your route on the navigation bar (main menu) you must also add the `data` object with `name` property in your route.
![initial-books-page](images/bookstore-initial-books-page-with-layout.png)
#### Book List Component
First, replace the `books.component.html` to the following line to place the router-outlet:
```html
<router-outlet></router-outlet>
```
Then run the command below on the terminal in the root folder to generate a new component, named book-list:
```bash
yarn ng generate component books/book-list
```
![creating-books-list-terminal](images/bookstore-creating-book-list-terminal.png)
Import the `SharedModule` to the `BooksModule` to reuse some components and services defined in:
```js
import { SharedModule } from '../shared/shared.module';
@NgModule({
//...
imports: [
//...
SharedModule,
],
})
export class BooksModule {}
```
Then, update the `routes` in the `books-routing.module.ts` to add the new book-list component:
```js
import { BookListComponent } from './book-list/book-list.component';
const routes: Routes = [
{
path: '',
component: BooksComponent,
children: [{ path: '', component: BookListComponent }],
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class BooksRoutingModule {}
```
![initial-book-list-page](images/bookstore-initial-book-list-page.png)
#### Create BooksState
Run the following command in the terminal to create a new state, named `BooksState`:
```shell
yarn ng generate ngxs-schematic:state books
```
This command creates several new files and edits `app.modules.ts` to import the `NgxsModule` with the new state:
```js
// app.module.ts
import { BooksState } from './store/states/books.state';
@NgModule({
imports: [
//...
NgxsModule.forRoot([BooksState]),
],
//...
})
export class AppModule {}
```
#### Get Books Data from Backend
First, create data types to map data returning from the backend (you can check swagger UI or your backend API to know the data format).
Modify the `books.ts` as shown below:
```js
export namespace Books {
export interface State {
books: Response;
}
export interface Response {
items: Book[];
totalCount: number;
}
export interface Book {
name: string;
type: BookType;
publishDate: string;
price: number;
lastModificationTime: string;
lastModifierId: string;
creationTime: string;
creatorId: string;
id: string;
}
export enum BookType {
Undefined,
Adventure,
Biography,
Dystopia,
Fantastic,
Horror,
Science,
ScienceFiction,
Poetry,
}
}
```
Added `Book` interface that represents a book object and `BookType` enum represents a book category.
#### BooksService
Now, create a new service, named `BooksService` to perform HTTP calls to the server:
```bash
yarn ng generate service books/shared/books
```
![service-terminal-output](images/bookstore-service-terminal-output.png)
Modify `books.service.ts` as shown below:
```js
import { Injectable } from '@angular/core';
import { RestService } from '@abp/ng.core';
import { Books } from '../../store/models';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root',
})
export class BooksService {
constructor(private restService: RestService) {}
get(): Observable<Books.Response> {
return this.restService.request<void, Books.Response>({
method: 'GET',
url: '/api/app/book'
});
}
}
```
Added the `get` method to get the list of books by performing an HTTP request to the related endpoint.
Replace `books.actions.ts` content as shown below:
```js
export class GetBooks {
static readonly type = '[Books] Get';
}
```
#### Implement the BooksState
Open the `books.state.ts` and change the file as shown below:
```js
import { State, Action, StateContext, Selector } from '@ngxs/store';
import { GetBooks } from '../actions/books.actions';
import { Books } from '../models/books';
import { BooksService } from '../../books/shared/books.service';
import { tap } from 'rxjs/operators';
@State<Books.State>({
name: 'BooksState',
defaults: { books: {} } as Books.State,
})
export class BooksState {
@Selector()
static getBooks(state: Books.State) {
return state.books.items || [];
}
constructor(private booksService: BooksService) {}
@Action(GetBooks)
get(ctx: StateContext<Books.State>) {
return this.booksService.get().pipe(
tap(booksResponse => {
ctx.patchState({
books: booksResponse,
});
}),
);
}
}
```
Added the `GetBooks` action that uses the `BookService` defined above to get the books and patch the state.
> NGXS requires to return the observable without subscribing it, as done in this sample (in the get function).
#### BookListComponent
Modify the `book-list.component.ts` as shown below:
```js
import { Component, OnInit } from '@angular/core';
import { Store, Select } from '@ngxs/store';
import { BooksState } from '../../store/states';
import { Observable } from 'rxjs';
import { Books } from '../../store/models';
import { GetBooks } from '../../store/actions';
@Component({
selector: 'app-book-list',
templateUrl: './book-list.component.html',
styleUrls: ['./book-list.component.scss'],
})
export class BookListComponent implements OnInit {
@Select(BooksState.getBooks)
books$: Observable<Books.Book[]>;
booksType = Books.BookType;
loading = false;
constructor(private store: Store) {}
ngOnInit() {
this.loading = true;
this.store.dispatch(new GetBooks()).subscribe(() => {
this.loading = false;
});
}
}
```
> See the [Dispatching Actions](https://ngxs.gitbook.io/ngxs/concepts/store#dispatching-actions) and [Select](https://ngxs.gitbook.io/ngxs/concepts/select) on the NGXS documentation for more information on these NGXS features.
Replace `book-list.component.html` content as shown below:
```html
<div id="wrapper" class="card">
<div class="card-header">
<div class="row">
<div class="col col-md-6">
<h5 class="card-title">
Books
</h5>
</div>
</div>
</div>
<div class="card-body">
<p-table [value]="books$ | async" [loading]="loading" [paginator]="true" [rows]="10">
<ng-template pTemplate="header">
<tr>
<th>Book name</th>
<th>Book type</th>
<th>Publish date</th>
<th>Price</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-data>
<tr>
<td>{%{{{ data.name }}}%}</td>
<td>{%{{{ booksType[data.type] }}}%}</td>
<td>{%{{{ data.publishDate | date }}}%}</td>
<td>{%{{{ data.price }}}%}</td>
</tr>
</ng-template>
</p-table>
</div>
</div>
```
> We've used [PrimeNG table](https://www.primefaces.org/primeng/#/table) in this component.
The resulting books page is shown below:
![bookstore-book-list](images/bookstore-book-list.png)
And this is the folder & file structure by the end of this tutorial:
<img src="images/bookstore-angular-file-tree.png" height="75%">
> This tutorial follows the [Angular Style Guide](https://angular.io/guide/styleguide#file-tree).
### Next Part
See the [next part](Part-II.md) of this tutorial.
* [With ASP.NET Core MVC / Razor Pages UI](../Part-1?UI=MVC)
* [With Angular UI](../Part-1?UI=NG)

589
docs/en/Tutorials/Angular/Part-II.md

@ -1,587 +1,6 @@
## Angular Tutorial - Part II
# Tutorials
### About this Tutorial
## Application Development
This is the second part of the Angular tutorial series. See all parts:
- [Part I: Create the project and a book list page](Part-I.md)
- **Part II: Create, Update and Delete books (this tutorial)**
- [Part III: Integration Tests](Part-III.md)
You can access to the **source code** of the application from the [GitHub repository](https://github.com/abpframework/abp/tree/dev/samples/BookStore-Angular-MongoDb).
### Creating a New Book
In this section, you will learn how to create a new modal dialog form to create a new book.
#### Type Definition
Create an interface, named `CreateUpdateBookInput` in the `books.ts` as shown below:
```js
export namespace Books {
//...
export interface CreateUpdateBookInput {
name: string;
type: BookType;
publishDate: string;
price: number;
}
}
```
`CreateUpdateBookInput` interface matches the `CreateUpdateBookDto` in the backend.
#### Service Method
Open the `books.service.ts` and add a new method, named `create` to perform an HTTP POST request to the server:
```js
create(createBookInput: Books.CreateUpdateBookInput): Observable<Books.Book> {
return this.restService.request<Books.CreateUpdateBookInput, Books.Book>({
method: 'POST',
url: '/api/app/book',
body: createBookInput
});
}
```
- `restService.request` function gets generic parameters for the types sent to and received from the server. This example sends a `CreateUpdateBookInput` object and receives a `Book` object (you can set `void` for request or return type if not used).
#### State Definitions
Add the `CreateUpdateBook` action to the `books.actions.ts` as shown below:
```js
import { Books } from '../models';
export class CreateUpdateBook {
static readonly type = '[Books] Create Update Book';
constructor(public payload: Books.CreateUpdateBookInput) {}
}
```
Open `books.state.ts` and define the `save` method that will listen to a `CreateUpdateBook` action to create a book:
```js
import { ... , CreateUpdateBook } from '../actions/books.actions';
import { ... , switchMap } from 'rxjs/operators';
//...
@Action(CreateUpdateBook)
save(ctx: StateContext<Books.State>, action: CreateUpdateBook) {
return this.booksService
.create(action.payload)
.pipe(switchMap(() => ctx.dispatch(new GetBooks())));
}
```
When the `SaveBook` action dispatched, the save method is executed. It call `create` method of the `BooksService` defined before. After the service call, `BooksState` dispatches the `GetBooks` action to get books again from the server to refresh the page.
#### Add a Modal to BookListComponent
Open the `book-list.component.html` and add the `abp-modal` to show/hide the modal to create a new book.
```html
<abp-modal [(visible)]="isModalOpen">
<ng-template #abpHeader>
<h3>New Book</h3>
</ng-template>
<ng-template #abpBody> </ng-template>
<ng-template #abpFooter>
<button type="button" class="btn btn-secondary" #abpClose>
Cancel
</button>
</ng-template>
</abp-modal>
```
`abp-modal` is a pre-built component to show modals. While you could use another approach to show a modal, `abp-modal` provides additional benefits.
Add a button, labeled `New book` to show the modal:
```html
<div class="row">
<div class="col col-md-6">
<h5 class="card-title">
Books
</h5>
</div>
<div class="text-right col col-md-6">
<button id="create-role" class="btn btn-primary" type="button" (click)="createBook()">
<i class="fa fa-plus mr-1"></i> <span>New book</span>
</button>
</div>
</div>
```
Open the `book-list.component.ts` and add `isModalOpen` variable and `createBook` method to show/hide the modal.
```js
isModalOpen = false;
//...
createBook() {
this.isModalOpen = true;
}
```
![empty-modal](images/bookstore-empty-new-book-modal.png)
#### Create a Reactive Form
> [Reactive forms](https://angular.io/guide/reactive-forms) provide a model-driven approach to handling form inputs whose values change over time.
Add a `form` variable and inject a `FormBuilder` service to the `book-list.component.ts` as shown below (remember add the import statement).
```js
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
form: FormGroup;
constructor(
//...
private fb: FormBuilder
) {}
```
> The [FormBuilder](https://angular.io/api/forms/FormBuilder) service provides convenient methods for generating controls. It reduces the amount of boilerplate needed to build complex forms.
Add the `buildForm` method to create book form.
```js
buildForm() {
this.form = this.fb.group({
name: ['', Validators.required],
type: [null, Validators.required],
publishDate: [null, Validators.required],
price: [null, Validators.required],
});
}
```
- The `group` method of `FormBuilder` (`fb`) creates a `FormGroup`.
- Added `Validators.required` static method that validates the related form element.
Modify the `createBook` method as shown below:
```js
createBook() {
this.buildForm();
this.isModalOpen = true;
}
```
#### Create the DOM Elements of the Form
Open `book-list.component.html` and add the form in the body template of the modal.
```html
<ng-template #abpBody>
<form [formGroup]="form">
<div class="form-group">
<label for="book-name">Name</label><span> * </span>
<input type="text" id="book-name" class="form-control" formControlName="name" autofocus />
</div>
<div class="form-group">
<label for="book-price">Price</label><span> * </span>
<input type="number" id="book-price" class="form-control" formControlName="price" />
</div>
<div class="form-group">
<label for="book-type">Type</label><span> * </span>
<select class="form-control" id="book-type" formControlName="type">
<option [ngValue]="null">Select a book type</option>
<option [ngValue]="booksType[type]" *ngFor="let type of bookTypeArr"> {%{{{ type }}}%}</option>
</select>
</div>
<div class="form-group">
<label>Publish date</label><span> * </span>
<input
#datepicker="ngbDatepicker"
class="form-control"
name="datepicker"
formControlName="publishDate"
ngbDatepicker
(click)="datepicker.toggle()"
/>
</div>
</form>
</ng-template>
```
- This template creates a form with Name, Price, Type and Publish date fields.
> We've used [NgBootstrap datepicker](https://ng-bootstrap.github.io/#/components/datepicker/overview) in this component.
#### Datepicker Requirements
You need to import `NgbDatepickerModule` to the `books.module.ts`:
```js
import { NgbDatepickerModule } from '@ng-bootstrap/ng-bootstrap';
@NgModule({
imports: [
// ...
NgbDatepickerModule,
],
})
export class BooksModule {}
```
Then open the `book-list.component.ts` and add `providers` as shown below:
```js
import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap';
@Component({
// ...
providers: [{ provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }],
})
export class BookListComponent implements OnInit {
// ...
```
> The `NgbDateAdapter` converts Datepicker value to `Date` type. See the [datepicker adapters](https://ng-bootstrap.github.io/#/components/datepicker/overview) for more details.
#### Create the Book Type Array
Open the `book-list.component.ts` and then create an array, named `bookTypeArr`:
```js
//...
booksType = Books.BookType;
bookTypeArr = Object.keys(Books.BookType).filter(
bookType => typeof this.booksType[bookType] === 'number'
);
```
The `bookTypeArr` contains the fields of the `BookType` enum. Resulting array is shown below:
```js
['Adventure', 'Biography', 'Dystopia', 'Fantastic' ...]
```
This array was used in the previous form template (in the `ngFor` loop).
![new-book-form](images/bookstore-new-book-form.png)
#### Saving the Book
Open the `book-list.component.html` and add an `abp-button` to save the form.
```html
<ng-template #abpFooter>
<button type="button" class="btn btn-secondary" #abpClose>
Cancel
</button>
<button class="btn btn-primary" (click)="save()">
<i class="fa fa-check mr-1"></i>
Save
</button>
</ng-template>
```
This adds a save button to the bottom area of the modal:
![bookstore-new-book-form-v2](images/bookstore-new-book-form-v2.png)
Then define a `save` method in the `BookListComponent`:
```js
//...
import { ..., CreateUpdateBook } from '../../store/actions';
//...
save() {
if (this.form.invalid) {
return;
}
this.store.dispatch(new CreateUpdateBook(this.form.value)).subscribe(() => {
this.isModalOpen = false;
this.form.reset();
});
}
```
### Updating An Existing Book
#### BooksService
Open the `books.service.ts` and then add the `getById` and `update` methods.
```js
getById(id: string): Observable<Books.Book> {
return this.restService.request<void, Books.Book>({
method: 'GET',
url: `/api/app/book/${id}`
});
}
update(updateBookInput: Books.CreateUpdateBookInput, id: string): Observable<Books.Book> {
return this.restService.request<Books.CreateUpdateBookInput, Books.Book>({
method: 'PUT',
url: `/api/app/book/${id}`,
body: updateBookInput
});
}
```
#### CreateUpdateBook Action
Open the `books.actions.ts` and add `id` parameter to the `CreateUpdateBook` action:
```js
export class CreateUpdateBook {
static readonly type = '[Books] Create Update Book';
constructor(public payload: Books.CreateUpdateBookInput, public id?: string) {}
}
```
Open `books.state.ts` and modify the `save` method as show below:
```js
@Action(CreateUpdateBook)
save(ctx: StateContext<Books.State>, action: CreateUpdateBook) {
let request;
if (action.id) {
request = this.booksService.update(action.payload, action.id);
} else {
request = this.booksService.create(action.payload);
}
return request.pipe(switchMap(() => ctx.dispatch(new GetBooks())));
}
```
#### BookListComponent
Inject `BooksService` dependency by adding it to the `book-list.component.ts` constructor and add a variable named `selectedBook`.
```js
import { BooksService } from '../shared/books.service';
//...
selectedBook = {} as Books.Book;
constructor(
//...
private booksService: BooksService
)
```
`booksService` is used to get the editing book to prepare the form. Modify the `buildForm` method to reuse the same form while editing a book.
```js
buildForm() {
this.form = this.fb.group({
name: [this.selectedBook.name || '', Validators.required],
type: this.selectedBook.type || null,
publishDate: this.selectedBook.publishDate ? new Date(this.selectedBook.publishDate) : null,
price: this.selectedBook.price || null,
});
}
```
Add the `editBook` method as shown below:
```js
editBook(id: string) {
this.booksService.getById(id).subscribe(book => {
this.selectedBook = book;
this.buildForm();
this.isModalOpen = true;
});
}
```
Added `editBook` method to get the editing book, build the form and show the modal.
Now, add the `selectedBook` definition to `createBook` method to reuse the same form while creating a new book:
```js
createBook() {
this.selectedBook = {} as Books.Book;
//...
}
```
Modify the `save` method to pass the id of the selected book as shown below:
```js
save() {
if (this.form.invalid) {
return;
}
this.store.dispatch(new CreateUpdateBook(this.form.value, this.selectedBook.id))
.subscribe(() => {
this.isModalOpen = false;
this.form.reset();
});
}
```
#### Add "Actions" Dropdown to the Table
Open the `book-list.component.html` and add modify the `p-table` as shown below:
```html
<p-table [value]="books$ | async" [loading]="loading" [paginator]="true" [rows]="10">
<ng-template pTemplate="header">
<tr>
<th>Actions</th>
<th>Book name</th>
<th>Book type</th>
<th>Publish date</th>
<th>Price</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-data>
<tr>
<td>
<div ngbDropdown class="d-inline-block">
<button
class="btn btn-primary btn-sm dropdown-toggle"
data-toggle="dropdown"
aria-haspopup="true"
ngbDropdownToggle
>
<i class="fa fa-cog mr-1"></i>Actions
</button>
<div ngbDropdownMenu>
<button ngbDropdownItem (click)="editBook(data.id)">Edit</button>
</div>
</div>
</td>
<td>{%{{{ data.name }}}%}</td>
<td>{%{{{ booksType[data.type] }}}%}</td>
<td>{%{{{ data.publishDate | date }}}%}</td>
<td>{%{{{ data.price }}}%}</td>
</tr>
</ng-template>
</p-table>
```
- Added a `th` for the "Actions" column.
- Added `button` with `ngbDropdownToggle` to open actions when clicked the button.
> We've used to [NgbDropdown](https://ng-bootstrap.github.io/#/components/dropdown/examples) for the dropdown menu of actions.
The final UI looks like:
![actions-buttons](images/bookstore-actions-buttons.png)
Update the modal header to change the title based on the current operation:
```html
<ng-template #abpHeader>
<h3>{%{{{ selectedBook.id ? 'Edit' : 'New Book' }}}%}</h3>
</ng-template>
```
![actions-buttons](images/bookstore-edit-modal.png)
### Deleting an Existing Book
#### BooksService
Open `books.service.ts` and add a `delete` method to delete a book with the `id` by performing an HTTP request to the related endpoint:
```js
delete(id: string): Observable<void> {
return this.restService.request<void, void>({
method: 'DELETE',
url: `/api/app/book/${id}`
});
}
```
#### DeleteBook Action
Add an action named `DeleteBook` to `books.actions.ts`:
```js
export class DeleteBook {
static readonly type = '[Books] Delete';
constructor(public id: string) {}
}
```
Open the `books.state.ts` and add the `delete` method that will listen to the `DeleteBook` action to delete a book:
```js
import { ... , DeleteBook } from '../actions/books.actions';
//...
@Action(DeleteBook)
delete(ctx: StateContext<Books.State>, action: DeleteBook) {
return this.booksService.delete(action.id).pipe(switchMap(() => ctx.dispatch(new GetBooks())));
}
```
- Added `DeleteBook` to the import list.
- Uses `bookService` to delete the book.
#### Add a Delete Button
Open `book-list.component.html` and modify the `ngbDropdownMenu` to add the delete button as shown below:
```html
<div ngbDropdownMenu>
...
<button ngbDropdownItem (click)="delete(data.id, data.name)">
Delete
</button>
</div>
```
The final actions dropdown UI looks like below:
![bookstore-final-actions-dropdown](images/bookstore-final-actions-dropdown.png)
#### Delete Confirmation Dialog
Open `book-list.component.ts` and inject the `ConfirmationService`.
```js
import { ConfirmationService } from '@abp/ng.theme.shared';
//...
constructor(
//...
private confirmationService: ConfirmationService
)
```
> `ConfirmationService` is a simple service provided by ABP framework that internally uses the PrimeNG.
Add a delete method to the `BookListComponent`:
```js
import { ... , DeleteBook } from '../../store/actions';
import { ... , Toaster } from '@abp/ng.theme.shared';
//...
delete(id: string, name: string) {
this.confirmationService
.error(`${name} will be deleted. Do you confirm that?`, 'Are you sure?')
.subscribe(status => {
if (status === Toaster.Status.confirm) {
this.store.dispatch(new DeleteBook(id));
}
});
}
```
The `delete` method shows a confirmation popup and subscribes for the user response. `DeleteBook` action dispatched only if user clicks to the `Yes` button. The confirmation popup looks like below:
![bookstore-confirmation-popup](images/bookstore-confirmation-popup.png)
### Next Part
See the [next part](Part-III.md) of this tutorial.
* [With ASP.NET Core MVC / Razor Pages UI](../Part-1?UI=MVC)
* [With Angular UI](../Part-1?UI=NG)

180
docs/en/Tutorials/Angular/Part-III.md

@ -1,178 +1,6 @@
## Angular Tutorial - Part III
# Tutorials
### About this Tutorial
## Application Development
This is the third part of the Angular tutorial series. See all parts:
- [Part I: Create the project and a book list page](Part-I.md)
- [Part II: Create, Update and Delete books](Part-II.md)
- **Part III: Integration Tests (this tutorial)**
This part covers the **server side** tests. You can access to the **source code** of the application from the [GitHub repository](https://github.com/abpframework/abp/tree/dev/samples/BookStore-Angular-MongoDb).
### Test Projects in the Solution
There are multiple test projects in the solution:
![bookstore-test-projects](images/bookstore-test-projects-v3.png)
Each project is used to test the related application project. Test projects use the following libraries for testing:
* [xunit](https://xunit.github.io/) as the main test framework.
* [Shoudly](http://shouldly.readthedocs.io/en/latest/) as an assertion library.
* [NSubstitute](http://nsubstitute.github.io/) as a mocking library.
### Adding Test Data
Startup template contains the `BookStoreTestDataSeedContributor` class in the `Acme.BookStore.TestBase` project that creates some data to run tests on.
Change the `BookStoreTestDataSeedContributor` class as show below:
````C#
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
}
);
}
}
}
````
* Injected `IRepository<Book, Guid>` and used it in the `SeedAsync` to create two book entities as the test data.
* Used `IGuidGenerator` service to create GUIDs. While `Guid.NewGuid()` would perfectly work for testing, `IGuidGenerator` has additional features especially important while using real databases (see the [Guid generation document](../../Guid-Generation.md) for more).
### Testing the BookAppService
Create a test class named `BookAppService_Tests` in the `Acme.BookStore.Application.Tests` project:
````C#
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` test simply uses `BookAppService.GetListAsync` method to get and check the list of users.
Add a new test that creates a valid new book:
````C#
[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");
}
````
Add a new test that tries to create an invalid book and fails:
````C#
[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"));
}
````
* Since the `Name` is empty, ABP throws an `AbpValidationException`.
Open the **Test Explorer Window** (use Test -> Windows -> Test Explorer menu if it is not visible) and **Run All** tests:
![bookstore-appservice-tests](images/bookstore-test-explorer.png)
Congratulations, green icons show that tests have been successfully passed!
* [With ASP.NET Core MVC / Razor Pages UI](../Part-1?UI=MVC)
* [With Angular UI](../Part-1?UI=NG)

BIN
docs/en/Tutorials/Angular/images/bookstore-actions-buttons.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

BIN
docs/en/Tutorials/Angular/images/bookstore-angular-file-tree.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

BIN
docs/en/Tutorials/Angular/images/bookstore-backend-solution-v2.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

BIN
docs/en/Tutorials/Angular/images/bookstore-book-list.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

BIN
docs/en/Tutorials/Angular/images/bookstore-creating-book-list-terminal.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

BIN
docs/en/Tutorials/Angular/images/bookstore-creating-books-module-terminal.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

BIN
docs/en/Tutorials/Angular/images/bookstore-edit-modal.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

BIN
docs/en/Tutorials/Angular/images/bookstore-final-actions-dropdown.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

BIN
docs/en/Tutorials/Angular/images/bookstore-initial-book-list-page.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

BIN
docs/en/Tutorials/Angular/images/bookstore-initial-books-page-with-layout.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

BIN
docs/en/Tutorials/Angular/images/bookstore-initial-books-page.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

BIN
docs/en/Tutorials/Angular/images/bookstore-service-terminal-output.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

BIN
docs/en/Tutorials/Angular/images/bookstore-swagger-api.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

BIN
docs/en/Tutorials/Angular/images/bookstore-test-explorer.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

BIN
docs/en/Tutorials/Angular/images/bookstore-test-projects-v3.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

478
docs/en/Tutorials/AspNetCore-Mvc/Part-I.md

@ -1,476 +1,6 @@
## ASP.NET Core MVC Tutorial - Part I
# Tutorials
### About this Tutorial
## Application Development
In this tutorial series, you will build an application that is used to manage a list of books & their authors. **Entity Framework Core** (EF Core) will be used as the ORM provider as it is the default database provider.
This is the first part of the ASP.NET Core MVC tutorial series. See all parts:
- **Part I: Create the project and a book list page (this tutorial)**
- [Part II: Create, Update and Delete books](Part-II.md)
- [Part III: Integration Tests](Part-III.md)
You can access to the **source code** of the application from [the GitHub repository](https://github.com/abpframework/abp/tree/master/samples/BookStore).
> You can also watch [this video course](https://amazingsolutions.teachable.com/p/lets-build-the-bookstore-application) prepared by an ABP community member, based on this tutorial.
### Creating the Project
Create a new project named `Acme.BookStore`, create the database and run the application by following the [Getting Started document](../../Getting-Started-AspNetCore-MVC-Template.md).
### Solution Structure
This is how the layered solution structure looks after it's created:
![bookstore-visual-studio-solution](images/bookstore-visual-studio-solution-v3.png)
> You can see the [Application template document](../../Startup-Templates/Application.md) to understand the solution structure in details. However, you will understand the basics with this tutorial.
### Create the Book Entity
Domain layer in the startup template is separated into two projects:
- `Acme.BookStore.Domain` contains your [entities](../../Entities.md), [domain services](../../Domain-Services.md) and other core domain objects.
- `Acme.BookStore.Domain.Shared` contains constants, enums or other domain related objects those can be shared with clients.
Define [entities](../../Entities.md) in the **domain layer** (`Acme.BookStore.Domain` project) of the solution. The main entity of the application is the `Book`. Create a class, named `Book`, in the `Acme.BookStore.Domain` project as shown below:
````C#
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; }
protected Book()
{
}
public Book(Guid id, string name, BookType type, DateTime publishDate, float price)
:base(id)
{
Name = name;
Type = type;
PublishDate = publishDate;
Price = price;
}
}
}
````
* ABP has two fundamental base classes for entities: `AggregateRoot` and `Entity`. **Aggregate Root** is one of the **Domain Driven Design (DDD)** concepts. See [entity document](../../Entities.md) for details and best practices.
* `Book` entity inherits `AuditedAggregateRoot` which adds some auditing properties (`CreationTime`, `CreatorId`, `LastModificationTime`... etc.) on top of the `AggregateRoot` class.
* `Guid` is the **primary key type** of the `Book` entity.
#### BookType Enum
Define the `BookType` enum in the `Acme.BookStore.Domain.Shared` project:
````C#
namespace Acme.BookStore
{
public enum BookType
{
Undefined,
Adventure,
Biography,
Dystopia,
Fantastic,
Horror,
Science,
ScienceFiction,
Poetry
}
}
````
#### Add Book Entity to Your DbContext
EF Core requires you to relate entities with your DbContext. The easiest way to do this is to add a `DbSet` property to the `BookStoreDbContext` class in the `Acme.BookStore.EntityFrameworkCore` project, as shown below:
````C#
public class BookStoreDbContext : AbpDbContext<BookStoreDbContext>
{
public DbSet<Book> Books { get; set; }
...
}
````
#### Configure Your Book Entity
Open `BookStoreDbContextModelCreatingExtensions.cs` file in the `Acme.BookStore.EntityFrameworkCore` project and add following code to the end of the `ConfigureBookStore` method to configure the Book entity:
````C#
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);
});
````
#### Add New Migration & Update the Database
The Startup template uses [EF Core Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/) to create and maintain the database schema. Open the **Package Manager Console (PMC)** (under the *Tools/Nuget Package Manager* menu), select the `Acme.BookStore.EntityFrameworkCore.DbMigrations` as the **default project** and execute the following command:
![bookstore-pmc-add-book-migration](images/bookstore-pmc-add-book-migration-v2.png)
This will create a new migration class inside the `Migrations` folder. Then execute the `Update-Database` command to update the database schema:
````
PM> Update-Database
````
#### Add Sample Data
`Update-Database` command created the `AppBooks` table in the database. Open your database and enter a few sample rows, so you can show them on the page:
![bookstore-books-table](images/bookstore-books-table.png)
### Create the Application Service
The next step is to create an [application service](../../Application-Services.md) to manage (create, list, update, delete...) the books. Application layer in the startup template is separated into two projects:
* `Acme.BookStore.Application.Contracts` mainly contains your DTOs and application service interfaces.
* `Acme.BookStore.Application` contains the implementations of your application services.
#### BookDto
Create a DTO class named `BookDto` into the `Acme.BookStore.Application.Contracts` project:
````C#
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; }
}
}
````
* **DTO** classes are used to **transfer data** between the *presentation layer* and the *application layer*. See the [Data Transfer Objects document](../../Data-Transfer-Objects.md) for more details.
* `BookDto` is used to transfer book data to the presentation layer in order to show the book information on the UI.
* `BookDto` is derived from the `AuditedEntityDto<Guid>` which has audit properties just like the `Book` class defined above.
It will be needed to convert `Book` entities to `BookDto` objects while returning books to the presentation layer. [AutoMapper](https://automapper.org) library can automate this conversion when you define the proper mapping. Startup template comes with AutoMapper configured, so you can just define the mapping in the `BookStoreApplicationAutoMapperProfile` class in the `Acme.BookStore.Application` project:
````csharp
using AutoMapper;
namespace Acme.BookStore
{
public class BookStoreApplicationAutoMapperProfile : Profile
{
public BookStoreApplicationAutoMapperProfile()
{
CreateMap<Book, BookDto>();
}
}
}
````
#### CreateUpdateBookDto
Create a DTO class named `CreateUpdateBookDto` into the `Acme.BookStore.Application.Contracts` project:
````c#
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; }
}
}
````
* This DTO class is used to get book information from the user interface while creating or updating a book.
* It defines data annotation attributes (like `[Required]`) to define validations for the properties. DTOs are [automatically validated](../../Validation.md) by the ABP framework.
Next, add a mapping in `BookStoreApplicationAutoMapperProfile` from the `CreateUpdateBookDto` object to the `Book` entity:
````csharp
CreateMap<CreateUpdateBookDto, Book>();
````
#### IBookAppService
Define an interface named `IBookAppService` in the `Acme.BookStore.Application.Contracts` project:
````C#
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
{
}
}
````
* Defining interfaces for application services is <u>not required</u> by the framework. However, it's suggested as a best practice.
* `ICrudAppService` defines common **CRUD** methods: `GetAsync`, `GetListAsync`, `CreateAsync`, `UpdateAsync` and `DeleteAsync`. It's not required to extend it. Instead, you could inherit from the empty `IApplicationService` interface and define your own methods manually.
* There are some variations of the `ICrudAppService` where you can use separated DTOs for each method.
#### BookAppService
Implement the `IBookAppService` as named `BookAppService` in the `Acme.BookStore.Application` project:
````C#
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` is derived from `CrudAppService<...>` which implements all the CRUD methods defined above.
* `BookAppService` injects `IRepository<Book, Guid>` which is the default repository for the `Book` entity. ABP automatically creates default repositories for each aggregate root (or entity). See the [repository document](../../Repositories.md).
* `BookAppService` uses `IObjectMapper` to convert `Book` objects to `BookDto` objects and `CreateUpdateBookDto` objects to `Book` objects. The Startup template uses the [AutoMapper](http://automapper.org/) library as the object mapping provider. You defined the mappings before, so it will work as expected.
### Auto API Controllers
You normally create **Controllers** to expose application services as **HTTP API** endpoints. Thus allowing browser or 3rd-party clients to call them via AJAX. ABP can [**automagically**](../../AspNetCore/Auto-API-Controllers.md) configures your application services as MVC API Controllers by convention.
#### Swagger UI
The startup template is configured to run the [swagger UI](https://swagger.io/tools/swagger-ui/) using the [Swashbuckle.AspNetCore](https://github.com/domaindrivendev/Swashbuckle.AspNetCore) library. Run the application and enter `https://localhost:XXXX/swagger/` (replace XXXX by your own port) as URL on your browser.
You will see some built-in service endpoints as well as the `Book` service and its REST-style endpoints:
![bookstore-swagger](images/bookstore-swagger.png)
Swagger has a nice UI to test APIs. You can try to execute the `[GET] /api/app/book` API to get a list of books.
### Dynamic JavaScript Proxies
It's common to call HTTP API endpoints via AJAX from the **JavaScript** side. You can use `$.ajax` or another tool to call the endpoints. However, ABP offers a better way.
ABP **dynamically** creates JavaScript **proxies** for all API endpoints. So, you can use any **endpoint** just like calling a **JavaScript function**.
#### Testing in the Browser Developer Console
You can easily test the JavaScript proxies using your favorite browser's **Developer Console** now. Run the application, open your browser's **developer tools** (shortcut: F12), switch to the **Console** tab, type the following code and press enter:
````js
acme.bookStore.book.getList({}).done(function (result) { console.log(result); });
````
* `acme.bookStore` is the namespace of the `BookAppService` converted to [camelCase](https://en.wikipedia.org/wiki/Camel_case).
* `book` is the conventional name for the `BookAppService` (removed AppService postfix and converted to camelCase).
* `getList` is the conventional name for the `GetListAsync` method defined in the `AsyncCrudAppService` base class (removed Async postfix and converted to camelCase).
* `{}` argument is used to send an empty object to the `GetListAsync` method which normally expects an object of type `PagedAndSortedResultRequestDto` that is used to send paging and sorting options to the server (all properties are optional, so you can send an empty object).
* `getList` function returns a `promise`. So, you can pass a callback to the `done` (or `then`) function to get the result from the server.
Running this code produces the following output:
![bookstore-test-js-proxy-getlist](images/bookstore-test-js-proxy-getlist.png)
You can see the **book list** returned from the server. You can also check the **network** tab of the developer tools to see the client to server communication:
![bookstore-test-js-proxy-getlist-network](images/bookstore-test-js-proxy-getlist-network.png)
Let's **create a new book** using the `create` function:
````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); });
````
You should see a message in the console something like that:
````
successfully created the book with id: f3f03580-c1aa-d6a9-072d-39e75c69f5c7
````
Check the `Books` table in the database to see the new book row. You can try `get`, `update` and `delete` functions yourself.
### Create the Books Page
It's time to create something visible and usable! Instead of classic MVC, we will use the new [Razor Pages UI](https://docs.microsoft.com/en-us/aspnet/core/tutorials/razor-pages/razor-pages-start) approach which is recommended by Microsoft.
Create a new `Books` folder under the `Pages` folder of the `Acme.BookStore.Web` project and add a new Razor Page named `Index.cshtml`:
![bookstore-add-index-page](images/bookstore-add-index-page-v2.png)
Open the `Index.cshtml` and change the content as shown below:
````html
@page
@using Acme.BookStore.Web.Pages.Books
@inherits Acme.BookStore.Web.Pages.BookStorePage
@model IndexModel
<h2>Books</h2>
````
* This code changes the default inheritance of the Razor View Page Model so it **inherits** from the `BookStorePage` class (instead of `PageModel`). The `BookStorePage` class which comes with the startup template and provides some shared properties/methods used by all pages.
* Ensure that the `IndexModel` (*Index.cshtml.cs)* has the `Acme.BookStore.Web.Pages.Books` namespace, or update it in the `Index.cshtml`.
#### Add Books Page to the Main Menu
Open the `BookStoreMenuContributor` class in the `Menus` folder and add the following code to the end of the `ConfigureMainMenuAsync` method:
````c#
context.Menu.AddItem(
new ApplicationMenuItem("BooksStore", l["Menu:BookStore"])
.AddItem(new ApplicationMenuItem("BooksStore.Books", l["Menu:Books"], url: "/Books"))
);
````
#### Localizing the Menu Items
Localization texts are located under the `Localization/BookStore` folder of the `Acme.BookStore.Domain.Shared` project:
![bookstore-localization-files](images/bookstore-localization-files-v2.png)
Open the `en.json` file and add localization texts for `Menu:BookStore` and `Menu:Books` keys to the end of the file:
````json
{
"culture": "en",
"texts": {
"Menu:BookStore": "Book Store",
"Menu:Books": "Books"
}
}
````
* ABP's localization system is built on [ASP.NET Core's standard localization](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/localization) system and extends it in many ways. See the [localization document](../../Localization.md) for details.
* Localization key names are arbitrary. You can set any name. We prefer to add `Menu:` prefix for menu items to distinguish from other texts. If a text is not defined in the localization file, it **fallbacks** to the localization key (ASP.NET Core's standard behavior).
Run the application and see the new menu item has been added to the top bar:
![bookstore-menu-items](images/bookstore-menu-items.png)
When you click to the Books menu item, you are redirected to the new Books page.
#### Book List
We will use the [Datatables.net](https://datatables.net/) JQuery plugin to show list of tables on the page. Datatables can completely work via AJAX, it is fast and provides a good user experience. Datatables plugin is configured in the startup template, so you can directly use it in any page without including any style or script file to your page.
##### Index.cshtml
Change the `Pages/Books/Index.cshtml` as following:
````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` [tag helper](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/tag-helpers/intro) is used to add external **scripts** to the page. It has many additional features compared to standard `script` tag. It handles **minification** and **versioning** for example. See the [bundling & minification document](../../AspNetCore/Bundling-Minification.md) for details.
* `abp-card` and `abp-table` are **tag helpers** for Twitter Bootstrap's [card component](http://getbootstrap.com/docs/4.1/components/card/). There are many tag helpers in ABP to easily use most of the [bootstrap](https://getbootstrap.com/) components. You can also use regular HTML tags instead of these tag helpers, but using tag helpers reduces HTML code and prevents errors by help of the intellisense and compile time type checking. See the [tag helpers document](../../AspNetCore/Tag-Helpers/Index.md).
* You can **localize** the column names in the localization file as you did for the menu items above.
##### Add a Script File
Create `index.js` JavaScript file under the `Pages/Books/` folder:
![bookstore-index-js-file](images/bookstore-index-js-file-v2.png)
`index.js` content is shown below:
````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` is a helper function to adapt ABP's dynamic JavaScript API proxies to Datatable's format.
* `abp.libs.datatables.normalizeConfiguration` is another helper function. There's no requirement to use it, but it simplifies the datatables configuration by providing conventional values for missing options.
* `acme.bookStore.book.getList` is the function to get list of books (you have seen it before).
* See [Datatable's documentation](https://datatables.net/manual/) for more configuration options.
The final UI is shown below:
![bookstore-book-list](images/bookstore-book-list-2.png)
### Next Part
See the [next part](Part-II.md) of this tutorial.
* [With ASP.NET Core MVC / Razor Pages UI](../Part-1?UI=MVC)
* [With Angular UI](../Part-1?UI=NG)

434
docs/en/Tutorials/AspNetCore-Mvc/Part-II.md

@ -1,432 +1,6 @@
## ASP.NET Core MVC Tutorial - Part II
# Tutorials
### About this Tutorial
## Application Development
This is the second part of the ASP.NET Core MVC tutorial series. See all parts:
* [Part I: Create the project and a book list page](Part-I.md)
* **Part II: Create, Update and Delete books (this tutorial)**
* [Part III: Integration Tests](Part-III.md)
You can access to the **source code** of the application from [the GitHub repository](https://github.com/volosoft/abp/tree/master/samples/BookStore).
> You can also watch [this video course](https://amazingsolutions.teachable.com/p/lets-build-the-bookstore-application) prepared by an ABP community member, based on this tutorial.
### Creating a New Book
In this section, you will learn how to create a new modal dialog form to create a new book. The result dialog will be like that:
![bookstore-create-dialog](images/bookstore-create-dialog-2.png)
#### Create the Modal Form
Create a new razor page, named `CreateModal.cshtml` under the `Pages/Books` folder of the `Acme.BookStore.Web` project:
![bookstore-add-create-dialog](images/bookstore-add-create-dialog-v2.png)
##### CreateModal.cshtml.cs
Open the `CreateModal.cshtml.cs` file (`CreateModalModel` class) and replace with the following code:
````C#
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();
}
}
}
````
* This class is derived from the `BookStorePageModel` instead of standard `PageModel`. `BookStorePageModel` inherits the `PageModel` and adds some common properties/methods those can be used by your page model classes.
* `[BindProperty]` attribute on the `Book` property binds post request data to this property.
* This class simply injects the `IBookAppService` in its constructor and calls the `CreateAsync` method in the `OnPostAsync` handler.
##### CreateModal.cshtml
Open the `CreateModal.cshtml` file and paste the code below:
````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>
````
* This modal uses `abp-dynamic-form` tag helper to automatically create the form from the `CreateBookViewModel` class.
* `abp-model` attribute indicates the model object, the `Book` property in this case.
* `data-ajaxForm` attribute makes the form submitting via AJAX, instead of a classic page post.
* `abp-form-content` tag helper is a placeholder to render the form controls (this is optional and needed only if you added some other content in the `abp-dynamic-form` tag, just like in this page).
#### Add the "New book" Button
Open the `Pages/Books/Index.cshtml` and change the `abp-card-header` tag as shown below:
````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>
````
Just added a **New book** button to the **top right** of the table:
![bookstore-new-book-button](images/bookstore-new-book-button.png)
Open the `pages/books/index.js` and add the following code just after the datatable configuration:
````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` is a helper class to open and manage modals in the client side. It internally uses Twitter Bootstrap's standard modal, but abstracts many details by providing a simple API.
Now, you can **run the application** and add new books using the new modal form.
### Updating An Existing Book
Create a new razor page, named `EditModal.cshtml` under the `Pages/Books` folder of the `Acme.BookStore.Web` project:
![bookstore-add-edit-dialog](images/bookstore-add-edit-dialog.png)
#### EditModal.cshtml.cs
Open the `EditModal.cshtml.cs` file (`EditModalModel` class) and replace with the following code:
````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]` and `[BindProperty]` are standard ASP.NET Core MVC attributes. Used `SupportsGet` to be able to get Id value from query string parameter of the request.
* Mapped `BookDto` (received from the `BookAppService.GetAsync`) to `CreateUpdateBookDto` in the `GetAsync` method.
* The `OnPostAsync` simply uses `BookAppService.UpdateAsync` to update the entity.
#### BookDto to CreateUpdateBookDto Mapping
In order to perform `BookDto` to `CreateUpdateBookDto` object mapping, open the `BookStoreWebAutoMapperProfile.cs` in the `Acme.BookStore.Web` project and change it as shown below:
````csharp
using AutoMapper;
namespace Acme.BookStore.Web
{
public class BookStoreWebAutoMapperProfile : Profile
{
public BookStoreWebAutoMapperProfile()
{
CreateMap<BookDto, CreateUpdateBookDto>();
}
}
}
````
* Just added `CreateMap<BookDto, CreateUpdateBookDto>();` as the mapping definition.
#### EditModal.cshtml
Replace `EditModal.cshtml` content with the following content:
````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>
````
This page is very similar to the `CreateModal.cshtml` except;
* It includes an `abp-input` for the `Id` property to store id of the editing book (which is a hidden input).
* It uses `Books/EditModal` as the post URL and *Update* text as the modal header.
#### Add "Actions" Dropdown to the Table
We will add a dropdown button ("Actions") for each row of the table. The final UI looks like this:
![bookstore-books-table-actions](images/bookstore-books-table-actions.png)
Open the `Pages/Books/Index.cshtml` page and change the table section as shown below:
````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>
````
* Just added a new `th` tag for the "Actions".
Open the `pages/books/index.js` and replace the content as below:
````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();
});
});
````
* Used `abp.localization.getResource('BookStore')` to be able to use the same localization texts defined on the server side.
* Added a new `ModalManager` named `createModal` to open the create modal dialog.
* Added a new `ModalManager` named `editModal` to open the edit modal dialog.
* Added a new column at the beginning of the `columnDefs` section. This column is used for the "Actions" dropdown button.
* "New Book" action simply calls `createModal.open` to open the create dialog.
* "Edit" action simply calls `editModal.open` to open the edit dialog.
`
You can run the application and edit any book by selecting the edit action.
### Deleting an Existing Book
Open the `pages/books/index.js` and add a new item to the `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` option is used to ask a confirmation question before executing the `action`.
* Used `acme.bookStore.book.delete` javascript proxy function to perform an AJAX request to delete a book.
* `abp.notify.info` is used to show a toastr notification just after the deletion.
The final `index.js` content is shown below:
````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();
});
});
````
Open the `en.json` in the `Acme.BookStore.Domain.Shared` project and add the following line:
````json
"BookDeletionConfirmationMessage": "Are you sure to delete the book {0}?",
"SuccessfullyDeleted": "Successfully deleted"
````
Run the application and try to delete a book.
### Next Part
See the [next part](Part-III.md) of this tutorial.
* [With ASP.NET Core MVC / Razor Pages UI](../Part-1?UI=MVC)
* [With Angular UI](../Part-1?UI=NG)

168
docs/en/Tutorials/AspNetCore-Mvc/Part-III.md

@ -1,166 +1,6 @@
## ASP.NET Core MVC Tutorial - Part III
# Tutorials
### About this Tutorial
## Application Development
This is the third part of the ASP.NET Core MVC tutorial series. See all parts:
- [Part I: Create the project and a book list page](Part-I.md)
- [Part II: Create, Update and Delete books](Part-II.md)
- **Part III: Integration Tests (this tutorial)**
You can access to the **source code** of the application from [the GitHub repository](https://github.com/volosoft/abp/tree/master/samples/BookStore).
> You can also watch [this video course](https://amazingsolutions.teachable.com/p/lets-build-the-bookstore-application) prepared by an ABP community member, based on this tutorial.
### Test Projects in the Solution
There are multiple test projects in the solution:
![bookstore-test-projects-v2](images/bookstore-test-projects-v2.png)
Each project is used to test the related application project. Test projects use the following libraries for testing:
* [xunit](https://xunit.github.io/) as the main test framework.
* [Shoudly](http://shouldly.readthedocs.io/en/latest/) as an assertion library.
* [NSubstitute](http://nsubstitute.github.io/) as a mocking library.
### Adding Test Data
Startup template contains the `BookStoreTestDataSeedContributor` class in the `Acme.BookStore.TestBase` project that creates some data to run tests on.
Change the `BookStoreTestDataSeedContributor` class as show below:
````C#
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(_guidGenerator.Create(), "Test book 1", BookType.Fantastic, new DateTime(2015, 05, 24), 21)
);
await _bookRepository.InsertAsync(
new Book(_guidGenerator.Create(), "Test book 2", BookType.Science, new DateTime(2014, 02, 11), 15)
);
}
}
}
````
* Injected `IRepository<Book, Guid>` and used it in the `SeedAsync` to create two book entities as the test data.
* Used `IGuidGenerator` service to create GUIDs. While `Guid.NewGuid()` would perfectly work for testing, `IGuidGenerator` has additional features especially important while using real databases (see the [Guid generation document](../../Guid-Generation.md) for more).
### Testing the BookAppService
Create a test class named `BookAppService_Tests` in the `Acme.BookStore.Application.Tests` project:
````C#
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` test simply uses `BookAppService.GetListAsync` method to get and check the list of users.
Add a new test that creates a valid new book:
````C#
[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");
}
````
Add a new test that tries to create an invalid book and fails:
````C#
[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"));
}
````
* Since the `Name` is empty, ABP throws an `AbpValidationException`.
Open the **Test Explorer Window** (use Test -> Windows -> Test Explorer menu if it is not visible) and **Run All** tests:
![bookstore-appservice-tests](images/bookstore-appservice-tests.png)
Congratulations, green icons show that tests have been successfully passed!
* [With ASP.NET Core MVC / Razor Pages UI](../Part-1?UI=MVC)
* [With Angular UI](../Part-1?UI=NG)

BIN
docs/en/Tutorials/AspNetCore-Mvc/images/bookstore-book-list-2.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

BIN
docs/en/Tutorials/AspNetCore-Mvc/images/bookstore-book-list.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

BIN
docs/en/Tutorials/AspNetCore-Mvc/images/bookstore-create-template.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

BIN
docs/en/Tutorials/AspNetCore-Mvc/images/bookstore-localization-files-v2.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

BIN
docs/en/Tutorials/AspNetCore-Mvc/images/bookstore-new-book-button.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

BIN
docs/en/Tutorials/AspNetCore-Mvc/images/bookstore-pmc-add-book-migration-v2.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

BIN
docs/en/Tutorials/AspNetCore-Mvc/images/bookstore-swagger.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

6
docs/en/Tutorials/Index.md

@ -1,6 +0,0 @@
# Tutorials
## Application Development
* [With ASP.NET Core MVC / Razor Pages UI](AspNetCore-Mvc/Part-I.md)
* [With Angular UI](Angular/Part-I.md)

1117
docs/en/Tutorials/Part-1.md

File diff suppressed because it is too large

1447
docs/en/Tutorials/Part-2.md

File diff suppressed because it is too large

BIN
docs/en/Tutorials/images/bookstore-actions-buttons.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

0
docs/en/Tutorials/AspNetCore-Mvc/images/bookstore-add-create-dialog-v2.png → docs/en/Tutorials/images/bookstore-add-create-dialog-v2.png

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

0
docs/en/Tutorials/AspNetCore-Mvc/images/bookstore-add-edit-dialog.png → docs/en/Tutorials/images/bookstore-add-edit-dialog.png

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

0
docs/en/Tutorials/AspNetCore-Mvc/images/bookstore-add-index-page-v2.png → docs/en/Tutorials/images/bookstore-add-index-page-v2.png

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

BIN
docs/en/Tutorials/images/bookstore-angular-file-tree.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 241 KiB

0
docs/en/Tutorials/AspNetCore-Mvc/images/bookstore-appservice-tests.png → docs/en/Tutorials/images/bookstore-appservice-tests.png

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

BIN
docs/en/Tutorials/images/bookstore-book-list-2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

BIN
docs/en/Tutorials/images/bookstore-book-list.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

0
docs/en/Tutorials/AspNetCore-Mvc/images/bookstore-books-table-actions.png → docs/en/Tutorials/images/bookstore-books-table-actions.png

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

0
docs/en/Tutorials/AspNetCore-Mvc/images/bookstore-books-table.png → docs/en/Tutorials/images/bookstore-books-table.png

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

0
docs/en/Tutorials/Angular/images/bookstore-confirmation-popup.png → docs/en/Tutorials/images/bookstore-confirmation-popup.png

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

0
docs/en/Tutorials/AspNetCore-Mvc/images/bookstore-create-dialog-2.png → docs/en/Tutorials/images/bookstore-create-dialog-2.png

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

0
docs/en/Tutorials/AspNetCore-Mvc/images/bookstore-create-dialog.png → docs/en/Tutorials/images/bookstore-create-dialog.png

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

BIN
docs/en/Tutorials/images/bookstore-create-project-angular.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
docs/en/Tutorials/images/bookstore-create-project-mvc.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

BIN
docs/en/Tutorials/images/bookstore-creating-book-list-terminal.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
docs/en/Tutorials/images/bookstore-creating-books-module-terminal.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
docs/en/Tutorials/images/bookstore-database-tables-ef.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

BIN
docs/en/Tutorials/images/bookstore-database-tables-mongodb.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

BIN
docs/en/Tutorials/images/bookstore-edit-button.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

0
docs/en/Tutorials/Angular/images/bookstore-empty-new-book-modal.png → docs/en/Tutorials/images/bookstore-empty-new-book-modal.png

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 102 KiB

BIN
docs/en/Tutorials/images/bookstore-final-actions-dropdown.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
docs/en/Tutorials/images/bookstore-generate-state-books.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

0
docs/en/Tutorials/AspNetCore-Mvc/images/bookstore-homepage.png → docs/en/Tutorials/images/bookstore-homepage.png

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

0
docs/en/Tutorials/AspNetCore-Mvc/images/bookstore-index-js-file-v2.png → docs/en/Tutorials/images/bookstore-index-js-file-v2.png

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
docs/en/Tutorials/images/bookstore-initial-book-list-page.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
docs/en/Tutorials/images/bookstore-initial-books-page-with-layout.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
docs/en/Tutorials/images/bookstore-localization-files-v2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

0
docs/en/Tutorials/AspNetCore-Mvc/images/bookstore-menu-items.png → docs/en/Tutorials/images/bookstore-menu-items.png

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

BIN
docs/en/Tutorials/images/bookstore-migrations-applied-angular.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

BIN
docs/en/Tutorials/images/bookstore-migrations-applied-mvc.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

BIN
docs/en/Tutorials/images/bookstore-new-book-button.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

0
docs/en/Tutorials/Angular/images/bookstore-new-book-form-v2.png → docs/en/Tutorials/images/bookstore-new-book-form-v2.png

Before

Width:  |  Height:  |  Size: 143 KiB

After

Width:  |  Height:  |  Size: 143 KiB

0
docs/en/Tutorials/Angular/images/bookstore-new-book-form.png → docs/en/Tutorials/images/bookstore-new-book-form.png

Before

Width:  |  Height:  |  Size: 132 KiB

After

Width:  |  Height:  |  Size: 132 KiB

BIN
docs/en/Tutorials/images/bookstore-new-menu-item.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

BIN
docs/en/Tutorials/images/bookstore-open-package-manager-console.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

BIN
docs/en/Tutorials/images/bookstore-pmc-add-book-migration-v2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

0
docs/en/Tutorials/AspNetCore-Mvc/images/bookstore-pmc-add-book-migration.png → docs/en/Tutorials/images/bookstore-pmc-add-book-migration.png

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

BIN
docs/en/Tutorials/images/bookstore-service-terminal-output.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
docs/en/Tutorials/images/bookstore-solution-structure-angular.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

0
docs/en/Tutorials/AspNetCore-Mvc/images/bookstore-visual-studio-solution-v3.png → docs/en/Tutorials/images/bookstore-solution-structure-mvc.png

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

BIN
docs/en/Tutorials/images/bookstore-start-project-angular.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
docs/en/Tutorials/images/bookstore-start-project-mvc.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

BIN
docs/en/Tutorials/images/bookstore-swagger-book-dto-properties.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

BIN
docs/en/Tutorials/images/bookstore-swagger.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 810 KiB

0
docs/en/Tutorials/AspNetCore-Mvc/images/bookstore-test-js-proxy-getlist-network.png → docs/en/Tutorials/images/bookstore-test-js-proxy-getlist-network.png

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

0
docs/en/Tutorials/AspNetCore-Mvc/images/bookstore-test-js-proxy-getlist.png → docs/en/Tutorials/images/bookstore-test-js-proxy-getlist.png

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

BIN
docs/en/Tutorials/images/bookstore-test-projects-angular.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

0
docs/en/Tutorials/AspNetCore-Mvc/images/bookstore-test-projects-v2.png → docs/en/Tutorials/images/bookstore-test-projects-mvc.png

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

BIN
docs/en/Tutorials/images/bookstore-test-projects-v2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

BIN
docs/en/Tutorials/images/bookstore-update-database-after-book-entity.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

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

Loading…
Cancel
Save