diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json
index 7224dab07e..cf70dafd7a 100644
--- a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json
+++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json
@@ -154,6 +154,7 @@
"Result": "Result",
"SeeTheDocumentForMoreInformation": "See the {0} document for more information",
"IndexPageHeroSection": "open sourceWeb Application Framework for asp.net core",
- "UiFramework": "UI Framework"
+ "UiFramework": "UI Framework",
+ "EmailAddress": "Email address"
}
}
\ No newline at end of file
diff --git a/build-all.ps1 b/build-all.ps1
index 023cbdeb4a..649abc5f8c 100644
--- a/build-all.ps1
+++ b/build-all.ps1
@@ -21,7 +21,6 @@ $solutionPaths = (
"modules/client-simulation",
"templates/module/aspnet-core",
"templates/app/aspnet-core",
- "samples/MicroserviceDemo",
"abp_io/AbpIoLocalization"
)
diff --git a/docs/cs/CLI.md b/docs/cs/CLI.md
index f3f2d21a96..cbf8006ad2 100644
--- a/docs/cs/CLI.md
+++ b/docs/cs/CLI.md
@@ -34,26 +34,32 @@ Příklad:
abp new Acme.BookStore
````
-* Acme.BookStore je tady název řešení.
+* `Acme.BookStore` je tady název řešení.
* Běžná konvence je nazvat řešení stylem *VaseSpolecnost.VasProjekt*. Nicméně můžete použít i jiné pojmenování jako *VasProjekt* (jednostupňový jmenný prostor) nebo *VaseSpolecnost.VasProjekt.VasModul* (třístupňový jmenný prostor).
#### Možnosti
-* `--template` nebo `-t`: Určuje název šablony. Výchozí šablona je `mvc`. Dostupné šablony:
- * `mvc` (výchozí): ASP.NET Core [MVC aplikační šablona](Startup-Templates/Mvc.md). Dodatečné možnosti:
- * `--database-provider` nebo `-d`: Určuje poskytovatele databáze. Vychozí poskytovatel je `ef`. Dostupní poskytovatelé:
+* `--template` nebo `-t`: Určuje název šablony. Výchozí šablona je `app`, která generuje webovou aplikaci. Dostupné šablony:
+ * `app` (výchozí): [Aplikační šablona](Startup-Templates/Application.md). Dodatečné možnosti:
+ * `--ui` nebo `-u`: Určuje UI framework. Výchozí framework je `mvc`. Dostupné frameworky:
+ * `mvc`: ASP.NET Core MVC. Pro tuto šablonu jsou dostupné dodatečné možnosti:
+ * `--tiered`: Vytvoří stupňovité řešení, kde jsou vrstvy Web a Http API fyzicky odděleny. Pokud není uvedeno, tak vytvoří vrstvené řešení, které je méně složité a vhodné pro většinu scénářů.
+ * `angular`: Angular. Pro tuto šablonu jsou dostupné dodatečné možnosti:
+ * `--separate-identity-server`: Oddělí identity server aplikaci od API host aplikace. Pokud není uvedeno, bude na straně serveru jediný koncový bod.
+ * `--database-provider` nebo `-d`: Určuje poskytovatele databáze. Výchozí poskytovatel je `ef`. Dostupní poskytovatelé:
* `ef`: Entity Framework Core.
* `mongodb`: MongoDB.
- * `--tiered`: Vytvoří stupňovité řešení, kde jsou vrstvy Web a Http API fyzicky odděleny. Pokud není uvedeno tak vytvoří vrstvené řešení, které je méně složité a vhodné pro většinu scénářů.
- * `mvc-module`: ASP.NET Core [MVC modulová šablona](Startup-Templates/Mvc-Module.md). Dodatečné možnosti:
- * `--no-ui`: Určuje, že nebude zahrnuto uživatelské rozhraní. To umožňuje vytvářet moduly pouze pro služby (a.k.a. mikroslužby - bez UI).
+ * `module`: [Šablona modulu](Startup-Templates/Module.md). Dodatečné možnosti:
+ * `--no-ui`: Určuje nezahrnutí uživatelského rozhraní. Umožňuje vytvořit moduly pouze pro služby (a.k.a. mikroslužby - bez uživatelského rozhraní).
* `--output-folder` nebo `-o`: Určuje výstupní složku. Výchozí hodnota je aktuální adresář.
+* `--version` nebo `-v`: Určuje verzi ABP & šablony. Může to být [štítek vydání](https://github.com/abpframework/abp/releases) nebo [název větve](https://github.com/abpframework/abp/branches). Pokud není uvedeno, používá nejnovější vydání. Většinou budete chtít použít nejnovější verzi.
+
### add-package
-Přidá nový balíček ABP do projektu pomocí,
+Přidá ABP balíček do projektu,
-* Přidání souvisejícícho nuget balíčku jako závislost do projektu.
+* Přidáním souvisejícícho nuget balíčku jako závislost do projektu.
* Přidáním `[DependsOn(...)]` atributu k modulové tříde v projektu (podívejte se na [dokument vývoje modulu](Module-Development-Basics.md)).
> Všimněte si, že přidaný modul může vyžadovat další konfiguraci, která je obecně uvedena v dokumentaci příslušného balíčku.
@@ -78,7 +84,7 @@ abp add-package Volo.Abp.MongoDB
### add-module
-Přidá více-balíčkový modul k řešení tím, že najde všechny balíčky modulu, vyhledá související projekty v řešení a přidá každý balíček do odpovídajícího projektu v řešení.
+Přidá [více-balíčkový aplikační modul](Modules/Index) k řešení tím, že najde všechny balíčky modulu, vyhledá související projekty v řešení a přidá každý balíček do odpovídajícího projektu v řešení.
> Modul se obecně skládá z několika balíčků (z důvodu vrstvení, různých možností poskytovatele databáze nebo jiných důvodů). Použití příkazu `add-module` dramaticky zjednodušuje přidání modulu do řešení. Každý modul však může vyžadovat další konfiguraci, která je obecně uvedena v dokumentaci příslušného modulu.
@@ -100,6 +106,7 @@ abp add-module Volo.Blogging
* `--solution` nebo `-s`: Určuje cestu k řešení (.sln). Pokud není zadáno, CLI se pokusí najít soubor .sln v aktuálním adresáři.
* `--skip-db-migrations`: Pro poskytovatele databáze EF Core automaticky přidá nový kód první migrace (`Add-Migration`) a v případě potřeby aktualizuje databázi (`Update-Database`). Tuto možnost určete k vynechání této operace.
+* `-sp` nebo `--startup-project`: Relativní cesta ke složce spouštěcího projektu. Výchozí hodnota je aktuální adresář.
### update
@@ -117,6 +124,8 @@ abp update [možnosti]
#### Možnosti
* `--include-previews` nebo `-p`: Zahrne náhledové, beta a rc balíčky při kontrole nových verzí.
+* `--npm`: Aktualizuje pouze balíčky NPM.
+* `--nuget`: Aktualizuje pouze balíčky NuGet.
### help
diff --git a/docs/cs/Contribution/Index.md b/docs/cs/Contribution/Index.md
index 3716d8dc76..fd7f65d14e 100644
--- a/docs/cs/Contribution/Index.md
+++ b/docs/cs/Contribution/Index.md
@@ -35,7 +35,7 @@ Existuje několik základních dokumentů, které je třeba přeložit než bude
* Tutoriály
* CLI
-Nový jazyk je publikován jakmile budou minimálně tyto překlady dokončeny.
+Nový jazyk je publikován jakmile jsou minimálně tyto překlady dokončeny.
### Lokalizace zdrojů
diff --git a/docs/cs/Getting-Started-AspNetCore-MVC-Template.md b/docs/cs/Getting-Started-AspNetCore-MVC-Template.md
index 953e99b9f2..b1cb3bb57e 100644
--- a/docs/cs/Getting-Started-AspNetCore-MVC-Template.md
+++ b/docs/cs/Getting-Started-AspNetCore-MVC-Template.md
@@ -37,7 +37,7 @@ Otevřete řešení ve **Visual Studio**:
Řešení má vrstvenou strukturu (založenou na [Domain Driven Design](Domain-Driven-Design.md)) a obsahuje projekty jednotkovových a integračních testů předkonfigurované pro práci s **EF Core** & **SQLite in-memory** databází.
-> Podívejte se na [dokument šablony MVC aplikace](Startup-Templates/Mvc.md) k detailnímu pochopení struktury řešení.
+> Podívejte se na [dokument šablony aplikace](Startup-Templates/Application.md) k detailnímu pochopení struktury řešení.
### Connection string databáze
diff --git a/docs/cs/Index.md b/docs/cs/Index.md
index 3a86344660..ac13bcbdb6 100644
--- a/docs/cs/Index.md
+++ b/docs/cs/Index.md
@@ -6,7 +6,7 @@ K procházení dokumentace využijte navigační nabídky vlevo.
## Stav projektu
-ABP je **novou generací** open source frameworku [ASP.NET Boilerplate](https://aspnetboilerplate.com/). V současné době je v ranné fázi a není připraven k ostrému nasazení. Na dokumentaci se stále pracuje a ještě zdaleka není dokončena.
+ABP je **novou generací** open source frameworku [ASP.NET Boilerplate](https://aspnetboilerplate.com/). V současné době je v náhledové fázi a není připraven k ostrému nasazení. Na dokumentaci se stále pracuje a ještě zdaleka není dokončena.
Pro aplikace na krátkodobé a ostré nasazení je doporučeno použít [ASP.NET Boilerplate](https://aspnetboilerplate.com/) framework, který je bohatý na funkce, vyspělý, aktivně udržovaný a aktuální.
@@ -14,7 +14,8 @@ Pro aplikace na krátkodobé a ostré nasazení je doporučeno použít [ASP.NET
Nejsnazší cestou jak začít nový projekt s ABP je užití startovací šablony:
-* [ASP.NET Core MVC Šablona](Getting-Started-AspNetCore-MVC-Template.md)
+* [ASP.NET Core MVC (Razor Pages) UI Šablona](Getting-Started-AspNetCore-MVC-Template.md)
+* [Angular UI Šablona](Getting-Started-Angular-Template.md)
Pokud chcete začít od nuly (s prázdným projektem) tak manuálně nainstalujte ABP Framework s pomocí následujících tutoriálů:
diff --git a/docs/cs/docs-nav.json b/docs/cs/docs-nav.json
index 2d6b8f7ef8..8560190492 100644
--- a/docs/cs/docs-nav.json
+++ b/docs/cs/docs-nav.json
@@ -7,8 +7,12 @@
"text": "Ze startovacích šablon",
"items": [
{
- "text": "ASP.NET Core MVC",
+ "text": "Aplikace s MVC (Razor Pages) UI",
"path": "Getting-Started-AspNetCore-MVC-Template.md"
+ },
+ {
+ "text": "Aplikace s Angular UI",
+ "path": "Getting-Started-Angular-Template.md"
}
]
},
@@ -34,8 +38,12 @@
"text": "Vývoj aplikace",
"items": [
{
- "text": "S ASP.NET Core MVC",
+ "text": "S ASP.NET Core MVC UI",
"path": "Tutorials/AspNetCore-Mvc/Part-I.md"
+ },
+ {
+ "text": "S Angular UI",
+ "path": "Tutorials/Angular/Part-I.md"
}
]
}
@@ -264,6 +272,20 @@
}
]
},
+ {
+ "text": "Startovací šablony",
+ "path": "Startup-Templates/Index.md",
+ "items": [
+ {
+ "text": "Aplikace",
+ "path": "Startup-Templates/Application.md"
+ },
+ {
+ "text": "Modul",
+ "path": "Startup-Templates/Module.md"
+ }
+ ]
+ },
{
"text": "Vzorky",
"items": [
diff --git a/docs/cs/images/MonthlyProfitWidgetFiles.png b/docs/cs/images/MonthlyProfitWidgetFiles.png
new file mode 100644
index 0000000000..e1e9d316f5
Binary files /dev/null and b/docs/cs/images/MonthlyProfitWidgetFiles.png differ
diff --git a/docs/cs/images/bookstore-swagger-ui-host.png b/docs/cs/images/bookstore-swagger-ui-host.png
new file mode 100644
index 0000000000..74ff73f7e3
Binary files /dev/null and b/docs/cs/images/bookstore-swagger-ui-host.png differ
diff --git a/docs/cs/images/bookstore-visual-studio-solution-for-spa.png b/docs/cs/images/bookstore-visual-studio-solution-for-spa.png
new file mode 100644
index 0000000000..5a5a283c7d
Binary files /dev/null and b/docs/cs/images/bookstore-visual-studio-solution-for-spa.png differ
diff --git a/docs/cs/images/dashboard1.png b/docs/cs/images/dashboard1.png
new file mode 100644
index 0000000000..4102bbd4c1
Binary files /dev/null and b/docs/cs/images/dashboard1.png differ
diff --git a/docs/en/Application-Services.md b/docs/en/Application-Services.md
index de9fc1f0df..bed14370f5 100644
--- a/docs/en/Application-Services.md
+++ b/docs/en/Application-Services.md
@@ -146,7 +146,7 @@ public interface IBookAppService : IApplicationService
`BookDto` is a simple [DTO](Data-Transfer-Objects.md) class defined as below:
````csharp
-[AutoMapFrom(typeof(Book))] //Defines the mapping
+[AbpAutoMapFrom(typeof(Book))] //Defines the mapping
public class BookDto
{
public Guid Id { get; set; }
@@ -159,7 +159,7 @@ public class BookDto
}
````
-* `BookDto` defines `[AutoMapFrom(typeof(Book))]` attribute to create the object mapping from `Book` to `BookDto`.
+* `BookDto` defines `[AbpAutoMapFrom(typeof(Book))]` attribute to create the object mapping from `Book` to `BookDto`.
Then you can implement the `GetAsync` method as shown below:
@@ -248,7 +248,7 @@ public interface IAsyncCrudAppService<
DTO classes used in this example are `BookDto` and `CreateUpdateBookDto`:
````csharp
-[AutoMapFrom(typeof(Book))]
+[AbpAutoMapFrom(typeof(Book))]
public class BookDto : AuditedEntityDto
{
public string Name { get; set; }
@@ -258,7 +258,7 @@ public class BookDto : AuditedEntityDto
public float Price { get; set; }
}
-[AutoMapTo(typeof(Book))]
+[AbpAutoMapTo(typeof(Book))]
public class CreateUpdateBookDto
{
[Required]
diff --git a/docs/en/Best-Practices/Index.md b/docs/en/Best-Practices/Index.md
index 93d6409909..1a48c6b46b 100644
--- a/docs/en/Best-Practices/Index.md
+++ b/docs/en/Best-Practices/Index.md
@@ -23,5 +23,5 @@ Also, this guide is mostly usable for general **application development**.
* [Data Transfer Objects](Data-Transfer-Objects.md)
* Data Access
* [Entity Framework Core Integration](Entity-Framework-Core-Integration.md)
- * [MongoDB Integration](MongoDB-Integration.md)
+ * [MongoDB Integration](MongoDB-Integration.md)
diff --git a/docs/en/Best-Practices/PostgreSQL-Integration.md b/docs/en/Best-Practices/PostgreSQL-Integration.md
new file mode 100644
index 0000000000..93c178d07c
--- /dev/null
+++ b/docs/en/Best-Practices/PostgreSQL-Integration.md
@@ -0,0 +1,35 @@
+## Entity Framework Core PostgreSQL Integration
+
+> See [Entity Framework Core Integration document](../Entity-Framework-Core.md) for the basics of the EF Core integration.
+
+### EntityFrameworkCore Project Update
+
+- In `Acme.BookStore.EntityFrameworkCore` project replace package `Volo.Abp.EntityFrameworkCore.SqlServer` with `Volo.Abp.EntityFrameworkCore.PostgreSql`
+- Update to use PostgreSQL in `BookStoreEntityFrameworkCoreModule`.
+- Replace the `AbpEntityFrameworkCoreSqlServerModule` with the `AbpEntityFrameworkCorePostgreSqlModule`
+- Replace the `options.UseSqlServer()` with the `options.UsePostgreSql()`
+- In other projects update the PostgreSQL connection string in necessary `appsettings.json` files.
+
+#### Delete Existing Migrations
+
+Delete all existing migration files (including `DbContextModelSnapshot`)
+
+
+
+#### Regenerate Initial Migration & Update the Database
+
+Set the correct startup project (usually a web project),
+Open the **Package Manager Console** (Tools -> Nuget Package Manager -> Package Manager Console), select the `Acme.BookStore.EntityFrameworkCore.DbMigrations` as the **Default project** and execute the following command:
+
+Run `Add-Migration` command.
+````
+PM> Add-Migration Initial
+````
+
+Then execute the `Update-Database` command to update the database schema:
+
+````
+PM> Update-Database
+````
+
+
diff --git a/docs/en/Best-Practices/images/postgresql-delete-initial-migrations.png b/docs/en/Best-Practices/images/postgresql-delete-initial-migrations.png
new file mode 100644
index 0000000000..39d9e6dedf
Binary files /dev/null and b/docs/en/Best-Practices/images/postgresql-delete-initial-migrations.png differ
diff --git a/docs/en/Best-Practices/images/postgresql-update-database.png b/docs/en/Best-Practices/images/postgresql-update-database.png
new file mode 100644
index 0000000000..02b61ef6a3
Binary files /dev/null and b/docs/en/Best-Practices/images/postgresql-update-database.png differ
diff --git a/docs/en/Blog-Posts/2019-08-16 v0_19_Release/Post.md b/docs/en/Blog-Posts/2019-08-16 v0_19_Release/Post.md
new file mode 100644
index 0000000000..eec70c5566
--- /dev/null
+++ b/docs/en/Blog-Posts/2019-08-16 v0_19_Release/Post.md
@@ -0,0 +1,46 @@
+# ABP v0.19 Release With New Angular UI
+
+ABP v0.19 has been released with [90 issues](https://github.com/abpframework/abp/milestone/17?closed=1) resolved and [650+ commits](https://github.com/abpframework/abp/compare/0.18.1...0.19.0) pushed.
+
+## New Features
+
+### Angular UI
+
+Finally, ABP has a **SPA UI** option with the latest [Angular](https://angular.io/) framework. Angular integration was not simply creating a startup template.
+
+* Created a base infrastructure to handle ABP's modularity, theming and some other features. This infrastructure has been deployed as [NPM packages](https://github.com/abpframework/abp/tree/dev/npm/ng-packs/packages).
+* 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.
+
+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 [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:
+
+* [Blazor](https://github.com/abpframework/abp/issues/394)
+* [Vue.js](https://github.com/abpframework/abp/issues/1168)
+* [React](https://github.com/abpframework/abp/issues/1638)
+
+### Widget System
+
+[Widget system](https://docs.abp.io/en/abp/latest/AspNetCore/Widgets) allows to **define and reuse** widgets for ASP.NET Core MVC applications. Widgets may have their own script and style resources and dependencies to 3rd-party libraries which are managed by the ABP framework.
+
+### Others
+
+We've solved many bugs and worked on existing features based on the community feedback. See the [v0.19 milestone](https://github.com/abpframework/abp/milestone/17?closed=1) for all the closed issues.
+
+## Road Map
+
+We had decided to wait for **ASP.NET Core 3.0** final release. Microsoft has announced to released it at [.NET Conf](https://www.dotnetconf.net/), between 23-25 September.
+
+We have planned to finalize our work and move to ASP.NET Core 3.0 (with preview or RC) before its release. Once Microsoft releases it, we will immediately start to upgrade and test with the final release.
+
+So, you can expect ABP **v1.0** to be released in the **first half of the October**. We are very excited and working hard on it.
+
+You can follow the progress from [the GitHub milestones](https://github.com/abpframework/abp/milestones).
+
+We will not add major features until v1.0.
\ No newline at end of file
diff --git a/docs/en/CLI.md b/docs/en/CLI.md
index f931d3688f..ced5b5817f 100644
--- a/docs/en/CLI.md
+++ b/docs/en/CLI.md
@@ -123,6 +123,8 @@ abp update [options]
#### Options
* `--include-previews` or `-p`: Includes preview, beta and rc packages while checking the latest versions.
+* `--npm`: Only updates NPM packages.
+* `--nuget`: Only updates NuGet packages.
### help
diff --git a/docs/en/Dapper.md b/docs/en/Dapper.md
new file mode 100644
index 0000000000..d8b17838eb
--- /dev/null
+++ b/docs/en/Dapper.md
@@ -0,0 +1,61 @@
+# Dapper Integration
+
+Because Dapper's idea is that the sql statement takes precedence, and mainly provides some extension methods for the `IDbConnection` interface.
+
+Abp does not encapsulate too many functions for Dapper. Abp Dapper provides a `DapperRepository` base class based on Abp EntityFrameworkCore, which provides the `IDbConnection` and `IDbTransaction` properties required by Dapper.
+
+These two properties can work well with [Unit-Of-Work](Unit-Of-Work.md).
+
+## Installation
+
+Please install and configure EF Core according to [EF Core's integrated documentation](Entity-Framework-Core.md).
+
+`Volo.Abp.Dapper` is the main nuget package for the Dapper integration. Install it to your project (for a layered application, to your data/infrastructure layer):
+
+```shell
+Install-Package Volo.Abp.Dapper
+```
+
+Then add `AbpDapperModule` module dependency (`DependsOn` attribute) to your [module](Module-Development-Basics.md):
+
+````C#
+using Volo.Abp.Dapper;
+using Volo.Abp.Modularity;
+
+namespace MyCompany.MyProject
+{
+ [DependsOn(typeof(AbpDapperModule))]
+ public class MyModule : AbpModule
+ {
+ //...
+ }
+}
+````
+
+## Implement Dapper Repository
+
+The following code implements the `Person` repository, which requires EF Core's `DbContext` (MyAppDbContext). You can inject `PersonDapperRepository` to call its methods.
+
+`DbConnection` and `DbTransaction` are from the `DapperRepository` base class.
+
+```C#
+public class PersonDapperRepository : DapperRepository, ITransientDependency
+{
+ public PersonDapperRepository(IDbContextProvider dbContextProvider)
+ : base(dbContextProvider)
+ {
+ }
+
+ public virtual async Task> GetAllPersonNames()
+ {
+ return (await DbConnection.QueryAsync("select Name from People", transaction: DbTransaction))
+ .ToList();
+ }
+
+ public virtual async Task UpdatePersonNames(string name)
+ {
+ return await DbConnection.ExecuteAsync("update People set Name = @NewName", new { NewName = name },
+ DbTransaction);
+ }
+}
+```
diff --git a/docs/en/Index.md b/docs/en/Index.md
index d911bf66e2..b40efffb9d 100644
--- a/docs/en/Index.md
+++ b/docs/en/Index.md
@@ -6,7 +6,7 @@ Explore the left navigation menu to deep dive in the documentation.
## Project Status
-ABP is the **next generation** of the open source [ASP.NET Boilerplate](https://aspnetboilerplate.com/) framework. It's currently in early preview stage and not ready to use in production. The documentation is still in progress and it is far from complete.
+ABP is the **next generation** of the open source [ASP.NET Boilerplate](https://aspnetboilerplate.com/) framework. It's currently in preview stage and not ready to use in production. The documentation is still in progress and it is far from complete.
For short-term and production level applications, it's suggested to use [ASP.NET Boilerplate](https://aspnetboilerplate.com/) framework which has rich feature set, mature, actively maintained and up-to-date.
@@ -14,7 +14,8 @@ For short-term and production level applications, it's suggested to use [ASP.NET
Easiest way to start a new project with ABP is to use the startup templates:
-* [ASP.NET Core MVC Template](Getting-Started-AspNetCore-MVC-Template.md)
+* [ASP.NET Core MVC (Razor Pages) UI Template](Getting-Started-AspNetCore-MVC-Template.md)
+* [Angular UI Template](Getting-Started-Angular-Template.md)
If you want to start from scratch (with an empty project) then manually install the ABP Framework and use the following tutorials:
diff --git a/docs/en/Startup-Templates/Application.md b/docs/en/Startup-Templates/Application.md
index 83950ddd0e..1fbf1be8e2 100644
--- a/docs/en/Startup-Templates/Application.md
+++ b/docs/en/Startup-Templates/Application.md
@@ -5,7 +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 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 [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).
## How to Start With?
diff --git a/docs/en/Tutorials/Angular/Part-I.md b/docs/en/Tutorials/Angular/Part-I.md
index 3a7baf1de4..88b5c8539e 100644
--- a/docs/en/Tutorials/Angular/Part-I.md
+++ b/docs/en/Tutorials/Angular/Part-I.md
@@ -8,6 +8,7 @@ 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).
@@ -19,7 +20,7 @@ Create a new project named `Acme.BookStore` by selecting the Angular as the UI f
This is how the layered solution structure looks after it's created:
-
+
> 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.
@@ -341,13 +342,13 @@ Run `yarn start`, wait Angular to run the application and open `http://localhost
Open the `app-routing.module.ts` and replace `books` as shown below:
-```typescript
-import { LayoutApplicationComponent } from '@abp/ng.theme.basic';-
+```js
+import { ApplicationLayoutComponent } from '@abp/ng.theme.basic';-
//...
{
path: 'books',
- component: LayoutApplicationComponent,
+ component: ApplicationLayoutComponent,
loadChildren: () => import('./books/books.module').then(m => m.BooksModule),
data: {
routes: {
@@ -357,7 +358,7 @@ import { LayoutApplicationComponent } from '@abp/ng.theme.basic';-
},
```
-`LayoutApplicationComponent` 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.
+`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.

@@ -379,7 +380,7 @@ yarn ng generate component books/book-list
Import the `SharedModule` to the `BooksModule` to reuse some components and services defined in:
-```typescript
+```js
import { SharedModule } from '../shared/shared.module';
@NgModule({
@@ -394,7 +395,7 @@ export class BooksModule {}
Then, update the `routes` in the `books-routing.module.ts` to add the new book-list component:
-```typescript
+```js
import { BookListComponent } from './book-list/book-list.component';
const routes: Routes = [
@@ -424,7 +425,7 @@ 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:
-```typescript
+```js
// app.module.ts
import { BooksState } from './store/states/books.state';
@@ -445,19 +446,20 @@ First, create data types to map data returning from the backend (you can check s
Modify the `books.ts` as shown below:
-```typescript
-import { ABP } from '@abp/ng.core';
-
+```js
export namespace Books {
export interface State {
books: Response;
}
- export type Response = ABP.PagedResponse;
+ export interface Response {
+ items: Book[];
+ totalCount: number;
+ }
export interface Book {
name: string;
- type: Type;
+ type: BookType;
publishDate: string;
price: number;
lastModificationTime: string;
@@ -493,11 +495,11 @@ yarn ng generate service books/shared/books

-Modify `book.service.ts` as shown below:
+Modify `books.service.ts` as shown below:
-```typescript
+```js
import { Injectable } from '@angular/core';
-import { RestService, Rest } from '@abp/ng.core';
+import { RestService } from '@abp/ng.core';
import { Books } from '../../store/models';
import { Observable } from 'rxjs';
@@ -505,15 +507,13 @@ import { Observable } from 'rxjs';
providedIn: 'root',
})
export class BooksService {
- constructor(private rest: RestService) {}
+ constructor(private restService: RestService) {}
get(): Observable {
- const request: Rest.Request = {
+ return this.restService.request({
method: 'GET',
- url: '/api/app/book',
- };
-
- return this.rest.request(request);
+ url: '/api/app/book'
+ });
}
}
```
@@ -522,7 +522,7 @@ Added the `get` method to get the list of books by performing an HTTP request to
Replace `books.actions.ts` content as shown below:
-```typescript
+```js
export class GetBooks {
static readonly type = '[Books] Get';
}
@@ -532,7 +532,7 @@ export class GetBooks {
Open the `books.state.ts` and change the file as shown below:
-```typescript
+```js
import { State, Action, StateContext, Selector } from '@ngxs/store';
import { GetBooks } from '../actions/books.actions';
import { Books } from '../models/books';
@@ -545,18 +545,18 @@ import { tap } from 'rxjs/operators';
})
export class BooksState {
@Selector()
- static getBooks({ books }: Books.State) {
- return books.items || [];
+ static getBooks(state: Books.State) {
+ return state.books.items || [];
}
constructor(private booksService: BooksService) {}
@Action(GetBooks)
- get({ patchState }: StateContext) {
+ get(ctx: StateContext) {
return this.booksService.get().pipe(
- tap(books => {
- patchState({
- books,
+ tap(booksResponse => {
+ ctx.patchState({
+ books: booksResponse,
});
}),
);
@@ -572,7 +572,7 @@ Added the `GetBooks` action that uses the `BookService` defined above to get the
Modify the `book-list.component.ts` as shown below:
-```typescript
+```js
import { Component, OnInit } from '@angular/core';
import { Store, Select } from '@ngxs/store';
import { BooksState } from '../../store/states';
@@ -597,10 +597,9 @@ export class BookListComponent implements OnInit {
ngOnInit() {
this.loading = true;
- this.store.dispatch(new GetBooks())
- .subscribe(() => {
- this.loading = false
- };
+ this.store.dispatch(new GetBooks()).subscribe(() => {
+ this.loading = false;
+ });
}
}
```
diff --git a/docs/en/Tutorials/Angular/Part-II.md b/docs/en/Tutorials/Angular/Part-II.md
index 48a36ae2c9..ed6afcb947 100644
--- a/docs/en/Tutorials/Angular/Part-II.md
+++ b/docs/en/Tutorials/Angular/Part-II.md
@@ -6,6 +6,7 @@ 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).
@@ -17,7 +18,7 @@ In this section, you will learn how to create a new modal dialog form to create
Create an interface, named `CreateUpdateBookInput` in the `books.ts` as shown below:
-```typescript
+```js
export namespace Books {
//...
export interface CreateUpdateBookInput {
@@ -35,23 +36,23 @@ export namespace Books {
Open the `books.service.ts` and add a new method, named `create` to perform an HTTP POST request to the server:
-```typescript
-create(body: Books.CreateUpdateBookInput): Observable {
- const request: Rest.Request = {
+```js
+create(createBookInput: Books.CreateUpdateBookInput): Observable {
+ return this.restService.request({
method: 'POST',
url: '/api/app/book',
- body,
- };
-
- return this.rest.request(request);
+ 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 `books.actions.ts` as shown below:
+Add the `CreateUpdateBook` action to the `books.actions.ts` as shown below:
-```typescript
+```js
import { Books } from '../models';
export class CreateUpdateBook {
@@ -60,24 +61,25 @@ export class CreateUpdateBook {
}
```
-
-
Open `books.state.ts` and define the `save` method that will listen to a `CreateUpdateBook` action to create a book:
-```typescript
- @Action(CreateUpdateBook)
- save({ dispatch }: StateContext, { payload }: CreateUpdateBook) {
- return this.booksService
- .create(payload)
- .pipe(switchMap(() => dispatch(new GetBooks())));
- }
+```js
+import { ... , CreateUpdateBook } from '../actions/books.actions';
+import { ... , switchMap } from 'rxjs/operators';
+//...
+@Action(CreateUpdateBook)
+save(ctx: StateContext, 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 book form.
+Open the `book-list.component.html` and add the `abp-modal` to show/hide the modal to create a new book.
```html
@@ -97,7 +99,7 @@ Open the `book-list.component.html` and add the `abp-modal` to show/hide the boo
`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.
+Add a button, labeled `New book` to show the modal:
```html
@@ -107,21 +109,21 @@ Add a button, labeled `New book` to show the modal.
-
```
-Open the `book-list.component.ts` and add `isModalOpen` variable and `onAdd` method to show/hide the modal.
+Open the `book-list.component.ts` and add `isModalOpen` variable and `createBook` method to show/hide the modal.
-```typescript
+```js
isModalOpen = false;
//...
-onAdd() {
+createBook() {
this.isModalOpen = true;
}
```
@@ -134,7 +136,7 @@ onAdd() {
Add a `form` variable and inject a `FormBuilder` service to the `book-list.component.ts` as shown below (remember add the import statement).
-```typescript
+```js
import { FormGroup, FormBuilder } from '@angular/forms';
form: FormGroup;
@@ -149,7 +151,7 @@ constructor(
Add the `buildForm` method to create book form.
-```typescript
+```js
buildForm() {
this.form = this.fb.group({
name: ['', Validators.required],
@@ -160,19 +162,19 @@ buildForm() {
}
```
-- The `group` method of `FormBuilder` creates a `FormGroup`.
-- Added `Validators.required` static method that validation of form element.
+- The `group` method of `FormBuilder` (`fb`) creates a `FormGroup`.
+- Added `Validators.required` static method that validates the related form element.
-Modify the `onAdd` method as shown below:
+Modify the `createBook` method as shown below:
-```typescript
-onAdd() {
+```js
+createBook() {
this.buildForm();
this.isModalOpen = true;
}
```
-### Create the DOM Elements of the Form
+#### Create the DOM Elements of the Form
Open `book-list.component.html` and add the form in the body template of the modal.
@@ -212,30 +214,34 @@ Open `book-list.component.html` and add the form in the body template of the mod
```
-TODO: Add a short description.
+- 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.
-Open the `book-list.component.ts` and then add the `bookTypes`.
+Open the `book-list.component.ts` and then create an array, named `bookTypeArr`:
-```typescript
+```js
//...
form: FormGroup;
-bookTypeArr = Object.keys(Books.BookType).filter(bookType => typeof this.booksType[bookType] === 'number');
+bookTypeArr = Object.keys(Books.BookType).filter(
+ bookType => typeof this.booksType[bookType] === 'number'
+);
```
-The `bookTypes` variable added to generate array from `BookType` enum. The `bookTypes` equals like this:
+The `bookTypeArr` contains the fields of the `BookType` enum. Resulting array is shown below:
```js
['Adventure', 'Biography', 'Dystopia', 'Fantastic' ...]
```
-### Add the Datepicker Requirements
+This array was used in the previous form template (in the `ngFor` loop).
+
+#### Datepicker Requirements
-Import `NgbDatepickerModule` to the `books.module.ts`.
+You need to import `NgbDatepickerModule` to the `books.module.ts`:
-```typescript
+```js
import { NgbDatepickerModule } from '@ng-bootstrap/ng-bootstrap';
@NgModule({
@@ -247,27 +253,46 @@ import { NgbDatepickerModule } from '@ng-bootstrap/ng-bootstrap';
export class BooksModule {}
```
-Open the `book-list.component.html` and then add `providers` as shown below:
+Then open the `book-list.component.ts` and add `providers` as shown below:
+
+```js
+import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap';
-```typescript
@Component({
// ...
- styleUrls: ['./book-list.component.scss'],
providers: [{ provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }],
})
export class BookListComponent implements OnInit {
// ...
```
-> The `NgbDateAdapter` convert Datepicker value type to `Date` type. See the [datepicker adapters](https://ng-bootstrap.github.io/#/components/datepicker/overview) for more details.
+> The `NgbDateAdapter` converts Datepicker value to `Date` type. See the [datepicker adapters](https://ng-bootstrap.github.io/#/components/datepicker/overview) for more details.

-### Create a New Book
+#### Saving the Book
+
+Open the `book-list.component.html` and add an `abp-button` to save the form.
+
+```html
+
+
+ Cancel
+
+
+
+ Save
+
+
+```
+
+This adds a save button to the bottom area of the modal:
+
+
-Add the `save` method to `BookListComponent`
+Then define a `save` method in the `BookListComponent`:
-```typescript
+```js
save() {
if (this.form.invalid) {
return;
@@ -280,72 +305,75 @@ save() {
}
```
-TODO: description ??
-
-Then, open the `book-list.component.html` and add the `abp-button` for the run `save` method.
-
-```html
-
-
- Cancel
-
- Save
-
-```
-
-Now, You can add a new book.
-
-
+### Updating An Existing Book
-### Add HTTP GET and PUT Methods
+#### BooksService
-TODO: Description
+Open the `books.service.ts` and then add the `getById` and `update` methods.
-Open the `book.service.ts` and then add the `getById` and `update` methods.
-
-```typescript
+```js
getById(id: string): Observable {
- const request: Rest.Request = {
+ return this.restService.request({
method: 'GET',
- url: `/api/app/book/${id}`,
- };
-
- return this.rest.request(request);
+ url: `/api/app/book/${id}`
+ });
}
-update(body: Books.CreateUpdateBookInput, id: string): Observable {
- const request: Rest.Request = {
+update(updateBookInput: Books.CreateUpdateBookInput, id: string): Observable {
+ return this.restService.request({
method: 'PUT',
url: `/api/app/book/${id}`,
- body,
- };
+ body: updateBookInput
+ });
+}
+```
- return this.rest.request(request);
+#### CreateUpdateBook Action
+
+Open the `books.actins.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) {}
}
```
-- Added the `getById` method to get the editing book by performing an HTTP request to the related endpoint.
-- Added the `update` method to update a book with the `id` by performing an HTTP request to the related endpoint.
+Open `books.state.ts` and modify the `save` method as show below:
-### Update a Book
+```js
+@Action(CreateUpdateBook)
+save(ctx: StateContext, 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())));
+}
+```
-Inject `BooksService` dependency by adding it to the `book-list.component.ts` constructor and add variable named `selectedBook`.
+#### BookListComponent
-```typescript
+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
)
```
-- Added `booksService` to get the detail of selected book by `id` before creating the form
-- Added `selectedBook` variable to reuse detail of selected book.
-
-Modify the `buildForm` method to reuse the same form while editing a book.
+`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.
-```typescript
+```js
buildForm() {
this.form = this.fb.group({
name: [this.selectedBook.name || '', Validators.required],
@@ -356,10 +384,10 @@ buildForm() {
}
```
-Add the `onEdit` method as shown below:
+Add the `editBook` method as shown below:
-```typescript
- onEdit(id: string) {
+```js
+ editBook(id: string) {
this.booksService.getById(id).subscribe(book => {
this.selectedBook = book;
this.buildForm();
@@ -368,28 +396,32 @@ Add the `onEdit` method as shown below:
}
```
-- Added `onEdit` method to get selected book detail, build form and then show the modal.
+Added `editBook` method to get the editing book, build the form and show the modal.
-Add the `selectedBook` definition to `onAdd` method for reuse same form while adding a new book.
+Now, add the `selectedBook` definition to `createBook` method to reuse the same form while creating a new book:
-```typescript
- onAdd() {
- this.selectedBook = {} as Books.Item;
+```js
+ createBook() {
+ this.selectedBook = {} as Books.Book;
//...
}
```
-Add the `this.selectedBook.id` to `CreateUpdateBook` action in `save` method.
+Modify the `save` method to pass the id of the selected book as shown below:
-```typescript
- save() {
- //...
- this.store.dispatch(new CreateUpdateBook(this.form.value, this.selectedBook.id))
- //...
+```js
+save() {
+ if (this.form.invalid) {
+ return;
}
-```
-Added the `this.selectedBook.id` property to reuse the `CreateUpdateBook` action.
+ this.store.dispatch(new CreateUpdateBook(this.form.value, this.selectedBook.id))
+ .subscribe(() => {
+ this.isModalOpen = false;
+ this.form.reset();
+ });
+}
+```
#### Add "Actions" Dropdown to the Table
@@ -419,7 +451,7 @@ Open the `book-list.component.html` and add modify the `p-table` as shown belo
Actions
- Edit
+ Edit
@@ -430,99 +462,70 @@ Open the `book-list.component.html` and add modify the `p-table` as shown belo
->
```
-Actions button added to each row of the 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 Actions buttons looks like this:
+The final UI looks like:

-Update the modal header for reuse the same modal.
+Update the modal header to change the title based on the current operation:
```html
-
{{ (selectedBook.id ? 'Edit' : 'New Book') }}
+
{{ selectedBook.id ? 'Edit' : 'New Book' }}
```

-TODO: header ??
+### Deleting an Existing Book
-Open the `books.actins.ts` and add `id` parameter.
+#### BooksService
-```typescript
-export class CreateUpdateBook {
- static readonly type = '[Books] Create Update Book';
- constructor(public payload: Books.CreateUpdateBookInput, public id?: string) {}
-}
-```
-
-Added `id` parameter to reuse the `BooksSave` while updating and creating a book.
-
-Open `books.state.ts` and then modify the `save` method as show below:
-
-```typescript
-@Action(CreateUpdateBook)
-save({ dispatch }: StateContext, { payload, id }: CreateUpdateBook) {
- let request;
-
- if (id) {
- request = this.booksService.update(payload, id);
- } else {
- request = this.booksService.create(payload);
- }
-
- return request.pipe(switchMap(() => dispatch(new GetBooks())));
-}
-```
-
-TODO: description & screenshot ??
-
-### Delete a Book
-
-Open `books.service.ts` and the the `delete` method for delete a book with the `id` by performing an HTTP request to the related endpoint.
+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:
-```typescript
-delete(id: string): Observable {
- const request: Rest.Request = {
+```js
+delete(id: string): Observable {
+ return this.restService.request({
method: 'DELETE',
- url: `/api/app/book/${id}`,
- };
-
- return this.rest.request(request);
+ url: `/api/app/book/${id}`
+ });
}
```
-### State Definitions
+#### DeleteBook Action
-Add an action named `BooksDelete` to `books.actions.ts`
+Add an action named `DeleteBook` to `books.actions.ts`:
-```typescript
+```js
export class DeleteBook {
static readonly type = '[Books] Delete';
constructor(public id: string) {}
}
```
-Then, open the `books.state.ts` and add the `delete` method that will listen to a `CreateUpdateBook` action to create a book
+Open the `books.state.ts` and add the `delete` method that will listen to the `DeleteBook` action to delete a book:
-```typescript
+```js
+import { ... , DeleteBook } from '../actions/books.actions';
+//...
@Action(DeleteBook)
-delete({ dispatch }: StateContext, { id }: DeleteBook) {
- return this.booksService.delete(id).pipe(switchMap(() => dispatch(new GetBooks())));
+delete(ctx: StateContext, action: DeleteBook) {
+ return this.booksService.delete(action.id).pipe(switchMap(() => ctx.dispatch(new GetBooks())));
}
```
-TODO: description??
+- Added `DeleteBook` to the import list.
+- Uses `bookService` to delete the book.
-### Add a Delete Button
+#### Add a Delete Button
-Open `book-list.component.html` and modify the `ngbDropdownMenu` for add the delete button as shown below:
+Open `book-list.component.html` and modify the `ngbDropdownMenu` to add the delete button as shown below:
```html
@@ -533,11 +536,15 @@ Open `book-list.component.html` and modify the `ngbDropdownMenu` for add the del
```
-### Open Confirmation Popup
+The final actions dropdown UI looks like below:
+
+
-Open `book-list.component.ts` and inject the `ConfirmationService` for show confirmation popup.
+#### Delete Confirmation Dialog
-```typescript
+Open `book-list.component.ts` and inject the `ConfirmationService`.
+
+```js
import { ConfirmationService } from '@abp/ng.theme.shared';
//...
constructor(
@@ -546,10 +553,13 @@ constructor(
)
```
-Add the following method to `BookListComponent`.
+> `ConfirmationService` is a simple service provided by ABP framework that internally uses the PrimeNG.
-```typescript
-import { ConfirmationService, Toaster } from '@abp/ng.theme.shared';
+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
@@ -562,4 +572,10 @@ delete(id: string, name: string) {
}
```
-The `delete` method shows confirmation popup and listens to them. When close the popup, the subscribe block runs. If confirmed this popup, it will dispatch the `DeleteBook` action.
+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:
+
+
+
+### Next Part
+
+See the [next part](Part-III.md) of this tutorial.
diff --git a/docs/en/Tutorials/Angular/Part-III.md b/docs/en/Tutorials/Angular/Part-III.md
new file mode 100644
index 0000000000..0da85f0751
--- /dev/null
+++ b/docs/en/Tutorials/Angular/Part-III.md
@@ -0,0 +1,178 @@
+## ASP.NET Core MVC Tutorial - Part III
+
+### About this Tutorial
+
+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:
+
+
+
+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 _bookRepository;
+ private readonly IGuidGenerator _guidGenerator;
+
+ public BookStoreTestDataSeedContributor(
+ IRepository 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` 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();
+ }
+
+ [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(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:
+
+
+
+Congratulations, green icons show that tests have been successfully passed!
\ No newline at end of file
diff --git a/docs/en/Tutorials/Angular/images/bookstore-confirmation-popup.png b/docs/en/Tutorials/Angular/images/bookstore-confirmation-popup.png
new file mode 100644
index 0000000000..c3f46af821
Binary files /dev/null and b/docs/en/Tutorials/Angular/images/bookstore-confirmation-popup.png differ
diff --git a/docs/en/Tutorials/Angular/images/bookstore-final-actions-dropdown.png b/docs/en/Tutorials/Angular/images/bookstore-final-actions-dropdown.png
new file mode 100644
index 0000000000..6b0be415c4
Binary files /dev/null and b/docs/en/Tutorials/Angular/images/bookstore-final-actions-dropdown.png differ
diff --git a/docs/en/Tutorials/Angular/images/bookstore-test-explorer.png b/docs/en/Tutorials/Angular/images/bookstore-test-explorer.png
new file mode 100644
index 0000000000..06e9e7d331
Binary files /dev/null and b/docs/en/Tutorials/Angular/images/bookstore-test-explorer.png differ
diff --git a/docs/en/Tutorials/Angular/images/bookstore-test-projects-v3.png b/docs/en/Tutorials/Angular/images/bookstore-test-projects-v3.png
new file mode 100644
index 0000000000..32ea91b325
Binary files /dev/null and b/docs/en/Tutorials/Angular/images/bookstore-test-projects-v3.png differ
diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json
index 103556823e..deaa6bcc67 100644
--- a/docs/en/docs-nav.json
+++ b/docs/en/docs-nav.json
@@ -7,8 +7,12 @@
"text": "From Startup Templates",
"items": [
{
- "text": "ASP.NET Core MVC",
+ "text": "Application with MVC (Razor Pages) UI",
"path": "Getting-Started-AspNetCore-MVC-Template.md"
+ },
+ {
+ "text": "Application with Angular UI",
+ "path": "Getting-Started-Angular-Template.md"
}
]
},
@@ -34,8 +38,12 @@
"text": "Application Development",
"items": [
{
- "text": "With ASP.NET Core MVC",
+ "text": "With ASP.NET Core MVC UI",
"path": "Tutorials/AspNetCore-Mvc/Part-I.md"
+ },
+ {
+ "text": "With Angular UI",
+ "path": "Tutorials/Angular/Part-I.md"
}
]
}
@@ -237,11 +245,21 @@
"items": [
{
"text": "Entity Framework Core Integration",
- "path": "Entity-Framework-Core.md"
+ "path": "Entity-Framework-Core.md",
+ "items": [
+ {
+ "text": "PostgreSQL Integration",
+ "path": "Best-Practices/PostgreSQL-Integration.md"
+ }
+ ]
},
{
"text": "MongoDB Integration",
"path": "MongoDB.md"
+ },
+ {
+ "text": "Dapper Integration",
+ "path": "Dapper.md"
}
]
},
@@ -264,6 +282,20 @@
}
]
},
+ {
+ "text": "Startup Templates",
+ "path": "Startup-Templates/Index.md",
+ "items": [
+ {
+ "text": "Application",
+ "path": "Startup-Templates/Application.md"
+ },
+ {
+ "text": "Module",
+ "path": "Startup-Templates/Module.md"
+ }
+ ]
+ },
{
"text": "Samples",
"items": [
diff --git a/docs/zh-Hans/Application-Services.md b/docs/zh-Hans/Application-Services.md
index e98e945405..74718eb571 100644
--- a/docs/zh-Hans/Application-Services.md
+++ b/docs/zh-Hans/Application-Services.md
@@ -146,7 +146,7 @@ public interface IBookAppService : IApplicationService
`BookDto`是一个简单的[DTO](Data-Transfer-Objects.md)类, 定义如下:
````csharp
-[AutoMapFrom(typeof(Book))] //Defines the mapping
+[AbpAutoMapFrom(typeof(Book))] //Defines the mapping
public class BookDto
{
public Guid Id { get; set; }
@@ -159,7 +159,7 @@ public class BookDto
}
````
-* `BookDto`定义了`[AutoMapFrom(typeof(Book))]`属性来从创建对象映射Book到BookDto.
+* `BookDto`定义了`[AbpAutoMapFrom(typeof(Book))]`属性来从创建对象映射Book到BookDto.
然后你可以实现`GetAsync`方法. 如下所示:
@@ -247,7 +247,7 @@ public interface IAsyncCrudAppService<
示例中使用的DTO类是`BookDto`和`CreateUpdateBookDto`:
````csharp
-[AutoMapFrom(typeof(Book))]
+[AbpAutoMapFrom(typeof(Book))]
public class BookDto : AuditedEntityDto
{
public string Name { get; set; }
@@ -257,7 +257,7 @@ public class BookDto : AuditedEntityDto
public float Price { get; set; }
}
-[AutoMapTo(typeof(Book))]
+[AbpAutoMapTo(typeof(Book))]
public class CreateUpdateBookDto
{
[Required]
diff --git a/docs/zh-Hans/CLI.md b/docs/zh-Hans/CLI.md
index ad79e30449..767d971b28 100644
--- a/docs/zh-Hans/CLI.md
+++ b/docs/zh-Hans/CLI.md
@@ -39,15 +39,20 @@ abp new Acme.BookStore
#### Options
-* `--template` 或 `-t`: 指定模板. 默认的模板是 `mvc`.可用的模板有:
- * `mvc` (默认): ASP.NET Core [MVC应用程序模板](Startup-Templates/Mvc.md). 其他选项:
- * `--database-provider` 或 `-d`: 指定数据库提供程序. 默认提供程序是 `ef`. 可用的提供程序有:
+* `--template` 或者 `-t`: 指定模板. 默认的模板是 `app`,会生成web项目.可用的模板有:
+ * `app` (default): [应用程序模板](Startup-Templates/Application.md)。 其他选项:
+ * `--ui` 或者 `-u`: 指定ui框架。默认`mvc`框架。其他选项:
+ * `mvc`: ASP.NET Core MVC。此模板的其他选项:
+ * `--tiered`: 创建分层解决方案,Web和Http Api层在物理上是分开的。如果未指定会创建一个分层的解决方案,此解决方案没有那么复杂,适合大多数场景。
+ * `angular`: Angular. 这个模板还有一些额外的选项:
+ * `--separate-identity-server`: Separates the identity server application from the API host application. If not specified, you will have a single endpoint in the server side.
+ * `--database-provider` 或者 `-d`: 指定数据库提供程序。默认是 `ef`。其他选项:
* `ef`: Entity Framework Core.
* `mongodb`: MongoDB.
- * `--tiered`: 创建分层解决方案,Web和Http Api层在物理上是分开的. 如果未指定会创建一个分层的解决方案, 此解决方案没有那么复杂,适合大多数场景.
- * `module`: [模块模板](Startup-Templates/Module.md). 其他选项:
- * `--no-ui`: 不包含UI. 仅创建服务模块 (也称为微服务 - 没有UI).
-* `--output-folder` 或 `-o`: 指定输出文件夹,默认是当前目录.
+ * `module`: [Module template](Startup-Templates/Module.md). 其他选项:
+ * `--no-ui`: 不包含UI。仅创建服务模块(也称为微服务 - 没有UI)。
+* `--output-folder` 或者 `-o`: 指定输出文件夹,默认是当前目录。
+* `--version` 或者 `-v`: 指定ABP和模板的版本。它可以是 [release tag](https://github.com/abpframework/abp/releases) 或者 [branch name](https://github.com/abpframework/abp/branches). 如果没有指定,则使用最新版本。大多数情况下,您会希望使用最新的版本。
### add-package
diff --git a/docs/zh-Hans/Dapper.md b/docs/zh-Hans/Dapper.md
new file mode 100644
index 0000000000..60289abd00
--- /dev/null
+++ b/docs/zh-Hans/Dapper.md
@@ -0,0 +1,61 @@
+# Dapper 集成
+
+由于Dapper的思想是sql语句优先, 且主要为`IDbConnection`接口提供了一些扩展方法.
+
+Abp并没有为Dapper封装太多功能. Abp Dapper在Abp EntityFrameworkCore的基础上提供了`DapperRepository`基类, 在其中提供了Dapper需要的`IDbConnection`和`IDbTransaction`属性.
+
+这两个属性可以和[工作单元](Unit-Of-Work.md)很好的配合.
+
+## 安装
+
+请先根据[EF Core的集成文档](Entity-Framework-Core.md)安装并配置好EF Core.
+
+`Volo.Abp.Dapper`是Dapper集成的主要nuget包. 将其安装到你的项目中(在分层应用程序中适用于 数据访问/基础设施层):
+
+```shell
+Install-Package Volo.Abp.Dapper
+```
+
+然后添加 `AbpDapperModule` 模块依赖项(`DependsOn` Attribute) 到 [module](Module-Development-Basics.cn.md)(项目中的Mudole类):
+
+````C#
+using Volo.Abp.Dapper;
+using Volo.Abp.Modularity;
+
+namespace MyCompany.MyProject
+{
+ [DependsOn(typeof(AbpDapperModule))]
+ public class MyModule : AbpModule
+ {
+ //...
+ }
+}
+````
+
+## 实现Dapper仓储
+
+下面的代码实现了`Person`仓储, 它需要EF Core的`DbContext`(MyAppDbContext). 你可以注入`PersonDapperRepository`来调用它的方法.
+
+`DbConnection`和`DbTransaction`来自于`DapperRepository`基类.
+
+```C#
+public class PersonDapperRepository : DapperRepository, ITransientDependency
+{
+ public PersonDapperRepository(IDbContextProvider dbContextProvider)
+ : base(dbContextProvider)
+ {
+ }
+
+ public virtual async Task> GetAllPersonNames()
+ {
+ return (await DbConnection.QueryAsync("select Name from People", transaction: DbTransaction))
+ .ToList();
+ }
+
+ public virtual async Task UpdatePersonNames(string name)
+ {
+ return await DbConnection.ExecuteAsync("update People set Name = @NewName", new { NewName = name },
+ DbTransaction);
+ }
+}
+```
diff --git a/docs/zh-Hans/Entity-Framework-Core.md b/docs/zh-Hans/Entity-Framework-Core.md
index e709767652..6a7f69c2aa 100644
--- a/docs/zh-Hans/Entity-Framework-Core.md
+++ b/docs/zh-Hans/Entity-Framework-Core.md
@@ -10,7 +10,7 @@
Install-Package Volo.Abp.EntityFrameworkCore
```
-然后添加 `AbpEntityFrameworkCoreModule` 模块依赖项(`DependsOn` Attribute) 到 [module](Module-Development-Basics.cn.md)(项目中的Mudole类):
+然后添加 `AbpEntityFrameworkCoreModule` 模块依赖项(`DependsOn` Attribute) 到 [module](Module-Development-Basics.md)(项目中的Mudole类):
````C#
using Volo.Abp.EntityFrameworkCore;
diff --git a/docs/zh-Hans/Startup-Templates/Module.md b/docs/zh-Hans/Startup-Templates/Module.md
index adc368ed89..a6706ad783 100644
--- a/docs/zh-Hans/Startup-Templates/Module.md
+++ b/docs/zh-Hans/Startup-Templates/Module.md
@@ -25,7 +25,7 @@ abp new Acme.IssueManagement -t module
默认情况模板附带一个MVC UI,你可以使用`--no-ui`选项指定不包含UI层.
````bash
-abp new Acme.IssueManagement -t mvc-module --no-ui
+abp new Acme.IssueManagement -t module --no-ui
````
## 解决方案结构
diff --git a/docs/zh-Hans/docs-nav.json b/docs/zh-Hans/docs-nav.json
index fb147ac0fe..60c4afb3d7 100644
--- a/docs/zh-Hans/docs-nav.json
+++ b/docs/zh-Hans/docs-nav.json
@@ -242,6 +242,10 @@
{
"text": "MongoDB 集成",
"path": "MongoDB.md"
+ },
+ {
+ "text": "Dapper 集成",
+ "path": "Dapper.md"
}
]
},
diff --git a/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/HttpContextTenantResolveResultAccessor.cs b/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/HttpContextTenantResolveResultAccessor.cs
index 6c44195b1a..593d9a7024 100644
--- a/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/HttpContextTenantResolveResultAccessor.cs
+++ b/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/HttpContextTenantResolveResultAccessor.cs
@@ -7,17 +7,11 @@ namespace Volo.Abp.AspNetCore.MultiTenancy
[Dependency(ReplaceServices = true)]
public class HttpContextTenantResolveResultAccessor : ITenantResolveResultAccessor, ITransientDependency
{
+ public const string HttpContextItemName = "__AbpTenantResolveResult";
+
public TenantResolveResult Result
{
- get
- {
- if (_httpContextAccessor.HttpContext == null)
- {
- return null;
- }
-
- return _httpContextAccessor.HttpContext.Items[""] as TenantResolveResult;
- }
+ get => _httpContextAccessor.HttpContext?.Items[HttpContextItemName] as TenantResolveResult;
set
{
if (_httpContextAccessor.HttpContext == null)
@@ -25,7 +19,7 @@ namespace Volo.Abp.AspNetCore.MultiTenancy
return;
}
- _httpContextAccessor.HttpContext.Items[""] = value;
+ _httpContextAccessor.HttpContext.Items[HttpContextItemName] = value;
}
}
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Menu/Default.cshtml b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Menu/Default.cshtml
index cace550248..61b9ef49ee 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Menu/Default.cshtml
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Menu/Default.cshtml
@@ -27,7 +27,16 @@
{
\n \n\n"}]}],"members":{"__ctor__":[{"__symbolic":"constructor","parameters":[{"__symbolic":"reference","module":"@ng-bootstrap/ng-bootstrap","name":"NgbModal","line":10,"character":36},{"__symbolic":"reference","module":"@angular/forms","name":"FormBuilder","line":10,"character":58}]}],"modalContent":[{"__symbolic":"property","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"ViewChild","line":16,"character":3},"arguments":["modalContent",{"static":false}]}]}],"createForm":[{"__symbolic":"method"}],"openModal":[{"__symbolic":"method"}],"onSwitch":[{"__symbolic":"method"}],"save":[{"__symbolic":"method"}]}},"ɵe":{"__symbolic":"class","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"NgModule","line":15,"character":1},"arguments":[{"imports":[{"__symbolic":"call","expression":{"__symbolic":"select","expression":{"__symbolic":"reference","module":"@angular/router","name":"RouterModule","line":16,"character":12},"member":"forChild"},"arguments":[[{"path":"","pathMatch":"full","redirectTo":"login"},{"path":"","component":{"__symbolic":"reference","module":"@abp/ng.core","name":"DynamicLayoutComponent","line":10,"character":15},"children":[{"path":"login","component":{"__symbolic":"reference","name":"ɵa"}},{"path":"register","component":{"__symbolic":"reference","name":"ɵc"}}]}]]}],"exports":[{"__symbolic":"reference","module":"@angular/router","name":"RouterModule","line":17,"character":12}]}]}],"members":{}},"ɵf":{"__symbolic":"function","parameters":["options"],"value":{"redirectUrl":"/"}},"ɵg":{"__symbolic":"new","expression":{"__symbolic":"reference","module":"@angular/core","name":"InjectionToken","line":10,"character":35},"arguments":["ACCOUNT_OPTIONS"]}},"origins":{"AccountModule":"./lib/account.module","ACCOUNT_ROUTES":"./lib/constants/routes","ɵa":"./lib/components/login/login.component","ɵb":"./lib/models/options","ɵc":"./lib/components/register/register.component","ɵd":"./lib/components/tenant-box/tenant-box.component","ɵe":"./lib/account-routing.module","ɵf":"./lib/tokens/options.token","ɵg":"./lib/tokens/options.token"},"importAs":"@abp/ng.account"}
\ No newline at end of file
+{"__symbolic":"module","version":4,"exports":[{"from":"./lib/options"},{"from":"./lib/user"},{"from":"./lib/tenant"}],"metadata":{"RootAccountModule":{"__symbolic":"class","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"NgModule","line":4,"character":1},"arguments":[{}]}],"members":{},"statics":{"forRoot":{"__symbolic":"function","parameters":["options"],"defaults":[{}],"value":{"ngModule":{"__symbolic":"reference","name":"RootAccountModule"},"providers":[{"provide":{"__symbolic":"reference","name":"ɵb"},"useValue":{"__symbolic":"reference","name":"options"}},{"provide":"ACCOUNT_OPTIONS","useFactory":{"__symbolic":"reference","name":"ɵa"},"deps":[{"__symbolic":"reference","name":"ɵb"}]}]}}}},"AccountModule":{"__symbolic":"class","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"NgModule","line":11,"character":1},"arguments":[{"declarations":[{"__symbolic":"reference","name":"ɵc"},{"__symbolic":"reference","name":"ɵe"},{"__symbolic":"reference","name":"ɵg"}],"imports":[{"__symbolic":"reference","module":"@abp/ng.core","name":"CoreModule","line":13,"character":12},{"__symbolic":"reference","name":"ɵh"},{"__symbolic":"reference","module":"@abp/ng.theme.shared","name":"ThemeSharedModule","line":13,"character":46},{"__symbolic":"reference","module":"primeng/table","name":"TableModule","line":13,"character":65},{"__symbolic":"reference","module":"@ng-bootstrap/ng-bootstrap","name":"NgbDropdownModule","line":13,"character":78},{"__symbolic":"reference","module":"@ngx-validate/core","name":"NgxValidateCoreModule","line":13,"character":97}],"exports":[]}]}],"members":{}},"ACCOUNT_ROUTES":{"routes":[{"name":"Account","path":"account","invisible":true,"layout":{"__symbolic":"select","expression":{"__symbolic":"reference","module":"@abp/ng.core","name":"eLayoutType","line":8,"character":14},"member":"application"},"children":[{"path":"login","name":"Login","order":1},{"path":"register","name":"Register","order":2}]}],"settings":[]},"ɵa":{"__symbolic":"function","parameters":["options"],"value":{"redirectUrl":"/"}},"ɵb":{"__symbolic":"new","expression":{"__symbolic":"reference","module":"@angular/core","name":"InjectionToken","line":10,"character":35},"arguments":["ACCOUNT_OPTIONS"]},"ɵc":{"__symbolic":"class","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Component","line":14,"character":1},"arguments":[{"selector":"abp-login","template":"