Browse Source

Merge branch 'dev' into menus

pull/5057/head
wakuflair 6 years ago
parent
commit
463f4bcffa
  1. 38
      .github/lock.yml
  2. 15
      .github/stale.yml
  3. 7
      abp_io/AbpIoLocalization/AbpIoLocalization/AbpIoLocalizationModule.cs
  4. 14
      abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/ar.json
  5. 161
      abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/ar.json
  6. 28
      abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json
  7. 10
      abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/AbpIoCommunityResource.cs
  8. 41
      abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json
  9. 30
      abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json
  10. 2
      build/build-all-release.ps1
  11. 6
      build/build-all.ps1
  12. 55
      build/common.ps1
  13. 4
      build/test-all.ps1
  14. 2
      common.props
  15. 7
      docs/docs-langs.json
  16. 2
      docs/en/API/Application-Configuration.md
  17. 30
      docs/en/Authentication/Social-External-Logins.md
  18. 5
      docs/en/Background-Jobs-Hangfire.md
  19. 3
      docs/en/Background-Workers-Quartz.md
  20. 78
      docs/en/Blob-Storing-Aliyun.md
  21. 85
      docs/en/Blob-Storing-Aws.md
  22. 11
      docs/en/Blob-Storing-Azure.md
  23. 69
      docs/en/Blob-Storing-Minio.md
  24. 3
      docs/en/Blob-Storing.md
  25. 177
      docs/en/Blog-Posts/2020-07-01 v3_0_Release/Post.md
  26. BIN
      docs/en/Blog-Posts/2020-07-01 v3_0_Release/file-management-ui.png
  27. 15
      docs/en/CLI.md
  28. 96
      docs/en/Community-Articles/2020-04-19-Customize-the-SignIn-Manager/POST.md
  29. 207
      docs/en/Community-Articles/2020-04-27-Use-Azure-Active-Directory-Authentication-for-MVC-Razor-Page-Applications/POST.md
  30. 108
      docs/en/Community-Articles/2020-05-09-Customize-the-Login-Page-for-MVC-Razor-Page-Applications/POST.md
  31. 462
      docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/POST.md
  32. BIN
      docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/chat.png
  33. BIN
      docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/dataflow-diagram.png
  34. BIN
      docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/header.png
  35. BIN
      docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/login1.png
  36. BIN
      docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/login2.png
  37. BIN
      docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/new-user.png
  38. 396
      docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/POST.md
  39. BIN
      docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/application-contracts-project.png
  40. BIN
      docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/file-upload-result.gif
  41. BIN
      docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/initial-project.png
  42. BIN
      docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/web-project.png
  43. 276
      docs/en/Community-Articles/2020-08-07-Passwordless-Authentication/POST.md
  44. 212
      docs/en/Community-Articles/2020-08-12-Patch-Chrome-Login-Issue-For-IdentityServer4/POST.md
  45. 26
      docs/en/Contribution/Index.md
  46. 12
      docs/en/CurrentUser.md
  47. 3
      docs/en/Distributed-Event-Bus.md
  48. 8
      docs/en/Entities.md
  49. 181
      docs/en/Getting-Started-Console-Application.md
  50. 212
      docs/en/How-To/Azure-Active-Directory-Authentication-MVC.md
  51. 112
      docs/en/How-To/Customize-Login-Page-MVC.md
  52. 101
      docs/en/How-To/Customize-SignIn-Manager.md
  53. 9
      docs/en/How-To/Index.md
  54. 2
      docs/en/Local-Event-Bus.md
  55. 2
      docs/en/Multi-Tenancy.md
  56. 34
      docs/en/Samples/Index.md
  57. 6
      docs/en/Samples/Microservice-Demo.md
  58. 8
      docs/en/Startup-Templates/Application.md
  59. 8
      docs/en/Startup-Templates/Console.md
  60. 4
      docs/en/Startup-Templates/Module.md
  61. 22
      docs/en/Tutorials/Part-1.md
  62. 10
      docs/en/Tutorials/Part-2.md
  63. 10
      docs/en/Tutorials/Part-3.md
  64. 12
      docs/en/Tutorials/Part-4.md
  65. 14
      docs/en/Tutorials/Part-5.md
  66. 11
      docs/en/Tutorials/Part-6.md
  67. 12
      docs/en/Tutorials/Part-8.md
  68. BIN
      docs/en/Tutorials/images/youtube.png
  69. 11
      docs/en/UI/Angular/Config-State.md
  70. 4
      docs/en/UI/Angular/Localization.md
  71. 135
      docs/en/UI/Angular/Multi-Tenancy.md
  72. 2
      docs/en/UI/Angular/Permission-Management.md
  73. 11
      docs/en/UI/Angular/Toaster-Service.md
  74. BIN
      docs/en/UI/Angular/images/tenant-switching-box.png
  75. BIN
      docs/en/UI/Angular/images/tenants-page.png
  76. 48
      docs/en/docs-nav.json
  77. BIN
      docs/en/images/basic-console-application-solution.png
  78. 12
      docs/zh-Hans/API/Application-Configuration.md
  79. 30
      docs/zh-Hans/Authentication/Social-External-Logins.md
  80. 2
      docs/zh-Hans/Authorization.md
  81. 5
      docs/zh-Hans/Background-Jobs-Hangfire.md
  82. 3
      docs/zh-Hans/Background-Workers-Quartz.md
  83. 2
      docs/zh-Hans/Best-Practices/Application-Services.md
  84. 77
      docs/zh-Hans/Blob-Storing-Aliyun.md
  85. 84
      docs/zh-Hans/Blob-Storing-Aws.md
  86. 11
      docs/zh-Hans/Blob-Storing-Azure.md
  87. 2
      docs/zh-Hans/Blob-Storing-Database.md
  88. 69
      docs/zh-Hans/Blob-Storing-Minio.md
  89. 3
      docs/zh-Hans/Blob-Storing.md
  90. 177
      docs/zh-Hans/Blog-Posts/2020-07-01 v3_0_Release/Post.md
  91. BIN
      docs/zh-Hans/Blog-Posts/2020-07-01 v3_0_Release/file-management-ui.png
  92. 11
      docs/zh-Hans/CLI.md
  93. 1
      docs/zh-Hans/Connection-Strings.md
  94. 10
      docs/zh-Hans/CurrentUser.md
  95. 2
      docs/zh-Hans/Entity-Framework-Core-Migrations.md
  96. 123
      docs/zh-Hans/Getting-Started-Console-Application.md
  97. 70
      docs/zh-Hans/Getting-Started-React-Native.md
  98. 114
      docs/zh-Hans/Getting-Started.md
  99. 2
      docs/zh-Hans/How-To/Azure-Active-Directory-Authentication-MVC.md
  100. 2
      docs/zh-Hans/How-To/Customize-Login-Page-MVC.md

38
.github/lock.yml

@ -0,0 +1,38 @@
# Configuration for Lock Threads - https://github.com/dessant/lock-threads-app
# Number of days of inactivity before a closed issue or pull request is locked
daysUntilLock: 30
# Skip issues and pull requests created before a given timestamp. Timestamp must
# follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable
skipCreatedBefore: false
# Issues and pull requests with these labels will be ignored. Set to `[]` to disable
exemptLabels: []
# Label to add before locking, such as `outdated`. Set to `false` to disable
lockLabel: false
# Comment to post before locking. Set to `false` to disable
lockComment: >
This thread has been automatically locked since there has not been
any recent activity after it was closed. Please open a new issue for
related bugs.
# Assign `resolved` as the reason for locking. Set to `false` to disable
setLockReason: true
# Limit to only `issues` or `pulls`
# only: issues
# Optionally, specify configuration settings just for `issues` or `pulls`
# issues:
# exemptLabels:
# - help-wanted
# lockLabel: outdated
# pulls:
# daysUntilLock: 30
# Repository to extend settings from
# _extends: repo

15
.github/stale.yml

@ -0,0 +1,15 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 60
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7
# Set to true to ignore issues in a milestone (defaults to false)
exemptMilestones: true
# Label to use when marking an issue as stale
staleLabel: inactive
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false

7
abp_io/AbpIoLocalization/AbpIoLocalization/AbpIoLocalizationModule.cs

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

14
abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/ar.json

@ -0,0 +1,14 @@
{
"culture": "ar",
"texts": {
"Account": "حساب ABP - تسجيل الدخول وإنشاء حساب | ABP.IO",
"Welcome": "أهلا و سهلا",
"UseOneOfTheFollowingLinksToContinue": "استخدم أحد الروابط التالية للمتابعة",
"FrameworkHomePage": "الصفحة الرئيسية للنظام",
"FrameworkDocumentation": "وثائق النظام",
"OfficialBlog": "المدونة الرسمية",
"CommercialHomePage": "الصفحة الرئيسية للنظام التجاري",
"CommercialSupportWebSite": "موقع الدعم الفني للنظام التجاري",
"CommunityWebSite": "موقع مجتمع ABP"
}
}

161
abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/ar.json

@ -0,0 +1,161 @@
{
"culture": "ar",
"texts": {
"Permission:Organizations": "المنظمات",
"Permission:Manage": "إدارة المنظمات",
"Permission:DiscountRequests": "طلبات الخصم",
"Permission:DiscountManage": "إدارة طلبات الخصم",
"Permission:Disable": "تعطيل",
"Permission:Enable": "تفعيل",
"Permission:EnableSendEmail": "تمكين إرسال البريد الإلكتروني",
"Permission:SendEmail": "ارسل بريد الكتروني",
"Permission:NpmPackages": "حزم NPM",
"Permission:NugetPackages": "حزم Nuget",
"Permission:Maintenance": "صيانة",
"Permission:Maintain": "المحافظة",
"Permission:ClearCaches": "مسح ذاكرة التخزين المؤقت",
"Permission:Modules": "الوحدات",
"Permission:Packages": "حزم",
"Permission:Edit": "تعديل",
"Permission:Delete": "حذف",
"Permission:Create": "إنشاء",
"Permission:Accounting": "محاسبة",
"Permission:Accounting:Quotation": "عرض سعر",
"Permission:Accounting:Invoice": "فاتورة",
"Menu:Organizations": "المنظمات",
"Menu:Accounting": "المحاسبة",
"Menu:Packages": "الحزم",
"Menu:DiscountRequests": "طلبات الخصم",
"NpmPackageDeletionWarningMessage": "سيتم حذف حزمة NPM هذه. هل تؤكد ذلك؟",
"NugetPackageDeletionWarningMessage": "سيتم حذف حزمة Nuget هذه. هل تؤكد ذلك؟",
"ModuleDeletionWarningMessage": "سيتم حذف هذه الوحدة. هل تؤكد ذلك؟",
"Name": "اسم",
"DisplayName": "اسم العرض",
"ShortDescription": "وصف قصير",
"NameFilter": "الاسم",
"CreationTime": "وقت الإنشاء",
"IsPro": "هو pro",
"ShowOnModuleList": "إظهار في قائمة الوحدات",
"EfCoreConfigureMethodName": "تكوين اسم method",
"IsProFilter": "هو pro",
"ApplicationType": "نوع التطبيق",
"Target": "استهداف",
"TargetFilter": "استهداف",
"Edit": "تعديل",
"Delete": "حذف",
"Refresh": "تحديث",
"NpmPackages": "حزم NPM",
"NugetPackages": "حزم Nuget",
"NpmPackageCount": "عدد حزم NPM",
"NugetPackageCount": "عدد حزم Nuget",
"Module": "الوحدات",
"ModuleInfo": "معلومات الوحدة",
"CreateANpmPackage": "إنشاء حزمة NPM",
"CreateAModule": "إنشاء وحدة",
"CreateANugetPackage": "أنشاء حزمة Nuget",
"AddNew": "اضف جديد",
"PackageAlreadyExist{0}": "",
"ModuleAlreadyExist{0}": "",
"ClearCache": "مسح ذاكرة التخزين المؤقت",
"SuccessfullyCleared": "تم محوه بنجاح",
"Menu:NpmPackages": "حزم NPM",
"Menu:Modules": "الوحدات",
"Menu:Maintenance": "الصيانة",
"Menu:NugetPackages": "حزم Nuget",
"CreateAnOrganization": "إنشاء منظمة",
"Organizations": "المنظمات",
"LongName": "اسم طويل",
"LicenseType": "نوع الرخصة",
"MissingLicenseTypeField": "حقل نوع الرخصة مطلوب!",
"LicenseStartTime": "وقت بدء الرخصة",
"LicenseEndTime": "وقت إنتهاء الرخصة",
"AllowedDeveloperCount": "عدد مطوري البرامج المسموح به",
"UserNameOrEmailAddress": "اسم المستخدم أو البريد الالكتروني",
"AddOwner": "أضف مالك",
"UserName": "اسم المستخدم",
"Email": "البريد الإلكتروني",
"Developers": "المطورين",
"AddDeveloper": "أضف مطور",
"Create": "إنشاء",
"UserNotFound": "لم نعثر على المستخدم",
"{0}WillBeRemovedFromDevelopers": "{0} ستتم إزالته من المطورين ، هل تؤكد ذلك؟",
"{0}WillBeRemovedFromOwners": "{0} ستتم إزالته من المالكين ، هل تؤكد ذلك؟",
"Computers": "أجهزة الكمبيوتر",
"UniqueComputerId": "معرف الكمبيوتر",
"LastSeenDate": "تاريخ آخر ظهور",
"{0}Computer{1}WillBeRemovedFromRecords": "ستتم إزالة كمبيوتر {0} ({1}) من السجلات",
"OrganizationDeletionWarningMessage": "سيتم حذف المنظمة",
"DeletingLastOwnerWarningMessage": "يجب أن يكون للمنظمة مالك واحد على الأقل! لذلك لا يمكنك إزالة هذا المالك",
"This{0}AlreadyExistInThisOrganization": "هذا {0} موجود بالفعل في هذه المنظمة",
"AreYouSureYouWantToDeleteAllComputers": "هل أنت متأكد أنك تريد حذف جميع أجهزة الكمبيوتر؟",
"DeleteAll": "حذف الكل",
"DoYouWantToCreateNewUser": "هل تريد إنشاء مستخدم جديد؟",
"MasterModules": "الوحدات الرئيسية",
"OrganizationName": "اسم المنظمة",
"CreationDate": "تاريخ الإنشاء",
"LicenseStart": "تاريخ بدء الرخصة",
"LicenseEndDate": "تاريخ إنتهاء الرخصة",
"OrganizationNamePlaceholder": "اسم المنظمة ...",
"TotalQuestionCountPlaceholder": "إجمالي عدد الأسئلة ...",
"RemainingQuestionCountPlaceholder": "عدد الأسئلة المتبقية ...",
"LicenseTypePlaceholder": "نوع الرخصة...",
"CreationDatePlaceholder": "تاريخ الإنشاء...",
"LicenseStartDatePlaceholder": "تاريخ بدء الرخصة ...",
"LicenseEndDatePlaceholder": "تاريخ إنتهاء الرخصة ...",
"UsernameOrEmail": "اسم المستخدم أو البريد الالكتروني",
"UsernameOrEmailPlaceholder": "اسم المستخدم أو البريد الالكتروني...",
"Member": "عضو",
"PurchaseOrderNo": "طلب الشراء رقم",
"QuotationDate": "تاريخ عرض السعر",
"CompanyName": "اسم الشركة",
"CompanyAddress": "عنوان الشركة",
"Price": "السعر",
"DiscountText": "نص الخصم",
"DiscountQuantity": "كمية الخصم",
"DiscountPrice": "سعر الخصم",
"Quotation": "عرض السعر",
"ExtraText": "نص إضافي",
"ExtraAmount": "مبلغ إضافي",
"DownloadQuotation": "تنزيل عرض الأسعار",
"Invoice": "فاتورة",
"TaxNumber": "الرقم الضريبي",
"InvoiceNumber": "رقم الفاتورة",
"InvoiceDate": "تاريخ الفاتورة",
"InvoiceNote": "ملاحظة الفاتورة",
"Quantity": "كمية",
"AddProduct": "أضف منتج",
"AddProductWarning": "تحتاج إلى إضافة منتج!",
"TotalPrice": "السعر الكلي",
"Generate": "انشاء",
"MissingQuantityField": "حقل الكمية مطلوب!",
"MissingPriceField": "حقل السعر مطلوب!",
"CodeUsageStatus": "الحالة",
"Country": "الدولة",
"DeveloperCount": "عدد المطورين",
"RequestCode": "كود الطلب",
"WebSite": "موقع الكتروني",
"GithubUsername": "اسم مستخدم Github",
"PhoneNumber": "رقم الهاتف",
"ProjectDescription": "وصف المشروع",
"Referrer": "المُحيل",
"DiscountRequests": "طلب الخصم",
"Copylink": "انسخ الرابط",
"Disable": "تعطيل",
"Enable": "تفعيل",
"EnableSendEmail": "تمكين إرسال البريد الإلكتروني",
"SendEmail": "ارسل بريد الكتروني",
"SuccessfullyDisabled": "تم التعطيل بنجاح",
"SuccessfullyEnabled": "تم التفعيل بنجاح",
"EmailSent": "تم ارسال البريد الالكتروني",
"SuccessfullySent": "أرسلت بنجاح",
"SuccessfullyDeleted": "تم الحذف بنجاح",
"DiscountRequestDeletionWarningMessage": "سيتم حذف طلب الخصم",
"BusinessType": "نوع العمل",
"TotalQuestionCount": "إجمالي عدد الأسئلة",
"RemainingQuestionCount": "عدد الأسئلة المتبقية",
"TotalQuestionMustBeGreaterWarningMessage": "يجب أن يكون TotalQuestionCount أكبر من RemainingQuestionCount!",
"QuestionCountsMustBeGreaterThanZero": "يجب أن يكون TotalQuestionCount و RemainingQuestionCount صفرًا أو أكبر من الصفر!",
"UnlimitedQuestionCount": "عدد أسئلة غير محدود",
"Notes": "ملاحظات"
}
}

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

@ -103,7 +103,7 @@
"MasterModules": "Master Modules", "MasterModules": "Master Modules",
"OrganizationName": "Organization name", "OrganizationName": "Organization name",
"CreationDate": "Creation date", "CreationDate": "Creation date",
"LicenseStart": "License start date", "LicenseStartDate": "License start date",
"LicenseEndDate": "License end date", "LicenseEndDate": "License end date",
"OrganizationNamePlaceholder": "Organization name...", "OrganizationNamePlaceholder": "Organization name...",
"TotalQuestionCountPlaceholder": "Total question count...", "TotalQuestionCountPlaceholder": "Total question count...",
@ -166,6 +166,30 @@
"TotalQuestionMustBeGreaterWarningMessage": "TotalQuestionCount must be greater than RemainingQuestionCount !", "TotalQuestionMustBeGreaterWarningMessage": "TotalQuestionCount must be greater than RemainingQuestionCount !",
"QuestionCountsMustBeGreaterThanZero": "TotalQuestionCount and RemainingQuestionCount must be zero or greater than zero !", "QuestionCountsMustBeGreaterThanZero": "TotalQuestionCount and RemainingQuestionCount must be zero or greater than zero !",
"UnlimitedQuestionCount": "Unlimited question count", "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
abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json

@ -15,7 +15,7 @@
"Tutorial": "Tutorial", "Tutorial": "Tutorial",
"UsingCLI": "Using CLI", "UsingCLI": "Using CLI",
"SeeDetails": "See Details", "SeeDetails": "See Details",
"AbpShortDescription": "ABP is a complete architecture and strong infrastructure to create modern web applications! Follows best practices and conventions to provide you a SOLID development experience.", "AbpShortDescription": "ABP Framework is a complete infrastructure to create modern web applications by following the software development best practices and conventions.",
"SourceCodeUpper": "SOURCE CODE", "SourceCodeUpper": "SOURCE CODE",
"LatestReleaseLogs": "Latest release logs", "LatestReleaseLogs": "Latest release logs",
"Infrastructure": "Infrastructure", "Infrastructure": "Infrastructure",
@ -31,31 +31,31 @@
"MultiTenancy": "Multi-Tenancy", "MultiTenancy": "Multi-Tenancy",
"MultiTenancyExplanationShort": "SaaS applications made easy! Integrated multi-tenancy from database to UI.", "MultiTenancyExplanationShort": "SaaS applications made easy! Integrated multi-tenancy from database to UI.",
"CrossCuttingConcerns": "Cross Cutting Concerns", "CrossCuttingConcerns": "Cross Cutting Concerns",
"CrossCuttingConcernsExplanationShort": "Complete infrastructure for authorization, validation, exception handling, caching, audit logging, transaction management and so on.", "CrossCuttingConcernsExplanationShort": "Complete infrastructure for authorization, validation, exception handling, caching, audit logging, transaction management and more.",
"BuiltInBundlingMinification": "Built-In Bundling & Minification", "BuiltInBundlingMinification": "Built-In Bundling & Minification",
"BuiltInBundlingMinificationExplanation": "Stop to use external tools for bundling & minification. ABP offers a simpler, dynamic, powerful, modular and built-in way!", "BuiltInBundlingMinificationExplanation": "No need to use external tools for bundling & minification. ABP offers a simpler, dynamic, powerful, modular and built-in way!",
"VirtualFileSystem": "Virtual File System", "VirtualFileSystem": "Virtual File System",
"VirtualFileSystemExplanation": "Embed views, scripts, styles, images... into packages/libraries and reuse in different applications.", "VirtualFileSystemExplanation": "Embed views, scripts, styles, images... into packages/libraries and reuse them in different applications.",
"Theming": "Theming", "Theming": "Theming",
"ThemingExplanationShort": "Use and customize the bootstrap-based standard UI theme or create your own one.", "ThemingExplanationShort": "Use and customize the bootstrap-based standard UI theme or create your own.",
"BootstrapTagHelpersDynamicForms": "Bootstrap Tag Helpers & Dynamic Forms", "BootstrapTagHelpersDynamicForms": "Bootstrap Tag Helpers & Dynamic Forms",
"BootstrapTagHelpersDynamicFormsExplanation": "Instead of manually writing the repeating details of bootstrap components, Use ABP's tag helpers to simplify it and take advantage of intellisense. Dynamic form can create the complete form from a C# class as the model.", "BootstrapTagHelpersDynamicFormsExplanation": "Instead of manually writing the repeating details of bootstrap components, Use ABP's tag helpers to simplify it and take advantage of intellisense. Quickly build UI forms based on a C# model using the dynamic form tag helper.",
"HTTPAPIsDynamicProxies": "HTTP APIs & Dynamic Proxies", "HTTPAPIsDynamicProxies": "HTTP APIs & Dynamic Proxies",
"HTTPAPIsDynamicProxiesExplanation": "Automatically expose application services as REST style HTTP APIs and consume with dynamic JavaScript & C# proxies.", "HTTPAPIsDynamicProxiesExplanation": "Automatically expose application services as REST style HTTP APIs, and consume them with dynamic JavaScript and C# proxies.",
"CompleteArchitectureInfo": "Modern architecture to create maintainable software solutions.", "CompleteArchitectureInfo": "Modern architecture to create maintainable software solutions.",
"DomainDrivenDesignBasedLayeringModelExplanation": "Helps you to implement a DDD based layered architecture and build a maintainable code base.", "DomainDrivenDesignBasedLayeringModelExplanation": "Helps you to implement a DDD based layered architecture and build a maintainable code base.",
"DomainDrivenDesignBasedLayeringModelExplanationCont": "Provides startup templates, abstractions, base classes, services, documentation and guides to help you to develop your application based on DDD patterns & principles.", "DomainDrivenDesignBasedLayeringModelExplanationCont": "Provides startup templates, abstractions, base classes, services, documentation and guides to help you to develop your application based on DDD patterns & principles.",
"MicroserviceCompatibleModelExplanation": "The core framework & pre-build modules are designed the microservice architecture in mind.", "MicroserviceCompatibleModelExplanation": "The core framework & pre-build modules are designed the microservice architecture in mind.",
"MicroserviceCompatibleModelExplanationCont": "Provides infrastructure, integrations, samples and documentation to implement microservice solutions easier, while it doesn\u2019t bring additional complexity if you want a monolithic application.", "MicroserviceCompatibleModelExplanationCont": "Provides infrastructure, integrations, samples and documentation to implement microservice solutions easier, while it doesn\u2019t bring additional complexity if you want a monolithic application.",
"ModularInfo": "ABP provides complete modularity system to allow you to develop reusable application modules.", "ModularInfo": "ABP provides a module system that allows you to develop reusable application modules, tie into application lifecycle events, and express dependencies between core parts of your system.",
"PreBuiltModulesThemes": "Pre-Built Modules & Themes", "PreBuiltModulesThemes": "Pre-Built Modules & Themes",
"PreBuiltModulesThemesExplanation": "Open source and commercial modules & themes are ready to use in your business application.", "PreBuiltModulesThemesExplanation": "Open source and commercial modules & themes are ready to use in your business application.",
"NuGetNPMPackages": "NuGet & NPM Packages", "NuGetNPMPackages": "NuGet & NPM Packages",
"NuGetNPMPackagesExplanation": "Distributed as NuGet & NPM packages. Easy to install and upgrade.", "NuGetNPMPackagesExplanation": "Distributed as NuGet & NPM packages. Easy to install and upgrade.",
"ExtensibleReplaceable": "Extensible/Replaceable", "ExtensibleReplaceable": "Extensible/Replaceable",
"ExtensibleReplaceableExplanation": "All services & modules are designed extensibility in mind. You can replace services, pages, styles, components...", "ExtensibleReplaceableExplanation": "All services & modules are designed extensibility in mind. You can replace services, pages, styles and components.",
"CrossCuttingConcernsExplanation2": "Keep your code cleaner and focus on your own business code.", "CrossCuttingConcernsExplanation2": "Keep your codebase smaller so you can maintain focus on the code that’s specific to your business.",
"CrossCuttingConcernsExplanation3": "Don\u2019t send time to implement common application requirements again and again.", "CrossCuttingConcernsExplanation3": "Don\u2019t send time implementing common application requirements on multiple projects.",
"AuthenticationAuthorization": "Authentication & Authorization", "AuthenticationAuthorization": "Authentication & Authorization",
"ExceptionHandling": "Exception Handling", "ExceptionHandling": "Exception Handling",
"Validation": "Validation", "Validation": "Validation",
@ -76,14 +76,14 @@
"BaseClasses": "Base Classes", "BaseClasses": "Base Classes",
"BaseClassesExplanation": "Pre-built base classes for common application patterns.", "BaseClassesExplanation": "Pre-built base classes for common application patterns.",
"DeveloperFocusedExplanation": "ABP is for developers.", "DeveloperFocusedExplanation": "ABP is for developers.",
"DeveloperFocusedExplanationCont": "It aims to simplify your daily software development while not restricting you to work low level when you need it.", "DeveloperFocusedExplanationCont": "It aims to simplify your daily software development while not restricting you from writing low level code.",
"SeeAllFeatures": "See All Features", "SeeAllFeatures": "See All Features",
"CLI_CommandLineInterface": "CLI (Command Line Interface)", "CLI_CommandLineInterface": "CLI (Command Line Interface)",
"CLI_CommandLineInterfaceExplanation": "CLI automates to create new projects and add modules to your application.", "CLI_CommandLineInterfaceExplanation": "Includes a CLI to help you automate the creation of new projects and the addition of new modules.",
"StartupTemplates": "Startup Templates", "StartupTemplates": "Startup Templates",
"StartupTemplatesExplanation": "Various startup templates provide you fully configured solution to jump start your development.", "StartupTemplatesExplanation": "Various startup templates provide a fully configured solution to jump start your development.",
"BasedOnFamiliarTools": "Based on Familiar Tools", "BasedOnFamiliarTools": "Based on Familiar Tools",
"BasedOnFamiliarToolsExplanation": "Built on and integrated to popular tools you already know. Low learning curve, easy adaptation, comfortable development.", "BasedOnFamiliarToolsExplanation": "Built on and integrated with popular tools you already know. Low learning curve, easy adaptation, comfortable development.",
"ORMIndependent": "ORM Independent", "ORMIndependent": "ORM Independent",
"ORMIndependentExplanation": "The core framework is ORM/database independent and can work with any data source. Entity Framework Core and MongoDB providers are already available.", "ORMIndependentExplanation": "The core framework is ORM/database independent and can work with any data source. Entity Framework Core and MongoDB providers are already available.",
"Features": "Explore the ABP Framework Features", "Features": "Explore the ABP Framework Features",

2
build/build-all-release.ps1

@ -1,4 +1,4 @@
. ".\common.ps1" . ".\common.ps1" -f
# Build all solutions # Build all solutions

6
build/build-all.ps1

@ -1,7 +1,11 @@
. ".\common.ps1" $full = $args[0]
. ".\common.ps1" $full
# Build all solutions # Build all solutions
Write-Host $solutionPaths
foreach ($solutionPath in $solutionPaths) { foreach ($solutionPath in $solutionPaths) {
$solutionAbsPath = (Join-Path $rootFolder $solutionPath) $solutionAbsPath = (Join-Path $rootFolder $solutionPath)
Set-Location $solutionAbsPath Set-Location $solutionAbsPath

55
build/common.ps1

@ -1,27 +1,38 @@
$full = $args[0]
# COMMON PATHS # COMMON PATHS
$rootFolder = (Get-Item -Path "./" -Verbose).FullName $rootFolder = (Get-Item -Path "./" -Verbose).FullName
# List of solutions # List of solutions used only in development mode
$solutionPaths = @(
"../framework",
"../modules/users",
"../modules/permission-management",
"../modules/setting-management",
"../modules/feature-management",
"../modules/identity",
"../modules/identityserver",
"../modules/tenant-management",
"../modules/audit-logging",
"../modules/background-jobs",
"../modules/account"
)
$solutionPaths = ( if ($full -eq "-f")
"../framework", {
"../modules/users", # List of additional solutions required for full build
"../modules/permission-management", $solutionPaths += (
"../modules/setting-management", "../modules/client-simulation",
"../modules/feature-management", "../modules/virtual-file-explorer",
"../modules/identity", "../modules/docs",
"../modules/identityserver", "../modules/blogging",
"../modules/tenant-management", "../templates/module/aspnet-core",
"../modules/account", "../templates/app/aspnet-core",
"../modules/docs", "../abp_io/AbpIoLocalization"
"../modules/blogging", )
"../modules/audit-logging", }else{
"../modules/background-jobs", Write-host ""
"../modules/client-simulation", Write-host ":::::::::::::: !!! You are in development mode !!! ::::::::::::::" -ForegroundColor red -BackgroundColor yellow
"../modules/virtual-file-explorer", Write-host ""
"../templates/module/aspnet-core", }
"../templates/app/aspnet-core",
"../samples/MicroserviceDemo",
"../abp_io/AbpIoLocalization"
)

4
build/test-all.ps1

@ -1,4 +1,6 @@
. ".\common.ps1" $full = $args[0]
. ".\common.ps1" $full
# Test all solutions # Test all solutions

2
common.props

@ -1,7 +1,7 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<Version>3.0.5</Version> <Version>3.1.0</Version>
<NoWarn>$(NoWarn);CS1591</NoWarn> <NoWarn>$(NoWarn);CS1591</NoWarn>
<PackageIconUrl>https://abp.io/assets/abp_nupkg.png</PackageIconUrl> <PackageIconUrl>https://abp.io/assets/abp_nupkg.png</PackageIconUrl>
<PackageProjectUrl>https://abp.io</PackageProjectUrl> <PackageProjectUrl>https://abp.io</PackageProjectUrl>

7
docs/docs-langs.json

@ -14,11 +14,6 @@
"DisplayName" : "简体中文", "DisplayName" : "简体中文",
"Code" : "zh-Hans", "Code" : "zh-Hans",
"IsDefault": false "IsDefault": false
}, }
{
"DisplayName" : "Čeština",
"Code" : "cs",
"IsDefault": false
}
] ]
} }

2
docs/en/API/Application-Configuration.md

@ -7,7 +7,7 @@ ABP Framework provides a pre-built and standard endpoint that contains some usef
* [Setting](../Settings.md) values for the current user. * [Setting](../Settings.md) values for the current user.
* Info about the [current user](../CurrentUser.md) (like id and user name). * Info about the [current user](../CurrentUser.md) (like id and user name).
* Info about the current [tenant](../Multi-Tenancy.md) (like id and name). * Info about the current [tenant](../Multi-Tenancy.md) (like id and name).
* [Time zone](../Timing.md) information for the current user and the [clock](Timing.md) type of the application. * [Time zone](../Timing.md) information for the current user and the [clock](../Timing.md) type of the application.
## HTTP API ## HTTP API

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.

5
docs/en/Background-Jobs-Hangfire.md

@ -66,9 +66,7 @@ After you have installed these NuGet packages, you need to configure your projec
} }
```` ````
2. We need to add `UseHangfireServer` call in the `OnApplicationInitialization` method in `Module` class 2. If you want to use hangfire's dashboard, you can add `UseHangfireDashboard` call in the `OnApplicationInitialization` method in `Module` class
If you want to use hangfire's dashboard, you can add it, too: by `UseHangfireDashboard`
````csharp ````csharp
public override void OnApplicationInitialization(ApplicationInitializationContext context) public override void OnApplicationInitialization(ApplicationInitializationContext context)
@ -77,7 +75,6 @@ If you want to use hangfire's dashboard, you can add it, too: by `UseHangfireDas
// ... others // ... others
app.UseHangfireServer();
app.UseHangfireDashboard(); app.UseHangfireDashboard();
} }

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 ## Configuration
See [Configuration](Background-Jobs-Quartz#Configuration). See [Configuration](Background-Jobs-Quartz#Configuration).

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

@ -0,0 +1,78 @@
# BLOB Storing Aliyun Provider
BLOB Storing Aliyun Provider can store BLOBs in [Aliyun Blob storage](https://help.aliyun.com/product/31815.html).
> Read the [BLOB Storing document](Blob-Storing.md) to understand how to use the BLOB storing system. This document only covers how to configure containers to use a Aliyun BLOB as the storage provider.
## Installation
Use the ABP CLI to add [Volo.Abp.BlobStoring.Aliyun](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Aliyun) NuGet package to your project:
* Install the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) if you haven't installed before.
* Open a command line (terminal) in the directory of the `.csproj` file you want to add the `Volo.Abp.BlobStoring.Aliyun` package.
* Run `abp add-package Volo.Abp.BlobStoring.Aliyun` command.
If you want to do it manually, install the [Volo.Abp.BlobStoring.Aliyun](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Aliyun) NuGet package to your project and add `[DependsOn(typeof(AbpBlobStoringAliyunModule))]` to the [ABP module](Module-Development-Basics.md) class inside your project.
## Configuration
Configuration is done in the `ConfigureServices` method of your [module](Module-Development-Basics.md) class, as explained in the [BLOB Storing document](Blob-Storing.md).
**Example: Configure to use the Aliyun storage provider by default**
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.ConfigureDefault(container =>
{
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;
});
});
});
````
> See the [BLOB Storing document](Blob-Storing.md) to learn how to configure this provider for a specific container.
### Options
* **AccessKeyId** ([NotNull]string): AccessKey is the key to access the Alibaba Cloud API. It has full permissions for the account. Please keep it safe! Recommend to follow [Alibaba Cloud security best practicess](https://help.aliyun.com/document_detail/102600.html),Use RAM sub-user AccessKey to call API.
* **AccessKeySecret** ([NotNull]string): Same as above.
* **Endpoint** ([NotNull]string): Endpoint is the external domain name of OSS. See the [document](https://help.aliyun.com/document_detail/31837.html) for details.
* **UseSecurityTokenService** (bool): Use [STS temporary credentials](https://help.aliyun.com/document_detail/100624.html) to access OSS services,default: `false`.
* **RegionId** (string): Access address of STS service. See the [document](https://help.aliyun.com/document_detail/66053.html) for details.
* **RoleArn** ([NotNull]string): STS required role ARN. See the [document](https://help.aliyun.com/document_detail/100624.html) for details.
* **RoleSessionName** ([NotNull]string): Used to identify the temporary access credentials, it is recommended to use different application users to distinguish.
* **Policy** (string): Additional permission restrictions. See the [document](https://help.aliyun.com/document_detail/100680.html) for details.
* **DurationSeconds** (int): Validity period(s) of a temporary access certificate,minimum is 900 and the maximum is 3600.
* **ContainerName** (string): You can specify the container name in Aliyun. If this is not specified, it uses the name of the BLOB container defined with the `BlogContainerName` attribute (see the [BLOB storing document](Blob-Storing.md)). Please note that Aliyun has some **rules for naming containers**. A container name must be a valid DNS name, conforming to the [following naming rules](https://help.aliyun.com/knowledge_detail/39668.html):
* Container names must start or end with a letter or number, and can contain only letters, numbers, and the dash (-) character.
* Container names Must start and end with lowercase letters and numbers.
* Container names must be from **3** through **63** characters long.
* **CreateContainerIfNotExists** (bool): Default value is `false`, If a container does not exist in Aliyun, `AliyunBlobProvider` will try to create it.
* **TemporaryCredentialsCacheKey** (bool): The cache key of STS credentials.
## Aliyun Blob Name Calculator
Aliyun Blob Provider organizes BLOB name and implements some conventions. The full name of a BLOB is determined by the following rules by default:
* Appends `host` string if [current tenant](Multi-Tenancy.md) is `null` (or multi-tenancy is disabled for the container - see the [BLOB Storing document](Blob-Storing.md) to learn how to disable multi-tenancy for a container).
* Appends `tenants/<tenant-id>` string if current tenant is not `null`.
* Appends the BLOB name.
## Other Services
* `AliyunBlobProvider` is the main service that implements the Aliyun BLOB storage provider, if you want to override/replace it via [dependency injection](Dependency-Injection.md) (don't replace `IBlobProvider` interface, but replace `AliyunBlobProvider` class).
* `IAliyunBlobNameCalculator` is used to calculate the full BLOB name (that is explained above). It is implemented by the `DefaultAliyunBlobNameCalculator` by default.
* `IOssClientFactory` is used create OSS client. It is implemented by the `DefaultOssClientFactory` by default. You can override/replace it,if you want customize.

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

@ -0,0 +1,85 @@
# BLOB Storing Aws Provider
BLOB Storing Aws Provider can store BLOBs in [Amazon Simple Storage Service](https://aws.amazon.com/s3/).
> Read the [BLOB Storing document](Blob-Storing.md) to understand how to use the BLOB storing system. This document only covers how to configure containers to use a Aws BLOB as the storage provider.
## Installation
Use the ABP CLI to add [Volo.Abp.BlobStoring.Aws](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Aws) NuGet package to your project:
* Install the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) if you haven't installed before.
* Open a command line (terminal) in the directory of the `.csproj` file you want to add the `Volo.Abp.BlobStoring.Aws` package.
* Run `abp add-package Volo.Abp.BlobStoring.Aws` command.
If you want to do it manually, install the [Volo.Abp.BlobStoring.Aws](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Aws) NuGet package to your project and add `[DependsOn(typeof(AbpBlobStoringAwsModule))]` to the [ABP module](Module-Development-Basics.md) class inside your project.
## Configuration
Configuration is done in the `ConfigureServices` method of your [module](Module-Development-Basics.md) class, as explained in the [BLOB Storing document](Blob-Storing.md).
**Example: Configure to use the Aws storage provider by default**
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.ConfigureDefault(container =>
{
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.
### Options
* **AccessKeyId** (string): AWS Access Key ID.
* **SecretAccessKey** (string): AWS Secret Access Key.
* **UseCredentials** (bool): Use [credentials](https://docs.aws.amazon.com/AmazonS3/latest/dev/AuthUsingAcctOrUserCredentials.html) to access AWS services,default : `false`.
* **UseTemporaryCredentials** (bool): Use [temporary credentials](https://docs.aws.amazon.com/AmazonS3/latest/dev/AuthUsingTempSessionToken.html) to access AWS services,default : `false`.
* **UseTemporaryFederatedCredentials** (bool): Use [federated user temporary credentials](https://docs.aws.amazon.com/AmazonS3/latest/dev/AuthUsingTempFederationToken.html) to access AWS services, default : `false`.
* **ProfileName** (string): The [name of the profile]((https://docs.aws.amazon.com/sdk-for-net/v3/developer-guide/net-dg-config-creds.html)) to get credentials from.
* **ProfilesLocation** (string): The path to the aws credentials file to look at.
* **Region** (string): The system name of the service.
* **Policy** (string): An IAM policy in JSON format that you want to use as an inline session policy.
* **DurationSeconds** (int): Validity period(s) of a temporary access certificate,minimum is 900 and the maximum is 3600. **note**: Using subaccounts operated OSS,if the value is 0.
* **ContainerName** (string): You can specify the container name in Aws. If this is not specified, it uses the name of the BLOB container defined with the `BlogContainerName` attribute (see the [BLOB storing document](Blob-Storing.md)). Please note that Aws has some **rules for naming containers**. A container name must be a valid DNS name, conforming to the [following naming rules](https://docs.aws.amazon.com/AmazonS3/latest/dev/BucketRestrictions.html):
* Bucket names must be between **3** and **63** characters long.
* Bucket names can consist only of **lowercase** letters, numbers, dots (.), and hyphens (-).
* Bucket names must begin and end with a letter or number.
* Bucket names must not be formatted as an IP address (for example, 192.168.5.4).
* Bucket names can't begin with **xn--** (for buckets created after February 2020).
* Bucket names must be unique within a partition.
* Buckets used with Amazon S3 Transfer Acceleration can't have dots (.) in their names. For more information about transfer acceleration, see Amazon S3 Transfer Acceleration.
* **CreateContainerIfNotExists** (bool): Default value is `false`, If a container does not exist in Aws, `AwsBlobProvider` will try to create it.
## Aws Blob Name Calculator
Aws Blob Provider organizes BLOB name and implements some conventions. The full name of a BLOB is determined by the following rules by default:
* Appends `host` string if [current tenant](Multi-Tenancy.md) is `null` (or multi-tenancy is disabled for the container - see the [BLOB Storing document](Blob-Storing.md) to learn how to disable multi-tenancy for a container).
* Appends `tenants/<tenant-id>` string if current tenant is not `null`.
* Appends the BLOB name.
## Other Services
* `AwsBlobProvider` is the main service that implements the Aws BLOB storage provider, if you want to override/replace it via [dependency injection](Dependency-Injection.md) (don't replace `IBlobProvider` interface, but replace `AwsBlobProvider` class).
* `IAwsBlobNameCalculator` is used to calculate the full BLOB name (that is explained above). It is implemented by the `DefaultAwsBlobNameCalculator` by default.
* `IAmazonS3ClientFactory` is used create OSS client. It is implemented by the `DefaultAmazonS3ClientFactory` by default. You can override/replace it,if you want customize.

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

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

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

@ -0,0 +1,69 @@
# BLOB Storing Minio Provider
BLOB Storing Minio Provider can store BLOBs in [MinIO Object storage](https://min.io/).
> Read the [BLOB Storing document](Blob-Storing.md) to understand how to use the BLOB storing system. This document only covers how to configure containers to use a Minio BLOB as the storage provider.
## Installation
Use the ABP CLI to add [Volo.Abp.BlobStoring.Minio](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Minio) NuGet package to your project:
* Install the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) if you haven't installed before.
* Open a command line (terminal) in the directory of the `.csproj` file you want to add the `Volo.Abp.BlobStoring.Minio` package.
* Run `abp add-package Volo.Abp.BlobStoring.Minio` command.
If you want to do it manually, install the [Volo.Abp.BlobStoring.Minio](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Minio) NuGet package to your project and add `[DependsOn(typeof(AbpBlobStoringMinioModule))]` to the [ABP module](Module-Development-Basics.md) class inside your project.
## Configuration
Configuration is done in the `ConfigureServices` method of your [module](Module-Development-Basics.md) class, as explained in the [BLOB Storing document](Blob-Storing.md).
**Example: Configure to use the minio storage provider by default**
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
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";
});
});
});
````
> See the [BLOB Storing document](Blob-Storing.md) to learn how to configure this provider for a specific container.
### Options
* **EndPoint** (string): URL to object storage service. Please refer to MinIO Client SDK for .NET: https://docs.min.io/docs/dotnet-client-quickstart-guide.html
* **AccessKey** (string): Access key is the user ID that uniquely identifies your account.
* **SecretKey** (string): Secret key is the password to your account.
* **BucketName** (string): You can specify the bucket name in MinIO. If this is not specified, it uses the name of the BLOB container defined with the `BlogContainerName` attribute (see the [BLOB storing document](Blob-Storing.md)).MinIO is the defacto standard for S3 compatibility, So MinIO has some **rules for naming bucket**. The [following rules](https://docs.aws.amazon.com/AmazonS3/latest/dev/BucketRestrictions.html) apply for naming MinIO buckets:
* Bucket names must be between **3** and **63** characters long.
* Bucket names can consist only of **lowercase** letters, numbers, dots (.), and hyphens (-).
* Bucket names must begin and end with a letter or number.
* Bucket names must not be formatted as an IP address (for example, 192.168.5.4).
* Bucket names can't begin with **xn--** (for buckets created after February 2020).
* Bucket names must be unique within a partition.
* Buckets used with Amazon S3 Transfer Acceleration can't have dots (.) in their names. For more information about transfer acceleration, see Amazon S3 Transfer Acceleration.
* **WithSSL** (bool): Default value is `false`,Chain to MinIO Client object to use https instead of http.
* **CreateContainerIfNotExists** (bool): Default value is `false`, If a bucket does not exist in minio, `MinioBlobProvider` will try to create it.
## Minio Blob Name Calculator
Minio Blob Provider organizes BLOB name and implements some conventions. The full name of a BLOB is determined by the following rules by default:
* Appends `host` string if [current tenant](Multi-Tenancy.md) is `null` (or multi-tenancy is disabled for the container - see the [BLOB Storing document](Blob-Storing.md) to learn how to disable multi-tenancy for a container).
* Appends `tenants/<tenant-id>` string if current tenant is not `null`.
* Appends the BLOB name.
## Other Services
* `MinioBlobProvider` is the main service that implements the Minio BLOB storage provider, if you want to override/replace it via [dependency injection](Dependency-Injection.md) (don't replace `IBlobProvider` interface, but replace `MinioBlobProvider` class).
* `IMinioBlobNameCalculator` is used to calculate the full BLOB name (that is explained above). It is implemented by the `DefaultMinioBlobNameCalculator` by default.

3
docs/en/Blob-Storing.md

@ -19,6 +19,9 @@ The ABP Framework has already the following storage provider implementations;
* [File System](Blob-Storing-File-System.md): Stores BLOBs in a folder of the local file system, as standard files. * [File System](Blob-Storing-File-System.md): Stores BLOBs in a folder of the local file system, as standard files.
* [Database](Blob-Storing-Database.md): Stores BLOBs in a database. * [Database](Blob-Storing-Database.md): Stores BLOBs in a database.
* [Azure](Blob-Storing-Azure.md): Stores BLOBs on the [Azure BLOB storage](https://azure.microsoft.com/en-us/services/storage/blobs/). * [Azure](Blob-Storing-Azure.md): Stores BLOBs on the [Azure BLOB storage](https://azure.microsoft.com/en-us/services/storage/blobs/).
* [Aliyun](Blob-Storing-Aliyun.md): Stores BLOBs on the [Aliyun Storage Service](https://help.aliyun.com/product/31815.html).
* [Ninio](Blob-Storing-Minio.md): Stores BLOBs on the [MinIO Object storage](https://min.io/).
* [Aws](Blob-Storing-Aws.md): Stores BLOBs on the[Amazon Simple Storage Service](https://min.io/).
More providers will be implemented by the time. You can [request](https://github.com/abpframework/abp/issues/new) it for your favorite provider or [create it yourself](Blob-Storing-Custom-Provider.md) and [contribute](Contribution/Index.md) to the ABP Framework. More providers will be implemented by the time. You can [request](https://github.com/abpframework/abp/issues/new) it for your favorite provider or [create it yourself](Blob-Storing-Custom-Provider.md) and [contribute](Contribution/Index.md) to the ABP Framework.

177
docs/en/Blog-Posts/2020-07-01 v3_0_Release/Post.md

@ -0,0 +1,177 @@
# ABP Framework v3.0 Has Been Released
We are excited to announce that the **ABP Framework** & and the **ABP Commercial** version 3.0 have been released. As different than the regular release lifecycle, which is 2-weeks, this version has taken 4-weeks with **119 [issues](https://github.com/abpframework/abp/issues?q=is%3Aopen+is%3Aissue+milestone%3A3.0)** closed, **89 [pull requests](https://github.com/abpframework/abp/pulls?q=is%3Aopen+is%3Apr+milestone%3A3.0)** merged and **798 commits** done in the main framework [repository](https://github.com/abpframework/abp).
Since this is a **major version**, it also includes some **breaking changes**. Don't panic, the changes are easy to adapt and will be explained below.
> See the [GitHub release notes](https://github.com/abpframework/abp/releases/tag/3.0.0) for a detailed change log.
## What's New with the ABP Framework 3.0?
This post will only cover the important features/changes. You can see all the changes on the [GitHub release notes](https://github.com/abpframework/abp/releases/tag/2.9.0).
### Angular 10!
Angular version 10 has just been [released](https://blog.angular.io/version-10-of-angular-now-available-78960babd41) and we've immediately migrated the [startup templates](https://docs.abp.io/en/abp/latest/Startup-Templates/Application) to Angular 10! So, when you [create a new solution](https://abp.io/get-started) with the Angular UI, you will take the advantage of the new Angular.
We've prepared a [migration guide](https://github.com/abpframework/abp/blob/dev/docs/en/UI/Angular/Migration-Guide-v3.md) for the projects created an older version and want to migrate to Angular 10.
### The Oracle Integration Package
We had created [an integration package](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore.Oracle.Devart) for the Oracle for EF Core based applications using the Devart's library since the official Oracle EF Core package was not supporting the EF Core 3.1. It now supports as a [beta release](https://www.nuget.org/packages/Oracle.EntityFrameworkCore/3.19.0-beta1). While it is in beta, we've created [the integration package](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore.Oracle), so you can use it in your application.
See [the documentation](https://docs.abp.io/en/abp/latest/Entity-Framework-Core-Oracle) for details.
### Azure BLOB Storage Provider
We had created a [BLOB storing system](https://docs.abp.io/en/abp/latest/Blob-Storing) in the previous version with a file system and database storage provider. This release introduces the Azure BLOB Storage provider. See [the documentation](https://docs.abp.io/en/abp/latest/Blob-Storing-Azure).
### Distributed Cache Bulk Operations & the New Redis Cache Package
The [standard IDistributeCache](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) interface of the ASP.NET Core doesn't contain **bulk operations**, like setting multiple items with a single method/server call. ABP Framework introduces new methods those can be used for bulk operations on the ABP's `IDistributedCache<T>` interface:
* GetManyAsync / GetMany
* SetManyAsync / SetMany
Then we needed to implement these new methods for Redis cache and [had to create](https://github.com/abpframework/abp/issues/4483) a Redis integration package which extends the Microsoft's implementation.
These methods are also used by the ABP Framework to cache settings, features and permissions for a user/role/tenant and brings a **significant performance improvement**.
See the [caching document](https://docs.abp.io/en/abp/latest/Caching) for details.
### Embedded Files Manifest Support for the Virtual File System
Virtual File System now supports to use `GenerateEmbeddedFilesManifest` in your projects to add the **real file/directory structure** of your embedded resources in the compiled assembly. So, you can now access to the files without any file name restriction (previously, some special chars like `.` in the directory names was a problem in some cases)
See [the documentation](https://docs.abp.io/en/abp/latest/Virtual-File-System) to learn how to take the advantage of new system.
### New Samples
Based on the requests from the community, we've prepared two new sample applications:
* [StoredProcedureDemo](https://github.com/abpframework/abp-samples/tree/master/StoredProcedureDemo) demonstrates how to call stored procedures, views and functions inside a custom repository.
* [OrganizationUnitSample](https://github.com/abpframework/abp-samples/tree/master/OrganizationUnitSample) shows how to use the organization unit system of the [Identity module](https://docs.abp.io/en/abp/latest/Modules/Identity) for your entities.
### DynamicStringLength & DynamicMaxLength Attributes
The standard `StringLength` and `MaxLength` data annotation attributes is useful to validate properties of a class when the class is used as a Model or [DTO](https://docs.abp.io/en/abp/latest/Data-Transfer-Objects). However, just like any other attribute, the length values should be literal (constant) values known at **compile time**.
**Example: Using the `StringLength`**
````csharp
public class CreateBookDto
{
public const int MaxNameLength = 128; //CONSTANT!
[StringLength(MaxNameLength)]
public string Name { get; set; }
}
````
ABP Framework now has the `DynamicStringLength` & `DynamicMaxLength` properties to allow to determine the lengths at **runtime**.
**Example: Using the `DynamicStringLength`**
````csharp
public class CreateBookDto
{
public static int MaxNameLength { get; set; } = 128;
[DynamicStringLength(typeof(CreateBookDto), nameof(MaxNameLength))]
public string Name { get; set; }
}
````
`DynamicStringLength` gets a class **type** and the **name** of a static property on this class to read the max length (there is also a minimum length option just like the `StringLength`).
This allows you to get the max value from a configuration and set on the application startup (generally, in the `PreConfigureServices` method of your [module](https://docs.abp.io/en/abp/latest/Module-Development-Basics)):
````csharp
CreateBookDto.MaxNameLength = 200;
````
This feature is used by the [pre-built application modules](https://docs.abp.io/en/abp/latest/Modules/Index), so you can now override the max lengths of the properties defined in these modules.
### Auto Distributed Events
ABP can **automatically publish distributed events** for all entities on their create, update and delete events. That's pretty useful since you commonly interest in these basic events in a distributed system.
This feature is **mature and [documented](https://docs.abp.io/en/abp/latest/Distributed-Event-Bus#pre-defined-events)** with the v3.0. You can easily configure some or all the entities to be published.
### IAsyncQueryableExecuter
When you work with LINQ extension methods, you need to call `ToListAsync()`, `FirstOrDefaultAsync()`... methods on your queries. Unfortunately, these methods are **not standard** LINQ extension methods. They are defined in the [Microsoft.EntityFrameworkCore](https://www.nuget.org/packages/Microsoft.EntityFrameworkCore) package (or in the [MongoDB.Driver](https://www.nuget.org/packages/MongoDB.Driver/) package if you are using the MongoDB).
So, you need to depend on this package if you want to use the async extension methods. That breaks the layering and makes your application or domain layer depends on the EF Core / MongoDB package.
`IAsyncQueryableExecuter` is a service defined by the ABP Framework to **execute queries asynchronously without depending the specific provider** (EF Core / MongoDB) package.
See [the documentation](https://docs.abp.io/en/abp/latest/Repositories#iqueryable-async-operations) to read the details and learn our recommendations.
### API documentation
We are now publishing [API documents](https://docs.abp.io/api-docs/abp/2.9/api/index.html) for the ABP Framework and modules in every release. So, you can explore the ABP Framework classes much more easier than before. Click the the **API Documentation** link on the navigation menu of the [documentation](https://docs.abp.io/en/abp/latest/).
### Package List
We have [created a page](http://abp.io/packages) to list all the ABP-related official NuGet and NPM packages.
### Others
* Implemented front-channel server-side clients [sign out](https://identityserver4.readthedocs.io/en/latest/topics/signout.html) for the identity server.
* `abp.currentUser` (`CurrentUser` service in the Angular UI) now has a `roles` array that contains role names of the current user.
* Upgraded all the NuGet and NPM package dependencies.
* Introduced `ReadOnlyAppService` base class (which has only the get operations) in addition to the `CrudAppService` base class (which has get, create, update and delete operations).
See the [GitHub release notes](https://github.com/abpframework/abp/releases/tag/3.0.0) for others updates.
## What's New with the ABP Commercial 3.0?
In addition to all the features coming with the ABP Framework, the ABP Commercial has additional features with this release, as always. This section covers the [ABP Commercial](https://commercial.abp.io/) highlights in the version 2.9.
### New File Management Module
We've created a new module that is used to store and manage files in your application. This new module is based on the [BLOB Storing system](https://docs.abp.io/en/abp/latest/Blob-Storing), so it can use different storage providers to store the file contents.
**Example screenshot**
![file-management-ui](file-management-ui.png)
You can upload, download and organize files in a hierarchical folder structure. It is also compatible to multi-tenancy and you can determine total size limit for your tenants. In the next versions, we will be working on a "share" system to share files between users in a more controlled way or share your files with your customers with a public link.
> File Management module is currently available only for the MVC / Razor Pages UI. We are working on the Angular UI and it will be released in the next versions.
## Breaking Changes
Since this is a major version, we've redesigned some APIs and introduced a few "easy to fix" breaking changes.
### ABP Framework
* Changed some **consts** in the pre-built application modules to static properties that is possible to change by your code. If you've used these consts on an attribute, then use the `DynamicStringLength` as explained above.
* Changed `ConcurrencyStamp` max length to 40. You need to **add a database migration** and update your database after upgrading the ABP Framework.
* Using `~` instead of `^` for NPM package dependencies anymore, to be more stable.
### ABP Commercial
* Changed file names for the application logos. Previously, it was using separate logo files for each theme, like `theme1.png`, `theme1-reverse.png`, `theme2.png`, `theme2-reverse.png` (... `6`). Now, we have only two logo files: `logo-light.png` and `logo-dark.png`. So, rename your logo in the `wwwroot/images/logo/` folder for the MVC UI and `/src/assets/images/logo/` folder for the Angular UI.
* We've added the [API documentation](https://docs.abp.io/api-docs/commercial/2.9/api/index.html) for the ABP Commercial too.
> **Also, see the [migration guide](https://github.com/abpframework/abp/blob/dev/docs/en/UI/Angular/Migration-Guide-v3.md) for Angular UI**.
## Known Issues
* 3.0.0 version has a problem with tiered architecture. See [this issue](https://github.com/abpframework/abp/pull/4564) to fix it for your application until we release the v3.0.1.
## About the Next Versions
We will continue to release a new minor/feature version in every two weeks. So, the next expected release date is **2020-07-16** for the version **3.1**.
In the next few versions, we will be focused on the **Blazor UI**, as promised on [the road map](https://docs.abp.io/en/abp/latest/Road-Map). We will continue to improve the documentation, create samples, add other new features and enhancements. Follow the [ABP Framework Twitter account](https://twitter.com/abpframework) for the latest news...
## Bonus: Articles!
Beside developing our products, our team are constantly writing articles/tutorials on various topics. You may want to check the latest articles:
* [What is New in Angular 10?](https://volosoft.com/blog/what-is-new-in-angular-10)
* [Real-Time Messaging In A Distributed Architecture Using ABP, SignalR & RabbitMQ](https://volosoft.com/blog/RealTime-Messaging-Distributed-Architecture-Abp-SingalR-RabbitMQ)
* [How to Use Attribute Directives to Avoid Repetition in Angular Templates](https://volosoft.com/blog/attribute-directives-to-avoid-repetition-in-angular-templates)

BIN
docs/en/Blog-Posts/2020-07-01 v3_0_Release/file-management-ui.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

15
docs/en/CLI.md

@ -90,6 +90,7 @@ abp new Acme.BookStore
* **`console`**: [Console template](Startup-Templates/Console.md). * **`console`**: [Console template](Startup-Templates/Console.md).
* `--output-folder` or `-o`: Specifies the output folder. Default value is the current directory. * `--output-folder` or `-o`: Specifies the output folder. Default value is the current directory.
* `--version` or `-v`: Specifies the ABP & template version. It can be a [release tag](https://github.com/abpframework/abp/releases) or a [branch name](https://github.com/abpframework/abp/branches). Uses the latest release if not specified. Most of the times, you will want to use the latest version. * `--version` or `-v`: Specifies the ABP & template version. It can be a [release tag](https://github.com/abpframework/abp/releases) or a [branch name](https://github.com/abpframework/abp/branches). Uses the latest release if not specified. Most of the times, you will want to use the latest version.
* `--preview`: Use latest pre-release version (Only if `--version ` is not specified and there is at least one pre-release after latest stable version).
* `--template-source` or `-ts`: Specifies a custom template source to use to build the project. Local and network sources can be used(Like `D:\local-template` or `https://.../my-template-file.zip`). * `--template-source` or `-ts`: Specifies a custom template source to use to build the project. Local and network sources can be used(Like `D:\local-template` or `https://.../my-template-file.zip`).
* `--create-solution-folder` or `-csf`: Specifies if the project will be in a new folder in the output folder or directly the output folder. * `--create-solution-folder` or `-csf`: Specifies if the project will be in a new folder in the output folder or directly the output folder.
* `--connection-string` or `-cs`: Overwrites the default connection strings in all `appsettings.json` files. The default connection string is `Server=localhost;Database=MyProjectName;Trusted_Connection=True;MultipleActiveResultSets=true` for EF Core and it is configured to use the SQL Server. If you want to use the EF Core, but need to change the DBMS, you can change it as [described here](Entity-Framework-Core-Other-DBMS.md) (after creating the solution). * `--connection-string` or `-cs`: Overwrites the default connection strings in all `appsettings.json` files. The default connection string is `Server=localhost;Database=MyProjectName;Trusted_Connection=True;MultipleActiveResultSets=true` for EF Core and it is configured to use the SQL Server. If you want to use the EF Core, but need to change the DBMS, you can change it as [described here](Entity-Framework-Core-Other-DBMS.md) (after creating the solution).
@ -196,12 +197,12 @@ abp generate-proxy --apiUrl https://localhost:44305 --ui angular --module all
### switch-to-preview ### switch-to-preview
You can use this command to switch your project to latest preview version of the ABP framework packages. You can use this command to switch your project to latest **nightly** preview version of the ABP framework packages.
Usage: Usage:
````bash ````bash
abp switch-to-preview [options] abp switch-to-nightly [options]
```` ````
#### Options #### Options
@ -210,7 +211,7 @@ abp switch-to-preview [options]
### switch-to-stable ### switch-to-stable
If you're using the ABP Framework preview packages, you can switch back to stable version using this command. If you're using the ABP Framework preview packages, you can switch back to latest stable version using this command.
Usage: Usage:
@ -273,11 +274,13 @@ Then review changes on your source control system to be sure that it has changed
Some features of the CLI requires to be logged in to abp.io platform. To login with your username write: Some features of the CLI requires to be logged in to abp.io platform. To login with your username write:
```bash ```bash
abp login <username> # Asks password separately abp login <username> # Allows you to enter your password hidden
abp login <username> -p <password> # Specify the password as a parameter abp login <username> -p <password> # Specify the password as a parameter (password is visible)
abp login <username> --organization <organization> # If you have multiple organizations, you need set your active organization
abp login <username> -p <password> -o <organization> # You can enter both your password and organization in the same command
``` ```
> Using `-p` parameter might not be safe if someone is watching your screen :) It can be useful for automation purposes. > When using the -p parameter, be careful as your password will be visible. It's useful for CI/CD automation pipelines.
A new login with an already active session overwrites the previous session. A new login with an already active session overwrites the previous session.

96
docs/en/Community-Articles/2020-04-19-Customize-the-SignIn-Manager/POST.md

@ -0,0 +1,96 @@
# How to Customize the SignIn Manager for ABP Applications
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.
This document explains how to customize the SignIn Manager for your own application.
## Create a CustomSignInManager
Create a new class inheriting the [SignInMager](https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs) of Microsoft Identity package.
````csharp
public class CustomSignInManager : Microsoft.AspNetCore.Identity.SignInManager<Volo.Abp.Identity.IdentityUser>
{
public CustomSignInManager(
Microsoft.AspNetCore.Identity.UserManager<Volo.Abp.Identity.IdentityUser> userManager,
Microsoft.AspNetCore.Http.IHttpContextAccessor contextAccessor,
Microsoft.AspNetCore.Identity.IUserClaimsPrincipalFactory<Volo.Abp.Identity.IdentityUser> claimsFactory,
Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Identity.IdentityOptions> optionsAccessor,
Microsoft.Extensions.Logging.ILogger<Microsoft.AspNetCore.Identity.SignInManager<Volo.Abp.Identity.IdentityUser>> logger,
Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider schemes,
Microsoft.AspNetCore.Identity.IUserConfirmation<Volo.Abp.Identity.IdentityUser> confirmation)
: base(userManager, contextAccessor, claimsFactory, optionsAccessor, logger, schemes, confirmation)
{
}
}
````
> It is important to use **Volo.Abp.Identity.IdentityUser** type for SignInManager to inherit, not the AppUser of your application.
Afterwards you can override any of the SignIn Manager methods you need and add new methods and properties needed for your authentication or registration flow.
## Overriding the GetExternalLoginInfoAsync Method
In this case we'll be overriding the `GetExternalLoginInfoAsync` method which is invoked when a third party authentication is implemented.
A good way to override a method is copying its [source code](https://github.com/dotnet/aspnetcore/blob/c56aa320c32ee5429d60647782c91d53ac765865/src/Identity/Core/src/SignInManager.cs#L638-L674). In this case, we will be using a minorly modified version of the source code which explicitly shows the namespaces of the methods and properties to help better understanding of the concept.
````csharp
public override async Task<Microsoft.AspNetCore.Identity.ExternalLoginInfo> GetExternalLoginInfoAsync(string expectedXsrf = null)
{
var auth = await Context.AuthenticateAsync(Microsoft.AspNetCore.Identity.IdentityConstants.ExternalScheme);
var items = auth?.Properties?.Items;
if (auth?.Principal == null || items == null || !items.ContainsKey(LoginProviderKey))
{
return null;
}
if (expectedXsrf != null)
{
if (!items.ContainsKey(XsrfKey))
{
return null;
}
var userId = items[XsrfKey] as string;
if (userId != expectedXsrf)
{
return null;
}
}
var providerKey = auth.Principal.FindFirstValue(ClaimTypes.NameIdentifier);
var provider = items[LoginProviderKey] as string;
if (providerKey == null || provider == null)
{
return null;
}
var providerDisplayName = (await GetExternalAuthenticationSchemesAsync()).FirstOrDefault(p => p.Name == provider)?.DisplayName
?? provider;
return new Microsoft.AspNetCore.Identity.ExternalLoginInfo(auth.Principal, provider, providerKey, providerDisplayName)
{
AuthenticationTokens = auth.Properties.GetTokens()
};
}
````
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
Registering `CustomSignInManager` should be done with adding **AddSignInManager** extension method of the [IdentityBuilderExtensions](https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/IdentityBuilderExtensions.cs) of the [IdentityBuilder](https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Extensions.Core/src/IdentityBuilder.cs).
Inside your `.Web` project, locate the `YourProjectNameWebModule` and add the following code under the `PreConfigureServices` method to replace the old `SignInManager` with your customized one:
````csharp
PreConfigure<IdentityBuilder>(identityBuilder =>
{
identityBuilder.AddSignInManager<CustomSignInManager>();
});
````
## The Source Code
You can find the source code of the completed example [here](https://github.com/abpframework/abp-samples/tree/master/Authentication-Customization).

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

@ -0,0 +1,207 @@
# How to Use the Azure Active Directory Authentication for MVC / Razor Page Applications
This guide demonstrates how to integrate AzureAD to an ABP application that enables users to sign in using OAuth 2.0 with credentials from **Azure Active Directory**.
Adding Azure Active Directory is pretty straightforward in ABP framework. Couple of configurations needs to be done correctly.
Two different **alternative approaches** for AzureAD integration will be demonstrated for better coverage.
1. **AddAzureAD**: This approach uses Microsoft [AzureAD UI nuget package](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.AzureAD.UI/) which is very popular when users search the web about how to integrate AzureAD to their web application.
2. **AddOpenIdConnect**: This approach uses default [OpenIdConnect](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.OpenIdConnect/) which can be used for not only AzureAD but for all OpenId connections.
> There is **no difference** in functionality between these approaches. AddAzureAD is an abstracted way of OpenIdConnection ([source](https://github.com/dotnet/aspnetcore/blob/c56aa320c32ee5429d60647782c91d53ac765865/src/Azure/AzureAD/Authentication.AzureAD.UI/src/AzureADAuthenticationBuilderExtensions.cs#L122)) with predefined cookie settings.
>
> However there are key differences in integration to ABP applications because of default configurated signin schemes which will be explained below.
## 1. AddAzureAD
This approach uses the most common way to integrate AzureAD by using the [Microsoft AzureAD UI nuget package](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.AzureAD.UI/).
If you choose this approach, you will need to install `Microsoft.AspNetCore.Authentication.AzureAD.UI` package to your **.Web** project. Also, since AddAzureAD extension uses [configuration binding](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-3.1#default-configuration), you need to update your appsettings.json file located in your **.Web** project.
#### **Updating `appsettings.json`**
You need to add a new section to your `appsettings.json` which will be binded to configuration when configuring the `OpenIdConnectOptions`:
````json
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"TenantId": "<your-tenant-id>",
"ClientId": "<your-client-id>",
"Domain": "domain.onmicrosoft.com",
"CallbackPath": "/signin-azuread-oidc"
}
````
> Important configuration here is the CallbackPath. This value must be the same with one of your Azure AD-> app registrations-> Authentication -> RedirectUri.
Then, you need to configure the `OpenIdConnectOptions` to complete the integration.
#### Configuring OpenIdConnectOptions
In your **.Web** project, locate your **ApplicationWebModule** and modify `ConfigureAuthentication` method with the following:
````csharp
private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration)
{
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier);
context.Services.AddAuthentication()
.AddIdentityServerAuthentication(options =>
{
options.Authority = configuration["AuthServer:Authority"];
options.RequireHttpsMetadata = false;
options.ApiName = "Acme.BookStore";
})
.AddAzureAD(options => configuration.Bind("AzureAd", options));
context.Services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
{
options.Authority = options.Authority + "/v2.0/";
options.ClientId = configuration["AzureAd:ClientId"];
options.CallbackPath = configuration["AzureAd:CallbackPath"];
options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
options.RequireHttpsMetadata = false;
options.TokenValidationParameters.ValidateIssuer = false;
options.GetClaimsFromUserInfoEndpoint = true;
options.SaveTokens = true;
options.SignInScheme = IdentityConstants.ExternalScheme;
options.Scope.Add("email");
});
}
````
> **Don't forget to:**
>
> * Add `.AddAzureAD(options => configuration.Bind("AzureAd", options))` after `.AddAuthentication()`. This binds your AzureAD appsettings and easy to miss out.
> * Add `JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear()`. This will disable the default Microsoft claim type mapping.
> * Add `JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier)`. Mapping this to [ClaimTypes.NameIdentifier](https://github.com/dotnet/runtime/blob/6d395de48ac718a913e567ae80961050f2a9a4fa/src/libraries/System.Security.Claims/src/System/Security/Claims/ClaimTypes.cs#L59) is important since default SignIn Manager behavior uses this claim type for external login information.
> * Add `options.SignInScheme = IdentityConstants.ExternalScheme` since [default signin scheme is `AzureADOpenID`](https://github.com/dotnet/aspnetcore/blob/c56aa320c32ee5429d60647782c91d53ac765865/src/Azure/AzureAD/Authentication.AzureAD.UI/src/AzureADOpenIdConnectOptionsConfiguration.cs#L35).
> * Add `options.Scope.Add("email")` if you are using **v2.0** endpoint of AzureAD since v2.0 endpoint doesn't return the `email` claim as default. The [Account Module](https://docs.abp.io/en/abp/latest/Modules/Account) uses `email` claim to [register external users](https://github.com/abpframework/abp/blob/be32a55449e270d2d456df3dabdc91f3ffdd4fa9/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs#L215).
You are done and integration is completed.
## 2. Alternative Approach: AddOpenIdConnect
If you don't want to use an extra nuget package in your application, you can use the straight default [OpenIdConnect](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.OpenIdConnect/) which can be used for all OpenId connections including AzureAD external authentication.
You don't have to use `appsettings.json` configuration but it is a good practice to set AzureAD information in the `appsettings.json`.
To get the AzureAD information from `appsettings.json`, which will be used in `OpenIdConnectOptions` configuration, simply add a new section to `appsettings.json` located in your **.Web** project:
````json
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"TenantId": "<your-tenant-id>",
"ClientId": "<your-client-id>",
"Domain": "domain.onmicrosoft.com",
"CallbackPath": "/signin-azuread-oidc"
}
````
Then, In your **.Web** project; you can modify the `ConfigureAuthentication` method located in your **ApplicationWebModule** with the following:
````csharp
private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration)
{
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier);
context.Services.AddAuthentication()
.AddIdentityServerAuthentication(options =>
{
options.Authority = configuration["AuthServer:Authority"];
options.RequireHttpsMetadata = false;
options.ApiName = "BookStore";
})
.AddOpenIdConnect("AzureOpenId", "Azure Active Directory OpenId", options =>
{
options.Authority = "https://login.microsoftonline.com/" + configuration["AzureAd:TenantId"] + "/v2.0/";
options.ClientId = configuration["AzureAd:ClientId"];
options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
options.CallbackPath = configuration["AzureAd:CallbackPath"];
options.RequireHttpsMetadata = false;
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("email");
});
}
````
And that's it, integration is completed. Keep on mind that you can connect any other external authentication providers.
## The Source Code
You can find the source code of the completed example [here](https://github.com/abpframework/abp-samples/tree/master/Authentication-Customization).
# FAQ
* Help! `GetExternalLoginInfoAsync` returns `null`!
* There can be 2 reasons for this;
1. You are trying to authenticate against wrong scheme. Check if you set **SignInScheme** to `IdentityConstants.ExternalScheme`:
````csharp
options.SignInScheme = IdentityConstants.ExternalScheme;
````
2. Your `ClaimTypes.NameIdentifier` is `null`. Check if you added claim mapping:
````csharp
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier);
````
* Help! I am getting ***System.ArgumentNullException: Value cannot be null. (Parameter 'userName')*** error!
* This occurs when you use Azure Authority **v2.0 endpoint** without requesting `email` scope. [Abp checks unique email to create user](https://github.com/abpframework/abp/blob/037ef9abe024c03c1f89ab6c933710bcfe3f5c93/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs#L208). Simply add
````csharp
options.Scope.Add("email");
````
to your openid configuration.
* Help! I keep getting ***AADSTS50011: The reply URL specified in the request does not match the reply URLs configured for the application*** error!
* If you set your **CallbackPath** in appsettings as:
````csharp
"AzureAd": {
...
"CallbackPath": "/signin-azuread-oidc"
}
````
your **Redirect URI** of your application in azure portal must be <u>with domain</u> like `https://localhost:44320/signin-azuread-oidc`, not only `/signin-azuread-oidc`.
* Help! I keep getting ***AADSTS700051: The response_type 'token' is not enabled for the application.*** error!
* This error occurs when you request **token** (access token) along with **id_token** without enabling Access tokens on Azure portal app registrations. Simply tick **Access tokens** checkbox located on top of ID tokens to be able to request token aswell.
* Help! I keep getting ***AADSTS7000218: The request body must contain the following parameter: 'client_assertion' or 'client_secret*** error!
* This error occurs when you request **code** along with **id_token**. You need to add **client secret** on azure portal app registrations, under **Certificates & secrets** menu. Afterwards, you need to add openid configuration option like:
````csharp
options.ClientSecret = "Value of your secret on azure portal";
````
* How can I **debug/watch** which claims I get before they get mapped?
* You can add a simple event under openid configuration to debug before mapping like:
````csharp
options.Events.OnTokenValidated = (async context =>
{
var claimsFromOidcProvider = context.Principal.Claims.ToList();
await Task.CompletedTask;
});
````

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

@ -0,0 +1,108 @@
# How to Customize the Login Page for MVC / Razor Page Applications
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.
## Create a Login PageModel
Create a new class inheriting from the [LoginModel](https://github.com/abpframework/abp/blob/037ef9abe024c03c1f89ab6c933710bcfe3f5c93/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs) of the Account module.
````csharp
public class CustomLoginModel : LoginModel
{
public CustomLoginModel(
Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider schemeProvider,
Microsoft.Extensions.Options.IOptions<Volo.Abp.Account.Web.AbpAccountOptions> accountOptions)
: base(schemeProvider, accountOptions)
{
}
}
````
> 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.
## Overriding the Login Page UI
Create folder named **Account** under **Pages** directory and create a **Login.cshtml** under this folder. It will automatically override the `Login.cshtml` file defined in the Account Module.
A good way to customize a page is to copy its source code. [Click here](https://github.com/abpframework/abp/blob/dev/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml) for the source code of the login page. At the time this document has been written, the source code was like below:
````xml
@page
@using Volo.Abp.Account.Settings
@using Volo.Abp.Settings
@model Acme.BookStore.Web.Pages.Account.CustomLoginModel
@inject Volo.Abp.Settings.ISettingProvider SettingProvider
@if (Model.EnableLocalLogin)
{
<div class="card mt-3 shadow-sm rounded">
<div class="card-body p-5">
<h4>@L["Login"]</h4>
@if (await SettingProvider.IsTrueAsync(AccountSettingNames.IsSelfRegistrationEnabled))
{
<strong>
@L["AreYouANewUser"]
<a href="@Url.Page("./Register", new {returnUrl = Model.ReturnUrl, returnUrlHash = Model.ReturnUrlHash})" class="text-decoration-none">@L["Register"]</a>
</strong>
}
<form method="post" class="mt-4">
<input asp-for="ReturnUrl" />
<input asp-for="ReturnUrlHash" />
<div class="form-group">
<label asp-for="LoginInput.UserNameOrEmailAddress"></label>
<input asp-for="LoginInput.UserNameOrEmailAddress" class="form-control" />
<span asp-validation-for="LoginInput.UserNameOrEmailAddress" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="LoginInput.Password"></label>
<input asp-for="LoginInput.Password" class="form-control" />
<span asp-validation-for="LoginInput.Password" class="text-danger"></span>
</div>
<div class="form-check">
<label asp-for="LoginInput.RememberMe" class="form-check-label">
<input asp-for="LoginInput.RememberMe" class="form-check-input" />
@Html.DisplayNameFor(m => m.LoginInput.RememberMe)
</label>
</div>
<abp-button type="submit" button-type="Primary" name="Action" value="Login" class="btn-block btn-lg mt-3">@L["Login"]</abp-button>
</form>
</div>
<div class="card-footer text-center border-0">
<abp-button type="button" button-type="Link" name="Action" value="Cancel" class="px-2 py-0">@L["Cancel"]</abp-button> @* TODO: Only show if identity server is used *@
</div>
</div>
}
@if (Model.VisibleExternalProviders.Any())
{
<div class="col-md-6">
<h4>@L["UseAnotherServiceToLogIn"]</h4>
<form asp-page="./Login" asp-page-handler="ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" asp-route-returnUrlHash="@Model.ReturnUrlHash" method="post">
<input asp-for="ReturnUrl" />
<input asp-for="ReturnUrlHash" />
@foreach (var provider in Model.VisibleExternalProviders)
{
<button type="submit" class="btn btn-primary" name="provider" value="@provider.AuthenticationScheme" title="@L["GivenTenantIsNotAvailable", provider.DisplayName]">@provider.DisplayName</button>
}
</form>
</div>
}
@if (!Model.EnableLocalLogin && !Model.VisibleExternalProviders.Any())
{
<div class="alert alert-warning">
<strong>@L["InvalidLoginRequest"]</strong>
@L["ThereAreNoLoginSchemesConfiguredForThisClient"]
</div>
}
````
Just changed the `@model` to `Acme.BookStore.Web.Pages.Account.CustomLoginModel` to use the customized `PageModel` class. You can change it however your application needs.
## The Source Code
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).

212
docs/en/Community-Articles/2020-08-12-Patch-Chrome-Login-Issue-For-IdentityServer4/POST.md

@ -0,0 +1,212 @@
# How to fix the Chrome login issue for the IdentityServer4
## Introduction
When you use HTTP on your Identity Server 4 enabled website, users may not login because of the changes made by Chrome in the version 8x. This occurs when you use HTTP schema in your website. The issue is explained here https://docs.microsoft.com/en-gb/dotnet/core/compatibility/3.0-3.1#http-browser-samesite-changes-impact-authentication
## How to solve it?
### Step-1
Create the below extension in your ***.Web** project.
```csharp
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
namespace Microsoft.Extensions.DependencyInjection
{
public static class SameSiteCookiesServiceCollectionExtensions
{
/// <summary>
/// -1 defines the unspecified value, which tells ASPNET Core to NOT
/// send the SameSite attribute. With ASPNET Core 3.1 the
/// <seealso cref="SameSiteMode" /> enum will have a definition for
/// Unspecified.
/// </summary>
private const SameSiteMode Unspecified = (SameSiteMode)(-1);
/// <summary>
/// Configures a cookie policy to properly set the SameSite attribute
/// for Browsers that handle unknown values as Strict. Ensure that you
/// add the <seealso cref="Microsoft.AspNetCore.CookiePolicy.CookiePolicyMiddleware" />
/// into the pipeline before sending any cookies!
/// </summary>
/// <remarks>
/// Minimum ASPNET Core Version required for this code:
/// - 2.1.14
/// - 2.2.8
/// - 3.0.1
/// - 3.1.0-preview1
/// Starting with version 80 of Chrome (to be released in February 2020)
/// cookies with NO SameSite attribute are treated as SameSite=Lax.
/// In order to always get the cookies send they need to be set to
/// SameSite=None. But since the current standard only defines Lax and
/// Strict as valid values there are some browsers that treat invalid
/// values as SameSite=Strict. We therefore need to check the browser
/// and either send SameSite=None or prevent the sending of SameSite=None.
/// Relevant links:
/// - https://tools.ietf.org/html/draft-west-first-party-cookies-07#section-4.1
/// - https://tools.ietf.org/html/draft-west-cookie-incrementalism-00
/// - https://www.chromium.org/updates/same-site
/// - https://devblogs.microsoft.com/aspnet/upcoming-samesite-cookie-changes-in-asp-net-and-asp-net-core/
/// - https://bugs.webkit.org/show_bug.cgi?id=198181
/// </remarks>
/// <param name="services">The service collection to register <see cref="CookiePolicyOptions" /> into.</param>
/// <returns>The modified <see cref="IServiceCollection" />.</returns>
public static IServiceCollection ConfigureNonBreakingSameSiteCookies(this IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
options.MinimumSameSitePolicy = Unspecified;
options.OnAppendCookie = cookieContext =>
CheckSameSite(cookieContext.Context, cookieContext.CookieOptions);
options.OnDeleteCookie = cookieContext =>
CheckSameSite(cookieContext.Context, cookieContext.CookieOptions);
});
return services;
}
private static void CheckSameSite(HttpContext httpContext, CookieOptions options)
{
if (options.SameSite == SameSiteMode.None)
{
var userAgent = httpContext.Request.Headers["User-Agent"].ToString();
if (DisallowsSameSiteNone(userAgent))
{
options.SameSite = Unspecified;
}
}
}
/// <summary>
/// Checks if the UserAgent is known to interpret an unknown value as Strict.
/// For those the <see cref="CookieOptions.SameSite" /> property should be
/// set to <see cref="Unspecified" />.
/// </summary>
/// <remarks>
/// This code is taken from Microsoft:
/// https://devblogs.microsoft.com/aspnet/upcoming-samesite-cookie-changes-in-asp-net-and-asp-net-core/
/// </remarks>
/// <param name="userAgent">The user agent string to check.</param>
/// <returns>Whether the specified user agent (browser) accepts SameSite=None or not.</returns>
private static bool DisallowsSameSiteNone(string userAgent)
{
// Cover all iOS based browsers here. This includes:
// - Safari on iOS 12 for iPhone, iPod Touch, iPad
// - WkWebview on iOS 12 for iPhone, iPod Touch, iPad
// - Chrome on iOS 12 for iPhone, iPod Touch, iPad
// All of which are broken by SameSite=None, because they use the
// iOS networking stack.
// Notes from Thinktecture:
// Regarding https://caniuse.com/#search=samesite iOS versions lower
// than 12 are not supporting SameSite at all. Starting with version 13
// unknown values are NOT treated as strict anymore. Therefore we only
// need to check version 12.
if (userAgent.Contains("CPU iPhone OS 12")
|| userAgent.Contains("iPad; CPU OS 12"))
{
return true;
}
// Cover Mac OS X based browsers that use the Mac OS networking stack.
// This includes:
// - Safari on Mac OS X.
// This does not include:
// - Chrome on Mac OS X
// because they do not use the Mac OS networking stack.
// Notes from Thinktecture:
// Regarding https://caniuse.com/#search=samesite MacOS X versions lower
// than 10.14 are not supporting SameSite at all. Starting with version
// 10.15 unknown values are NOT treated as strict anymore. Therefore we
// only need to check version 10.14.
if (userAgent.Contains("Safari")
&& userAgent.Contains("Macintosh; Intel Mac OS X 10_14")
&& userAgent.Contains("Version/"))
{
return true;
}
// Cover Chrome 50-69, because some versions are broken by SameSite=None
// and none in this range require it.
// Note: this covers some pre-Chromium Edge versions,
// but pre-Chromium Edge does not require SameSite=None.
// Notes from Thinktecture:
// We can not validate this assumption, but we trust Microsofts
// evaluation. And overall not sending a SameSite value equals to the same
// behavior as SameSite=None for these old versions anyways.
if (userAgent.Contains("Chrome/5") || userAgent.Contains("Chrome/6"))
{
return true;
}
if (GetChromeVersion(userAgent) >= 80)
{
return true;
}
return false;
}
private static int GetChromeVersion(string userAgent)
{
try
{
return Convert.ToInt32(userAgent.Split("Chrome/")[1].Split('.')[0]);
}
catch (Exception)
{
return 0;
}
}
}
}
```
### Step-2
Assume that your project name is *Acme.BookStore*. Then open `AcmeBookStoreWebModule.cs` class.
Add the following line to `ConfigureServices()` method.
```csharp
context.Services.ConfigureNonBreakingSameSiteCookies();
```
### Step-3
Go to`OnApplicationInitialization()` method in `AcmeBookStoreWebModule.cs` add `app.UseCookiePolicy();`
```csharp
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();
var env = context.GetEnvironment();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseErrorPage();
app.UseHsts();
}
app.UseCookiePolicy(); //<--- added this --->
//....
}
```
It's all! You are ready to go!
---
Referenced from https://www.thinktecture.com/en/identity/samesite/prepare-your-identityserver/

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. 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. 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. 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. 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. 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: 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. 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. 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). 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. 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. * Once you done the translation, use `abp translate -a` command to apply changes to the related files.
* Send a pull request on GitHub. * 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. 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 ## Bug Report
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
If you find any bug, please [create an issue on the Github repository](https://github.com/abpframework/abp/issues/new). If you find any bug, please [create an issue on the Github repository](https://github.com/abpframework/abp/issues/new).

12
docs/en/CurrentUser.md

@ -1,4 +1,4 @@
# Current User # Current User
It is very common to retrieve the information about the logged in user in a web application. The current user is the active user related to the current request in a web application. It is very common to retrieve the information about the logged in user in a web application. The current user is the active user related to the current request in a web application.
@ -86,7 +86,7 @@ Beside these standard methods, there are some extension methods:
## ICurrentPrincipalAccessor ## ICurrentPrincipalAccessor
`ICurrentPrincipalAccessor` is the service that should be used (by the ABP Framework and your application code) whenever the current principle of the current user is needed. `ICurrentPrincipalAccessor` is the service that should be used (by the ABP Framework and your application code) whenever the current principal of the current user is needed.
For a web application, it gets the `User` property of the current `HttpContext`. For a non-web application, it returns the `Thread.CurrentPrincipal`. For a web application, it gets the `User` property of the current `HttpContext`. For a non-web application, it returns the `Thread.CurrentPrincipal`.
@ -114,9 +114,9 @@ public class MyService : ITransientDependency
} }
```` ````
### Changing the Current Principle ### Changing the Current Principal
Current principle is not something you want to set or change, except at some advanced scenarios. If you need it, use the `Change` method of the `ICurrentPrincipalAccessor`. It takes a `ClaimsPrinciple` object and makes it "current" for a scope. Current principal is not something you want to set or change, except at some advanced scenarios. If you need it, use the `Change` method of the `ICurrentPrincipalAccessor`. It takes a `ClaimsPrincipal` object and makes it "current" for a scope.
Example: Example:
@ -132,7 +132,7 @@ public class MyAppService : ApplicationService
public void Foo() public void Foo()
{ {
var newPrinciple = new ClaimsPrincipal( var newPrincipal = new ClaimsPrincipal(
new ClaimsIdentity( new ClaimsIdentity(
new Claim[] new Claim[]
{ {
@ -143,7 +143,7 @@ public class MyAppService : ApplicationService
) )
); );
using (_currentPrincipalAccessor.Change(newPrinciple)) using (_currentPrincipalAccessor.Change(newPrincipal))
{ {
var userName = CurrentUser.UserName; //returns "john" var userName = CurrentUser.UserName; //returns "john"
//... //...

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. 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. > 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 ## Pre-Defined Events
ABP Framework **automatically publishes** distributed events for **create, update and delete** operations for an [entity](Entities.md) once you configure it. 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. 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 ## 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**. 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 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 ## 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).

212
docs/en/How-To/Azure-Active-Directory-Authentication-MVC.md

@ -1,212 +0,0 @@
# How to Use the Azure Active Directory Authentication for MVC / Razor Page Applications
This guide demonstrates how to integrate AzureAD to an ABP application that enables users to sign in using OAuth 2.0 with credentials from **Azure Active Directory**.
Adding Azure Active Directory is pretty straightforward in ABP framework. Couple of configurations needs to be done correctly.
Two different **alternative approaches** for AzureAD integration will be demonstrated for better coverage.
1. **AddAzureAD**: This approach uses Microsoft [AzureAD UI nuget package](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.AzureAD.UI/) which is very popular when users search the web about how to integrate AzureAD to their web application.
2. **AddOpenIdConnect**: This approach uses default [OpenIdConnect](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.OpenIdConnect/) which can be used for not only AzureAD but for all OpenId connections.
> There is **no difference** in functionality between these approaches. AddAzureAD is an abstracted way of OpenIdConnection ([source](https://github.com/dotnet/aspnetcore/blob/c56aa320c32ee5429d60647782c91d53ac765865/src/Azure/AzureAD/Authentication.AzureAD.UI/src/AzureADAuthenticationBuilderExtensions.cs#L122)) with predefined cookie settings.
>
> However there are key differences in integration to ABP applications because of default configurated signin schemes which will be explained below.
## 1. AddAzureAD
This approach uses the most common way to integrate AzureAD by using the [Microsoft AzureAD UI nuget package](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.AzureAD.UI/).
If you choose this approach, you will need to install `Microsoft.AspNetCore.Authentication.AzureAD.UI` package to your **.Web** project. Also, since AddAzureAD extension uses [configuration binding](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-3.1#default-configuration), you need to update your appsettings.json file located in your **.Web** project.
#### **Updating `appsettings.json`**
You need to add a new section to your `appsettings.json` which will be binded to configuration when configuring the `OpenIdConnectOptions`:
````json
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"TenantId": "<your-tenant-id>",
"ClientId": "<your-client-id>",
"Domain": "domain.onmicrosoft.com",
"CallbackPath": "/signin-azuread-oidc"
}
````
> Important configuration here is the CallbackPath. This value must be the same with one of your Azure AD-> app registrations-> Authentication -> RedirectUri.
Then, you need to configure the `OpenIdConnectOptions` to complete the integration.
#### Configuring OpenIdConnectOptions
In your **.Web** project, locate your **ApplicationWebModule** and modify `ConfigureAuthentication` method with the following:
````csharp
private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration)
{
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier);
context.Services.AddAuthentication()
.AddIdentityServerAuthentication(options =>
{
options.Authority = configuration["AuthServer:Authority"];
options.RequireHttpsMetadata = false;
options.ApiName = "Acme.BookStore";
})
.AddAzureAD(options => configuration.Bind("AzureAd", options));
context.Services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
{
options.Authority = options.Authority + "/v2.0/";
options.ClientId = configuration["AzureAd:ClientId"];
options.CallbackPath = configuration["AzureAd:CallbackPath"];
options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
options.RequireHttpsMetadata = false;
options.TokenValidationParameters.ValidateIssuer = false;
options.GetClaimsFromUserInfoEndpoint = true;
options.SaveTokens = true;
options.SignInScheme = IdentityConstants.ExternalScheme;
options.Scope.Add("email");
});
}
````
> **Don't forget to:**
>
> * Add `.AddAzureAD(options => configuration.Bind("AzureAd", options))` after `.AddAuthentication()`. This binds your AzureAD appsettings and easy to miss out.
> * Add `JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear()`. This will disable the default Microsoft claim type mapping.
> * Add `JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier)`. Mapping this to [ClaimTypes.NameIdentifier](https://github.com/dotnet/runtime/blob/6d395de48ac718a913e567ae80961050f2a9a4fa/src/libraries/System.Security.Claims/src/System/Security/Claims/ClaimTypes.cs#L59) is important since default SignIn Manager behavior uses this claim type for external login information.
> * Add `options.SignInScheme = IdentityConstants.ExternalScheme` since [default signin scheme is `AzureADOpenID`](https://github.com/dotnet/aspnetcore/blob/c56aa320c32ee5429d60647782c91d53ac765865/src/Azure/AzureAD/Authentication.AzureAD.UI/src/AzureADOpenIdConnectOptionsConfiguration.cs#L35).
> * Add `options.Scope.Add("email")` if you are using **v2.0** endpoint of AzureAD since v2.0 endpoint doesn't return the `email` claim as default. The [Account Module](../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).
You are done and integration is completed.
## 2. Alternative Approach: AddOpenIdConnect
If you don't want to use an extra nuget package in your application, you can use the straight default [OpenIdConnect](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.OpenIdConnect/) which can be used for all OpenId connections including AzureAD external authentication.
You don't have to use `appsettings.json` configuration but it is a good practice to set AzureAD information in the `appsettings.json`.
To get the AzureAD information from `appsettings.json`, which will be used in `OpenIdConnectOptions` configuration, simply add a new section to `appsettings.json` located in your **.Web** project:
````json
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"TenantId": "<your-tenant-id>",
"ClientId": "<your-client-id>",
"Domain": "domain.onmicrosoft.com",
"CallbackPath": "/signin-azuread-oidc"
}
````
Then, In your **.Web** project; you can modify the `ConfigureAuthentication` method located in your **ApplicationWebModule** with the following:
````csharp
private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration)
{
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier);
context.Services.AddAuthentication()
.AddIdentityServerAuthentication(options =>
{
options.Authority = configuration["AuthServer:Authority"];
options.RequireHttpsMetadata = false;
options.ApiName = "BookStore";
})
.AddOpenIdConnect("AzureOpenId", "Azure Active Directory OpenId", options =>
{
options.Authority = "https://login.microsoftonline.com/" + configuration["AzureAd:TenantId"] + "/v2.0/";
options.ClientId = configuration["AzureAd:ClientId"];
options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
options.CallbackPath = configuration["AzureAd:CallbackPath"];
options.RequireHttpsMetadata = false;
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("email");
});
}
````
And that's it, integration is completed. Keep on mind that you can connect any other external authentication providers.
## The Source Code
You can find the source code of the completed example [here](https://github.com/abpframework/abp-samples/tree/master/aspnet-core/Authentication-Customization).
# FAQ
* Help! `GetExternalLoginInfoAsync` returns `null`!
* There can be 2 reasons for this;
1. You are trying to authenticate against wrong scheme. Check if you set **SignInScheme** to `IdentityConstants.ExternalScheme`:
````csharp
options.SignInScheme = IdentityConstants.ExternalScheme;
````
2. Your `ClaimTypes.NameIdentifier` is `null`. Check if you added claim mapping:
````csharp
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier);
````
* Help! I am getting ***System.ArgumentNullException: Value cannot be null. (Parameter 'userName')*** error!
* This occurs when you use Azure Authority **v2.0 endpoint** without requesting `email` scope. [Abp checks unique email to create user](https://github.com/abpframework/abp/blob/037ef9abe024c03c1f89ab6c933710bcfe3f5c93/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs#L208). Simply add
````csharp
options.Scope.Add("email");
````
to your openid configuration.
* Help! I keep getting ***AADSTS50011: The reply URL specified in the request does not match the reply URLs configured for the application*** error!
* If you set your **CallbackPath** in appsettings as:
````csharp
"AzureAd": {
...
"CallbackPath": "/signin-azuread-oidc"
}
````
your **Redirect URI** of your application in azure portal must be <u>with domain</u> like `https://localhost:44320/signin-azuread-oidc`, not only `/signin-azuread-oidc`.
* Help! I keep getting ***AADSTS700051: The response_type 'token' is not enabled for the application.*** error!
* This error occurs when you request **token** (access token) along with **id_token** without enabling Access tokens on Azure portal app registrations. Simply tick **Access tokens** checkbox located on top of ID tokens to be able to request token aswell.
* Help! I keep getting ***AADSTS7000218: The request body must contain the following parameter: 'client_assertion' or 'client_secret*** error!
* This error occurs when you request **code** along with **id_token**. You need to add **client secret** on azure portal app registrations, under **Certificates & secrets** menu. Afterwards, you need to add openid configuration option like:
````csharp
options.ClientSecret = "Value of your secret on azure portal";
````
* How can I **debug/watch** which claims I get before they get mapped?
* You can add a simple event under openid configuration to debug before mapping like:
````csharp
options.Events.OnTokenValidated = (async context =>
{
var claimsFromOidcProvider = context.Principal.Claims.ToList();
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).

112
docs/en/How-To/Customize-Login-Page-MVC.md

@ -1,112 +0,0 @@
# 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.
This document explains how to customize the login page for your own application.
## Create a Login PageModel
Create a new class inheriting from the [LoginModel](https://github.com/abpframework/abp/blob/037ef9abe024c03c1f89ab6c933710bcfe3f5c93/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs) of the Account module.
````csharp
public class CustomLoginModel : LoginModel
{
public CustomLoginModel(
Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider schemeProvider,
Microsoft.Extensions.Options.IOptions<Volo.Abp.Account.Web.AbpAccountOptions> accountOptions)
: base(schemeProvider, accountOptions)
{
}
}
````
> 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.
Then you can override any method you need and add new methods and properties needed by the UI.
## Overriding the Login Page UI
Create folder named **Account** under **Pages** directory and create a **Login.cshtml** under this folder. It will automatically override the `Login.cshtml` file defined in the Account Module.
A good way to customize a page is to copy its source code. [Click here](https://github.com/abpframework/abp/blob/dev/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml) for the source code of the login page. At the time this document has been written, the source code was like below:
````xml
@page
@using Volo.Abp.Account.Settings
@using Volo.Abp.Settings
@model Acme.BookStore.Web.Pages.Account.CustomLoginModel
@inject Volo.Abp.Settings.ISettingProvider SettingProvider
@if (Model.EnableLocalLogin)
{
<div class="card mt-3 shadow-sm rounded">
<div class="card-body p-5">
<h4>@L["Login"]</h4>
@if (await SettingProvider.IsTrueAsync(AccountSettingNames.IsSelfRegistrationEnabled))
{
<strong>
@L["AreYouANewUser"]
<a href="@Url.Page("./Register", new {returnUrl = Model.ReturnUrl, returnUrlHash = Model.ReturnUrlHash})" class="text-decoration-none">@L["Register"]</a>
</strong>
}
<form method="post" class="mt-4">
<input asp-for="ReturnUrl" />
<input asp-for="ReturnUrlHash" />
<div class="form-group">
<label asp-for="LoginInput.UserNameOrEmailAddress"></label>
<input asp-for="LoginInput.UserNameOrEmailAddress" class="form-control" />
<span asp-validation-for="LoginInput.UserNameOrEmailAddress" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="LoginInput.Password"></label>
<input asp-for="LoginInput.Password" class="form-control" />
<span asp-validation-for="LoginInput.Password" class="text-danger"></span>
</div>
<div class="form-check">
<label asp-for="LoginInput.RememberMe" class="form-check-label">
<input asp-for="LoginInput.RememberMe" class="form-check-input" />
@Html.DisplayNameFor(m => m.LoginInput.RememberMe)
</label>
</div>
<abp-button type="submit" button-type="Primary" name="Action" value="Login" class="btn-block btn-lg mt-3">@L["Login"]</abp-button>
</form>
</div>
<div class="card-footer text-center border-0">
<abp-button type="button" button-type="Link" name="Action" value="Cancel" class="px-2 py-0">@L["Cancel"]</abp-button> @* TODO: Only show if identity server is used *@
</div>
</div>
}
@if (Model.VisibleExternalProviders.Any())
{
<div class="col-md-6">
<h4>@L["UseAnotherServiceToLogIn"]</h4>
<form asp-page="./Login" asp-page-handler="ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" asp-route-returnUrlHash="@Model.ReturnUrlHash" method="post">
<input asp-for="ReturnUrl" />
<input asp-for="ReturnUrlHash" />
@foreach (var provider in Model.VisibleExternalProviders)
{
<button type="submit" class="btn btn-primary" name="provider" value="@provider.AuthenticationScheme" title="@L["GivenTenantIsNotAvailable", provider.DisplayName]">@provider.DisplayName</button>
}
</form>
</div>
}
@if (!Model.EnableLocalLogin && !Model.VisibleExternalProviders.Any())
{
<div class="alert alert-warning">
<strong>@L["InvalidLoginRequest"]</strong>
@L["ThereAreNoLoginSchemesConfiguredForThisClient"]
</div>
}
````
Just changed the `@model` to `Acme.BookStore.Web.Pages.Account.CustomLoginModel` to use the customized `PageModel` class. You can change it however your application needs.
## The Source Code
You can find the source code of the completed example [here](https://github.com/abpframework/abp-samples/tree/master/aspnet-core/Authentication-Customization).
## See Also
* [ASP.NET Core (MVC / Razor Pages) User Interface Customization Guide](../UI/AspNetCore/Customization-User-Interface.md).

101
docs/en/How-To/Customize-SignIn-Manager.md

@ -1,101 +0,0 @@
# 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)).
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.
This document explains how to customize the SignIn Manager for your own application.
## Create a CustomSignInManager
Create a new class inheriting the [SignInMager](https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs) of Microsoft Identity package.
````csharp
public class CustomSignInManager : Microsoft.AspNetCore.Identity.SignInManager<Volo.Abp.Identity.IdentityUser>
{
public CustomSignInManager(
Microsoft.AspNetCore.Identity.UserManager<Volo.Abp.Identity.IdentityUser> userManager,
Microsoft.AspNetCore.Http.IHttpContextAccessor contextAccessor,
Microsoft.AspNetCore.Identity.IUserClaimsPrincipalFactory<Volo.Abp.Identity.IdentityUser> claimsFactory,
Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Identity.IdentityOptions> optionsAccessor,
Microsoft.Extensions.Logging.ILogger<Microsoft.AspNetCore.Identity.SignInManager<Volo.Abp.Identity.IdentityUser>> logger,
Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider schemes,
Microsoft.AspNetCore.Identity.IUserConfirmation<Volo.Abp.Identity.IdentityUser> confirmation)
: base(userManager, contextAccessor, claimsFactory, optionsAccessor, logger, schemes, confirmation)
{
}
}
````
> It is important to use **Volo.Abp.Identity.IdentityUser** type for SignInManager to inherit, not the AppUser of your application.
Afterwards you can override any of the SignIn Manager methods you need and add new methods and properties needed for your authentication or registration flow.
## Overriding the GetExternalLoginInfoAsync Method
In this case we'll be overriding the `GetExternalLoginInfoAsync` method which is invoked when a third party authentication is implemented.
A good way to override a method is copying its [source code](https://github.com/dotnet/aspnetcore/blob/c56aa320c32ee5429d60647782c91d53ac765865/src/Identity/Core/src/SignInManager.cs#L638-L674). In this case, we will be using a minorly modified version of the source code which explicitly shows the namespaces of the methods and properties to help better understanding of the concept.
````csharp
public override async Task<Microsoft.AspNetCore.Identity.ExternalLoginInfo> GetExternalLoginInfoAsync(string expectedXsrf = null)
{
var auth = await Context.AuthenticateAsync(Microsoft.AspNetCore.Identity.IdentityConstants.ExternalScheme);
var items = auth?.Properties?.Items;
if (auth?.Principal == null || items == null || !items.ContainsKey(LoginProviderKey))
{
return null;
}
if (expectedXsrf != null)
{
if (!items.ContainsKey(XsrfKey))
{
return null;
}
var userId = items[XsrfKey] as string;
if (userId != expectedXsrf)
{
return null;
}
}
var providerKey = auth.Principal.FindFirstValue(ClaimTypes.NameIdentifier);
var provider = items[LoginProviderKey] as string;
if (providerKey == null || provider == null)
{
return null;
}
var providerDisplayName = (await GetExternalAuthenticationSchemesAsync()).FirstOrDefault(p => p.Name == provider)?.DisplayName
?? provider;
return new Microsoft.AspNetCore.Identity.ExternalLoginInfo(auth.Principal, provider, providerKey, providerDisplayName)
{
AuthenticationTokens = auth.Properties.GetTokens()
};
}
````
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).
## Register to Dependency Injection
Registering `CustomSignInManager` should be done with adding **AddSignInManager** extension method of the [IdentityBuilderExtensions](https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/IdentityBuilderExtensions.cs) of the [IdentityBuilder](https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Extensions.Core/src/IdentityBuilder.cs).
Inside your `.Web` project, locate the `YourProjectNameWebModule` and add the following code under the `PreConfigureServices` method to replace the old `SignInManager` with your customized one:
````csharp
PreConfigure<IdentityBuilder>(identityBuilder =>
{
identityBuilder.AddSignInManager<CustomSignInManager>();
});
````
## The Source Code
You can find the source code of the completed example [here](https://github.com/abpframework/abp-samples/tree/master/aspnet-core/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).

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. * **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. * 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. > 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 ## 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: Instead of ``options.TenantResolvers.Insert(1, new DomainTenantResolveContributor("{0}.mydomain.com"));`` you can use this shortcut:

34
docs/en/Samples/Index.md

@ -15,17 +15,12 @@ A complete solution to demonstrate how to build systems based on the microservic
A simple CRUD application to show basic principles of developing an application with the ABP Framework. The same sample was implemented with different technologies: A simple CRUD application to show basic principles of developing an application with the ABP Framework. The same sample was implemented with different technologies:
* **Book Store: Razor Pages UI & Entity Framework Core** * **Book Store: Razor Pages UI & Entity Framework Core**
* [Tutorial](https://docs.abp.io/en/abp/latest/Tutorials/Part-1?UI=MVC&DB=EF)
* [Tutorial](https://docs.abp.io/en/abp/latest/Tutorials/Part-1?UI=MVC) * [Source code](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore)
* [Source code](https://github.com/abpframework/abp-samples/tree/master/BookStore)
* **Book Store: Angular UI & MongoDB** * **Book Store: Angular UI & MongoDB**
* [Tutorial](https://docs.abp.io/en/abp/latest/Tutorials/Part-1?UI=NG&DB=Mongo)
* [Tutorial](https://docs.abp.io/en/abp/latest/Tutorials/Part-1?UI=NG)
* [Source code](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb) * [Source code](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb)
* **Book Store: Modular application (Razor Pages UI & EF Core)** * **Book Store: Modular application (Razor Pages UI & EF Core)**
* [Source code](https://github.com/abpframework/abp-samples/tree/master/BookStore-Modular) * [Source code](https://github.com/abpframework/abp-samples/tree/master/BookStore-Modular)
While there is no Razor Pages & MongoDB combination, you can check both documents to understand it since DB & UI selection don't effect each other. While there is no Razor Pages & MongoDB combination, you can check both documents to understand it since DB & UI selection don't effect each other.
@ -33,11 +28,14 @@ While there is no Razor Pages & MongoDB combination, you can check both document
### Other Samples ### Other Samples
* **Entity Framework Migrations**: A solution to demonstrate how to split your application into multiple databases each database contains different modules. * **Entity Framework Migrations**: A solution to demonstrate how to split your application into multiple databases each database contains different modules.
* [Source code](https://github.com/abpframework/abp-samples/tree/master/DashboardDemo) * [Source code](https://github.com/abpframework/abp-samples/tree/master/EfCoreMigrationDemo)
* [EF Core database migrations document](../Entity-Framework-Core-Migrations.md) * [EF Core database migrations document](../Entity-Framework-Core-Migrations.md)
* **SignalR Demo**: A simple chat application that allows to send and receive messages among authenticated users. * **SignalR Demo**: A simple chat application that allows to send and receive messages among authenticated users.
* [Source code](https://github.com/abpframework/abp-samples/tree/master/SignalRDemo) * [Source code](https://github.com/abpframework/abp-samples/tree/master/SignalRDemo)
* [SignalR Integration document](../SignalR-Integration.md) * [SignalR Integration document](../SignalR-Integration.md)
* **Real Time Messaging In A Distributed Architecture** (using SingalR & RabbitMQ)
* [Source code](https://github.com/abpframework/abp-samples/tree/master/SignalRTieredDemo)
* [Article](https://community.abp.io/articles/real-time-messaging-in-a-distributed-architecture-using-abp-framework-singalr-rabbitmq-daf47e17)
* **Dashboard Demo**: A simple application to show how to use the widget system for the ASP.NET Core MVC UI. * **Dashboard Demo**: A simple application to show how to use the widget system for the ASP.NET Core MVC UI.
* [Source code](https://github.com/abpframework/abp-samples/tree/master/DashboardDemo) * [Source code](https://github.com/abpframework/abp-samples/tree/master/DashboardDemo)
* [Widget documentation](../UI/AspNetCore/Widgets.md) * [Widget documentation](../UI/AspNetCore/Widgets.md)
@ -50,15 +48,17 @@ While there is no Razor Pages & MongoDB combination, you can check both document
* [Text templating documentation](../Text-Templating.md) * [Text templating documentation](../Text-Templating.md)
* **Stored Procedure Demo**: Demonstrates how to use stored procedures, database views and functions with best practices. * **Stored Procedure Demo**: Demonstrates how to use stored procedures, database views and functions with best practices.
* [Source code](https://github.com/abpframework/abp-samples/tree/master/StoredProcedureDemo) * [Source code](https://github.com/abpframework/abp-samples/tree/master/StoredProcedureDemo)
* **Passwordless Authentication**: Shows how to add a custom token provider to authenticate a user with a link, instead of entering a password.
* [Source code](https://github.com/abpframework/abp-samples/tree/master/PasswordlessAuthentication)
* [Article](https://community.abp.io/articles/implementing-passwordless-authentication-with-asp.net-core-identity-c25l8koj)
* **Authentication Customization**: A solution to show how to customize the authentication for ASP.NET Core MVC / Razor Pages applications. * **Authentication Customization**: A solution to show how to customize the authentication for ASP.NET Core MVC / Razor Pages applications.
* [Source code](https://github.com/abpframework/abp-samples/tree/master/aspnet-core/Authentication-Customization) * [Source code](https://github.com/abpframework/abp-samples/tree/master/Authentication-Customization)
* Related "[How To](../How-To/Index.md)" documents: * Related articles:
* [Azure Active Directory Authentication](../How-To/Azure-Active-Directory-Authentication-MVC.md) * [Azure Active Directory Authentication](https://community.abp.io/articles/how-to-use-the-azure-active-directory-authentication-for-mvc-razor-page-applications-4603b9cf)
* [Customize the Login Page](../How-To/Customize-Login-Page-MVC.md) * [Customize the Login Page](https://community.abp.io/articles/how-to-customize-the-login-page-for-mvc-razor-page-applications-9a40f3cd)
* [Customize the SignIn Manager](../How-To/Customize-SignIn-Manager.md) * [Customize the SignIn Manager](https://community.abp.io/articles/how-to-customize-the-signin-manager-3e858753)
* **GRPC Demo**: Shows how to add a gRPC service to an ABP Framework based web application and consume it from a console application.
* [Source code](https://github.com/abpframework/abp-samples/tree/master/GrpcDemo)
* **Empty ASP.NET Core Application**: The most basic ASP.NET Core application with the ABP Framework installed. * **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) * [Source code](https://github.com/abpframework/abp-samples/tree/master/BasicAspNetCoreApplication)
* [Documentation](../Getting-Started-AspNetCore-Application.md) * [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)

6
docs/en/Samples/Microservice-Demo.md

@ -43,6 +43,12 @@ To be able to run the solution from source code, following tools should be insta
* [ElasticSearch](https://www.elastic.co/downloads/elasticsearch) 6.6+ * [ElasticSearch](https://www.elastic.co/downloads/elasticsearch) 6.6+
* [Kibana](https://www.elastic.co/downloads/kibana) 6.6+ (optional, recommended to show logs) * [Kibana](https://www.elastic.co/downloads/kibana) 6.6+ (optional, recommended to show logs)
### Running Infrastructure
* Docker-compose is used to run the pre requirements with ease as default. If you don't have it, you can download and start using [Docker for Windows](https://docs.docker.com/docker-for-windows/) from [here](https://docs.docker.com/docker-for-windows/install/) on windows environment.
* Run the command `docker-compose -f docker-compose.infrastructure.yml -f docker-compose.infrastructure.override.yml up -d` at `MicroserviceDemo` directory or run the powershell script `__Run_Infrastructure.ps1` located at `MicroserviceDemo/_run` directory.
* If you don't want to use docker for pre required services and install them on your local development, you need to update `appsettings.json` files of the projects in the MicroserviceDemo solution accordingly.
### Open & Build the Visual Studio Solution ### Open & Build the Visual Studio Solution
* Open the `samples\MicroserviceDemo\MicroserviceDemo.sln` in Visual Studio 2017 (15.9.0+). * Open the `samples\MicroserviceDemo\MicroserviceDemo.sln` in Visual Studio 2017 (15.9.0+).

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. * [Formik](https://github.com/jaredpalmer/formik) is used to build forms.
* [Yup](https://github.com/jquense/yup) is used for form validations. * [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? ## 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 getting started document](../Getting-Started.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 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. `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.

4
docs/en/Startup-Templates/Module.md

@ -149,6 +149,10 @@ The diagram below shows the relation of the applications:
`.Web.Host` project uses OpenId Connect Authentication to get identity and access tokens for the current user from the `.IdentityServer`. Then uses the access token to call the `.HttpApi.Host`. HTTP API server uses bearer token authentication to obtain claims from the access token to authorize the current user. `.Web.Host` project uses OpenId Connect Authentication to get identity and access tokens for the current user from the `.IdentityServer`. Then uses the access token to call the `.HttpApi.Host`. HTTP API server uses bearer token authentication to obtain claims from the access token to authorize the current user.
##### Pre-requirements
* [Redis](https://redis.io/): The applications use Redis as as distributed cache. So, you need to have Redis installed & running.
##### How to Run? ##### How to Run?
You should run the application with the given order: You should run the application with the given order:

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

@ -45,11 +45,19 @@ This tutorial is organized as the following parts;
### Download the Source Code ### 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) * [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) * [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 ## 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). 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 ### 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 ````csharp
namespace Acme.BookStore.Books namespace Acme.BookStore.Books
@ -293,7 +301,7 @@ In this section, you will create an application service to get, create, update a
### BookDto ### 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 ````csharp
using System; using System;
@ -340,7 +348,7 @@ namespace Acme.BookStore
### CreateUpdateBookDto ### 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 ````csharp
using System; using System;
@ -391,7 +399,7 @@ namespace Acme.BookStore
### IBookAppService ### 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 ````csharp
using System; using System;
@ -418,7 +426,7 @@ namespace Acme.BookStore.Books
### BookAppService ### 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 ````csharp
using System; using System;
@ -456,7 +464,7 @@ In a typical ASP.NET Core application, you create **API Controllers** to expose
ABP can [**automagically**](../API/Auto-API-Controllers.md) configures your application services as MVC API Controllers by convention. ABP can [**automagically**](../API/Auto-API-Controllers.md) configures your application services as MVC API Controllers by convention.
#### Swagger UI ### Swagger UI
The startup template is configured to run the [Swagger UI](https://swagger.io/tools/swagger-ui/) using the [Swashbuckle.AspNetCore](https://github.com/domaindrivendev/Swashbuckle.AspNetCore) library. Run the application by pressing `CTRL+F5` and navigate to `https://localhost:<port>/swagger/` on your browser. (Replace `<port>` with your own port number.) The startup template is configured to run the [Swagger UI](https://swagger.io/tools/swagger-ui/) using the [Swashbuckle.AspNetCore](https://github.com/domaindrivendev/Swashbuckle.AspNetCore) library. Run the application by pressing `CTRL+F5` and navigate to `https://localhost:<port>/swagger/` on your browser. (Replace `<port>` with your own port number.)

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

@ -45,11 +45,19 @@ This tutorial is organized as the following parts;
### Download the Source Code ### 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) * [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) * [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"}} {{if UI == "MVC"}}
## Dynamic JavaScript Proxies ## 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 ### 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) * [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) * [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"}} {{if UI == "MVC"}}
## Creating a New Book ## 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 ### 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) * [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) * [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 ## Test Projects in the Solution
This part covers the **server side** tests. There are several 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 ## 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 ````csharp
using System.Threading.Tasks; 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 ### 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) * [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) * [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 ## 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. 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. 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 ````csharp
namespace Acme.BookStore.Permissions 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. 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 ````csharp
using Acme.BookStore.Localization; 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. > **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 ````csharp
using Volo.Abp; 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}" "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. Whenever you throw an `AuthorAlreadyExistsException`, the end use will see a nice error message on the UI.
## IAuthorRepository ## 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. 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 ````csharp
namespace Acme.BookStore.Permissions 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 ## 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: 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

11
docs/en/UI/Angular/Config-State.md

@ -127,6 +127,17 @@ const defaultLang = this.config.getSetting("Abp.Localization.DefaultLanguage");
// 'en' // 'en'
``` ```
### How to Get a Specific Feature From the Store
You can use the `getFeature` method of `ConfigStateService` to get a specific feature from the configuration state. Here is an example:
```js
// this.config is instance of ConfigStateService
const isChatEnabled = this.config.getFeature("Chat.Enable");
// 'en'
```
### How to Get a Specific Permission From the Store ### How to Get a Specific Permission From the Store
You can use the `getGrantedPolicy` method of `ConfigStateService` to get a specific permission from the configuration state. For that, you should pass a policy key as parameter to the method. You can use the `getGrantedPolicy` method of `ConfigStateService` to get a specific permission from the configuration state. For that, you should pass a policy key as parameter to the method.

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) ![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 ```js
// app.module.ts // app.module.ts
@ -209,7 +209,7 @@ If you see an error like this, you should pass the `cultureNameToLocaleFileNameM
// other imports // other imports
CoreModule.forRoot({ CoreModule.forRoot({
// other options // other options
cultureNameToLocaleFileNameMapping: { cultureNameLocaleFileMap: {
"DotnetCultureName": "AngularLocaleFileName", "DotnetCultureName": "AngularLocaleFileName",
"pt-BR": "pt" // example "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 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? ## 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 ### How to Display a Toast Overlay
```js ```js
this.toast.success('Message', 'Title'); this.toaster.success('Message', 'Title');
``` ```
- The `ToasterService` methods accept three parameters that are `message`, `title`, and `options`. - 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: The open toast overlay can be removed manually via the `remove` method by passing the `id` of toast:
```js ```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: The all open toasts can be removed manually via the `clear` method:
```js ```js
this.toast.clear(); this.toaster.clear();
``` ```
## API ## 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

48
docs/en/docs-nav.json

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

12
docs/zh-Hans/API/Application-Configuration.md

@ -2,12 +2,12 @@
ABP框架提供了一个预构建的标准端点,其中包含一些有关应用程序/服务的有用信息. 这里是此端点的一些基本信息的列表: ABP框架提供了一个预构建的标准端点,其中包含一些有关应用程序/服务的有用信息. 这里是此端点的一些基本信息的列表:
* [本地化](Localization.md)值, 支持应用程序的当前语言. * [本地化](../Localization.md)值, 支持应用程序的当前语言.
* 当前用户可用和已授予的[策略](Authorization.md)(权限). * 当前用户可用和已授予的[策略](../Authorization.md)(权限).
* 当前用户的[设置](Settings.md)值. * 当前用户的[设置](../Settings.md)值.
* 关于[当前用户](CurrentUser.md)的信息 (如 id 和用户名). * 关于[当前用户](../CurrentUser.md)的信息 (如 id 和用户名).
* 关于当前[租户](Multi-Tenancy.md)的信息 (如 id 和名称). * 关于当前[租户](../Multi-Tenancy.md)的信息 (如 id 和名称).
* 当前用户的[时区](Timing.md)信息和应用程序的[时钟](Timing.md)类型. * 当前用户的[时区](../Timing.md)信息和应用程序的[时钟](../Timing.md)类型.
## HTTP API ## HTTP API

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)文档了解如何使用用户机密.

2
docs/zh-Hans/Authorization.md

@ -285,8 +285,6 @@ public async Task CreateAsync(CreateAuthorDto input)
abp.auth.isGranted('MyPermissionName'); abp.auth.isGranted('MyPermissionName');
```` ````
参阅 [abp.auth](AspNetCore/JavaScript-API/Auth.md) API 文档了解详情.
## 权限管理 ## 权限管理
通常权限管理是管理员用户使用权限管理模态框进行授权: 通常权限管理是管理员用户使用权限管理模态框进行授权:

5
docs/zh-Hans/Background-Jobs-Hangfire.md

@ -66,9 +66,7 @@ public class YourModule : AbpModule
} }
```` ````
1. 我们需要在 `Module` 类的 `OnApplicationInitialization` 方法添加 `UseHangfireServer` 方法调用. 1. 如果你想要使用Hangfire的面板,你可以在 `Module` 类的 `OnApplicationInitialization` 方法添加: `UseHangfireDashboard`
如果你想要使用Hangfire的面板,你可以使用: `UseHangfireDashboard`
````csharp ````csharp
public override void OnApplicationInitialization(ApplicationInitializationContext context) public override void OnApplicationInitialization(ApplicationInitializationContext context)
@ -77,7 +75,6 @@ public class YourModule : AbpModule
// ... others // ... others
app.UseHangfireServer();
app.UseHangfireDashboard(); app.UseHangfireDashboard();
} }

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#配置). 参阅[配置](Background-Jobs-Quartz.md#配置).

2
docs/zh-Hans/Best-Practices/Application-Services.md

@ -203,7 +203,7 @@ Task<int> VoteAsync(Guid id, VoteType type);
#### 额外的属性 #### 额外的属性
* **推荐** 使用 `MapExtraPropertiesTo` 扩展方法 ([参阅](Object-Extensions.md)) 或配置对象映射 (`MapExtraProperties`) 以允许应用开发人员能够扩展对象和服务. * **推荐** 使用 `MapExtraPropertiesTo` 扩展方法 ([参阅](../Object-Extensions.md)) 或配置对象映射 (`MapExtraProperties`) 以允许应用开发人员能够扩展对象和服务.
#### 操作/删除 实体 #### 操作/删除 实体

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

@ -0,0 +1,77 @@
# BLOB Storing Aliyun提供程序
BLOB存储Aliyun提供程序可以将BLOB存储在[Aliyun Blob storage](https://help.aliyun.com/product/31815.html)中.
> 阅读[BLOB存储文档](Blob-Storing.md)了解如何使用BLOB存储系统. 本文档仅介绍如何为容器配置Aliyun提供程序.
## 安装
使用ABP CLI添加[Volo.Abp.BlobStoring.Aliyun](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Aliyun)NuGet包到你的项目:
* 安装 [ABP CLI](https://docs.abp.io/en/abp/latest/CLI), 如果你还没有安装.
* 在要添加 `Volo.Abp.BlobStoring.Aliyun` 包的 `.csproj` 文件目录打开命令行.
* 运行 `Volo.Abp.BlobStoring.Aliyun` 命令.
如果要手动安装,在你的项目中安装 `Volo.Abp.BlobStoring.Aliyun` NuGet包然后将`[DependsOn(typeof(AbpBlobStoringAliyunModule))]`添加到项目内的[ABP模块](Module-Development-Basics.md)类中.
## 配置
如同[BLOB存储文档](Blob-Storing.md)所述,配置是在[模块](Module-Development-Basics.md)类的 `ConfigureServices` 方法完成的.
**示例: 配置为默认使用Aliyun存储提供程序**
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.ConfigureDefault(container =>
{
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;
});
});
});
````
> 参阅[BLOB存储文档](Blob-Storing.md) 学习如何为指定容器配置提供程序.
### 选项
* **AccessKeyId** ([NotNull]string): 云账号AccessKey是访问阿里云API的密钥,具有该账户完全的权限,请你务必妥善保管!强烈建议遵循[阿里云安全最佳实践](https://help.aliyun.com/document_detail/102600.html),使用RAM子用户AccessKey来进行API调用.
* **AccessKeySecret** ([NotNull]string): 同上.
* **Endpoint** ([NotNull]string): Endpoint表示OSS对外服务的访问域名. [访问域名和数据中心](https://help.aliyun.com/document_detail/31837.html)
* **UseSecurityTokenService** (bool): 是否使用STS临时授权访问OSS,默认false. [STS临时授权访问OSS](https://help.aliyun.com/document_detail/100624.html)
* **RegionId** (string): STS服务的接入地址,每个地址的功能都相同,请尽量在同地域进行调用. [接入地址](https://help.aliyun.com/document_detail/66053.html)
* **RoleArn** ([NotNull]string): STS所需角色ARN.
* **RoleSessionName** ([NotNull]string): 用来标识临时访问凭证的名称,建议使用不同的应用程序用户来区分.
* **Policy** (string): 在扮演角色的时候额外添加的权限限制. 请参见[基于RAM Policy的权限控制](https://help.aliyun.com/document_detail/100680.html).
* **DurationSeconds** (int): 设置临时访问凭证的有效期,单位是s,最小为900,最大为3600.
* **ContainerName** (string): 你可以在aliyun中指定容器名称. 如果没有指定它将使用 `BlogContainerName` 属性定义的BLOB容器的名称(请参阅[BLOB存储文档](Blob-Storing.md)). 请注意Aliyun有一些**命名容器的规则**,容器名称必须是有效的DNS名称,[符合以下命名规则](https://help.aliyun.com/knowledge_detail/39668.html):
* 只能包含小写字母,数字和短横线(-)
* 必须以小写字母和数字开头和结尾
* Bucket名称的长度限制在**3**到**63**个字符之间
* **CreateContainerIfNotExists** (bool): 默认值为 `false`, 如果aliyun中不存在容器, `AliyunBlobProvider` 将尝试创建它.
* **TemporaryCredentialsCacheKey** (bool): STS凭证缓存Key,默认Guid.NewGuid().ToString("N").
## Aliyun BLOB 名称计算器
Aliyun BLOB提供程序组织BLOB名称并实现一些约定. 默认情况下BLOB的全名由以下规则确定:
* 如果当前租户为 `null`(或容器禁用多租户 - 请参阅[BLOB存储文档](Blob-Storing.md) 了解如何禁用容器的多租户),则追加 `host` 字符串.
* 如果当前租户不为 `null`,则追加 `tenants/<tenant-id>` 字符串.
* 追加 BLOB 名称.
## 其他服务
* `AliyunBlobProvider` 是实现Aliyun BLOB存储提供程序的主要服务,如果你想要通过[依赖注入](Dependency-Injection.md)覆盖/替换它(不要替换 `IBlobProvider` 接口,而是替换 `AliyunBlobProvider` 类).
* `IAliyunBlobNameCalculator` 服务用于计算文件路径. 默认实现是 `DefaultAliyunBlobNameCalculator`. 如果你想自定义文件路径计算,可以替换/覆盖它.
* `IOssClientFactory` 服务用于生成OSS客户端. 默认实现是 `DefaultOssClientFactory` . 如果你想自定义OSS客户端生成,可以替换/覆盖它.

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

@ -0,0 +1,84 @@
# BLOB Storing Aws提供程序
BLOB存储Aws提供程序可以将BLOB存储在[Amazon Simple Storage Service](https://aws.amazon.com/cn/s3/)中.
> 阅读[BLOB存储文档](Blob-Storing.md)了解如何使用BLOB存储系统. 本文档仅介绍如何为容器配置Aws提供程序.
## 安装
使用ABP CLI添加[Volo.Abp.BlobStoring.Aws](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Aws)NuGet包到你的项目:
* 安装 [ABP CLI](https://docs.abp.io/en/abp/latest/CLI), 如果你还没有安装.
* 在要添加 `Volo.Abp.BlobStoring.Aws` 包的 `.csproj` 文件目录打开命令行.
* 运行 `Volo.Abp.BlobStoring.Aws` 命令.
如果要手动安装,在你的项目中安装 `Volo.Abp.BlobStoring.Aws` NuGet包然后将`[DependsOn(typeof(AbpBlobStoringAwsModule))]`添加到项目内的[ABP模块](Module-Development-Basics.md)类中.
## 配置
如同[BLOB存储文档](Blob-Storing.md)所述,配置是在[模块](Module-Development-Basics.md)类的 `ConfigureServices` 方法完成的.
**示例: 配置为默认使用Aws存储提供程序**
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.ConfigureDefault(container =>
{
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;
});
});
});
````
> 参阅[BLOB存储文档](Blob-Storing.md) 学习如何为指定容器配置提供程序.
### 选项
* **AccessKeyId** (string): AWS Access Key ID.
* **SecretAccessKey** (string): AWS Secret Access Key.
* **UseCredentials** (bool): 使用[本地凭证](https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/dev/AuthUsingAcctOrUserCredentials.html)访问AWS服务,默认: `false`.
* **UseTemporaryCredentials** (bool): 使用[临时凭证](https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/dev/AuthUsingTempSessionToken.html)访问AWS服务,默认: `false`.
* **UseTemporaryFederatedCredentials** (bool): 使用[联合身份用户临时凭证](https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/dev/AuthUsingTempFederationToken.html)访问AWS服务, 默认: `false`.
* **ProfileName** (string): [本地凭证配置文件](https://docs.aws.amazon.com/zh_cn/sdk-for-net/v3/developer-guide/net-dg-config-creds.html)名称.
* **ProfilesLocation** (string): 本地配置文件位置.
* **Region** (string): 服务的地区名称.
* **Policy** (string): JSON格式的IAM策略.
* **DurationSeconds** (int): 设置临时访问凭证的有效期,单位是s,最小为900,最大为129600.
* **ContainerName** (string): 你可以在Aws中指定容器名称. 如果没有指定它将使用 `BlogContainerName` 属性定义的BLOB容器的名称(请参阅[BLOB存储文档](Blob-Storing.md)). 请注意Aws有一些**命名容器的规则**,容器名称必须是有效的DNS名称,[符合以下命名规则](https://docs.aws.amazon.com/AmazonS3/latest/dev/BucketRestrictions.html):
* Bucket名称必须介于 3 到 63 个字符之间.
* Bucket名称只能由小写字母、数字、句点 (.) 和连字符 (-) 组成.
* Bucket名称必须以字母或数字开头和结尾.
* Bucket名称不能是ip (例如, 192.168.5.4).
* Bucket名称不能以 **xn--** 开头, (对于2020年2月以后创建的 Bucket).
* Bucket名称在分区中必须唯一 .
* Bucket 与 Amazon S3 Transfer Acceleration 一起使用时名称中不能有句点 (.).
* **CreateContainerIfNotExists** (bool): 默认值为 `false`, 如果Aws中不存在容器, `AwsBlobProvider` 将尝试创建它.
## Aws BLOB 名称计算器
Aws BLOB提供程序组织BLOB名称并实现一些约定. 默认情况下BLOB的全名由以下规则确定:
* 如果当前租户为 `null`(或容器禁用多租户 - 请参阅[BLOB存储文档](Blob-Storing.md) 了解如何禁用容器的多租户),则追加 `host` 字符串.
* 如果当前租户不为 `null`,则追加 `tenants/<tenant-id>` 字符串.
* 追加 BLOB 名称.
## 其他服务
* `AwsBlobProvider` 是实现Aws BLOB存储提供程序的主要服务,如果你想要通过[依赖注入](Dependency-Injection.md)覆盖/替换它(不要替换 `IBlobProvider` 接口,而是替换 `AwsBlobProvider` 类).
* `IAwsBlobNameCalculator` 服务用于计算文件路径. 默认实现是 `DefaultAwsBlobNameCalculator`. 如果你想自定义文件路径计算,可以替换/覆盖它.
* `IAmazonS3ClientFactory` 服务用于生成AWS S3客户端. 默认实现是 `DefaultAmazonS3ClientFactory` . 如果你想自定义AWS S3客户端生成,可以替换/覆盖它.

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

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

2
docs/zh-Hans/Blob-Storing-Database.md

@ -27,7 +27,7 @@ abp add-module Volo.Abp.BlobStoring.Database
这里是此提供程序定义的所有包: 这里是此提供程序定义的所有包:
* [Volo.Abp.BlobStoring.Database.Domain.Shared](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Domain.Shared) * [Volo.Abp.BlobStoring.Database.Domain.Shared](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Database.Domain.Shared)
* [Volo.Abp.BlobStoring.Database.Domain](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Database.Domain) * [Volo.Abp.BlobStoring.Database.Domain](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Database.Domain)
* [Volo.Abp.BlobStoring.Database.EntityFrameworkCore](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Database.EntityFrameworkCore) * [Volo.Abp.BlobStoring.Database.EntityFrameworkCore](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Database.EntityFrameworkCore)
* [Volo.Abp.BlobStoring.Database.MongoDB](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Database.MongoDB) * [Volo.Abp.BlobStoring.Database.MongoDB](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Database.MongoDB)

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

@ -0,0 +1,69 @@
# BLOB Storing Minio 提供程序
BLOB Storing Minio提供程序帮助你存储对象到 [MinIO Object storage](https://min.io/),
> 阅读[BLOB存储文档](Blob-Storing.md)了解如何使用BLOB存储系统, 本文档仅介绍如何为容器配置Minio提供程序,
## 安装
使用 ABP CLI 来安装 [Volo.Abp.BlobStoring.Minio](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Minio) NuGet 包到你的项目:
* 如果你没有安装ABP CLI,请先安装 [ABP CLI](https://docs.abp.io/en/abp/latest/CLI),
* 在要添加 `Volo.Abp.BlobStoring.Minio` 包的 `.csproj` 文件目录打开命令行,
* 执行 `abp add-package Volo.Abp.BlobStoring.Minio` 命令,
如果你要手动安装, 通过NuGet安装 [Volo.Abp.BlobStoring.Minio](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Minio) 到你的项目,然后添加 `[DependsOn(typeof(AbpBlobStoringMinioModule))]` 特性到你的 [ABP module](Module-Development-Basics.md) 类上,
## 配置
配置在你的[module](Module-Development-Basics.md)类中的`ConfigureServices`方法中完成,
**例: 配置使用Minio存储**
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.ConfigureDefault(container =>
{
container.UseMinio(minio =>
{
minio.EndPoint = "你的 minio endPoint";
minio.AccessKey = "你的 minio accessKey";
minio.SecretKey = "你的 minio secretKey";
minio.BucketName = "你的 minio bucketName";
});
});
});
````
> 参阅[BLOB存储文档](Blob-Storing.md) 学习如何为指定容器配置提供程序,
### 选项
* **EndPoint** (string): 你的Minio对象存储服务的URL, 查看文档:https://docs.min.io/docs/dotnet-client-quickstart-guide.html
* **AccessKey** (string): Access key是唯一标识你的账户的用户ID,
* **SecretKey** (string): Access key是唯一标识你的账户的用户ID
* **BucketName** (string):你可以指定bucket名称,如果没有指定,将使用 `BlogContainerName` 属性定义的BLOB容器的名称(查阅[BLOB storing document](Blob-Storing.md)),MinIO完全兼容S3标准,所以有一些 **bucket命名规则**,必须符合[规则](https://docs.aws.amazon.com/AmazonS3/latest/dev/BucketRestrictions.html):
* Bucket名称必须介于 3 到 63 个字符之间.
* Bucket名称只能由小写字母、数字、句点 (.) 和连字符 (-) 组成.
* Bucket名称必须以字母或数字开头和结尾.
* Bucket名称不能是ip (例如, 192.168.5.4).
* Bucket名称不能以 **xn--** 开头, (对于2020年2月以后创建的 Bucket).
* Bucket名称在分区中必须唯一 .
* Bucket 与 Amazon S3 Transfer Acceleration 一起使用时名称中不能有句点 (.).
* **WithSSL** (bool): 默认 `false`,代表使用HTTPS,
* **CreateContainerIfNotExists** (bool): 默认 `false`,如果不存在bucket, `MinioBlobProvider` 将会创建一个,
## Minio BLOB 名称计算器
默认情况下BLOB的全名由以下规则确定:
* 如果当前租户为 `null`(或容器禁用多租户 - 请参阅[BLOB存储文档](Blob-Storing.md) 了解如何禁用容器的多租户),则追加 `host` 字符串,
* 如果当前租户不为 `null`,则追加 `tenants/<tenant-id>` 字符串,
* 追加 BLOB 名称,
## 其他服务
* `MinioBlobProvider` 是实现Minio BLOB存储提供程序的主要服务,如果你想要通过[依赖注入](Dependency-Injection.md)覆盖/替换它(不要替换 `IBlobProvider` 接口,而是替换 `MinioBlobProvider` 类).
* `IMinioBlobNameCalculator` 服务用于计算文件路径. 默认实现是 `DefaultMinioBlobNameCalculator`. 如果你想自定义文件路径计算,可以替换/覆盖它.

3
docs/zh-Hans/Blob-Storing.md

@ -19,6 +19,9 @@ ABP框架已经有以下存储提供程序的实现;
* [File System](Blob-Storing-File-System.md):将BLOB作为标准文件存储在本地文件系统的文件夹中. * [File System](Blob-Storing-File-System.md):将BLOB作为标准文件存储在本地文件系统的文件夹中.
* [Database](Blob-Storing-Database.md): 将BLOB存储在数据库中. * [Database](Blob-Storing-Database.md): 将BLOB存储在数据库中.
* [Azure](Blob-Storing-Azure.md): 将BLOG存储在 [Azure BLOB storage](https://azure.microsoft.com/en-us/services/storage/blobs/)中. * [Azure](Blob-Storing-Azure.md): 将BLOG存储在 [Azure BLOB storage](https://azure.microsoft.com/en-us/services/storage/blobs/)中.
* [Aliyun](Blob-Storing-Aliyun.md): 将BLOB存储在[Aliyun Storage Service](https://help.aliyun.com/product/31815.html)中.
* [Ninio](Blob-Storing-Minio.md): 将BLOB存储在[MinIO Object storage](https://min.io/)中.
* [Aws](Blob-Storing-Aws.md): 将BLOB存储在[Amazon Simple Storage Service](https://min.io/)中.
以后会实现更多的提供程序,你可以为自己喜欢的提供程序创建[请求](https://github.com/abpframework/abp/issues/new),或者你也可以[自己实现](Blob-Storing-Custom-Provider.md)它并[贡献](Contribution/Index.md)到ABP框架. 以后会实现更多的提供程序,你可以为自己喜欢的提供程序创建[请求](https://github.com/abpframework/abp/issues/new),或者你也可以[自己实现](Blob-Storing-Custom-Provider.md)它并[贡献](Contribution/Index.md)到ABP框架.

177
docs/zh-Hans/Blog-Posts/2020-07-01 v3_0_Release/Post.md

@ -0,0 +1,177 @@
# ABP框架 v3.0 已发布
我们很高兴地宣布,**ABP框架**和**ABP商业版**3.0版已经发布.与常规的2周发布一个版本不同的是, 这个版本用了4周的时间.关闭了**119个[issue](https://github.com/abpframework/abp/issues?q=is%3Aopen+is%3Aissue+milestone%3A3.0)**,合并了**89个[pull request](https://github.com/abpframework/abp/pulls?q=is%3Aopen+is%3Apr+milestone%3A3.0)** 和主框架[仓库](https://github.com/abpframework/abp)中的**798次提交**.
由于这是一个**主要版本**,它也包括了一些**重大更改**.不要害怕,这些变化都容易对应,并且下面会详细解释.
> 详细更改日志请查看[GitHub的发布说明](https://github.com/abpframework/abp/releases/tag/3.0.0).
## ABP框架3.0有哪些新增内容?
这篇文章将只包括重要功能/更改.你可在[GitHub的发布说明](https://github.com/abpframework/abp/releases/tag/3.0.0)上看到所有的更改.
### Angular 10!
Angular 10刚刚[发布](https://blog.angular.io/version-10-of-angular-now-available-78960babd41),我们就立即迁移了[启动模板](https://docs.abp.io/en/abp/latest/Startup-Templates/Application)到Angular 10!所以,当你以Angular UI[创建一个新的解决方案](https://abp.io/get-started)时, 你将获得新Angular的优点.
我们为想要迁移到Angular 10的老版本项目准备了一份[迁移指南](https://github.com/abpframework/abp/blob/dev/docs/en/UI/Angular/Migration-Guide-v3.md).
### Oracle集成包
之前我们为使用Devart库, 基于EF Core for Oracle的应用程序创建了[一个集成包](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore.Oracle.Devart), 因为官方的Oracle EF Core包还未支持EF Core 3.1. 现在它以[beta发布](https://www.nuget.org/packages/Oracle.EntityFrameworkCore/3.19.0-beta1).虽然是beta版,但我们已经创建了它的[集成包](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore.Oracle),这样你就可以在你的应用程序中使用它.
更多细节请查看[文档](https://docs.abp.io/en/abp/latest/Entity-Framework-Core-Oracle).
### Azure BLOB存储提供器
在上一个版本中我们创建了一个[Blob存储系统](https://docs.abp.io/en/abp/latest/Blob-Storing), 包含一个文件系统和数据库存储提供器. 本次发布我们引入了Azure BLOB存储提供器.请查看[文档](https://docs.abp.io/en/abp/latest/Blob-Storing-Azure).
### 分布式缓存批量操作和新的Redis缓存包
ASP.NET Core的[标准IDistributeCache](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed)接口不包含**批量操作**,如使用单独方法/服务器调用设置多个项目. ABP框架引入了新的方法可用于在ABP的`IDistributedCache<T>`接口中进行批量操作:
* GetManyAsync / GetMany
* SetManyAsync / SetMany
然后,我们需要为Redis缓存实现这些新方法并且[必须创建](https://github.com/abpframework/abp/issues/4483)一个Redis集成包用于扩展微软的实现.
这些方法也被ABP框架所使用, 用来缓存某个用户/角色/租户的设置,功能和权限,并且带来了**显著的性能提升**.
更多细节请查看[缓存文档](https://docs.abp.io/en/abp/latest/Caching).
### 为虚拟文件系统支持嵌入文件清单
虚拟文件系统现在支持在项目中使用`GenerateEmbeddedFilesManifest`来添加编译程序集中嵌入资源的**真实的文件/目录结构**.所以,你现在可以访问文件,而没有任何文件名的限制(以前,目录名称中的一些特殊字符,比如`.`在某些情况会有问题)
查看[文档](https://docs.abp.io/en/abp/latest/Virtual-File-System)学习如何利用新系统的优点.
### 新示例
基于社区的需求,我们准备了两个新的示例应用程序:
* [StoredProcedureDemo](https://github.com/abpframework/abp-samples/tree/master/StoredProcedureDemo)演示了如何在自定义仓储库中调用存储过程,视图和函数.
* [OrganizationUnitSample](https://github.com/abpframework/abp-samples/tree/master/OrganizationUnitSample)演示了如何为你的实体使用[Identity模块](https://docs.abp.io/en/abp/latest/Modules/Identity)的组织单元系统.
### DynamicStringLength 和 DynamicMaxLength 属性
当类被用作模型或[DTO](https://docs.abp.io/en/abp/latest/Data-Transfer-Objects)时, 标准的`StringLength`和`MaxLength`数据注解属性用来验证类中的属性是很有用的. 但是, 就像任何其它的属性一样, 长度的值应该是一个**编译时**已知的字面值(常量).
**示例: 使用`StringLength`**
````csharp
public class CreateBookDto
{
public const int MaxNameLength = 128; //常量!
[StringLength(MaxNameLength)]
public string Name { get; set; }
}
````
ABP框架现在有`DynamicStringLength` 和 `DynamicMaxLength`属性允许在**运行时**确定长度.
**示例: 使用`DynamicStringLength`**
````csharp
public class CreateBookDto
{
public static int MaxNameLength { get; set; } = 128;
[DynamicStringLength(typeof(CreateBookDto), nameof(MaxNameLength))]
public string Name { get; set; }
}
````
`DynamicStringLength`通过类的**类型**和这个类的一个静态属性的**名字**来读取最大长度(也有一个最小长度的选项, 就像`StringLength`).
这样就允许你从配置中读取最大值并且在应用程序启动时设置(一般在[模块](https://docs.abp.io/en/abp/latest/Module-Development-Basics)的`PreConfigureServices`方法)
````csharp
CreateBookDto.MaxNameLength = 200;
````
[预建应用程序模块](https://docs.abp.io/en/abp/latest/Modules/Index)使用了这个功能,所以你现在可以覆盖这些模块中定义的属性的最大长度了.
### 自动分布式事件
ABP可以为所有的实体在创建,更新和删除时**自动发布分布式事件**. 这很有用因为在分布式系统中你通常会对这些基础事件感兴趣.
这个功能在v3.0中**已成熟并[文档化了](https://docs.abp.io/en/abp/latest/Distributed-Event-Bus#pre-defined-events)**. 你可以轻松地配置一部分或全部要发布事件的实体.
### IAsyncQueryableExecuter
使用LINQ扩展方法,当你在查询时需要调用`ToListAsync()`,`FirstOrDefaultAsync()`... 不幸的是,这些方法并不是**标准的**LINQ扩展方法.它们定义在[Microsoft.EntityFrameworkCore](https://www.nuget.org/packages/Microsoft.EntityFrameworkCore)包中(或在[MongoDB.Driver](https://www.nuget.org/packages/MongoDB.Driver/),如果你使用MongoDB的话).
所以,如果你想使用异步扩展方法, 你需要依赖这个包.这样做破坏了分层,并且使你的应用程序或领域层依赖于EF Core / MongoDB的包.
`IAsyncQueryableExecuter`是ABP框架定义的服务, 用来**不依赖特定提供器地执行异步查询**(EF Core / MongoDB包).
查看[文档](https://docs.abp.io/en/abp/latest/Repositories#iqueryable-async-operations)来获取细节和了解我们的建议.
### API 文档
现在我们为每次发布ABP框架和模块都公开[API文档](https://docs.abp.io/api-docs/abp/3.0/api/index.html).所以,你可以更容易地查看ABP框架的类.点击[文档](https://docs.abp.io/en/abp/latest/)导航菜单中的**API文档**的链接.
### 包列表
我们[创建了一个页面](http://abp.io/packages)列出所有ABP相关的官方NuGet和NPM包.
### 其他
* 为Identity Server实现了前端通道的服务端客户[登出](https://identityserver4.readthedocs.io/en/latest/topics/signout.html)
* `abp.currentUser`(Angular UI中的`CurrentUser`服务)现在有一个包含当前用户的角色名的`roles`数组.
* 升级所有的Nu​​Get和NPM软件包的依赖.
* 引入了`ReadOnlyAppService`基类(只包含get操作), 做为`CrudAppService`基类(其中有get,create,update和delete操作)的补充.
其他更新请查看[GitHub发行说明](https://github.com/abpframework/abp/releases/tag/3.0.0).
## ABP商业版3.0有哪些新增内容?
除了所有ABP框架的功能以外,ABP商业版同样在本次发布中有一些额外的功能.本节介绍3.0版本中[ABP商业版](https://commercial.abp.io/)的亮点.
### 新的文件管理模块
我们创建了一个用于在应用程序中存储和管理文件的新模块.这个新模块基于[BLOB存储系统](https://docs.abp.io/en/abp/latest/Blob-Storing),因此它可以使用不同的存储提供器来存储文件内容.
**示例截图**
![file-management-ui](file-management-ui.png)
您可以上传,下载和以分层文件夹结构组织文件.它也兼容多租户,你可以为租户限制总文件大小.在未来的版本中,我们将致力于“共享”系统, 用于以一个更可控的方式在用户之间或通过公开链接与你的客户共享文件.
> 文件管理模块是目前仅适用于MVC / Razor Pages UI.我们正工作于Angular UI,它会在未来的版本中发布.
## 重大更改
由于这是一个主要版本,我们重新设计了一些API,并引入了一些"容易解决"的重大更改.
### ABP框架
* 更改了一些预建应用程序模块的**常量**为静态属性, 这样你就可在你的代码中对它们修改. 如果你在属性中使用了这些常量,那么请使用上面介绍的`DynamicStringLength`.
* 更改`ConcurrencyStamp`最大长度为40. 你需要在更新ABP框架后**添加一个数据库迁移**并且更新数据库.
* NPM软件包的依赖关系使用`~`代替`^`,为了更加稳定.
### ABP商业版
* 更改了应用程序标识的文件名.此前,它为每个主题使用了单独的标识文件,如`theme1.png`,`theme1-reverse.png`,`theme2.png`,`theme2-reverse.png`(...`6`).现在,我们只有两个标识文件:`logo-light.png`和`logo-dark.png`.所以,重命名MVC UI的`wwwroot/images/logo/`和Angular UI的`/src/assets/images/logo/`下的标识.
* 我们也增加了ABP商业版的[API文档](https://docs.abp.io/api-docs/commercial/3.0/api/index.html).
> **此外,Angular UI请查看[迁移指南](https://github.com/abpframework/abp/blob/dev/docs/en/UI/Angular/Migration-Guide-v3.md)**.
## 已知问题
* 3.0.0版本有一个分层架构的问题.查看[这个issue](https://github.com/abpframework/abp/pull/4564)为你的应用程序解决它,直到我们发布V3.0.1.
## 关于未来的版本
我们将继续在每两个星期发布一个新的小功能版本.所以,下一个预计发布日期是**2020年7月16日**的**3.1**版本.
在接下来的几个版本中,我们将专注于**Blazor UI**,如[路线图](https://docs.abp.io/en/abp/latest/Road-Map)中承诺的.我们将继续改进文档,创建示例,添加其他新功能和改善.关注[ABP框架Twitter账号](https://twitter.com/abpframework)获取最新消息...
## 彩蛋: 文章!
除了开发我们的产品,我们的团队一直在不断书写各种主题的文章/教程.你可能感兴趣的最新文章:
* [Angular 10有哪些新增内容?](https://volosoft.com/blog/what-is-new-in-angular-10)
* [在分布式架构中使用ABP, SignalR和RabbitMQ构建实时消息系统](https://volosoft.com/blog/RealTime-Messaging-Distributed-Architecture-Abp-SingalR-RabbitMQ)
* [如何在Angular模板中使用Attribute Directives来避免重复](https://volosoft.com/blog/attribute-directives-to-avoid-repetition-in-angular-templates)

BIN
docs/zh-Hans/Blog-Posts/2020-07-01 v3_0_Release/file-management-ui.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

11
docs/zh-Hans/CLI.md

@ -90,9 +90,10 @@ abp new Acme.BookStore
* **`console`**: [Console template](Startup-Templates/Console.md). * **`console`**: [Console template](Startup-Templates/Console.md).
* `--output-folder` 或者 `-o`: 指定输出文件夹,默认是当前目录. * `--output-folder` 或者 `-o`: 指定输出文件夹,默认是当前目录.
* `--version` 或者 `-v`: 指定ABP和模板的版本.它可以是 [release tag](https://github.com/abpframework/abp/releases) 或者 [branch name](https://github.com/abpframework/abp/branches). 如果没有指定,则使用最新版本.大多数情况下,你会希望使用最新的版本. * `--version` 或者 `-v`: 指定ABP和模板的版本.它可以是 [release tag](https://github.com/abpframework/abp/releases) 或者 [branch name](https://github.com/abpframework/abp/branches). 如果没有指定,则使用最新版本.大多数情况下,你会希望使用最新的版本.
* `--template-source` 或者 `-ts`: 指定自定义模板源用于生成项目,可以使用本地源和网络源(例如 `D\localTemplate` 或 `https://<your url>.zip`). * `--preview`: 使用最新的预发行版本 (仅在未指定 `--version` 且最新稳定版本之后至少有一个预发行版时).
* `--template-source` 或者 `-ts`: 指定自定义模板源用于生成项目,可以使用本地源和网络源(例如 `D:\local-templat` 或 `https://.../my-template-file.zip`).
* `--create-solution-folder` 或者 `-csf`: 指定项目是在输出文件夹中的新文件夹中还是直接在输出文件夹中. * `--create-solution-folder` 或者 `-csf`: 指定项目是在输出文件夹中的新文件夹中还是直接在输出文件夹中.
* `--connection-string` 或者 `-cs`: 重写所有 `appsettings.json` 文件的默认连接字符串. 默认连接字符串是 `Server=localhost;Database=MyProjectName;Trusted_Connection=True;MultipleActiveResultSets=true`. 如果你不想使用默认,你可以设置自己的连接字符串. 默认的数据库提供程序是 `SQL Server`, 所以你只能输入SQL Server连接字符串! * `--connection-string` 或者 `-cs`: 重写所有 `appsettings.json` 文件的默认连接字符串. 默认连接字符串是 `Server=localhost;Database=MyProjectName;Trusted_Connection=True;MultipleActiveResultSets=true`. 默认的数据库提供程序是 `SQL Server`. 如果你使用EF Core但需要更改DBMS,可以按[这里所述](Entity-Framework-Core-Other-DBMS.md)进行更改(创建解决方案之后).
* `--local-framework-ref --abp-path`: 使用对项目的本地引用,而不是替换为NuGet包引用. * `--local-framework-ref --abp-path`: 使用对项目的本地引用,而不是替换为NuGet包引用.
### add-package ### add-package
@ -173,15 +174,15 @@ abp update [options]
### 切换到每晚构建(预览)包 ### 切换到每晚构建(预览)包
想要切换到ABP框架的最新预览版可以使用此命令. 想要切换到ABP框架的最新**每晚构建**预览版可以使用此命令.
用法: 用法:
````bash ````bash
abp switch-to-preview [options] abp switch-to-nightly [options]
```` ````
你也可以使用切换回稳定版本: 你也可以使用切换回最新稳定版本:
````bash ````bash
abp switch-to-stable [options] abp switch-to-stable [options]

1
docs/zh-Hans/Connection-Strings.md

@ -84,7 +84,6 @@ public class IdentityServerDbContext
ABP定义了 `IConnectionStringResolver`,并在需要连接字符串时使用它. 有两个预构建的实现: ABP定义了 `IConnectionStringResolver`,并在需要连接字符串时使用它. 有两个预构建的实现:
* `DefaultConnectionStringResolver` 根据上面"配置连接字符串"一节中定义的规则,使用 `AbpDbConnectionOptions` 选择连接字符串. * `DefaultConnectionStringResolver` 根据上面"配置连接字符串"一节中定义的规则,使用 `AbpDbConnectionOptions` 选择连接字符串.
* `MultiTenantConnectionStringResolver` used for multi-tenant applications and tries to get the configured connection string for the current tenant if available. It uses the `ITenantStore` to find the connection strings. It inherits from the `DefaultConnectionStringResolver` and fallbacks to the base logic if no connection string specified for the current tenant.
* `MultiTenantConnectionStringResolver` 用于多租户应用程序,并尝试获取当前租户的已配置连接字符串(如果有). 它使用 `ITenantStore` 查找连接字符串. 它继承了 `DefaultConnectionStringResolver`, 如果没有为当前租户指定连接字符串则回退到基本逻辑. * `MultiTenantConnectionStringResolver` 用于多租户应用程序,并尝试获取当前租户的已配置连接字符串(如果有). 它使用 `ITenantStore` 查找连接字符串. 它继承了 `DefaultConnectionStringResolver`, 如果没有为当前租户指定连接字符串则回退到基本逻辑.
如果需要自定义逻辑来确定连接字符串,可以实现 `IConnectionStringResolver` 接口(也可以从现有类派生)并使用[依赖注入](Dependency-Injection.md)系统替换现有实现. 如果需要自定义逻辑来确定连接字符串,可以实现 `IConnectionStringResolver` 接口(也可以从现有类派生)并使用[依赖注入](Dependency-Injection.md)系统替换现有实现.

10
docs/zh-Hans/CurrentUser.md

@ -86,7 +86,7 @@ namespace AbpDemo
## ICurrentPrincipalAccessor ## ICurrentPrincipalAccessor
`ICurrentPrincipalAccessor` 是当需要当前用户的principle时使用的服务(由ABP框架和你的应用程序代码使用). `ICurrentPrincipalAccessor` 是当需要当前用户的Principal时使用的服务(由ABP框架和你的应用程序代码使用).
对于Web应用程序, 它获取当前 `HttpContext` 的 `User` 属性,对于非Web应用程序它将返回 `Thread.CurrentPrincipal`. 对于Web应用程序, 它获取当前 `HttpContext` 的 `User` 属性,对于非Web应用程序它将返回 `Thread.CurrentPrincipal`.
@ -114,9 +114,9 @@ public class MyService : ITransientDependency
} }
```` ````
### 更改当前Principle ### 更改当前Principal
除了某些高级场景外,你不需要设置或更改当前principle. 如果需要可以使用 `ICurrentPrincipalAccessor` 的 `Change` 方法. 它接受一个 `ClaimsPrinciple` 对象并使其成为作用域的"当前"对象. 除了某些高级场景外,你不需要设置或更改当前Principal. 如果需要可以使用 `ICurrentPrincipalAccessor` 的 `Change` 方法. 它接受一个 `ClaimsPrincipal` 对象并使其成为作用域的"当前"对象.
示例: 示例:
@ -132,7 +132,7 @@ public class MyAppService : ApplicationService
public void Foo() public void Foo()
{ {
var newPrinciple = new ClaimsPrincipal( var newPrincipal = new ClaimsPrincipal(
new ClaimsIdentity( new ClaimsIdentity(
new Claim[] new Claim[]
{ {
@ -143,7 +143,7 @@ public class MyAppService : ApplicationService
) )
); );
using (_currentPrincipalAccessor.Change(newPrinciple)) using (_currentPrincipalAccessor.Change(newPrincipal))
{ {
var userName = CurrentUser.UserName; //returns "john" var userName = CurrentUser.UserName; //returns "john"
//... //...

2
docs/zh-Hans/Entity-Framework-Core-Migrations.md

@ -3,7 +3,7 @@
本文首先介绍[应用程序启动模板](Startup-Templates/Application.md)提供的**默认结构**,并讨论你可能希望为自己的应用程序实现的**各种场景**. 本文首先介绍[应用程序启动模板](Startup-Templates/Application.md)提供的**默认结构**,并讨论你可能希望为自己的应用程序实现的**各种场景**.
> 本文档适用于希望完全理解和自定义[应用程序启动模板](Startup-Templates/Application.md)附带的数据库结构的人员. 如果你只是想创建实体和管理代码优先(code first)迁移,只需要遵循[启动教程](Tutorials/Index.md). > 本文档适用于希望完全理解和自定义[应用程序启动模板](Startup-Templates/Application.md)附带的数据库结构的人员. 如果你只是想创建实体和管理代码优先(code first)迁移,只需要遵循[启动教程](Tutorials/Part-1.md).
### 源码 ### 源码

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

@ -1,124 +1,3 @@
## 在控制台应用中使用ABP ## 在控制台应用中使用ABP
本教程将介绍如何从头开始以最小的依赖关系启动ABP. 你通常希望以 **[启动模板](https://abp.io/Templates)** 开头. ABP提供了控制台应用程序启动模板. 参阅[控制台应用程序启动模板]文档了解更多信息.
### 创建一个新项目
使用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)获取本教程中创建的示例项目的源代码.

70
docs/zh-Hans/Getting-Started-React-Native.md

@ -0,0 +1,70 @@
# React Native入门
ABP平台提供了[React Native](https://reactnative.dev/)模板用于开发移动应用程序.
当你按照[入门文档](Getting-Started.md)中所述**创建新应用程序**时,解决方案默认将React Native应用程序包含在 `react-native` 文件夹中.
## 配置你的本地IP地址
运行在Android模拟器或真机上的React Native应用程序无法连接到 `localhost` 上的后.要修复此问题,需要在本地IP上运行后端.
{{ if Tiered == "No"}}
![React Native host project local IP entry](images/rn-host-local-ip.png)
* 打开 `.HttpApi.Host` 文件夹下的 `appsettings.json` 文件. 将 `SelfUrl` 和 `Authority` 属性的 `localhost` 替换为你本地的IP地址.
* 打开 `.HttpApi.Host/Properties` 文件夹下的 `launchSettings.json` 文件. 将 `applicationUrl` 属性的 `localhost` 替换为你本地的IP地址.
{{ else if Tiered == "Yes" }}
![React Native tiered project local IP entry](images/rn-tiered-local-ip.png)
* 打开 `.IdentityServer` 文件夹下的 `appsettings.json` 文件. 将 `SelfUrl` 属性的 `localhost` 替换为你本地的IP地址.
* 打开 `.IdentityServer/Properties` 文件夹下的 `launchSettings.json` 文件. 将 `applicationUrl` 属性的 `localhost` 替换为你本地的IP地址.
* 打开 `.HttpApi.Host` 文件夹下的 `appsettings.json` 文件. 将 `Authority` 属性的 `localhost` 替换为你本地的IP地址.
* 打开 `.HttpApi.Host/Properties` 文件夹下的 `launchSettings.json` 文件. 将 `applicationUrl` 属性的 `localhost` 替换为你本地的IP地址.
{{ end }}
按照**运行HTTP API Host (服务端口)**那样运行后端.
> React Native应用程序不信任自动生成的.NET HTTPS证书,你可以在开发期间使用HTTP.
在 `react-native` 文件夹打开命令行终端,输入 `yarn` 命令(我们推荐使用[yarn](https://yarnpkg.com/)包管理, `npm install` 在大多数情况下也可以工作).
```bash
yarn
```
* 打开 `react-nativer` 文件夹下的 `Environment.js` 文件. 将 `apiUrl` 和 `issuer` 属性的 `localhost` 替换为你本地的IP地址:
![react native environment local IP](images/rn-environment-local-ip.png)
{{ if Tiered == "Yes" }}
> 确保 `issuer` 与正在运行的 `.IdentityServer` 项目匹配, `apiUrl` 与正在运行的 `.HttpApi.Host` 项目匹配.
{{else}}
> 确保 `issuer` 和 `apiUrl` 与正在运行的 `.HttpApi.Host` 项目匹配
{{ end }}
等到所有node模块加载成功, 执行 `yarn start` (或 `npm start`) 命令:
```bash
yarn start
```
等待Expo CLI启动后Expo CLI在 `http://localhost:19002/` 地址要开管理页面.
![expo-interface](images/rn-expo-interface.png)
在上面的管理界面中,可以通过使用[Expo Client](https://expo.io/tools#client)扫描二维码,使用Android模拟器,iOS模拟器或真机来启动应用程序.
> 请参阅expo.io上的[Android Studio模拟器](https://docs.expo.io/workflow/android-simulator/)和[iOS模拟器文档](https://docs.expo.io/workflow/ios-simulator/).
![React Native login screen on iPhone 11](images/rn-login-iphone.png)
输入用户名 **admin**,密码 **1q2w3E*** 登录到应用程序.
应用程序已经启动并执行,你可以基于该启动模板开发应用程序.

114
docs/zh-Hans/Getting-Started.md

@ -19,24 +19,27 @@
你需要安装以下工具: 你需要安装以下工具:
* [Visual Studio 2019 (v16.4+)](https://visualstudio.microsoft.com/vs/) for Windows / [Visual Studio for Mac](https://visualstudio.microsoft.com/vs/mac/). * [Visual Studio 2019 (v16.4+)](https://visualstudio.microsoft.com/vs/) for Windows / [Visual Studio for Mac](https://visualstudio.microsoft.com/vs/mac/).<sup id="a-editor">[1](#f-editor)</sup>
* [.NET Core 3.0+](https://www.microsoft.com/net/download/dotnet-core/) * [.NET Core 3.1+](https://www.microsoft.com/net/download/dotnet-core/)
* [Node v12+](https://nodejs.org) * [Node v12 或 v14](https://nodejs.org/en/)
* [Yarn v1.19+](https://classic.yarnpkg.com/) * [Yarn v1.19+](https://classic.yarnpkg.com/)
* [Yarn v1.20+ (not v2)](https://classic.yarnpkg.com/en/docs/install) <sup id="a-yarn">[2](#f-yarn)</sup> 或 npm v6+ (与Node一起安装)
{{ if Tiered == "Yes" }} {{ if Tiered == "Yes" }}
* [Redis](https://redis.io/): 应用程序将Redis用作[分布式缓存](../Caching.md). 因此你需要安装并运行Redis. * [Redis](https://redis.io/): 入门解决方案将Redis用作[分布式缓存](Caching.md). 因此你需要安装并运行Redis.
{{ end }} {{ end }}
> 你可以也使用其他支持.NET Core 和 ASP.NET Core的编辑器. <sup id="f-editor"><b>1</b></sup> _只要支持.NET Core和ASP.NET Core,就可以使用其他编辑器代替Visual Studio._ <sup>[↩](#a-editor)</sup>
<sup id="f-yarn"><b>2</b></sup> _Yarn v2 的工作方式不同,不受支持._ <sup>[↩](#a-yarn)</sup>
### 安装ABP CLI ### 安装ABP CLI
[ABP CLI](./CLI.md)是一个命令行页面,用于为基于ABP的应用程序验证和自动化一些任务. [ABP CLI](./CLI.md)是一个命令行页面,用于自动执行一些基于ABP的应用程序的常见任务.
> ABP CLI是[ABP框架](https://abp.io/)一个免费开源的工具. > ABP CLI是ABP框架一个免费开源的工具.
你需要使用以下命令安排ABP CLI: 你需要使用以下命令安排ABP CLI:
@ -59,10 +62,10 @@ dotnet tool update -g Volo.Abp.Cli
使用ABP CLI的 `new` 命令创建新项目: 使用ABP CLI的 `new` 命令创建新项目:
````shell ````shell
abp new Acme.BookStore -t app{{if UI == "NG"}} -u angular {{end}}{{if DB == "Mongo"}} -d mongodb{{end}}{{if Tiered == "Yes" && UI != "NG"}} --tiered {{else if Tiered == "Yes" && UI == "NG"}}--separate-identity-server{{end}} abp new Acme.BookStore{{if UI == "NG"}} -u angular {{end}}{{if DB == "Mongo"}} -d mongodb{{end}}{{if Tiered == "Yes" && UI != "NG"}} --tiered {{else if Tiered == "Yes" && UI == "NG"}}--separate-identity-server{{end}}
```` ````
* `-t` 参数指定 [启动模板](Startup-Templates/Application.md) 名称. `app` 是一个启动模板名称,包含了预安装并且配置好的[ABP模块](Modules/Index.md). * 此命令还会在解决方案文件夹内创建一个React Native移动应用程序. 如果你不想要它,可以安全地删除它,或者在 `abp new` 命令中指定 `-m none` 选项,以使其完全不包含在解决方案中.
{{ if UI == "NG" }} {{ if UI == "NG" }}
@ -70,7 +73,7 @@ abp new Acme.BookStore -t app{{if UI == "NG"}} -u angular {{end}}{{if DB == "Mon
{{ if Tiered == "Yes" }} {{ if Tiered == "Yes" }}
* `--separate-identity-server` 参数用于将Identity服务器应用程序与API主机应用程序分隔开. 如果未指定,你将只有一个端点. * `--separate-identity-server` 参数用于将Identity服务器应用程序与API主机应用程序分隔开. 如果未指定,则服务器上将只有一个端点.
{{ end }} {{ end }}
@ -90,6 +93,12 @@ abp new Acme.BookStore -t app{{if UI == "NG"}} -u angular {{end}}{{if DB == "Mon
> 你可以使用不同级别的命令空间; 例如. BookStore, Acme.BookStore or Acme.Retail.BookStore. > 你可以使用不同级别的命令空间; 例如. BookStore, Acme.BookStore or Acme.Retail.BookStore.
#### ABP CLI 命令 & 选项
[ABP CLI文档](./CLI.md)涵盖了ABP CLI的所有可用命令和选项. 本文档使用[应用程序启动模板](Startup-Templates/Application.md)创建新的Web应用程序. 有关其他模板,请参见[ABP启动模板](Startup-Templates/Index.md)文档.
> 或者,您可以从[ABP Framework网站](https://abp.io/get-started)中选择"直接下载"选项卡创建新的解决方案.
## 解决方案结构 ## 解决方案结构
{{ if UI == "MVC" }} {{ if UI == "MVC" }}
@ -146,7 +155,7 @@ abp new Acme.BookStore -t app{{if UI == "NG"}} -u angular {{end}}{{if DB == "Mon
## 创建数据库 ## 创建数据库
### 数据库连接字符串 ### 连接字符串
检查 {{if UI == "MVC"}}{{if Tiered == "Yes"}}`.IdentityServer` 和 `.HttpApi.Host` 项目{{else}}`.Web` 项目{{end}}{{else if UI == "NG" }}`.HttpApi.Host` 项目{{end}}下 `appsettings.json` 文件中的 **链接字符串**: 检查 {{if UI == "MVC"}}{{if Tiered == "Yes"}}`.IdentityServer` 和 `.HttpApi.Host` 项目{{else}}`.Web` 项目{{end}}{{else if UI == "NG" }}`.HttpApi.Host` 项目{{end}}下 `appsettings.json` 文件中的 **链接字符串**:
@ -214,7 +223,9 @@ Ef Core具有`Update-Database`命令, 可根据需要创建数据库并应用挂
这将基于配置的连接字符串创建新数据库. 这将基于配置的连接字符串创建新数据库.
> 使用`.Migrator`工具是建议的方法, 因为它还能初始化初始数据能够正确运行Web应用程序. > **使用`.DbMigrator`工具是建议的方法**, 因为它能初始化初始数据能够正确运行Web应用程序.
>
> 如果你只是使用 `Update-Database` 命令,你会得到一个空数据库,所以你无法登录到应用程序因为数据库中没有初始管理用户. 不需要种子数据库时,可以在开发期间使用 `Update-Database` 命令. 但是使用 `.DbMigrator` 应用程序会更简单,你始终可以使用它来迁移模式并为数据库添加种子.
{{ else if DB == "Mongo" }} {{ else if DB == "Mongo" }}
@ -240,7 +251,7 @@ Ef Core具有`Update-Database`命令, 可根据需要创建数据库并应用挂
![db-migrator-output](images/db-migrator-output.png) ![db-migrator-output](images/db-migrator-output.png)
> 数据库创建后会初始化种子数据, 其中包含用于登录的 `admin` 用户. 所以你至少使用 `.DbMigrator` 一次. > 数据库创建后会初始化[种子数据](Data-Seeding.md), 其中包含用于登录的 `admin` 用户. 所以你至少使用 `.DbMigrator` 一次.
{{ end }} {{ end }}
@ -336,83 +347,18 @@ yarn start
{{ end }} {{ end }}
输入用户名 **admin**,密码 **1q2w3E*** 登录到应用程序. 输入用户名 **admin**,密码 **1q2w3E*** 登录到应用程序,应用程序已经启动并执行,你可以基于此启动模板开始开发应用程序.
![bookstore-home](images/bookstore-home.png)
应用程序已经启动并执行,你可以基于该启动模板开发应用程序.
#### 移动开发 #### 移动开发
ABP平台提供了[React Native](https://reactnative.dev/)模板用于开发移动应用程序. 当你创建一个新的应用程序时.该解决方案默认包含 `react-native`文件夹. 这是一个基础的[React Native](https://reactnative.dev/)启动模板,用于开发与基于ABP的后端集成的移动应用程序.
> 该解决方案默认 `react-native` 包含了React Native应用程序,如果你不计划使用React Native开发移动应用程序,你可以忽略并删除 `react-native` 文件夹.
运行在Android模拟器或真机上的React Native应用程序无法连接到 `localhost` 上的后.要修复此问题,需要在本地IP上运行后端.
{{ if Tiered == "No"}}
![React Native host project local IP entry](images/rn-host-local-ip.png)
* 打开 `.HttpApi.Host` 文件夹下的 `appsettings.json` 文件. 将 `SelfUrl` 和 `Authority` 属性的 `localhost` 替换为你本地的IP地址.
* 打开 `.HttpApi.Host/Properties` 文件夹下的 `launchSettings.json` 文件. 将 `applicationUrl` 属性的 `localhost` 替换为你本地的IP地址.
{{ else if Tiered == "Yes" }}
![React Native tiered project local IP entry](images/rn-tiered-local-ip.png)
* 打开 `.IdentityServer` 文件夹下的 `appsettings.json` 文件. 将 `SelfUrl` 属性的 `localhost` 替换为你本地的IP地址.
* 打开 `.IdentityServer/Properties` 文件夹下的 `launchSettings.json` 文件. 将 `applicationUrl` 属性的 `localhost` 替换为你本地的IP地址.
* 打开 `.HttpApi.Host` 文件夹下的 `appsettings.json` 文件. 将 `Authority` 属性的 `localhost` 替换为你本地的IP地址.
* 打开 `.HttpApi.Host/Properties` 文件夹下的 `launchSettings.json` 文件. 将 `applicationUrl` 属性的 `localhost` 替换为你本地的IP地址.
{{ end }}
按照**运行HTTP API Host (服务端口)**那样运行后端.
> React Native应用程序不信任自动生成的.NET HTTPS证书,你可以在开发期间使用HTTP.
在 `react-native` 文件夹打开命令行终端,输入 `yarn` 命令(我们推荐使用[yarn](https://yarnpkg.com/)包管理, `npm install` 在大多数情况下也可以工作).
```bash
yarn
```
* 打开 `react-nativer` 文件夹下的 `Environment.js` 文件. 将 `apiUrl` 和 `issuer` 属性的 `localhost` 替换为你本地的IP地址:
![react native environment local IP](images/rn-environment-local-ip.png)
{{ if Tiered == "Yes" }}
> 确保 `issuer` 与正在运行的 `.IdentityServer` 项目匹配, `apiUrl` 与正在运行的 `.HttpApi.Host` 项目匹配.
{{else}}
> 确保 `issuer` 和 `apiUrl` 与正在运行的 `.HttpApi.Host` 项目匹配
{{ end }}
等到所有node模块加载成功, 执行 `yarn start` (或 `npm start`) 命令:
```bash
yarn start
```
等待Expo CLI启动后Expo CLI在 `http://localhost:19002/` 地址要开管理页面.
![expo-interface](images/rn-expo-interface.png)
在上面的管理界面中,可以通过使用[Expo Client](https://expo.io/tools#client)扫描二维码,使用Android模拟器,iOS模拟器或真机来启动应用程序.
> 请参阅expo.io上的[Android Studio模拟器](https://docs.expo.io/workflow/android-simulator/)和[iOS模拟器文档](https://docs.expo.io/workflow/ios-simulator/).
![React Native login screen on iPhone 11](images/rn-login-iphone.png)
输入用户名 **admin**,密码 **1q2w3E*** 登录到应用程序. 如果你不计划使用React Native开发移动应用程序,你可以忽略并删除 `react-native` 文件夹.
应用程序已经启动并执行,你可以基于该启动模板开发应用程序. > 你可以在ABP CLI中指定 `-m none` 选项,以使 `react-native` 目录完全不包含在解决方案中
> [应用程序启动模板](Startup-Templates/Application.md) 包含租户管理和Identity模块. 请参阅"[React Native入门](Getting-Started-React-Native.md)"文档了解如何配置和运行React Native应用程序.
## 下一步是什么? ## 下一步是什么?
[应用程序开发教程](Tutorials/Part-1.md) [Web应用程序开发教程](Tutorials/Part-1.md)

2
docs/zh-Hans/How-To/Azure-Active-Directory-Authentication-MVC.md

@ -136,7 +136,7 @@ private void ConfigureAuthentication(ServiceConfigurationContext context, IConfi
## 本文的源代码 ## 本文的源代码
你可以在[这里](https://github.com/abpframework/abp-samples/tree/master/aspnet-core/Authentication-Customization)找到已完成的示例源码. 你可以在[这里](https://github.com/abpframework/abp-samples/tree/master/Authentication-Customization)找到已完成的示例源码.
# FAQ # FAQ

2
docs/zh-Hans/How-To/Customize-Login-Page-MVC.md

@ -105,7 +105,7 @@ public class CustomLoginModel : LoginModel
## 本文的源代码 ## 本文的源代码
你可以在[这里](https://github.com/abpframework/abp-samples/tree/master/aspnet-core/Authentication-Customization)找到已完成的示例源码. 你可以在[这里](https://github.com/abpframework/abp-samples/tree/master/Authentication-Customization)找到已完成的示例源码.
## 另请参阅 ## 另请参阅

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

Loading…
Cancel
Save