Merge branch 'rel-4.0' into dev

pull/6462/head
Halil İbrahim Kalkan 4 years ago
commit accb547bc0

@ -0,0 +1,65 @@
# Getting Started
````json
//[doc-params]
{
"UI": ["MVC", "Blazor", "NG"],
"DB": ["EF", "Mongo"],
"Tiered": ["Yes", "No"]
}
````
> This document assumes that you prefer to use **{{ UI_Value }}** as the UI framework and **{{ DB_Value }}** as the database provider. For other options, please change the preference on top of this document.
## Create a New Project
Use the `new` command of the ABP CLI to create a new project:
````shell
abp new Acme.BookStore{{if UI == "NG"}} -u angular{{else if UI == "Blazor"}} -u blazor{{end}}{{if DB == "Mongo"}} -d mongodb{{end}}{{if Tiered == "Yes"}}{{if UI == "MVC"}} --tiered{{else}} --separate-identity-server{{end}}{{end}}
````
*You can use different level of namespaces; e.g. BookStore, Acme.BookStore or Acme.Retail.BookStore.*
{{ if Tiered == "Yes" }}
{{ if UI == "MVC" }}
* `--tiered` argument is used to create N-tiered solution where authentication server, UI and API layers are physically separated.
{{ else }}
* `--separate-identity-server` argument is used to separate the identity server application from the API host application. If not specified, you will have a single endpoint on the server.
{{ end }}
{{ end }}
> [ABP CLI document](./CLI.md) covers all of the available commands and options.
> Alternatively, you can **create and download** projects from [ABP Framework website](https://abp.io/get-started) by easily selecting the all the options from the page.
### The Solution Structure
The solution has a layered structure (based on the [Domain Driven Design](Domain-Driven-Design.md)) and contains unit & integration test projects. See the [application template document](Startup-Templates/Application.md) to understand the solution structure in details.
{{ if DB == "Mongo" }}
#### MongoDB Transactions
The [startup template](Startup-templates/Index.md) **disables** transactions in the `.MongoDB` project by default. If your MongoDB server supports transactions, you can enable the it in the *YourProjectMongoDbModule* class's `ConfigureServices` method:
```csharp
Configure<AbpUnitOfWorkDefaultOptions>(options =>
{
options.TransactionBehavior = UnitOfWorkTransactionBehavior.Auto;
});
```
> Or you can delete that code since `Auto` is already the default behavior.
{{ end }}
## Next Step
* [Running the solution](Getting-Started-Running-Solution.md)

@ -0,0 +1,217 @@
# Getting Started
````json
//[doc-params]
{
"UI": ["MVC", "Blazor", "NG"],
"DB": ["EF", "Mongo"],
"Tiered": ["Yes", "No"]
}
````
> This document assumes that you prefer to use **{{ UI_Value }}** as the UI framework and **{{ DB_Value }}** as the database provider. For other options, please change the preference on top of this document.
## Create the Database
### Connection String
Check the **connection string** in the `appsettings.json` file under the {{if Tiered == "Yes"}}`.IdentityServer` and `.HttpApi.Host` projects{{else}}{{if UI=="MVC"}}`.Web` project{{else}}`.HttpApi.Host` project{{end}}{{end}}
{{ if DB == "EF" }}
````json
"ConnectionStrings": {
"Default": "Server=localhost;Database=BookStore;Trusted_Connection=True"
}
````
The solution is configured to use **Entity Framework Core** with **MS SQL Server** by default. EF Core supports [various](https://docs.microsoft.com/en-us/ef/core/providers/) database providers, so you can use any supported DBMS. See [the Entity Framework integration document](Entity-Framework-Core.md) to learn how to [switch to another DBMS](Entity-Framework-Core-Other-DBMS.md).
### Apply the Migrations
The solution uses the [Entity Framework Core Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/?tabs=dotnet-core-cli). So, you need to apply migrations to create the database. There are two ways of applying the database migrations.
#### Apply Migrations Using the DbMigrator
The solution comes with a `.DbMigrator` console application which applies migrations and also **seeds the initial data**. It is useful on **development** as well as on **production** environment.
> `.DbMigrator` project has its own `appsettings.json`. So, if you have changed the connection string above, you should also change this one.
Right click to the `.DbMigrator` project and select **Set as StartUp Project**
![set-as-startup-project](images/set-as-startup-project.png)
Hit F5 (or Ctrl+F5) to run the application. It will have an output like shown below:
![db-migrator-output](images/db-migrator-output.png)
> Initial [seed data](Data-Seeding.md) creates the `admin` user in the database (with the password is `1q2w3E*`) which is then used to login to the application. So, you need to use `.DbMigrator` at least once for a new database.
#### Using EF Core Update-Database Command
Ef Core has `Update-Database` command which creates database if necessary and applies pending migrations.
{{ if UI == "MVC" }}
Right click to the {{if Tiered == "Yes"}}`.IdentityServer`{{else}}`.Web`{{end}} project and select **Set as StartUp project**:
{{ else if UI != "MVC" }}
Right click to the `.HttpApi.Host` project and select **Set as StartUp Project**:
{{ end }}
![set-as-startup-project](images/set-as-startup-project.png)
Open the **Package Manager Console**, select `.EntityFrameworkCore.DbMigrations` project as the **Default Project** and run the `Update-Database` command:
![package-manager-console-update-database](images/package-manager-console-update-database.png)
This will create a new database based on the configured connection string.
> **Using the `.DbMigrator` tool is the suggested way**, because it also seeds the initial data to be able to properly run the web application.
>
> If you just use the `Update-Database` command, you will have an empty database, so you can not login to the application since there is no initial admin user in the database. You can use the `Update-Database` command in development time when you don't need to seed the database. However, using the `.DbMigrator` application is easier and you can always use it to migrate the schema and seed the database.
{{ else if DB == "Mongo" }}
````json
"ConnectionStrings": {
"Default": "mongodb://localhost:27017/BookStore"
}
````
The solution is configured to use **MongoDB** in your local computer, so you need to have a MongoDB server instance up and running or change the connection string to another MongoDB server.
### Seed Initial Data
The solution comes with a `.DbMigrator` console application which **seeds the initial data**. It is useful on **development** as well as on **production** environment.
> `.DbMigrator` project has its own `appsettings.json`. So, if you have changed the connection string above, you should also change this one.
Right click to the `.DbMigrator` project and select **Set as StartUp Project**
![set-as-startup-project](images/set-as-startup-project.png)
Hit F5 (or Ctrl+F5) to run the application. It will have an output like shown below:
![db-migrator-output](images/db-migrator-output.png)
> Initial [seed data](Data-Seeding.md) creates the `admin` user in the database (with the password is `1q2w3E*`) which is then used to login to the application. So, you need to use `.DbMigrator` at least once for a new database.
{{ end }}
## Run the Application
{{ if UI == "MVC" }}
{{ if Tiered == "Yes" }}
> Tiered solutions use **Redis** as the distributed cache. Ensure that it is installed and running in your local computer. If you are using a remote Redis Server, set the configuration in the `appsettings.json` files of the projects below.
1. Ensure that the `.IdentityServer` project is the startup project. Run this application that will open a **login** page in your browser.
> Use Ctrl+F5 in Visual Studio (instead of F5) to run the application without debugging. If you don't have a debug purpose, this will be faster.
You can login, but you cannot enter to the main application here. This is **just the authentication server**.
2. Ensure that the `.HttpApi.Host` project is the startup project and run the application which will open a **Swagger UI** in your browser.
![swagger-ui](images/swagger-ui.png)
This is the HTTP API that is used by the web application.
3. Lastly, ensure that the `.Web` project is the startup project and run the application which will open a **welcome** page in your browser
![mvc-tiered-app-home](images/bookstore-home.png)
Click to the **login** button which will redirect you to the *authentication server* to login to the application:
![bookstore-login](images/bookstore-login.png)
{{ else # Tiered != "Yes" }}
Ensure that the `.Web` project is the startup project. Run the application which will open the **login** page in your browser:
> Use Ctrl+F5 in Visual Studio (instead of F5) to run the application without debugging. If you don't have a debug purpose, this will be faster.
![bookstore-login](images/bookstore-login.png)
{{ end # Tiered }}
{{ else # UI != "MVC" }}
### Running the HTTP API Host (Server Side)
{{ if Tiered == "Yes" }}
> Tiered solutions use Redis as the distributed cache. Ensure that it is installed and running in your local computer. If you are using a remote Redis Server, set the configuration in the `appsettings.json` files of the projects below.
Ensure that the `.IdentityServer` project is the startup project. Run the application which will open a **login** page in your browser.
> Use Ctrl+F5 in Visual Studio (instead of F5) to run the application without debugging. If you don't have a debug purpose, this will be faster.
You can login, but you cannot enter to the main application here. This is just the authentication server.
Ensure that the `.HttpApi.Host` project is the startup project and run the application which will open a Swagger UI:
{{ else # Tiered == "No" }}
Ensure that the `.HttpApi.Host` project is the startup project and run the application which will open a Swagger UI:
> Use Ctrl+F5 in Visual Studio (instead of F5) to run the application without debugging. If you don't have a debug purpose, this will be faster.
{{ end # Tiered }}
![swagger-ui](images/swagger-ui.png)
You can see the application APIs and test them here. Get [more info](https://swagger.io/tools/swagger-ui/) about the Swagger UI.
{{ end # UI }}
{{ if UI == "Blazor" }}
### Running the Blazor Application (Client Side)
Ensure that the `.Blazor` project is the startup project and run the application.
> Use Ctrl+F5 in Visual Studio (instead of F5) to run the application without debugging. If you don't have a debug purpose, this will be faster.
Once the application starts, click to the **Login** link on to header, which redirects you to the authentication server to enter a username and password:
![bookstore-login](images/bookstore-login.png)
{{ else if UI == "NG" }}
### Running 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)
```bash
yarn
```
Once all node modules are loaded, execute `yarn start` (or `npm start`) command:
```bash
yarn start
```
It may take a longer time for the first build. Once it finishes, it opens the Angular UI in your default browser with the [localhost:4200](http://localhost:4200/) address.
![bookstore-login](images/bookstore-login.png)
{{ end }}
Enter **admin** as the username and **1q2w3E*** as the password to login to the application. The application is up and running. You can start developing your application based on this startup template.
## Mobile Development
If you want to include a [React Native](https://reactnative.dev/) project in your solution, add `-m react-native` (or `--mobile react-native`) argument to project creation command. This is a basic React Native startup template to develop mobile applications integrated to your ABP based backends.
See the [Getting Started with the React Native](Getting-Started-React-Native.md) document to learn how to configure and run the React Native application.
## See Also
* [Web Application Development Tutorial](Tutorials/Part-1.md)
* [Application Startup Template](Startup-Templates/Application.md)

@ -0,0 +1,56 @@
# Getting Started
````json
//[doc-params]
{
"UI": ["MVC", "Blazor", "NG"],
"DB": ["EF", "Mongo"],
"Tiered": ["Yes", "No"]
}
````
> This document assumes that you prefer to use **{{ UI_Value }}** as the UI framework and **{{ DB_Value }}** as the database provider. For other options, please change the preference on top of this document.
## Setup Your Development Environment
First things first! Let's setup your development environment before creating the project.
### Pre-Requirements
The following tools should be installed on your development machine:
* [Visual Studio 2019](https://visualstudio.microsoft.com/vs/) (v16.8+) for Windows / [Visual Studio for Mac](https://visualstudio.microsoft.com/vs/mac/). <sup id="a-editor">[1](#f-editor)</sup>
* [.NET Core 5.0+](https://www.microsoft.com/net/download/dotnet-core/)
{{ if UI != "Blazor" }}
* [Node v12 or v14](https://nodejs.org/)
* [Yarn v1.20+ (not v2)](https://classic.yarnpkg.com/en/docs/install) <sup id="a-yarn">[2](#f-yarn)</sup> or npm v6+ (already installed with Node)
{{ end }}
{{ if Tiered == "Yes" }}
* [Redis](https://redis.io/) (the startup solution uses the Redis as the [distributed cache](Caching.md)).
{{ end }}
<sup id="f-editor"><b>1</b></sup> _You can use another editor instead of Visual Studio as long as it supports .NET Core and ASP.NET Core._ <sup>[↩](#a-editor)</sup>
{{ if UI != "Blazor" }}
<sup id="f-yarn"><b>2</b></sup> _Yarn v2 works differently and is not supported._ <sup>[↩](#a-yarn)</sup>
{{ end }}
### Install the ABP CLI
[ABP CLI](./CLI.md) is a command line interface that is used to automate some common tasks for ABP based solutions. First, you need to install the ABP CLI using the following command:
````shell
dotnet tool install -g Volo.Abp.Cli
````
If you've already installed, you can update it using the following command:
````shell
dotnet tool update -g Volo.Abp.Cli
````
## Next Step
* [Creating a new solution](Getting-Started-Create-Solution.md)

@ -9,310 +9,12 @@
}
````
This tutorial explains how to create a new web application using the [application startup template](Startup-Templates/Application.md).
> This document assumes that you prefer to use **{{ UI_Value }}** as the UI framework and **{{ DB_Value }}** as the database provider. For other options, please change the preference on top of this document.
## Contents
## Setup Your Development Environment
First things first! Let's setup your development environment before creating the first project.
### Pre-Requirements
The following tools should be installed on your development machine:
* [Visual Studio 2019](https://visualstudio.microsoft.com/vs/) (v16.8+) for Windows / [Visual Studio for Mac](https://visualstudio.microsoft.com/vs/mac/). <sup id="a-editor">[1](#f-editor)</sup>
* [.NET Core 5.0+](https://www.microsoft.com/net/download/dotnet-core/)
{{ if UI != "Blazor" }}
* [Node v12 or v14](https://nodejs.org/)
* [Yarn v1.20+ (not v2)](https://classic.yarnpkg.com/en/docs/install) <sup id="a-yarn">[2](#f-yarn)</sup> or npm v6+ (already installed with Node)
{{ end }}
{{ if Tiered == "Yes" }}
* [Redis](https://redis.io/) (the startup solution uses the Redis as the [distributed cache](Caching.md)).
{{ end }}
<sup id="f-editor"><b>1</b></sup> _You can use another editor instead of Visual Studio as long as it supports .NET Core and ASP.NET Core._ <sup>[↩](#a-editor)</sup>
{{ if UI != "Blazor" }}
<sup id="f-yarn"><b>2</b></sup> _Yarn v2 works differently and is not supported._ <sup>[↩](#a-yarn)</sup>
{{ end }}
### Install the ABP CLI
[ABP CLI](./CLI.md) is a command line interface that is used to automate some common tasks for ABP based solutions.
> ABP CLI is a free & open source tool for the ABP framework.
First, you need to install the ABP CLI using the following command:
````shell
dotnet tool install -g Volo.Abp.Cli
````
If you've already installed, you can update it using the following command:
````shell
dotnet tool update -g Volo.Abp.Cli
````
## Create a New Project
Use the `new` command of the ABP CLI to create a new project:
````shell
abp new Acme.BookStore{{if UI == "NG"}} -u angular{{else if UI == "Blazor"}} -u blazor{{end}}{{if DB == "Mongo"}} -d mongodb{{end}}{{if Tiered == "Yes"}}{{if UI == "MVC"}} --tiered{{else}} --separate-identity-server{{end}}{{end}}
````
> You can use different level of namespaces; e.g. BookStore, Acme.BookStore or Acme.Retail.BookStore.
> Alternatively, you can select the "Direct Download" tab from the [ABP Framework web site](https://abp.io/get-started) to create a new solution.
{{ if Tiered == "Yes" }}
{{ if UI == "MVC" }}
* `--tiered` argument is used to create N-tiered solution where authentication server, UI and API layers are physically separated.
{{ else }}
* `--separate-identity-server` argument is used to separate the identity server application from the API host application. If not specified, you will have a single endpoint on the server.
{{ end }}
{{ end }}
### ABP CLI Commands & Options
[ABP CLI document](./CLI.md) covers all of the available commands and options for the ABP CLI. This document uses the [application startup template](Startup-Templates/Application.md) to create a new web application. See the [ABP Startup Templates](Startup-Templates/Index.md) document for other templates.
### The Solution Structure
The solution has a layered structure (based on the [Domain Driven Design](Domain-Driven-Design.md)) and contains unit & integration test projects. See the [application template document](Startup-Templates/Application.md) to understand the solution structure in details.
{{ if DB == "Mongo" }}
#### MongoDB Transactions
The [startup template](Startup-templates/Index.md) **disables** transactions in the `.MongoDB` project by default. If your MongoDB server supports transactions, you can enable the it in the *YourProjectMongoDbModule* class:
```csharp
Configure<AbpUnitOfWorkDefaultOptions>(options =>
{
options.TransactionBehavior = UnitOfWorkTransactionBehavior.Auto;
});
```
> Or you can delete this code since this is already the default behavior.
{{ end }}
## Create the Database
### Connection String
Check the **connection string** in the `appsettings.json` file under the {{if Tiered == "Yes"}}`.IdentityServer` and `.HttpApi.Host` projects{{else}}{{if UI=="MVC"}}`.Web` project{{else}}`.HttpApi.Host` project{{end}}{{end}}
{{ if DB == "EF" }}
````json
"ConnectionStrings": {
"Default": "Server=localhost;Database=BookStore;Trusted_Connection=True"
}
````
The solution is configured to use **Entity Framework Core** with **MS SQL Server** by default. EF Core supports [various](https://docs.microsoft.com/en-us/ef/core/providers/) database providers, so you can use any supported DBMS. See [the Entity Framework integration document](Entity-Framework-Core.md) to learn how to [switch to another DBMS](Entity-Framework-Core-Other-DBMS.md).
### Apply the Migrations
The solution uses the [Entity Framework Core Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/?tabs=dotnet-core-cli). So, you need to apply migrations to create the database. There are two ways of applying the database migrations.
#### Apply Migrations Using the DbMigrator
The solution comes with a `.DbMigrator` console application which applies migrations and also **seeds the initial data**. It is useful on **development** as well as on **production** environment.
> `.DbMigrator` project has its own `appsettings.json`. So, if you have changed the connection string above, you should also change this one.
Right click to the `.DbMigrator` project and select **Set as StartUp Project**
![set-as-startup-project](images/set-as-startup-project.png)
Hit F5 (or Ctrl+F5) to run the application. It will have an output like shown below:
![db-migrator-output](images/db-migrator-output.png)
> Initial [seed data](Data-Seeding.md) creates the `admin` user in the database (with the password is `1q2w3E*`) which is then used to login to the application. So, you need to use `.DbMigrator` at least once for a new database.
#### Using EF Core Update-Database Command
Ef Core has `Update-Database` command which creates database if necessary and applies pending migrations.
{{ if UI == "MVC" }}
Right click to the {{if Tiered == "Yes"}}`.IdentityServer`{{else}}`.Web`{{end}} project and select **Set as StartUp project**:
{{ else if UI != "MVC" }}
Right click to the `.HttpApi.Host` project and select **Set as StartUp Project**:
{{ end }}
![set-as-startup-project](images/set-as-startup-project.png)
Open the **Package Manager Console**, select `.EntityFrameworkCore.DbMigrations` project as the **Default Project** and run the `Update-Database` command:
![package-manager-console-update-database](images/package-manager-console-update-database.png)
This will create a new database based on the configured connection string.
> **Using the `.DbMigrator` tool is the suggested way**, because it also seeds the initial data to be able to properly run the web application.
>
> If you just use the `Update-Database` command, you will have an empty database, so you can not login to the application since there is no initial admin user in the database. You can use the `Update-Database` command in development time when you don't need to seed the database. However, using the `.DbMigrator` application is easier and you can always use it to migrate the schema and seed the database.
{{ else if DB == "Mongo" }}
````json
"ConnectionStrings": {
"Default": "mongodb://localhost:27017/BookStore"
}
````
The solution is configured to use **MongoDB** in your local computer, so you need to have a MongoDB server instance up and running or change the connection string to another MongoDB server.
### Seed Initial Data
The solution comes with a `.DbMigrator` console application which **seeds the initial data**. It is useful on **development** as well as on **production** environment.
> `.DbMigrator` project has its own `appsettings.json`. So, if you have changed the connection string above, you should also change this one.
Right click to the `.DbMigrator` project and select **Set as StartUp Project**
![set-as-startup-project](images/set-as-startup-project.png)
Hit F5 (or Ctrl+F5) to run the application. It will have an output like shown below:
![db-migrator-output](images/db-migrator-output.png)
> Initial [seed data](Data-Seeding.md) creates the `admin` user in the database (with the password is `1q2w3E*`) which is then used to login to the application. So, you need to use `.DbMigrator` at least once for a new database.
{{ end }}
## Run the Application
{{ if UI == "MVC" }}
{{ if Tiered == "Yes" }}
> Tiered solutions use Redis as the distributed cache. Ensure that it is installed and running in your local computer. If you are using a remote Redis Server, set the configuration in the `appsettings.json` files of the projects below.
1. Ensure that the `.IdentityServer` project is the startup project. Run this application that will open a **login** page in your browser.
> Use Ctrl+F5 in Visual Studio (instead of F5) to run the application without debugging. If you don't have a debug purpose, this will be faster.
You can login, but you cannot enter to the main application here. This is **just the authentication server**.
2. Ensure that the `.HttpApi.Host` project is the startup project and run the application which will open a **Swagger UI** in your browser.
![swagger-ui](images/swagger-ui.png)
This is the HTTP API that is used by the web application.
3. Lastly, ensure that the `.Web` project is the startup project and run the application which will open a **welcome** page in your browser
![mvc-tiered-app-home](images/bookstore-home.png)
Click to the **login** button which will redirect you to the *authentication server* to login to the application:
![bookstore-login](images/bookstore-login.png)
{{ else # Tiered != "Yes" }}
Ensure that the `.Web` project is the startup project. Run the application which will open the **login** page in your browser:
> Use Ctrl+F5 in Visual Studio (instead of F5) to run the application without debugging. If you don't have a debug purpose, this will be faster.
![bookstore-login](images/bookstore-login.png)
{{ end # Tiered }}
{{ else # UI != "MVC" }}
### Running the HTTP API Host (Server Side)
{{ if Tiered == "Yes" }}
> Tiered solutions use Redis as the distributed cache. Ensure that it is installed and running in your local computer. If you are using a remote Redis Server, set the configuration in the `appsettings.json` files of the projects below.
Ensure that the `.IdentityServer` project is the startup project. Run the application which will open a **login** page in your browser.
> Use Ctrl+F5 in Visual Studio (instead of F5) to run the application without debugging. If you don't have a debug purpose, this will be faster.
You can login, but you cannot enter to the main application here. This is just the authentication server.
Ensure that the `.HttpApi.Host` project is the startup project and run the application which will open a Swagger UI:
{{ else # Tiered == "No" }}
Ensure that the `.HttpApi.Host` project is the startup project and run the application which will open a Swagger UI:
> Use Ctrl+F5 in Visual Studio (instead of F5) to run the application without debugging. If you don't have a debug purpose, this will be faster.
{{ end # Tiered }}
![swagger-ui](images/swagger-ui.png)
You can see the application APIs and test them here. Get [more info](https://swagger.io/tools/swagger-ui/) about the Swagger UI.
> ##### Authorization for the Swagger UI
>
> Most of the HTTP APIs require authentication & authorization. If you want to test authorized APIs, manually go to the `/Account/Login` page, enter `admin` as the username and `1q2w3E*` as the password to login to the application. Then you will be able to execute authorized APIs too.
{{ end # UI }}
{{ if UI == "Blazor" }}
### Running the Blazor Application (Client Side)
Ensure that the `.Blazor` project is the startup project and run the application.
> Use Ctrl+F5 in Visual Studio (instead of F5) to run the application without debugging. If you don't have a debug purpose, this will be faster.
Once the application starts, click to the **Login** link on to header, which redirects you to the authentication server to enter a username and password:
![bookstore-login](images/bookstore-login.png)
{{ else if UI == "NG" }}
### Running 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)
```bash
yarn
```
Once all node modules are loaded, execute `yarn start` (or `npm start`) command:
```bash
yarn start
```
It may take a longer time for the first build. Once it finishes, it opens the Angular UI in your default browser with the [localhost:4200](http://localhost:4200/) address.
![bookstore-login](images/bookstore-login.png)
{{ end }}
Enter **admin** as the username and **1q2w3E*** as the password to login to the application. The application is up and running. You can start developing your application based on this startup template.
## Mobile Development
If you want to include a [React Native](https://reactnative.dev/) project in your solution, add `-m react-native` (or `--mobile react-native`) argument to project creation command. This is a basic React Native startup template to develop mobile applications integrated to your ABP based backends.
See the [Getting Started with the React Native](Getting-Started-React-Native.md) document to learn how to configure and run the React Native application.
## Next
This tutorial explains how to **create and run** a new web application using the ABP Framework. Follow the steps below;
* [Web Application Development Tutorial](Tutorials/Part-1.md)
1. [Setup your development environment](Getting-Started-Setup-Environment)
2. [Creating a new solution](Getting-Started-Create-Solution.md)
3. [Running the solution](Getting-Started-Running-Solution.md)

@ -0,0 +1,3 @@
# Swagger UI Integration
TODO

@ -37,14 +37,6 @@ describe('Validators', () => {
expect(control.errors).toEqual(expected);
});
it('should return null when control is pristine', () => {
const invalidNumber = '5105105105105101';
const control = new FormControl(invalidNumber, [validateCreditCard()]);
// control is not dirty
expect(control.valid).toBe(true);
});
});
describe('Email Validator', () => {
@ -81,14 +73,6 @@ describe('Validators', () => {
expect(control.errors).toEqual(expected);
},
);
it('should return null when control is pristine', () => {
const invalidDate = '';
const control = new FormControl(invalidDate, [validateMinAge({ age: Infinity })]);
// control is not dirty
expect(control.valid).toBe(true);
});
});
describe('Range Validator', () => {
@ -115,14 +99,6 @@ describe('Validators', () => {
expect(control.errors).toEqual(expected);
},
);
it('should return null when control is pristine', () => {
const invalidUrl = '';
const control = new FormControl(invalidUrl, [validateRange({ minimum: 3 })]);
// control is not dirty
expect(control.valid).toBe(true);
});
});
describe('Required Validator', () => {
@ -148,14 +124,6 @@ describe('Validators', () => {
expect(control.errors).toEqual(expected);
},
);
it('should return null when control is pristine', () => {
const invalidUrl = '';
const control = new FormControl(invalidUrl, [validateRequired()]);
// control is not dirty
expect(control.valid).toBe(true);
});
});
describe('String Length Validator', () => {
@ -178,14 +146,6 @@ describe('Validators', () => {
expect(control.errors).toEqual(expected);
},
);
it('should return null when control is pristine', () => {
const invalidUrl = '';
const control = new FormControl(invalidUrl, [validateStringLength({ minimumLength: 3 })]);
// control is not dirty
expect(control.valid).toBe(true);
});
});
describe('Url Validator', () => {
@ -217,13 +177,5 @@ describe('Validators', () => {
expect(control.errors).toEqual(expected);
});
it('should return null when control is pristine', () => {
const invalidUrl = 'x';
const control = new FormControl(invalidUrl, [validateUrl()]);
// control is not dirty
expect(control.valid).toBe(true);
});
});
});

@ -12,8 +12,6 @@ export interface MinAgeOptions {
export function validateMinAge({ age = 18 }: MinAgeOptions = {}): ValidatorFn {
return (control: AbstractControl): MinAgeError | null => {
if (control.pristine) return null;
if (['', null, undefined].indexOf(control.value) > -1) return null;
return isValidMinAge(control.value, age) ? null : { minAge: { age } };

@ -6,8 +6,6 @@ export interface CreditCardError {
export function validateCreditCard(): ValidatorFn {
return (control: AbstractControl): CreditCardError | null => {
if (control.pristine) return null;
if (['', null, undefined].indexOf(control.value) > -1) return null;
return isValidCreditCard(String(control.value)) ? null : { creditCard: true };

@ -14,8 +14,6 @@ export interface RangeOptions {
export function validateRange({ maximum = Infinity, minimum = 0 }: RangeOptions = {}): ValidatorFn {
return (control: AbstractControl): RangeError | null => {
if (control.pristine) return null;
if (['', null, undefined].indexOf(control.value) > -1) return null;
const value = Number(control.value);

@ -10,9 +10,7 @@ export interface RequiredOptions {
export function validateRequired({ allowEmptyStrings }: RequiredOptions = {}): ValidatorFn {
return (control: AbstractControl): RequiredError | null => {
return control.pristine || isValidRequired(control.value, allowEmptyStrings)
? null
: { required: true };
return isValidRequired(control.value, allowEmptyStrings) ? null : { required: true };
};
}

@ -19,8 +19,6 @@ export function validateStringLength({
minimumLength = 0,
}: StringLengthOptions = {}): ValidatorFn {
return (control: AbstractControl): StringLengthError | null => {
if (control.pristine) return null;
if (['', null, undefined].indexOf(control.value) > -1) return null;
const value = String(control.value);

@ -6,8 +6,6 @@ export interface UrlError {
export function validateUrl(): ValidatorFn {
return (control: AbstractControl): UrlError | null => {
if (control.pristine) return null;
if (['', null, undefined].indexOf(control.value) > -1) return null;
return isValidUrl(control.value) ? null : { url: true };

@ -91,7 +91,7 @@
</div>
</div>
<abp-modal [(visible)]="isModalVisible" [busy]="modalBusy" (disappear)="form = null">
<abp-modal size="md" [(visible)]="isModalVisible" [busy]="modalBusy" (disappear)="form = null">
<ng-template #abpHeader>
<h3>{{ (selected?.id ? 'AbpIdentity::Edit' : 'AbpIdentity::NewUser') | abpLocalization }}</h3>
</ng-template>

@ -9,14 +9,14 @@
"license": "MIT",
"schematics": "./collection.json",
"dependencies": {
"@angular-devkit/core": "~10.0.3",
"@angular-devkit/schematics": "~10.0.3",
"@angular-devkit/core": "~11.0.2",
"@angular-devkit/schematics": "~11.0.2",
"got": "^11.5.2",
"jsonc-parser": "^2.3.0",
"typescript": "~3.9.2"
},
"devDependencies": {
"@schematics/angular": "~10.0.3",
"@schematics/angular": "~11.0.2",
"@types/jest": "^26.0.0",
"@types/node": "^12.11.1",
"jest": "^26.0.0",

@ -2,6 +2,7 @@ export const SYSTEM_TYPES = new Map([
['Bool', 'boolean'],
['Byte', 'number'],
['Char', 'string'],
['Collections.Generic.Dictionary', 'Record'],
['DateTime', 'string'],
['DateTimeOffset', 'string'],
['Decimal', 'number'],

@ -5,72 +5,70 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import { experimental } from '@angular-devkit/core';
export enum ProjectType {
Application = 'application',
Library = 'library',
Application = 'application',
Library = 'library',
}
export enum Builders {
AppShell = '@angular-devkit/build-angular:app-shell',
Server = '@angular-devkit/build-angular:server',
Browser = '@angular-devkit/build-angular:browser',
Karma = '@angular-devkit/build-angular:karma',
TsLint = '@angular-devkit/build-angular:tslint',
NgPackagr = '@angular-devkit/build-ng-packagr:build',
DevServer = '@angular-devkit/build-angular:dev-server',
ExtractI18n = '@angular-devkit/build-angular:extract-i18n',
Protractor = '@angular-devkit/build-angular:protractor',
AppShell = '@angular-devkit/build-angular:app-shell',
Server = '@angular-devkit/build-angular:server',
Browser = '@angular-devkit/build-angular:browser',
Karma = '@angular-devkit/build-angular:karma',
TsLint = '@angular-devkit/build-angular:tslint',
DeprecatedNgPackagr = '@angular-devkit/build-ng-packagr:build',
NgPackagr = '@angular-devkit/build-angular:ng-packagr',
DevServer = '@angular-devkit/build-angular:dev-server',
ExtractI18n = '@angular-devkit/build-angular:extract-i18n',
Protractor = '@angular-devkit/build-angular:protractor',
}
export interface FileReplacements {
replace: string;
with: string;
replace: string;
with: string;
}
export interface BrowserBuilderBaseOptions {
main: string;
tsConfig: string;
fileReplacements?: FileReplacements[];
outputPath?: string;
index?: string;
polyfills: string;
assets?: (object|string)[];
styles?: (object|string)[];
scripts?: (object|string)[];
sourceMap?: boolean;
main: string;
tsConfig: string;
fileReplacements?: FileReplacements[];
outputPath?: string;
index?: string;
polyfills: string;
assets?: (object | string)[];
styles?: (object | string)[];
scripts?: (object | string)[];
sourceMap?: boolean;
}
export type OutputHashing = 'all' | 'media' | 'none' | 'bundles';
export interface BrowserBuilderOptions extends BrowserBuilderBaseOptions {
serviceWorker?: boolean;
optimization?: boolean;
outputHashing?: OutputHashing;
resourcesOutputPath?: string;
extractCss?: boolean;
namedChunks?: boolean;
aot?: boolean;
extractLicenses?: boolean;
vendorChunk?: boolean;
buildOptimizer?: boolean;
ngswConfigPath?: string;
budgets?: {
type: string;
maximumWarning?: string;
maximumError?: string;
}[];
webWorkerTsConfig?: string;
serviceWorker?: boolean;
optimization?: boolean;
outputHashing?: OutputHashing;
resourcesOutputPath?: string;
extractCss?: boolean;
namedChunks?: boolean;
aot?: boolean;
extractLicenses?: boolean;
vendorChunk?: boolean;
buildOptimizer?: boolean;
ngswConfigPath?: string;
budgets?: {
type: string;
maximumWarning?: string;
maximumError?: string;
}[];
webWorkerTsConfig?: string;
}
export interface ServeBuilderOptions {
browserTarget: string;
browserTarget: string;
}
export interface LibraryBuilderOptions {
tsConfig: string;
project: string;
tsConfig: string;
project: string;
}
export interface ServerBuilderOptions {
@ -82,45 +80,47 @@ export interface ServerBuilderOptions {
scripts?: boolean;
styles?: boolean;
};
sourceMap?: boolean | {
scripts?: boolean;
styles?: boolean;
hidden?: boolean;
vendor?: boolean;
};
sourceMap?:
| boolean
| {
scripts?: boolean;
styles?: boolean;
hidden?: boolean;
vendor?: boolean;
};
}
export interface AppShellBuilderOptions {
browserTarget: string;
serverTarget: string;
route: string;
browserTarget: string;
serverTarget: string;
route: string;
}
export interface TestBuilderOptions extends Partial<BrowserBuilderBaseOptions> {
karmaConfig: string;
karmaConfig: string;
}
export interface LintBuilderOptions {
tsConfig: string[] | string;
exclude?: string[];
tsConfig: string[] | string;
exclude?: string[];
}
export interface ExtractI18nOptions {
browserTarget: string;
browserTarget: string;
}
export interface E2EOptions {
protractorConfig: string;
devServerTarget: string;
protractorConfig: string;
devServerTarget: string;
}
export interface BuilderTarget<TBuilder extends Builders, TOptions> {
builder: TBuilder;
options: TOptions;
configurations?: {
production: Partial<TOptions>;
[key: string]: Partial<TOptions>;
};
builder: TBuilder;
options: TOptions;
configurations?: {
production: Partial<TOptions>;
[key: string]: Partial<TOptions>;
};
}
export type LibraryBuilderTarget = BuilderTarget<Builders.NgPackagr, LibraryBuilderOptions>;
@ -133,39 +133,47 @@ export type ServeBuilderTarget = BuilderTarget<Builders.DevServer, ServeBuilderO
export type ExtractI18nBuilderTarget = BuilderTarget<Builders.ExtractI18n, ExtractI18nOptions>;
export type E2EBuilderTarget = BuilderTarget<Builders.Protractor, E2EOptions>;
export interface WorkspaceSchema extends experimental.workspace.WorkspaceSchema {
projects: {
[key: string]: WorkspaceProject<ProjectType.Application | ProjectType.Library>;
};
export interface WorkspaceSchema {
version: 1;
defaultProject?: string;
cli?: { warnings?: Record<string, boolean> };
projects: {
[key: string]: WorkspaceProject<ProjectType.Application | ProjectType.Library>;
};
}
export interface WorkspaceProject<TProjectType extends ProjectType = ProjectType.Application>
extends experimental.workspace.WorkspaceProject {
/**
* Project type.
*/
projectType: ProjectType;
export interface WorkspaceProject<TProjectType extends ProjectType = ProjectType.Application> {
/**
* Project type.
*/
projectType: ProjectType;
root: string;
sourceRoot: string;
prefix: string;
cli?: { warnings?: Record<string, boolean> };
/**
* Tool options.
*/
architect?: WorkspaceTargets<TProjectType>;
/**
* Tool options.
*/
targets?: WorkspaceTargets<TProjectType>;
/**
* Tool options.
*/
architect?: WorkspaceTargets<TProjectType>;
/**
* Tool options.
*/
targets?: WorkspaceTargets<TProjectType>;
}
export interface WorkspaceTargets<TProjectType extends ProjectType = ProjectType.Application> {
build?: TProjectType extends ProjectType.Library ? LibraryBuilderTarget : BrowserBuilderTarget;
server?: ServerBuilderTarget;
lint?: LintBuilderTarget;
test?: TestBuilderTarget;
serve?: ServeBuilderTarget;
e2e?: E2EBuilderTarget;
'app-shell'?: AppShellBuilderTarget;
'extract-i18n'?: ExtractI18nBuilderTarget;
// TODO(hans): change this any to unknown when google3 supports TypeScript 3.0.
// tslint:disable-next-line:no-any
[key: string]: any;
build?: TProjectType extends ProjectType.Library ? LibraryBuilderTarget : BrowserBuilderTarget;
server?: ServerBuilderTarget;
lint?: LintBuilderTarget;
test?: TestBuilderTarget;
serve?: ServeBuilderTarget;
e2e?: E2EBuilderTarget;
'app-shell'?: AppShellBuilderTarget;
'extract-i18n'?: ExtractI18nBuilderTarget;
// TODO(hans): change this any to unknown when google3 supports TypeScript 3.0.
// tslint:disable-next-line:no-any
[key: string]: any;
}

@ -1,4 +1,3 @@
import { strings } from '@angular-devkit/core';
import { SYSTEM_TYPES } from '../constants';
import { VOLO_REGEX } from '../constants/volo';
import { eImportKeyword } from '../enums';
@ -8,16 +7,22 @@ import { relativePathToEnum, relativePathToModel } from './path';
import { parseGenerics } from './tree';
export function createTypeSimplifier() {
const parseType = createTypeParser(type => {
type = type.replace(
const parseType = createTypeParser(t => {
let type = t.replace(
/System\.([0-9A-Za-z.]+)/g,
(_, match) => SYSTEM_TYPES.get(match) ?? strings.camelize(match),
(_, match) => SYSTEM_TYPES.get(match) ?? 'any',
);
type = /any</.test(type) ? 'any' : type;
const regexp = new RegExp(/.*(?<=\.)(?<generic>.+)<.*(?<=[\.<])(?<genericType>.+)>/gm);
const { generic, genericType } = regexp.exec(type)?.groups ?? {};
return generic ? `${generic}<${genericType}>` : type.split('.').pop()!;
return generic
? generic === 'any'
? 'any'
: `${generic}<${genericType}>`
: type.split('.').pop()!;
});
return (type: string) => {

@ -1,8 +1,8 @@
import { experimental, strings, workspaces } from '@angular-devkit/core';
import { strings, workspaces } from '@angular-devkit/core';
import { SchematicsException, Tree } from '@angular-devkit/schematics';
import { Exception } from '../enums';
import { Project } from '../models';
import { getWorkspace, ProjectType } from './angular';
import { getWorkspace, ProjectType, WorkspaceSchema } from './angular';
import { findEnvironmentExpression } from './ast';
import { readFileInTree } from './common';
@ -23,7 +23,7 @@ export function readWorkspaceSchema(tree: Tree) {
const workspaceBuffer = tree.read('/angular.json') || tree.read('/workspace.json');
if (!workspaceBuffer) throw new SchematicsException(Exception.NoWorkspace);
let workspaceSchema: experimental.workspace.WorkspaceSchema;
let workspaceSchema: WorkspaceSchema;
try {
workspaceSchema = JSON.parse(workspaceBuffer.toString());

@ -65,10 +65,9 @@ export class ExtensibleFormPropComponent implements OnChanges {
this.typeaheadModel = selectedOption || { key: null, value: null };
const { key, value } = this.typeaheadModel;
const [keyControl, valueControl] = this.getTypeaheadControls();
if (valueControl.value && !value) valueControl.markAsDirty();
keyControl.setValue(key);
valueControl.setValue(value);
valueControl.markAsDirty();
valueControl.markAsTouched();
}
search = (text$: Observable<string>) =>
@ -106,7 +105,7 @@ export class ExtensibleFormPropComponent implements OnChanges {
}
private setAsterisk() {
this.asterisk = this.validators.some(v => v === Validators.required) ? '*' : '';
this.asterisk = this.validators.some(isRequired) ? '*' : '';
}
getComponent(prop: FormProp): string {
@ -167,3 +166,7 @@ export class ExtensibleFormPropComponent implements OnChanges {
this.typeaheadModel = { key: keyControl.value, value: valueControl.value };
}
}
function isRequired(validator: ValidatorFn) {
return validator === Validators.required || validator.toString().includes('required');
}

@ -1,37 +0,0 @@
// https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Utilities/StringUtils.cs#L155
export function jsonNetCamelCase(str: string) {
if (!str || !isUpperCase(str[0])) return str;
const chars = str.split('');
const { length } = chars;
for (let i = 0; i < length; i++) {
if (i === 1 && !isUpperCase(chars[i])) break;
const hasNext = i + 1 < length;
if (i > 0 && hasNext && !isUpperCase(chars[i + 1])) {
if (isSeparator(chars[i + 1])) {
chars[i] = toLowerCase(chars[i]);
}
break;
}
chars[i] = toLowerCase(chars[i]);
}
return chars.join('');
}
function isSeparator(str = '') {
return /[\s\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,\-.\/:;<=>?@\[\]^_`{|}~]+/.test(str);
}
function isUpperCase(str = '') {
return /[A-Z]+/.test(str);
}
function toLowerCase(str = '') {
return str.toLowerCase();
}

@ -11,7 +11,6 @@ import { EntityProp, EntityPropList } from '../models/entity-props';
import { FormProp, FormPropList } from '../models/form-props';
import { ObjectExtensions } from '../models/object-extensions';
import { PropCallback } from '../models/props';
import { jsonNetCamelCase } from './case.util';
import { createEnum, createEnumOptions, createEnumValueResolver } from './enum.util';
import { createDisplayNameLocalizationPipeKeyGenerator } from './localization.util';
import { createExtraPropertyValueResolver } from './props.util';
@ -122,7 +121,7 @@ function createPropertiesToContributorsMapper<T = any>(
Object.keys(properties).forEach((name: string) => {
const property = properties[name];
const propName = jsonNetCamelCase(name);
const propName = name;
const lookup = property.ui.lookup || ({} as ExtensionPropertyUiLookupDto);
const type = getTypeaheadType(lookup, name) || getTypeFromProperty(property);
const displayName = generateDisplayName(property.displayName, { name, resource });

@ -1,18 +0,0 @@
import { jsonNetCamelCase } from '../lib/utils/case.util';
describe('Case Utils', () => {
describe('#jsonNetCamelCase', () => {
test.each`
input | output
${'Primary'} | ${'primary'}
${'PrimaryRole'} | ${'primaryRole'}
${'Primary Role'} | ${'primary Role'}
${'PrimaryRole_Text'} | ${'primaryRole_Text'}
${'ISBN'} | ${'isbn'}
${''} | ${''}
${'iMDB'} | ${'iMDB'}
`('should return $output when input is $input', ({ input, output }) => {
expect(jsonNetCamelCase(input)).toBe(output);
});
});
});

@ -51,28 +51,28 @@ describe('State Utils', () => {
contributors.prop.Role.forEach(callback => callback(propList));
expect(propList.length).toBe(4);
expect(propList.head.value.name).toBe('title');
expect(propList.head.next.value.name).toBe('isHero');
expect(propList.head.next.next.value.name).toBe('myEnum');
expect(propList.head.next.next.next.value.name).toBe('foo_Text');
expect(propList.head.value.name).toBe('Title');
expect(propList.head.next.value.name).toBe('IsHero');
expect(propList.head.next.next.value.name).toBe('MyEnum');
expect(propList.head.next.next.next.value.name).toBe('Foo_Text');
const createFormList = new FormPropList();
contributors.createForm.Role.forEach(callback => callback(createFormList));
expect(createFormList.length).toBe(4);
expect(createFormList.head.value.name).toBe('title');
expect(createFormList.head.next.value.name).toBe('myEnum');
expect(createFormList.head.next.next.value.name).toBe('foo');
expect(createFormList.head.next.next.next.value.name).toBe('foo_Text');
expect(createFormList.head.value.name).toBe('Title');
expect(createFormList.head.next.value.name).toBe('MyEnum');
expect(createFormList.head.next.next.value.name).toBe('Foo');
expect(createFormList.head.next.next.next.value.name).toBe('Foo_Text');
const editFormList = new FormPropList();
contributors.editForm.Role.forEach(callback => callback(editFormList));
expect(editFormList.length).toBe(4);
expect(editFormList.head.value.name).toBe('title');
expect(editFormList.head.next.value.name).toBe('isHero');
expect(editFormList.head.next.next.value.name).toBe('foo');
expect(editFormList.head.next.next.next.value.name).toBe('foo_Text');
expect(editFormList.head.value.name).toBe('Title');
expect(editFormList.head.next.value.name).toBe('IsHero');
expect(editFormList.head.next.next.value.name).toBe('Foo');
expect(editFormList.head.next.next.next.value.name).toBe('Foo_Text');
});
});
});

@ -49,18 +49,22 @@ async function* copyPackageFiles(packageName: string) {
}
(async () => {
await fse.remove(`../dist/${PACKAGE_TO_BUILD}`);
try {
await fse.remove(`../dist/${PACKAGE_TO_BUILD}`);
await execa(
'tsc',
['-p', `packages/${PACKAGE_TO_BUILD}/tsconfig.json`, '--outDir', `dist/${PACKAGE_TO_BUILD}`],
{
stdout: 'inherit',
cwd: '../',
},
);
await execa(
'tsc',
['-p', `packages/${PACKAGE_TO_BUILD}/tsconfig.json`, '--outDir', `dist/${PACKAGE_TO_BUILD}`],
{
stdout: 'inherit',
cwd: '../',
},
);
for await (const filecopy of copyPackageFiles(PACKAGE_TO_BUILD)) {
// do nothing
for await (const filecopy of copyPackageFiles(PACKAGE_TO_BUILD)) {
// do nothing
}
} catch (error) {
process.exit(1);
}
})();

@ -2,12 +2,12 @@
# yarn lockfile v1
"@abp/ng.core@~4.0.0-rc.4":
version "4.0.0-rc.4"
resolved "https://registry.yarnpkg.com/@abp/ng.core/-/ng.core-4.0.0-rc.4.tgz#f1cbb7d43662579a7977c9d98bf28f7a6726537f"
integrity sha512-EtvoUr52v4+DEn3j2mru4OiLJf9tcg5ptLRjwBg1psftz1t3atnmvP1F8+sIhOwnj/Zm5hlhqSqnedpTzdALPg==
"@abp/ng.core@~4.0.0-rc.5":
version "4.0.0-rc.5"
resolved "https://registry.yarnpkg.com/@abp/ng.core/-/ng.core-4.0.0-rc.5.tgz#30db71708801ea7f04307577d9482d0bb3b386b0"
integrity sha512-5l7KwxCw21rbYO1dWvpIrUU77EfM68XuNm2s+3bLpckUgsanf2zjdpYPbXgzpeAedsUtjrTjCZLqlRj9n4iwKQ==
dependencies:
"@abp/utils" "^4.0.0-rc.3"
"@abp/utils" "^4.0.0-rc.4"
"@angular/localize" "~10.0.10"
"@ngxs/router-plugin" "^3.7.0"
"@ngxs/store" "^3.7.0"
@ -18,29 +18,29 @@
ts-toolbelt "6.15.4"
tslib "^2.0.0"
"@abp/ng.feature-management@~4.0.0-rc.4":
version "4.0.0-rc.4"
resolved "https://registry.yarnpkg.com/@abp/ng.feature-management/-/ng.feature-management-4.0.0-rc.4.tgz#ab740e4327b8fd10ba4567bfeb24debbd4ffe8a5"
integrity sha512-PlWxAA2wECRrBGwqWeaWi+f3YPayIaOJhW4INTvD5JUFMIRJsOJZlxpmcz8x2pU64bUO42/YM26+Xy2TyVKFNg==
"@abp/ng.feature-management@~4.0.0-rc.5":
version "4.0.0-rc.5"
resolved "https://registry.yarnpkg.com/@abp/ng.feature-management/-/ng.feature-management-4.0.0-rc.5.tgz#0755c8f0eb69c339cc511f2e599786c8fcb2d369"
integrity sha512-owYYnnNqQOCq9MepTVjn8lWTw1Uux+hj83QZ47Mfz3IKP/EuYz2cS3rkqIw5MQ4hPfQZxBwu5qM2vhbFLt0K4w==
dependencies:
"@abp/ng.theme.shared" "~4.0.0-rc.4"
"@abp/ng.theme.shared" "~4.0.0-rc.5"
tslib "^2.0.0"
"@abp/ng.identity@~4.0.0-rc.4":
version "4.0.0-rc.4"
resolved "https://registry.yarnpkg.com/@abp/ng.identity/-/ng.identity-4.0.0-rc.4.tgz#5b8694b1d1d6f1a039e1a445add14dcc5d6eb5ad"
integrity sha512-nqx3ofuTT5kOdJHWAyrZyEmGGp08DZNNC7IIY7L1/NueiAcfjghKrAiGkNnt5SApdk86OzdrLdIvpELnZVCMzg==
"@abp/ng.identity@~4.0.0-rc.5":
version "4.0.0-rc.5"
resolved "https://registry.yarnpkg.com/@abp/ng.identity/-/ng.identity-4.0.0-rc.5.tgz#f67fc5ed50be150e9229754b27506121817c03a7"
integrity sha512-NsJ5BJaqLuSt+tutN258FeBLFlfLQvoGI1etn8+1CoBd+uYGEcIYNVkJj/i+8q/ShMU7LoZ9Di/G4NjxU8WFig==
dependencies:
"@abp/ng.permission-management" "~4.0.0-rc.4"
"@abp/ng.theme.shared" "~4.0.0-rc.4"
"@abp/ng.permission-management" "~4.0.0-rc.5"
"@abp/ng.theme.shared" "~4.0.0-rc.5"
tslib "^2.0.0"
"@abp/ng.permission-management@~4.0.0-rc.4":
version "4.0.0-rc.4"
resolved "https://registry.yarnpkg.com/@abp/ng.permission-management/-/ng.permission-management-4.0.0-rc.4.tgz#3d838528549860e2a8b6aba2c5f35f6764089322"
integrity sha512-2o/1dtfNQOkqHWzdHyI8Ske5E5VseCM+N3erJu4NSLIWrj5G3CG9wwbJm3BqEjHG7hzbcvR6fYxKpDUmqvQR0A==
"@abp/ng.permission-management@~4.0.0-rc.5":
version "4.0.0-rc.5"
resolved "https://registry.yarnpkg.com/@abp/ng.permission-management/-/ng.permission-management-4.0.0-rc.5.tgz#04ea4199074ff2689d7a429ba482c8a7b37abb0a"
integrity sha512-T+iAhDFXaYW9/JRPPWXqm1lbHv5F0uTR/Ij5yiCpFgrbDu5bPVf/0WR7y8VyMn5lZ+1gWa+o9VievBnlKVRwHg==
dependencies:
"@abp/ng.theme.shared" "~4.0.0-rc.4"
"@abp/ng.theme.shared" "~4.0.0-rc.5"
tslib "^2.0.0"
"@abp/ng.schematics@~3.3.1":
@ -54,52 +54,45 @@
jsonc-parser "^2.3.0"
typescript "~3.9.2"
"@abp/ng.setting-management@~4.0.0-rc.4":
version "4.0.0-rc.4"
resolved "https://registry.yarnpkg.com/@abp/ng.setting-management/-/ng.setting-management-4.0.0-rc.4.tgz#45e7fe04a8069da19d644860617e5b5aeab39281"
integrity sha512-L7liYuB+DdWdcdJwG4OkQhBdb1bNqFO08TjfbRdkPARHje/tat+z8jtZ58yQVYOVCs9M2vJt3ydHnvZ+PQWpow==
"@abp/ng.setting-management@~4.0.0-rc.5":
version "4.0.0-rc.5"
resolved "https://registry.yarnpkg.com/@abp/ng.setting-management/-/ng.setting-management-4.0.0-rc.5.tgz#32837b5e0b49b1596dc9ebc708a14fb3250eda36"
integrity sha512-LSCoXvE22HbKrEAQ92rbvfotCL5pyZSG1wJImJg8zLOkw0i83mT7tycjDeBdixz4FkbYF4nhbuPwaz3GDbxL0Q==
dependencies:
"@abp/ng.theme.shared" "~4.0.0-rc.4"
"@abp/ng.theme.shared" "~4.0.0-rc.5"
tslib "^2.0.0"
"@abp/ng.tenant-management@~4.0.0-rc.4":
version "4.0.0-rc.4"
resolved "https://registry.yarnpkg.com/@abp/ng.tenant-management/-/ng.tenant-management-4.0.0-rc.4.tgz#0978a65423b9f56df1fd58eaaa01fd912b2875ab"
integrity sha512-K7BCZMTs/nPoPrDo/yGu3IWZjXktZHcq0qg2pvjVZRqlfbgk0m+mih01H6WC+/tjKYfdb4PtWQJ36mGR17zvuQ==
"@abp/ng.tenant-management@~4.0.0-rc.5":
version "4.0.0-rc.5"
resolved "https://registry.yarnpkg.com/@abp/ng.tenant-management/-/ng.tenant-management-4.0.0-rc.5.tgz#7090459579be7ec800990ed8e843e7a2258536d2"
integrity sha512-axBvi9XroErRTQEVvmkn3bTabH44yAqoFxuTaVjRADuBYO6DG/CREWlfDgeDtMLPQHFxCl705OMh0gU56+AFdw==
dependencies:
"@abp/ng.feature-management" "~4.0.0-rc.4"
"@abp/ng.theme.shared" "~4.0.0-rc.4"
"@abp/ng.feature-management" "~4.0.0-rc.5"
"@abp/ng.theme.shared" "~4.0.0-rc.5"
tslib "^2.0.0"
"@abp/ng.theme.basic@~4.0.0-rc.4":
version "4.0.0-rc.4"
resolved "https://registry.yarnpkg.com/@abp/ng.theme.basic/-/ng.theme.basic-4.0.0-rc.4.tgz#438d73fed9962dae3d92b0d1f87f36e3601ab720"
integrity sha512-Gunzo6N1YoGZ3crLCYqflmvnuayekyV5Aa63dwCikOTloEBmfQy4FylAcUvncNo841BwmWOk7oDGmxQmrx+5bw==
"@abp/ng.theme.basic@~4.0.0-rc.5":
version "4.0.0-rc.5"
resolved "https://registry.yarnpkg.com/@abp/ng.theme.basic/-/ng.theme.basic-4.0.0-rc.5.tgz#01e3dd7ec3d315004860e438bad7ac4459e66cb5"
integrity sha512-NHrTB5Ck7j5xeVfN3vNiom9HlcwzqF1mQc1bZUc73/wiN8LttrnnMdRuW5vclS6BbJP2p2Ziw/fq8d9hw7GJ1w==
dependencies:
"@abp/ng.theme.shared" "~4.0.0-rc.4"
"@abp/ng.theme.shared" "~4.0.0-rc.5"
tslib "^2.0.0"
"@abp/ng.theme.shared@~4.0.0-rc.4":
version "4.0.0-rc.4"
resolved "https://registry.yarnpkg.com/@abp/ng.theme.shared/-/ng.theme.shared-4.0.0-rc.4.tgz#6341acbb647c8606c1147cad4387334a42908a2c"
integrity sha512-r3V+PIJC9pCA1OD7xL6I+sJ0AFeF/wcjyUJvn7ZRvevK+eDkHf6AqTXRCNgd+VTfCqtjIpiU5oC7rSOB1gGwPw==
"@abp/ng.theme.shared@~4.0.0-rc.5":
version "4.0.0-rc.5"
resolved "https://registry.yarnpkg.com/@abp/ng.theme.shared/-/ng.theme.shared-4.0.0-rc.5.tgz#0493c98cfd378750b5a5ea8d0ea25da0fceec908"
integrity sha512-TmgKgt+yo6i3jiO05UlpIWt5nvv3pIsVHsLKaj7qXFWvQXKZxSeGiy1/9fhmdwFyV2Lq7c+FuoihDWCuGukDhw==
dependencies:
"@abp/ng.core" "~4.0.0-rc.4"
"@abp/ng.core" "~4.0.0-rc.5"
"@fortawesome/fontawesome-free" "^5.14.0"
"@ng-bootstrap/ng-bootstrap" "^7.0.0"
"@ngx-validate/core" "^0.0.12"
"@ngx-validate/core" "^0.0.13"
"@swimlane/ngx-datatable" "^17.1.0"
bootstrap "^4.5.0"
chart.js "^2.9.3"
tslib "^2.0.0"
"@abp/utils@^4.0.0-rc.3":
version "4.0.0-rc.3"
resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-4.0.0-rc.3.tgz#95a67fc0f7c929e0404d33ee2ab8571c30e7f28d"
integrity sha512-6KH7Tddx5vQQ8j3vhDlveAyDw8VSnLlTpEKaNtQmIyJ+yZ025Faa3m+MKb83jXxDE9hPi3v8QGagEOT5TQjNHg==
dependencies:
just-compare "^1.3.0"
"@abp/utils@^4.0.0-rc.4":
version "4.0.0-rc.4"
resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-4.0.0-rc.4.tgz#99573069c7dac8751e3124e60511a11a169df565"
@ -107,6 +100,13 @@
dependencies:
just-compare "^1.3.0"
"@abp/utils@^4.0.0-rc.5":
version "4.0.0-rc.5"
resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-4.0.0-rc.5.tgz#45b55d6bbf69ad2a052bc2635c039d2cc4aac401"
integrity sha512-pRYUSh30VT4702lfnQCswIwLVqNC6ZFsmydXngwZASJEJVKj30XitSlixnKhocvozG/87H0sYVzvwmUyJiHHQw==
dependencies:
just-compare "^1.3.0"
"@angular-builders/jest@^10.0.0":
version "10.0.1"
resolved "https://registry.yarnpkg.com/@angular-builders/jest/-/jest-10.0.1.tgz#a1a6fb5d11b5d54c051bdaa2012b5f046371560c"
@ -2484,13 +2484,6 @@
enhanced-resolve "5.3.1"
webpack-sources "2.0.1"
"@ngx-validate/core@^0.0.12":
version "0.0.12"
resolved "https://registry.yarnpkg.com/@ngx-validate/core/-/core-0.0.12.tgz#4924247c363e0e876e6d63794215914ac9232e8d"
integrity sha512-AhHfb44M2E2Wc37IX9DxAWjgSIZMNrzzpjnPL+VXMNJQj9GqynBjqw0zQtrGYANsAYeFPFhn7UuX6uqrkRvHtQ==
dependencies:
tslib "^1.9.0"
"@ngx-validate/core@^0.0.13":
version "0.0.13"
resolved "https://registry.yarnpkg.com/@ngx-validate/core/-/core-0.0.13.tgz#954c6d247df8107668f23a39db24ca45c274f3d9"

@ -12,10 +12,13 @@
},
"private": true,
"dependencies": {
"@abp/ng.components": "~4.0.0-rc.5",
"@abp/ng.core": "~4.0.0-rc.5",
"@abp/ng.identity": "~4.0.0-rc.5",
"@abp/ng.setting-management": "~4.0.0-rc.5",
"@abp/ng.tenant-management": "~4.0.0-rc.5",
"@abp/ng.theme.basic": "~4.0.0-rc.5",
"@abp/ng.theme.shared": "~4.0.0-rc.5",
"@angular/animations": "~11.0.0",
"@angular/common": "~11.0.0",
"@angular/compiler": "~11.0.0",
@ -29,7 +32,7 @@
"zone.js": "~0.10.2"
},
"devDependencies": {
"@abp/ng.schematics": "~3.3.1",
"@abp/ng.schematics": "~4.0.0-rc.5",
"@angular-devkit/build-angular": "~0.1100.0",
"@angular/cli": "~11.0.0",
"@angular/compiler-cli": "~11.0.0",
@ -37,7 +40,7 @@
"@types/jasmine": "~3.5.0",
"@types/jasminewd2": "~2.0.3",
"@types/node": "^12.11.1",
"codelyzer": "^6.0.0",
"codelyzer": "^6.0.1",
"jasmine-core": "~3.6.0",
"jasmine-spec-reporter": "~5.0.0",
"karma": "~5.1.1",
@ -49,6 +52,6 @@
"protractor": "~7.0.0",
"ts-node": "~8.3.0",
"tslint": "~6.1.0",
"typescript": "~4.0.2"
"typescript": "~4.0.3"
}
}
}

@ -311,7 +311,7 @@ namespace MyCompanyName.MyProjectName.IdentityServer
{
foreach (var origin in corsOrigins)
{
if (client.FindCorsOrigin(origin) == null)
if (!origin.IsNullOrWhiteSpace() && client.FindCorsOrigin(origin) == null)
{
client.AddCorsOrigin(origin);
}

@ -9,13 +9,6 @@
"Authority": "https://localhost:44303",
"RequireHttpsMetadata": "false"
},
"IdentityServer": {
"Clients": {
"MyProjectName_App": {
"ClientId": "MyProjectName_App"
}
}
},
"StringEncryption": {
"DefaultPassPhrase": "gsKnGZ041HLL4IM8"
},

File diff suppressed because it is too large Load Diff

@ -15,10 +15,13 @@
},
"private": true,
"dependencies": {
"@abp/ng.components": "~4.0.0-rc.5",
"@abp/ng.core": "~4.0.0-rc.5",
"@abp/ng.identity": "~4.0.0-rc.5",
"@abp/ng.setting-management": "~4.0.0-rc.5",
"@abp/ng.tenant-management": "~4.0.0-rc.5",
"@abp/ng.theme.basic": "~4.0.0-rc.5",
"@abp/ng.theme.shared": "~4.0.0-rc.5",
"@angular/animations": "~11.0.0",
"@angular/common": "~11.0.0",
"@angular/compiler": "~11.0.0",
@ -27,19 +30,20 @@
"@angular/platform-browser": "~11.0.0",
"@angular/platform-browser-dynamic": "~11.0.0",
"@angular/router": "~11.0.0",
"rxjs": "~6.6.3",
"rxjs": "~6.6.0",
"tslib": "^2.0.0",
"zone.js": "~0.10.2"
},
"devDependencies": {
"@abp/ng.schematics": "~3.3.1",
"@abp/ng.schematics": "~4.0.0-rc.5",
"@angular-devkit/build-angular": "~0.1100.0",
"@angular/cli": "~11.0.0",
"@angular/compiler-cli": "~11.0.0",
"@angular/language-service": "~11.0.0",
"@types/jasmine": "~3.5.0",
"@types/jasminewd2": "~2.0.3",
"@types/node": "^12.11.1",
"codelyzer": "^6.0.0",
"codelyzer": "^6.0.1",
"jasmine-core": "~3.6.0",
"jasmine-spec-reporter": "~5.0.0",
"karma": "~5.1.1",

@ -76,7 +76,7 @@ namespace MyCompanyName.MyProjectName.Blazor.Host
private static void ConfigureUI(WebAssemblyHostBuilder builder)
{
builder.RootComponents.Add<App>("app");
builder.RootComponents.Add<App>("#ApplicationContainer");
}
private static void ConfigureHttpClient(ServiceConfigurationContext context, IWebAssemblyHostEnvironment environment)

@ -7,33 +7,33 @@
<title>MyCompanyName.MyProjectName.Blazor</title>
<base href="/" />
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.12.0/css/all.css">
<link href="_content/Blazorise/blazorise.css" rel="stylesheet" />
<link href="_content/Blazorise.Bootstrap/blazorise.bootstrap.css" rel="stylesheet" />
<link href="_content/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/theme.css" rel="stylesheet" />
<link href="main.css" rel="stylesheet" />
<!--ABP:Styles-->
<link href="_content/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/libs/bootstrap/css/bootstrap.min.css" rel="stylesheet"/>
<link href="_content/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/libs/fontawesome/css/all.css" rel="stylesheet"/>
<link href="_content/Blazorise/blazorise.css" rel="stylesheet"/>
<link href="_content/Blazorise.Bootstrap/blazorise.bootstrap.css" rel="stylesheet"/>
<link href="_content/Blazorise.Snackbar/blazorise.snackbar.css" rel="stylesheet"/>
<link href="_content/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/libs/flag-icon/css/flag-icon.css" rel="stylesheet"/>
<link href="_content/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/libs/abp/css/theme.css" rel="stylesheet"/>
<link href="main.css" rel="stylesheet"/>
<!--/ABP:Styles-->
</head>
<body>
<app>Loading...</app>
<div id="blazor-error-ui">
An unhandled error has occurred.
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
<body class="abp-application-layout bg-light">
<div id="ApplicationContainer">
<div class="spinner">
<div class="double-bounce1"></div>
<div class="double-bounce2"></div>
</div>
<script src="_content/Microsoft.AspNetCore.Components.WebAssembly.Authentication/AuthenticationService.js"></script>
<script src="_framework/blazor.webassembly.js"></script>
</div>
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>
<!--ABP:Scripts-->
<script src="_content/Volo.Abp.AspNetCore.Components.WebAssembly/libs/abp/js/abp.js"></script>
<script src="_content/Blazorise/blazorise.js"></script>
<script src="_content/Blazorise.Bootstrap/blazorise.bootstrap.js"></script>
<script src="_content/Microsoft.AspNetCore.Components.WebAssembly.Authentication/AuthenticationService.js"></script>
<script src="_framework/blazor.webassembly.js"></script>
<!--/ABP:Scripts-->
<script src="_content/Blazorise/blazorise.js"></script>
<script src="_content/Blazorise.Bootstrap/blazorise.bootstrap.js"></script>
<script src="_content/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/theme.js"></script>
</body>
</html>

@ -1,18 +1,48 @@
#blazor-error-ui {
background: lightyellow;
bottom: 0;
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
display: none;
left: 0;
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
.spinner {
width: 40px;
height: 40px;
display: block;
position: fixed;
width: 100%;
z-index: 1000;
top: calc( 50% - ( 40px / 2) );
right: calc( 50% - ( 40px / 2) );
}
#blazor-error-ui .dismiss {
cursor: pointer;
.double-bounce1, .double-bounce2 {
width: 100%;
height: 100%;
border-radius: 50%;
background-color: #333;
opacity: 0.6;
position: absolute;
right: 0.75rem;
top: 0.5rem;
}
top: 0;
left: 0;
-webkit-animation: sk-bounce 2.0s infinite ease-in-out;
animation: sk-bounce 2.0s infinite ease-in-out;
}
.double-bounce2 {
-webkit-animation-delay: -1.0s;
animation-delay: -1.0s;
}
@-webkit-keyframes sk-bounce {
0%, 100% {
-webkit-transform: scale(0.0)
}
50% {
-webkit-transform: scale(1.0)
}
}
@keyframes sk-bounce {
0%, 100% {
transform: scale(0.0);
-webkit-transform: scale(0.0);
}
50% {
transform: scale(1.0);
-webkit-transform: scale(1.0);
}
}

@ -311,7 +311,7 @@ namespace MyCompanyName.MyProjectName.IdentityServer
{
foreach (var origin in corsOrigins)
{
if (client.FindCorsOrigin(origin) == null)
if (!origin.IsNullOrWhiteSpace() && client.FindCorsOrigin(origin) == null)
{
client.AddCorsOrigin(origin);
}

Loading…
Cancel
Save