Browse Source

Merge branch 'dev' into maliming/HandleExtraPropertiesOnSave

pull/4920/head
Halil İbrahim Kalkan 6 years ago
committed by GitHub
parent
commit
194fa311fa
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 7
      abp_io/AbpIoLocalization/AbpIoLocalization/AbpIoLocalizationModule.cs
  2. 26
      abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json
  3. 10
      abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/AbpIoCommunityResource.cs
  4. 41
      abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json
  5. 30
      docs/en/Authentication/Social-External-Logins.md
  6. 3
      docs/en/Background-Workers-Quartz.md
  7. 25
      docs/en/Blob-Storing-Aliyun.md
  8. 32
      docs/en/Blob-Storing-Aws.md
  9. 11
      docs/en/Blob-Storing-Azure.md
  10. 15
      docs/en/Blob-Storing-Minio.md
  11. 11
      docs/en/Community-Articles/2020-04-19-Customize-the-SignIn-Manager/POST.md
  12. 7
      docs/en/Community-Articles/2020-04-27-Use-Azure-Active-Directory-Authentication-for-MVC-Razor-Page-Applications/POST.md
  13. 10
      docs/en/Community-Articles/2020-05-09-Customize-the-Login-Page-for-MVC-Razor-Page-Applications/POST.md
  14. 462
      docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/POST.md
  15. BIN
      docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/chat.png
  16. BIN
      docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/dataflow-diagram.png
  17. BIN
      docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/header.png
  18. BIN
      docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/login1.png
  19. BIN
      docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/login2.png
  20. BIN
      docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/new-user.png
  21. 396
      docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/POST.md
  22. BIN
      docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/application-contracts-project.png
  23. BIN
      docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/file-upload-result.gif
  24. BIN
      docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/initial-project.png
  25. BIN
      docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/web-project.png
  26. 276
      docs/en/Community-Articles/2020-08-07-Passwordless-Authentication/POST.md
  27. 26
      docs/en/Contribution/Index.md
  28. 3
      docs/en/Distributed-Event-Bus.md
  29. 8
      docs/en/Entities.md
  30. 181
      docs/en/Getting-Started-Console-Application.md
  31. 9
      docs/en/How-To/Index.md
  32. 2
      docs/en/Local-Event-Bus.md
  33. 2
      docs/en/Multi-Tenancy.md
  34. 5
      docs/en/Samples/Index.md
  35. 8
      docs/en/Startup-Templates/Application.md
  36. 8
      docs/en/Startup-Templates/Console.md
  37. 20
      docs/en/Tutorials/Part-1.md
  38. 10
      docs/en/Tutorials/Part-2.md
  39. 10
      docs/en/Tutorials/Part-3.md
  40. 12
      docs/en/Tutorials/Part-4.md
  41. 14
      docs/en/Tutorials/Part-5.md
  42. 11
      docs/en/Tutorials/Part-6.md
  43. 12
      docs/en/Tutorials/Part-8.md
  44. BIN
      docs/en/Tutorials/images/youtube.png
  45. 4
      docs/en/UI/Angular/Localization.md
  46. 135
      docs/en/UI/Angular/Multi-Tenancy.md
  47. 2
      docs/en/UI/Angular/Permission-Management.md
  48. 11
      docs/en/UI/Angular/Toaster-Service.md
  49. BIN
      docs/en/UI/Angular/images/tenant-switching-box.png
  50. BIN
      docs/en/UI/Angular/images/tenants-page.png
  51. 38
      docs/en/docs-nav.json
  52. BIN
      docs/en/images/basic-console-application-solution.png
  53. 30
      docs/zh-Hans/Authentication/Social-External-Logins.md
  54. 3
      docs/zh-Hans/Background-Workers-Quartz.md
  55. 25
      docs/zh-Hans/Blob-Storing-Aliyun.md
  56. 31
      docs/zh-Hans/Blob-Storing-Aws.md
  57. 11
      docs/zh-Hans/Blob-Storing-Azure.md
  58. 15
      docs/zh-Hans/Blob-Storing-Minio.md
  59. 123
      docs/zh-Hans/Getting-Started-Console-Application.md
  60. 8
      docs/zh-Hans/Startup-Templates/Application.md
  61. 10
      docs/zh-Hans/Startup-Templates/Console.md
  62. 6
      docs/zh-Hans/Tutorials/Part-1.md
  63. 1
      docs/zh-Hans/Tutorials/Part-10.md
  64. 1302
      docs/zh-Hans/Tutorials/Part-2.md
  65. 1233
      docs/zh-Hans/Tutorials/Part-3.md
  66. 255
      docs/zh-Hans/Tutorials/Part-4.md
  67. 1
      docs/zh-Hans/Tutorials/Part-5.md
  68. 1
      docs/zh-Hans/Tutorials/Part-6.md
  69. 1
      docs/zh-Hans/Tutorials/Part-7.md
  70. 1
      docs/zh-Hans/Tutorials/Part-8.md
  71. 1
      docs/zh-Hans/Tutorials/Part-9.md
  72. 4
      docs/zh-Hans/UI/Angular/Localization.md
  73. 4
      docs/zh-Hans/Unit-Of-Work.md
  74. 9
      docs/zh-Hans/docs-nav.json
  75. BIN
      docs/zh-Hans/images/basic-console-application-solution.png
  76. 2
      framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo.Abp.AspNetCore.Authentication.JwtBearer.csproj
  77. 6
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.csproj
  78. 4
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo.Abp.AspNetCore.Mvc.csproj
  79. 12
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionFilter.cs
  80. 8
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionPageFilter.cs
  81. 2
      framework/src/Volo.Abp.AspNetCore.TestBase/Volo.Abp.AspNetCore.TestBase.csproj
  82. 20
      framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/VirtualFileSystemApplicationBuilderExtensions.cs
  83. 402
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/VirtualFileSystem/AbpAspNetCoreContentOptions.cs
  84. 39
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/VirtualFileSystem/AbpFileExtensionContentTypeProvider.cs
  85. 2
      framework/src/Volo.Abp.Authorization/Volo.Abp.Authorization.csproj
  86. 2
      framework/src/Volo.Abp.AutoMapper/Volo.Abp.AutoMapper.csproj
  87. 2
      framework/src/Volo.Abp.Autofac/Volo.Abp.Autofac.csproj
  88. 4
      framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/MinioBlobProvider.cs
  89. 2
      framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo.Abp.Caching.StackExchangeRedis.csproj
  90. 2
      framework/src/Volo.Abp.Caching/Volo.Abp.Caching.csproj
  91. 6
      framework/src/Volo.Abp.Cli/Volo.Abp.Cli.csproj
  92. 9
      framework/src/Volo.Abp.Core/Microsoft/Extensions/Logging/AbpLoggerExtensions.cs
  93. 18
      framework/src/Volo.Abp.Core/Volo.Abp.Core.csproj
  94. 76
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/AggregateRoot.cs
  95. 95
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/BasicAggregateRoot.cs
  96. 6
      framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Localization/ar.json
  97. 2
      framework/src/Volo.Abp.EntityFrameworkCore.SqlServer/Volo.Abp.EntityFrameworkCore.SqlServer.csproj
  98. 8
      framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo.Abp.EntityFrameworkCore.Sqlite.csproj
  99. 4
      framework/src/Volo.Abp.EntityFrameworkCore/Volo.Abp.EntityFrameworkCore.csproj
  100. 8
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs

7
abp_io/AbpIoLocalization/AbpIoLocalization/AbpIoLocalizationModule.cs

@ -3,6 +3,7 @@ using AbpIoLocalization.Admin.Localization;
using AbpIoLocalization.Base.Localization;
using AbpIoLocalization.Blog.Localization;
using AbpIoLocalization.Commercial.Localization;
using AbpIoLocalization.Community.Localization;
using AbpIoLocalization.Docs.Localization;
using AbpIoLocalization.Support.Localization;
using AbpIoLocalization.Www;
@ -29,6 +30,7 @@ namespace AbpIoLocalization
{
options.MapCodeNamespace("Volo.AbpIo.Commercial", typeof(AbpIoCommercialResource));
options.MapCodeNamespace("Volo.AbpIo.Domain", typeof(AbpIoBaseResource));
options.MapCodeNamespace("Volo.AbpIo.Community", typeof(AbpIoCommunityResource));
});
Configure<AbpLocalizationOptions>(options =>
@ -74,6 +76,11 @@ namespace AbpIoLocalization
.Add<AbpIoWwwResource>("en")
.AddVirtualJson("/Www/Localization/Resources")
.AddBaseTypes(typeof(AbpIoBaseResource));
options.Resources
.Add<AbpIoCommunityResource>("en")
.AddVirtualJson("/Community/Localization/Resources")
.AddBaseTypes(typeof(AbpIoBaseResource));
});
}
}

26
abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json

@ -166,6 +166,30 @@
"TotalQuestionMustBeGreaterWarningMessage": "TotalQuestionCount must be greater than RemainingQuestionCount !",
"QuestionCountsMustBeGreaterThanZero": "TotalQuestionCount and RemainingQuestionCount must be zero or greater than zero !",
"UnlimitedQuestionCount": "Unlimited question count",
"Notes": "Notes"
"Notes": "Notes",
"Menu:Community": "Community",
"Menu:Articles": "Articles",
"Wait": "Wait",
"Approve": "Approve",
"Reject": "Reject",
"Details": "Details",
"Url": "Url",
"Title": "Title",
"ContentSource": "Content source",
"Status": "Status",
"ReadArticle": "Read article",
"ArticleHasBeenWaiting": "Article has been waiting",
"ArticleHasBeenApproved": "Article has been approved",
"ArticleHasBeenRejected": "Article has been rejected",
"Permission:Community": "Community",
"Permission:CommunityArticle": "Article",
"Link": "Link",
"Enum:ContentSource:0": "Github",
"Enum:ContentSource:1": "External",
"Enum:Status:0": "Waiting",
"Enum:Status:1": "Rejected",
"Enum:Status:2": "Approved",
"Summary": "Summary",
"AuthorName": "Author name"
}
}

10
abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/AbpIoCommunityResource.cs

@ -0,0 +1,10 @@
using Volo.Abp.Localization;
namespace AbpIoLocalization.Community.Localization
{
[LocalizationResourceName("AbpIoCommunity")]
public class AbpIoCommunityResource
{
}
}

41
abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json

@ -0,0 +1,41 @@
{
"culture": "en",
"texts": {
"Permission:CommunityArticle": "Community Article",
"Permission:Edit": "Edit",
"Waiting": "Waiting",
"Approved": "Approved",
"Rejected": "Rejected",
"Wait": "Wait",
"Approve": "Approve",
"Reject": "Reject",
"ReadArticle": "Read Article",
"Status": "Status",
"ContentSource": "Content Source",
"Details": "Details",
"Url": "Url",
"Title": "Title",
"CreationTime": "Creation time",
"Save": "Save",
"SameUrlAlreadyExist": "Same url already exist if you want to add this article, you should change the url!",
"UrlIsNotValid": "Url is not valid.",
"UrlNotFound" : "Url not found.",
"UrlContentNotFound": "Url content not found.",
"Summary": "Summary",
"MostRead": "Most Read",
"LatestArticles": "Latest Articles",
"ContributeAbpCommunity": "Contribute to the ABP Community",
"SubmitYourArticle": "Submit Your Article",
"ContributionGuide": "Contribution Guide",
"BugReport": "Bug Report",
"SeeAllArticles": "See All Articles",
"WelcomeToABPCommunity!": "Welcome to the ABP Community!",
"MyProfile": "My profile",
"MyOrganizations": "My organizations",
"EmailNotValid": "Please enter a valid email address.",
"FeatureRequest": "Feature Request",
"CreateArticleTitleInfo": "Title of the article to be shown on the article list.",
"CreateArticleUrlInfo": "Original GitHub/External URL of the article.",
"CreateArticleSummaryInfo": "A short summary of the article to be shown on the article list."
}
}

30
docs/en/Authentication/Social-External-Logins.md

@ -0,0 +1,30 @@
# Social/External Logins
## ASP.NET Core MVC / Razor Pages UI
The [Account Module](../Modules/Account.md) has already configured to handle social or external logins out of the box. You can follow the ASP.NET Core documentation to add a social/external login provider to your application.
### Example: Facebook Authentication
Follow the [ASP.NET Core Facebook integration document](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/social/facebook-logins) to support the Facebook login for your application.
#### Add the NuGet Package
Add the [Microsoft.AspNetCore.Authentication.Facebook](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.Facebook) package to your project. Based on your architecture, this can be `.Web`, `.IdentityServer` (for tiered setup) or `.Host` project.
#### Configure the Provider
Use the `.AddFacebook(...)` extension method in the `ConfigureServices` method of your [module](../Module-Development-Basics.md), to configure the client:
````csharp
context.Services.AddAuthentication()
.AddFacebook(facebook =>
{
facebook.AppId = "...";
facebook.AppSecret = "...";
facebook.Scope.Add("email");
facebook.Scope.Add("public_profile");
});
````
> It would be a better practice to use the `appsettings.json` or the ASP.NET Core User Secrets system to store your credentials, instead of a hard-coded value like that. Follow the [Microsoft's document](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/social/facebook-logins) to learn the user secrets usage.

3
docs/en/Background-Workers-Quartz.md

@ -36,6 +36,9 @@ public class YourModule : AbpModule
}
````
> Quartz background worker integration provided `QuartzPeriodicBackgroundWorkerAdapter` to adapt `PeriodicBackgroundWorkerBase` and `AsyncPeriodicBackgroundWorkerBase` derived class. So, you can still fllow the [background workers document](Background-Workers.md) to define the background worker.
> `BackgroundJobWorker` checks todo jobs every 5 seconds, but quartz will not block when long time jobs are executing. So,after Added Quartz background worker integration, you also need to add [Quartz Background Jobs](Background-Jobs-Quartz.md) or [Hangfire Background Jobs](Background-Jobs-Hangfire.md) to avoid duplicate execution jobs.
## Configuration
See [Configuration](Background-Jobs-Quartz#Configuration).

25
docs/en/Blob-Storing-Aliyun.md

@ -23,18 +23,21 @@ Configuration is done in the `ConfigureServices` method of your [module](Module-
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containerscontainer.UseAliyun(aliyun =>
options.Containers.ConfigureDefault(container =>
{
aliyun.AccessKeyId = "your aliyun access key id";
aliyun.AccessKeySecret = "your aliyun access key secret";
aliyun.Endpoint = "your oss endpoint";
aliyun.RegionId = "your sts region id";
aliyun.RoleArn = "the arn of ram role";
aliyun.RoleSessionName = "the name of the certificate";
aliyun.Policy = "policy";
aliyun.DurationSeconds = "expiration date";
aliyun.ContainerName = "your aliyun container name";
aliyun.CreateContainerIfNotExists = false;
container.UseAliyun(aliyun =>
{
aliyun.AccessKeyId = "your aliyun access key id";
aliyun.AccessKeySecret = "your aliyun access key secret";
aliyun.Endpoint = "your oss endpoint";
aliyun.RegionId = "your sts region id";
aliyun.RoleArn = "the arn of ram role";
aliyun.RoleSessionName = "the name of the certificate";
aliyun.Policy = "policy";
aliyun.DurationSeconds = "expiration date";
aliyun.ContainerName = "your aliyun container name";
aliyun.CreateContainerIfNotExists = false;
});
});
});
````

32
docs/en/Blob-Storing-Aws.md

@ -23,23 +23,27 @@ Configuration is done in the `ConfigureServices` method of your [module](Module-
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containerscontainer.UseAws(Aws =>
options.Containers.ConfigureDefault(container =>
{
Aws.AccessKeyId = "your Aws access key id";
Aws.SecretAccessKey = "your Aws access key secret";
Aws.UseCredentials = "set true to use credentials";
Aws.UseTemporaryCredentials = "set true to use temporary credentials";
Aws.UseTemporaryFederatedCredentials = "set true to use temporary federated credentials";
Aws.ProfileName = "the name of the profile to get credentials from";
Aws.ProfilesLocation = "the path to the aws credentials file to look at";
Aws.Region = "the system name of the service";
Aws.Name = "the name of the federated user";
Aws.Policy = "policy";
Aws.DurationSeconds = "expiration date";
Aws.ContainerName = "your Aws container name";
Aws.CreateContainerIfNotExists = false;
container.UseAws(Aws =>
{
Aws.AccessKeyId = "your Aws access key id";
Aws.SecretAccessKey = "your Aws access key secret";
Aws.UseCredentials = "set true to use credentials";
Aws.UseTemporaryCredentials = "set true to use temporary credentials";
Aws.UseTemporaryFederatedCredentials = "set true to use temporary federated credentials";
Aws.ProfileName = "the name of the profile to get credentials from";
Aws.ProfilesLocation = "the path to the aws credentials file to look at";
Aws.Region = "the system name of the service";
Aws.Name = "the name of the federated user";
Aws.Policy = "policy";
Aws.DurationSeconds = "expiration date";
Aws.ContainerName = "your Aws container name";
Aws.CreateContainerIfNotExists = false;
});
});
});
````
> See the [BLOB Storing document](Blob-Storing.md) to learn how to configure this provider for a specific container.

11
docs/en/Blob-Storing-Azure.md

@ -23,11 +23,14 @@ Configuration is done in the `ConfigureServices` method of your [module](Module-
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containerscontainer.UseAzure(azure =>
options.Containers.ConfigureDefault(container =>
{
azure.ConnectionString = "your azure connection string";
azure.ContainerName = "your azure container name";
azure.CreateContainerIfNotExists = false;
container.UseAzure(azure =>
{
azure.ConnectionString = "your azure connection string";
azure.ContainerName = "your azure container name";
azure.CreateContainerIfNotExists = false;
});
});
});
````

15
docs/en/Blob-Storing-Minio.md

@ -23,12 +23,15 @@ Configuration is done in the `ConfigureServices` method of your [module](Module-
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containerscontainer.UseMinio(minio =>
{
minio.EndPoint = "your minio endPoint";
minio.AccessKey = "your minio accessKey";
minio.SecretKey = "your minio secretKey";
minio.BucketName = "your minio bucketName";
options.Containers.ConfigureDefault(container =>
{
container.UseMinio(minio =>
{
minio.EndPoint = "your minio endPoint";
minio.AccessKey = "your minio accessKey";
minio.SecretKey = "your minio secretKey";
minio.BucketName = "your minio bucketName";
});
});
});
````

11
docs/en/How-To/Customize-SignIn-Manager.md → docs/en/Community-Articles/2020-04-19-Customize-the-SignIn-Manager/POST.md

@ -1,6 +1,6 @@
# How to Customize the SignIn Manager for ABP Applications
After creating a new application using the [application startup template](../Startup-Templates/Application.md), you may want extend or change the default behavior of the SignIn Manager for your authentication and registration flow needs. ABP [Account Module](../Modules/Account.md) uses the [Identity Management Module](../Modules/Identity.md) for SignIn Manager and the [Identity Management Module](../Modules/Identity.md) uses default [Microsoft Identity SignIn Manager](https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs) ([see here](https://github.com/abpframework/abp/blob/be32a55449e270d2d456df3dabdc91f3ffdd4fa9/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreModule.cs#L17)).
After creating a new application using the [application startup template](https://docs.abp.io/en/abp/latest/Startup-Templates/Application), you may want extend or change the default behavior of the SignIn Manager for your authentication and registration flow needs. ABP [Account Module](https://docs.abp.io/en/abp/latest/Modules/Account) uses the [Identity Management Module](https://docs.abp.io/en/abp/latest/Modules/Identity) for SignIn Manager and the [Identity Management Module](https://docs.abp.io/en/abp/latest/Modules/Identity) uses default [Microsoft Identity SignIn Manager](https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs) ([see here](https://github.com/abpframework/abp/blob/be32a55449e270d2d456df3dabdc91f3ffdd4fa9/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreModule.cs#L17)).
To write your Custom SignIn Manager, you need to extend [Microsoft Identity SignIn Manager](https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs) class and register it to the DI container.
@ -76,7 +76,7 @@ public override async Task<Microsoft.AspNetCore.Identity.ExternalLoginInfo> GetE
}
````
To get your overridden method invoked and your customized SignIn Manager class to work, you need to register your class to the [Dependency Injection System](../Dependency-Injection.md).
To get your overridden method invoked and your customized SignIn Manager class to work, you need to register your class to the [Dependency Injection System](https://docs.abp.io/en/abp/latest/Dependency-Injection).
## Register to Dependency Injection
@ -93,9 +93,4 @@ PreConfigure<IdentityBuilder>(identityBuilder =>
## The Source Code
You can find the source code of the completed example [here](https://github.com/abpframework/abp-samples/tree/master/Authentication-Customization).
## See Also
* [How to Customize the Login Page for MVC / Razor Page Applications](Customize-Login-Page-MVC.md).
* [Identity Management Module](../Modules/Identity.md).
You can find the source code of the completed example [here](https://github.com/abpframework/abp-samples/tree/master/Authentication-Customization).

7
docs/en/How-To/Azure-Active-Directory-Authentication-MVC.md → docs/en/Community-Articles/2020-04-27-Use-Azure-Active-Directory-Authentication-for-MVC-Razor-Page-Applications/POST.md

@ -80,7 +80,7 @@ private void ConfigureAuthentication(ServiceConfigurationContext context, IConfi
> * Add `JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear()`. This will disable the default Microsoft claim type mapping.
> * Add `JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier)`. Mapping this to [ClaimTypes.NameIdentifier](https://github.com/dotnet/runtime/blob/6d395de48ac718a913e567ae80961050f2a9a4fa/src/libraries/System.Security.Claims/src/System/Security/Claims/ClaimTypes.cs#L59) is important since default SignIn Manager behavior uses this claim type for external login information.
> * Add `options.SignInScheme = IdentityConstants.ExternalScheme` since [default signin scheme is `AzureADOpenID`](https://github.com/dotnet/aspnetcore/blob/c56aa320c32ee5429d60647782c91d53ac765865/src/Azure/AzureAD/Authentication.AzureAD.UI/src/AzureADOpenIdConnectOptionsConfiguration.cs#L35).
> * Add `options.Scope.Add("email")` if you are using **v2.0** endpoint of AzureAD since v2.0 endpoint doesn't return the `email` claim as default. The [Account Module](../Modules/Account.md) uses `email` claim to [register external users](https://github.com/abpframework/abp/blob/be32a55449e270d2d456df3dabdc91f3ffdd4fa9/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs#L215).
> * Add `options.Scope.Add("email")` if you are using **v2.0** endpoint of AzureAD since v2.0 endpoint doesn't return the `email` claim as default. The [Account Module](https://docs.abp.io/en/abp/latest/Modules/Account) uses `email` claim to [register external users](https://github.com/abpframework/abp/blob/be32a55449e270d2d456df3dabdc91f3ffdd4fa9/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs#L215).
You are done and integration is completed.
@ -205,8 +205,3 @@ You can find the source code of the completed example [here](https://github.com/
await Task.CompletedTask;
});
````
## See Also
* [How to Customize the Login Page for MVC / Razor Page Applications](Customize-Login-Page-MVC.md).
* [How to Customize the SignIn Manager for ABP Applications](Customize-SignIn-Manager.md).

10
docs/en/How-To/Customize-Login-Page-MVC.md → docs/en/Community-Articles/2020-05-09-Customize-the-Login-Page-for-MVC-Razor-Page-Applications/POST.md

@ -1,6 +1,6 @@
# How to Customize the Login Page for MVC / Razor Page Applications
When you create a new application using the [application startup template](../Startup-Templates/Application.md), source code of the login page will not be inside your solution, so you can not directly change it. The login page comes from the [Account Module](../Modules/Account.md) that is used a [NuGet package](https://www.nuget.org/packages/Volo.Abp.Account.Web) reference.
When you create a new application using the [application startup template](https://docs.abp.io/en/abp/latest/Startup-Templates/Application), source code of the login page will not be inside your solution, so you can not directly change it. The login page comes from the [Account Module](https://docs.abp.io/en/abp/latest/Modules/Account) that is used a [NuGet package](https://www.nuget.org/packages/Volo.Abp.Account.Web) reference.
This document explains how to customize the login page for your own application.
@ -20,7 +20,7 @@ public class CustomLoginModel : LoginModel
}
````
> Naming convention is important here. If your class name doesn't end with `LoginModel`, you need to manually replace the `LoginModel` using the [dependency injection](../Dependency-Injection.md) system.
> Naming convention is important here. If your class name doesn't end with `LoginModel`, you need to manually replace the `LoginModel` using the [dependency injection](https://docs.abp.io/en/abp/latest/Dependency-Injection) system.
Then you can override any method you need and add new methods and properties needed by the UI.
@ -105,8 +105,4 @@ Just changed the `@model` to `Acme.BookStore.Web.Pages.Account.CustomLoginModel`
## The Source Code
You can find the source code of the completed example [here](https://github.com/abpframework/abp-samples/tree/master/Authentication-Customization).
## See Also
* [ASP.NET Core (MVC / Razor Pages) User Interface Customization Guide](../UI/AspNetCore/Customization-User-Interface.md).
You can find the source code of the completed example [here](https://github.com/abpframework/abp-samples/tree/master/Authentication-Customization).

462
docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/POST.md

@ -0,0 +1,462 @@
# Real Time Messaging In A Distributed Architecture Using Abp Framework, SingalR & RabbitMQ
In this article, we will build a basic real time messaging application in a distributed architecture. We will use [Abp Framework](https://abp.io) for infrastructure and tiered startup template, [SignalR](https://dotnet.microsoft.com/apps/aspnet/signalr) for real time server-client communication and [RabbitMQ](https://www.rabbitmq.com/) as the distributed event bus.
When Web & API tiers are separated, it is impossible to directly send a server-to-client message from the HTTP API. This is also true for a microservice architected application. We suggest to use the distributed event bus to deliver the message from API application to the web application, then to the client.
![data flow](dataflow-diagram.png)
Above, you can see the data-flow that we will implement in this article. This diagram represents how data will flow in our application when **Client 1** sends a message to **Client 2**. It is explained in 5 steps:
1. **Client 1** sends a message data to **Web Application** via REST call.
2. **Web Application** redirects the message data to **Http Api**.
3. The message data is processed in **Http Api** and **Http Api** publishes an event that holds the data that will be sent to **Client 2**.
4. **Web application**, that is subscribed to that event, receives it.
5. **Web Application** sends the message to **Client 2**.
For this example flow, we could send message from **Client 1** to **Client 2** directly on the **SignalR Hub**. However, what we are trying here to demonstrate is sending a real-time message from the **Http Api** to a specific user who is connected to the web application.
## Implementation
### Startup template and initial run
[Abp Framework](https://www.abp.io) offers startup templates to get into the business faster. We can download a new tiered startup template using [Abp CLI](https://docs.abp.io/en/abp/latest/CLI):
`abp new SignalRTieredDemo --tiered`
After download is finished, we run ***.DbMigrator** project to create the database and seed initial data (admin user, role etc). Then we run ***.IdentityServer**, ***.HttpApi.Host** and ***.Web** to see our application working.
### Creating Application Layer
We create an [application service](https://docs.abp.io/en/abp/latest/Application-Services) that publishes the message as event.
In ***.Application.Contracts** project:
````csharp
using System.Threading.Tasks;
using Volo.Abp.Application.Services;
namespace SignalRTieredDemo
{
public interface IChatAppService : IApplicationService
{
Task SendMessageAsync(SendMessageInput input);
}
}
````
Input DTO for SendMessageAsync method:
````csharp
namespace SignalRTieredDemo
{
public class SendMessageInput
{
public string TargetUserName { get; set; }
public string Message { get; set; }
}
}
````
Event transfer object (ETO) for communication on event bus:
````csharp
using System;
namespace SignalRTieredDemo
{
public class ReceivedMessageEto
{
public string ReceivedText { get; set; }
public Guid TargetUserId { get; set; }
public string SenderUserName { get; set; }
public ReceivedMessageEto(
Guid targetUserId, string senderUserName, string receivedText)
{
ReceivedText = receivedText;
TargetUserId = targetUserId;
SenderUserName = senderUserName;
}
}
}
````
In ***.Application** project:
````csharp
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.Identity;
namespace SignalRTieredDemo
{
public class ChatAppService: SignalRTieredDemoAppService, IChatAppService
{
private readonly IIdentityUserRepository _identityUserRepository;
private readonly ILookupNormalizer _lookupNormalizer;
private readonly IDistributedEventBus _distributedEventBus;
public ChatAppService(IIdentityUserRepository identityUserRepository, ILookupNormalizer lookupNormalizer, IDistributedEventBus distributedEventBus)
{
_identityUserRepository = identityUserRepository;
_lookupNormalizer = lookupNormalizer;
_distributedEventBus = distributedEventBus;
}
public async Task SendMessageAsync(SendMessageInput input)
{
var targetId = (await _identityUserRepository.FindByNormalizedUserNameAsync(_lookupNormalizer.NormalizeName(input.TargetUserName))).Id;
await _distributedEventBus.PublishAsync(new ReceivedMessageEto(targetId, CurrentUser.UserName, input.Message));
}
}
}
````
### Creating API Layer
We create an endpoint for sending message that redirects the process to application layer:
In **controllers** folder of ***.HttpApi** project:
````csharp
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc;
namespace SignalRTieredDemo.Controllers
{
[Route("api/app/chat")]
public class ChatController : AbpController, IChatAppService
{
private readonly IChatAppService _chatAppService;
public ChatController(IChatAppService chatAppService)
{
_chatAppService = chatAppService;
}
[HttpPost]
[Route("send-message")]
public async Task SendMessageAsync(SendMessageInput input)
{
await _chatAppService.SendMessageAsync(input);
}
}
}
````
### Adding SignalR
To add SignalR to our solution, we add `Volo.Abp.AspNetCore.SignalR` nuget package to ***.Web** project.
And then add `AbpAspNetCoreSignalRModule` dependency:
````csharp
namespace SignalRTieredDemo.Web
{
[DependsOn(
...
typeof(AbpAspNetCoreSignalRModule) // <---
)]
public class SignalRTieredDemoWebModule : AbpModule
{
````
Also, we need to add [@abp/signalr](https://www.npmjs.com/package/@abp/signalr) npm package to package.json in ***.Web** project, then run **yarn** and **gulp** commands.
`````json
{
.
.
"dependencies": {
.
.
"@abp/signalr": "^2.9.0"
}
}
`````
*Remember to add the latest package version.*
You can find more information for Abp SignalR Integration on [the related document](https://docs.abp.io/en/abp/latest/SignalR-Integration).
### Creating A Hub
We need a hub for SignalR connection. We can inherit it from `AbpHup` base class.
In ***.Web** project:
````csharp
using Microsoft.AspNetCore.Authorization;
using Volo.Abp.AspNetCore.SignalR;
namespace SignalRTieredDemo.Web
{
[Authorize]
public class ChatHub : AbpHub
{
}
}
````
While you could inherit from the standard `Hub` class, `AbpHub` has some common services pre-injected as base properties, which is useful on your development.
### Adding & Configuring RabbitMQ
To add RabbitMQ to our solution, we add `Volo.Abp.EventBus.RabbitMQ` nuget package to ***.HttpApi.Host** and ***.Web** projects.
Launch a **command line**, navigate to directory where ***.HttpApi.Host.csproj** file exist, and run the command below using [Abp CLI](https://docs.abp.io/en/abp/latest/CLI):
````bash
abp add-package Volo.Abp.EventBus.RabbitMQ
````
Then do the same for ***.Web** project.
After we add the package, we configure RabbitMQ by adding configuration in **appsettings.json** files of those projects.
For ***.HttpApi.Host** project:
````json
{
...
"RabbitMQ": {
"Connections": {
"Default": {
"HostName": "localhost"
}
},
"EventBus": {
"ClientName": "SignalRTieredDemo_HttpApi",
"ExchangeName": "SignalRTieredDemoTest"
}
},
...
}
````
For ***.Web** project:
````json
{
...
"RabbitMQ": {
"Connections": {
"Default": {
"HostName": "localhost"
}
},
"EventBus": {
"ClientName": "SignalRTieredDemo_Web",
"ExchangeName": "SignalRTieredDemoTest"
}
},
...
}
````
### Handling New Message Event
Once we publish a new message event from `Http Api`, we must to handle it in `Web Application`. Therefore we need an event handler in ***.Web** Project:
````csharp
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus.Distributed;
namespace SignalRTieredDemo.Web
{
public class ReceivedMessageEventHandler :
IDistributedEventHandler<ReceivedMessageEto>,
ITransientDependency
{
private readonly IHubContext<ChatHub> _hubContext;
public ReceivedMessageEventHandler(IHubContext<ChatHub> hubContext)
{
_hubContext = hubContext;
}
public async Task HandleEventAsync(ReceivedMessageEto eto)
{
var message = $"{eto.SenderUserName}: {eto.ReceivedText}";
await _hubContext.Clients
.User(eto.TargetUserId.ToString())
.SendAsync("ReceiveMessage", message);
}
}
}
````
### Creating Chat Page
We create the files below in **Pages** folder of ***.Web** Project.
**Chat.cshtml**:
````html
@page
@using Volo.Abp.AspNetCore.Mvc.UI.Packages.SignalR
@model SignalRTieredDemo.Web.Pages.ChatModel
@section styles {
<abp-style src="/Pages/Chat.css" />
}
@section scripts {
<abp-script type="typeof(SignalRBrowserScriptContributor)" />
<abp-script src="/Pages/Chat.js" />
}
<h1>Chat</h1>
<div>
<abp-row>
<abp-column size-md="_6">
<div>All Messages:</div>
<ul id="MessageList" style="">
</ul>
</abp-column>
<abp-column size-md="_6">
<form>
<abp-row>
<abp-column>
<label for="TargetUser">Target user:</label>
<input type="text" id="TargetUser" />
</abp-column>
</abp-row>
<abp-row class="mt-2">
<abp-column>
<label for="Message">Message:</label>
<textarea id="Message" rows="4"></textarea>
</abp-column>
</abp-row>
<abp-row class="mt-2">
<abp-column>
<abp-button type="submit" id="SendMessageButton" button-type="Primary" size="Block" text="SEND!" />
</abp-column>
</abp-row>
</form>
</abp-column>
</abp-row>
</div>
````
**Chat.cshtml.cs**:
````csharp
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace SignalRTieredDemo.Web.Pages
{
public class ChatModel : PageModel
{
public void OnGet()
{
}
}
}
````
**Chat.css**:
````css
#MessageList {
border: 1px solid gray;
height: 400px;
overflow: auto;
list-style: none;
padding-left: 0;
padding: 10px;
}
#TargetUser {
width: 100%;
}
#Message {
width: 100%;
}
````
**Chat.js**:
````javascript
$(function () {
var connection = new signalR.HubConnectionBuilder().withUrl("/signalr-hubs/chat").build();
connection.on("ReceiveMessage", function (message) {
console.log(message);
$('#MessageList').append('<li><strong><i class="fas fa-long-arrow-alt-right"></i> ' + message + '</strong></li>');
});
connection.start().then(function () {
}).catch(function (err) {
return console.error(err.toString());
});
$('#SendMessageButton').click(function (e) {
e.preventDefault();
var targetUserName = $('#TargetUser').val();
var message = $('#Message').val();
$('#Message').val('');
signalRTieredDemo.controllers.chat.sendMessage({
targetUserName: targetUserName,
message: message
}).then(function() {
$('#MessageList')
.append('<li><i class="fas fa-long-arrow-alt-left"></i> ' + abp.currentUser.userName + ': ' + message + '</li>');
});
});
});
````
Then we can add this new page to menu on ***MenuContributor.cs** in **Menus** folder:
````csharp
...
public class SignalRTieredDemoMenuContributor : IMenuContributor
{
...
private Task ConfigureMainMenuAsync(MenuConfigurationContext context)
{
...
context.Menu.Items.Add(new ApplicationMenuItem("SignalRDemo.Chat", "Chat", "/Chat")); // <-- We add this line
return Task.CompletedTask;
}
...
}
````
## Running & Testing
We run ***.IdentityServer**, ***.HttpApi.Host** and ***.Web** in order. After ***.Web** project is ran, firstly login with `admin` username and `1q2w3E*` password.
![click on login](login1.png)
![login with `admin` username and `1q2w3E*` password.](login2.png)
After we login, go to `/Identity/Users` page and create a new user. So that we can chat with them.
![create a new user](new-user.png)
Then we open the application in another browser and login with the user we created above. Now we can go to chat page and start messaging:
![messaging](chat.png)
We can test with more user. All sent and incoming messages are displayed in the left box.
### Source code
Source code of the final application can be found on the [GitHub repository](https://github.com/abpframework/abp-samples/tree/master/SignalRTieredDemo).

BIN
docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/chat.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

BIN
docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/dataflow-diagram.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

BIN
docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/header.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

BIN
docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/login1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/login2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/new-user.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

396
docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/POST.md

@ -0,0 +1,396 @@
# File Upload/Download with BLOB Storage System in ASP.NET Core & ABP Framework
## Introduction
This step-by-step article describes how to upload a file to a Web server and also download by client with using ASP.NET Core & ABP Framework. By following this article, you will create a web project and its related code to upload and download files.
Before the creating application, we need to know some fundamentals.
## BLOB Storing
It is typical to **store file contents** in an application and read these file contents on need. Not only files, but you may also need to save various types of **large binary objects**, a.k.a. [BLOB](https://en.wikipedia.org/wiki/Binary_large_object)s, into a **storage**. For example, you may want to save user profile pictures.
A BLOB is a typically **byte array**. There are various places to store a BLOB item; storing in the local file system, in a shared database or on the [Azure BLOB storage](https://azure.microsoft.com/en-us/services/storage/blobs/) can be options.
The ABP Framework provides an abstraction to work with BLOBs and provides some pre-built storage providers that you can easily integrate to. Having such an abstraction has some benefits;
- You can **easily integrate** to your favorite BLOB storage provides with a few lines of configuration.
- You can then **easily change** your BLOB storage without changing your application code.
- If you want to create **reusable application modules**, you don't need to make assumption about how the BLOBs are stored.
ABP BLOB Storage system is also compatible to other ABP Framework features like [multi-tenancy](https://docs.abp.io/en/abp/latest/Multi-Tenancy).
To get more information about ABP BLOB Storing system, please check this [documentation](https://docs.abp.io/en/abp/latest/Blob-Storing).
## Preparing the Project
### Startup template and the initial run
Abp Framework offers startup templates to get into the business faster. We can download a new startup template using Abp CLI:
`abp new FileActionsDemo -m none`
After the download is finished, we run `FileActionsDemo.DbMigrator` project to create the database and seed initial data (admin user, role, etc). Then we run `FileActionsDemo.Web` to see our application working.
> _Default admin username is **admin** and password is **1q2w3E\***_
![initial-project](initial-project.png)
### Adding Blob Storing Module
For this article, we use [Blob Storing Database Provider](https://docs.abp.io/en/abp/latest/Blob-Storing-Database).
You can use [Azure](https://docs.abp.io/en/abp/latest/Blob-Storing-Azure) or [File System](https://docs.abp.io/en/abp/latest/Blob-Storing-File-System) providers also.
Open a command prompt (terminal) in the folder containing your solution (.sln) file and run the following command:
`abp add-module Volo.Abp.BlobStoring.Database`
This action will add the module depencies and also module migration. After this action, run `FileActionsDemo.DbMigrator` to update the database.
### Setting up Blob Storaging
BLOB Strorage system works with `Containers`. Before the using blob storage, we need to create our blob container.
Create a class that name `MyFileContainer` at the `FileActionsDemo.Domain` project.
```csharp
using Volo.Abp.BlobStoring;
namespace FileActionsDemo
{
[BlobContainerName("my-file-container")]
public class MyFileContainer
{
}
}
```
That's all, we can start to use BLOB storing in our application.
## Creating Application Layer
Before the creating Application Service, we need to create some [DTO](https://docs.abp.io/en/abp/latest/Data-Transfer-Objects)s that used by Application Service.
Create following DTOs in `FileActionsDemo.Application.Contracts` project.
- `BlobDto.cs`
```csharp
namespace FileActionsDemo
{
public class BlobDto
{
public byte[] Content { get; set; }
public string Name { get; set; }
}
}
```
- `GetBlobRequestDto.cs`
```csharp
using System.ComponentModel.DataAnnotations;
namespace FileActionsDemo
{
public class GetBlobRequestDto
{
[Required]
public string Name { get; set; }
}
}
```
- `SaveBlobInputDto.cs`
```csharp
using System.ComponentModel.DataAnnotations;
namespace FileActionsDemo
{
public class SaveBlobInputDto
{
public byte[] Content { get; set; }
[Required]
public string Name { get; set; }
}
}
```
Create `IFileAppService.cs` interface at the same place with DTOs.
- `IFileAppService`
```csharp
using System.Threading.Tasks;
using Volo.Abp.Application.Services;
namespace FileActionsDemo
{
public interface IFileAppService : IApplicationService
{
Task SaveBlobAsync(SaveBlobInputDto input);
Task<BlobDto> GetBlobAsync(GetBlobRequestDto input);
}
}
```
After creating DTOs and interface, `FileActionsDemo.Application.Contracts` project should be like as following image.
![application-contracts-project](application-contracts-project.png)
Then we can create our Application Service.
Create `FileAppService.cs` in `FileActionsDemo.Application` project.
```csharp
using System.Threading.Tasks;
using Volo.Abp.Application.Services;
using Volo.Abp.BlobStoring;
namespace FileActionsDemo
{
public class FileAppService : ApplicationService, IFileAppService
{
private readonly IBlobContainer<MyFileContainer> _fileContainer;
public FileAppService(IBlobContainer<MyFileContainer> fileContainer)
{
_fileContainer = fileContainer;
}
public async Task SaveBlobAsync(SaveBlobInputDto input)
{
await _fileContainer.SaveAsync(input.Name, input.Content, true);
}
public async Task<BlobDto> GetBlobAsync(GetBlobRequestDto input)
{
var blob = await _fileContainer.GetAllBytesAsync(input.Name);
return new BlobDto
{
Name = input.Name,
Content = blob
};
}
}
}
```
As you see in previous code block, we inject `IBlobContainer<MyFileContainer>` to our app service. It will handle all blob actions for us.
- `SaveBlobAsync` method uses `SaveAsync` of `IBlobContainer<MyFileContainer>` to save the given blob to storage, this is a simple example so we don't check is there any file exist with same name. We sent blob name, blob content and `true` for `overrideExisting` parameter.
- `GetBlobAsync` method is uses `GetAllBytesAsync` of `IBlobContainer<MyFileContainer>` to get blob content by name.
We finished the application layer for this project. After that we will create a `Controller` for API and `Razor Page` for UI.
## Creating Controller
Create `FileController.cs` in your `FileActionsDemo.HttpApi` project.
```csharp
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc;
namespace FileActionsDemo
{
public class FileController : AbpController
{
private readonly IFileAppService _fileAppService;
public FileController(IFileAppService fileAppService)
{
_fileAppService = fileAppService;
}
[HttpGet]
[Route("download/{fileName}")]
public async Task<IActionResult> DownloadAsync(string fileName)
{
var fileDto = await _fileAppService.GetBlobAsync(new GetBlobRequestDto{ Name = fileName });
return File(fileDto.Content, "application/octet-stream", fileDto.Name);
}
}
}
```
As you see, `FileController` injects `IFileAppService` that we defined before. This controller has only one endpoint.
`DownloadAsync` is using to send file from server to client.
This endpoint is requires only a `string` parameter, then we use that parameter to get stored blob. If blob is exist, we return a `File` result so download process can start.
## Creating User Interface
We will create only one page to prove download and upload actions are working.
Create folder that name `Files` in your `Pages` folder at `FileActionsDemo.Web` project.
Create a Razor page that name `Index` with its model.
- `Index.cshtml.cs`
```csharp
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.UI.RazorPages;
namespace FileActionsDemo.Web.Pages.Files
{
public class Index : AbpPageModel
{
[BindProperty]
public UploadFileDto UploadFileDto { get; set; }
private readonly IFileAppService _fileAppService;
public bool Uploaded { get; set; } = false;
public Index(IFileAppService fileAppService)
{
_fileAppService = fileAppService;
}
public void OnGet()
{
}
public async Task<IActionResult> OnPostAsync()
{
using (var memoryStream = new MemoryStream())
{
await UploadFileDto.File.CopyToAsync(memoryStream);
await _fileAppService.SaveBlobAsync(
new SaveBlobInputDto
{
Name = UploadFileDto.Name,
Content = memoryStream.ToArray()
}
);
}
return Page();
}
}
public class UploadFileDto
{
[Required]
[Display(Name = "File")]
public IFormFile File { get; set; }
[Required]
[Display(Name = "Filename")]
public string Name { get; set; }
}
}
```
As you see, we use `UploadFileDto` as a `BindProperty` and we inject `IFileAppService` to upload files.
The `UploadFileDto` is requires a `string` parameter for using as a blob name and a `IFormFile` that sent by user.
At the post action (`OnPostAsync`), if everything is well, we use `MemoryStream` to get all bytes from file content.
Then we save file with `SaveBlobAsync` method of `IFileAppService`.
- `Index.cshtml`
```csharp
@page
@model FileActionsDemo.Web.Pages.Files.Index
@section scripts{
<abp-script src="/Pages/Files/index.js" />
}
<abp-card>
<abp-card-header>
<h3>File Upload and Download</h3>
</abp-card-header>
<abp-card-body>
<abp-row>
<abp-column>
<h3>Upload File</h3>
<hr />
<form method="post" enctype="multipart/form-data">
<abp-input asp-for="UploadFileDto.Name"></abp-input>
<abp-input asp-for="UploadFileDto.File"></abp-input>
<input type="submit" class="btn btn-info" />
</form>
</abp-column>
<abp-column style="border-left: 1px dotted gray">
<h3>Download File</h3>
<hr />
<form id="DownloadFile">
<div class="form-group">
<label for="fileName">Filename</label><span> * </span>
<input type="text" id="fileName" name="fileName" class="form-control ">
</div>
<input type="submit" class="btn btn-info"/>
</form>
</abp-column>
</abp-row>
</abp-card-body>
</abp-card>
```
We divided the page vertically, left side will be using for upload and right side will be using for download. We use [ABP Tag Helpers](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Tag-Helpers/Index) to create page.
- `index.js`
```javascript
$(function () {
var DOWNLOAD_ENDPOINT = "/download";
var downloadForm = $("form#DownloadFile");
downloadForm.submit(function (event) {
event.preventDefault();
var fileName = $("#fileName").val().trim();
var downloadWindow = window.open(
DOWNLOAD_ENDPOINT + "/" + fileName,
"_blank"
);
downloadWindow.focus();
});
$("#UploadFileDto_File").change(function () {
var fileName = $(this)[0].files[0].name;
$("#UploadFileDto_Name").val(fileName);
});
});
```
This jQuery codes are using for download. Also we wrote a simple code to autofill `Filename` input when user selects a file.
After creating razor page and js file, `FileActionsDemo.Web` project should be like as following image.
![web-project](web-project.png)
## Result
After completing code tutorial, run `FileActionsDemo.Web` project and go `/Files`. You can upload any file with any name and also download those uploaded files.
![file-up](file-upload-result.gif)

BIN
docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/application-contracts-project.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

BIN
docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/file-upload-result.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

BIN
docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/initial-project.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/web-project.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

276
docs/en/Community-Articles/2020-08-07-Passwordless-Authentication/POST.md

@ -0,0 +1,276 @@
# Implementing Passwordless Authentication in ASP.NET Core Identity
## Introduction
To allow a user login with a magic URL, you need to implement a custom token provider. In this tutorial, we will show you how to add a custom token provider to authenticate a user with a link, instead of entering a password.
### Source Code
The completed sample is available on [the GitHub repository](https://github.com/abpframework/abp-samples/tree/master/PasswordlessAuthentication).
## Creating the Solution
Before starting to the development, create a new solution named `PasswordlessAuthentication` and run it by following the [getting started tutorial](https://docs.abp.io/en/abp/latest/Getting-Started?UI=MVC&DB=EF&Tiered=No).
## Step-1
Create a class named **PasswordlessLoginProvider** in your ***.Web** project:
```csharp
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
namespace PasswordlessAuthentication.Web
{
public class PasswordlessLoginProvider<TUser> : TotpSecurityStampBasedTokenProvider<TUser>
where TUser : class
{
public override Task<bool> CanGenerateTwoFactorTokenAsync(UserManager<TUser> manager, TUser user)
{
return Task.FromResult(false);
}
//We need to override this method as well.
public override async Task<string> GetUserModifierAsync(string purpose, UserManager<TUser> manager, TUser user)
{
var userId = await manager.GetUserIdAsync(user);
return "PasswordlessLogin:" + purpose + ":" + userId;
}
}
}
```
## Step-2
Create **IdentityBuilderExtensions.cs** in your ***.Web** project. We will use this extension method in the `ConfigureServices`.
```csharp
using Microsoft.AspNetCore.Identity;
namespace PasswordlessAuthentication.Web
{
public static class IdentityBuilderExtensions
{
public static IdentityBuilder AddPasswordlessLoginProvider(this IdentityBuilder builder)
{
var userType = builder.UserType;
var totpProvider = typeof(PasswordlessLoginProvider<>).MakeGenericType(userType);
return builder.AddTokenProvider("PasswordlessLoginProvider", totpProvider);
}
}
}
```
## Step-3
Add the token provider to the `Identity` middleware. To do this, find the module class (eg: `PasswordlessAuthenticationWebModule.cs` in here) in your ***.Web** project and add the below into the `ConfigureServices()` method.
```csharp
public override void ConfigureServices(ServiceConfigurationContext context)
{
//...
context.Services
.GetObject<IdentityBuilder>()
.AddDefaultTokenProviders()
.AddPasswordlessLoginProvider();
}
```
## Step-4
We need to create a user interface to be able to generate the magic login link. To do this quickly, open your existing **Index.cshtml.cs** in your ***.Web** project. It's under `Pages` folder. And copy-paste the below content.
**Index.cshtml.cs**
```csharp
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.Identity;
namespace PasswordlessAuthentication.Web.Pages
{
public class IndexModel : PasswordlessAuthenticationPageModel
{
protected IdentityUserManager UserManager { get; }
private readonly IIdentityUserRepository _userRepository;
public string PasswordlessLoginUrl { get; set; }
public string Email { get; set; }
public IndexModel(IdentityUserManager userManager, IIdentityUserRepository userRepository)
{
UserManager = userManager;
_userRepository = userRepository;
}
public ActionResult OnGet()
{
if (!CurrentUser.IsAuthenticated)
{
return Redirect("/Account/Login");
}
return Page();
}
//added for passwordless authentication
public async Task<IActionResult> OnPostGeneratePasswordlessTokenAsync()
{
var adminUser = await _userRepository.FindByNormalizedUserNameAsync("admin");
var token = await UserManager.GenerateUserTokenAsync(adminUser, "PasswordlessLoginProvider",
"passwordless-auth");
PasswordlessLoginUrl = Url.Action("Login", "Passwordless",
new {token = token, userId = adminUser.Id.ToString()}, Request.Scheme);
return Page();
}
}
}
```
We added `OnPostGeneratePasswordlessTokenAsync()` action to generate the link. We will generate a link for the **admin** user. Therefore, we injected `IIdentityUserRepository` to get admin user Id. Using the `UserManager.GenerateUserTokenAsync()` method, we generated a token. After that, we created the URL with the admin user Id and the token. Now we will show the `PasswordlessLoginUrl` on the page.
## Step-5
Create a class named **PasswordlessAuthenticationMenus** under `Menus` folder in your ***.Web** project. And set the content as below.
```csharp
namespace PasswordlessAuthentication.Web.Menus
{
public class PasswordlessAuthenticationMenus
{
public const string GroupName = "PasswordlessAuthentication";
public const string Home = GroupName + ".Home";
}
}
```
## Step-6
Open your **Index.cshtml** and set the content as below. We added a form that posts to `GeneratePasswordlessToken` action in the razor page. And it will set the `PasswordlessLoginUrl` field.
```html
@page
@inject IHtmlLocalizer<PasswordlessAuthenticationResource> L
@using Microsoft.AspNetCore.Mvc.Localization
@using PasswordlessAuthentication.Localization
@using PasswordlessAuthentication.Web.Menus
@using Volo.Abp.AspNetCore.Mvc.UI.Layout
@model PasswordlessAuthentication.Web.Pages.IndexModel
@{
ViewBag.PageTitle = "Home";
}
@inject IPageLayout PageLayout;
@{
PageLayout.Content.Title = L["Home"].Value;
PageLayout.Content.BreadCrumb.Add(L["Menu:Home"].Value);
PageLayout.Content.MenuItemName = PasswordlessAuthenticationMenus.Home;
}
<abp-card>
<abp-card-body>
<form asp-page-handler="GeneratePasswordlessToken" method="post">
<abp-button button-type="Dark" type="submit">Generate passwordless token link</abp-button>
@if (Model.PasswordlessLoginUrl != null)
{
<abp-card class="mt-3 p-3">
[@Model.PasswordlessLoginUrl](/en/commercial/latest/how-to/@Model.PasswordlessLoginUrl)
</abp-card>
}
</form>
</abp-card-body>
</abp-card>
```
## Step-7
We implemented token generation infrastructure, now it's time validate the token and let the user in. To do this create a folder named `Controllers` in your ***.Web** project and create a controller, named **PasswordlessController** inside it:
```csharp
using System;
using System.Collections.Generic;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.Identity;
using Volo.Abp.Security.Claims;
using Volo.Abp.Users;
namespace PasswordlessAuthentication.Web.Controllers
{
public class PasswordlessController : AbpController
{
protected IdentityUserManager UserManager { get; }
public PasswordlessController(IdentityUserManager userManager)
{
UserManager = userManager;
}
public virtual async Task<IActionResult> Login(string token, string userId)
{
var user = await UserManager.FindByIdAsync(userId);
var isValid = await UserManager.VerifyUserTokenAsync(user, "PasswordlessLoginProvider", "passwordless-auth", token);
if (!isValid)
{
throw new UnauthorizedAccessException("The token " + token + " is not valid for the user " + userId);
}
await UserManager.UpdateSecurityStampAsync(user);
var roles = await UserManager.GetRolesAsync(user);
var principal = new ClaimsPrincipal(
new ClaimsIdentity(CreateClaims(user, roles), IdentityConstants.ApplicationScheme)
);
await HttpContext.SignInAsync(IdentityConstants.ApplicationScheme, principal);
return Redirect("/");
}
private static IEnumerable<Claim> CreateClaims(IUser user, IEnumerable<string> roles)
{
var claims = new List<Claim>
{
new Claim("sub", user.Id.ToString()),
new Claim(AbpClaimTypes.UserId, user.Id.ToString()),
new Claim(AbpClaimTypes.Email, user.Email),
new Claim(AbpClaimTypes.UserName, user.UserName),
new Claim(AbpClaimTypes.EmailVerified, user.EmailConfirmed.ToString().ToLower()),
};
if (!string.IsNullOrWhiteSpace(user.PhoneNumber))
{
claims.Add(new Claim(AbpClaimTypes.PhoneNumber, user.PhoneNumber));
}
foreach (var role in roles)
{
claims.Add(new Claim(AbpClaimTypes.Role, role));
}
return claims;
}
}
}
```
We created an endpoint for `/Passwordless/Login` that gets the token and the user Id. In this action, we find the user via repository and validate the token via `UserManager.VerifyUserTokenAsync()` method. If it's valid, we create claims of the user then call `HttpContext.SignInAsync` to be able to create an encrypted cookie and add it to the current response. Finally we redirect the page to the root URL.
That's all! We created a passwordless login with 7 steps.
## Source Code
The completed sample is available on [the GitHub repository](https://github.com/abpframework/abp-samples/tree/master/PasswordlessAuthentication).

26
docs/en/Contribution/Index.md

@ -1,8 +1,12 @@
## Contribution Guide
# Contribution Guide
ABP is an [open source](https://github.com/abpframework) and community driven project. This guide is aims to help anyone wants to contribute to the project.
### Code Contribution
## community.abp.io
If you want to write articles or "how to" guides related to the ABP Framework and ASP.NET Core, please submit your article to the [community.abp.io](https://community.abp.io/) web site.
## Code Contribution
You can always send pull requests to the Github repository.
@ -12,15 +16,15 @@ You can always send pull requests to the Github repository.
Before making any change, please discuss it on the [Github issues](https://github.com/abpframework/abp/issues). In this way, no other developer will work on the same issue and your PR will have a better chance to be accepted.
#### Bug Fixes & Enhancements
### Bug Fixes & Enhancements
You may want to fix a known bug or work on a planned enhancement. See [the issue list](https://github.com/abpframework/abp/issues) on Github.
#### Feature Requests
### Feature Requests
If you have a feature idea for the framework or modules, [create an issue](https://github.com/abpframework/abp/issues/new) on Github or attend to an existing discussion. Then you can implement it if it's embraced by the community.
### Document Translation
## Document Translation
You may want to translate the complete [documentation](https://abp.io/documents/) (including this one) to your mother language. If so, follow these steps:
@ -37,13 +41,13 @@ There are some fundamental documents need to be translated before publishing a l
A new language is published after these minimum translations have been completed.
### Resource Localization
## Resource Localization
ABP framework has a flexible [localization system](../Localization.md). You can create localized user interfaces for your own application.
In addition to that, the framework and the [pre-build modules](https://docs.abp.io/en/abp/latest/Modules/Index) have localized texts. As an example, see [the localization texts for the Volo.Abp.UI package](https://github.com/abpframework/abp/blob/master/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json).
#### Using the "abp translate" command
### Using the "abp translate" command
This is the recommended approach, since it automatically finds all missing texts for a specific culture and lets you to translate in one place.
@ -54,14 +58,10 @@ This is the recommended approach, since it automatically finds all missing texts
* Once you done the translation, use `abp translate -a` command to apply changes to the related files.
* Send a pull request on GitHub.
#### Manual Translation
### Manual Translation
If you want to make a change on a specific resource file, you can find the file yourself, make the necessary change (or create a new file for your language) and send a pull request on GitHub.
### Blog Posts & Tutorials
If you decide to create some tutorials or blog posts on ABP, please inform us (by creating a [Github issue](https://github.com/abpframework/abp/issues)), so we may add a link to your tutorial/post in the official documentation and we can announce it on our [Twitter account](https://twitter.com/abpframework).
### Bug Report
## Bug Report
If you find any bug, please [create an issue on the Github repository](https://github.com/abpframework/abp/issues/new).

3
docs/en/Distributed-Event-Bus.md

@ -178,8 +178,11 @@ That's all.
You can inject any service and perform any required logic here. A single event handler class can **subscribe to multiple events** but implementing the `IDistributedEventHandler<TEvent>` interface for each event type.
If you perform **database operations** and use the [repositories](Repositories.md) inside the event handler, you may need to create a [unit of work](Unit-Of-Work.md), because some repository methods need to work inside an **active unit of work**. Make the handle method `virtual` and add a `[UnitOfWork]` attribute for the method, or manually use the `IUnitOfWorkManager` to create a unit of work scope.
> The handler class must be registered to the dependency injection (DI). The sample above uses the `ITransientDependency` to accomplish it. See the [DI document](Dependency-Injection.md) for more options.
## Pre-Defined Events
ABP Framework **automatically publishes** distributed events for **create, update and delete** operations for an [entity](Entities.md) once you configure it.

8
docs/en/Entities.md

@ -234,6 +234,12 @@ ABP Framework does not force you to apply any DDD rule or patterns. However, it
While it's not common (and not suggested) for aggregate roots, it is in fact possible to define composite keys in the same way as defined for the mentioned entities above. Use non-generic `AggregateRoot` base class in that case.
### BasicAggregateRoot Class
`AggregateRoot` class implements the `IHasExtraProperties` and `IHasConcurrencyStamp` interfaces which brings two properties to the derived class. `IHasExtraProperties` makes the entity extensible (see the *Extra Properties* section below) and `IHasConcurrencyStamp` adds a `ConcurrencyStamp` property that is managed by the ABP Framework to implement the [optimistic concurrency](https://docs.microsoft.com/en-us/ef/core/saving/concurrency). In most cases, these are wanted features for aggregate roots.
However, if you don't need these features, you can inherit from the `BasicAggregateRoot<TKey>` (or `BasicAggregateRoot`) for your aggregate root.
## Base Classes & Interfaces for Audit Properties
There are some properties like `CreationTime`, `CreatorId`, `LastModificationTime`... which are very common in all applications. ABP Framework provides some interfaces and base classes to **standardize** these properties and also **sets their values automatically**.
@ -293,7 +299,7 @@ While you can manually implement any of the interfaces defined above, it is sugg
All these base classes also have non-generic versions to take `AuditedEntity` and `FullAuditedAggregateRoot` to support the composite primary keys.
All these base classes also have `...WithUser` pairs, like `FullAuditedAggregateRootWithUser<TUser>` and`FullAuditedAggregateRootWithUser<TKey, TUser>`. This makes possible to add a navigation property to your user entity. However, it is not a good practice to add navigation properties between aggregate roots, so this usage is not suggested (unless you are using an ORM, like EF Core, that well supports this scenario and you really need it - otherwise remember that this approach doesn't work for NoSQL databases like MongoDB where you must truly implement the aggregate pattern).
All these base classes also have `...WithUser` pairs, like `FullAuditedAggregateRootWithUser<TUser>` and `FullAuditedAggregateRootWithUser<TKey, TUser>`. This makes possible to add a navigation property to your user entity. However, it is not a good practice to add navigation properties between aggregate roots, so this usage is not suggested (unless you are using an ORM, like EF Core, that well supports this scenario and you really need it - otherwise remember that this approach doesn't work for NoSQL databases like MongoDB where you must truly implement the aggregate pattern). Also, if you add navigation properties to the AppUser class that comes with the startup template, consider to handle (ignore/map) it on the migration dbcontext (see [the EF Core migration document](Entity-Framework-Core-Migrations.md)).
## Extra Properties

181
docs/en/Getting-Started-Console-Application.md

@ -1,181 +0,0 @@
# Getting Started ABP With Console Application
This tutorial explains how to start ABP from scratch with minimal dependencies. You generally want to start with a **[startup template](https://abp.io/Templates)**.
## Create A New Project
Create a new Regular .Net Core Console Application from Visual Studio:
![](images/create-new-net-core-console-application.png)
## Install Volo.Abp Package
Volo.Abp.Core is the core nuget package to create ABP based applications. So, install it to your project:
````
Install-Package Volo.Abp.Core
````
## Create First ABP Module
ABP is a modular framework and it requires a **startup (root) module** class derived from ``AbpModule``:
````C#
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Modularity;
namespace AbpConsoleDemo
{
public class AppModule : AbpModule
{
}
}
````
``AppModule`` is a good name for the startup module for an application.
## Initialize The Application
The next step is to bootstrap the application using the startup module created above:
````C#
using System;
using Volo.Abp;
namespace AbpConsoleDemo
{
class Program
{
static void Main(string[] args)
{
using (var application = AbpApplicationFactory.Create<AppModule>())
{
application.Initialize();
Console.WriteLine("Press ENTER to stop application...");
Console.ReadLine();
}
}
}
}
````
``AbpApplicationFactory`` is used to create the application and load all modules taking ``AppModule`` as the startup module. ``Initialize()`` method starts the application.
## Hello World!
The application above does nothing. Let's create a service that does something:
````C#
using System;
using Volo.Abp.DependencyInjection;
namespace AbpConsoleDemo
{
public class HelloWorldService : ITransientDependency
{
public void SayHello()
{
Console.WriteLine("Hello World!");
}
}
}
````
``ITransientDependency`` is a special interface of ABP that automatically registers the service as transient (see [dependency injection document](Dependency-Injection.md)).
Now, we can resolve the ``HelloWorldService`` and say hello. Change the Program.cs as shown below:
````C#
using System;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp;
namespace AbpConsoleDemo
{
class Program
{
static void Main(string[] args)
{
using (var application = AbpApplicationFactory.Create<AppModule>())
{
application.Initialize();
//Resolve a service and use it
var helloWorldService =
application.ServiceProvider.GetService<HelloWorldService>();
helloWorldService.SayHello();
Console.WriteLine("Press ENTER to stop application...");
Console.ReadLine();
}
}
}
}
````
While it's enough for this simple code example, it's always suggested to create scopes in case of directly resolving dependencies from ``IServiceProvider`` (see the [Dependency Injection documentation](Dependency-Injection.md)).
## Using Autofac as the Dependency Injection Framework
While AspNet Core's Dependency Injection (DI) system is fine for basic requirements, Autofac provides advanced features like Property Injection and Method Interception which are required by ABP to perform advanced application framework features.
Replacing AspNet Core's DI system by Autofac and integrating to ABP is pretty easy.
1. Install [Volo.Abp.Autofac](https://www.nuget.org/packages/Volo.Abp.Autofac) package
```
Install-Package Volo.Abp.Autofac
```
1. Add ``AbpAutofacModule`` Dependency
```c#
[DependsOn(typeof(AbpAutofacModule))] //Add dependency to the AbpAutofacModule
public class AppModule : AbpModule
{
}
```
1. Change ``Program.cs`` file as shown below:
```c#
using System;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp;
namespace AbpConsoleDemo
{
class Program
{
static void Main(string[] args)
{
using (var application = AbpApplicationFactory.Create<AppModule>(options =>
{
options.UseAutofac(); //Autofac integration
}))
{
application.Initialize();
//Resolve a service and use it
var helloWorldService =
application.ServiceProvider.GetService<HelloWorldService>();
helloWorldService.SayHello();
Console.WriteLine("Press ENTER to stop application...");
Console.ReadLine();
}
}
}
}
```
Just called `options.UseAutofac()` method in the `AbpApplicationFactory.Create` options.
## Source Code
Get source code of the sample project created in this tutorial from [here](https://github.com/abpframework/abp-samples/tree/master/BasicConsoleApplication).

9
docs/en/How-To/Index.md

@ -1,9 +0,0 @@
# "How To" Guides
This section contains "how to" guides for some specific questions frequently asked. While some of them are common development tasks and not directly related to the ABP Framework, we think it is useful to have some concrete examples those directly work with your ABP based applications.
## Authentication
* [How to Customize the Login Page for MVC / Razor Page Applications](Customize-Login-Page-MVC.md)
* [How to Use the Azure Active Directory Authentication for MVC / Razor Page Applications](Azure-Active-Directory-Authentication-MVC.md)
* [How to Customize the SignIn Manager for ABP Applications](Customize-SignIn-Manager.md)

2
docs/en/Local-Event-Bus.md

@ -154,6 +154,8 @@ That's all. `MyHandler` is **automatically discovered** by the ABP Framework and
* **Zero or more handlers** can subscribe to the same event.
* A single event handler class can **subscribe to multiple events** but implementing the `ILocalEventHandler<TEvent>` interface for each event type.
If you perform **database operations** and use the [repositories](Repositories.md) inside the event handler, you may need to create a [unit of work](Unit-Of-Work.md), because some repository methods need to work inside an **active unit of work**. Make the handle method `virtual` and add a `[UnitOfWork]` attribute for the method, or manually use the `IUnitOfWorkManager` to create a unit of work scope.
> The handler class must be registered to the dependency injection (DI). The sample above uses the `ITransientDependency` to accomplish it. See the [DI document](Dependency-Injection.md) for more options.
## Transaction & Exception Behavior

2
docs/en/Multi-Tenancy.md

@ -355,7 +355,7 @@ namespace MyCompany.MyProject
}
````
{0} is the the placeholder to determine current tenant's unique name.
{0} is the placeholder to determine current tenant's unique name.
Instead of ``options.TenantResolvers.Insert(1, new DomainTenantResolveContributor("{0}.mydomain.com"));`` you can use this shortcut:

5
docs/en/Samples/Index.md

@ -58,7 +58,4 @@ While there is no Razor Pages & MongoDB combination, you can check both document
* [Customize the SignIn Manager](../How-To/Customize-SignIn-Manager.md)
* **Empty ASP.NET Core Application**: The most basic ASP.NET Core application with the ABP Framework installed.
* [Source code](https://github.com/abpframework/abp-samples/tree/master/BasicAspNetCoreApplication)
* [Documentation](../Getting-Started-AspNetCore-Application.md)
* **Empty Console Application**: The most basic console application with the ABP Framework installed.
* [Source code](https://github.com/abpframework/abp-samples/tree/master/BasicConsoleApplication)
* [Documentation](../Getting-Started-Console-Application.md)
* [Documentation](../Getting-Started-AspNetCore-Application.md)

8
docs/en/Startup-Templates/Application.md

@ -437,7 +437,11 @@ See the [Testing Overview](https://reactjs.org/docs/testing.html) document.
* [Formik](https://github.com/jaredpalmer/formik) is used to build forms.
* [Yup](https://github.com/jquense/yup) is used for form validations.
## Social / External Logins
If you want to configure social/external logins for your application, please follow the [Social/External Logins](../Authentication/Social-External-Logins.md) document.
## What's Next?
- [The getting started document](../Getting-Started-With-Startup-Templates.md) explains how to create a new application in a few minutes.
- [The application development tutorial](../Tutorials/Part-1) explains step by step application development.
- [The getting started document](../Getting-Started.md) explains how to create a new application in a few minutes.
- [The application development tutorial](../Tutorials/Part-1.md) explains step by step application development.

8
docs/en/Startup-Templates/Console.md

@ -18,4 +18,10 @@ abp new Acme.MyConsoleApp -t console
`Acme.MyConsoleApp` is the solution name, like *YourCompany.YourProduct*. You can use single level, two-levels or three-levels naming.
###
## Solution Structure
After you use the above command to create a solution, you will have a solution like shown below:
![basic-console-application-solution](../images/basic-console-application-solution.png)
* `HelloWorldService` is a sample service that implements the `ITransientDependency` interface to register this service to the [dependency injection](../Dependency-Injection.md) system.

20
docs/en/Tutorials/Part-1.md

@ -45,11 +45,19 @@ This tutorial is organized as the following parts;
### Download the Source Code
This tutorials has multiple versions based on your **UI** and **Database** preferences. We've prepared two combinations of the source code to be downloaded:
This tutorial has multiple versions based on your **UI** and **Database** preferences. We've prepared two combinations of the source code to be downloaded:
* [MVC (Razor Pages) UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore)
* [Angular UI with MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb)
{{if UI == "MVC" && DB == "EF"}}
### Video Tutorial
This part is also recorded as a video tutorial and **<a href="https://www.youtube.com/watch?v=cJzyIFfAlp8&list=PLsNclT2aHJcPNaCf7Io3DbMN6yAk_DgWJ&index=1" target="_blank">published on YouTube</a>**.
{{end}}
## Creating the Solution
Before starting to the development, create a new solution named `Acme.BookStore` and run it by following the [getting started tutorial](../Getting-Started.md).
@ -92,7 +100,7 @@ namespace Acme.BookStore.Books
### BookType Enum
The `Book` entity uses the `BookType` enum. Create the `BookType` in the `Acme.BookStore.Domain.Shared` project:
The `Book` entity uses the `BookType` enum. Create a `Books` folder (namespace) in the `Acme.BookStore.Domain.Shared` project and add a `BookType` inside it:
````csharp
namespace Acme.BookStore.Books
@ -293,7 +301,7 @@ In this section, you will create an application service to get, create, update a
### BookDto
`CrudAppService` base class requires to define the fundamental DTOs for the entity. Create a DTO class named `BookDto` into the `Acme.BookStore.Application.Contracts` project:
`CrudAppService` base class requires to define the fundamental DTOs for the entity. Create a `Books` folder (namespace) in the `Acme.BookStore.Application.Contracts` project and add a `BookDto` class inside it:
````csharp
using System;
@ -340,7 +348,7 @@ namespace Acme.BookStore
### CreateUpdateBookDto
Create another DTO class named `CreateUpdateBookDto` into the `Acme.BookStore.Application.Contracts` project:
Create a `CreateUpdateBookDto` class in the `Books` folder (namespace) of the `Acme.BookStore.Application.Contracts` project:
````csharp
using System;
@ -391,7 +399,7 @@ namespace Acme.BookStore
### IBookAppService
Next step is to define an interface for the application service. Create an interface named `IBookAppService` in the `Acme.BookStore.Application.Contracts` project:
Next step is to define an interface for the application service. Create an `IBookAppService` interface in the `Books` folder (namespace) of the `Acme.BookStore.Application.Contracts` project:
````csharp
using System;
@ -418,7 +426,7 @@ namespace Acme.BookStore.Books
### BookAppService
Implement the `IBookAppService`, as named `BookAppService`, in the `Acme.BookStore.Application` project:
It is time to implement the `IBookAppService` interface. Create a new class, named `BookAppService` in the `Books` namespace (folder) of the Acme.BookStore.Application project:
````csharp
using System;

10
docs/en/Tutorials/Part-2.md

@ -45,11 +45,19 @@ This tutorial is organized as the following parts;
### Download the Source Code
This tutorials has multiple versions based on your **UI** and **Database** preferences. We've prepared two combinations of the source code to be downloaded:
This tutorial has multiple versions based on your **UI** and **Database** preferences. We've prepared two combinations of the source code to be downloaded:
* [MVC (Razor Pages) UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore)
* [Angular UI with MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb)
{{if UI == "MVC" && DB == "EF"}}
### Video Tutorial
This part is also recorded as a video tutorial and **<a href="https://www.youtube.com/watch?v=UDNlLiPiBiw&list=PLsNclT2aHJcPNaCf7Io3DbMN6yAk_DgWJ&index=2" target="_blank">published on YouTube</a>**.
{{end}}
{{if UI == "MVC"}}
## Dynamic JavaScript Proxies

10
docs/en/Tutorials/Part-3.md

@ -45,11 +45,19 @@ This tutorial is organized as the following parts;
### Download the Source Code
This tutorials has multiple versions based on your **UI** and **Database** preferences. We've prepared two combinations of the source code to be downloaded:
This tutorial has multiple versions based on your **UI** and **Database** preferences. We've prepared two combinations of the source code to be downloaded:
* [MVC (Razor Pages) UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore)
* [Angular UI with MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb)
{{if UI == "MVC" && DB == "EF"}}
### Video Tutorial
This part is also recorded as a video tutorial and **<a href="https://www.youtube.com/watch?v=TLShZO8u2VE&list=PLsNclT2aHJcPNaCf7Io3DbMN6yAk_DgWJ&index=3" target="_blank">published on YouTube</a>**.
{{end}}
{{if UI == "MVC"}}
## Creating a New Book

12
docs/en/Tutorials/Part-4.md

@ -45,11 +45,19 @@ This tutorial is organized as the following parts;
### Download the Source Code
This tutorials has multiple versions based on your **UI** and **Database** preferences. We've prepared two combinations of the source code to be downloaded:
This tutorial has multiple versions based on your **UI** and **Database** preferences. We've prepared two combinations of the source code to be downloaded:
* [MVC (Razor Pages) UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore)
* [Angular UI with MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb)
{{if UI == "MVC" && DB == "EF"}}
### Video Tutorial
This part is also recorded as a video tutorial and **<a href="https://www.youtube.com/watch?v=aidRB4YFDLM&list=PLsNclT2aHJcPNaCf7Io3DbMN6yAk_DgWJ&index=4" target="_blank">published on YouTube</a>**.
{{end}}
## Test Projects in the Solution
This part covers the **server side** tests. There are several test projects in the solution:
@ -78,7 +86,7 @@ If you had created a data seed contributor as described in the [first part](Part
## Testing the BookAppService
Create a test class named `BookAppService_Tests` in the `Acme.BookStore.Application.Tests` project:
Add a new test class, named `BookAppService_Tests` in the `Books` namespace (folder) of the `Acme.BookStore.Application.Tests` project:
````csharp
using System.Threading.Tasks;

14
docs/en/Tutorials/Part-5.md

@ -45,11 +45,19 @@ This tutorial is organized as the following parts;
### Download the Source Code
This tutorials has multiple versions based on your **UI** and **Database** preferences. We've prepared two combinations of the source code to be downloaded:
This tutorial has multiple versions based on your **UI** and **Database** preferences. We've prepared two combinations of the source code to be downloaded:
* [MVC (Razor Pages) UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore)
* [Angular UI with MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb)
{{if UI == "MVC" && DB == "EF"}}
### Video Tutorial
This part is also recorded as a video tutorial and **<a href="https://www.youtube.com/watch?v=1WsfMITN_Jk&list=PLsNclT2aHJcPNaCf7Io3DbMN6yAk_DgWJ&index=5" target="_blank">published on YouTube</a>**.
{{end}}
## Permissions
ABP Framework provides an [authorization system](../Authorization.md) based on the ASP.NET Core's [authorization infrastructure](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/introduction). One major feature added on top of the standard authorization infrastructure is the **permission system** which allows to define permissions and enable/disable per role, user or client.
@ -58,7 +66,7 @@ ABP Framework provides an [authorization system](../Authorization.md) based on t
A permission must have a unique name (a `string`). The best way is to define it as a `const`, so we can reuse the permission name.
Open the `BookStorePermissions` class inside the `Acme.BookStore.Application.Contracts` project and change the content as shown below:
Open the `BookStorePermissions` class inside the `Acme.BookStore.Application.Contracts` project (in the `Permissions` folder) and change the content as shown below:
````csharp
namespace Acme.BookStore.Permissions
@ -84,7 +92,7 @@ This is a hierarchical way of defining permission names. For example, "create bo
You should define permissions before using them.
Open the `BookStorePermissionDefinitionProvider` class inside the `Acme.BookStore.Application.Contracts` project and change the content as shown below:
Open the `BookStorePermissionDefinitionProvider` class inside the `Acme.BookStore.Application.Contracts` project (in the `Permissions` folder) and change the content as shown below:
````csharp
using Acme.BookStore.Localization;

11
docs/en/Tutorials/Part-6.md

@ -204,7 +204,7 @@ namespace Acme.BookStore.Authors
> **DDD tip**: Do not introduce domain service methods unless they are really needed and perform some core business rules. For this case, we needed to this service to be able to force the unique name constraint.
Both methods checks if there is already an author with the given name and throws a special business exception, `AuthorAlreadyExistsException`, defined in the `Acme.BookStore.Domain` project as shown below:
Both methods checks if there is already an author with the given name and throws a special business exception, `AuthorAlreadyExistsException`, defined in the `Acme.BookStore.Domain` project (in the `Authors` folder) as shown below:
````csharp
using Volo.Abp;
@ -242,6 +242,15 @@ This is a unique string represents the error code thrown by your application and
"BookStore:00001": "There is already an author with the same name: {name}"
````
Then open the `BookStoreDomainSharedModule` and add the following code block inside the `ConfigureServices` method:
````csharp
Configure<AbpExceptionLocalizationOptions>(options =>
{
options.MapCodeNamespace("BookStore", typeof(BookStoreResource));
});
````
Whenever you throw an `AuthorAlreadyExistsException`, the end use will see a nice error message on the UI.
## IAuthorRepository

12
docs/en/Tutorials/Part-8.md

@ -339,7 +339,7 @@ public async Task DeleteAsync(Guid id)
You can't compile the code since it is expecting some constants declared in the `BookStorePermissions` class.
Open the `BookStorePermissions` class inside the `Acme.BookStore.Application.Contracts` project and change the content as shown below:
Open the `BookStorePermissions` class inside the `Acme.BookStore.Application.Contracts` project (in the `Permissions` folder) and change the content as shown below:
````csharp
namespace Acme.BookStore.Permissions
@ -489,6 +489,16 @@ namespace Acme.BookStore
}
````
{{if DB=="EF"}}
You can now run the `.DbMigrator` console application to **migrate** the **database schema** and **seed** the initial data.
{{else if DB=="Mongo"}}
You can now run the `.DbMigrator` console application to **seed** the initial data.
{{end}}
## Testing the Author Application Service
Finally, we can write some tests for the `IAuthorAppService`. Add a new class, named `AuthorAppService_Tests` in the `Authors` namespace (folder) of the `Acme.BookStore.Application.Tests` project:

BIN
docs/en/Tutorials/images/youtube.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

4
docs/en/UI/Angular/Localization.md

@ -199,7 +199,7 @@ Some of the culture names defined in .NET do not match Angular locales. In such
![locale-error](./images/locale-error.png)
If you see an error like this, you should pass the `cultureNameToLocaleFileNameMapping` property like below to CoreModule's forRoot static method.
If you see an error like this, you should pass the `cultureNameLocaleFileMap` property like below to CoreModule's forRoot static method.
```js
// app.module.ts
@ -209,7 +209,7 @@ If you see an error like this, you should pass the `cultureNameToLocaleFileNameM
// other imports
CoreModule.forRoot({
// other options
cultureNameToLocaleFileNameMapping: {
cultureNameLocaleFileMap: {
"DotnetCultureName": "AngularLocaleFileName",
"pt-BR": "pt" // example
}

135
docs/en/UI/Angular/Multi-Tenancy.md

@ -0,0 +1,135 @@
# Multi Tenancy in Angular UI
ABP Angular UI supports the multi-tenancy. The following features related to multi-tenancy are available in the startup templates.
![Tenants Page](./images/tenants-page.png)
<p style="font-size:small;text-align:center;">Tenants page</p>
On the page above, you can;
- See the all tenants.
- Create a new tenant.
- Edit an existing tenant.
- Delete a tenant.
![Tenant Switching Component](./images/tenant-switching-box.png)
<p style="font-size:small;text-align:center;">Tenant Switching Component</p>
You can switch between existing tenants by using the tenant switching component in the child pages of the `AccountLayoutComponent` (like Login page). Angular UI sends the selected tenant id sends to the backend as `__tenant` header on each request.
## Domain Tenant Resolver
Angular UI can get the tenant name from the app running URL. You can determine the current tenant by subdomain (like mytenant1.mydomain.com) or by the whole domain (like mytenant.com). To do this, you need to set the `application.baseUrl` property in the environment:
Subdomain resolver:
```js
// environment.prod.ts
export const environment = {
//...
application: {
baseUrl: 'https://{0}.mydomain.com/'
},
//...
}
```
**{0}** is the placeholder to determine current tenant's unique name.
After the configuration above, if your app runs on the `mytenant1.mydomain.com`, the app will get the tenant name as **mytenant1**. Then, the app will call the `/api/abp/multi-tenancy/tenants/by-name/mytenant1` endpoint to check if the tenant exists. If the tenant (mytenant1) exists, the app will keep this tenant data and send its `id` as `__tenant` header to the backend on each request. If the tenant does not exist, the app will not send `__tenant` header to the backend.
> **Important Note:** If you define the `baseUrl` with the placeholder (**{0}**), the tenant switching component in the child pages of the `AccountLayoutComponent` (like Login page) will be hidden.
Domain resolver:
```js
// environment.prod.ts
export const environment = {
//...
application: {
baseUrl: 'https://{0}.com/'
},
//...
}
```
After the configuration above, if your app runs on the `mytenant.com`, the app will get the tenant name as **mytenant**.
### Tenant Specific Remote Endpoints
The **{0}** placeholder can be put to the API URLs in the environment to determine tenant specific endpoints.
```js
// environment.prod.ts
export const environment = {
//...
application: {
baseUrl: 'https://{0}.mydomain.com/',
//...
},
oAuthConfig: {
issuer: 'https://{0}.ids.mydomain.com',
//...
},
apis: {
default: {
url: 'https://{0}.api.mydomain.com',
},
AbpIdentity: {
url: 'https://{0}.identity.mydomain.com',
},
},
}
```
> **Important Note:** The `application.baseUrl` and the `{0}` placeholder in the value of the `baseUrl` property are required to be able to get tenant from running URL. Other placeholders in API URLs are optional.
After the configuration above, if your app runs on the `mytenant1.mydomain.com`, the app will get tenant name as **mytenant1** and replace the environment object in `ConfigState` on app initialization as follows:
```js
// environment object in ConfigState
{
//...
application: {
baseUrl: 'https://mytenant1.mydomain.com/',
//...
},
oAuthConfig: {
issuer: 'https://mytenant1.ids.mydomain.com',
//...
},
apis: {
default: {
url: 'https://mytenant1.api.mydomain.com',
},
AbpIdentity: {
url: 'https://mytenant1.identity.mydomain.com',
},
},
}
```
After this replacement, the app will use the following URLs:
- `https://mytenant1.ids.mydomain.com` as IdentityServer URL.
- `https://mytenant1.api.mydomain.com` as default URL.
- `https://mytenant1.identity.mydomain.com` as `AbpIdentity` remote endpoint URL.
The app sends the `__tenant` header that contains the current tenant id on each request.
## See Also
* [Multi Tenancy in ABP](../../Multi-Tenancy.md)
## What's Next?
- [Confirmation Popup](./Confirmation-Service.md)

2
docs/en/UI/Angular/Permission-Management.md

@ -78,4 +78,4 @@ Granted Policies are stored in the `auth` property of `ConfigState`.
## What's Next?
- [Confirmation Popup](./Confirmation-Service.md)
* [Multi Tenancy](./Multi-Tenancy.md)

11
docs/en/UI/Angular/Toaster-Service.md

@ -26,7 +26,7 @@ You can use the `success`, `warn`, `error`, and `info` methods of `ToasterServic
### How to Display a Toast Overlay
```js
this.toast.success('Message', 'Title');
this.toaster.success('Message', 'Title');
```
- The `ToasterService` methods accept three parameters that are `message`, `title`, and `options`.
@ -72,17 +72,18 @@ With the options above, the toast overlay looks like this:
The open toast overlay can be removed manually via the `remove` method by passing the `id` of toast:
```js
const toastId = this.toast.success('Message', 'Title')
const toastId = this.toaster.success('Message', 'Title')
this.toast.remove(toastId);
this.toaster.remove(toastId);
```
### How to Remove All Toasts
### How to Remove All Toasts
The all open toasts can be removed manually via the `clear` method:
```js
this.toast.clear();
this.toaster.clear();
```
## API

BIN
docs/en/UI/Angular/images/tenant-switching-box.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

BIN
docs/en/UI/Angular/images/tenants-page.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

38
docs/en/docs-nav.json

@ -4,21 +4,16 @@
"text": "Getting Started",
"items": [
{
"text": "From Startup Templates",
"text": "Web Application",
"path": "Getting-Started.md"
},
{
"text": "From Empty Projects",
"items": [
{
"text": "With ASP.NET Core Web Application",
"path": "Getting-Started-AspNetCore-Application.md"
},
{
"text": "With Console Application",
"path": "Getting-Started-Console-Application.md"
}
]
"text": "Console Application",
"path": "Startup-Templates/Console.md"
},
{
"text": "Empty Web Project",
"path": "Getting-Started-AspNetCore-Application.md"
}
]
},
@ -93,10 +88,6 @@
}
]
},
{
"text": "\"How to\" Guides",
"path": "How-To/Index.md"
},
{
"text": "Migrating from the ASP.NET Boilerplate",
"path": "AspNet-Boilerplate-Migration-Guide.md"
@ -107,6 +98,15 @@
"text": "CLI",
"path": "CLI.md"
},
{
"text": "Authentication",
"items": [
{
"text": "Social/External Logins",
"path": "Authentication/Social-External-Logins.md"
}
]
},
{
"text": "Fundamentals",
"items": [
@ -253,7 +253,7 @@
"path": "Blob-Storing-Custom-Provider.md"
}
]
}
}
]
},
{
@ -426,6 +426,10 @@
"text": "Permission Management",
"path": "UI/Angular/Permission-Management.md"
},
{
"text": "Multi Tenancy",
"path": "UI/Angular/Multi-Tenancy.md"
},
{
"text": "Confirmation Popup",
"path": "UI/Angular/Confirmation-Service.md"

BIN
docs/en/images/basic-console-application-solution.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

30
docs/zh-Hans/Authentication/Social-External-Logins.md

@ -0,0 +1,30 @@
# 社交/外部登录
## ASP.NET Core MVC / Razor Pages UI
[帐户模块](../Modules/Account.md)已配置为开箱即用的处理社交或外部登录. 你可以按照ASP.NET Core文档向你的应用程序添加社交/外部登录提供程序.
### 示例: Facebook 认证
按照[ASP.NET Core Facebook集成文档](https://docs.microsoft.com/zh-cn/aspnet/core/security/authentication/social/facebook-logins)向你应用程序添加Facebook登录.
#### 添加NuGet包
添加[Microsoft.AspNetCore.Authentication.Facebook]包到你的项目. 基于你的架构,可能是 `.Web`,`.IdentityServer`(对于分层启动)或 `.Host` 项目.
#### 配置提供程序
在你模块的 `ConfigureServices` 方法中使用 `.AddFacebook(...)` 扩展方法来配置客户端:
````csharp
context.Services.AddAuthentication()
.AddFacebook(facebook =>
{
facebook.AppId = "...";
facebook.AppSecret = "...";
facebook.Scope.Add("email");
facebook.Scope.Add("public_profile");
});
````
> 最佳实践是使用 `appsettings.json` 或ASP.NET Core用户机密系统来存储你的凭据,而不是像这样硬编码值. 请参阅[微软](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/social/facebook-logins)文档了解如何使用用户机密.

3
docs/zh-Hans/Background-Workers-Quartz.md

@ -36,6 +36,9 @@ public class YourModule : AbpModule
}
````
> Quartz后台工作者集成提供了 `QuartzPeriodicBackgroundWorkerAdapter` 来适配 `PeriodicBackgroundWorkerBase``AsyncPeriodicBackgroundWorkerBase` 派生类. 所以你依然可以按照[后台工作者文档](Background-Workers.md)来定义后台作业.
> `BackgroundJobWorker` 每5秒检查待执行作业,但是长时间的作业不会阻塞quartz. 所以安装Quartz后台工作者集成后,你同时需要安装[Quartz后台作业](Background-Jobs-Quartz.md)或[Hangfire后台作业](Background-Jobs-Hangfire.md)以避免重复执行作业.
## 配置
参阅[配置](Background-Jobs-Quartz.md#配置).

25
docs/zh-Hans/Blob-Storing-Aliyun.md

@ -23,18 +23,21 @@ BLOB存储Aliyun提供程序可以将BLOB存储在[Aliyun Blob storage](https://
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containerscontainer.UseAliyun(aliyun =>
options.Containers.ConfigureDefault(container =>
{
aliyun.AccessKeyId = "your aliyun access key id";
aliyun.AccessKeySecret = "your aliyun access key secret";
aliyun.Endpoint = "your oss endpoint";
aliyun.RegionId = "your sts region id";
aliyun.RoleArn = "the arn of ram role";
aliyun.RoleSessionName = "the name of the certificate";
aliyun.Policy = "policy";
aliyun.DurationSeconds = "expiration date";
aliyun.ContainerName = "your aliyun container name";
aliyun.CreateContainerIfNotExists = false;
container.UseAliyun(aliyun =>
{
aliyun.AccessKeyId = "your aliyun access key id";
aliyun.AccessKeySecret = "your aliyun access key secret";
aliyun.Endpoint = "your oss endpoint";
aliyun.RegionId = "your sts region id";
aliyun.RoleArn = "the arn of ram role";
aliyun.RoleSessionName = "the name of the certificate";
aliyun.Policy = "policy";
aliyun.DurationSeconds = "expiration date";
aliyun.ContainerName = "your aliyun container name";
aliyun.CreateContainerIfNotExists = false;
});
});
});
````

31
docs/zh-Hans/Blob-Storing-Aws.md

@ -23,21 +23,24 @@ BLOB存储Aws提供程序可以将BLOB存储在[Amazon Simple Storage Service](h
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containerscontainer.UseAws(Aws =>
options.Containers.ConfigureDefault(container =>
{
Aws.AccessKeyId = "your Aws access key id";
Aws.SecretAccessKey = "your Aws access key secret";
Aws.UseCredentials = "set true to use credentials";
Aws.UseTemporaryCredentials = "set true to use temporary credentials";
Aws.UseTemporaryFederatedCredentials = "set true to use temporary federated credentials";
Aws.ProfileName = "the name of the profile to get credentials from";
Aws.ProfilesLocation = "the path to the aws credentials file to look at";
Aws.Region = "the system name of the service";
Aws.Name = "the name of the federated user";
Aws.Policy = "policy";
Aws.DurationSeconds = "expiration date";
Aws.ContainerName = "your Aws container name";
Aws.CreateContainerIfNotExists = false;
container.UseAws(Aws =>
{
Aws.AccessKeyId = "your Aws access key id";
Aws.SecretAccessKey = "your Aws access key secret";
Aws.UseCredentials = "set true to use credentials";
Aws.UseTemporaryCredentials = "set true to use temporary credentials";
Aws.UseTemporaryFederatedCredentials = "set true to use temporary federated credentials";
Aws.ProfileName = "the name of the profile to get credentials from";
Aws.ProfilesLocation = "the path to the aws credentials file to look at";
Aws.Region = "the system name of the service";
Aws.Name = "the name of the federated user";
Aws.Policy = "policy";
Aws.DurationSeconds = "expiration date";
Aws.ContainerName = "your Aws container name";
Aws.CreateContainerIfNotExists = false;
});
});
});
````

11
docs/zh-Hans/Blob-Storing-Azure.md

@ -23,11 +23,14 @@ BLOB存储Azure提供程序可以将BLOB存储在[Azure Blob storage](https://az
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containerscontainer.UseAzure(azure =>
options.Containers.ConfigureDefault(container =>
{
azure.ConnectionString = "your azure connection string";
azure.ContainerName = "your azure container name";
azure.CreateContainerIfNotExists = false;
container.UseAzure(azure =>
{
azure.ConnectionString = "your azure connection string";
azure.ContainerName = "your azure container name";
azure.CreateContainerIfNotExists = false;
});
});
});
````

15
docs/zh-Hans/Blob-Storing-Minio.md

@ -23,12 +23,15 @@ BLOB Storing Minio提供程序帮助你存储对象到 [MinIO Object storage](ht
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containerscontainer.UseMinio(minio =>
{
minio.EndPoint = "你的 minio endPoint";
minio.AccessKey = "你的 minio accessKey";
minio.SecretKey = "你的 minio secretKey";
minio.BucketName = "你的 minio bucketName";
options.Containers.ConfigureDefault(container =>
{
container.UseMinio(minio =>
{
minio.EndPoint = "你的 minio endPoint";
minio.AccessKey = "你的 minio accessKey";
minio.SecretKey = "你的 minio secretKey";
minio.BucketName = "你的 minio bucketName";
});
});
});
````

123
docs/zh-Hans/Getting-Started-Console-Application.md

@ -1,124 +1,3 @@
## 在控制台应用中使用ABP
本教程将介绍如何从头开始以最小的依赖关系启动ABP. 你通常希望以 **[启动模板](https://abp.io/Templates)** 开头.
### 创建一个新项目
使用Visual Studio创建一个新的.Net Core Console应用程序:
![](images/create-new-net-core-console-application.png)
### 安装 Volo.Abp 包
Volo.Abp.Core是创建基于ABP的应用程序的核心nuget包. 所以,将它安装到你的项目中:
````
Install-Package Volo.Abp.Core
````
### 创建第一个ABP模块
ABP是一个模块化框架, 它需要一个从``AbpModule``类派生的 **启动(根)模块** 类:
````C#
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Modularity;
namespace AbpConsoleDemo
{
public class AppModule : AbpModule
{
}
}
````
``AppModule`` 是应用程序启动模块的好名称.
### 初始化应用程序
下一步是使用上面创建的启动模块引导应用程序:
````C#
using System;
using Volo.Abp;
namespace AbpConsoleDemo
{
class Program
{
static void Main(string[] args)
{
using (var application = AbpApplicationFactory.Create<AppModule>())
{
application.Initialize();
Console.WriteLine("Press ENTER to stop application...");
Console.ReadLine();
}
}
}
}
````
``AbpApplicationFactory`` 用于创建应用程序并加载所有以``AppModule``作为启动模块的模块. ``Initialize()``方法启动应用程序.
### Hello World!
上面的应用程序什么都不做, 让我们创建一个服务做一些事情:
````C#
using System;
using Volo.Abp.DependencyInjection;
namespace AbpConsoleDemo
{
public class HelloWorldService : ITransientDependency
{
public void SayHello()
{
Console.WriteLine("Hello World!");
}
}
}
````
``ITransientDependency``是ABP的一个特殊接口, 它自动将服务注册为**Transient**(参见[依赖注入文档](Dependency-Injection.md)).
现在,我们可以解析``HelloWorldService``并调用``SayHello``. 更改Program.cs, 如下所示:
````C#
using System;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp;
namespace AbpConsoleDemo
{
class Program
{
static void Main(string[] args)
{
using (var application = AbpApplicationFactory.Create<AppModule>())
{
application.Initialize();
// 解析服务并使用它
var helloWorldService =
application.ServiceProvider.GetService<HelloWorldService>();
helloWorldService.SayHello();
Console.WriteLine("Press ENTER to stop application...");
Console.ReadLine();
}
}
}
}
````
这对于这个简单的代码示例已足够, 如果是直接从``IServiceProvider``解析建议创建**Scoped**依赖.(参见[依赖注入文档](Dependency-Injection.md)).
### 源码
从[这里](https://github.com/abpframework/abp-samples/tree/master/BasicConsoleApplication)获取本教程中创建的示例项目的源代码.
ABP提供了控制台应用程序启动模板. 参阅[控制台应用程序启动模板]文档了解更多信息.

8
docs/zh-Hans/Startup-Templates/Application.md

@ -423,7 +423,11 @@ Screens 是通过在 `src/screens` 文件夹中创建将名称分开的文件夹
* [Formik](https://github.com/jaredpalmer/formik) 用于构建表单.
* [Yup](https://github.com/jquense/yup) 用于表单验证.
## 社交/外部登录
如果你想要为你的应用程序配置社交/外部登录,请参阅[社交/外部登录](../Authentication/Social-External-Logins.md)文档.
## 下一步是什么?
* 参阅[ASP.NET Core MVC 模板入门](../Getting-Started-AspNetCore-MVC-Template.md)创建此模板的新解决方案并运行它.
* 参阅[ASP.NET Core MVC 教程](../Tutorials/AspNetCore-Mvc/Part-I.md)学习使用此模板开发应用程序.
* [入门文档](../Getting-Started.md)介绍了如何在几分钟内创建新应用程序.
* [Web应用程序开发教程](../Tutorials/Part-1.md)逐步介绍了应用程序开发.

10
docs/zh-Hans/Startup-Templates/Console.md

@ -16,4 +16,12 @@ dotnet tool install -g Volo.Abp.Cli
abp new Acme.MyConsoleApp -t console
````
`Acme.MyConsoleApp` 是解决方案的名称, 如*YourCompany.YourProduct*. 你可以使用单级或多级名称.
`Acme.MyConsoleApp` 是解决方案的名称, 如*YourCompany.YourProduct*. 你可以使用单级或多级名称.
## 解决方案结构
使用以上命令创建解决方案后,你会得到如下所示的解决方案:
![basic-console-application-solution](../images/basic-console-application-solution.png)
* `HelloWorldService` 是一个实现了 `ITransientDependency` 接口的示例服务. 它会自动注册到[依赖注入](../Dependency-Injection.md)系统.

6
docs/zh-Hans/Tutorials/Part-1.md

@ -1,4 +1,4 @@
## Web应用程序开发教程 - 第一章: 创建服务端
# Web应用程序开发教程 - 第一章: 创建服务端
````json
//[doc-params]
{
@ -23,7 +23,7 @@ else
end
}}
### 关于本教程
## 关于本教程
在本系列教程中, 你将构建一个名为 `Acme.BookStore` 的用于管理书籍及其作者列表的基于ABP的应用程序. 它是使用以下技术开发的:
@ -43,7 +43,7 @@ end
- [Part 9: 作者: 用户页面](Part-9.md)
- [Part 10: 图书到作者的关系](Part-10.md)
### 下载源码
## 下载源码
本教程根据你的**UI** 和 **Database**偏好有多个版,我们准备了两种可供下载的源码组合:

1
docs/zh-Hans/Tutorials/Part-10.md

@ -0,0 +1 @@
TODO..

1302
docs/zh-Hans/Tutorials/Part-2.md

File diff suppressed because it is too large

1233
docs/zh-Hans/Tutorials/Part-3.md

File diff suppressed because it is too large

255
docs/zh-Hans/Tutorials/Part-4.md

@ -0,0 +1,255 @@
# Web应用程序开发教程 - 第三章: 集成测试
````json
//[doc-params]
{
"UI": ["MVC","NG"],
"DB": ["EF","Mongo"]
}
````
{{
if UI == "MVC"
UI_Text="mvc"
else if UI == "NG"
UI_Text="angular"
else
UI_Text="?"
end
if DB == "EF"
DB_Text="Entity Framework Core"
else if DB == "Mongo"
DB_Text="MongoDB"
else
DB_Text="?"
end
}}
## 关于本教程
在本系列教程中, 你将构建一个名为 `Acme.BookStore` 的用于管理书籍及其作者列表的基于ABP的应用程序. 它是使用以下技术开发的:
* **{{DB_Text}}** 做为ORM提供程序.
* **{{UI_Value}}** 做为UI框架.
本教程分为以下部分:
- [Part 1: 创建服务端](Part-1.md)
- [Part 2: 图书列表页面](Part-2.md)
- [Part 3: 创建,更新和删除图书](Part-3.md)
- **Part 4: 集成测试**(本章)
- [Part 5: 授权](Part-5.md)
- [Part 6: 作者: 领域层](Part-6.md)
- [Part 7: 作者: 数据库集成](Part-7.md)
- [Part 8: 作者: 应用服务层](Part-8.md)
- [Part 9: 作者: 用户页面](Part-9.md)
- [Part 10: 图书到作者的关系](Part-10.md)
## 下载源码
本教程根据你的**UI** 和 **Database**偏好有多个版,我们准备了两种可供下载的源码组合:
* [MVC (Razor Pages) UI 与 EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore)
* [Angular UI 与 MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb)
## 解决方案中的测试项目
这一部分涵盖了 **服务器端** 测试. 解决方案中有多个测试项目:
![bookstore-test-projects-v2](./images/bookstore-test-projects-{{UI_Text}}.png)
每个项目用于测试相关的应用程序项目.测试项目使用以下库进行测试:
* [xunit](https://xunit.github.io/) 作为主测试框架.
* [Shoudly](http://shouldly.readthedocs.io/en/latest/) 作为断言库.
* [NSubstitute](http://nsubstitute.github.io/) 作为模拟库.
{{if DB=="EF"}}
> 测试项目配置为使用 **SQLite内存** 作为数据库. 创建一个单独的数据库实例并使用数据种子系统进行初始化种子数据,为每个测试准备一个新的数据库.
{{else if DB=="Mongo"}}
> **[Mongo2Go](https://github.com/Mongo2Go/Mongo2Go)**库用于模拟MongoDB数据库. 创建一个单独的数据库实例并使用数据种子系统进行初始化种子数据,为每个测试准备一个新的数据库.
{{end}}
## 添加测试数据
如果你已经按照[第一部分](Part-1.md)中的描述创建了数据种子贡献者,则相同的数据也在测试中可用. 因此你可以跳过此部分. 如果你尚未创建种子贡献者,可以使用 `BookStoreTestDataSeedContributor` 来为要在以下测试中使用的相同数据提供种子.
## 测试 BookAppService
`Acme.BookStore.Application.Tests` 项目中创建一个名叫 `BookAppService_Tests` 的测试类:
````csharp
using System.Threading.Tasks;
using Shouldly;
using Volo.Abp.Application.Dtos;
using Xunit;
namespace Acme.BookStore.Books
{ {{if DB=="Mongo"}}
[Collection(BookStoreTestConsts.CollectionDefinitionName)]{{end}}
public class BookAppService_Tests : BookStoreApplicationTestBase
{
private readonly IBookAppService _bookAppService;
public BookAppService_Tests()
{
_bookAppService = GetRequiredService<IBookAppService>();
}
[Fact]
public async Task Should_Get_List_Of_Books()
{
//Act
var result = await _bookAppService.GetListAsync(
new PagedAndSortedResultRequestDto()
);
//Assert
result.TotalCount.ShouldBeGreaterThan(0);
result.Items.ShouldContain(b => b.Name == "1984");
}
}
}
````
* 测试方法 `Should_Get_List_Of_Books` 直接使用 `BookAppService.GetListAsync` 方法来获取用户列表,并执行检查.
* 我们可以安全地检查 "1984" 这本书的名称,因为我们知道这本书可以在数据库中找到,我们已将其添加到种子数据中.
新增测试方法,用以测试创建一个**合法**book实体的场景:
````csharp
[Fact]
public async Task Should_Create_A_Valid_Book()
{
//Act
var result = await _bookAppService.CreateAsync(
new CreateUpdateBookDto
{
Name = "New test book 42",
Price = 10,
PublishDate = System.DateTime.Now,
Type = BookType.ScienceFiction
}
);
//Assert
result.Id.ShouldNotBe(Guid.Empty);
result.Name.ShouldBe("New test book 42");
}
````
新增测试方法,用以测试创建一个非法book实体失败的场景:
````csharp
[Fact]
public async Task Should_Not_Create_A_Book_Without_Name()
{
var exception = await Assert.ThrowsAsync<AbpValidationException>(async () =>
{
await _bookAppService.CreateAsync(
new CreateUpdateBookDto
{
Name = "",
Price = 10,
PublishDate = DateTime.Now,
Type = BookType.ScienceFiction
}
);
});
exception.ValidationErrors
.ShouldContain(err => err.MemberNames.Any(mem => mem == "Name"));
}
````
* 由于 `Name` 是空值, ABP 抛出一个 `AbpValidationException` 异常.
最终的测试类如下所示:
````csharp
using System;
using System.Linq;
using System.Threading.Tasks;
using Shouldly;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Validation;
using Xunit;
namespace Acme.BookStore.Books
{ {{if DB=="Mongo"}}
[Collection(BookStoreTestConsts.CollectionDefinitionName)]{{end}}
public class BookAppService_Tests : BookStoreApplicationTestBase
{
private readonly IBookAppService _bookAppService;
public BookAppService_Tests()
{
_bookAppService = GetRequiredService<IBookAppService>();
}
[Fact]
public async Task Should_Get_List_Of_Books()
{
//Act
var result = await _bookAppService.GetListAsync(
new PagedAndSortedResultRequestDto()
);
//Assert
result.TotalCount.ShouldBeGreaterThan(0);
result.Items.ShouldContain(b => b.Name == "1984");
}
[Fact]
public async Task Should_Create_A_Valid_Book()
{
//Act
var result = await _bookAppService.CreateAsync(
new CreateUpdateBookDto
{
Name = "New test book 42",
Price = 10,
PublishDate = System.DateTime.Now,
Type = BookType.ScienceFiction
}
);
//Assert
result.Id.ShouldNotBe(Guid.Empty);
result.Name.ShouldBe("New test book 42");
}
[Fact]
public async Task Should_Not_Create_A_Book_Without_Name()
{
var exception = await Assert.ThrowsAsync<AbpValidationException>(async () =>
{
await _bookAppService.CreateAsync(
new CreateUpdateBookDto
{
Name = "",
Price = 10,
PublishDate = DateTime.Now,
Type = BookType.ScienceFiction
}
);
});
exception.ValidationErrors
.ShouldContain(err => err.MemberNames.Any(mem => mem == "Name"));
}
}
}
````
打开**测试资源管理器**(测试 -> Windows -> 测试资源管理器)并**执行**所有测试:
![bookstore-appservice-tests](./images/bookstore-appservice-tests.png)
恭喜你, **绿色图标**表示测试已成功通过!
## 下一章
查看本教程的[下一章](Part-5.md).

1
docs/zh-Hans/Tutorials/Part-5.md

@ -0,0 +1 @@
TODO..

1
docs/zh-Hans/Tutorials/Part-6.md

@ -0,0 +1 @@
TODO..

1
docs/zh-Hans/Tutorials/Part-7.md

@ -0,0 +1 @@
TODO..

1
docs/zh-Hans/Tutorials/Part-8.md

@ -0,0 +1 @@
TODO..

1
docs/zh-Hans/Tutorials/Part-9.md

@ -0,0 +1 @@
TODO..

4
docs/zh-Hans/UI/Angular/Localization.md

@ -199,7 +199,7 @@ export class AppComponent {}
![locale-error](./images/locale-error.png)
如果你看到这样的错误,你应该像下面这样传递 `cultureNameToLocaleFileNameMapping` 属性到CoreModule的forRoot静态方法.
如果你看到这样的错误,你应该像下面这样传递 `cultureNameLocaleFileMap` 属性到CoreModule的forRoot静态方法.
```js
// app.module.ts
@ -209,7 +209,7 @@ export class AppComponent {}
// other imports
CoreModule.forRoot({
// other options
cultureNameToLocaleFileNameMapping: {
cultureNameLocaleFileMap: {
"DotnetCultureName": "AngularLocaleFileName",
"pt-BR": "pt" // example
}

4
docs/zh-Hans/Unit-Of-Work.md

@ -21,7 +21,7 @@ ABP的UOW系统是;
UOW自动针对这些方法开始,除非**周围已经有一个(环境)**UOW在运行.示例;
* 如果你调用一个[仓储]方法(Repositories.md),但还没有启动UOW,它将自动**启动一个新的事务UOW**,其中包括在仓储方法中完成的所有操作,如果仓储方法没有抛出任何异常,则**提交事务**. 仓储方法根本不知道UOW或事务. 它只在一个常规的数据库对象上工作(例如用于[EF Core](Entity-Framework-Core.md)的`DbContext`),而UOW由ABP框架处理.
* 如果你调用一个[仓储](Repositories.md)方法,但还没有启动UOW,它将自动**启动一个新的事务UOW**,其中包括在仓储方法中完成的所有操作,如果仓储方法没有抛出任何异常,则**提交事务**. 仓储方法根本不知道UOW或事务. 它只在一个常规的数据库对象上工作(例如用于[EF Core](Entity-Framework-Core.md)的`DbContext`),而UOW由ABP框架处理.
* 如果调用[应用服务](Application-Services.md)方法,则相同的UOW系统将按上述说明工作. 如果应用服务方法使用某些仓储,这些仓储**不会开始新的UOW**,而是**参与由ABP框架为应用程序服务方法启动的当前工作单元中**.
* ASP.NET Core控制器操作也是如此. 如果操作以控制器action开始,**UOW范围是控制器action的方法主体**.
@ -352,4 +352,4 @@ public async Task<int> CreateAsync(string name)
````csharp
app.UseUnitOfWork();
app.UseConfiguredEndpoints();
````
````

9
docs/zh-Hans/docs-nav.json

@ -80,6 +80,15 @@
"text": "CLI",
"path": "CLI.md"
},
{
"text": "认证",
"items": [
{
"text": "社交/外部登录",
"path": "Authentication/Social-External-Logins.md"
}
]
},
{
"text": "基础知识",
"items": [

BIN
docs/zh-Hans/images/basic-console-application-solution.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

2
framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo.Abp.AspNetCore.Authentication.JwtBearer.csproj

@ -19,7 +19,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.1.5" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.1.6" />
</ItemGroup>
</Project>

6
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.csproj

@ -24,11 +24,11 @@
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared\Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="3.1.5" />
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="3.1.6" />
</ItemGroup>
<ItemGroup>
<Content Remove="Views\Components\Themes\Shared\**\*.cshtml" />
<EmbeddedResource Include="Views\Components\Themes\Shared\**\*.cshtml" />

4
framework/src/Volo.Abp.AspNetCore.Mvc/Volo.Abp.AspNetCore.Mvc.csproj

@ -26,9 +26,9 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="3.1.5" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="3.1.6" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning" Version="4.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.5" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.6" />
</ItemGroup>
</Project>

12
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionFilter.cs

@ -1,4 +1,5 @@
using System;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
@ -25,7 +26,7 @@ namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling
public AbpExceptionFilter(
IExceptionToErrorInfoConverter errorInfoConverter,
IHttpExceptionStatusCodeFinder statusCodeFinder,
IHttpExceptionStatusCodeFinder statusCodeFinder,
IJsonSerializer jsonSerializer)
{
_errorInfoConverter = errorInfoConverter;
@ -54,7 +55,7 @@ namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling
{
return true;
}
if (context.HttpContext.Request.CanAccept(MimeTypes.Application.Json))
{
return true;
@ -81,8 +82,11 @@ namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling
var logLevel = context.Exception.GetLogLevel();
Logger.LogWithLevel(logLevel, $"---------- {nameof(RemoteServiceErrorInfo)} ----------");
Logger.LogWithLevel(logLevel, _jsonSerializer.Serialize(remoteServiceErrorInfo, indented: true));
var remoteServiceErrorInfoBuilder = new StringBuilder();
remoteServiceErrorInfoBuilder.AppendLine($"---------- {nameof(RemoteServiceErrorInfo)} ----------");
remoteServiceErrorInfoBuilder.AppendLine( _jsonSerializer.Serialize(remoteServiceErrorInfo, indented: true));
Logger.LogWithLevel(logLevel, remoteServiceErrorInfoBuilder.ToString());
Logger.LogException(context.Exception, logLevel);
await context.HttpContext

8
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionPageFilter.cs

@ -1,4 +1,5 @@
using System;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
@ -93,8 +94,11 @@ namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling
var logLevel = context.Exception.GetLogLevel();
Logger.LogWithLevel(logLevel, $"---------- {nameof(RemoteServiceErrorInfo)} ----------");
Logger.LogWithLevel(logLevel, _jsonSerializer.Serialize(remoteServiceErrorInfo, indented: true));
var remoteServiceErrorInfoBuilder = new StringBuilder();
remoteServiceErrorInfoBuilder.AppendLine($"---------- {nameof(RemoteServiceErrorInfo)} ----------");
remoteServiceErrorInfoBuilder.AppendLine( _jsonSerializer.Serialize(remoteServiceErrorInfo, indented: true));
Logger.LogWithLevel(logLevel, remoteServiceErrorInfoBuilder.ToString());
Logger.LogException(context.Exception, logLevel);
await context.HttpContext

2
framework/src/Volo.Abp.AspNetCore.TestBase/Volo.Abp.AspNetCore.TestBase.csproj

@ -24,7 +24,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="3.1.5" />
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="3.1.6" />
</ItemGroup>
</Project>

20
framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/VirtualFileSystemApplicationBuilderExtensions.cs

@ -1,18 +1,22 @@
using Microsoft.Extensions.DependencyInjection;
using System;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.AspNetCore.VirtualFileSystem;
namespace Microsoft.AspNetCore.Builder
{
public static class VirtualFileSystemApplicationBuilderExtensions
{
public static IApplicationBuilder UseVirtualFiles(this IApplicationBuilder app)
public static IApplicationBuilder UseVirtualFiles(this IApplicationBuilder app, Action<StaticFileOptions> configure = null)
{
return app.UseStaticFiles(
new StaticFileOptions
{
FileProvider = app.ApplicationServices.GetRequiredService<IWebContentFileProvider>()
}
);
var staticFileOptions = new StaticFileOptions
{
FileProvider = app.ApplicationServices.GetRequiredService<IWebContentFileProvider>(),
ContentTypeProvider = app.ApplicationServices.GetRequiredService<AbpFileExtensionContentTypeProvider>()
};
configure?.Invoke(staticFileOptions);
return app.UseStaticFiles(staticFileOptions);
}
}
}

402
framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/VirtualFileSystem/AbpAspNetCoreContentOptions.cs

@ -1,14 +1,401 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Volo.Abp.AspNetCore.VirtualFileSystem
{
public class AbpAspNetCoreContentOptions
{
public Dictionary<string, string> ContentTypeMaps { get; }
public List<string> AllowedExtraWebContentFolders { get; }
public List<string> AllowedExtraWebContentFileExtensions { get; }
public AbpAspNetCoreContentOptions()
{
ContentTypeMaps = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{".323", "text/h323"},
{".3g2", "video/3gpp2"},
{".3gp2", "video/3gpp2"},
{".3gp", "video/3gpp"},
{".3gpp", "video/3gpp"},
{".aac", "audio/aac"},
{".aaf", "application/octet-stream"},
{".aca", "application/octet-stream"},
{".accdb", "application/msaccess"},
{".accde", "application/msaccess"},
{".accdt", "application/msaccess"},
{".acx", "application/internet-property-stream"},
{".adt", "audio/vnd.dlna.adts"},
{".adts", "audio/vnd.dlna.adts"},
{".afm", "application/octet-stream"},
{".ai", "application/postscript"},
{".aif", "audio/x-aiff"},
{".aifc", "audio/aiff"},
{".aiff", "audio/aiff"},
{".appcache", "text/cache-manifest"},
{".application", "application/x-ms-application"},
{".art", "image/x-jg"},
{".asd", "application/octet-stream"},
{".asf", "video/x-ms-asf"},
{".asi", "application/octet-stream"},
{".asm", "text/plain"},
{".asr", "video/x-ms-asf"},
{".asx", "video/x-ms-asf"},
{".atom", "application/atom+xml"},
{".au", "audio/basic"},
{".avi", "video/x-msvideo"},
{".axs", "application/olescript"},
{".bas", "text/plain"},
{".bcpio", "application/x-bcpio"},
{".bin", "application/octet-stream"},
{".bmp", "image/bmp"},
{".c", "text/plain"},
{".cab", "application/vnd.ms-cab-compressed"},
{".calx", "application/vnd.ms-office.calx"},
{".cat", "application/vnd.ms-pki.seccat"},
{".cdf", "application/x-cdf"},
{".chm", "application/octet-stream"},
{".class", "application/x-java-applet"},
{".clp", "application/x-msclip"},
{".cmx", "image/x-cmx"},
{".cnf", "text/plain"},
{".cod", "image/cis-cod"},
{".cpio", "application/x-cpio"},
{".cpp", "text/plain"},
{".crd", "application/x-mscardfile"},
{".crl", "application/pkix-crl"},
{".crt", "application/x-x509-ca-cert"},
{".csh", "application/x-csh"},
{".css", "text/css"},
{".csv", "application/octet-stream"},
{".cur", "application/octet-stream"},
{".dcr", "application/x-director"},
{".deploy", "application/octet-stream"},
{".der", "application/x-x509-ca-cert"},
{".dib", "image/bmp"},
{".dir", "application/x-director"},
{".disco", "text/xml"},
{".dlm", "text/dlm"},
{".doc", "application/msword"},
{".docm", "application/vnd.ms-word.document.macroEnabled.12"},
{".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"},
{".dot", "application/msword"},
{".dotm", "application/vnd.ms-word.template.macroEnabled.12"},
{".dotx", "application/vnd.openxmlformats-officedocument.wordprocessingml.template"},
{".dsp", "application/octet-stream"},
{".dtd", "text/xml"},
{".dvi", "application/x-dvi"},
{".dvr-ms", "video/x-ms-dvr"},
{".dwf", "drawing/x-dwf"},
{".dwp", "application/octet-stream"},
{".dxr", "application/x-director"},
{".eml", "message/rfc822"},
{".emz", "application/octet-stream"},
{".eot", "application/vnd.ms-fontobject"},
{".eps", "application/postscript"},
{".etx", "text/x-setext"},
{".evy", "application/envoy"},
{".fdf", "application/vnd.fdf"},
{".fif", "application/fractals"},
{".fla", "application/octet-stream"},
{".flr", "x-world/x-vrml"},
{".flv", "video/x-flv"},
{".gif", "image/gif"},
{".gtar", "application/x-gtar"},
{".gz", "application/x-gzip"},
{".h", "text/plain"},
{".hdf", "application/x-hdf"},
{".hdml", "text/x-hdml"},
{".hhc", "application/x-oleobject"},
{".hhk", "application/octet-stream"},
{".hhp", "application/octet-stream"},
{".hlp", "application/winhlp"},
{".hqx", "application/mac-binhex40"},
{".hta", "application/hta"},
{".htc", "text/x-component"},
{".htm", "text/html"},
{".html", "text/html"},
{".htt", "text/webviewhtml"},
{".hxt", "text/html"},
{".ical", "text/calendar"},
{".icalendar", "text/calendar"},
{".ico", "image/x-icon"},
{".ics", "text/calendar"},
{".ief", "image/ief"},
{".ifb", "text/calendar"},
{".iii", "application/x-iphone"},
{".inf", "application/octet-stream"},
{".ins", "application/x-internet-signup"},
{".isp", "application/x-internet-signup"},
{".IVF", "video/x-ivf"},
{".jar", "application/java-archive"},
{".java", "application/octet-stream"},
{".jck", "application/liquidmotion"},
{".jcz", "application/liquidmotion"},
{".jfif", "image/pjpeg"},
{".jpb", "application/octet-stream"},
{".jpe", "image/jpeg"},
{".jpeg", "image/jpeg"},
{".jpg", "image/jpeg"},
{".js", "application/javascript"},
{".json", "application/json"},
{".jsx", "text/jscript"},
{".latex", "application/x-latex"},
{".lit", "application/x-ms-reader"},
{".lpk", "application/octet-stream"},
{".lsf", "video/x-la-asf"},
{".lsx", "video/x-la-asf"},
{".lzh", "application/octet-stream"},
{".m13", "application/x-msmediaview"},
{".m14", "application/x-msmediaview"},
{".m1v", "video/mpeg"},
{".m2ts", "video/vnd.dlna.mpeg-tts"},
{".m3u", "audio/x-mpegurl"},
{".m4a", "audio/mp4"},
{".m4v", "video/mp4"},
{".man", "application/x-troff-man"},
{".manifest", "application/x-ms-manifest"},
{".map", "text/plain"},
{".markdown", "text/markdown"},
{".md", "text/markdown"},
{".mdb", "application/x-msaccess"},
{".mdp", "application/octet-stream"},
{".me", "application/x-troff-me"},
{".mht", "message/rfc822"},
{".mhtml", "message/rfc822"},
{".mid", "audio/mid"},
{".midi", "audio/mid"},
{".mix", "application/octet-stream"},
{".mmf", "application/x-smaf"},
{".mno", "text/xml"},
{".mny", "application/x-msmoney"},
{".mov", "video/quicktime"},
{".movie", "video/x-sgi-movie"},
{".mp2", "video/mpeg"},
{".mp3", "audio/mpeg"},
{".mp4", "video/mp4"},
{".mp4v", "video/mp4"},
{".mpa", "video/mpeg"},
{".mpe", "video/mpeg"},
{".mpeg", "video/mpeg"},
{".mpg", "video/mpeg"},
{".mpp", "application/vnd.ms-project"},
{".mpv2", "video/mpeg"},
{".ms", "application/x-troff-ms"},
{".msi", "application/octet-stream"},
{".mso", "application/octet-stream"},
{".mvb", "application/x-msmediaview"},
{".mvc", "application/x-miva-compiled"},
{".nc", "application/x-netcdf"},
{".nsc", "video/x-ms-asf"},
{".nws", "message/rfc822"},
{".ocx", "application/octet-stream"},
{".oda", "application/oda"},
{".odc", "text/x-ms-odc"},
{".ods", "application/oleobject"},
{".oga", "audio/ogg"},
{".ogg", "video/ogg"},
{".ogv", "video/ogg"},
{".ogx", "application/ogg"},
{".one", "application/onenote"},
{".onea", "application/onenote"},
{".onetoc", "application/onenote"},
{".onetoc2", "application/onenote"},
{".onetmp", "application/onenote"},
{".onepkg", "application/onenote"},
{".osdx", "application/opensearchdescription+xml"},
{".otf", "font/otf"},
{".p10", "application/pkcs10"},
{".p12", "application/x-pkcs12"},
{".p7b", "application/x-pkcs7-certificates"},
{".p7c", "application/pkcs7-mime"},
{".p7m", "application/pkcs7-mime"},
{".p7r", "application/x-pkcs7-certreqresp"},
{".p7s", "application/pkcs7-signature"},
{".pbm", "image/x-portable-bitmap"},
{".pcx", "application/octet-stream"},
{".pcz", "application/octet-stream"},
{".pdf", "application/pdf"},
{".pfb", "application/octet-stream"},
{".pfm", "application/octet-stream"},
{".pfx", "application/x-pkcs12"},
{".pgm", "image/x-portable-graymap"},
{".pko", "application/vnd.ms-pki.pko"},
{".pma", "application/x-perfmon"},
{".pmc", "application/x-perfmon"},
{".pml", "application/x-perfmon"},
{".pmr", "application/x-perfmon"},
{".pmw", "application/x-perfmon"},
{".png", "image/png"},
{".pnm", "image/x-portable-anymap"},
{".pnz", "image/png"},
{".pot", "application/vnd.ms-powerpoint"},
{".potm", "application/vnd.ms-powerpoint.template.macroEnabled.12"},
{".potx", "application/vnd.openxmlformats-officedocument.presentationml.template"},
{".ppam", "application/vnd.ms-powerpoint.addin.macroEnabled.12"},
{".ppm", "image/x-portable-pixmap"},
{".pps", "application/vnd.ms-powerpoint"},
{".ppsm", "application/vnd.ms-powerpoint.slideshow.macroEnabled.12"},
{".ppsx", "application/vnd.openxmlformats-officedocument.presentationml.slideshow"},
{".ppt", "application/vnd.ms-powerpoint"},
{".pptm", "application/vnd.ms-powerpoint.presentation.macroEnabled.12"},
{".pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"},
{".prf", "application/pics-rules"},
{".prm", "application/octet-stream"},
{".prx", "application/octet-stream"},
{".ps", "application/postscript"},
{".psd", "application/octet-stream"},
{".psm", "application/octet-stream"},
{".psp", "application/octet-stream"},
{".pub", "application/x-mspublisher"},
{".qt", "video/quicktime"},
{".qtl", "application/x-quicktimeplayer"},
{".qxd", "application/octet-stream"},
{".ra", "audio/x-pn-realaudio"},
{".ram", "audio/x-pn-realaudio"},
{".rar", "application/octet-stream"},
{".ras", "image/x-cmu-raster"},
{".rf", "image/vnd.rn-realflash"},
{".rgb", "image/x-rgb"},
{".rm", "application/vnd.rn-realmedia"},
{".rmi", "audio/mid"},
{".roff", "application/x-troff"},
{".rpm", "audio/x-pn-realaudio-plugin"},
{".rtf", "application/rtf"},
{".rtx", "text/richtext"},
{".scd", "application/x-msschedule"},
{".sct", "text/scriptlet"},
{".sea", "application/octet-stream"},
{".setpay", "application/set-payment-initiation"},
{".setreg", "application/set-registration-initiation"},
{".sgml", "text/sgml"},
{".sh", "application/x-sh"},
{".shar", "application/x-shar"},
{".sit", "application/x-stuffit"},
{".sldm", "application/vnd.ms-powerpoint.slide.macroEnabled.12"},
{".sldx", "application/vnd.openxmlformats-officedocument.presentationml.slide"},
{".smd", "audio/x-smd"},
{".smi", "application/octet-stream"},
{".smx", "audio/x-smd"},
{".smz", "audio/x-smd"},
{".snd", "audio/basic"},
{".snp", "application/octet-stream"},
{".spc", "application/x-pkcs7-certificates"},
{".spl", "application/futuresplash"},
{".spx", "audio/ogg"},
{".src", "application/x-wais-source"},
{".ssm", "application/streamingmedia"},
{".sst", "application/vnd.ms-pki.certstore"},
{".stl", "application/vnd.ms-pki.stl"},
{".sv4cpio", "application/x-sv4cpio"},
{".sv4crc", "application/x-sv4crc"},
{".svg", "image/svg+xml"},
{".svgz", "image/svg+xml"},
{".swf", "application/x-shockwave-flash"},
{".t", "application/x-troff"},
{".tar", "application/x-tar"},
{".tcl", "application/x-tcl"},
{".tex", "application/x-tex"},
{".texi", "application/x-texinfo"},
{".texinfo", "application/x-texinfo"},
{".tgz", "application/x-compressed"},
{".thmx", "application/vnd.ms-officetheme"},
{".thn", "application/octet-stream"},
{".tif", "image/tiff"},
{".tiff", "image/tiff"},
{".toc", "application/octet-stream"},
{".tr", "application/x-troff"},
{".trm", "application/x-msterminal"},
{".ts", "video/vnd.dlna.mpeg-tts"},
{".tsv", "text/tab-separated-values"},
{".ttc", "application/x-font-ttf"},
{".ttf", "application/x-font-ttf"},
{".tts", "video/vnd.dlna.mpeg-tts"},
{".txt", "text/plain"},
{".u32", "application/octet-stream"},
{".uls", "text/iuls"},
{".ustar", "application/x-ustar"},
{".vbs", "text/vbscript"},
{".vcf", "text/x-vcard"},
{".vcs", "text/plain"},
{".vdx", "application/vnd.ms-visio.viewer"},
{".vml", "text/xml"},
{".vsd", "application/vnd.visio"},
{".vss", "application/vnd.visio"},
{".vst", "application/vnd.visio"},
{".vsto", "application/x-ms-vsto"},
{".vsw", "application/vnd.visio"},
{".vsx", "application/vnd.visio"},
{".vtx", "application/vnd.visio"},
{".wasm", "application/wasm"},
{".wav", "audio/wav"},
{".wax", "audio/x-ms-wax"},
{".wbmp", "image/vnd.wap.wbmp"},
{".wcm", "application/vnd.ms-works"},
{".wdb", "application/vnd.ms-works"},
{".webm", "video/webm"},
{".webp", "image/webp"},
{".wks", "application/vnd.ms-works"},
{".wm", "video/x-ms-wm"},
{".wma", "audio/x-ms-wma"},
{".wmd", "application/x-ms-wmd"},
{".wmf", "application/x-msmetafile"},
{".wml", "text/vnd.wap.wml"},
{".wmlc", "application/vnd.wap.wmlc"},
{".wmls", "text/vnd.wap.wmlscript"},
{".wmlsc", "application/vnd.wap.wmlscriptc"},
{".wmp", "video/x-ms-wmp"},
{".wmv", "video/x-ms-wmv"},
{".wmx", "video/x-ms-wmx"},
{".wmz", "application/x-ms-wmz"},
{".woff", "application/font-woff"}, // https://www.w3.org/TR/WOFF/#appendix-b
{".woff2", "font/woff2"}, // https://www.w3.org/TR/WOFF2/#IMT
{".wps", "application/vnd.ms-works"},
{".wri", "application/x-mswrite"},
{".wrl", "x-world/x-vrml"},
{".wrz", "x-world/x-vrml"},
{".wsdl", "text/xml"},
{".wtv", "video/x-ms-wtv"},
{".wvx", "video/x-ms-wvx"},
{".x", "application/directx"},
{".xaf", "x-world/x-vrml"},
{".xaml", "application/xaml+xml"},
{".xap", "application/x-silverlight-app"},
{".xbap", "application/x-ms-xbap"},
{".xbm", "image/x-xbitmap"},
{".xdr", "text/plain"},
{".xht", "application/xhtml+xml"},
{".xhtml", "application/xhtml+xml"},
{".xla", "application/vnd.ms-excel"},
{".xlam", "application/vnd.ms-excel.addin.macroEnabled.12"},
{".xlc", "application/vnd.ms-excel"},
{".xlm", "application/vnd.ms-excel"},
{".xls", "application/vnd.ms-excel"},
{".xlsb", "application/vnd.ms-excel.sheet.binary.macroEnabled.12"},
{".xlsm", "application/vnd.ms-excel.sheet.macroEnabled.12"},
{".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"},
{".xlt", "application/vnd.ms-excel"},
{".xltm", "application/vnd.ms-excel.template.macroEnabled.12"},
{".xltx", "application/vnd.openxmlformats-officedocument.spreadsheetml.template"},
{".xlw", "application/vnd.ms-excel"},
{".xml", "text/xml"},
{".xof", "x-world/x-vrml"},
{".xpm", "image/x-xpixmap"},
{".xps", "application/vnd.ms-xpsdocument"},
{".xsd", "text/xml"},
{".xsf", "text/xml"},
{".xsl", "text/xml"},
{".xslt", "text/xml"},
{".xsn", "application/octet-stream"},
{".xtp", "application/octet-stream"},
{".xwd", "image/x-xwindowdump"},
{".z", "application/x-compress"},
{".zip", "application/x-zip-compressed"},
};
AllowedExtraWebContentFolders = new List<string>
{
"/Pages",
@ -16,18 +403,7 @@ namespace Volo.Abp.AspNetCore.VirtualFileSystem
"/Themes"
};
AllowedExtraWebContentFileExtensions = new List<string>
{
".js",
".css",
".png",
".jpg",
".jpeg",
".woff",
".woff2",
".tff",
".otf"
};
AllowedExtraWebContentFileExtensions = ContentTypeMaps.Select(x => x.Key).ToList();
}
}
}

39
framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/VirtualFileSystem/AbpFileExtensionContentTypeProvider.cs

@ -0,0 +1,39 @@
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.AspNetCore.VirtualFileSystem
{
public class AbpFileExtensionContentTypeProvider : IContentTypeProvider, ITransientDependency
{
protected AbpAspNetCoreContentOptions Options { get; }
public AbpFileExtensionContentTypeProvider(IOptions<AbpAspNetCoreContentOptions> abpAspNetCoreContentOptions)
{
Options = abpAspNetCoreContentOptions.Value;
}
public bool TryGetContentType(string subpath, out string contentType)
{
var extension = GetExtension(subpath);
if (extension == null)
{
contentType = null;
return false;
}
return Options.ContentTypeMaps.TryGetValue(extension, out contentType);
}
protected virtual string GetExtension(string path)
{
if (string.IsNullOrWhiteSpace(path))
{
return null;
}
var index = path.LastIndexOf('.');
return index < 0 ? null : path.Substring(index);
}
}
}

2
framework/src/Volo.Abp.Authorization/Volo.Abp.Authorization.csproj

@ -15,7 +15,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="3.1.5" />
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="3.1.6" />
</ItemGroup>
<ItemGroup>

2
framework/src/Volo.Abp.AutoMapper/Volo.Abp.AutoMapper.csproj

@ -21,7 +21,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="AutoMapper" Version="9.0.0" />
<PackageReference Include="AutoMapper" Version="10.0.0" />
</ItemGroup>
</Project>

2
framework/src/Volo.Abp.Autofac/Volo.Abp.Autofac.csproj

@ -17,7 +17,7 @@
<ItemGroup>
<PackageReference Include="Autofac.Extras.DynamicProxy" Version="5.0.0" />
<PackageReference Include="Autofac" Version="5.2.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.5" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.6" />
</ItemGroup>
<ItemGroup>

4
framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/MinioBlobProvider.cs

@ -72,11 +72,11 @@ namespace Volo.Abp.BlobStoring.Minio
}
var memoryStream = new MemoryStream();
await client.GetObjectAsync(containerName, blobName, stream =>
await client.GetObjectAsync(containerName, blobName, async stream =>
{
if (stream != null)
{
stream.CopyTo(memoryStream);
await stream.CopyToAsync(memoryStream);
}
else
{

2
framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo.Abp.Caching.StackExchangeRedis.csproj

@ -19,7 +19,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="3.1.5" />
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="3.1.6" />
</ItemGroup>
</Project>

2
framework/src/Volo.Abp.Caching/Volo.Abp.Caching.csproj

@ -15,7 +15,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.5" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.6" />
</ItemGroup>
<ItemGroup>

6
framework/src/Volo.Abp.Cli/Volo.Abp.Cli.csproj

@ -18,12 +18,12 @@
<PackageReference Include="Serilog.Extensions.Logging" Version="3.0.1" />
<PackageReference Include="Serilog.Sinks.File" Version="4.1.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="3.1.5" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="3.1.5" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="3.1.6" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="3.1.6" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.Autofac\Volo.Abp.Autofac.csproj" />
<ProjectReference Include="..\Volo.Abp.Cli.Core\Volo.Abp.Cli.Core.csproj" />
</ItemGroup>
</Project>
</Project>

9
framework/src/Volo.Abp.Core/Microsoft/Extensions/Logging/AbpLoggerExtensions.cs

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Text;
using Volo.Abp.ExceptionHandling;
using Volo.Abp.Logging;
@ -87,12 +88,14 @@ namespace Microsoft.Extensions.Logging
return;
}
logger.LogWithLevel(logLevel, "---------- Exception Data ----------");
var exceptionData = new StringBuilder();
exceptionData.AppendLine("---------- Exception Data ----------");
foreach (var key in exception.Data.Keys)
{
logger.LogWithLevel(logLevel, $"{key} = {exception.Data[key]}");
exceptionData.AppendLine($"{key} = {exception.Data[key]}");
}
logger.LogWithLevel(logLevel, exceptionData.ToString());
}
private static void LogSelfLogging(ILogger logger, Exception exception)

18
framework/src/Volo.Abp.Core/Volo.Abp.Core.csproj

@ -16,15 +16,15 @@
<ItemGroup>
<PackageReference Include="System.ComponentModel.Annotations" Version="4.7.0" />
<PackageReference Include="System.Collections.Immutable" Version="1.7.1" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="3.1.5" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.5" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="3.1.5" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="3.1.5" />
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="3.1.5" />
<PackageReference Include="Microsoft.Extensions.Options" Version="3.1.5" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="3.1.5" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="3.1.5" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.5" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="3.1.6" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.6" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="3.1.6" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="3.1.6" />
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="3.1.6" />
<PackageReference Include="Microsoft.Extensions.Options" Version="3.1.6" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="3.1.6" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="3.1.6" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.6" />
<PackageReference Include="System.Runtime.Loader" Version="4.3.0" />
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.1.5" />
<PackageReference Include="System.Linq.Queryable" Version="4.3.0" />

76
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/AggregateRoot.cs

@ -9,9 +9,7 @@ using Volo.Abp.ObjectExtending;
namespace Volo.Abp.Domain.Entities
{
[Serializable]
public abstract class AggregateRoot : Entity,
IAggregateRoot,
IGeneratesDomainEvents,
public abstract class AggregateRoot : BasicAggregateRoot,
IHasExtraProperties,
IHasConcurrencyStamp
{
@ -20,9 +18,6 @@ namespace Volo.Abp.Domain.Entities
[DisableAuditing]
public virtual string ConcurrencyStamp { get; set; }
private readonly ICollection<object> _localEvents = new Collection<object>();
private readonly ICollection<object> _distributedEvents = new Collection<object>();
protected AggregateRoot()
{
ConcurrencyStamp = Guid.NewGuid().ToString("N");
@ -30,36 +25,6 @@ namespace Volo.Abp.Domain.Entities
this.SetDefaultsForExtraProperties();
}
protected virtual void AddLocalEvent(object eventData)
{
_localEvents.Add(eventData);
}
protected virtual void AddDistributedEvent(object eventData)
{
_distributedEvents.Add(eventData);
}
public virtual IEnumerable<object> GetLocalEvents()
{
return _localEvents;
}
public virtual IEnumerable<object> GetDistributedEvents()
{
return _distributedEvents;
}
public virtual void ClearLocalEvents()
{
_localEvents.Clear();
}
public virtual void ClearDistributedEvents()
{
_distributedEvents.Clear();
}
public virtual IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
return ExtensibleObjectValidator.GetValidationErrors(
@ -70,9 +35,7 @@ namespace Volo.Abp.Domain.Entities
}
[Serializable]
public abstract class AggregateRoot<TKey> : Entity<TKey>,
IAggregateRoot<TKey>,
IGeneratesDomainEvents,
public abstract class AggregateRoot<TKey> : BasicAggregateRoot<TKey>,
IHasExtraProperties,
IHasConcurrencyStamp
{
@ -81,9 +44,6 @@ namespace Volo.Abp.Domain.Entities
[DisableAuditing]
public virtual string ConcurrencyStamp { get; set; }
private readonly ICollection<object> _localEvents = new Collection<object>();
private readonly ICollection<object> _distributedEvents = new Collection<object>();
protected AggregateRoot()
{
ConcurrencyStamp = Guid.NewGuid().ToString("N");
@ -99,36 +59,6 @@ namespace Volo.Abp.Domain.Entities
this.SetDefaultsForExtraProperties();
}
protected virtual void AddLocalEvent(object eventData)
{
_localEvents.Add(eventData);
}
protected virtual void AddDistributedEvent(object eventData)
{
_distributedEvents.Add(eventData);
}
public virtual IEnumerable<object> GetLocalEvents()
{
return _localEvents;
}
public virtual IEnumerable<object> GetDistributedEvents()
{
return _distributedEvents;
}
public virtual void ClearLocalEvents()
{
_localEvents.Clear();
}
public virtual void ClearDistributedEvents()
{
_distributedEvents.Clear();
}
public virtual IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
return ExtensibleObjectValidator.GetValidationErrors(
@ -137,4 +67,4 @@ namespace Volo.Abp.Domain.Entities
);
}
}
}
}

95
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/BasicAggregateRoot.cs

@ -0,0 +1,95 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace Volo.Abp.Domain.Entities
{
[Serializable]
public abstract class BasicAggregateRoot : Entity,
IAggregateRoot,
IGeneratesDomainEvents
{
private readonly ICollection<object> _distributedEvents = new Collection<object>();
private readonly ICollection<object> _localEvents = new Collection<object>();
public virtual IEnumerable<object> GetLocalEvents()
{
return _localEvents;
}
public virtual IEnumerable<object> GetDistributedEvents()
{
return _distributedEvents;
}
public virtual void ClearLocalEvents()
{
_localEvents.Clear();
}
public virtual void ClearDistributedEvents()
{
_distributedEvents.Clear();
}
protected virtual void AddLocalEvent(object eventData)
{
_localEvents.Add(eventData);
}
protected virtual void AddDistributedEvent(object eventData)
{
_distributedEvents.Add(eventData);
}
}
[Serializable]
public abstract class BasicAggregateRoot<TKey> : Entity<TKey>,
IAggregateRoot<TKey>,
IGeneratesDomainEvents
{
private readonly ICollection<object> _distributedEvents = new Collection<object>();
private readonly ICollection<object> _localEvents = new Collection<object>();
protected BasicAggregateRoot()
{
}
protected BasicAggregateRoot(TKey id)
: base(id)
{
}
public virtual IEnumerable<object> GetLocalEvents()
{
return _localEvents;
}
public virtual IEnumerable<object> GetDistributedEvents()
{
return _distributedEvents;
}
public virtual void ClearLocalEvents()
{
_localEvents.Clear();
}
public virtual void ClearDistributedEvents()
{
_distributedEvents.Clear();
}
protected virtual void AddLocalEvent(object eventData)
{
_localEvents.Add(eventData);
}
protected virtual void AddDistributedEvent(object eventData)
{
_distributedEvents.Add(eventData);
}
}
}

6
framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Localization/ar.json

@ -1,4 +1,4 @@
{
{
"culture": "ar",
"texts": {
"DisplayName:Abp.Mailing.DefaultFromAddress": "العنوان الإفتراضي",
@ -18,6 +18,8 @@
"Description:Abp.Mailing.Smtp.Password": "كلمة المرور لاسم المستخدم المرتبط ببيانات الاعتماد.",
"Description:Abp.Mailing.Smtp.Domain": "اسم المجال أو الكمبيوتر الذي يتحقق من بيانات الاعتماد.",
"Description:Abp.Mailing.Smtp.EnableSsl": "ما إذا كان SmtpClient يستخدم (SSL) لتشفير الاتصال.",
"Description:Abp.Mailing.Smtp.UseDefaultCredentials": "إرسال الصلاحيات الافتراضية مع الطلب."
"Description:Abp.Mailing.Smtp.UseDefaultCredentials": "إرسال الصلاحيات الافتراضية مع الطلب.",
"TextTemplate:StandardEmailTemplates.Layout": "نموذج تخطيط إفتراضي للبريد الإلكتروني",
"TextTemplate:StandardEmailTemplates.Message": "نموذج بسيط للرسائل الإلكترونية"
}
}

2
framework/src/Volo.Abp.EntityFrameworkCore.SqlServer/Volo.Abp.EntityFrameworkCore.SqlServer.csproj

@ -19,7 +19,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.6" />
</ItemGroup>
</Project>

8
framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo.Abp.EntityFrameworkCore.Sqlite.csproj

@ -2,7 +2,7 @@
<Import Project="..\..\..\configureawait.props" />
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<AssemblyName>Volo.Abp.EntityFrameworkCore.Sqlite</AssemblyName>
@ -13,11 +13,11 @@
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.6" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.EntityFrameworkCore\Volo.Abp.EntityFrameworkCore.csproj" />
</ItemGroup>

4
framework/src/Volo.Abp.EntityFrameworkCore/Volo.Abp.EntityFrameworkCore.csproj

@ -20,8 +20,8 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="3.1.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="3.1.6" />
</ItemGroup>
</Project>

8
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs

@ -183,6 +183,14 @@ namespace Volo.Abp.EntityFrameworkCore
}
}
/// <summary>
/// This method will call the DbContext <see cref="SaveChangesAsync(bool, CancellationToken)"/> method directly of EF Core, which doesn't apply concepts of abp.
/// </summary>
public virtual Task<int> SaveChangesOnDbContextAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default)
{
return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
public virtual void Initialize(AbpEfCoreDbContextInitializationContext initializationContext)
{
if (initializationContext.UnitOfWork.Options.Timeout.HasValue &&

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

Loading…
Cancel
Save