diff --git a/.gitignore b/.gitignore
index 2f820efc4d..f3f48c5963 100644
--- a/.gitignore
+++ b/.gitignore
@@ -271,6 +271,7 @@ modules/blogging/app/Volo.BloggingTestApp/wwwroot/files/*.*
modules/docs/app/VoloDocs.Web/Logs/*.*
modules/setting-management/app/Volo.Abp.SettingManagement.DemoApp/Logs/*.*
templates/module/app/MyCompanyName.MyProjectName.DemoApp/Logs/*.*
+templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Logs/logs.txt
templates/mvc/src/MyCompanyName.MyProjectName.Web/Logs/*.*
@@ -313,4 +314,4 @@ abp-build-config.json
tempkey.jwk
# ABP Analyzing files
-*.abppkg.analyze.json
\ No newline at end of file
+*.abppkg.analyze.json
diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en-GB.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en-GB.json
index 0694811b78..af20ee9ba7 100644
--- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en-GB.json
+++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en-GB.json
@@ -100,6 +100,7 @@
"VolosoftSharingInformationMessage": "I would like Volosoft to share my information with select partners so I can receive relevant information about their products and services.",
"WeWillSendYouADownloadLink": "We've sent the file to {0}.",
"InvalidFormInputs": "Please, type the valid information specified on the form.",
- "DDDBookEmailBody": "Thank you.
To download your book, click here."
+ "DDDBookEmailBody": "Thank you.
To download your book, click here.",
+ "FreeDDDEBook": "Free DDD E-Book"
}
}
\ No newline at end of file
diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json
index d7f9f3c651..58e4b561d6 100644
--- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json
+++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json
@@ -357,6 +357,7 @@
"VolosoftSharingInformationMessage": "I would like Volosoft to share my information with select partners so I can receive relevant information about their products and services.",
"WeWillSendYouADownloadLink": "A link to download the e-book has been sent to {0}.
Check your inbox / junk / spam boxes!",
"InvalidFormInputs": "Please, type the valid information specified on the form.",
- "DDDBookEmailBody": "Thank you.
To download your book, click here."
+ "DDDBookEmailBody": "Thank you.
To download your book, click here.",
+ "FreeDDDEBook": "Free DDD E-Book"
}
}
diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/tr.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/tr.json
index 76d6fc7c57..c07678b890 100644
--- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/tr.json
+++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/tr.json
@@ -97,6 +97,7 @@
"VolosoftSharingInformationMessage": "Volosoft'un ürünlerimi seçkin ortaklarla paylaşmasını kabul ediyorum, böylece ilgili ortakların ürünleri ve hizmetleri hakkında ilgili bilgileri alabilirim.",
"WeWillSendYouADownloadLink": "{0} adresine email gönderilmiştir.",
"InvalidFormInputs": "Lütfen formda belirtilen geçerli bilgileri yazınız.",
- "DDDBookEmailBody": "Teşekkürler.
Kitabı indirmek için, buraya tıklayınız."
+ "DDDBookEmailBody": "Teşekkürler.
Kitabı indirmek için, buraya tıklayınız.",
+ "FreeDDDEBook": "Ücretsiz DDD E-Kitap"
}
}
\ No newline at end of file
diff --git a/docs/en/CLI.md b/docs/en/CLI.md
index f2e7df219b..829b9480c7 100644
--- a/docs/en/CLI.md
+++ b/docs/en/CLI.md
@@ -42,6 +42,7 @@ Here, the list of all available commands before explaining their details:
* **`login`**: Authenticates on your computer with your [abp.io](https://abp.io/) username and password.
* **`logout`**: Logouts from your computer if you've authenticated before.
* **`bundle`**: Generates script and style references for an ABP Blazor project.
+* **`install-libs`**: Install NPM Packages for MVC / Razor Pages and Blazor Server UI types.
### help
@@ -433,3 +434,18 @@ abp bundle [options]
`bundle` command reads the `appsettings.json` file inside the Blazor project for bundling options. For more details about managing style and script references in Blazor apps, see [Managing Global Scripts & Styles](UI/Blazor/Global-Scripts-Styles.md)
+### install-libs
+
+This command install NPM Packages for MVC / Razor Pages and Blazor Server UI types. Its **executing directory** or passed ```--working-directory``` parameter's directory must contain a project file(*.csproj).
+
+`install-libs` command reads the `abp.resourcemapping.js` file to manage package. For more details see [Client Side Package Management](UI/AspNetCore/Client-Side-Package-Management.md).
+
+Usage:
+
+````bash
+abp install-libs [options]
+````
+
+#### Options
+
+* ```--working-directory``` or ```-wd```: Specifies the working directory. This option is useful when executing directory doesn't contain a project file.
\ No newline at end of file
diff --git a/docs/en/Community-Articles/2020-04-27-Use-Azure-Active-Directory-Authentication-for-MVC-Razor-Page-Applications/POST.md b/docs/en/Community-Articles/2020-04-27-Use-Azure-Active-Directory-Authentication-for-MVC-Razor-Page-Applications/POST.md
index 146d250d1f..b2c307bcb3 100644
--- a/docs/en/Community-Articles/2020-04-27-Use-Azure-Active-Directory-Authentication-for-MVC-Razor-Page-Applications/POST.md
+++ b/docs/en/Community-Articles/2020-04-27-Use-Azure-Active-Directory-Authentication-for-MVC-Razor-Page-Applications/POST.md
@@ -6,23 +6,21 @@ Adding Azure Active Directory is pretty straightforward in ABP framework. Couple
Two different **alternative approaches** for AzureAD integration will be demonstrated for better coverage.
-1. ~~**AddAzureAD**: This approach uses Microsoft [AzureAD UI nuget package](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.AzureAD.UI/) which is very popular when users search the web about how to integrate AzureAD to their web application.~~ Now marked **Obsolete** (see https://github.com/aspnet/Announcements/issues/439).
-2. **AddOpenIdConnect**: This approach uses default [OpenIdConnect](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.OpenIdConnect/) which can be used for not only AzureAD but for all OpenId connections.
+1. **AddOpenIdConnect**: This approach uses default [OpenIdConnect](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.OpenIdConnect/) which can be used for not only AzureAD but for all OpenId connections.
3. **AddMicrosoftIdentityWebAppAuthentication:** This approach uses newly introduced [Microsoft.Identity.Web nuget package](https://www.nuget.org/packages/Microsoft.Identity.Web/) to replace AddAzureAD.
+3. ~~**AddAzureAD**: This approach uses Microsoft [AzureAD UI nuget package](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.AzureAD.UI/) which is very popular when users search the web about how to integrate AzureAD to their web application.~~ Now marked **Obsolete** (see https://github.com/aspnet/Announcements/issues/439).
> There is **no difference** in functionality between these approaches. AddAzureAD is an abstracted way of OpenIdConnection ([source](https://github.com/dotnet/aspnetcore/blob/c56aa320c32ee5429d60647782c91d53ac765865/src/Azure/AzureAD/Authentication.AzureAD.UI/src/AzureADAuthenticationBuilderExtensions.cs#L122)) with predefined cookie settings.
>
> However there are key differences in integration to ABP applications because of default configurated signin schemes which will be explained below.
-## 1. AddAzureAD
+## 1. AddOpenIdConnect
-This approach uses the most common way to integrate AzureAD by using the [Microsoft AzureAD UI nuget package](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.AzureAD.UI/).
-
-If you choose this approach, you will need to install `Microsoft.AspNetCore.Authentication.AzureAD.UI` package to your **.Web** project. Also, since AddAzureAD extension uses [configuration binding](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-3.1#default-configuration), you need to update your appsettings.json file located in your **.Web** project.
+If you don't want to use an extra nuget package in your application, you can use the straight default [OpenIdConnect](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.OpenIdConnect/) which can be used for all OpenId connections including AzureAD external authentication.
-#### **Updating `appsettings.json`**
+You don't have to use `appsettings.json` configuration but it is a good practice to set AzureAD information in the `appsettings.json`.
-You need to add a new section to your `appsettings.json` which will be binded to configuration when configuring the `OpenIdConnectOptions`:
+To get the AzureAD information from `appsettings.json`, which will be used in `OpenIdConnectOptions` configuration, simply add a new section to `appsettings.json` located in your **.Web** project:
````json
"AzureAd": {
@@ -34,63 +32,90 @@ You need to add a new section to your `appsettings.json` which will be binded to
}
````
-> Important configuration here is the CallbackPath. This value must be the same with one of your Azure AD-> app registrations-> Authentication -> RedirectUri.
-
-Then, you need to configure the `OpenIdConnectOptions` to complete the integration.
-
-#### Configuring OpenIdConnectOptions
-
-In your **.Web** project, locate your **ApplicationWebModule** and modify `ConfigureAuthentication` method with the following:
+Then, In your **.Web** project; you can modify the `ConfigureAuthentication` method located in your **ApplicationWebModule** with the following:
````csharp
private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration)
{
- JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
- JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier);
context.Services.AddAuthentication()
- .AddIdentityServerAuthentication(options =>
- {
- options.Authority = configuration["AuthServer:Authority"];
+ ... //Omitted other third party configurations
+ .AddOpenIdConnect("AzureOpenId", "Azure Active Directory OpenId", options =>
+ {
+ options.Authority = "https://login.microsoftonline.com/" + configuration["AzureAd:TenantId"] + "/v2.0/";
+ options.ClientId = configuration["AzureAd:ClientId"];
+ options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
+ options.CallbackPath = configuration["AzureAd:CallbackPath"];
+ options.ClientSecret = configuration["AzureAd:ClientSecret"];
options.RequireHttpsMetadata = false;
- options.ApiName = "Acme.BookStore";
- })
- .AddAzureAD(options => configuration.Bind("AzureAd", options));
+ options.SaveTokens = true;
+ options.GetClaimsFromUserInfoEndpoint = true;
+ options.Scope.Add("email");
- context.Services.Configure(AzureADDefaults.OpenIdScheme, options =>
+ options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "sub");
+ });
+ }
+````
+
+> **Don't forget to:**
+>
+> * Add `options.Scope.Add("email");` since [default signin scheme is `AzureADOpenID`](https://github.com/dotnet/aspnetcore/blob/c56aa320c32ee5429d60647782c91d53ac765865/src/Azure/AzureAD/Authentication.AzureAD.UI/src/AzureADOpenIdConnectOptionsConfiguration.cs#L35).
+> * Add `options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "sub");`. Mapping this to [ClaimTypes.NameIdentifier](https://github.com/dotnet/runtime/blob/6d395de48ac718a913e567ae80961050f2a9a4fa/src/libraries/System.Security.Claims/src/System/Security/Claims/ClaimTypes.cs#L59) is important since default SignIn Manager behavior uses this claim type for external login information.
+
+
+
+And that's it, integration is completed. Keep on mind that you can connect any other external authentication providers.
+
+## 2. Alternative Approach: AddMicrosoftIdentityWebApp
+
+With .Net 5.0, AzureAd is marked [obsolete](https://github.com/dotnet/aspnetcore/issues/25807) and will not be supported in the near future. However its expanded functionality is available in [microsoft-identity-web](https://github.com/AzureAD/microsoft-identity-web/wiki) packages.
+
+Add (or replace with) the new nuget package Microsoft.Identity.Web nuget package](https://www.nuget.org/packages/Microsoft.Identity.Web/).
+
+In your **.Web** project; you update the `ConfigureAuthentication` method located in your **ApplicationWebModule** with the following while having the AzureAd appsettings section as defined before:
+
+````csharp
+private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration)
+ {
+ context.Services.AddAuthentication()
+ ... //Omitted other third party configurations
+ .AddMicrosoftIdentityWebApp(configuration.GetSection("AzureAd"));
+
+ context.Services.Configure(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
- options.Authority = options.Authority + "/v2.0/";
+ options.Authority = "https://login.microsoftonline.com/" + configuration["AzureAd:TenantId"] + "/v2.0/";
options.ClientId = configuration["AzureAd:ClientId"];
- options.CallbackPath = configuration["AzureAd:CallbackPath"];
options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
+ options.CallbackPath = configuration["AzureAd:CallbackPath"];
+ options.ClientSecret = configuration["AzureAd:ClientSecret"];
options.RequireHttpsMetadata = false;
-
- options.TokenValidationParameters.ValidateIssuer = false;
+ options.SaveTokens = false;
options.GetClaimsFromUserInfoEndpoint = true;
- options.SaveTokens = true;
- options.SignInScheme = IdentityConstants.ExternalScheme;
- options.Scope.Add("email");
+ options.SignInScheme = IdentityConstants.ExternalScheme;
+
+ options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "sub");
});
}
````
+And that's all to add new Microsoft-Identity-Web.
+
> **Don't forget to:**
>
-> * Add `.AddAzureAD(options => configuration.Bind("AzureAd", options))` after `.AddAuthentication()`. This binds your AzureAD appsettings and easy to miss out.
-> * Add `JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear()`. This will disable the default Microsoft claim type mapping.
-> * Add `JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier)`. Mapping this to [ClaimTypes.NameIdentifier](https://github.com/dotnet/runtime/blob/6d395de48ac718a913e567ae80961050f2a9a4fa/src/libraries/System.Security.Claims/src/System/Security/Claims/ClaimTypes.cs#L59) is important since default SignIn Manager behavior uses this claim type for external login information.
> * Add `options.SignInScheme = IdentityConstants.ExternalScheme` since [default signin scheme is `AzureADOpenID`](https://github.com/dotnet/aspnetcore/blob/c56aa320c32ee5429d60647782c91d53ac765865/src/Azure/AzureAD/Authentication.AzureAD.UI/src/AzureADOpenIdConnectOptionsConfiguration.cs#L35).
-> * Add `options.Scope.Add("email")` if you are using **v2.0** endpoint of AzureAD since v2.0 endpoint doesn't return the `email` claim as default. The [Account Module](https://docs.abp.io/en/abp/latest/Modules/Account) uses `email` claim to [register external users](https://github.com/abpframework/abp/blob/be32a55449e270d2d456df3dabdc91f3ffdd4fa9/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs#L215).
+> * Add `options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "sub");`. Mapping this to [ClaimTypes.NameIdentifier](https://github.com/dotnet/runtime/blob/6d395de48ac718a913e567ae80961050f2a9a4fa/src/libraries/System.Security.Claims/src/System/Security/Claims/ClaimTypes.cs#L59) is important since default SignIn Manager behavior uses this claim type for external login information.
-You are done and integration is completed.
+Keep in mind that [Microsoft-Identity-Web](https://github.com/AzureAD/microsoft-identity-web) is relatively new and keeps getting new enhancements, features and documentation.
-## 2. Alternative Approach: AddOpenIdConnect
+## 3. Obsolete Alternative Approach: AddAzureAD
-If you don't want to use an extra nuget package in your application, you can use the straight default [OpenIdConnect](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.OpenIdConnect/) which can be used for all OpenId connections including AzureAD external authentication.
+This approach uses the most common way to integrate AzureAD by using the [Microsoft AzureAD UI nuget package](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.AzureAD.UI/).
-You don't have to use `appsettings.json` configuration but it is a good practice to set AzureAD information in the `appsettings.json`.
+If you choose this approach, you will need to install `Microsoft.AspNetCore.Authentication.AzureAD.UI` package to your **.Web** project. Also, since AddAzureAD extension uses [configuration binding](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-3.1#default-configuration), you need to update your appsettings.json file located in your **.Web** project.
-To get the AzureAD information from `appsettings.json`, which will be used in `OpenIdConnectOptions` configuration, simply add a new section to `appsettings.json` located in your **.Web** project:
+#### **Updating `appsettings.json`**
+
+You need to add a new section to your `appsettings.json` which will be binded to configuration when configuring the `OpenIdConnectOptions`:
````json
"AzureAd": {
@@ -102,74 +127,49 @@ To get the AzureAD information from `appsettings.json`, which will be used in `O
}
````
-Then, In your **.Web** project; you can modify the `ConfigureAuthentication` method located in your **ApplicationWebModule** with the following:
-
-````csharp
-private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration)
- {
- JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
- JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier);
-
- context.Services.AddAuthentication()
- .AddIdentityServerAuthentication(options =>
- {
- options.Authority = configuration["AuthServer:Authority"];
- options.RequireHttpsMetadata = false;
- options.ApiName = "BookStore";
- })
- .AddOpenIdConnect("AzureOpenId", "Azure Active Directory OpenId", options =>
- {
- options.Authority = "https://login.microsoftonline.com/" + configuration["AzureAd:TenantId"] + "/v2.0/";
- options.ClientId = configuration["AzureAd:ClientId"];
- options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
- options.CallbackPath = configuration["AzureAd:CallbackPath"];
- options.RequireHttpsMetadata = false;
- options.SaveTokens = true;
- options.GetClaimsFromUserInfoEndpoint = true;
-
- options.Scope.Add("email");
- });
- }
-````
-
-And that's it, integration is completed. Keep on mind that you can connect any other external authentication providers.
-
-## 3. AddMicrosoftIdentityWebAppAuthentication
+> Important configuration here is the CallbackPath. This value must be the same with one of your Azure AD-> app registrations-> Authentication -> RedirectUri.
-With .Net 5.0, AzureAd is marked [obsolete](https://github.com/dotnet/aspnetcore/issues/25807) and will not be supported in the near future. However its expanded functionality is available in [microsoft-identity-web](https://github.com/AzureAD/microsoft-identity-web/wiki) packages.
+Then, you need to configure the `OpenIdConnectOptions` to complete the integration.
-Add (or replace with) the new nuget package Microsoft.Identity.Web nuget package](https://www.nuget.org/packages/Microsoft.Identity.Web/).
+#### Configuring OpenIdConnectOptions
-In your **.Web** project; you update the `ConfigureAuthentication` method located in your **ApplicationWebModule** with the following while having the AzureAd appsettings section as defined before:
+In your **.Web** project, locate your **ApplicationWebModule** and modify `ConfigureAuthentication` method with the following:
````csharp
private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration)
{
- JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
- JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier);
context.Services.AddAuthentication()
- .AddIdentityServerAuthentication(options =>
- {
- options.Authority = configuration["AuthServer:Authority"];
- options.RequireHttpsMetadata = false;
- options.ApiName = "Acme.BookStore";
- });
-
- context.Services.AddMicrosoftIdentityWebAppAuthentication(
- configuration: configuration,
- configSectionName: "AzureAd",
- openIdConnectScheme:"AzureAD",
- cookieScheme:null);
+ ... //Omitted other third party configurations
+ .AddAzureAD(options => configuration.Bind("AzureAd", options));
+
+ context.Services.Configure(AzureADDefaults.OpenIdScheme, options =>
+ {
+ options.Authority = options.Authority + "/v2.0/";
+ options.ClientId = configuration["AzureAd:ClientId"];
+ options.CallbackPath = configuration["AzureAd:CallbackPath"];
+ options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
+ options.RequireHttpsMetadata = false;
+
+ options.TokenValidationParameters.ValidateIssuer = false;
+ options.GetClaimsFromUserInfoEndpoint = true;
+ options.SaveTokens = true;
+ options.SignInScheme = IdentityConstants.ExternalScheme;
+
+ options.Scope.Add("email");
+
+ options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "sub");
+ });
}
````
-And that's all to add new Microsoft-Identity-Web.
-
> **Don't forget to:**
>
-> * Pass **cookieScheme** parameter as **null** or your [*GetExternalLoginInfoAsync* method will always return null](https://github.com/AzureAD/microsoft-identity-web/issues/133#).
+> * Add `.AddAzureAD(options => configuration.Bind("AzureAd", options))` after `.AddAuthentication()`. This binds your AzureAD appsettings and easy to miss out.
+> * Add `options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "sub");`. Mapping this to [ClaimTypes.NameIdentifier](https://github.com/dotnet/runtime/blob/6d395de48ac718a913e567ae80961050f2a9a4fa/src/libraries/System.Security.Claims/src/System/Security/Claims/ClaimTypes.cs#L59) is important since default SignIn Manager behavior uses this claim type for external login information.
+> * Add `options.SignInScheme = IdentityConstants.ExternalScheme` since [default signin scheme is `AzureADOpenID`](https://github.com/dotnet/aspnetcore/blob/c56aa320c32ee5429d60647782c91d53ac765865/src/Azure/AzureAD/Authentication.AzureAD.UI/src/AzureADOpenIdConnectOptionsConfiguration.cs#L35).
+> * Add `options.Scope.Add("email")` if you are using **v2.0** endpoint of AzureAD since v2.0 endpoint doesn't return the `email` claim as default. The [Account Module](https://docs.abp.io/en/abp/latest/Modules/Account) uses `email` claim to [register external users](https://github.com/abpframework/abp/blob/be32a55449e270d2d456df3dabdc91f3ffdd4fa9/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs#L215).
-Keep in mind that [Microsoft-Identity-Web](https://github.com/AzureAD/microsoft-identity-web) is relatively new and keeps getting new enhancements, features and documentation.
+You are done and integration is completed.
## The Source Code
@@ -247,3 +247,14 @@ You can find the source code of the completed example [here](https://github.com/
await Task.CompletedTask;
});
````
+
+* I get page not found error on redirection to **https://login.live.com/oauth20_authorize.srf?**!
+
+ * Probably you are trying to login with Microsoft account to Azure portal instead of the Azure AD account. Try azure AD user account for login instead of microsoft account. You can also check the answers for [this question](https://answers.microsoft.com/en-us/msoffice/forum/msoffice_o365admin-mso_dirservices-mso_o365b/cant-login-in-loginlivecom-with-a-ms-account-valid/6da991e6-9528-461a-9638-9c5680e95888) and [this question](https://docs.microsoft.com/en-us/answers/questions/34806/azure-ad-404-error-when-login-with-microsoft-accou.html) for more details.
+
+# May 2021 Update
+
+- **AddOpenIdConnect**: Removed `JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();` and added `sub` claim mapping in ClaimActions rather than global mapping.
+- Updated `AddMicrosoftIdentityWebAppAuthentication ` to `AddMicrosoftIdentityWebApp`.
+- Updated OpenIdConnect and AddMicrosoftIdentityWebApp configurations.
+- Obsolete approach moved to third place in the list.
diff --git a/docs/en/Community-Articles/2020-09-16-How-to-Setup-Azure-Active-Directory-and-Integrate-Abp-Angular-Application/POST.md b/docs/en/Community-Articles/2020-09-16-How-to-Setup-Azure-Active-Directory-and-Integrate-Abp-Angular-Application/POST.md
index c7f4f40917..a1c63529a2 100644
--- a/docs/en/Community-Articles/2020-09-16-How-to-Setup-Azure-Active-Directory-and-Integrate-Abp-Angular-Application/POST.md
+++ b/docs/en/Community-Articles/2020-09-16-How-to-Setup-Azure-Active-Directory-and-Integrate-Abp-Angular-Application/POST.md
@@ -23,20 +23,9 @@ This means your application will be using AzureAD user store for authentication.
Lets start with adding OpenId connection. Open the **HttpApiHostModule.cs** and update the **ConfigureAuthentication** method as below:
```csharp
-JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
-JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier);
context.Services.AddAuthentication()
- .AddIdentityServerAuthentication(options =>
- {
- options.Authority = configuration["AuthServer:Authority"];
- options.RequireHttpsMetadata = false;
- options.ApiName = "NonTieredAngular";
- options.JwtBackChannelHandler = new HttpClientHandler()
- {
- ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
- };
- })
+ ... //Omitted other third party configurations
.AddOpenIdConnect("AzureOpenId", "Azure AD OpenId", options =>
{
options.Authority = "https://login.microsoftonline.com/" + configuration["AzureAd:TenantId"] + "/v2.0/";
@@ -48,6 +37,8 @@ context.Services.AddAuthentication()
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("email");
+
+ options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "sub");
});
```
@@ -58,10 +49,7 @@ public override void ConfigureServices(ServiceConfigurationContext context)
{
var hostingEnvironment = context.Services.GetHostingEnvironment();
var configuration = context.Services.GetConfiguration();
-
- JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
- JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier);
-
+
context.Services.AddAuthentication()
.AddOpenIdConnect("AzureOpenId", "Azure AD OpenId", options =>
{
@@ -120,3 +108,8 @@ Next time you hit login, you should be seeing login screen enabled Azure AD like
* But I don't want my users to see default login screen. I want my users to login **only** from AzureAD.
* You can **mimic** this behaviour by customizing the login page and instantly trigger Azure AD provider click. For more info, you can check [this article](https://community.abp.io/articles/how-to-customize-the-login-page-for-mvc-razor-page-applications-9a40f3cd).
+
+# May 2021 Update
+
+- **AddOpenIdConnect**: Removed `JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();` and added `sub` claim mapping in ClaimActions rather than global mapping.
+- Updated OpenIdConnect configurations.
diff --git a/docs/en/Modules/Setting-Management.md b/docs/en/Modules/Setting-Management.md
index dff8da3f8e..255efc9a5f 100644
--- a/docs/en/Modules/Setting-Management.md
+++ b/docs/en/Modules/Setting-Management.md
@@ -83,4 +83,192 @@ Setting Management module is extensible, just like the [setting system](../Setti
* `TenantSettingManagementProvider`: Gets or sets the setting value for a tenant.
* `UserSettingManagementProvider`: Gets the setting value for a user.
-`ISettingManager` uses the setting management providers on get/set methods. Typically, every setting management provider defines extension methods on the `ISettingManagement` service (like `SetForUserAsync` defined by the user setting management provider).
\ No newline at end of file
+`ISettingManager` uses the setting management providers on get/set methods. Typically, every setting management provider defines extension methods on the `ISettingManagement` service (like `SetForUserAsync` defined by the user setting management provider).
+
+## Setting Management UI
+
+Setting Mangement module provided the email setting UI by default, and it is extensible; You can add your tabs to this page for your application settings.
+
+### MVC UI
+
+#### Create a setting View Component
+
+Create `MySettingGroup` folder under the `Components` folder. Add a new view component. Name it as `MySettingGroupViewComponent`:
+
+
+
+Open the `MySettingGroupViewComponent.cs` and change the whole content as shown below:
+
+```csharp
+public class MySettingGroupViewComponent : AbpViewComponent
+{
+ public virtual IViewComponentResult Invoke()
+ {
+ return View("~/Components/MySettingGroup/Default.cshtml");
+ }
+}
+```
+
+> You can also use the `InvokeAsync` method, In this example, we use the `Invoke` method.
+
+#### Default.cshtml
+
+Create a `Default.cshtml` file under the `MySettingGroup` folder.
+
+Open the `Default.cshtml` and change the whole content as shown below:
+
+```html
+
+
My setting group page
+
+```
+
+#### BookStoreSettingPageContributor
+
+Create a `BookStoreSettingPageContributor.cs` file under the `Settings` folder:
+
+
+
+The content of the file is shown below:
+
+```csharp
+public class BookStoreSettingPageContributor : ISettingPageContributor
+{
+ public Task ConfigureAsync(SettingPageCreationContext context)
+ {
+ context.Groups.Add(
+ new SettingPageGroup(
+ "Volo.Abp.MySettingGroup",
+ "MySettingGroup",
+ typeof(MySettingGroupViewComponent)
+ )
+ );
+
+ return Task.CompletedTask;
+ }
+
+ public Task CheckPermissionsAsync(SettingPageCreationContext context)
+ {
+ // You can check the permissions here
+ return Task.FromResult(true);
+ }
+}
+```
+
+Open the `BookStoreWebModule.cs` file and add the following code:
+
+```csharp
+Configure(options =>
+{
+ options.Contributors.Add(new BookStoreSettingPageContributor());
+});
+```
+
+#### Run the Application
+
+Navigate to `/SettingManagement` route to see the changes:
+
+
+
+### Blazor UI
+
+#### Create a Razor Component
+
+Create `MySettingGroup` folder under the `Pages` folder. Add a new razor component. Name it as `MySettingGroupComponent`:
+
+
+
+Open the `MySettingGroupComponent.razor` and change the whole content as shown below:
+
+```csharp
+
+ my setting group
+
+```
+
+#### BookStoreSettingComponentContributor
+
+Create a `BookStoreSettingComponentContributor.cs` file under the `Settings` folder:
+
+
+
+The content of the file is shown below:
+
+```csharp
+public class BookStoreSettingComponentContributor : ISettingComponentContributor
+{
+ public Task ConfigureAsync(SettingComponentCreationContext context)
+ {
+ context.Groups.Add(
+ new SettingComponentGroup(
+ "Volo.Abp.MySettingGroup",
+ "MySettingGroup",
+ typeof(MySettingGroupComponent)
+ )
+ );
+
+ return Task.CompletedTask;
+ }
+
+ public Task CheckPermissionsAsync(SettingComponentCreationContext context)
+ {
+ // You can check the permissions here
+ return Task.FromResult(true);
+ }
+}
+```
+
+Open the `BookStoreBlazorModule.cs` file and add the following code:
+
+```csharp
+Configure(options =>
+{
+ options.Contributors.Add(new BookStoreSettingComponentContributor());
+});
+```
+
+#### Run the Application
+
+Navigate to `/setting-management` route to see the changes:
+
+
+
+### Angular UI
+
+#### Create a Component
+
+Create a component with the following command:
+
+```bash
+yarn ng generate component my-settings
+```
+
+Open the `app.component.ts` and modify the file as shown below:
+
+```js
+import { Component } from '@angular/core';
+import { SettingTabsService } from '@abp/ng.core'; // imported SettingTabsService
+import { MySettingsComponent } from './my-settings/my-settings.component'; // imported MySettingsComponent
+
+@Component(/* component metadata */)
+export class AppComponent {
+ constructor(private settingTabs: SettingTabsService) // injected MySettingsComponent
+ {
+ // added below
+ settingTabs.add([
+ {
+ name: 'MySettings',
+ order: 1,
+ requiredPolicy: 'policy key here',
+ component: MySettingsComponent,
+ },
+ ]);
+ }
+}
+```
+
+#### Run the Application
+
+Navigate to `/setting-management` route to see the changes:
+
+
\ No newline at end of file
diff --git a/docs/en/Modules/Tenant-Management.md b/docs/en/Modules/Tenant-Management.md
index 73e16aee8e..dca6ea3d1e 100644
--- a/docs/en/Modules/Tenant-Management.md
+++ b/docs/en/Modules/Tenant-Management.md
@@ -1,3 +1,107 @@
# Tenant Management Module
-TODO
\ No newline at end of file
+[Multi-Tenancy](../Multi-Tenancy.md) is one of the core features of ABP Framework. It provides the fundamental infrastructure to build your own SaaS (Software-as-a-Service) solution. ABP's multi-tenancy system abstracts where your tenants are stored, by providing the `ITenantStore` interface. All you need to do is to implement that interface.
+
+**The Tenant Management module is an implementation of the the `ITenantStore` interface. It stores tenants in a database. It also provides UI to manage your tenants and their [features](../Features.md).**
+
+> Please **refer to the [Multi-Tenancy](../Multi-Tenancy.md) documentation** to understand the multi-tenancy system of the ABP Framework. This document focuses on the Tenant Management module.
+
+### About the ABP Commercial SaaS Module
+
+The [SaaS Module](https://commercial.abp.io/modules/Volo.Saas) is an alternative implementation of this module with more features and possibilities. It is distributed as a part of the [ABP Commercial](https://commercial.abp.io/) subscription.
+
+## How to Install
+
+This module comes as pre-installed (as [NuGet/NPM packages](NuGet/NPM packages)) when you [create a new solution](https://abp.io/get-started) with the ABP Framework. You can continue to use it as package and get updates easily, or you can include its source code into your solution (see `get-source` [CLI](../CLI.md) command) to develop your custom module.
+
+### The Source Code
+
+The source code of this module can be accessed [here](https://github.com/abpframework/abp/tree/dev/modules/tenant-management). The source code is licensed with [MIT](https://choosealicense.com/licenses/mit/), so you can freely use and customize it.
+
+## User Interface
+
+This module adds "*Administration -> Tenant Management -> Tenants*" menu item to the main menu of the application, which opens the page shown below:
+
+
+
+In this page, you see the all the tenants. You can create a new tenant as shown below:
+
+
+
+In this modal;
+
+* **Name**: The unique name of the tenant. If you use subdomains for your tenants (like https://some-tenant.your-domain.com), this will be the subdomain name.
+* **Admin Email Address**: Email address of the admin user for this tenant.
+* **Admin Password**: The password of the admin user for this tenant.
+
+When you click to *Actions* button near to a tenant, you will see the actions you can take:
+
+
+
+### Managing the Tenant Features
+
+The Features action opens a modal to enable/disable/set [features](../Features.md) for the related tenant. Here, an example modal:
+
+
+
+### Managing the Host Features
+
+*Manage Host features* button is used to set features for the host side, if you use the features of your application also in the host side.
+
+## Internals
+
+This section can be used as a reference if you want to [customize](../Customizing-Application-Modules-Guide.md) this module without changing [its source code](https://github.com/abpframework/abp/tree/dev/modules/tenant-management).
+
+### Domain Layer
+
+#### Aggregates
+
+* `Tenant`
+
+#### Repositories
+
+* `ITenantRepository`
+
+#### Domain Services
+
+* `TenantManager`
+
+### Application Layer
+
+#### Application Services
+
+* `TenantAppService`
+
+#### Permissions
+
+- `AbpTenantManagement.Tenants`: Tenant management.
+- `AbpTenantManagement.Tenants.Create`: Creating a new tenant.
+- `AbpTenantManagement.Tenants.Update`: Editing an existing tenant.
+- `AbpTenantManagement.Tenants.Delete`: Deleting an existing tenant.
+- `AbpTenantManagement.Tenants.ManageFeatures`: Manage features of the tenants.
+
+### Entity Framework Core Integration
+
+* `TenantManagementDbContext` (implements `ITenantManagementDbContext`)
+
+**Database Tables:**
+
+* `AbpTenants`
+* `AbpTenantConnectionStrings`
+
+### MongoDB Integration
+
+* `TenantManagementMongoDbContext` (implements `ITenantManagementMongoDbContext`)
+
+**Database Collections:**
+
+* `AbpTenants` (also includes the connection string)
+
+## Notices
+
+ABP Framework allows to use *database per tenant* approach that allows a tenant can have a dedicated database. This module has the fundamental infrastructure to make that implementation possible (see its source code), however it doesn't implement the application layer and UI functionalities to provide it as an out of the box implementation. You can implement these features yourself, or consider to use the [ABP Commercial Saas Module](https://docs.abp.io/en/commercial/latest/modules/saas) that fully implements it and provides much more business features.
+
+## See Also
+
+* [Multi-Tenancy](../Multi-Tenancy.md)
+* [ABP Commercial SaaS Module](https://docs.abp.io/en/commercial/latest/modules/saas)
\ No newline at end of file
diff --git a/docs/en/Tutorials/Todo/Index.md b/docs/en/Tutorials/Todo/Index.md
index e71749544e..64cbca13f3 100644
--- a/docs/en/Tutorials/Todo/Index.md
+++ b/docs/en/Tutorials/Todo/Index.md
@@ -135,7 +135,7 @@ namespace TodoApp
}
````
-`BasicAggregateRoot` is one the simplest base class to create root entities, and `Guid` is the primary key (`Id`) of the entity here.
+`BasicAggregateRoot` is the simplest base class to create root entities, and `Guid` is the primary key (`Id`) of the entity here.
## Database Integration
@@ -214,7 +214,7 @@ Now, we can use ABP repositories to save and retrieve todo items, as we'll do in
## Application Layer
-An [Application Service](../../Application-Services.md) is used to perform use cases of the application. We need to perform the following use cases;
+An [Application Service](../../Application-Services.md) is used to perform use cases of the application. We need to perform the following use cases:
* Get the list of todo items
* Create a new todo item
@@ -243,7 +243,7 @@ namespace TodoApp
### Data Transfer Object
-`GetListAsync` and `CreateAsync` methods return `TodoItemDto`. Applications Services typically gets and returns DTOs ([Data Transfer Objects](../../Data-Transfer-Objects.md)) instead of entities. So, we should define the DTO class here. Create a new `TodoItemDto` class inside the *TodoApp.Application.Contracts* project:
+`GetListAsync` and `CreateAsync` methods return `TodoItemDto`. `ApplicationService` typically gets and returns DTOs ([Data Transfer Objects](../../Data-Transfer-Objects.md)) instead of entities. So, we should define the DTO class here. Create a new `TodoItemDto` class inside the *TodoApp.Application.Contracts* project:
````csharp
using System;
@@ -465,9 +465,9 @@ $(function () {
});
````
-In the first part, we are registering to click events of the trash icons near to the todo items, deleting the related item on the server and showing a notification on the UI. Also, we are removing the deleted item from DOM, so we don't need to refresh the page.
+In the first part, we are subscribing to click events of the trash icons near to the todo items, deleting the related item on the server and showing a notification on the UI. Also, we are removing the deleted item from DOM, so we don't need to refresh the page.
-In the second part, we are creating a new todo item on the server. If it succeed, we are then manipulating DOM to insert a new `` element to the todo list. In this way, no need to refresh the whole page after creating a new todo item.
+In the second part, we are creating a new todo item on the server. If it succeeds, we are then manipulating DOM to insert a new `` element to the todo list. This way we don't need to refresh the whole page after creating a new todo item.
The interesting part here is how we communicate with the server. See the *Dynamic JavaScript Proxies & Auto API Controllers* section to understand how it works. But now, let's continue and complete the application.
@@ -504,13 +504,13 @@ As the final touch, open the `Index.css` file in the `Pages` folder of the *Todo
This is a simple styling for the todo page. We believe that you can do much better :)
-Now, you can run the application again to see the result.
+Now, you can run the application again and see the result.
### Dynamic JavaScript Proxies & Auto API Controllers
In the `Index.js` file, we've used `todoApp.todo.delete(...)` and `todoApp.todo.create(...)` functions to communicate with the server. These functions are dynamically created by the ABP Framework, thanks to the [Dynamic JavaScript Client Proxy](../../UI/AspNetCore/Dynamic-JavaScript-Proxies.md) system. They perform HTTP API calls to the server and return a promise, so you can register a callback to the `then` function as we've done above.
-However, you may ask that we haven't created any API Controller, so how server handles these requests? This question brings us the [Auto API Controller](../../API/Auto-API-Controllers.md) feature of the ABP Framework. It automatically converts the application services to API Controllers by conventions.
+However, you may notice that we haven't created any API Controller, so how server handles these requests? This question brings us the [Auto API Controller](../../API/Auto-API-Controllers.md) feature of the ABP Framework. It automatically converts the application services to API Controllers by convention.
If you open the [Swagger UI](https://swagger.io/tools/swagger-ui/) by entering the `/swagger` URL in your application, you can see the Todo API:
@@ -559,7 +559,7 @@ namespace TodoApp.Blazor.Pages
}
````
-This class uses the `ITodoAppService` to perform operations for the todo items. It manipulates the `TodoItems` list after create and delete operations. In this way, we don't need to refresh the whole todo list from the server.
+This class uses the `ITodoAppService` to perform operations for the todo items. It manipulates the `TodoItems` list after create and delete operations. This way, we don't need to refresh the whole todo list from the server.
{{if UI=="Blazor"}}
@@ -653,7 +653,7 @@ In the `Index.razor.cs` file, we've injected (with the `[Inject]` attribute) and
The magic is done by the ABP Framework's [Dynamic C# Client Proxy](../../API/Dynamic-CSharp-API-Clients.md) system. It uses the standard `HttpClient` and performs HTTP API requests to the remote server. It also handles all the standard tasks for us, including authorization, JSON serialization and exception handling.
-However, you may ask that we haven't created any API Controller, so how server handles these requests? This question brings us the [Auto API Controller](../../API/Auto-API-Controllers.md) feature of the ABP Framework. It automatically converts the application services to API Controllers by conventions.
+However, you may ask that we haven't created any API Controller, so how server handles these requests? This question brings us the [Auto API Controller](../../API/Auto-API-Controllers.md) feature of the ABP Framework. It automatically converts the application services to API Controllers by convention.
If you run the `TodoApp.HttpApi.Host` application, you can see the Todo API:
@@ -669,7 +669,7 @@ ABP provides a handy feature to automatically create client-side services to eas
You first need to run the `TodoApp.HttpApi.Host` project since the proxy generator reads API definitions from the server application.
-> **Warning**: There is a problem with IIS Express; it doesn't allow to connect to the application from another process. If you are using Visual Studio, select the `TodoApp.HttpApi.Host` instead of IIS Express in the run button drop-down list, as shown in the figure below:
+> **Warning**: There is an issue with IIS Express: it doesn't allow to connect to the application from another process. If you are using Visual Studio, select the `TodoApp.HttpApi.Host` instead of IIS Express in the run button drop-down list, as shown in the figure below:

@@ -813,7 +813,7 @@ Now, you can run the application again to see the result.
## Conclusion
-In this tutorial, we've build a very simple application to warm up to the ABP Framework. If you are looking to build a serious application, please check the [web application development tutorial](../Part-1.md) which covers all the aspects of a real-life web application development.
+In this tutorial, we've built a very simple application to warm up to the ABP Framework. If you are looking to build a serious application, please check the [web application development tutorial](../Part-1.md) which covers all the aspects of a real-life web application development.
## Source Code
@@ -821,4 +821,4 @@ You can find source code of the completed application [here](https://github.com/
## See Also
-* [Web Application Development Tutorial](../Part-1.md)
\ No newline at end of file
+* [Web Application Development Tutorial](../Part-1.md)
diff --git a/docs/en/UI/Angular/Custom-Setting-Page.md b/docs/en/UI/Angular/Custom-Setting-Page.md
deleted file mode 100644
index 9a17f6ad7b..0000000000
--- a/docs/en/UI/Angular/Custom-Setting-Page.md
+++ /dev/null
@@ -1,37 +0,0 @@
-# Custom Setting Page
-
-There are several settings tabs from different modules. You can add a custom setting page to your project.
-
-1. Create a component with the following command:
-
-```bash
-yarn ng generate component my-settings
-```
-
-2. Open the `app.component.ts` and modify the file as shown below:
-
-```js
-import { Component } from '@angular/core';
-import { SettingTabsService } from '@abp/ng.core'; // imported SettingTabsService
-import { MySettingsComponent } from './my-settings/my-settings.component'; // imported MySettingsComponent
-
-@Component(/* component metadata */)
-export class AppComponent {
- constructor(private settingTabs: SettingTabsService) // injected MySettingsComponent
- {
- // added below
- settingTabs.add([
- {
- name: 'MySettings',
- order: 1,
- requiredPolicy: 'policy key here',
- component: MySettingsComponent,
- },
- ]);
- }
-}
-```
-
-Navigate to `/setting-management` route to see the changes:
-
-
diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json
index 8abfddd251..a8554d9354 100644
--- a/docs/en/docs-nav.json
+++ b/docs/en/docs-nav.json
@@ -1027,10 +1027,6 @@
"text": "Component Replacement",
"path": "UI/Angular/Component-Replacement.md"
},
- {
- "text": "Custom Setting Page",
- "path": "UI/Angular/Custom-Setting-Page.md"
- },
{
"text": "Extensions",
"items": [
diff --git a/docs/en/UI/Angular/images/custom-settings.png b/docs/en/images/custom-settings.png
similarity index 100%
rename from docs/en/UI/Angular/images/custom-settings.png
rename to docs/en/images/custom-settings.png
diff --git a/docs/en/images/module-tenant-management-actions.png b/docs/en/images/module-tenant-management-actions.png
new file mode 100644
index 0000000000..d1f01c2e93
Binary files /dev/null and b/docs/en/images/module-tenant-management-actions.png differ
diff --git a/docs/en/images/module-tenant-management-new-tenant.png b/docs/en/images/module-tenant-management-new-tenant.png
new file mode 100644
index 0000000000..ab9ee6ed56
Binary files /dev/null and b/docs/en/images/module-tenant-management-new-tenant.png differ
diff --git a/docs/en/images/module-tenant-management-page.png b/docs/en/images/module-tenant-management-page.png
new file mode 100644
index 0000000000..563c64aac8
Binary files /dev/null and b/docs/en/images/module-tenant-management-page.png differ
diff --git a/docs/en/images/my-setting-group-blazor.png b/docs/en/images/my-setting-group-blazor.png
new file mode 100644
index 0000000000..0e685ff186
Binary files /dev/null and b/docs/en/images/my-setting-group-blazor.png differ
diff --git a/docs/en/images/my-setting-group-component-contributor.png b/docs/en/images/my-setting-group-component-contributor.png
new file mode 100644
index 0000000000..3ca6dbedfb
Binary files /dev/null and b/docs/en/images/my-setting-group-component-contributor.png differ
diff --git a/docs/en/images/my-setting-group-component.png b/docs/en/images/my-setting-group-component.png
new file mode 100644
index 0000000000..edcf315b2a
Binary files /dev/null and b/docs/en/images/my-setting-group-component.png differ
diff --git a/docs/en/images/my-setting-group-page-contributor.png b/docs/en/images/my-setting-group-page-contributor.png
new file mode 100644
index 0000000000..3be3737e6f
Binary files /dev/null and b/docs/en/images/my-setting-group-page-contributor.png differ
diff --git a/docs/en/images/my-setting-group-ui.png b/docs/en/images/my-setting-group-ui.png
new file mode 100644
index 0000000000..b9d3f1b403
Binary files /dev/null and b/docs/en/images/my-setting-group-ui.png differ
diff --git a/docs/en/images/my-setting-group-view-component.png b/docs/en/images/my-setting-group-view-component.png
new file mode 100644
index 0000000000..02843f968e
Binary files /dev/null and b/docs/en/images/my-setting-group-view-component.png differ
diff --git a/docs/zh-Hans/CLI.md b/docs/zh-Hans/CLI.md
index 262f546bf2..4325ee1f07 100644
--- a/docs/zh-Hans/CLI.md
+++ b/docs/zh-Hans/CLI.md
@@ -39,6 +39,7 @@ dotnet tool update -g Volo.Abp.Cli
* **`translate`**: 当源代码控制存储库中有多个JSON[本地化](Localization.md文件时,可简化翻译本地化文件的过程.
* **`login`**: 使用你在[abp.io](https://abp.io/)的用户名和密码在你的计算机上认证.
* **`logout`**: 在你的计算机注销认证.
+* **`install-libs`**: 为 MVC / Razor Pages 和 Blazor Server UI 类型安装NPM包.
### help
@@ -339,3 +340,19 @@ abp login -p -o # You can enter both your
```
abp logout
```
+
+### install-libs
+
+为 MVC / Razor Pages 和 Blazor Server UI 类型安装NPM包, 它的 **执行目录** 或者传递的 ```--working-directory``` 目录必须包含一个项目文件(*.csproj).
+
+`install-libs` 命令读取 `abp.resourcemapping.js` 来管理包. 参阅[客户端包管理](UI/AspNetCore/Client-Side-Package-Management.md)了解更多细节.
+
+用法:
+
+````bash
+abp install-libs [options]
+````
+
+#### Options
+
+* ```--working-directory``` 或 ```-wd```: 指定工作目录, 当执行目录不包含项目文件时会很有用.
\ No newline at end of file
diff --git a/docs/zh-Hans/Modules/Setting-Management.md b/docs/zh-Hans/Modules/Setting-Management.md
index fd87dd734c..acb4469fea 100644
--- a/docs/zh-Hans/Modules/Setting-Management.md
+++ b/docs/zh-Hans/Modules/Setting-Management.md
@@ -83,4 +83,194 @@ namespace Demo
* `TenantSettingManagementProvider`: 获取或设定租户的设置值.
* `UserSettingManagementProvider`: 获取或设定用户的设置值.
-`ISettingManager` 在 `get/set` 方法中使用设置管理提供程序. 通常每个设置程序提供程序都在 `ISettingManagement` 服务上定义了模块方法 (比如用户设置管理程序提供定义了 `SetForUserAsync` 方法).
\ No newline at end of file
+`ISettingManager` 在 `get/set` 方法中使用设置管理提供程序. 通常每个设置程序提供程序都在 `ISettingManagement` 服务上定义了模块方法 (比如用户设置管理程序提供定义了 `SetForUserAsync` 方法).
+
+## Setting Management UI.
+
+设置管理模块默认提供了邮件设置页面并且它是可扩展的; 你可以为你的应用程序设置添加设置标签到设置页面.
+
+### MVC UI
+
+#### 创建视图组件
+
+在 `Components` 目录下创建 `MySettingGroup` 文件夹, 添加一个名为 `MySettingGroupViewComponent` 的视图组件:
+
+
+
+打开 `MySettingGroupViewComponent.cs` 替换为以下内容:
+
+```csharp
+public class MySettingGroupViewComponent : AbpViewComponent
+{
+ public virtual IViewComponentResult Invoke()
+ {
+ return View("~/Components/MySettingGroup/Default.cshtml");
+ }
+}
+```
+
+> 你还可以使用 `InvokeAsync` 方法,在这个示例中我们使用 `Invoke` 方法.
+
+#### Default.cshtml
+
+在 `MySettingGroup` 目录下创建 `Default.cshtml` 文件.
+
+打开 `Default.cshtml` 替换为以下内容:
+
+```html
+
+
My setting group page
+
+```
+
+#### BookStoreSettingPageContributor
+
+在 `Settings` 目录下创建 `BookStoreSettingPageContributor.cs` 文件.
+
+
+
+文件内容如下:
+
+```csharp
+public class BookStoreSettingPageContributor : ISettingPageContributor
+{
+ public Task ConfigureAsync(SettingPageCreationContext context)
+ {
+ context.Groups.Add(
+ new SettingPageGroup(
+ "Volo.Abp.MySettingGroup",
+ "MySettingGroup",
+ typeof(MySettingGroupViewComponent)
+ )
+ );
+
+ return Task.CompletedTask;
+ }
+
+ public Task CheckPermissionsAsync(SettingPageCreationContext context)
+ {
+ // You can check the permissions here
+ return Task.FromResult(true);
+ }
+}
+```
+
+打开 `BookStoreWebModule.cs` 文件添加以下代码:
+
+```csharp
+Configure(options =>
+{
+ options.Contributors.Add(new BookStoreSettingPageContributor());
+});
+```
+
+#### 运行应用程序
+
+导航到 `/SettingManagement` 路由查看更改:
+
+
+
+### Blazor UI
+
+#### 创建 Razor 组件
+
+在 `Pages` 目录下创建 `MySettingGroup` 文件夹, 添加一个名为 `MySettingGroupComponent` 的Razor组件:
+
+
+
+打开 `MySettingGroupComponent.razor` 替换为以下内容:
+
+```csharp
+
+ my setting group
+
+```
+
+#### BookStoreSettingComponentContributor
+
+在 `Settings` 目录下创建 `BookStoreSettingComponentContributor.cs` 文件.
+
+
+
+文件内容如下:
+
+```csharp
+public class BookStoreSettingComponentContributor : ISettingComponentContributor
+{
+ public Task ConfigureAsync(SettingComponentCreationContext context)
+ {
+ context.Groups.Add(
+ new SettingComponentGroup(
+ "Volo.Abp.MySettingGroup",
+ "MySettingGroup",
+ typeof(MySettingGroupComponent)
+ )
+ );
+
+ return Task.CompletedTask;
+ }
+
+ public Task CheckPermissionsAsync(SettingComponentCreationContext context)
+ {
+ // You can check the permissions here
+ return Task.FromResult(true);
+ }
+}
+```
+
+打开 `BookStoreBlazorModule.cs` 文件添加以下代码:
+
+```csharp
+Configure(options =>
+{
+ options.Contributors.Add(new BookStoreSettingComponentContributor());
+});
+```
+
+#### 运行应用程序
+
+导航到 `/setting-management` 路由查看更改:
+
+
+
+### Angular UI
+
+不同的模块提供它们的设置选项卡. 你可以通过3个步骤在项目中自定义设置页面.
+
+#### 创建组件
+
+使用以下命令创建一个组件
+
+```bash
+yarn ng generate component my-settings
+```
+
+打开 `app.component.ts` 做以下修改:
+
+```js
+import { Component } from '@angular/core';
+import { SettingTabsService } from '@abp/ng.core'; // imported SettingTabsService
+import { MySettingsComponent } from './my-settings/my-settings.component'; // imported MySettingsComponent
+
+@Component(/* component metadata */)
+export class AppComponent {
+ constructor(private settingTabs: SettingTabsService) // injected MySettingsComponent
+ {
+ // added below
+ settingTabs.add([
+ {
+ name: 'MySettings',
+ order: 1,
+ requiredPolicy: 'policy key here',
+ component: MySettingsComponent,
+ },
+ ]);
+ }
+}
+```
+
+#### 运行应用程序
+
+导航到 `/setting-management` 路由你会看到以下变化:
+
+
\ No newline at end of file
diff --git a/docs/zh-Hans/UI/Angular/Custom-Setting-Page.md b/docs/zh-Hans/UI/Angular/Custom-Setting-Page.md
deleted file mode 100644
index 2b888e8d75..0000000000
--- a/docs/zh-Hans/UI/Angular/Custom-Setting-Page.md
+++ /dev/null
@@ -1,41 +0,0 @@
-# 自定义设置页面
-
-不同的模块提供它们的设置选项卡. 你可以通过3个步骤在项目中自定义设置页面.
-
-1. 使用以下命令创建一个组件
-
-```bash
-yarn ng generate component my-settings
-```
-
-2. 打开 `app.component.ts` 做以下修改:
-
-```js
-import { Component } from '@angular/core';
-import { SettingTabsService } from '@abp/ng.core'; // imported SettingTabsService
-import { MySettingsComponent } from './my-settings/my-settings.component'; // imported MySettingsComponent
-
-@Component(/* component metadata */)
-export class AppComponent {
- constructor(private settingTabs: SettingTabsService) // injected MySettingsComponent
- {
- // added below
- settingTabs.add([
- {
- name: 'MySettings',
- order: 1,
- requiredPolicy: 'policy key here',
- component: MySettingsComponent,
- },
- ]);
- }
-}
-```
-
-导航到 `/setting-management` 路由你会看到以下变化:
-
-
-
-## 下一步是什么?
-
-- [懒加载 Scripts 与 Styles](./Lazy-Load-Service.md)
diff --git a/docs/zh-Hans/docs-nav.json b/docs/zh-Hans/docs-nav.json
index 4f47dd2760..28ce41cdb1 100644
--- a/docs/zh-Hans/docs-nav.json
+++ b/docs/zh-Hans/docs-nav.json
@@ -518,10 +518,6 @@
"text": "替换组件",
"path": "UI/Angular/Component-Replacement.md"
},
- {
- "text": "自定义设置页",
- "path": "UI/Angular/Custom-Setting-Page.md"
- },
{
"text": "懒加载Scripts与Styles",
"path": "UI/Angular/Lazy-Load-Service.md"
diff --git a/docs/zh-Hans/UI/Angular/images/custom-settings.png b/docs/zh-Hans/images/custom-settings.png
similarity index 100%
rename from docs/zh-Hans/UI/Angular/images/custom-settings.png
rename to docs/zh-Hans/images/custom-settings.png
diff --git a/docs/zh-Hans/images/my-setting-group-blazor.png b/docs/zh-Hans/images/my-setting-group-blazor.png
new file mode 100644
index 0000000000..0e685ff186
Binary files /dev/null and b/docs/zh-Hans/images/my-setting-group-blazor.png differ
diff --git a/docs/zh-Hans/images/my-setting-group-component-contributor.png b/docs/zh-Hans/images/my-setting-group-component-contributor.png
new file mode 100644
index 0000000000..3ca6dbedfb
Binary files /dev/null and b/docs/zh-Hans/images/my-setting-group-component-contributor.png differ
diff --git a/docs/zh-Hans/images/my-setting-group-component.png b/docs/zh-Hans/images/my-setting-group-component.png
new file mode 100644
index 0000000000..edcf315b2a
Binary files /dev/null and b/docs/zh-Hans/images/my-setting-group-component.png differ
diff --git a/docs/zh-Hans/images/my-setting-group-page-contributor.png b/docs/zh-Hans/images/my-setting-group-page-contributor.png
new file mode 100644
index 0000000000..3be3737e6f
Binary files /dev/null and b/docs/zh-Hans/images/my-setting-group-page-contributor.png differ
diff --git a/docs/zh-Hans/images/my-setting-group-ui.png b/docs/zh-Hans/images/my-setting-group-ui.png
new file mode 100644
index 0000000000..b9d3f1b403
Binary files /dev/null and b/docs/zh-Hans/images/my-setting-group-ui.png differ
diff --git a/docs/zh-Hans/images/my-setting-group-view-component.png b/docs/zh-Hans/images/my-setting-group-view-component.png
new file mode 100644
index 0000000000..02843f968e
Binary files /dev/null and b/docs/zh-Hans/images/my-setting-group-view-component.png differ
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo.Abp.Cli.Core.csproj b/framework/src/Volo.Abp.Cli.Core/Volo.Abp.Cli.Core.csproj
index 1b7603194a..4f28bad500 100644
--- a/framework/src/Volo.Abp.Cli.Core/Volo.Abp.Cli.Core.csproj
+++ b/framework/src/Volo.Abp.Cli.Core/Volo.Abp.Cli.Core.csproj
@@ -13,6 +13,7 @@
+
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs
index b3de9d71ff..08da2e3d00 100644
--- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs
@@ -2,9 +2,11 @@
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Cli.Commands;
using Volo.Abp.Cli.Http;
+using Volo.Abp.Cli.LIbs;
using Volo.Abp.Domain;
using Volo.Abp.IdentityModel;
using Volo.Abp.Json;
+using Volo.Abp.Json.SystemTextJson;
using Volo.Abp.Minify;
using Volo.Abp.Modularity;
@@ -25,6 +27,11 @@ namespace Volo.Abp.Cli
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
+ Configure(options =>
+ {
+ options.UnsupportedTypes.Add(typeof(ResourceMapping));
+ });
+
Configure(options =>
{
//TODO: Define constants like done for GenerateProxyCommand.Name.
@@ -49,6 +56,7 @@ namespace Volo.Abp.Cli
options.Commands["build"] = typeof(BuildCommand);
options.Commands["bundle"] = typeof(BundleCommand);
options.Commands["create-migration-and-run-migrator"] = typeof(CreateMigrationAndRunMigratorCommand);
+ options.Commands["install-libs"] = typeof(InstallLibsCommand);
});
}
}
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/InstallLibsCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/InstallLibsCommand.cs
new file mode 100644
index 0000000000..36db14856f
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/InstallLibsCommand.cs
@@ -0,0 +1,78 @@
+using System;
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using Volo.Abp.Cli.Args;
+using Volo.Abp.Cli.LIbs;
+using Volo.Abp.DependencyInjection;
+
+namespace Volo.Abp.Cli.Commands
+{
+ public class InstallLibsCommand : IConsoleCommand, ITransientDependency
+ {
+ public ILogger Logger { get; set; }
+
+ protected IInstallLibsService InstallLibsService { get; }
+
+ public InstallLibsCommand(IInstallLibsService installLibsService)
+ {
+ InstallLibsService = installLibsService;
+ Logger = NullLogger.Instance;
+ }
+
+ public async Task ExecuteAsync(CommandLineArgs commandLineArgs)
+ {
+ var workingDirectoryArg = commandLineArgs.Options.GetOrNull(
+ Options.WorkingDirectory.Short,
+ Options.WorkingDirectory.Long
+ );
+
+ var workingDirectory = workingDirectoryArg ?? Directory.GetCurrentDirectory();
+
+ if (!Directory.Exists(workingDirectory))
+ {
+ throw new CliUsageException(
+ "Specified directory does not exist." +
+ Environment.NewLine + Environment.NewLine +
+ GetUsageInfo()
+ );
+ }
+
+ await InstallLibsService.InstallLibs(workingDirectory);
+ }
+
+ public string GetUsageInfo()
+ {
+ var sb = new StringBuilder();
+
+ sb.AppendLine("");
+ sb.AppendLine("Usage:");
+ sb.AppendLine("");
+ sb.AppendLine(" abp install-libs");
+ sb.AppendLine("");
+ sb.AppendLine("Options:");
+ sb.AppendLine("");
+ sb.AppendLine("-wd|--working-directory (default: empty)");
+ sb.AppendLine("");
+ sb.AppendLine("See the documentation for more info: https://docs.abp.io/en/abp/latest/CLI");
+
+ return sb.ToString();
+ }
+
+ public string GetShortDescription()
+ {
+ return "Install NPM Packages for MVC / Razor Pages and Blazor Server UI types.";
+ }
+
+ public static class Options
+ {
+ public static class WorkingDirectory
+ {
+ public const string Short = "wd";
+ public const string Long = "working-directory";
+ }
+ }
+ }
+}
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/LIbs/IInstallLibsService.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/LIbs/IInstallLibsService.cs
new file mode 100644
index 0000000000..af96df6e22
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/LIbs/IInstallLibsService.cs
@@ -0,0 +1,9 @@
+using System.Threading.Tasks;
+
+namespace Volo.Abp.Cli.LIbs
+{
+ public interface IInstallLibsService
+ {
+ Task InstallLibs(string directory);
+ }
+}
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/LIbs/InstallLibsService.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/LIbs/InstallLibsService.cs
new file mode 100644
index 0000000000..7416859486
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/LIbs/InstallLibsService.cs
@@ -0,0 +1,222 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.Extensions.FileSystemGlobbing;
+using Microsoft.Extensions.FileSystemGlobbing.Abstractions;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using NuGet.Versioning;
+using Volo.Abp.Cli.Utils;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.Json;
+
+namespace Volo.Abp.Cli.LIbs
+{
+ public class InstallLibsService : IInstallLibsService, ITransientDependency
+ {
+ public ILogger Logger { get; set; }
+
+ private readonly IJsonSerializer _jsonSerializer;
+
+ public InstallLibsService(IJsonSerializer jsonSerializer)
+ {
+ _jsonSerializer = jsonSerializer;
+ Logger = NullLogger.Instance;
+ }
+
+ public async Task InstallLibs(string directory)
+ {
+ var projectFiles = Directory.GetFiles(directory, "*.csproj");
+ if (!projectFiles.Any())
+ {
+ Logger.LogError("No project file found in the directory.");
+ return;
+ }
+
+ if (!await CanInstallLibs(directory))
+ {
+ Logger.LogWarning(
+ "abp install-libs command is available for MVC, Razor Page, and Blazor-Server UI types");
+ return;
+ }
+
+ if (!IsNpmInstalled())
+ {
+ Logger.LogWarning("NPM is not installed, visit https://nodejs.org/en/download/ and install NPM");
+ return;
+ }
+
+ if (IsYarnAvailable())
+ {
+ RunYarn(directory);
+ }
+ else
+ {
+ RunNpmInstall(directory);
+ }
+
+ await CleanAndCopyResources(directory);
+ }
+
+ private async Task CleanAndCopyResources(string fileDirectory)
+ {
+ var mappingFiles = Directory.GetFiles(fileDirectory, "abp.resourcemapping.js", SearchOption.AllDirectories);
+ var resourceMapping = new ResourceMapping
+ {
+ Clean = new List {"./wwwroot/libs"}
+ };
+
+ foreach (var mappingFile in mappingFiles)
+ {
+ using (var reader = File.OpenText(mappingFile))
+ {
+ var mappingFileContent = await reader.ReadToEndAsync();
+
+ var mapping = _jsonSerializer.Deserialize(mappingFileContent
+ .Replace("module.exports", string.Empty)
+ .Replace("=", string.Empty).Trim().TrimEnd(';'));
+
+ mapping.ReplaceAliases();
+
+ mapping.Clean.ForEach(c => resourceMapping.Clean.AddIfNotContains(c));
+ mapping.Aliases.ToList().ForEach(x =>
+ {
+ resourceMapping.Aliases.AddIfNotContains(new KeyValuePair(x.Key, x.Value));
+ });
+ mapping.Mappings.ToList().ForEach(x =>
+ {
+ resourceMapping.Mappings.AddIfNotContains(new KeyValuePair(x.Key, x.Value));
+ });
+ }
+ }
+
+ CleanDirsAndFiles(fileDirectory, resourceMapping);
+ CopyResourcesToLibs(fileDirectory, resourceMapping);
+ }
+
+ private void CopyResourcesToLibs(string fileDirectory, ResourceMapping resourceMapping)
+ {
+ foreach (var mapping in resourceMapping.Mappings)
+ {
+ var destPath = Path.Combine(fileDirectory, mapping.Value);
+
+ var files = FindFiles(fileDirectory, mapping.Key);
+
+ foreach (var file in files)
+ {
+ var destFilePath = Path.Combine(destPath, Path.GetFileName(file));
+ if (File.Exists(destFilePath))
+ {
+ continue;
+ }
+
+ Directory.CreateDirectory(Path.GetFullPath(destPath));
+ File.Copy(file, destFilePath);
+
+ }
+ }
+ }
+
+ private async Task CanInstallLibs(string fileDirectory)
+ {
+ var projectFiles = Directory.GetFiles(fileDirectory, "*.csproj");
+
+ using (var reader = File.OpenText(projectFiles[0]))
+ {
+ var projectFileContent = await reader.ReadToEndAsync();
+
+ return projectFileContent.Contains("Microsoft.NET.Sdk.Web");
+ }
+ }
+
+ private void CleanDirsAndFiles(string directory, ResourceMapping resourceMapping)
+ {
+ var files = FindFiles(directory, resourceMapping.Clean.ToArray());
+
+ foreach (var file in files)
+ {
+ if (File.Exists(file))
+ {
+ File.Delete(file);
+ }
+ }
+
+ foreach (var directoryInfo in Directory.GetDirectories(Path.Combine(directory, resourceMapping.Clean.First()),"*", SearchOption.AllDirectories).Reverse())
+ {
+ if (!Directory.EnumerateFileSystemEntries(directoryInfo).Any())
+ {
+ Directory.Delete(directoryInfo);
+ }
+ }
+ }
+
+ private string[] FindFiles(string directory, params string[] patterns)
+ {
+ var matcher = new Matcher();
+
+ foreach (var pattern in patterns)
+ {
+ if (pattern.StartsWith("!"))
+ {
+ matcher.AddExclude(NormalizeGlob(pattern).TrimStart('!'));
+ }
+ else
+ {
+ matcher.AddInclude(NormalizeGlob(pattern));
+ }
+ }
+
+ var result = matcher.Execute(new DirectoryInfoWrapper(new DirectoryInfo(directory)));
+
+ return result.Files.Select(x => Path.Combine(directory, x.Path)).ToArray();
+ }
+
+ private string NormalizeGlob(string pattern)
+ {
+ pattern = pattern.Replace("//", "/");
+
+ if (!Path.HasExtension(pattern) && !pattern.EndsWith("*"))
+ {
+ return pattern.EnsureEndsWith('/') + "**";
+ }
+
+ return pattern;
+ }
+
+ private void RunNpmInstall(string directory)
+ {
+ Logger.LogInformation($"Running npm install on {directory}");
+ CmdHelper.RunCmd($"cd {directory} && npm install");
+ }
+
+ private void RunYarn(string directory)
+ {
+ Logger.LogInformation($"Running Yarn on {directory}");
+ CmdHelper.RunCmd($"cd {directory} && yarn");
+ }
+
+ private bool IsNpmInstalled()
+ {
+ var output = CmdHelper.RunCmdAndGetOutput("npm -v").Trim();
+ return SemanticVersion.TryParse(output, out _);
+ }
+
+ private bool IsYarnAvailable()
+ {
+ var output = CmdHelper.RunCmdAndGetOutput("npm list yarn -g").Trim();
+ if (output.Contains("empty"))
+ {
+ return false;
+ }
+
+ if (!SemanticVersion.TryParse(output.Substring(output.IndexOf('@') + 1), out var version))
+ {
+ return false;
+ }
+
+ return version > SemanticVersion.Parse("1.20.0");
+ }
+ }
+}
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/LIbs/ResourceMapping.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/LIbs/ResourceMapping.cs
new file mode 100644
index 0000000000..ed46b833ad
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/LIbs/ResourceMapping.cs
@@ -0,0 +1,53 @@
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Volo.Abp.Cli.LIbs
+{
+ public class ResourceMapping
+ {
+ public Dictionary Aliases { get; set; }
+
+ public List Clean { get; set; }
+
+ public Dictionary Mappings { get; set; }
+
+ public ResourceMapping()
+ {
+ Aliases = new Dictionary
+ {
+ {"@node_modules", "./node_modules"},
+ {"@libs", "./wwwroot/libs"},
+ };
+ Clean = new List();
+ Mappings = new Dictionary();
+ }
+
+ public void ReplaceAliases()
+ {
+ for (var i = 0; i < Mappings.Count; i++)
+ {
+ var mapping = Mappings.ElementAt(i);
+ Mappings.Remove(mapping.Key);
+
+ var key = mapping.Key;
+ var value = mapping.Value;
+
+ foreach (var alias in Aliases)
+ {
+ key = key.Replace(alias.Key, alias.Value);
+ value = value.Replace(alias.Key, alias.Value);
+ }
+
+ Mappings[key] = value;
+ }
+
+ for (var i = 0; i < Clean.Count; i++)
+ {
+ foreach (var alias in Aliases)
+ {
+ Clean[i] = Clean[i].Replace(alias.Key, alias.Value);
+ }
+ }
+ }
+ }
+}
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Utils/ProjectNameValidator.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Utils/ProjectNameValidator.cs
index 8430cd79a0..4606aa59c4 100644
--- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Utils/ProjectNameValidator.cs
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Utils/ProjectNameValidator.cs
@@ -17,6 +17,11 @@ namespace Volo.Abp.Cli.Utils
"LPT2"
};
+ private static readonly string[] IllegalKeywords = new[]
+ {
+ "Blazor"
+ };
+
private static bool HasParentDirectoryString(string projectName)
{
return projectName.Contains("..");
@@ -40,6 +45,19 @@ namespace Volo.Abp.Cli.Utils
return false;
}
+ private static bool HasIllegalKeywords(string projectName)
+ {
+ foreach (var illegalKeyword in IllegalKeywords)
+ {
+ if (projectName.Contains(illegalKeyword))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
public static bool IsValid(string projectName)
{
if (projectName == null)
@@ -62,6 +80,11 @@ namespace Volo.Abp.Cli.Utils
return false;
}
+ if (HasIllegalKeywords(projectName))
+ {
+ return false;
+ }
+
return true;
}
}
diff --git a/framework/test/Volo.Abp.Cli.Core.Tests/Volo/Abp/Cli/ProjectNameValidation_Tests.cs b/framework/test/Volo.Abp.Cli.Core.Tests/Volo/Abp/Cli/ProjectNameValidation_Tests.cs
index cd8581f912..5d08f90fc4 100644
--- a/framework/test/Volo.Abp.Cli.Core.Tests/Volo/Abp/Cli/ProjectNameValidation_Tests.cs
+++ b/framework/test/Volo.Abp.Cli.Core.Tests/Volo/Abp/Cli/ProjectNameValidation_Tests.cs
@@ -45,5 +45,23 @@ namespace Volo.Abp.Cli
var args = new CommandLineArgs("new", "Test..Test");
await _newCommand.ExecuteAsync(args).ShouldThrowAsync();
}
+
+
+ [Fact]
+ public async Task Has_Illegel_Keyword_Test()
+ {
+ var illegalKeywords = new[]
+ {
+ "Acme.Blazor",
+ "MyBlazor",
+ };
+
+ foreach (var illegalKeyword in illegalKeywords)
+ {
+ var args = new CommandLineArgs("new", illegalKeyword);
+ await _newCommand.ExecuteAsync(args).ShouldThrowAsync();
+ }
+ }
+
}
}
diff --git a/templates/module/aspnet-core/MyCompanyName.MyProjectName.sln b/templates/module/aspnet-core/MyCompanyName.MyProjectName.sln
index 8d87117893..ca958e6f89 100644
--- a/templates/module/aspnet-core/MyCompanyName.MyProjectName.sln
+++ b/templates/module/aspnet-core/MyCompanyName.MyProjectName.sln
@@ -57,6 +57,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyCompanyName.MyProjectName
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyCompanyName.MyProjectName.Blazor.WebAssembly", "src\MyCompanyName.MyProjectName.Blazor.WebAssembly\MyCompanyName.MyProjectName.Blazor.WebAssembly.csproj", "{F0EE5760-262D-456A-AA7C-E84F484A05F6}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyCompanyName.MyProjectName.Blazor.Server.Host", "host\MyCompanyName.MyProjectName.Blazor.Server.Host\MyCompanyName.MyProjectName.Blazor.Server.Host.csproj", "{FEA752A1-5B4E-49E9-B1F3-DDC25E41BB52}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -159,6 +161,10 @@ Global
{F0EE5760-262D-456A-AA7C-E84F484A05F6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F0EE5760-262D-456A-AA7C-E84F484A05F6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F0EE5760-262D-456A-AA7C-E84F484A05F6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {FEA752A1-5B4E-49E9-B1F3-DDC25E41BB52}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {FEA752A1-5B4E-49E9-B1F3-DDC25E41BB52}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FEA752A1-5B4E-49E9-B1F3-DDC25E41BB52}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {FEA752A1-5B4E-49E9-B1F3-DDC25E41BB52}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -188,6 +194,7 @@ Global
{D7E0F672-F5E2-4338-AFF4-4E5091C55A62} = {E400416D-2895-4512-9D17-90681EEC7E0A}
{299BE52E-823F-408E-9C6D-7E2F81BA34FF} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545}
{F0EE5760-262D-456A-AA7C-E84F484A05F6} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545}
+ {FEA752A1-5B4E-49E9-B1F3-DDC25E41BB52} = {E400416D-2895-4512-9D17-90681EEC7E0A}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {4324B3B4-B60B-4E3C-91D8-59576B4E26DD}
diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/EntityFrameworkCore/UnifiedDbContext.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/EntityFrameworkCore/UnifiedDbContext.cs
new file mode 100644
index 0000000000..ac1f756c45
--- /dev/null
+++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/EntityFrameworkCore/UnifiedDbContext.cs
@@ -0,0 +1,34 @@
+using Microsoft.EntityFrameworkCore;
+using MyCompanyName.MyProjectName.EntityFrameworkCore;
+using Volo.Abp.AuditLogging.EntityFrameworkCore;
+using Volo.Abp.EntityFrameworkCore;
+using Volo.Abp.FeatureManagement.EntityFrameworkCore;
+using Volo.Abp.Identity.EntityFrameworkCore;
+using Volo.Abp.PermissionManagement.EntityFrameworkCore;
+using Volo.Abp.SettingManagement.EntityFrameworkCore;
+using Volo.Abp.TenantManagement.EntityFrameworkCore;
+
+namespace MyCompanyName.MyProjectName.Blazor.Server.Host.EntityFrameworkCore
+{
+ public class UnifiedDbContext : AbpDbContext
+ {
+ public UnifiedDbContext(DbContextOptions options)
+ : base(options)
+ {
+
+ }
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
+ {
+ base.OnModelCreating(modelBuilder);
+
+ modelBuilder.ConfigurePermissionManagement();
+ modelBuilder.ConfigureSettingManagement();
+ modelBuilder.ConfigureAuditLogging();
+ modelBuilder.ConfigureIdentity();
+ modelBuilder.ConfigureFeatureManagement();
+ modelBuilder.ConfigureTenantManagement();
+ modelBuilder.ConfigureMyProjectName();
+ }
+ }
+}
diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/EntityFrameworkCore/UnifiedDbContextFactory.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/EntityFrameworkCore/UnifiedDbContextFactory.cs
new file mode 100644
index 0000000000..cddc6f20c1
--- /dev/null
+++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/EntityFrameworkCore/UnifiedDbContextFactory.cs
@@ -0,0 +1,29 @@
+using System.IO;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Design;
+using Microsoft.Extensions.Configuration;
+
+namespace MyCompanyName.MyProjectName.Blazor.Server.Host.EntityFrameworkCore
+{
+ public class UnifiedDbContextFactory : IDesignTimeDbContextFactory
+ {
+ public UnifiedDbContext CreateDbContext(string[] args)
+ {
+ var configuration = BuildConfiguration();
+
+ var builder = new DbContextOptionsBuilder()
+ .UseSqlServer(configuration.GetConnectionString("Default"));
+
+ return new UnifiedDbContext(builder.Options);
+ }
+
+ private static IConfigurationRoot BuildConfiguration()
+ {
+ var builder = new ConfigurationBuilder()
+ .SetBasePath(Directory.GetCurrentDirectory())
+ .AddJsonFile("appsettings.json", optional: false);
+
+ return builder.Build();
+ }
+ }
+}
diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/FodyWeavers.xml b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/FodyWeavers.xml
new file mode 100644
index 0000000000..00e1d9a1c1
--- /dev/null
+++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/FodyWeavers.xsd b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/FodyWeavers.xsd
new file mode 100644
index 0000000000..3f3946e282
--- /dev/null
+++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/FodyWeavers.xsd
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.
+
+
+
+
+ A comma-separated list of error codes that can be safely ignored in assembly verification.
+
+
+
+
+ 'false' to turn off automatic generation of the XML Schema file.
+
+
+
+
+
\ No newline at end of file
diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Menus/MyProjectNameMenuContributor.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Menus/MyProjectNameMenuContributor.cs
new file mode 100644
index 0000000000..a9a6e6e522
--- /dev/null
+++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Menus/MyProjectNameMenuContributor.cs
@@ -0,0 +1,39 @@
+using System.Threading.Tasks;
+using MyCompanyName.MyProjectName.MultiTenancy;
+using Volo.Abp.Identity.Blazor;
+using Volo.Abp.SettingManagement.Blazor.Menus;
+using Volo.Abp.TenantManagement.Blazor.Navigation;
+using Volo.Abp.UI.Navigation;
+
+namespace MyCompanyName.MyProjectName.Blazor.Server.Host.Menus
+{
+ public class MyProjectNameMenuContributor : IMenuContributor
+ {
+ public async Task ConfigureMenuAsync(MenuConfigurationContext context)
+ {
+ if (context.Menu.Name == StandardMenus.Main)
+ {
+ await ConfigureMainMenuAsync(context);
+ }
+ }
+
+ private Task ConfigureMainMenuAsync(MenuConfigurationContext context)
+ {
+ var administration = context.Menu.GetAdministration();
+
+ if (MultiTenancyConsts.IsEnabled)
+ {
+ administration.SetSubItemOrder(TenantManagementMenuNames.GroupName, 1);
+ }
+ else
+ {
+ administration.TryRemoveMenuItem(TenantManagementMenuNames.GroupName);
+ }
+
+ administration.SetSubItemOrder(IdentityMenuNames.GroupName, 2);
+ administration.SetSubItemOrder(SettingManagementMenus.GroupName, 3);
+
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Menus/MyProjectNameMenus.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Menus/MyProjectNameMenus.cs
new file mode 100644
index 0000000000..568dff3a68
--- /dev/null
+++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Menus/MyProjectNameMenus.cs
@@ -0,0 +1,10 @@
+namespace MyCompanyName.MyProjectName.Blazor.Server.Host.Menus
+{
+ public class MyProjectNameMenus
+ {
+ private const string Prefix = "MyProjectName";
+
+ //Add your menu items here...
+
+ }
+}
diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Migrations/20210528061540_Initial.Designer.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Migrations/20210528061540_Initial.Designer.cs
new file mode 100644
index 0000000000..5e59d21c1c
--- /dev/null
+++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Migrations/20210528061540_Initial.Designer.cs
@@ -0,0 +1,1222 @@
+//
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using MyCompanyName.MyProjectName.Blazor.Server.Host.EntityFrameworkCore;
+using Volo.Abp.EntityFrameworkCore;
+
+namespace MyCompanyName.MyProjectName.Blazor.Server.Host.Migrations
+{
+ [DbContext(typeof(UnifiedDbContext))]
+ [Migration("20210528061540_Initial")]
+ partial class Initial
+ {
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer)
+ .HasAnnotation("Relational:MaxIdentifierLength", 128)
+ .HasAnnotation("ProductVersion", "5.0.6")
+ .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
+
+ modelBuilder.Entity("Volo.Abp.AuditLogging.AuditLog", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("ApplicationName")
+ .HasMaxLength(96)
+ .HasColumnType("nvarchar(96)")
+ .HasColumnName("ApplicationName");
+
+ b.Property("BrowserInfo")
+ .HasMaxLength(512)
+ .HasColumnType("nvarchar(512)")
+ .HasColumnName("BrowserInfo");
+
+ b.Property("ClientId")
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)")
+ .HasColumnName("ClientId");
+
+ b.Property("ClientIpAddress")
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)")
+ .HasColumnName("ClientIpAddress");
+
+ b.Property("ClientName")
+ .HasMaxLength(128)
+ .HasColumnType("nvarchar(128)")
+ .HasColumnName("ClientName");
+
+ b.Property("Comments")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)")
+ .HasColumnName("Comments");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasMaxLength(40)
+ .HasColumnType("nvarchar(40)")
+ .HasColumnName("ConcurrencyStamp");
+
+ b.Property("CorrelationId")
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)")
+ .HasColumnName("CorrelationId");
+
+ b.Property("Exceptions")
+ .HasMaxLength(4000)
+ .HasColumnType("nvarchar(4000)")
+ .HasColumnName("Exceptions");
+
+ b.Property("ExecutionDuration")
+ .HasColumnType("int")
+ .HasColumnName("ExecutionDuration");
+
+ b.Property("ExecutionTime")
+ .HasColumnType("datetime2");
+
+ b.Property("ExtraProperties")
+ .HasColumnType("nvarchar(max)")
+ .HasColumnName("ExtraProperties");
+
+ b.Property("HttpMethod")
+ .HasMaxLength(16)
+ .HasColumnType("nvarchar(16)")
+ .HasColumnName("HttpMethod");
+
+ b.Property("HttpStatusCode")
+ .HasColumnType("int")
+ .HasColumnName("HttpStatusCode");
+
+ b.Property("ImpersonatorTenantId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("ImpersonatorTenantId");
+
+ b.Property("ImpersonatorUserId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("ImpersonatorUserId");
+
+ b.Property("TenantId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("TenantId");
+
+ b.Property("TenantName")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Url")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)")
+ .HasColumnName("Url");
+
+ b.Property("UserId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("UserId");
+
+ b.Property("UserName")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)")
+ .HasColumnName("UserName");
+
+ b.HasKey("Id");
+
+ b.HasIndex("TenantId", "ExecutionTime");
+
+ b.HasIndex("TenantId", "UserId", "ExecutionTime");
+
+ b.ToTable("AbpAuditLogs");
+ });
+
+ modelBuilder.Entity("Volo.Abp.AuditLogging.AuditLogAction", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("AuditLogId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("AuditLogId");
+
+ b.Property("ExecutionDuration")
+ .HasColumnType("int")
+ .HasColumnName("ExecutionDuration");
+
+ b.Property("ExecutionTime")
+ .HasColumnType("datetime2")
+ .HasColumnName("ExecutionTime");
+
+ b.Property("ExtraProperties")
+ .HasColumnType("nvarchar(max)")
+ .HasColumnName("ExtraProperties");
+
+ b.Property("MethodName")
+ .HasMaxLength(128)
+ .HasColumnType("nvarchar(128)")
+ .HasColumnName("MethodName");
+
+ b.Property("Parameters")
+ .HasMaxLength(2000)
+ .HasColumnType("nvarchar(2000)")
+ .HasColumnName("Parameters");
+
+ b.Property("ServiceName")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)")
+ .HasColumnName("ServiceName");
+
+ b.Property("TenantId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("TenantId");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AuditLogId");
+
+ b.HasIndex("TenantId", "ServiceName", "MethodName", "ExecutionTime");
+
+ b.ToTable("AbpAuditLogActions");
+ });
+
+ modelBuilder.Entity("Volo.Abp.AuditLogging.EntityChange", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("AuditLogId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("AuditLogId");
+
+ b.Property("ChangeTime")
+ .HasColumnType("datetime2")
+ .HasColumnName("ChangeTime");
+
+ b.Property("ChangeType")
+ .HasColumnType("tinyint")
+ .HasColumnName("ChangeType");
+
+ b.Property("EntityId")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("nvarchar(128)")
+ .HasColumnName("EntityId");
+
+ b.Property("EntityTenantId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("EntityTypeFullName")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("nvarchar(128)")
+ .HasColumnName("EntityTypeFullName");
+
+ b.Property("ExtraProperties")
+ .HasColumnType("nvarchar(max)")
+ .HasColumnName("ExtraProperties");
+
+ b.Property("TenantId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("TenantId");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AuditLogId");
+
+ b.HasIndex("TenantId", "EntityTypeFullName", "EntityId");
+
+ b.ToTable("AbpEntityChanges");
+ });
+
+ modelBuilder.Entity("Volo.Abp.AuditLogging.EntityPropertyChange", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("EntityChangeId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("NewValue")
+ .HasMaxLength(512)
+ .HasColumnType("nvarchar(512)")
+ .HasColumnName("NewValue");
+
+ b.Property("OriginalValue")
+ .HasMaxLength(512)
+ .HasColumnType("nvarchar(512)")
+ .HasColumnName("OriginalValue");
+
+ b.Property("PropertyName")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("nvarchar(128)")
+ .HasColumnName("PropertyName");
+
+ b.Property("PropertyTypeFullName")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)")
+ .HasColumnName("PropertyTypeFullName");
+
+ b.Property("TenantId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("TenantId");
+
+ b.HasKey("Id");
+
+ b.HasIndex("EntityChangeId");
+
+ b.ToTable("AbpEntityPropertyChanges");
+ });
+
+ modelBuilder.Entity("Volo.Abp.FeatureManagement.FeatureValue", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("nvarchar(128)");
+
+ b.Property("ProviderKey")
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("ProviderName")
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("Value")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("nvarchar(128)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Name", "ProviderName", "ProviderKey");
+
+ b.ToTable("AbpFeatureValues");
+ });
+
+ modelBuilder.Entity("Volo.Abp.Identity.IdentityClaimType", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasMaxLength(40)
+ .HasColumnType("nvarchar(40)")
+ .HasColumnName("ConcurrencyStamp");
+
+ b.Property("Description")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.Property("ExtraProperties")
+ .HasColumnType("nvarchar(max)")
+ .HasColumnName("ExtraProperties");
+
+ b.Property("IsStatic")
+ .HasColumnType("bit");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.Property("Regex")
+ .HasMaxLength(512)
+ .HasColumnType("nvarchar(512)");
+
+ b.Property("RegexDescription")
+ .HasMaxLength(128)
+ .HasColumnType("nvarchar(128)");
+
+ b.Property("Required")
+ .HasColumnType("bit");
+
+ b.Property("ValueType")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.ToTable("AbpClaimTypes");
+ });
+
+ modelBuilder.Entity("Volo.Abp.Identity.IdentityLinkUser", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("SourceTenantId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("SourceUserId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("TargetTenantId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("TargetUserId")
+ .HasColumnType("uniqueidentifier");
+
+ b.HasKey("Id");
+
+ b.HasIndex("SourceUserId", "SourceTenantId", "TargetUserId", "TargetTenantId")
+ .IsUnique()
+ .HasFilter("[SourceTenantId] IS NOT NULL AND [TargetTenantId] IS NOT NULL");
+
+ b.ToTable("AbpLinkUsers");
+ });
+
+ modelBuilder.Entity("Volo.Abp.Identity.IdentityRole", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasMaxLength(40)
+ .HasColumnType("nvarchar(40)")
+ .HasColumnName("ConcurrencyStamp");
+
+ b.Property("ExtraProperties")
+ .HasColumnType("nvarchar(max)")
+ .HasColumnName("ExtraProperties");
+
+ b.Property("IsDefault")
+ .HasColumnType("bit")
+ .HasColumnName("IsDefault");
+
+ b.Property("IsPublic")
+ .HasColumnType("bit")
+ .HasColumnName("IsPublic");
+
+ b.Property("IsStatic")
+ .HasColumnType("bit")
+ .HasColumnName("IsStatic");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.Property("NormalizedName")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.Property("TenantId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("TenantId");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedName");
+
+ b.ToTable("AbpRoles");
+ });
+
+ modelBuilder.Entity("Volo.Abp.Identity.IdentityRoleClaim", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("ClaimType")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.Property("ClaimValue")
+ .HasMaxLength(1024)
+ .HasColumnType("nvarchar(1024)");
+
+ b.Property("RoleId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("TenantId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("TenantId");
+
+ b.HasKey("Id");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AbpRoleClaims");
+ });
+
+ modelBuilder.Entity("Volo.Abp.Identity.IdentitySecurityLog", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("Action")
+ .HasMaxLength(96)
+ .HasColumnType("nvarchar(96)");
+
+ b.Property("ApplicationName")
+ .HasMaxLength(96)
+ .HasColumnType("nvarchar(96)");
+
+ b.Property("BrowserInfo")
+ .HasMaxLength(512)
+ .HasColumnType("nvarchar(512)");
+
+ b.Property("ClientId")
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("ClientIpAddress")
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasMaxLength(40)
+ .HasColumnType("nvarchar(40)")
+ .HasColumnName("ConcurrencyStamp");
+
+ b.Property("CorrelationId")
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("CreationTime")
+ .HasColumnType("datetime2");
+
+ b.Property("ExtraProperties")
+ .HasColumnType("nvarchar(max)")
+ .HasColumnName("ExtraProperties");
+
+ b.Property("Identity")
+ .HasMaxLength(96)
+ .HasColumnType("nvarchar(96)");
+
+ b.Property("TenantId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("TenantId");
+
+ b.Property("TenantName")
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("UserId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("UserName")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("TenantId", "Action");
+
+ b.HasIndex("TenantId", "ApplicationName");
+
+ b.HasIndex("TenantId", "Identity");
+
+ b.HasIndex("TenantId", "UserId");
+
+ b.ToTable("AbpSecurityLogs");
+ });
+
+ modelBuilder.Entity("Volo.Abp.Identity.IdentityUser", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("AccessFailedCount")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int")
+ .HasDefaultValue(0)
+ .HasColumnName("AccessFailedCount");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasMaxLength(40)
+ .HasColumnType("nvarchar(40)")
+ .HasColumnName("ConcurrencyStamp");
+
+ b.Property("CreationTime")
+ .HasColumnType("datetime2")
+ .HasColumnName("CreationTime");
+
+ b.Property("CreatorId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("CreatorId");
+
+ b.Property("DeleterId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("DeleterId");
+
+ b.Property("DeletionTime")
+ .HasColumnType("datetime2")
+ .HasColumnName("DeletionTime");
+
+ b.Property("Email")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)")
+ .HasColumnName("Email");
+
+ b.Property("EmailConfirmed")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bit")
+ .HasDefaultValue(false)
+ .HasColumnName("EmailConfirmed");
+
+ b.Property("ExtraProperties")
+ .HasColumnType("nvarchar(max)")
+ .HasColumnName("ExtraProperties");
+
+ b.Property("IsDeleted")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bit")
+ .HasDefaultValue(false)
+ .HasColumnName("IsDeleted");
+
+ b.Property("IsExternal")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bit")
+ .HasDefaultValue(false)
+ .HasColumnName("IsExternal");
+
+ b.Property("LastModificationTime")
+ .HasColumnType("datetime2")
+ .HasColumnName("LastModificationTime");
+
+ b.Property("LastModifierId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("LastModifierId");
+
+ b.Property("LockoutEnabled")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bit")
+ .HasDefaultValue(false)
+ .HasColumnName("LockoutEnabled");
+
+ b.Property("LockoutEnd")
+ .HasColumnType("datetimeoffset");
+
+ b.Property("Name")
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)")
+ .HasColumnName("Name");
+
+ b.Property("NormalizedEmail")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)")
+ .HasColumnName("NormalizedEmail");
+
+ b.Property("NormalizedUserName")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)")
+ .HasColumnName("NormalizedUserName");
+
+ b.Property("PasswordHash")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)")
+ .HasColumnName("PasswordHash");
+
+ b.Property("PhoneNumber")
+ .HasMaxLength(16)
+ .HasColumnType("nvarchar(16)")
+ .HasColumnName("PhoneNumber");
+
+ b.Property("PhoneNumberConfirmed")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bit")
+ .HasDefaultValue(false)
+ .HasColumnName("PhoneNumberConfirmed");
+
+ b.Property("SecurityStamp")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)")
+ .HasColumnName("SecurityStamp");
+
+ b.Property("Surname")
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)")
+ .HasColumnName("Surname");
+
+ b.Property("TenantId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("TenantId");
+
+ b.Property("TwoFactorEnabled")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bit")
+ .HasDefaultValue(false)
+ .HasColumnName("TwoFactorEnabled");
+
+ b.Property("UserName")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)")
+ .HasColumnName("UserName");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Email");
+
+ b.HasIndex("NormalizedEmail");
+
+ b.HasIndex("NormalizedUserName");
+
+ b.HasIndex("UserName");
+
+ b.ToTable("AbpUsers");
+ });
+
+ modelBuilder.Entity("Volo.Abp.Identity.IdentityUserClaim", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("ClaimType")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.Property("ClaimValue")
+ .HasMaxLength(1024)
+ .HasColumnType("nvarchar(1024)");
+
+ b.Property("TenantId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("TenantId");
+
+ b.Property("UserId")
+ .HasColumnType("uniqueidentifier");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AbpUserClaims");
+ });
+
+ modelBuilder.Entity("Volo.Abp.Identity.IdentityUserLogin", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("LoginProvider")
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("ProviderDisplayName")
+ .HasMaxLength(128)
+ .HasColumnType("nvarchar(128)");
+
+ b.Property("ProviderKey")
+ .IsRequired()
+ .HasMaxLength(196)
+ .HasColumnType("nvarchar(196)");
+
+ b.Property("TenantId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("TenantId");
+
+ b.HasKey("UserId", "LoginProvider");
+
+ b.HasIndex("LoginProvider", "ProviderKey");
+
+ b.ToTable("AbpUserLogins");
+ });
+
+ modelBuilder.Entity("Volo.Abp.Identity.IdentityUserOrganizationUnit", b =>
+ {
+ b.Property("OrganizationUnitId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("UserId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("CreationTime")
+ .HasColumnType("datetime2")
+ .HasColumnName("CreationTime");
+
+ b.Property("CreatorId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("CreatorId");
+
+ b.Property("TenantId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("TenantId");
+
+ b.HasKey("OrganizationUnitId", "UserId");
+
+ b.HasIndex("UserId", "OrganizationUnitId");
+
+ b.ToTable("AbpUserOrganizationUnits");
+ });
+
+ modelBuilder.Entity("Volo.Abp.Identity.IdentityUserRole", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("RoleId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("TenantId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("TenantId");
+
+ b.HasKey("UserId", "RoleId");
+
+ b.HasIndex("RoleId", "UserId");
+
+ b.ToTable("AbpUserRoles");
+ });
+
+ modelBuilder.Entity("Volo.Abp.Identity.IdentityUserToken", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("LoginProvider")
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("Name")
+ .HasMaxLength(128)
+ .HasColumnType("nvarchar(128)");
+
+ b.Property("TenantId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("TenantId");
+
+ b.Property("Value")
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("UserId", "LoginProvider", "Name");
+
+ b.ToTable("AbpUserTokens");
+ });
+
+ modelBuilder.Entity("Volo.Abp.Identity.OrganizationUnit", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("Code")
+ .IsRequired()
+ .HasMaxLength(95)
+ .HasColumnType("nvarchar(95)")
+ .HasColumnName("Code");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasMaxLength(40)
+ .HasColumnType("nvarchar(40)")
+ .HasColumnName("ConcurrencyStamp");
+
+ b.Property("CreationTime")
+ .HasColumnType("datetime2")
+ .HasColumnName("CreationTime");
+
+ b.Property("CreatorId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("CreatorId");
+
+ b.Property("DeleterId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("DeleterId");
+
+ b.Property("DeletionTime")
+ .HasColumnType("datetime2")
+ .HasColumnName("DeletionTime");
+
+ b.Property("DisplayName")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("nvarchar(128)")
+ .HasColumnName("DisplayName");
+
+ b.Property("ExtraProperties")
+ .HasColumnType("nvarchar(max)")
+ .HasColumnName("ExtraProperties");
+
+ b.Property("IsDeleted")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bit")
+ .HasDefaultValue(false)
+ .HasColumnName("IsDeleted");
+
+ b.Property("LastModificationTime")
+ .HasColumnType("datetime2")
+ .HasColumnName("LastModificationTime");
+
+ b.Property("LastModifierId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("LastModifierId");
+
+ b.Property("ParentId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("TenantId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("TenantId");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Code");
+
+ b.HasIndex("ParentId");
+
+ b.ToTable("AbpOrganizationUnits");
+ });
+
+ modelBuilder.Entity("Volo.Abp.Identity.OrganizationUnitRole", b =>
+ {
+ b.Property("OrganizationUnitId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("RoleId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("CreationTime")
+ .HasColumnType("datetime2")
+ .HasColumnName("CreationTime");
+
+ b.Property("CreatorId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("CreatorId");
+
+ b.Property("TenantId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("TenantId");
+
+ b.HasKey("OrganizationUnitId", "RoleId");
+
+ b.HasIndex("RoleId", "OrganizationUnitId");
+
+ b.ToTable("AbpOrganizationUnitRoles");
+ });
+
+ modelBuilder.Entity("Volo.Abp.PermissionManagement.PermissionGrant", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("nvarchar(128)");
+
+ b.Property("ProviderKey")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("ProviderName")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("TenantId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("TenantId");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Name", "ProviderName", "ProviderKey");
+
+ b.ToTable("AbpPermissionGrants");
+ });
+
+ modelBuilder.Entity("Volo.Abp.SettingManagement.Setting", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("nvarchar(128)");
+
+ b.Property("ProviderKey")
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("ProviderName")
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("Value")
+ .IsRequired()
+ .HasMaxLength(2048)
+ .HasColumnType("nvarchar(2048)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Name", "ProviderName", "ProviderKey");
+
+ b.ToTable("AbpSettings");
+ });
+
+ modelBuilder.Entity("Volo.Abp.TenantManagement.Tenant", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasMaxLength(40)
+ .HasColumnType("nvarchar(40)")
+ .HasColumnName("ConcurrencyStamp");
+
+ b.Property("CreationTime")
+ .HasColumnType("datetime2")
+ .HasColumnName("CreationTime");
+
+ b.Property("CreatorId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("CreatorId");
+
+ b.Property("DeleterId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("DeleterId");
+
+ b.Property("DeletionTime")
+ .HasColumnType("datetime2")
+ .HasColumnName("DeletionTime");
+
+ b.Property("ExtraProperties")
+ .HasColumnType("nvarchar(max)")
+ .HasColumnName("ExtraProperties");
+
+ b.Property("IsDeleted")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bit")
+ .HasDefaultValue(false)
+ .HasColumnName("IsDeleted");
+
+ b.Property("LastModificationTime")
+ .HasColumnType("datetime2")
+ .HasColumnName("LastModificationTime");
+
+ b.Property("LastModifierId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("LastModifierId");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Name");
+
+ b.ToTable("AbpTenants");
+ });
+
+ modelBuilder.Entity("Volo.Abp.TenantManagement.TenantConnectionString", b =>
+ {
+ b.Property("TenantId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("Name")
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("Value")
+ .IsRequired()
+ .HasMaxLength(1024)
+ .HasColumnType("nvarchar(1024)");
+
+ b.HasKey("TenantId", "Name");
+
+ b.ToTable("AbpTenantConnectionStrings");
+ });
+
+ modelBuilder.Entity("Volo.Abp.AuditLogging.AuditLogAction", b =>
+ {
+ b.HasOne("Volo.Abp.AuditLogging.AuditLog", null)
+ .WithMany("Actions")
+ .HasForeignKey("AuditLogId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Volo.Abp.AuditLogging.EntityChange", b =>
+ {
+ b.HasOne("Volo.Abp.AuditLogging.AuditLog", null)
+ .WithMany("EntityChanges")
+ .HasForeignKey("AuditLogId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Volo.Abp.AuditLogging.EntityPropertyChange", b =>
+ {
+ b.HasOne("Volo.Abp.AuditLogging.EntityChange", null)
+ .WithMany("PropertyChanges")
+ .HasForeignKey("EntityChangeId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Volo.Abp.Identity.IdentityRoleClaim", b =>
+ {
+ b.HasOne("Volo.Abp.Identity.IdentityRole", null)
+ .WithMany("Claims")
+ .HasForeignKey("RoleId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Volo.Abp.Identity.IdentityUserClaim", b =>
+ {
+ b.HasOne("Volo.Abp.Identity.IdentityUser", null)
+ .WithMany("Claims")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Volo.Abp.Identity.IdentityUserLogin", b =>
+ {
+ b.HasOne("Volo.Abp.Identity.IdentityUser", null)
+ .WithMany("Logins")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Volo.Abp.Identity.IdentityUserOrganizationUnit", b =>
+ {
+ b.HasOne("Volo.Abp.Identity.OrganizationUnit", null)
+ .WithMany()
+ .HasForeignKey("OrganizationUnitId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("Volo.Abp.Identity.IdentityUser", null)
+ .WithMany("OrganizationUnits")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Volo.Abp.Identity.IdentityUserRole", b =>
+ {
+ b.HasOne("Volo.Abp.Identity.IdentityRole", null)
+ .WithMany()
+ .HasForeignKey("RoleId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("Volo.Abp.Identity.IdentityUser", null)
+ .WithMany("Roles")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Volo.Abp.Identity.IdentityUserToken", b =>
+ {
+ b.HasOne("Volo.Abp.Identity.IdentityUser", null)
+ .WithMany("Tokens")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Volo.Abp.Identity.OrganizationUnit", b =>
+ {
+ b.HasOne("Volo.Abp.Identity.OrganizationUnit", null)
+ .WithMany()
+ .HasForeignKey("ParentId");
+ });
+
+ modelBuilder.Entity("Volo.Abp.Identity.OrganizationUnitRole", b =>
+ {
+ b.HasOne("Volo.Abp.Identity.OrganizationUnit", null)
+ .WithMany("Roles")
+ .HasForeignKey("OrganizationUnitId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("Volo.Abp.Identity.IdentityRole", null)
+ .WithMany()
+ .HasForeignKey("RoleId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Volo.Abp.TenantManagement.TenantConnectionString", b =>
+ {
+ b.HasOne("Volo.Abp.TenantManagement.Tenant", null)
+ .WithMany("ConnectionStrings")
+ .HasForeignKey("TenantId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Volo.Abp.AuditLogging.AuditLog", b =>
+ {
+ b.Navigation("Actions");
+
+ b.Navigation("EntityChanges");
+ });
+
+ modelBuilder.Entity("Volo.Abp.AuditLogging.EntityChange", b =>
+ {
+ b.Navigation("PropertyChanges");
+ });
+
+ modelBuilder.Entity("Volo.Abp.Identity.IdentityRole", b =>
+ {
+ b.Navigation("Claims");
+ });
+
+ modelBuilder.Entity("Volo.Abp.Identity.IdentityUser", b =>
+ {
+ b.Navigation("Claims");
+
+ b.Navigation("Logins");
+
+ b.Navigation("OrganizationUnits");
+
+ b.Navigation("Roles");
+
+ b.Navigation("Tokens");
+ });
+
+ modelBuilder.Entity("Volo.Abp.Identity.OrganizationUnit", b =>
+ {
+ b.Navigation("Roles");
+ });
+
+ modelBuilder.Entity("Volo.Abp.TenantManagement.Tenant", b =>
+ {
+ b.Navigation("ConnectionStrings");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Migrations/20210528061540_Initial.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Migrations/20210528061540_Initial.cs
new file mode 100644
index 0000000000..c8f1e05a17
--- /dev/null
+++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Migrations/20210528061540_Initial.cs
@@ -0,0 +1,726 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+namespace MyCompanyName.MyProjectName.Blazor.Server.Host.Migrations
+{
+ public partial class Initial : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "AbpAuditLogs",
+ columns: table => new
+ {
+ Id = table.Column(type: "uniqueidentifier", nullable: false),
+ ApplicationName = table.Column(type: "nvarchar(96)", maxLength: 96, nullable: true),
+ UserId = table.Column(type: "uniqueidentifier", nullable: true),
+ UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true),
+ TenantId = table.Column(type: "uniqueidentifier", nullable: true),
+ TenantName = table.Column(type: "nvarchar(max)", nullable: true),
+ ImpersonatorUserId = table.Column(type: "uniqueidentifier", nullable: true),
+ ImpersonatorTenantId = table.Column(type: "uniqueidentifier", nullable: true),
+ ExecutionTime = table.Column(type: "datetime2", nullable: false),
+ ExecutionDuration = table.Column(type: "int", nullable: false),
+ ClientIpAddress = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true),
+ ClientName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: true),
+ ClientId = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true),
+ CorrelationId = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true),
+ BrowserInfo = table.Column(type: "nvarchar(512)", maxLength: 512, nullable: true),
+ HttpMethod = table.Column(type: "nvarchar(16)", maxLength: 16, nullable: true),
+ Url = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true),
+ Exceptions = table.Column(type: "nvarchar(4000)", maxLength: 4000, nullable: true),
+ Comments = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true),
+ HttpStatusCode = table.Column(type: "int", nullable: true),
+ ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true),
+ ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_AbpAuditLogs", x => x.Id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "AbpClaimTypes",
+ columns: table => new
+ {
+ Id = table.Column(type: "uniqueidentifier", nullable: false),
+ Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false),
+ Required = table.Column(type: "bit", nullable: false),
+ IsStatic = table.Column