Merge branch 'rel-6.0' into issue-11401

pull/13700/head
Mahmut Gundogdu 3 years ago
commit 9878e2f63f

@ -124,7 +124,7 @@ For more samples, go to [ABP CLI Create Solution Samples](CLI-New-Command-Sample
* `--separate-auth-server`: The Identity Server project comes as a separate project and runs at a different endpoint. It separates the Identity Server from the API Host application. If not specified, you will have a single endpoint in the server side.
* `--mobile` or `-m`: Specifies the mobile application framework. If not specified, no mobile application will be created. Available options:
* `react-native`: React Native.
* `maui`: MAUI.
* `maui`: MAUI. This mobile option is only available for ABP Commercial.
* `--database-provider` or `-d`: Specifies the database provider. Default provider is `ef`. Available providers:
* `ef`: Entity Framework Core.
* `mongodb`: MongoDB.
@ -145,7 +145,8 @@ For more samples, go to [ABP CLI Create Solution Samples](CLI-New-Command-Sample
* `mongodb`: MongoDB.
* `--theme`: Specifes the theme. Default theme is `leptonx-lite`. Available themes:
* `leptonx-lite`: [LeptonX Lite Theme](/Themes/LeptonXLite/mvc.md).
* `basic`: [Basic Theme](/UI/AspNetCore/Basic-Theme.md).
* `basic`: [Basic Theme](/UI/AspNetCore/Basic-Theme.md).
* **`maui`**: .NET MAUI. A minimalist .NET MAUI application will be created if you specify this option.
* `--output-folder` or `-o`: Specifies the output folder. Default value is the current directory.
* `--version` or `-v`: Specifies the ABP & template version. It can be a [release tag](https://github.com/abpframework/abp/releases) or a [branch name](https://github.com/abpframework/abp/branches). Uses the latest release if not specified. Most of the times, you will want to use the latest version.
* `--preview`: Use latest preview version.
@ -245,7 +246,7 @@ It can also create a new module for your solution and add it to your solution. S
> A business module generally consists of several packages (because of layering, different database provider options or other reasons). Using `add-module` command dramatically simplifies adding a module to a solution. However, each module may require some additional configurations which is generally indicated in the documentation of the related module.
Usage
Usage:
````bash
abp add-module <module-name> [options]
@ -279,7 +280,7 @@ abp add-module ProductManagement --new --add-to-solution-file
Lists names of open-source application modules.
Usage
Usage:
````bash
abp list-modules [options]
@ -295,11 +296,21 @@ abp list-modules
* `--include-pro-modules`: Includes commercial (pro) modules in the output.
### list-templates
Lists all available templates to create a solution.
Usage:
```bash
abp list-templates
```
### get-source
Downloads the source code of a module to your computer.
Usage
Usage:
````bash
abp get-source <module-name> [options]

@ -0,0 +1,425 @@
# Deploy Abp Webapp to Azure App Service
> In this document, you'll learn how to create and deploy your first abp web app to [Azure App Service](https://docs.microsoft.com/en-us/azure/app-service/overview). The App Service supports various versions of .NET apps, and provides a highly scalable, self-patching web hosting service. Abp web apps are cross-platform and can be hosted on Linux and Windows.
****Prerequisites****
- An Azure account with an active subscription. [Create an account for free](https://azure.microsoft.com/free/dotnet).
- A GitHub account [Create an account for free](http://github.com/).
## Create a new ABP Framework application
Create a repository on [GitHub.com](https://github.com/) (keep all the default settings)
Open the command prompt and clone the repository into a folder on your computer
```bash
git clone https://github.com/your-username/your-repository-name.git
```
Check your dotnet version. It should be at least 3.1.x
```bash
dotnet --version
```
Install or update the *ABP CLI* using a command line window
```bash
dotnet tool install -g Volo.Abp.Cli || dotnet tool update -g Volo.Abp.Cli
```
Open the command prompt in the *GitHub repository folder* and create a *new abp Blazor solution* with the command below
```bash
abp new YourAppName -u blazor
```
Open the command prompt in the *[YourAppName].DbMigrator* project and enter the command below to apply the database migrations
```bash
dotnet run
```
Open the command prompt in the *[YourAppName].HttpApi.Host* project to run the API project
```bash
dotnet run
```
Navigate to the *applicationUrl* specified in *the launchSettings.json* file of the *[YourAppName].HttpApi.Host project*. You should get the *Swagger window*
Open the command prompt in the *[YourAppName].Blazor* folder and enter the command below to run the Blazor project
```bash
dotnet run
```
Navigate to the *applicationUrl* specified in the *launchSettings.json* file of the *[YourAppName].Blazor* project. You should get the *ABP Framework Welcome window*
Stop both the *API* and the *Blazor* project by pressing **CTRL+C**
Before the github commit, you have to delete "**/wwwroot/libs/*" at *.gitignore* file.
![azdevops-23](../images/azdevops-23.png)
Open the command prompt in the root folder of your project and *add, commit and push* all your changes to your GitHub repository
```bash
git add .
git commit -m initialcommit
git push
```
## Create a SQL Database on Azure and change the connection string in the appsettings.json files
* Login into [Azure Portal](https://portal.azure.com/)
* Click **Create a resource**
* Search for *SQL Database*
* Click the **Create** button in the *SQL Database window*
* Create a new resource group. Name it *rg[YourAppName]*
* Enter *[YourAppName]Db* as database name
* Create a new Server and name it *[yourappname]server*
* Enter a serveradmin login and passwords. Click the **OK** button
* Select your *Location*
* Check *Allow Azure services to access server*
* Click **Configure database**. Go to the *Basic* version and click the **Apply** button
* Click the **Review + create** button. Click **Create**
* Click **Go to resource** and click **SQL server** when the SQL Database is created
* Click **Networking** under Security left side menu
* Select **Selected networks** and click **Add your client IP$ address** at the Firewall rules
* Select **Allow Azure and resources to access this seerver** and save
* Go to your **SQL database**, click **Connection strings** and copy the connection string
* Copy/paste the appsettings.json files of the [YourAppName].HttpApi.Host and the [YourAppName].DbMigrator project
* Do not forget to replace {your_password} with the correct server password you entered in Azure SQL Database
Open the command prompt in the [YourAppName].DbMigrator project again and enter the command below to apply the database migrations
```bash
dotnet run
```
Open the command prompt in the [YourAppName].HttpApi.Host project and enter the command below to check your API is working
```bash
dotnet run
```
Stop the [YourAppName].HttpApi.Host by entering CTRL+C
Open the command prompt in the root folder of your project and add, commit and push all your changes to your GitHub repository
```bash
git add .
git commit -m initialcommit
git push
```
## Set up the Build pipeline in AzureDevops and publish the Build Artifacts
* Sign in into Azure DevOps
* Click **New organization** and follow the steps to create a new organisation. Name it [YourAppName]org
* Enter [YourAppName]Proj as project name in the ***Create a project to get started*** window
* Select **Public visibility** and click the **Create project** button
* Click the **Pipelines** button to continue
* Click the **Create Pipeline** button
Select GitHub in the Select your repository window
![azdevops-1](../images/azdevops-1.png)
* Enter the Connection name. [YourAppName]GitHubConnection and click **Authorize using OAuth**
* Select your **GitHub** [YourAppName]repo and click Continue
* Search for **ASP.NET** in the ***Select a template*** window
![azdevops-2](../images/azdevops-2.png)
* Select the ASP.NET Core template and click the **Apply** button
* Add the below commands block as a first step in the pipeline
```
- task: UseDotNet@2
inputs:
packageType: 'sdk'
version: '6.0.106'
```
![azdevops-18](../images/azdevops-18.png)
* Select **Settings** on the second task(Nugetcommand@2) in the pipeline
* Select **Feeds in my Nuget.config** and type **Nuget.config** in the text box
![azdevops-3](../images/azdevops-3.png)
* Add the below commands block to the end of the pipeline
```
- task: PublishBuildArtifacts@1
displayName: 'Publish Artifact'
inputs:
PathtoPublish: '$(build.artifactstagingdirectory)'
ArtifactName: '$(Parameters.ArtifactName)'
condition: succeededOrFailed()
```
![azdevops-4](../images/azdevops-4.png)
```
# ASP.NET
# Build and test ASP.NET projects.
# Add steps that publish symbols, save build artifacts, deploy, and more:
# https://docs.microsoft.com/azure/devops/pipelines/apps/aspnet/build-aspnet-4
trigger:
- main
pool:
vmImage: 'windows-latest'
variables:
solution: '**/*.sln'
buildPlatform: 'Any CPU'
buildConfiguration: 'Release'
steps:
- task: UseDotNet@2
inputs:
packageType: 'sdk'
version: '6.0.106'
- task: NuGetToolInstaller@1
- task: NuGetCommand@2
inputs:
command: 'restore'
restoreSolution: '$(solution)'
feedsToUse: 'config'
nugetConfigPath: 'NuGet.config'
- task: VSBuild@1
inputs:
solution: '$(solution)'
msbuildArgs: '/p:DeployOnBuild=true /p:WebPublishMethod=Package /p:PackageAsSingleFile=true /p:SkipInvalidConfigurations=true /p:PackageLocation="$(build.artifactStagingDirectory)"'
platform: '$(buildPlatform)'
configuration: '$(buildConfiguration)'
- task: VSTest@2
inputs:
platform: '$(buildPlatform)'
configuration: '$(buildConfiguration)'
- task: PublishBuildArtifacts@1
displayName: 'Publish Artifact'
inputs:
PathtoPublish: '$(build.artifactstagingdirectory)'
ArtifactName: '$(Parameters.ArtifactName)'
publishLocation: 'Container'
condition: succeededOrFailed()
```
* Click **Save & queue** in the top menu. Click **Save & queue** again and click **Save and run** to run the Build pipeline
* When the Build pipeline has finished. Click **1 published; 1 consumed**
## Create a Web App in the Azure Portal to deploy [YourAppName].HttpApi.Host project
* Search for Web App in the *Search the Marketplace* field
* Click the **Create** button in the Web App window
* Select rg[YourAppName] in the *Resource Group* dropdown
* Enter [YourAppName]API in the *Name input* field
* Select code, .NET Core 3.1 (LTS) and windows as *Operating System*
* Enter [YourAppName]API in the *Name input* field
* Select .NET Core 3.1 (LTS) in the *Runtime stack* dropdown
* Select Windows as *Operating System*
* Select the same *Region* as in the SQL server you created in Part 3
![azdevops-5](../images/azdevops-5.png)
* Click **Create new** in the Windows Plan. Name it [YourAppName]ApiWinPlan
* Click **Change size** in Sku and size. Go to the Dev/Test Free F1 version and click the **Apply** button
![azdevops-6](../images/azdevops-6.png)
* Click the **Review + create** button. Click the **Create** button
* Click **Go to resource** when the Web App has been created
## Create a Release pipeline in the AzureDevops and deploy [YourAppName].HttpApi.Host project
* Sign in into [Azure DevOps](https://azure.microsoft.com/en-us/services/devops/)
* Click [YourAppName]Proj and click **Releases** in the *Pipelines* menu
* Click the **New pipeline** button in the *No release pipelines found* window
* Select *Azure App Service deployment* and click the **Apply** button
![azdevops-7](../images/azdevops-7.png)
* Enter *[YourAppName]staging* in the *Stage name* field in the *Stage* window. And close the window
* Click **+ Add an artifact** in the *Pipeline* tab
* Select the **Build** icon as *Source type* in the *Add an artifact* window
* Select Build pipeline in the *Source (build pipeline)* dropdown and click the **Add** button
![azdevops-8](../images/azdevops-8.png)
* Click the **Continuous deployment trigger (thunderbolt icon)**
* Set the toggle to **Enabled** in the the *Continuous deployment trigger* window
* Click **+ Add** in *No filters added*. Select **Include** in the *Type* dropdown. Select your branch in the *Build branch* dropdown and close the window
![azdevops-9](../images/azdevops-9.png)
* Click **the little red circle with the exclamation mark** in the *Tasks* tab menu
* Select your subscription in the *Azure subscription* dropdown.
![azdevops-10](../images/azdevops-10.png)
* Click **Authorize** and enter your credentials in the next screens
* After Authorization, select the **[YourAppName]API** in the *App service name* dropdown
* Click the **Deploy Azure App Service** task
* Select **[YourAppName].HttpApi.Host.zip** in the *Package or folder* input field
![azdevops-11](../images/azdevops-11.png)
* Click the **Save** icon in the top menu and click **OK**
* Click **Create release** in the top menu. Click **Create** to create a release
* Click the *Pipeline* tab and wait until the Deployment succeeds
![azdevops-12](../images/azdevops-12.png)
* Open a browser and navigate to the URL of your Web App
```
https://[YourAppName]api.azurewebsites.net
```
![azdevops-13](../images/azdevops-13.png)
## Create a Web App in Azure Portal to deploy [YourAppName].Blazor project
* Login into [Azure Portal](https://portal.azure.com/)
* Click **Create a resource**
* Search for *Web App* in the *Search the Marketplace* field
* Click the **Create** button in the *Web App* window
* Select *rg[YourAppName]* in the *Resource Group* dropdown
* Enter *[YourAppName]Blazor* in the *Name* input field
* Select *.NET Core 3.1 (LTS)* in the *Runtime stack* dropdown
* Select *Windows* as *Operating System*
* Select the same region as the SQL server you created in Part 3
* Select the [YourAppName]ApiWinPlan in the *Windows Plan* dropdown
![azdevops-14](../images/azdevops-14.png)
* Click the **Review + create** button. Click **Create** button
* Click **Go to resource** when the Web App has been created
* Copy the URL of the Blazor Web App for later use
```
https://[YourAppName]blazor.azurewebsites.net
```
## Change the Web App configuration for the Azure App Service
Copy the URL of the Api Host and Blazor Web App. Change appsettings.json files in the Web App as follows images.
![azdevops-19](../images/azdevops-19.png)
![azdevops-20](../images/azdevops-20.png)
![azdevops-21](../images/azdevops-21.png)
## Add an extra Stage in the Release pipeline in the AzureDevops to deploy [YourAppName].Blazor project
* Go to the *Release* pipeline in [Azure DevOps](https://azure.microsoft.com/en-us/services/devops/) and click **Edit**
* Click the **+ Add** link and add a **New Stage**
![azdevops-15](../images/azdevops-15.png)
* Select *Azure App Service deployment* and click the **Apply** button
* Enter *BlazorDeployment* in the *Stage name* input field and close the *Stage* window
* Click the **little red circle with the exclamation mark** in the BlazorDeployment stage
* Select your subscription in the *Azure subscription* dropdown
* Select your Blazor Web App in the *App service name* dropdown
* Click the **Deploy Azure App Service task**
* Select *[YourAppName].Blazor.zip* in the *Package or folder* input field
![azdevops-16](../images/azdevops-16.png)
* Click **Save** in the top menu and click the **OK** button after
* Click **Create release** in the top menu and click the **Create** button
![azdevops-17](../images/azdevops-17.png)
![azdevops-22](../images/azdevops-22.png)

@ -6,4 +6,7 @@ However, there are some topics that you should care about when you are deploying
## Guides
* [Deploying to a clustered environment](Clustered-Environment.md): Explains how to configure your application when you want to run multiple instances of your application concurrently.
* [Deploying to a clustered environment](Clustered-Environment.md): Explains how to configure your application when you want to run multiple instances of your application concurrently.
* [Deploy Abp Webapp to Azure App Service](Deploy-Azure-App-Service.md): Explains how to create and deploy your first abp web app to [Azure App Service](https://docs.microsoft.com/en-us/azure/app-service/overview).

@ -216,7 +216,7 @@ https://documentation.openiddict.com/guides/index.html#events-model
## Migrating Guide
[Migrating from IdentityServer to OpenIddict Step by Step Guide ](Migration-Guides/OpenIddict-Step-by-Step.md)
[Migrating from IdentityServer to OpenIddict Step by Step Guide ](../Migration-Guides/OpenIddict-Step-by-Step.md)
## Sponsor

@ -16,10 +16,11 @@ In order to accomplish these goals, ABP Framework;
### Current Themes
Currently, two themes are **officially provided**:
Currently, three themes are **officially provided**:
* The [Basic Theme](Basic-Theme.md) is the minimalist theme with the plain Bootstrap style. It is **open source and free**.
* The [Lepton Theme](https://commercial.abp.io/themes) is a **commercial** theme developed by the core ABP team and is a part of the [ABP Commercial](https://commercial.abp.io/) license.
* The [LeptonX Theme](https://x.leptontheme.com/) is a theme that has both [commercial](https://docs.abp.io/en/commercial/latest/themes/lepton-x/commercial/angular) and [lite](../../Themes/LeptonXLite/Angular.md) choices.
## Overall

@ -32,10 +32,11 @@ ABP Framework provides a complete [Theming](Theming.md) system with the followin
### Current Themes
Currently, two themes are **officially provided**:
Currently, three themes are **officially provided**:
* The [Basic Theme](Basic-Theme.md) is the minimalist theme with the plain Bootstrap style. It is **open source and free**.
* The [Lepton Theme](https://commercial.abp.io/themes) is a **commercial** theme developed by the core ABP team and is a part of the [ABP Commercial](https://commercial.abp.io/) license.
* The [LeptonX Theme](https://x.leptontheme.com/) is a theme that has both [commercial](https://docs.abp.io/en/commercial/latest/themes/lepton-x/commercial/mvc) and [lite](../../Themes/LeptonXLite/AspNetCore.md) choices.
There are also some community-driven themes for the ABP Framework (you can search on the web).

@ -23,6 +23,8 @@ Page Title can be set as shown in the example below:
### Breadcrumb
> **The [Basic Theme](Basic-Theme.md) currently doesn't implement the breadcrumbs.**
>
> The [LeptonX Lite Theme](../../Themes/LeptonXLite/AspNetCore.md) supports breadcrumbs.
Breadcrumb items can be added to the `PageLayout.Content.BreadCrumb`.
@ -48,11 +50,13 @@ Any item that you add is inserted between Home and Current Page items. You can a
### The Selected Menu Item
> **The [Basic Theme](Basic-Theme.md) currently doesn't implement the selected menu item since it is not applicable to the top menu which is the only option for the Basic Theme for now.**
>
> The [LeptonX Lite Theme](../../Themes/LeptonXLite/AspNetCore.md) supports selected menu item.
You can set the Menu Item name related to this page:
````csharp
```csharp
PageLayout.Content.MenuItemName = "BookStore.Books";
````
```
Menu item name should match a unique menu item name defined using the [Navigation / Menu](Navigation-Menu.md) system. In this case, it is expected from the theme to make the menu item "active" in the main menu.

@ -8,6 +8,12 @@ There is only one **standard toolbar** named "Main" (defined as a constant: `Sta
In the screenshot above, there are two items added to the main toolbar: Language switch component & user menu. You can add your own items here.
Also, [LeptonX Lite Theme](../../Themes/LeptonXLite/AspNetCore.md) has 2 different toolbars for desktop and mobile views which defined as constants: `LeptonXLiteToolbars.Main`, `LeptonXLiteToolbars.MainMobile`.
| LeptonXLiteToolbars.Main | LeptonXLiteToolbars.MainMobile |
| :---: | :---: |
| ![leptonx](../../images/leptonxlite-toolbar-main-example.png) | ![leptonx](../../images/leptonxlite-toolbar-mainmobile-example.png) |
## Example: Add a Notification Icon
In this example, we will add a **notification (bell) icon** to the left of the language switch item. A item in the toolbar should be a **view component**. So, first, create a new view component in your project:

@ -75,10 +75,11 @@ ABP Framework provides a complete [Theming](Theming.md) system with the followin
### Current Themes
Currently, two themes are **officially provided**:
Currently, three themes are **officially provided**:
* The [Basic Theme](Basic-Theme.md) is the minimalist theme with the plain Bootstrap style. It is **open source and free**.
* The [Lepton Theme](https://commercial.abp.io/themes) is a **commercial** theme developed by the core ABP team and is a part of the [ABP Commercial](https://commercial.abp.io/) license.
* The [LeptonX Theme](https://x.leptontheme.com/) is a theme that has both [commercial](https://docs.abp.io/en/commercial/latest/themes/lepton-x/commercial/blazor) and [lite](../../Themes/LeptonXLite/Blazor.md) choices.
### Base Libraries

@ -16,6 +16,8 @@ Once you add the `PageHeader` component to your page, you can control the relate
## Breadcrumb
> **The [Basic Theme](Basic-Theme.md) currently doesn't implement the breadcrumbs.**
>
> The [LeptonX Lite Theme](../../Themes/LeptonXLite/Blazor.md) supports breadcrumbs.
Breadcrumbs can be added using the `BreadcrumbItems` property.

@ -42,6 +42,10 @@ Menu item name can be set on runtime too.
}
```
![leptonx selected menu item](../../images/leptonx-selected-menu-item-example.gif)
> Be aware, The [Basic Theme](../Blazor/Basic-Theme.md) currently doesn't support the selected menu item since it is not applicable to the top menu.
## BreadCrumbs

@ -8,6 +8,12 @@ There is only one **standard toolbar** named "Main" (defined as a constant: `Sta
In the screenshot above, there are two items added to the main toolbar: Language switch component & user menu. You can add your own items here.
Also, [LeptonX Lite Theme](../../Themes/LeptonXLite/Blazor.md) has 2 different toolbars for desktop and mobile views which defined as constants: `LeptonXLiteToolbars.Main`, `LeptonXLiteToolbars.MainMobile`.
| LeptonXLiteToolbars.Main | LeptonXLiteToolbars.MainMobile |
| :---: | :---: |
| ![leptonx](../../images/leptonxlite-toolbar-main-example.png) | ![leptonx](../../images/leptonxlite-toolbar-mainmobile-example.png) |
## Example: Add a Notification Icon
In this example, we will add a **notification (bell) icon** to the left of the language switch item. A item in the toolbar should be a **Razor Component**. So, first, create a new razor component in your project (the location of the component doesn't matter):

@ -43,6 +43,10 @@
"text": "WPF Application",
"path": "Startup-Templates/WPF.md"
},
{
"text": "MAUI",
"path": "Startup-Templates/MAUI.md"
},
{
"text": "Empty Web Project",
"path": "Getting-Started-AspNetCore-Application.md"
@ -1278,6 +1282,10 @@
{
"text": "Deploying to a Clustered Environment",
"path": "Deployment/Clustered-Environment.md"
},
{
"text": "Deploy Abp Webapp to Azure App Service",
"path": "Deployment/Deploy-Azure-App-Service.md"
}
]
},

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 487 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 491 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 539 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 415 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 400 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

@ -123,6 +123,7 @@ abp new Acme.BookStore
* `module`: [Module template](Startup-Templates/Module.md). 其他选项:
* `--no-ui`: 不包含UI.仅创建服务模块(也称为微服务 - 没有UI).
* **`console`**: [Console template](Startup-Templates/Console.md).
* **`maui`**: [Maui template](Startup-Templates/MAUI.md).
* **`app-nolayers`**: 应用程序单层模板
* `--ui` 或者 `-u`: 指定ui框架.默认`mvc`框架.其他选项:
* `mvc`: ASP.NET Core MVC.

@ -61,6 +61,10 @@
{
"text": "WPF",
"path": "Startup-Templates/WPF.md"
},
{
"text": "MAUI",
"path": "Startup-Templates/MAUI.md"
}
]
},

@ -1,11 +1,14 @@
using System;
using System.Linq;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Volo.Abp.AspNetCore.ExceptionHandling;
using Volo.Abp.AspNetCore.WebClientInfo;
using Volo.Abp.Auditing;
using Volo.Abp.DependencyInjection;
using Volo.Abp.ExceptionHandling;
namespace Volo.Abp.AspNetCore.Auditing;
@ -57,28 +60,46 @@ public class AspNetCoreAuditLogContributor : AuditLogContributor, ITransientDepe
public override void PostContribute(AuditLogContributionContext context)
{
if (context.AuditInfo.HttpStatusCode != null)
{
return;
}
var httpContext = context.ServiceProvider.GetRequiredService<IHttpContextAccessor>().HttpContext;
if (httpContext == null)
{
return;
}
if (context.AuditInfo.HttpStatusCode == null)
if (context.AuditInfo.Exceptions.Any())
{
context.AuditInfo.HttpStatusCode = httpContext.Response.StatusCode;
var httpExceptionStatusCodeFinder = context.ServiceProvider.GetRequiredService<IHttpExceptionStatusCodeFinder>();
foreach (var auditInfoException in context.AuditInfo.Exceptions)
{
var statusCode = httpExceptionStatusCodeFinder.GetStatusCode(httpContext, auditInfoException);
context.AuditInfo.HttpStatusCode = (int) statusCode;
}
if (context.AuditInfo.HttpStatusCode != null)
{
return;
}
}
context.AuditInfo.HttpStatusCode = httpContext.Response.StatusCode;
}
protected virtual string BuildUrl(HttpContext httpContext)
{
//TODO: Add options to include/exclude query, schema and host
var uriBuilder = new UriBuilder();
uriBuilder.Scheme = httpContext.Request.Scheme;
uriBuilder.Host = httpContext.Request.Host.Host;
uriBuilder.Path = httpContext.Request.Path.ToString();
uriBuilder.Query = httpContext.Request.QueryString.ToString();
var uriBuilder = new UriBuilder
{
Scheme = httpContext.Request.Scheme,
Host = httpContext.Request.Host.Host,
Path = httpContext.Request.Path.ToString(),
Query = httpContext.Request.QueryString.ToString()
};
return uriBuilder.Uri.AbsolutePath;
}

@ -123,7 +123,7 @@ public class NewCommand : ProjectCreationCommandBase, IConsoleCommand, ITransien
sb.AppendLine("--tiered (if supported by the template)");
sb.AppendLine("--no-ui (if supported by the template)");
sb.AppendLine("--no-random-port (Use template's default ports)");
sb.AppendLine("--separate-identity-server (if supported by the template)");
sb.AppendLine("--separate-auth-server (if supported by the template)");
sb.AppendLine("--local-framework-ref --abp-path <your-local-abp-repo-path> (keeps local references to projects instead of replacing with NuGet package references)");
sb.AppendLine("");
sb.AppendLine("Examples:");

@ -285,14 +285,12 @@ public class ChangeThemeStep : ProjectBuildPipelineStep
{"Domain.Shared", "MyCompanyName.MyProjectName.Domain.Shared.csproj"},
{"Application", "MyCompanyName.MyProjectName.Application.csproj"},
{"Application.Contracts", "MyCompanyName.MyProjectName.Application.Contracts.csproj"},
{"Blazor.WebAssembly", "MyCompanyName.MyProjectName.Blazor.csproj"},
{"Blazor.Server", "MyCompanyName.MyProjectName.Blazor.Server.csproj"},
{"HttpApi", "MyCompanyName.MyProjectName.HttpApi.csproj"},
{"HttpApi.Client", "MyCompanyName.MyProjectName.HttpApi.Client.csproj"},
{"Web.Host", "MyCompanyName.MyProjectName.Web.Host.csproj"},
{"Web", "MyCompanyName.MyProjectName.Web.csproj"},
{"HttpApi.Client", "MyCompanyName.MyProjectName.HttpApi.Client.csproj"}
};
AddUiProjectToProjects(projects, context);
foreach (var project in projects)
{
AddLeptonThemeManagementReference(context, project);
@ -314,6 +312,28 @@ public class ChangeThemeStep : ProjectBuildPipelineStep
AddLeptonThemeManagementReference(context, microserviceServiceProject);
}
}
private void AddUiProjectToProjects(Dictionary<string, string> projects, ProjectBuildContext context)
{
if (projects.IsNullOrEmpty())
{
return;
}
switch (context.BuildArgs.UiFramework)
{
case UiFramework.Mvc:
projects["Web.Host"] = "MyCompanyName.MyProjectName.Web.Host.csproj";
projects["Web"] = "MyCompanyName.MyProjectName.Web.csproj";
break;
case UiFramework.Blazor:
projects["Blazor.WebAssembly"] = "MyCompanyName.MyProjectName.Blazor.csproj";
break;
case UiFramework.BlazorServer:
projects["Blazor.Server"] = "MyCompanyName.MyProjectName.Blazor.csproj";
break;
}
}
private void AddLeptonThemeManagementReference(ProjectBuildContext context, KeyValuePair<string, string> projectInfo)
{

@ -14,6 +14,8 @@ public class TemplateCodeDeleteStep : ProjectBuildPipelineStep
file.Name.EndsWith(".json") ||
file.Name.EndsWith(".gitignore") ||
file.Name.EndsWith(".yml") ||
file.Name.EndsWith(".yaml") ||
file.Name.EndsWith(".md") ||
file.Name.EndsWith(".ps1") ||
file.Name.EndsWith(".html") ||
file.Name.EndsWith(".ts") ||

@ -130,6 +130,8 @@ public abstract class MicroserviceTemplateBase : TemplateInfo
steps.Add(new RemoveFolderStep("/apps/blazor"));
steps.Add(new RemoveProjectFromTyeStep("blazor"));
steps.Add(new RemoveProjectFromTyeStep("blazor-server"));
context.Symbols.Add("ui:angular");
break;
case UiFramework.Blazor:
@ -147,7 +149,8 @@ public abstract class MicroserviceTemplateBase : TemplateInfo
null,
"/apps/blazor/src/MyCompanyName.MyProjectName.Blazor.Server"));
steps.Add(new RemoveProjectFromTyeStep("blazor-server"));
context.Symbols.Add("ui:blazor");
break;
case UiFramework.BlazorServer:
@ -169,7 +172,8 @@ public abstract class MicroserviceTemplateBase : TemplateInfo
steps.Add(new TemplateProjectRenameStep("MyCompanyName.MyProjectName.Blazor.Server",
"MyCompanyName.MyProjectName.Blazor"));
steps.Add(new RenameProjectInTyeStep("blazor-server", "blazor"));
context.Symbols.Add("ui:blazor-server");
break;
case UiFramework.Mvc:
@ -186,6 +190,8 @@ public abstract class MicroserviceTemplateBase : TemplateInfo
steps.Add(new RemoveProjectFromTyeStep("blazor-server"));
steps.Add(new RemoveFolderStep("/apps/angular"));
context.Symbols.Add("ui:mvc");
break;
}

@ -1,19 +1,13 @@
using System;
using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection;
namespace Volo.Abp.DependencyInjection;
public class AbpLazyServiceProvider : IAbpLazyServiceProvider, ITransientDependency
[ExposeServices(typeof(IAbpLazyServiceProvider))]
public class AbpLazyServiceProvider : CachedServiceProviderBase, IAbpLazyServiceProvider, ITransientDependency
{
protected IDictionary<Type, object> CachedServices { get; set; }
protected IServiceProvider ServiceProvider { get; set; }
public AbpLazyServiceProvider(IServiceProvider serviceProvider)
: base(serviceProvider)
{
ServiceProvider = serviceProvider;
CachedServices = new Dictionary<Type, object>();
}
public virtual T LazyGetRequiredService<T>()
@ -23,7 +17,7 @@ public class AbpLazyServiceProvider : IAbpLazyServiceProvider, ITransientDepende
public virtual object LazyGetRequiredService(Type serviceType)
{
return CachedServices.GetOrAdd(serviceType, () => ServiceProvider.GetRequiredService(serviceType));
return GetService(serviceType);
}
public virtual T LazyGetService<T>()
@ -33,7 +27,7 @@ public class AbpLazyServiceProvider : IAbpLazyServiceProvider, ITransientDepende
public virtual object LazyGetService(Type serviceType)
{
return CachedServices.GetOrAdd(serviceType, () => ServiceProvider.GetService(serviceType));
return GetService(serviceType);
}
public virtual T LazyGetService<T>(T defaultValue)
@ -53,6 +47,9 @@ public class AbpLazyServiceProvider : IAbpLazyServiceProvider, ITransientDepende
public virtual object LazyGetService(Type serviceType, Func<IServiceProvider, object> factory)
{
return CachedServices.GetOrAdd(serviceType, () => factory(ServiceProvider));
return CachedServices.GetOrAdd(
serviceType,
_ => new Lazy<object>(() => factory(ServiceProvider))
).Value;
}
}

@ -5,21 +5,21 @@ namespace Volo.Abp.DependencyInjection;
public abstract class CachedServiceProviderBase
{
private readonly IServiceProvider _serviceProvider;
private readonly ConcurrentDictionary<Type, Lazy<object>> _cachedServices;
protected IServiceProvider ServiceProvider { get; }
protected ConcurrentDictionary<Type, Lazy<object>> CachedServices { get; }
protected CachedServiceProviderBase(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
_cachedServices = new ConcurrentDictionary<Type, Lazy<object>>();
_cachedServices.TryAdd(typeof(IServiceProvider), new Lazy<object>(() => _serviceProvider));
ServiceProvider = serviceProvider;
CachedServices = new ConcurrentDictionary<Type, Lazy<object>>();
CachedServices.TryAdd(typeof(IServiceProvider), new Lazy<object>(() => ServiceProvider));
}
public virtual object GetService(Type serviceType)
{
return _cachedServices.GetOrAdd(
return CachedServices.GetOrAdd(
serviceType,
_ => new Lazy<object>(() => _serviceProvider.GetService(serviceType))
_ => new Lazy<object>(() => ServiceProvider.GetService(serviceType))
).Value;
}
}
}

@ -0,0 +1,50 @@
using Microsoft.Extensions.DependencyInjection;
using Shouldly;
using Volo.Abp.Modularity;
using Volo.Abp.Testing.Utils;
using Xunit;
namespace Volo.Abp.DependencyInjection;
public class AbpLazyServiceProvider_Tests
{
[Fact]
public void LazyServiceProvider_Should_Cache_Services()
{
using (var application = AbpApplicationFactory.Create<TestModule>())
{
application.Initialize();
var lazyServiceProvider = application.ServiceProvider.GetRequiredService<IAbpLazyServiceProvider>();
var transientTestService1 = lazyServiceProvider.LazyGetRequiredService<TransientTestService>();
var transientTestService2 = lazyServiceProvider.LazyGetRequiredService<TransientTestService>();
transientTestService1.ShouldBeSameAs(transientTestService2);
var testCounter = application.ServiceProvider.GetRequiredService<ITestCounter>();
testCounter.GetValue(nameof(TransientTestService)).ShouldBe(1);
}
}
[DependsOn(typeof(AbpTestBaseModule))]
private class TestModule : AbpModule
{
public TestModule()
{
SkipAutoServiceRegistration = true;
}
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddType<TransientTestService>();
}
}
private class TransientTestService : ITransientDependency
{
public TransientTestService(ITestCounter counter)
{
counter.Increment(nameof(TransientTestService));
}
}
}

@ -3,6 +3,9 @@ using Volo.Abp.Identity;
using Volo.Abp.Localization;
using Volo.Abp.Localization.ExceptionHandling;
using Volo.Abp.Modularity;
using Volo.Abp.ObjectExtending;
using Volo.Abp.ObjectExtending.Modularity;
using Volo.Abp.Threading;
using Volo.Abp.Validation.Localization;
using Volo.Abp.VirtualFileSystem;
@ -13,6 +16,8 @@ namespace Volo.Abp.Account;
)]
public class AbpAccountApplicationContractsModule : AbpModule
{
private readonly static OneTimeRunner OneTimeRunner = new OneTimeRunner();
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpVirtualFileSystemOptions>(options =>
@ -33,4 +38,17 @@ public class AbpAccountApplicationContractsModule : AbpModule
options.MapCodeNamespace("Volo.Account", typeof(AccountResource));
});
}
public override void PostConfigureServices(ServiceConfigurationContext context)
{
OneTimeRunner.Run(() =>
{
ModuleExtensionConfigurationHelper.ApplyEntityConfigurationToApi(
IdentityModuleExtensionConsts.ModuleName,
IdentityModuleExtensionConsts.EntityNames.User,
getApiTypes: new[] { typeof(ProfileDto) },
updateApiTypes: new[] { typeof(UpdateProfileDto) }
);
});
}
}

@ -10,10 +10,11 @@ public class AbpAccountBlazorAutoMapperProfile : Profile
public AbpAccountBlazorAutoMapperProfile()
{
CreateMap<ProfileDto, PersonalInfoModel>()
.MapExtraProperties()
.Ignore(x => x.PhoneNumberConfirmed)
.Ignore(x => x.EmailConfirmed);
CreateMap<PersonalInfoModel, UpdateProfileDto>()
.Ignore(x => x.ExtraProperties);
.MapExtraProperties();
}
}

@ -1,8 +1,12 @@
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Account.Blazor.Pages.Account;
using Volo.Abp.AspNetCore.Components.Web.Theming;
using Volo.Abp.AspNetCore.Components.Web.Theming.Routing;
using Volo.Abp.AutoMapper;
using Volo.Abp.Modularity;
using Volo.Abp.ObjectExtending;
using Volo.Abp.ObjectExtending.Modularity;
using Volo.Abp.Threading;
using Volo.Abp.UI.Navigation;
namespace Volo.Abp.Account.Blazor;
@ -14,6 +18,8 @@ namespace Volo.Abp.Account.Blazor;
)]
public class AbpAccountBlazorModule : AbpModule
{
private readonly static OneTimeRunner OneTimeRunner = new OneTimeRunner();
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddAutoMapperObjectMapper<AbpAccountBlazorModule>();
@ -33,4 +39,17 @@ public class AbpAccountBlazorModule : AbpModule
options.AdditionalAssemblies.Add(typeof(AbpAccountBlazorModule).Assembly);
});
}
public override void PostConfigureServices(ServiceConfigurationContext context)
{
OneTimeRunner.Run(() =>
{
ModuleExtensionConfigurationHelper
.ApplyEntityConfigurationToUi(
IdentityModuleExtensionConsts.ModuleName,
IdentityModuleExtensionConsts.EntityNames.User,
editFormTypes: new[] { typeof(PersonalInfoModel) }
);
});
}
}

@ -1,5 +1,9 @@
@page "/account/manage-profile"
@using Microsoft.AspNetCore.Components.Forms
@using Volo.Abp.Account.Localization
@using Volo.Abp.AspNetCore.Components.Web
@using Volo.Abp.BlazoriseUI.Components.ObjectExtending
@inject AbpBlazorMessageLocalizerHelper<AccountResource> LH
@inherits AbpAccountComponentBase
<Row>
@ -58,6 +62,7 @@
<FieldLabel>@L["DisplayName:PhoneNumber"]</FieldLabel>
<TextEdit @bind-Text="@PersonalInfoModel.PhoneNumber"/>
</Field>
<ExtensionProperties TEntityType="PersonalInfoModel" TResourceType="AccountResource" Entity="@PersonalInfoModel" LH="@LH"/>
<Field>
<SubmitButton Form="UpdatePersonalInfoForm" Clicked="@UpdatePersonalInfoAsync" />
</Field>

@ -2,6 +2,7 @@
using Microsoft.AspNetCore.Components;
using Volo.Abp.AspNetCore.Components.Messages;
using Volo.Abp.Identity;
using Volo.Abp.ObjectExtending;
namespace Volo.Abp.Account.Blazor.Pages.Account;
@ -58,7 +59,7 @@ public partial class AccountManage
await UiMessageService.Success(L["PasswordChanged"]);
}
protected async Task UpdatePersonalInfoAsync()
protected virtual async Task UpdatePersonalInfoAsync()
{
await ProfileAppService.UpdateAsync(
ObjectMapper.Map<PersonalInfoModel, UpdateProfileDto>(PersonalInfoModel)
@ -86,7 +87,7 @@ public class ChangePasswordModel
}
}
public class PersonalInfoModel
public class PersonalInfoModel : ExtensibleObject
{
public string UserName { get; set; }

@ -9,6 +9,7 @@ public class AbpAccountWebAutoMapperProfile : Profile
{
public AbpAccountWebAutoMapperProfile()
{
CreateMap<ProfileDto, AccountProfilePersonalInfoManagementGroupViewComponent.PersonalInfoModel>();
CreateMap<ProfileDto, AccountProfilePersonalInfoManagementGroupViewComponent.PersonalInfoModel>()
.MapExtraProperties();
}
}

@ -2,6 +2,7 @@
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Account.Localization;
using Volo.Abp.Account.Web.Pages.Account;
using Volo.Abp.Account.Web.Pages.Account.Components.ProfileManagementGroup.PersonalInfo;
using Volo.Abp.Account.Web.ProfileManagement;
using Volo.Abp.AspNetCore.Mvc.Localization;
using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
@ -12,6 +13,9 @@ using Volo.Abp.ExceptionHandling;
using Volo.Abp.Http.ProxyScripting.Generators.JQuery;
using Volo.Abp.Identity.AspNetCore;
using Volo.Abp.Modularity;
using Volo.Abp.ObjectExtending;
using Volo.Abp.ObjectExtending.Modularity;
using Volo.Abp.Threading;
using Volo.Abp.UI.Navigation;
using Volo.Abp.VirtualFileSystem;
@ -26,6 +30,8 @@ namespace Volo.Abp.Account.Web;
)]
public class AbpAccountWebModule : AbpModule
{
private readonly static OneTimeRunner OneTimeRunner = new OneTimeRunner();
public override void PreConfigureServices(ServiceConfigurationContext context)
{
context.Services.PreConfigure<AbpMvcDataAnnotationsLocalizationOptions>(options =>
@ -95,4 +101,17 @@ public class AbpAccountWebModule : AbpModule
});
}
public override void PostConfigureServices(ServiceConfigurationContext context)
{
OneTimeRunner.Run(() =>
{
ModuleExtensionConfigurationHelper
.ApplyEntityConfigurationToUi(
IdentityModuleExtensionConsts.ModuleName,
IdentityModuleExtensionConsts.EntityNames.User,
editFormTypes: new[] { typeof(AccountProfilePersonalInfoManagementGroupViewComponent.PersonalInfoModel) }
);
});
}
}

@ -5,6 +5,7 @@ using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.UI.Widgets;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Identity;
using Volo.Abp.ObjectExtending;
using Volo.Abp.Validation;
namespace Volo.Abp.Account.Web.Pages.Account.Components.ProfileManagementGroup.PersonalInfo;
@ -30,7 +31,7 @@ public class AccountProfilePersonalInfoManagementGroupViewComponent : AbpViewCom
return View("~/Pages/Account/Components/ProfileManagementGroup/PersonalInfo/Default.cshtml", model);
}
public class PersonalInfoModel : IHasConcurrencyStamp
public class PersonalInfoModel : ExtensibleObject, IHasConcurrencyStamp
{
[Required]
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxUserNameLength))]
@ -54,7 +55,6 @@ public class AccountProfilePersonalInfoManagementGroupViewComponent : AbpViewCom
[Display(Name = "DisplayName:PhoneNumber")]
public string PhoneNumber { get; set; }
[HiddenInput]
public string ConcurrencyStamp { get; set; }
[HiddenInput] public string ConcurrencyStamp { get; set; }
}
}
}

@ -1,13 +1,24 @@
@using Volo.Abp.Account.Localization
@using Volo.Abp.Users
@using Microsoft.AspNetCore.Mvc.Localization
@using Microsoft.AspNetCore.Mvc.TagHelpers
@using Microsoft.Extensions.Localization
@using Volo.Abp.Account.Web.Pages.Account.Components.ProfileManagementGroup.PersonalInfo
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Alert
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Button
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Grid
@using Volo.Abp.AspNetCore.Mvc.UI.Theming
@using Volo.Abp.Data
@using Volo.Abp.Identity.Settings
@using Volo.Abp.Localization
@using Volo.Abp.ObjectExtending
@using Volo.Abp.Settings
@inject IHtmlLocalizer<AccountResource> L
@inject ICurrentUser CurrentUser
@inject ISettingProvider SettingManager
@inject IThemeManager ThemeManager
@inject IStringLocalizerFactory StringLocalizerFactory
@model Volo.Abp.Account.Web.Pages.Account.Components.ProfileManagementGroup.PersonalInfo.AccountProfilePersonalInfoManagementGroupViewComponent.PersonalInfoModel
@{
var isUserNameUpdateEnabled = string.Equals(await SettingManager.GetOrNullAsync(IdentitySettingNames.User.IsUserNameUpdateEnabled), "true",
@ -19,8 +30,8 @@
<h4>@L["PersonalSettings"]</h4><hr/>
<form method="post" id="PersonalSettingsForm">
<input asp-for="ConcurrencyStamp" />
<input asp-for="ConcurrencyStamp"/>
<abp-input asp-for="UserName" readonly="!isUserNameUpdateEnabled"/>
@ -37,5 +48,39 @@
<abp-input asp-for="PhoneNumber"/>
@foreach (var propertyInfo in ObjectExtensionManager.Instance.GetProperties<AccountProfilePersonalInfoManagementGroupViewComponent.PersonalInfoModel>())
{
if (!propertyInfo.Name.EndsWith("_Text"))
{
if (propertyInfo.Type.IsEnum || !propertyInfo.Lookup.Url.IsNullOrEmpty())
{
if (propertyInfo.Type.IsEnum)
{
Model.ExtraProperties.ToEnum(propertyInfo.Name, propertyInfo.Type);
}
<abp-select asp-for="ExtraProperties[propertyInfo.Name]"
name="ExtraProperties.@propertyInfo.Name"
label="@propertyInfo.GetLocalizedDisplayName(StringLocalizerFactory)"
autocomplete-api-url="@propertyInfo.Lookup.Url"
autocomplete-selected-item-name="@Model.GetProperty(propertyInfo.Name + "_Text")"
autocomplete-selected-item-value="@Model.GetProperty(propertyInfo.Name)"
autocomplete-filter-param-name="@propertyInfo.Lookup.FilterParamName"
autocomplete-items-property-name="@propertyInfo.Lookup.ResultListPropertyName"
autocomplete-display-property-name="@propertyInfo.Lookup.DisplayPropertyName"
autocomplete-value-property-name="@propertyInfo.Lookup.ValuePropertyName">
</abp-select>
}
else
{
<abp-input type="@propertyInfo.GetInputType()"
asp-for="ExtraProperties[propertyInfo.Name]"
name="ExtraProperties.@propertyInfo.Name"
label="@propertyInfo.GetLocalizedDisplayName(StringLocalizerFactory)"
asp-format="@propertyInfo.GetInputFormatOrNull()"
value="@propertyInfo.GetInputValueOrNull(Model.GetProperty(propertyInfo.Name))"/>
}
}
}
<abp-button type="submit" button-type="Primary" text="@L["Submit"].Value"/>
</form>
</form>

@ -9,7 +9,7 @@
return false;
}
var input = $('#PersonalSettingsForm').serializeFormToObject();
var input = $('#PersonalSettingsForm').serializeFormToObject(false);
volo.abp.account.profile.update(input).then(function (result) {
abp.notify.success(l('PersonalSettingsSaved'));

@ -20,11 +20,11 @@ public class ContentParser : ITransientDependency
public Task<List<ContentFragment>> ParseAsync(string content)
{
if (!_options.WidgetConfigs.Any())
if (!_options.WidgetConfigs.Any() || content is null)
{
return Task.FromResult(new List<ContentFragment>
{
new ContentFragment { Type = Markdown }.SetProperty(Content, content),
new ContentFragment { Type = Markdown }.SetProperty(Content, content ?? string.Empty),
});
}

@ -1,6 +1,6 @@
namespace Volo.CmsKit.GlobalResources;
public class GlobalResourceConsts
public static class GlobalResourceConsts
{
public const string GlobalStyleName = "GlobalStyle";

@ -1,8 +1,12 @@
namespace Volo.CmsKit.Public.GlobalResources;
using System;
using Volo.Abp.Application.Dtos;
public class GlobalResourceDto
namespace Volo.CmsKit.Public.GlobalResources;
[Serializable]
public class GlobalResourceDto : AuditedEntityDto
{
public string Name { get; set; }
public string Value { get; set; }
}

@ -5,11 +5,10 @@ using Volo.Abp.Domain.Entities.Events;
using Volo.Abp.EventBus;
using Volo.Abp.ObjectMapping;
using Volo.CmsKit.GlobalResources;
using Volo.CmsKit.Public.GlobalResources;
namespace Volo.CmsKit.Public.GlobalResources.Handlers;
public class GlobalResourceEventHandler:
public class GlobalResourceEventHandler :
ILocalEventHandler<EntityUpdatedEventData<GlobalResource>>,
ITransientDependency
{
@ -23,11 +22,11 @@ public class GlobalResourceEventHandler:
ObjectMapper = objectMapper;
_resourceCache = resourceCache;
}
public async Task HandleEventAsync(EntityUpdatedEventData<GlobalResource> eventData)
{
await _resourceCache.SetAsync(
eventData.Entity.Name,
eventData.Entity.Name,
ObjectMapper.Map<GlobalResource, GlobalResourceDto>(eventData.Entity));
}
}

@ -1 +1,3 @@
<link rel="stylesheet" href="/cms-kit/global-resources/style"/>
@model Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.GlobalResources.Style.GlobalStyleModel
<link rel="stylesheet" href="@("/cms-kit/global-resources/style?v=" + Model.LastModificationTimeTimestamp)"/>

@ -0,0 +1,6 @@
namespace Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.GlobalResources.Style;
public class GlobalStyleModel
{
public long LastModificationTimeTimestamp { get; set; }
}

@ -1,13 +1,33 @@
using System.Threading.Tasks;
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc;
using Volo.CmsKit.Public.GlobalResources;
namespace Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.GlobalResources.Style;
public class GlobalStyleViewComponent : AbpViewComponent
{
protected IGlobalResourcePublicAppService GlobalResourcePublicAppService { get; }
public GlobalStyleViewComponent(IGlobalResourcePublicAppService globalResourcePublicAppService)
{
GlobalResourcePublicAppService = globalResourcePublicAppService;
}
[BindProperty(SupportsGet = true)]
public DateTime? LastModificationTime { get; set; }
public async Task<IViewComponentResult> InvokeAsync()
{
return View("~/Pages/CmsKit/Shared/Components/GlobalResources/Style/Default.cshtml");
var lastModificationTime = (await GlobalResourcePublicAppService.GetGlobalStyleAsync())?.LastModificationTime;
var lastModificationTimeTimestamp = (long)(lastModificationTime.HasValue ? lastModificationTime.Value.Subtract(DateTime.UnixEpoch).TotalSeconds : 0);
return View("~/Pages/CmsKit/Shared/Components/GlobalResources/Style/Default.cshtml",
new GlobalStyleModel()
{
LastModificationTimeTimestamp = lastModificationTimeTimestamp
});
}
}

@ -1,11 +1,11 @@
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Markdig;
using System.Threading.Tasks;
using System.Web;
using Volo.Abp.DependencyInjection;
using Ganss.XSS;
using Markdig;
using Volo.Abp.DependencyInjection;
namespace Volo.CmsKit.Public.Web.Renderers;
@ -20,7 +20,7 @@ public class MarkdownToHtmlRenderer : IMarkdownToHtmlRenderer, ITransientDepende
_htmlSanitizer = new HtmlSanitizer();
}
public async Task<string> RenderAsync(string rawMarkdown, bool allowHtmlTags = true, bool preventXSS = true)
public Task<string> RenderAsync(string rawMarkdown, bool allowHtmlTags = true, bool preventXSS = true)
{
if (!allowHtmlTags)
{
@ -34,7 +34,7 @@ public class MarkdownToHtmlRenderer : IMarkdownToHtmlRenderer, ITransientDepende
html = _htmlSanitizer.Sanitize(html);
}
return html;
return Task.FromResult(html);
}

@ -62,7 +62,7 @@ public class IdentityRole : AggregateRoot<Guid>, IMultiTenant
Name = name;
TenantId = tenantId;
NormalizedName = name.ToUpperInvariant();
ConcurrencyStamp = Guid.NewGuid().ToString();
ConcurrencyStamp = Guid.NewGuid().ToString("N");
Claims = new Collection<IdentityRoleClaim>();
}

@ -158,7 +158,7 @@ public class IdentityUser : FullAuditedAggregateRoot<Guid>, IUser
NormalizedUserName = userName.ToUpperInvariant();
Email = email;
NormalizedEmail = email.ToUpperInvariant();
ConcurrencyStamp = Guid.NewGuid().ToString();
ConcurrencyStamp = Guid.NewGuid().ToString("N");
SecurityStamp = Guid.NewGuid().ToString();
IsActive = true;

@ -112,7 +112,7 @@ public class IdentityUserStore_Tests : AbpIdentityDomainTestBase
(await _identityUserStore.UpdateAsync(user)).Succeeded.ShouldBeTrue();
user.ConcurrencyStamp = Guid.NewGuid().ToString();
user.ConcurrencyStamp = Guid.NewGuid().ToString("N");
var identityResult = await _identityUserStore.UpdateAsync(user);
identityResult.Succeeded.ShouldBeFalse();

@ -9,11 +9,11 @@ using Volo.Abp.DependencyInjection;
namespace Volo.Abp.OpenIddict.Scopes;
public class AbpOpenIddictScopeCacheAbpOpenIddictAuthorizationCache : AbpOpenIddictCacheBase<OpenIddictScope, OpenIddictScopeModel, IOpenIddictScopeStore<OpenIddictScopeModel>>,
public class AbpOpenIddictScopeCache : AbpOpenIddictCacheBase<OpenIddictScope, OpenIddictScopeModel, IOpenIddictScopeStore<OpenIddictScopeModel>>,
IOpenIddictScopeCache<OpenIddictScopeModel>,
ITransientDependency
{
public AbpOpenIddictScopeCacheAbpOpenIddictAuthorizationCache(
public AbpOpenIddictScopeCache(
IDistributedCache<OpenIddictScopeModel> cache,
IDistributedCache<OpenIddictScopeModel[]> arrayCache,
IOpenIddictScopeStore<OpenIddictScopeModel> store)

@ -1,6 +1,7 @@
using Volo.Abp.Localization;
using Volo.Abp.Modularity;
using Volo.Abp.TenantManagement.Localization;
using Volo.Abp.Localization.ExceptionHandling;
using Volo.Abp.Validation;
using Volo.Abp.Validation.Localization;
using Volo.Abp.VirtualFileSystem;
@ -25,5 +26,10 @@ public class AbpTenantManagementDomainSharedModule : AbpModule
typeof(AbpValidationResource)
).AddVirtualJson("/Volo/Abp/TenantManagement/Localization/Resources");
});
Configure<AbpExceptionLocalizationOptions>(options =>
{
options.MapCodeNamespace("Volo.Abp.TenantManagement", typeof(AbpTenantManagementResource));
});
}
}

@ -1,6 +1,7 @@
{
"culture": "ar",
"texts": {
"Volo.Abp.TenantManagement:DuplicateTenantName": "اسم المستأجر موجود بالفعل: {Name}",
"Menu:TenantManagement": "إدارة الجهات",
"Tenants": "الجهات",
"NewTenant": "جهة جديدة",

@ -1,6 +1,7 @@
{
"culture": "de-DE",
"texts": {
"Volo.Abp.TenantManagement:DuplicateTenantName": "Der Name des Mandanten ist bereits vorhanden: {Name}",
"Menu:TenantManagement": "Mandantenverwaltung",
"Tenants": "Mandanten",
"NewTenant": "Neuer Mandant",

@ -1,6 +1,7 @@
{
"culture": "en",
"texts": {
"Volo.Abp.TenantManagement:DuplicateTenantName": "Tenant name already exist: {Name}",
"Menu:TenantManagement": "Tenant management",
"Tenants": "Tenants",
"NewTenant": "New tenant",

@ -1,6 +1,7 @@
{
"culture": "es",
"texts": {
"Volo.Abp.TenantManagement:DuplicateTenantName": "El nombre del inquilino ya existe: {Name}",
"Menu:TenantManagement": "Gestión de tenants",
"Tenants": "Inquilinos",
"NewTenant": "Nuevo inquilino",

@ -1,6 +1,7 @@
{
"culture": "fi",
"texts": {
"Volo.Abp.TenantManagement:DuplicateTenantName": "Vuokralaisen nimi on jo olemassa: {Name}",
"Menu:TenantManagement": "Vuokralaisten hallinta",
"Tenants": "Vuokralaiset",
"NewTenant": "Uusi vuokralainen",

@ -1,6 +1,7 @@
{
"culture": "fr",
"texts": {
"Volo.Abp.TenantManagement:DuplicateTenantName": "Le nom du locataire existe déjà: {Name}",
"Menu:TenantManagement": "Gestion des locataires",
"Tenants": "Locataires",
"NewTenant": "Nouveau locataire",

@ -1,6 +1,7 @@
{
"culture": "hi",
"texts": {
"Volo.Abp.TenantManagement:DuplicateTenantName": "किरायेदार नाम पहले से मौजूद है: {Name}",
"Menu:TenantManagement": "किरायेदार प्रबंधन",
"Tenants": "किरायेदारों",
"NewTenant": "नया किरायेदार",

@ -1,6 +1,7 @@
{
"culture": "it",
"texts": {
"Volo.Abp.TenantManagement:DuplicateTenantName": "Il nome del tenant esiste già: {Name}",
"Menu:TenantManagement": "Gestione tenants",
"Tenants": "Tenants",
"NewTenant": "Nuovo Tenant",

@ -1,6 +1,7 @@
{
"culture": "pt-BR",
"texts": {
"Volo.Abp.TenantManagement:DuplicateTenantName": "O nome do ambiente já existe: {Name}",
"Menu:TenantManagement": "Gerencimento de Inquilinos",
"Tenants": "Inquilinos",
"NewTenant": "Novo Inquilino",

@ -1,6 +1,7 @@
{
"culture": "ru",
"texts": {
"Volo.Abp.TenantManagement:DuplicateTenantName": "Имя арендатора уже существует: {Name}",
"Menu:TenantManagement": "Управление арендаторами",
"Tenants": "Арендаторы",
"NewTenant": "Новый арендатор",

@ -1,6 +1,7 @@
{
"culture": "sk",
"texts": {
"Volo.Abp.TenantManagement:DuplicateTenantName": "Názov tenanta už existuje: {Name}",
"Menu:TenantManagement": "Správa tenantov",
"Tenants": "Tentanti",
"NewTenant": "Nový tenant",

@ -1,6 +1,7 @@
{
"culture": "sl",
"texts": {
"Volo.Abp.TenantManagement:DuplicateTenantName": "Ime najemnika že obstaja: {Name}",
"Menu:TenantManagement": "Upravljanje najemnikov",
"Tenants": "Najemniki",
"NewTenant": "Nov najemnik",

@ -1,6 +1,7 @@
{
"culture": "tr",
"texts": {
"Volo.Abp.TenantManagement:DuplicateTenantName": "Müşteri ismi zaten mevcut: {Name}",
"Menu:TenantManagement": "Müşteri yönetimi",
"Tenants": "Müşteriler",
"NewTenant": "Yeni müşteri",

@ -1,6 +1,7 @@
{
"culture": "zh-Hans",
"texts": {
"Volo.Abp.TenantManagement:DuplicateTenantName": "租户名称已存在: {Name}",
"Menu:TenantManagement": "租户管理",
"Tenants": "租户",
"NewTenant": "新租户",

@ -1,6 +1,7 @@
{
"culture": "zh-Hant",
"texts": {
"Volo.Abp.TenantManagement:DuplicateTenantName": "租戶名稱已存在: {Name}",
"Menu:TenantManagement": "租戶管理",
"Tenants": "租戶",
"NewTenant": "新租戶",

@ -36,7 +36,7 @@ public class TenantManager : DomainService, ITenantManager
var tenant = await TenantRepository.FindByNameAsync(name);
if (tenant != null && tenant.Id != expectedId)
{
throw new UserFriendlyException("Duplicate tenancy name: " + name); //TODO: A domain exception would be better..?
throw new BusinessException("Volo.Abp.TenantManagement:DuplicateTenantName").WithData("Name", name);
}
}
}

@ -66,7 +66,7 @@ public class TenantAppService_Tests : AbpTenantManagementApplicationTestBase
[Fact]
public async Task CreateAsync_Should_Not_Allow_Duplicate_Names()
{
await Assert.ThrowsAsync<UserFriendlyException>(async () =>
await Assert.ThrowsAsync<BusinessException>(async () =>
{
await _tenantAppService.CreateAsync(new TenantCreateDto { Name = "acme", AdminEmailAddress = "admin@admin.com", AdminPassword = "123456" });
});
@ -90,7 +90,7 @@ public class TenantAppService_Tests : AbpTenantManagementApplicationTestBase
{
var acme = UsingDbContext(dbContext => dbContext.Tenants.Single(t => t.Name == "acme"));
await Assert.ThrowsAsync<UserFriendlyException>(async () =>
await Assert.ThrowsAsync<BusinessException>(async () =>
{
await _tenantAppService.UpdateAsync(acme.Id, new TenantUpdateDto { Name = "volosoft" });
});

@ -26,7 +26,7 @@ public class TenantManager_Tests : AbpTenantManagementDomainTestBase
[Fact]
public async Task Create_Tenant_Name_Can_Not_Duplicate()
{
await Assert.ThrowsAsync<UserFriendlyException>(async () => await _tenantManager.CreateAsync("volosoft"));
await Assert.ThrowsAsync<BusinessException>(async () => await _tenantManager.CreateAsync("volosoft"));
}
[Fact]
@ -46,6 +46,6 @@ public class TenantManager_Tests : AbpTenantManagementDomainTestBase
var tenant = await _tenantRepository.FindByNameAsync("acme");
tenant.ShouldNotBeNull();
await Assert.ThrowsAsync<UserFriendlyException>(async () => await _tenantManager.ChangeNameAsync(tenant, "volosoft"));
await Assert.ThrowsAsync<BusinessException>(async () => await _tenantManager.ChangeNameAsync(tenant, "volosoft"));
}
}

@ -39,7 +39,7 @@
><div class="text-center"><i class="fa fa-pulse fa-spinner"></i></div
></ng-template>
<form *ngIf="form; else loaderRef" [formGroup]="form" (ngSubmit)="save()">
<form *ngIf="form; else loaderRef" [blueprints]="blueprints" [formGroup]="form" (ngSubmit)="save()">
<ul ngbNav #nav="ngbNav" class="nav-tabs">
<li ngbNavItem>
<a ngbNavLink>{{ 'AbpIdentity::UserInformations' | abpLocalization }}</a>

@ -61,6 +61,9 @@ export class UsersComponent implements OnInit {
entityDisplayName: string;
blueprints = { pattern: 'AbpIdentity::Volo.Abp.Identity:InvalidUserName[{{ actualValue }}]' };
trackByFn: TrackByFunction<AbstractControl> = (index, item) => Object.keys(item)[0] || index;
onVisiblePermissionChange = event => {

@ -1,6 +1,7 @@
import { IdentityUserDto } from '@abp/ng.identity/proxy';
import {IdentityUserDto} from '@abp/ng.identity/proxy';
import { EntityAction } from '@abp/ng.theme.shared/extensions';
import { UsersComponent } from '../components/users/users.component';
import {ConfigStateService, CurrentUserDto} from "@abp/ng.core";
export const DEFAULT_USERS_ENTITY_ACTIONS = EntityAction.createMany<IdentityUserDto>([
{
@ -18,13 +19,18 @@ export const DEFAULT_USERS_ENTITY_ACTIONS = EntityAction.createMany<IdentityUser
component.openPermissionsModal(data.record.id, data.record.userName);
},
permission: 'AbpIdentity.Users.ManagePermissions',
},
{
}, {
text: 'AbpIdentity::Delete',
action: data => {
const component = data.getInjected(UsersComponent);
component.delete(data.record.id, data.record.name || data.record.userName);
},
visible: data => {
const userName = data.record.userName;
const configStateService = data.getInjected(ConfigStateService)
const currentUser = configStateService.getOne("currentUser") as CurrentUserDto;
return userName !== currentUser.userName;
},
permission: 'AbpIdentity.Users.Delete',
},
]);

@ -3,13 +3,15 @@ import { getPasswordValidators } from '@abp/ng.theme.shared';
import { ePropType, FormProp } from '@abp/ng.theme.shared/extensions';
import { Validators } from '@angular/forms';
const onlyLetterAndNumberRegex = /^[a-zA-Z0-9]+$/;
export const DEFAULT_USERS_CREATE_FORM_PROPS = FormProp.createMany<IdentityUserDto>([
{
type: ePropType.String,
name: 'userName',
displayName: 'AbpIdentity::UserName',
id: 'user-name',
validators: () => [Validators.required, Validators.maxLength(256)],
validators: () => [Validators.required, Validators.maxLength(256), Validators.pattern(onlyLetterAndNumberRegex)],
},
{
type: ePropType.Password,

@ -36,10 +36,9 @@ public class Program
services.AddApplicationAsync<MyProjectNameModule>(options =>
{
options.Services.ReplaceConfiguration(services.GetConfiguration());
options.UseAutofac();
options.Services.AddLogging(loggingBuilder => loggingBuilder.AddSerilog());
});
}).UseConsoleLifetime();
}).UseAutofac().UseConsoleLifetime();
var host = builder.Build();
await host.Services.GetRequiredService<IAbpApplicationWithExternalServiceProvider>().InitializeAsync(host.Services);

@ -20,9 +20,8 @@
<ProjectReference Include="..\..\..\..\..\framework\src\Volo.Abp.AspNetCore.Authentication.OpenIdConnect\Volo.Abp.AspNetCore.Authentication.OpenIdConnect.csproj" />
<ProjectReference Include="..\..\..\..\..\framework\src\Volo.Abp.Http.Client.IdentityModel.Web\Volo.Abp.Http.Client.IdentityModel.Web.csproj" />
<ProjectReference Include="..\..\..\..\..\framework\src\Volo.Abp.Http.Client.Web\Volo.Abp.Http.Client.Web.csproj" />
<ProjectReference Include="..\..\..\..\..\modules\basic-theme\src\Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic\Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.csproj" />
<ProjectReference Include="..\..\..\..\..\framework\src\Volo.Abp.Swashbuckle\Volo.Abp.Swashbuckle.csproj" />
<ProjectReference Include="..\..\..\..\..\framework\src\Volo.Abp.Http.Client.Web\Volo.Abp.Http.Client.Web.csproj" />
<ProjectReference Include="..\..\..\..\..\modules\basic-theme\src\Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic\Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.csproj" />
<ProjectReference Include="..\..\..\..\..\modules\identity\src\Volo.Abp.Identity.Web\Volo.Abp.Identity.Web.csproj" />
<ProjectReference Include="..\..\..\..\..\modules\identity\src\Volo.Abp.Identity.HttpApi.Client\Volo.Abp.Identity.HttpApi.Client.csproj" />
<ProjectReference Include="..\..\..\..\..\modules\feature-management\src\Volo.Abp.FeatureManagement.Web\Volo.Abp.FeatureManagement.Web.csproj" />

Loading…
Cancel
Save