Merge branch 'dev' into net7

pull/13626/head
maliming 3 years ago
commit dab37df8c1

@ -173,6 +173,10 @@
"BuyOrRenewLicenseToGetExtra2Months": "Buy or Renew License Now and Get 2 Extra Months! HURRY UP! ⏰ Last Day: {0}",
"HurryUp": "HURRY UP!",
"LastDay": "Last Day: {0}",
"BuyNewLicenseBetweenDatesToGetBenefit": "Buy a new license between {0} and {1} to get benefit for extra 2 months!"
"BuyNewLicenseBetweenDatesToGetBenefit": "Buy a new license between {0} and {1} to get benefit for extra 2 months!",
"CheckAllCommunityTalks": "Check All Community Posts",
"ReadMore": "Read More",
"Post": "Post",
"ExploreTheContentsCreatedByTheCoreABPTeamAndTheABPCommunity": "Explore the contents created by the core ABP team and the ABP community."
}
}

@ -170,6 +170,10 @@
"BuyOrRenewLicenseToGetExtra2Months": "立即购买或续订 ABP 商业许可证(适用于所有版本)并额外获得 2 个月!",
"HurryUp": "赶快下单!",
"LastDay": "活动截止日期: {0}",
"BuyNewLicenseBetweenDatesToGetBenefit": "在 {0} 和 {1} 之间购买一个新的许可证以获得额外 2 个月的收益!"
"BuyNewLicenseBetweenDatesToGetBenefit": "在 {0} 和 {1} 之间购买一个新的许可证以获得额外 2 个月的收益!",
"CheckAllCommunityTalks": "检查所有社区帖子",
"ReadMore": "阅读更多",
"Post": "邮政",
"ExploreTheContentsCreatedByTheCoreABPTeamAndTheABPCommunity": "探索核心 ABP 团队和 ABP 社区创建的内容。"
}
}

@ -150,7 +150,6 @@
"GetStarted": "Get Started",
"SourceCode": "Source Code",
"LeaveComment": "Leave Comment",
"ReadMore": "Read more",
"ShowMore": "Show More",
"NoPublishedPostsYet": "No published posts yet.",
"Name": "Name",
@ -185,7 +184,6 @@
"Layout_Title": "{0} | ABP Community",
"Layout_MetaDescription": "ABP Community is an environment where people can share posts about ABP framework and follows the projects.",
"Index_Page_CommunityIntroduction": "This is a hub for ABP Framework, .NET and software development. You can read the articles, watch the video tutorials, get informed about ABPs development progress and ABP-related events, help other developers and share your expertise with the ABP community.",
"TagsInArticle": "Tags in article",
"ExploreTheContentsCreatedByTheCoreABPTeamAndTheABPCommunity": "Explore the contents created by the core ABP team and the ABP community."
"TagsInArticle": "Tags in article"
}
}

@ -150,7 +150,6 @@
"GetStarted": "开始使用",
"SourceCode": "源代码",
"LeaveComment": "发表评论",
"ReadMore": "阅读更多",
"ShowMore": "展示更多",
"NoPublishedPostsYet": "还没有发布的帖子。",
"Name": "名字",

@ -293,7 +293,6 @@
"ExploreDocumentationAndGuides": "Explore the comprehensive documentation and guides.",
"Documentations": "Documentation",
"Views": "Views",
"ReadMore": "Read More",
"EnterYouEmailToGetNews": "Enter your email to get the latest news about the ABP Framework",
"Tiered": "Tiered",
"SeparateIdentityServer": "Separate Identity Server",

@ -293,7 +293,6 @@
"ExploreDocumentationAndGuides": "探索全面的文档和指南。",
"Documentations": "文档",
"Views": "意见",
"ReadMore": "阅读更多",
"EnterYouEmailToGetNews": "输入您的电子邮件以获取有关 ABP 框架的最新消息",
"Tiered": "分层",
"SeparateIdentityServer": "独立的身份服务器",

@ -1,9 +1,13 @@
# How to Add Custom Properties to the User Entity
> **Note:** If your application is greater than version 4.3.3, please follow [this article](../2022-07-19-How-To-Add-Custom-Property-To-The-User-Entity/How-To-Add-Custom-Property-To-The-User-Entity.md).
## Introduction
In this step-by-step article, I will explain how you can customize the user entity class, which is available in every web application you create using the ABP framework, according to your needs. When you read this article, you will learn how to override the services of built-in modules, extend the entities, extend data transfer objects and customize the user interface in the applications you develop using the ABP framework.
> **Note:** This article is not about customizing the `Login` page. If you have such a need, please follow [this article](../2020-05-09-Customize-the-Login-Page-for-MVC-Razor-Page-Applications/POST.md).
You can see the screenshots below which we will reach at the end of the article.
![custom-identity-user-list](./custom-identity-user-list.png)

@ -0,0 +1,154 @@
# How to Add Custom Properties to the User Entity
> **Note:** If your application is less than version 4.4.x, please follow [this article](../2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/How-To-Add-Custom-Property-To-The-User-Entity.md).
## Introduction
In this step-by-step article, I will explain how you can customize the user entity class, which is available in every web application you create using the ABP framework, according to your needs. When you read this article, you will learn how to override the services of built-in modules, extend the entities, extend data transfer objects and customize the user interface in the applications you develop using the ABP framework.
> **Note:** This article is not about customizing the `Login` page. If you have such a need, please follow [this article](../2020-05-09-Customize-the-Login-Page-for-MVC-Razor-Page-Applications/POST.md).
You can see the screenshots below which we will reach at the end of the article.
![custom-identity-user-list](./custom-identity-user-list.png)
![new-user](./new-user.png)
## Preparing the Project
### Startup template and the initial run
Abp Framework offers startup templates to get into the work faster. We can create a new startup template using Abp CLI:
`abp new CustomizeUserDemo`
> In this article, I will go through the MVC application, but it will work also in the [Angular](https://docs.abp.io/en/abp/latest/Getting-Started?UI=NG&DB=EF&Tiered=No), [Blazor Server](https://docs.abp.io/en/abp/latest/Getting-Started?UI=BlazorServer&DB=EF&Tiered=No), and [Blazor WebAssembly](https://docs.abp.io/en/abp/latest/Getting-Started?UI=Blazor&DB=EF&Tiered=No) application.
After the download is finished, we can run **CustomizeUserDemo.DbMigrator** project to create the database migrations and seed the initial data (admin user, role, etc). Then we can run `CustomizeUserDemo.Web` to see that our application is working.
> Default admin username is **admin** and password is **1q2w3E\***
![initial-project](./initial-project.png)
In this article, we will go through a scenario together and find the solutions to our questions through this scenario. However, since the scenario is not a real-life scenario, it may be strange, please don't get too about this issue :)
## Step-1
Create the Users folder in the **CustomizeUserDemo.Domain.Shared** project, create the class `UserConsts` inside the folder and update the class you created as below:
```csharp
public static class UserConsts
{
public const string TitlePropertyName = "Title";
public const string ReputationPropertyName = "Reputation";
public const int MaxTitleLength = 64;
public const double MaxReputationValue = 1_000;
public const double MinReputationValue = 1;
}
```
## Step-2
Update the `CustomizeUserDemoEfCoreEntityExtensionMappings` class in the **CustomizeUserDemo.EntityFramework** project in the EntityFrameworkCore folder as below:
```csharp
public static class CustomizeUserDemoEfCoreEntityExtensionMappings
{
private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner();
public static void Configure()
{
CustomizeUserDemoGlobalFeatureConfigurator.Configure();
CustomizeUserDemoModuleExtensionConfigurator.Configure();
OneTimeRunner.Run(() =>
{
ObjectExtensionManager.Instance
.MapEfCoreProperty<IdentityUser, string>(
UserConsts.TitlePropertyName,
(_, propertyBuilder) =>
{
propertyBuilder.HasDefaultValue("");
propertyBuilder.HasMaxLength(UserConsts.MaxTitleLength);
}
).MapEfCoreProperty<IdentityUser, int>(
UserConsts.ReputationPropertyName,
(_, propertyBuilder) =>
{
propertyBuilder.HasDefaultValue(UserConsts.MinReputationValue);
}
);
});
}
}
```
This class can be used to map these extra properties to table fields in the database. Please read [this](https://docs.abp.io/en/abp/latest/Customizing-Application-Modules-Extending-Entities) article to improve your understanding of what we are doing.
So far, we have added our extra features to the `User` entity and matched these features with the `ef core`.
Now we need to add migration to see what has changed in our database. This for, open the Package Manager Console (PMC) under the menu Tools > NuGet Package Manager.
![nuget-package-manager](./nuget-package-manager.png)
Select the **CustomizeUserDemo.EntityFramework** as the **default project** and execute the following command:
```bash
Add-Migration "Updated-User-Entity"
```
![added-new-migration](./added-new-migration.png)
This will create a new migration class inside the `Migrations` folder of the **CustomizeUserDemo.EntityFrameworkCore** project.
> If you are using another IDE than the Visual Studio, you can use `dotnet-ef` tool as [documented here](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/?tabs=dotnet-core-cli#create-a-migration).
Finally, run the **CustomizeUserDemo.DbMigrator** project to update the database.
When we updated the database, you can see that the `Title` and `Reputation` columns are added to the `Users` table.
![user-table](./user-table.png)
## Step-3
Open the `CustomizeUserDemoModuleExtensionConfigurator` in the **CustomizeUserDemo.Domain.Shared** project, and change the contents of the `ConfigureExtraProperties` method as shown below:
```csharp
private static void ConfigureExtraProperties()
{
ObjectExtensionManager.Instance.Modules().ConfigureIdentity(identity =>
{
identity.ConfigureUser(user =>
{
user.AddOrUpdateProperty<string>(
UserConsts.TitlePropertyName,
options =>
{
options.Attributes.Add(new RequiredAttribute());
options.Attributes.Add(
new StringLengthAttribute(UserConsts.MaxTitleLength)
);
}
);
user.AddOrUpdateProperty<int>(
UserConsts.ReputationPropertyName,
options =>
{
options.DefaultValue = UserConsts.MinReputationValue;
options.Attributes.Add(
new RangeAttribute(UserConsts.MinReputationValue, UserConsts.MaxReputationValue)
);
}
);
});
});
}
```
That's it. Now let's run the application and look at the Identity user page. You can also try to edit and recreate a record if you want, it will work even though we haven't done anything extra. Here is the magic code behind ABP framework.
If there is a situation you want to add, you can click the contribute button or make a comment. Also, if you like the article, don't forget to share it :)
Happy coding :)

@ -0,0 +1,452 @@
# Deploying ABP Project to Azure App Service
In this document, you will 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, Windows or MacOS.
****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/).
## Creating a new ABP application
Create a repository on [GitHub.com](https://github.com/) (keep all settings as default).
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](https://docs.abp.io/en/abp/latest/cli) with the following command:
```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
```
## Running the application
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 and you should see the landing page.
Stop both the *API* and the *Blazor* project by pressing **CTRL+C**
## Committing to GitHub
Before the GitHub commit, you have to delete the line "**/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
```
## Configuring Azure database connection string
Create a SQL database on Azure and change the connection string in all 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
## Running DB Migrations
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 pressing CTRL+C.
## Committing to GitHub
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
```
## Setting up the Build pipeline in AzureDevops and publish the Build Artifacts
* Sign in 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**
## Creating 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
## Creating 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)
## Creating 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
```
## Changing 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)
## Adding 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)

@ -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).

@ -1282,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

@ -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;
}

@ -15,18 +15,18 @@ namespace Volo.Abp.Caching.StackExchangeRedis;
[DisableConventionalRegistration]
public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems
{
protected static readonly string SetScript;
protected static readonly string AbsoluteExpirationKey;
protected static readonly string SlidingExpirationKey;
protected static readonly string DataKey;
protected static readonly long NotPresent;
private static readonly FieldInfo RedisDatabaseField;
private static readonly MethodInfo ConnectMethod;
private static readonly MethodInfo ConnectAsyncMethod;
private static readonly MethodInfo MapMetadataMethod;
private static readonly MethodInfo GetAbsoluteExpirationMethod;
private static readonly MethodInfo GetExpirationInSecondsMethod;
private readonly static FieldInfo SetScriptField;
private readonly static FieldInfo RedisDatabaseField;
private readonly static MethodInfo ConnectMethod;
private readonly static MethodInfo ConnectAsyncMethod;
private readonly static MethodInfo MapMetadataMethod;
private readonly static MethodInfo GetAbsoluteExpirationMethod;
private readonly static MethodInfo GetExpirationInSecondsMethod;
protected IDatabase RedisDatabase => GetRedisDatabase();
private IDatabase _redisDatabase;
@ -36,9 +36,11 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems
static AbpRedisCache()
{
var type = typeof(RedisCache);
RedisDatabaseField = type.GetField("_cache", BindingFlags.Instance | BindingFlags.NonPublic);
SetScriptField = type.GetField("_setScript", BindingFlags.Instance | BindingFlags.NonPublic);
ConnectMethod = type.GetMethod("Connect", BindingFlags.Instance | BindingFlags.NonPublic);
ConnectAsyncMethod = type.GetMethod("ConnectAsync", BindingFlags.Instance | BindingFlags.NonPublic);
@ -51,9 +53,6 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems
GetExpirationInSecondsMethod =
type.GetMethod("GetExpirationInSeconds", BindingFlags.Static | BindingFlags.NonPublic);
SetScript = type.GetField("SetScript", BindingFlags.Static | BindingFlags.NonPublic)?.GetValue(null)
.ToString();
AbsoluteExpirationKey = type.GetField("AbsoluteExpirationKey", BindingFlags.Static | BindingFlags.NonPublic)
?.GetValue(null).ToString();
@ -83,14 +82,14 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems
ConnectMethod.Invoke(this, Array.Empty<object>());
}
protected virtual Task ConnectAsync(CancellationToken token = default)
protected virtual async Task ConnectAsync(CancellationToken token = default)
{
if (GetRedisDatabase() != null)
{
return Task.CompletedTask;
return;
}
return (Task)ConnectAsyncMethod.Invoke(this, new object[] { token });
await (Task)ConnectAsyncMethod.Invoke(this, new object[] { token });
}
public byte[][] GetMany(
@ -283,7 +282,7 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems
for (var i = 0; i < itemArray.Length; i++)
{
tasks[i] = RedisDatabase.ScriptEvaluateAsync(SetScript, new RedisKey[] { Instance + itemArray[i].Key },
tasks[i] = RedisDatabase.ScriptEvaluateAsync(GetSetScript(), new RedisKey[] { Instance + itemArray[i].Key },
new RedisValue[]
{
absoluteExpiration?.Ticks ?? NotPresent,
@ -333,4 +332,9 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems
return _redisDatabase;
}
private string GetSetScript()
{
return SetScriptField?.GetValue(this).ToString();
}
}

@ -21,6 +21,16 @@ public class AbpConfigurationBuilderOptions
/// </summary>
public string FileName { get; set; } = "appsettings";
/// <summary>
/// Whether the file is optional, Default value: true.
/// </summary>
public bool Optional { get; set; } = true;
/// <summary>
/// Whether the configuration should be reloaded if the file changes, Default value: true.
/// </summary>
public bool ReloadOnChange { get; set; } = true;
/// <summary>
/// Environment name. Generally used "Development", "Staging" or "Production".
/// </summary>

@ -18,11 +18,11 @@ public static class ConfigurationHelper
var builder = new ConfigurationBuilder()
.SetBasePath(options.BasePath)
.AddJsonFile(options.FileName + ".json", optional: true, reloadOnChange: true);
.AddJsonFile(options.FileName + ".json", optional: options.Optional, reloadOnChange: options.ReloadOnChange);
if (!options.EnvironmentName.IsNullOrEmpty())
{
builder = builder.AddJsonFile($"{options.FileName}.{options.EnvironmentName}.json", optional: true, reloadOnChange: true);
builder = builder.AddJsonFile($"{options.FileName}.{options.EnvironmentName}.json", optional: options.Optional, reloadOnChange: options.ReloadOnChange);
}
if (options.EnvironmentName == "Development")

@ -9,7 +9,7 @@
"DisplayName:Abp.Mailing.Smtp.Password": "Senha",
"DisplayName:Abp.Mailing.Smtp.Domain": "Domínio",
"DisplayName:Abp.Mailing.Smtp.EnableSsl": "Habilitar SSL",
"DisplayName:Abp.Mailing.Smtp.UseDefaultCredentials": "Use credenciais padrão",
"DisplayName:Abp.Mailing.Smtp.UseDefaultCredentials": "Usar credenciais padrão",
"Description:Abp.Mailing.DefaultFromAddress": "O endereço de origem padrão",
"Description:Abp.Mailing.DefaultFromDisplayName": "O nome de exibição padrão",
"Description:Abp.Mailing.Smtp.Host": "O nome ou endereço IP do host usado para transações SMTP.",

@ -1,10 +1,10 @@
{
"culture": "pt-BR",
"texts": {
"Languages": "línguas",
"Languages": "Idiomas",
"AreYouSure": "Você tem certeza?",
"Cancel": "Cancelar",
"Clear": "Claro",
"Clear": "Limpar",
"Yes": "Sim",
"No": "Não",
"Ok": "OK",
@ -16,7 +16,7 @@
"SuccessfullyDeleted": "Excluído com sucesso",
"Edit": "Editar",
"Refresh": "Atualizar",
"Language": "Língua",
"Language": "Idioma",
"LoadMore": "Carregar mais",
"ProcessingWithThreeDot": "Processando...",
"LoadingWithThreeDot": "Carregando...",

@ -24,6 +24,8 @@
"ThisFieldMustBeAStringOrArrayTypeWithAMinimumLengthOf{0}": "Este campo deve ser do tipo palavra ou matriz com comprimento mínimo de '{0}'.",
"ThisFieldIsNotAValidPhoneNumber.": "Número de telefone inválido.",
"ThisFieldMustBeBetween{0}And{1}": "Este campo deve estar entre {0} e {1}.",
"ThisFieldMustBeGreaterThanOrEqual{0}": "Este campo deve ser maior ou igual a {0}.",
"ThisFieldMustBeLessOrEqual{0}": "Este campo deve ser menor ou igual a {0}.",
"ThisFieldMustMatchTheRegularExpression{0}": "Este campo deve ser compatível com a expressão regular: '{0}'.",
"ThisFieldIsRequired.": "Campo Obrigatório.",
"ThisFieldMustBeAStringWithAMaximumLengthOf{0}": "Campo com no máximo {0} caracteres.",

@ -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'));

@ -37,7 +37,7 @@
"Edit": "Editar",
"BLOG": "BLOG",
"CommentDeletionWarningMessage": "O comentário será excluído.",
"PostDeletionWarningMessage": "O post será excluído.",
"PostDeletionWarningMessage": "A postagem será excluída.",
"BlogDeletionWarningMessage": "O Blog será excluído.",
"AreYouSure": "Você tem certeza?",
"CommentWithCount": "{0} comentários",
@ -54,8 +54,10 @@
"Blogs": "Blogs",
"Tags": "Etiquetas",
"ShareOn": "Compartilhar no",
"TitleLengthWarning": "Mantenha o tamanho do título abaixo de 60 caracteres para ser SEO amigável!",
"TitleLengthWarning": "Mantenha o tamanho do título abaixo de 60 caracteres para ser amigável ao SEO!",
"ClearCache": "Limpar cache",
"ClearCacheConfirmationMessage": "Tem certeza de que deseja limpar o cache?"
"ClearCacheConfirmationMessage": "Tem certeza de que deseja limpar o cache?",
"MarkdownSupported": "Markdown é suportado",
"FileUploadInfo": "Arrastar, soltar, ou colar uma imagem copiada."
}
}

@ -1,15 +1,40 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel.DataAnnotations;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using Volo.Abp.Validation.Localization;
using Volo.CmsKit.Localization;
using Volo.CmsKit.Tags;
namespace Volo.CmsKit.Admin.Tags;
[Serializable]
public class EntityTagSetDto
public class EntityTagSetDto : IValidatableObject
{
public string EntityId { get; set; }
public string EntityType { get; set; }
[Required]
public List<string> Tags { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var l = validationContext.GetRequiredService<IStringLocalizer<AbpValidationResource>>();
foreach (var tag in Tags)
{
if (tag.Length > TagConsts.MaxNameLength)
{
yield return new ValidationResult(
l[
"ThisFieldMustBeAStringWithAMaximumLengthOf{0}",
TagConsts.MaxNameLength
],
new[] { nameof(Tags) }
);
}
}
}
}

@ -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,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.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);
}

@ -75,7 +75,7 @@
"PageDeletionConfirmationMessage": "هل أنت متأكد من حذف هذه الصفحة؟",
"PageId": "صفحة",
"Pages": "الصفحات",
"PageSlugInformation": "سبيكة تستخدم على url. سيكون عنوان url الخاص بك هو \"/pages/{{slug}}\".",
"PageSlugInformation": "سبيكة تستخدم على url. سيكون عنوان url الخاص بك هو \"/{slug}}\".",
"Permission:BlogManagement": "إدارة المدونة",
"Permission:BlogManagement.Create": "إنشاء",
"Permission:BlogManagement.Delete": "حذف",

@ -75,7 +75,7 @@
"PageDeletionConfirmationMessage": "Opravdu chcete smazat tuto stránku?",
"PageId": "Strana",
"Pages": "stránky",
"PageSlugInformation": "Na adrese URL je použit Slug. Vaše adresa URL bude '/pages/{{slug}}'.",
"PageSlugInformation": "Na adrese URL je použit Slug. Vaše adresa URL bude '/{{slug}}'.",
"Permission:BlogManagement": "Správa blogu",
"Permission:BlogManagement.Create": "Vytvořit",
"Permission:BlogManagement.Delete": "Vymazat",

@ -75,7 +75,7 @@
"PageDeletionConfirmationMessage": "Möchten Sie diese Seite wirklich löschen?",
"PageId": "Buchseite",
"Pages": "Seiten",
"PageSlugInformation": "Slug wird auf URL verwendet. Ihre URL lautet '/pages/{{slug}}'.",
"PageSlugInformation": "Slug wird auf URL verwendet. Ihre URL lautet '/{{slug}}'.",
"Permission:BlogManagement": "Blog-Verwaltung",
"Permission:BlogManagement.Create": "Schaffen",
"Permission:BlogManagement.Delete": "Löschen",

@ -75,7 +75,7 @@
"PageDeletionConfirmationMessage": "Είστε βέβαιοι ότι θα διαγράψετε αυτήν τη σελίδα;",
"PageId": "Σελίδα",
"Pages": "Σελίδες",
"PageSlugInformation": "Το Slug χρησιμοποιείται στο url. Το url σας θα είναι '/pages/{{slug}}'.",
"PageSlugInformation": "Το Slug χρησιμοποιείται στο url. Το url σας θα είναι '/{{slug}}'.",
"Permission:BlogManagement": "Διαχείριση ιστολογίου",
"Permission:BlogManagement.Create": "Δημιουργώ",
"Permission:BlogManagement.Delete": "Διαγράφω",

@ -75,7 +75,7 @@
"PageDeletionConfirmationMessage": "Are you sure to delete this page?",
"PageId": "Page",
"Pages": "Pages",
"PageSlugInformation": "Slug is used on url. Your url will be '/pages/{{slug}}'.",
"PageSlugInformation": "Slug is used on url. Your url will be '/{{slug}}'.",
"Permission:BlogManagement": "Blog Management",
"Permission:BlogManagement.Create": "Create",
"Permission:BlogManagement.Delete": "Delete",

@ -75,7 +75,7 @@
"PageDeletionConfirmationMessage": "¿Está seguro de eliminar esta página?",
"PageId": "Página",
"Pages": "Paginas",
"PageSlugInformation": "Slug se usa en la URL. Su URL será '/pages/{{slug}}'.",
"PageSlugInformation": "Slug se usa en la URL. Su URL será '/{{slug}}'.",
"Permission:BlogManagement": "Gestión de blogs",
"Permission:BlogManagement.Create": "Crear",
"Permission:BlogManagement.Delete": "Borrar",

@ -75,7 +75,7 @@
"PageDeletionConfirmationMessage": "آیا مطمئن هستید که این صفحه را حذف می کنید؟",
"PageId": "صفحه",
"Pages": "صفحات",
"PageSlugInformation": "Slug در url استفاده می شود. آدرس اینترنتی شما '/pages/{{slug}}' خواهد بود.",
"PageSlugInformation": "Slug در url استفاده می شود. آدرس اینترنتی شما '/{{slug}}' خواهد بود.",
"Permission:BlogManagement": "مدیریت وبلاگ",
"Permission:BlogManagement.Create": "ایجاد",
"Permission:BlogManagement.Delete": "حذف",

@ -75,7 +75,7 @@
"PageDeletionConfirmationMessage": "Haluatko varmasti poistaa tämän sivun?",
"PageId": "Sivu",
"Pages": "Sivut",
"PageSlugInformation": "Etanaa käytetään URL-osoitteessa. URL-osoitteesi on '/pages/{{slug}}'.",
"PageSlugInformation": "Etanaa käytetään URL-osoitteessa. URL-osoitteesi on '/{{slug}}'.",
"Permission:BlogManagement": "Blogin hallinta",
"Permission:BlogManagement.Create": "Luoda",
"Permission:BlogManagement.Delete": "Poistaa",

@ -75,7 +75,7 @@
"PageDeletionConfirmationMessage": "Êtes-vous sûr de vouloir supprimer cette page?",
"PageId": "Page",
"Pages": "Pages",
"PageSlugInformation": "Slug est utilisé sur l'url. Votre URL sera '/pages/{{slug}}'.",
"PageSlugInformation": "Slug est utilisé sur l'url. Votre URL sera '/{{slug}}'.",
"Permission:BlogManagement": "Gestion de blog",
"Permission:BlogManagement.Create": "Créer",
"Permission:BlogManagement.Delete": "Effacer",

@ -75,7 +75,7 @@
"PageDeletionConfirmationMessage": "क्या आप इस पृष्ठ को हटाना सुनिश्चित कर रहे हैं?",
"PageId": "पृष्ठ",
"Pages": "पृष्ठों",
"PageSlugInformation": "स्लग का उपयोग url पर किया जाता है। आपका url '/pages/{{slug}}' होगा।",
"PageSlugInformation": "स्लग का उपयोग url पर किया जाता है। आपका url '/{{slug}}' होगा।",
"Permission:BlogManagement": "ब्लॉग प्रबंधन",
"Permission:BlogManagement.Create": "सृजन करना",
"Permission:BlogManagement.Delete": "हटाएं",

@ -75,7 +75,7 @@
"PageDeletionConfirmationMessage": "Biztosan törlöd ezt az oldalt?",
"PageId": "oldal",
"Pages": "Oldalak",
"PageSlugInformation": "A Slug az url-en használatos. Az Ön URL-je a következő lesz: „/pages/{{slug}}”.",
"PageSlugInformation": "A Slug az url-en használatos. Az Ön URL-je a következő lesz: „/{{slug}}”.",
"Permission:BlogManagement": "Blogkezelés",
"Permission:BlogManagement.Create": "Teremt",
"Permission:BlogManagement.Delete": "Töröl",

@ -75,7 +75,7 @@
"PageDeletionConfirmationMessage": "Sei sicuro di cancellare questa pagina?",
"PageId": "Pagina",
"Pages": "Pagine",
"PageSlugInformation": "Lo slug viene utilizzato sull'URL. Il tuo URL sarà '/pages/{{slug}}'.",
"PageSlugInformation": "Lo slug viene utilizzato sull'URL. Il tuo URL sarà '/{{slug}}'.",
"Permission:BlogManagement": "Gestione del blog",
"Permission:BlogManagement.Create": "Crea",
"Permission:BlogManagement.Delete": "Elimina",

@ -75,7 +75,7 @@
"PageDeletionConfirmationMessage": "Weet u zeker dat u deze pagina wilt verwijderen?",
"PageId": "Bladzijde",
"Pages": "Pagina's",
"PageSlugInformation": "Slug wordt gebruikt voor de url. Uw url wordt '/pages/{{slug}}'.",
"PageSlugInformation": "Slug wordt gebruikt voor de url. Uw url wordt '/{{slug}}'.",
"Permission:BlogManagement": "Blogbeheer",
"Permission:BlogManagement.Create": "Toevoegen",
"Permission:BlogManagement.Delete": "Verwijderen",

@ -75,7 +75,7 @@
"PageDeletionConfirmationMessage": "Czy na pewno chcesz usunąć tę stronę?",
"PageId": "Strona",
"Pages": "Strony",
"PageSlugInformation": "Slug jest używany na adresie URL. Twój adres URL to „/pages/{{slug}}”.",
"PageSlugInformation": "Slug jest używany na adresie URL. Twój adres URL to „/{{slug}}”.",
"Permission:BlogManagement": "Zarządzanie blogiem",
"Permission:BlogManagement.Create": "Tworzyć",
"Permission:BlogManagement.Delete": "Kasować",

@ -75,7 +75,7 @@
"PageDeletionConfirmationMessage": "Tem certeza que deseja deletar esta página?",
"PageId": "Página",
"Pages": "Páginas",
"PageSlugInformation": "Slug é usado na url. Sua url será '/pages/{{slug}}'.",
"PageSlugInformation": "Slug é usado na url. Sua url será '/{{slug}}'.",
"Permission:BlogManagement": "Gerenciamento de blogs",
"Permission:BlogManagement.Create": "Criar",
"Permission:BlogManagement.Delete": "Excluir",
@ -181,6 +181,31 @@
"HasBlogPostWaitingForReviewMessage": "Você tem uma postagem para revisão. Clique para listar.",
"SelectAStatus": "Selecione um status",
"Status": "Status",
"CmsKit.BlogPost.ScrollIndex": "Barra de navegação rápida em postagens"
"CmsKit.BlogPost.ScrollIndex": "Barra de navegação rápida em postagens",
"Add": "Adicionar",
"AddWidget": "Adicionar Widget",
"PleaseConfigureWidgets": "Por favor, configure os widgets",
"SelectAnAuthor": "Selecione um Autor",
"InThisDocument": "Neste Documento",
"GoToTop": "Ir para o Topo",
"Feature:CmsKitGroup": "Cms Kit",
"Feature:BlogEnable": "Habilitar página de blog",
"Feature:BlogEnableDescription": "Habilitar página de blog no aplicativo.",
"Feature:CommentEnable": "Habilitar comentários",
"Feature:CommentEnableDescription": "Habilitar comentários no aplicativo.",
"Feature:GlobalResourceEnable": "Recursos globais ativados",
"Feature:GlobalResourceEnableDescription": "Habilitar recurso global no aplicativo.",
"Feature:MediaEnable": "Mídia ativada",
"Feature:MediaEnableDescription": "Mídia ativada no aplicativo.",
"Feature:MenuEnable": "Menu habilitado",
"Feature:MenuEnableDescription": "Menu habilitado no aplicativo.",
"Feature:PageEnable": "Página de paginação ativada",
"Feature:PageEnableDescription": "Página de paginação ativada no aplicativo.",
"Feature:RatingEnable": "Classificação ativada",
"Feature:RatingEnableDescription": "Classificação ativada no aplicativo.",
"Feature:ReactionEnable": "Reações habilitadas",
"Feature:ReactionEnableDescription": "Reações habilitadas no aplicativo.",
"Feature:TagEnable": "Habilitar tag",
"Feature:TagEnableDescription": "Habilitar tag no aplicativo."
}
}

@ -75,7 +75,7 @@
"PageDeletionConfirmationMessage": "Sunteţi sigur(ă) că vreţi să ştergeţi această pagină?",
"PageId": "Pagina",
"Pages": "Pagini",
"PageSlugInformation": "Slug este folosit pe url. Url-ul dumneavoastră va fi '/pages/{{slug}}'.",
"PageSlugInformation": "Slug este folosit pe url. Url-ul dumneavoastră va fi '/{{slug}}'.",
"Permission:BlogManagement": "Administrare Blog",
"Permission:BlogManagement.Create": "Creează",
"Permission:BlogManagement.Delete": "Şterge",

@ -75,7 +75,7 @@
"PageDeletionConfirmationMessage": "Вы уверены, что хотите удалить эту страницу?",
"PageId": "Страница",
"Pages": "Страницы",
"PageSlugInformation": "Слаг используется для URL. Ваш URL-адрес будет '/pages/{{slug}}'.",
"PageSlugInformation": "Слаг используется для URL. Ваш URL-адрес будет '/{{slug}}'.",
"Permission:BlogManagement": "Управление блогом",
"Permission:BlogManagement.Create": "Создавать",
"Permission:BlogManagement.Delete": "Удалить",

@ -75,7 +75,7 @@
"PageDeletionConfirmationMessage": "Určite chcete túto stránku vymazať?",
"PageId": "Stránka",
"Pages": "Stránky",
"PageSlugInformation": "Slug sa používa v URL. Vaša URL bude '/pages/{{slug}}'.",
"PageSlugInformation": "Slug sa používa v URL. Vaša URL bude '/{{slug}}'.",
"Permission:BlogManagement": "Správa blogov",
"Permission:BlogManagement.Create": "Vytvoriť",
"Permission:BlogManagement.Delete": "Zmazať",

@ -75,7 +75,7 @@
"PageDeletionConfirmationMessage": "Ali ste prepričani, da želite izbrisati to stran?",
"PageId": "stran",
"Pages": "strani",
"PageSlugInformation": "Slug se uporablja na url-ju. Vaš url bo '/pages/{{slug}}'.",
"PageSlugInformation": "Slug se uporablja na url-ju. Vaš url bo '/{{slug}}'.",
"Permission:BlogManagement": "Upravljanje blogov",
"Permission:BlogManagement.Create": "Ustvari",
"Permission:BlogManagement.Delete": "Izbriši",

@ -75,7 +75,7 @@
"PageDeletionConfirmationMessage": "Bu sayfayı silmek istediğinize emin misiniz?",
"PageId": "Sayfa",
"Pages": "Sayfalar",
"PageSlugInformation": "Etiket URL'de kullanılır. Url şöyle görünür: '/pages/{{slug}}'.",
"PageSlugInformation": "Etiket URL'de kullanılır. Url şöyle görünür: '/{{slug}}'.",
"Permission:BlogManagement": "Blog Yönetimi",
"Permission:BlogManagement.Create": "Oluşturma",
"Permission:BlogManagement.Delete": "Silme",

@ -75,7 +75,7 @@
"PageDeletionConfirmationMessage": "Bạn có chắc chắn xóa trang này không?",
"PageId": "Trang",
"Pages": "Các trang",
"PageSlugInformation": "Slug được sử dụng trên url. Url của bạn sẽ là '/pages/{{slug}}'.",
"PageSlugInformation": "Slug được sử dụng trên url. Url của bạn sẽ là '/{{slug}}'.",
"Permission:BlogManagement": "Quản lý blog",
"Permission:BlogManagement.Create": "Tạo ra",
"Permission:BlogManagement.Delete": "Xóa bỏ",

@ -75,7 +75,7 @@
"PageDeletionConfirmationMessage": "你确定删除这个页面吗?",
"PageId": "页",
"Pages": "页面",
"PageSlugInformation": "Slug用于url. 你的url将是 '/pages/{{slug}}'.",
"PageSlugInformation": "Slug用于url. 你的url将是 '/{{slug}}'.",
"Permission:BlogManagement": "博客管理",
"Permission:BlogManagement.Create": "创建",
"Permission:BlogManagement.Delete": "删除",

@ -75,7 +75,7 @@
"PageDeletionConfirmationMessage": "你確定刪除這個頁面嗎?",
"PageId": "頁",
"Pages": "頁面",
"PageSlugInformation": "Slug用於網址. 你的網址將是 '/pages/{{slug}}'.",
"PageSlugInformation": "Slug用於網址. 你的網址將是 '/{{slug}}'.",
"Permission:BlogManagement": "部落格管理",
"Permission:BlogManagement.Create": "創建",
"Permission:BlogManagement.Delete": "刪除",

@ -2,7 +2,7 @@
namespace Volo.CmsKit.Pages;
public class PageConsts
public static class PageConsts
{
public const string EntityType = "Page";
@ -16,7 +16,7 @@ public class PageConsts
public static int MaxStyleLength { get; set; } = int.MaxValue;
private static string _urlPrefix = "/pages/";
private static string _urlPrefix = "/";
public static string UrlPrefix {
get => _urlPrefix;
set => _urlPrefix = value.EnsureEndsWith('/').EnsureStartsWith('/');

@ -1,9 +1,9 @@
using JetBrains.Annotations;
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Volo.Abp.Domain.Services;
namespace Volo.CmsKit.Tags;
@ -62,7 +62,7 @@ public class EntityTagManager : DomainService
var deletedTags = existingTags.Where(x => !tags.Contains(x.Name)).ToList();
var addedTags = tags.Where(x => !existingTags.Any(a => a.Name == x));
await EntityTagRepository.DeleteManyAsync(deletedTags.Select(s => s.Id).ToArray());
await EntityTagRepository.DeleteManyAsync(deletedTags.Select(s => s.Id).ToArray(), entityId);
foreach (var addedTag in addedTags)
{
@ -85,6 +85,6 @@ public class EntityTagManager : DomainService
[CanBeNull] Guid? tenantId,
CancellationToken cancellationToken = default)
{
return await EntityTagRepository.GetEntityIdsFilteredByTagNameAsync(tagName, entityType,tenantId, cancellationToken);
return await EntityTagRepository.GetEntityIdsFilteredByTagNameAsync(tagName, entityType, tenantId, cancellationToken);
}
}

@ -1,8 +1,8 @@
using JetBrains.Annotations;
using System;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Volo.Abp.Domain.Repositories;
namespace Volo.CmsKit.Tags;
@ -15,7 +15,7 @@ public interface IEntityTagRepository : IBasicRepository<EntityTag>
[CanBeNull] Guid? tenantId,
CancellationToken cancellationToken = default);
Task DeleteManyAsync(Guid[] tagIds, CancellationToken cancellationToken = default);
Task DeleteManyAsync(Guid[] tagIds, string entityId, CancellationToken cancellationToken = default);
Task<List<string>> GetEntityIdsFilteredByTagAsync(
[NotNull] Guid tagId,
@ -25,6 +25,6 @@ public interface IEntityTagRepository : IBasicRepository<EntityTag>
Task<List<string>> GetEntityIdsFilteredByTagNameAsync(
[NotNull] string tagName,
[NotNull] string entityType,
[CanBeNull] Guid? tenantId=null,
CancellationToken cancellationToken=default);
[CanBeNull] Guid? tenantId = null,
CancellationToken cancellationToken = default);
}

@ -1,9 +1,9 @@
using JetBrains.Annotations;
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
using Volo.Abp;
using Volo.Abp.Domain.Repositories.EntityFrameworkCore;
@ -19,11 +19,11 @@ public class EfCoreEntityTagRepository : EfCoreRepository<ICmsKitDbContext, Enti
{
}
public virtual async Task DeleteManyAsync(Guid[] tagIds, CancellationToken cancellationToken = default)
public virtual async Task DeleteManyAsync(Guid[] tagIds, string entityId, CancellationToken cancellationToken = default)
{
var dbContext = await GetDbContextAsync();
var dbSet = dbContext.Set<EntityTag>();
dbSet.RemoveRange(dbSet.Where(x => tagIds.Contains(x.TagId)));
dbSet.RemoveRange(dbSet.Where(x => tagIds.Contains(x.TagId) && x.EntityId == entityId));
await dbContext.SaveChangesAsync(GetCancellationToken(cancellationToken));
}
@ -56,18 +56,18 @@ public class EfCoreEntityTagRepository : EfCoreRepository<ICmsKitDbContext, Enti
public async Task<List<string>> GetEntityIdsFilteredByTagNameAsync(
[NotNull] string tagName,
[NotNull] string entityType,
[CanBeNull] Guid? tenantId=null,
CancellationToken cancellationToken=default)
[CanBeNull] Guid? tenantId = null,
CancellationToken cancellationToken = default)
{
var dbContext = await GetDbContextAsync();
var result = from et in dbContext.Set<EntityTag>()
join t in dbContext.Set<Tag>() on et.TagId equals t.Id
where t.Name == tagName
&& t.EntityType == entityType
&& et.TenantId == tenantId
where t.Name == tagName
&& t.EntityType == entityType
&& et.TenantId == tenantId
&& t.TenantId == tenantId
&& !t.IsDeleted
select et.EntityId;
return await result.ToListAsync(cancellationToken:GetCancellationToken(cancellationToken));
select et.EntityId;
return await result.ToListAsync(cancellationToken: GetCancellationToken(cancellationToken));
}
}

@ -1,15 +1,14 @@
using JetBrains.Annotations;
using MongoDB.Driver;
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using MongoDB.Driver;
using Volo.Abp;
using Volo.Abp.Domain.Repositories.MongoDB;
using Volo.Abp.MongoDB;
using Volo.CmsKit.Tags;
using Tag = Volo.CmsKit.Tags.Tag;
namespace Volo.CmsKit.MongoDB.Tags;
@ -20,12 +19,19 @@ public class MongoEntityTagRepository : MongoDbRepository<ICmsKitMongoDbContext,
{
}
public virtual async Task DeleteManyAsync(Guid[] tagIds, CancellationToken cancellationToken = default)
public virtual async Task DeleteManyAsync(Guid[] tagIds, string entityId, CancellationToken cancellationToken = default)
{
var token = GetCancellationToken(cancellationToken);
var collection = await GetCollectionAsync(token);
await collection.DeleteManyAsync(Builders<EntityTag>.Filter.In(x => x.TagId, tagIds), token);
var builder = Builders<EntityTag>.Filter;
var filter = builder.And(
builder.In(x => x.TagId, tagIds),
builder.Eq(x => x.EntityId, entityId)
);
await collection.DeleteManyAsync(filter, token);
}
public virtual Task<EntityTag> FindAsync(
@ -57,7 +63,7 @@ public class MongoEntityTagRepository : MongoDbRepository<ICmsKitMongoDbContext,
public async Task<List<string>> GetEntityIdsFilteredByTagNameAsync(
[NotNull] string tagName,
[NotNull] string entityType,
[CanBeNull] Guid? tenantId=null,
[CanBeNull] Guid? tenantId = null,
CancellationToken cancellationToken = default)
{
var dbContext = await GetDbContextAsync();
@ -76,7 +82,7 @@ public class MongoEntityTagRepository : MongoDbRepository<ICmsKitMongoDbContext,
&& x.tag.IsDeleted == false
)
.Select(s => s.entityTag.EntityId);
return await AsyncExecuter.ToListAsync(resultQueryable, GetCancellationToken(cancellationToken));
}
}

@ -9,11 +9,11 @@ namespace Volo.CmsKit.Tags;
public abstract class EntityTagRepository_Test<TStartupModule> : CmsKitTestBase<TStartupModule>
where TStartupModule : IAbpModule
{
private CmsKitTestData _cmsKitTestData;
private IEntityTagRepository _entityTagRepository;
private ITagRepository _tagRepository;
private readonly CmsKitTestData _cmsKitTestData;
private readonly IEntityTagRepository _entityTagRepository;
private readonly ITagRepository _tagRepository;
public EntityTagRepository_Test()
protected EntityTagRepository_Test()
{
_cmsKitTestData = GetRequiredService<CmsKitTestData>();
_entityTagRepository = GetRequiredService<IEntityTagRepository>();
@ -25,7 +25,7 @@ public abstract class EntityTagRepository_Test<TStartupModule> : CmsKitTestBase<
{
var relatedTags = await _tagRepository.GetAllRelatedTagsAsync(_cmsKitTestData.Content_1_EntityType, _cmsKitTestData.Content_1_EntityId);
await _entityTagRepository.DeleteManyAsync(relatedTags.Select(s => s.Id).ToArray());
await _entityTagRepository.DeleteManyAsync(relatedTags.Select(s => s.Id).ToArray(), _cmsKitTestData.Content_1_EntityId);
relatedTags = await _tagRepository.GetAllRelatedTagsAsync(_cmsKitTestData.Content_1_EntityType, _cmsKitTestData.Content_1_EntityId);
@ -35,7 +35,7 @@ public abstract class EntityTagRepository_Test<TStartupModule> : CmsKitTestBase<
[Fact]
public async Task GetEntityIdsFilteredByTagNameAsync_ShouldWorkProperly()
{
var entityIds = await _entityTagRepository.GetEntityIdsFilteredByTagNameAsync(_cmsKitTestData.TagName_1, _cmsKitTestData.EntityType1);
var entityIds = await _entityTagRepository.GetEntityIdsFilteredByTagNameAsync(_cmsKitTestData.TagName_1, _cmsKitTestData.EntityType1);
entityIds.ShouldNotBeNull();
entityIds.ShouldNotBeEmpty();

@ -32,7 +32,7 @@
"DisplayName:GitHubAccessToken": "Token de acesso do GitHub",
"DisplayName:GitHubUserAgent": "Usuário agente do GitHub",
"DisplayName:GithubVersionProviderSource": "Fonte do provedor de versão do GitHub",
"DisplayName:VersionBranchPrefix": "Prefixo da ramificação da versão",
"DisplayName:VersionBranchPrefix": "Prefixo da Branch da versão",
"DisplayName:All": "Puxe tudo",
"DisplayName:LanguageCode": "Código de idioma",
"DisplayName:Version": "Versão",
@ -55,6 +55,7 @@
"LanguageCode": "Código de idioma",
"FileName": "Nome do arquivo",
"LastCachedTime": "Tempo de cache",
"Project": "Projeto"
"Project": "Projeto",
"AdvancedFilters": "Filtros Avançados"
}
}

@ -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();

@ -4,10 +4,10 @@
"TheOpenIDConnectRequestCannotBeRetrieved": "Não foi possivel recuperar a requisição do OpenID Connect.",
"TheUserDetailsCannotBbeRetrieved": "Não foi possivel recuperar os detalhes do usuário.",
"TheApplicationDetailsCannotBeFound": "Os detalhes da aplicação não foram encontrados.",
"DetailsConcerningTheCallingClientApplicationCannotBeFound": "Os detalhes relativos ao aplicativo cliente requisitante não firam encontrados.",
"DetailsConcerningTheCallingClientApplicationCannotBeFound": "Os detalhes relativos ao aplicativo cliente requisitante não foram encontrados.",
"TheSpecifiedGrantTypeIsNotImplemented.": "O tipo de permissão {0} não está implementando.",
"Authorization": "Autorização",
"DoYouWantToGrantAccessToYourData": "Deseja permitir {0} acesse seus dados?",
"DoYouWantToGrantAccessToYourData": "Deseja permitir {0} acessar seus dados?",
"ScopesRequested": "Escopo solicitado",
"Accept": "Aceitar",
"Deny": "Negar"

@ -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)

@ -8,7 +8,7 @@
"VirtualFileName": "Nome do arquivo virtual",
"FileContent": "Conteúdo do arquivo",
"Size": "Tamanho",
"BackToRoot": "De volta à raiz",
"BackToRoot": "Voltar à raiz",
"EmptyFileInfoList": "Não há arquivos virtuais"
}
}

@ -34,6 +34,7 @@
<PackageReference Include="Serilog.AspNetCore" Version="5.0.0" />
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
<PackageReference Include="Microsoft.AspNetCore.DataProtection.StackExchangeRedis" Version="6.0.5" />
<PackageReference Include="DistributedLock.Redis" Version="1.0.2" />
</ItemGroup>
<ItemGroup>
@ -48,6 +49,7 @@
<ItemGroup>
<ProjectReference Include="..\..\..\..\..\framework\src\Volo.Abp.Autofac\Volo.Abp.Autofac.csproj" />
<ProjectReference Include="..\..\..\..\..\framework\src\Volo.Abp.Caching.StackExchangeRedis\Volo.Abp.Caching.StackExchangeRedis.csproj" />
<ProjectReference Include="..\..\..\..\..\framework\src\Volo.Abp.DistributedLocking\Volo.Abp.DistributedLocking.csproj" />
<ProjectReference Include="..\..\..\..\..\framework\src\Volo.Abp.AspNetCore.Serilog\Volo.Abp.AspNetCore.Serilog.csproj" />
<ProjectReference Include="..\..\..\..\..\modules\account\src\Volo.Abp.Account.Web.OpenIddict\Volo.Abp.Account.Web.OpenIddict.csproj" />
<ProjectReference Include="..\..\..\..\..\modules\account\src\Volo.Abp.Account.Application\Volo.Abp.Account.Application.csproj" />

@ -2,6 +2,8 @@ using System;
using System.IO;
using System.Linq;
using Localization.Resources.AbpUi;
using Medallion.Threading;
using Medallion.Threading.Redis;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.DataProtection;
@ -153,6 +155,13 @@ public class MyProjectNameAuthServerModule : AbpModule
var redis = ConnectionMultiplexer.Connect(configuration["Redis:Configuration"]);
dataProtectionBuilder.PersistKeysToStackExchangeRedis(redis, "MyProjectName-Protection-Keys");
}
context.Services.AddSingleton<IDistributedLockProvider>(sp =>
{
var connection = ConnectionMultiplexer
.Connect(configuration["Redis:Configuration"]);
return new RedisDistributedSynchronizationProvider(connection.GetDatabase());
});
context.Services.AddCors(options =>
{

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

Loading…
Cancel
Save