diff --git a/.github/lock.yml b/.github/lock.yml new file mode 100644 index 0000000000..2e36a791b6 --- /dev/null +++ b/.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 \ No newline at end of file diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 0000000000..c1133728da --- /dev/null +++ b/.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 \ No newline at end of file diff --git a/.github/workflows/angular.yml b/.github/workflows/angular.yml index 3f576ee9c1..60051c2dc1 100644 --- a/.github/workflows/angular.yml +++ b/.github/workflows/angular.yml @@ -3,15 +3,6 @@ on: pull_request: paths: - 'npm/ng-packs/**' - branches: - #- master - - dev - push: - paths: - - 'npm/ng-packs/**' - branches: - #- master - - dev jobs: build-test-lint: runs-on: ubuntu-18.04 @@ -19,6 +10,6 @@ jobs: - uses: actions/checkout@v1 - uses: actions/setup-node@v1 with: - node-version: '10.x' + node-version: '12.x' - run: yarn && yarn ci working-directory: npm/ng-packs diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/AbpIoLocalizationModule.cs b/abp_io/AbpIoLocalization/AbpIoLocalization/AbpIoLocalizationModule.cs index 37541b4d4c..b3ebccd72f 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/AbpIoLocalizationModule.cs +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/AbpIoLocalizationModule.cs @@ -3,6 +3,7 @@ using AbpIoLocalization.Admin.Localization; using AbpIoLocalization.Base.Localization; using AbpIoLocalization.Blog.Localization; using AbpIoLocalization.Commercial.Localization; +using AbpIoLocalization.Community.Localization; using AbpIoLocalization.Docs.Localization; using AbpIoLocalization.Support.Localization; using AbpIoLocalization.Www; @@ -29,6 +30,7 @@ namespace AbpIoLocalization { options.MapCodeNamespace("Volo.AbpIo.Commercial", typeof(AbpIoCommercialResource)); options.MapCodeNamespace("Volo.AbpIo.Domain", typeof(AbpIoBaseResource)); + options.MapCodeNamespace("Volo.AbpIo.Community", typeof(AbpIoCommunityResource)); }); Configure(options => @@ -74,6 +76,11 @@ namespace AbpIoLocalization .Add("en") .AddVirtualJson("/Www/Localization/Resources") .AddBaseTypes(typeof(AbpIoBaseResource)); + + options.Resources + .Add("en") + .AddVirtualJson("/Community/Localization/Resources") + .AddBaseTypes(typeof(AbpIoBaseResource)); }); } } diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/ar.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/ar.json new file mode 100644 index 0000000000..2f7a9b9142 --- /dev/null +++ b/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" + } +} \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/ar.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/ar.json new file mode 100644 index 0000000000..96661aebeb --- /dev/null +++ b/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": "ملاحظات" + } +} \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json index a07b35c66b..e5cfbcae4e 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json @@ -103,7 +103,7 @@ "MasterModules": "Master Modules", "OrganizationName": "Organization name", "CreationDate": "Creation date", - "LicenseStart": "License start date", + "LicenseStartDate": "License start date", "LicenseEndDate": "License end date", "OrganizationNamePlaceholder": "Organization name...", "TotalQuestionCountPlaceholder": "Total question count...", @@ -166,6 +166,30 @@ "TotalQuestionMustBeGreaterWarningMessage": "TotalQuestionCount must be greater than RemainingQuestionCount !", "QuestionCountsMustBeGreaterThanZero": "TotalQuestionCount and RemainingQuestionCount must be zero or greater than zero !", "UnlimitedQuestionCount": "Unlimited question count", - "Notes": "Notes" + "Notes": "Notes", + "Menu:Community": "Community", + "Menu:Articles": "Articles", + "Wait": "Wait", + "Approve": "Approve", + "Reject": "Reject", + "Details": "Details", + "Url": "Url", + "Title": "Title", + "ContentSource": "Content source", + "Status": "Status", + "ReadArticle": "Read article", + "ArticleHasBeenWaiting": "Article has been waiting", + "ArticleHasBeenApproved": "Article has been approved", + "ArticleHasBeenRejected": "Article has been rejected", + "Permission:Community": "Community", + "Permission:CommunityArticle": "Article", + "Link": "Link", + "Enum:ContentSource:0": "Github", + "Enum:ContentSource:1": "External", + "Enum:Status:0": "Waiting", + "Enum:Status:1": "Rejected", + "Enum:Status:2": "Approved", + "Summary": "Summary", + "AuthorName": "Author name" } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/AbpIoCommunityResource.cs b/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/AbpIoCommunityResource.cs new file mode 100644 index 0000000000..2d7f93aeae --- /dev/null +++ b/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 + { + + } +} \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json new file mode 100644 index 0000000000..dbe786fb97 --- /dev/null +++ b/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." + } +} diff --git a/build/build-all-release.ps1 b/build/build-all-release.ps1 index 1c86351379..f54912d4f5 100644 --- a/build/build-all-release.ps1 +++ b/build/build-all-release.ps1 @@ -1,4 +1,4 @@ -. ".\common.ps1" +. ".\common.ps1" -f # Build all solutions diff --git a/build/build-all.ps1 b/build/build-all.ps1 index ffaffa67a3..16f8bf037c 100644 --- a/build/build-all.ps1 +++ b/build/build-all.ps1 @@ -1,6 +1,10 @@ -. ".\common.ps1" +$full = $args[0] -# Build all solutions +. ".\common.ps1" $full + +# Build all solutions + +Write-Host $solutionPaths foreach ($solutionPath in $solutionPaths) { $solutionAbsPath = (Join-Path $rootFolder $solutionPath) diff --git a/build/common.ps1 b/build/common.ps1 index c75e77178c..18019a154e 100644 --- a/build/common.ps1 +++ b/build/common.ps1 @@ -1,26 +1,38 @@ -# COMMON PATHS +$full = $args[0] + +# COMMON PATHS $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 = ( - "../framework", - "../modules/users", - "../modules/permission-management", - "../modules/setting-management", - "../modules/feature-management", - "../modules/identity", - "../modules/identityserver", - "../modules/tenant-management", - "../modules/account", - "../modules/docs", - "../modules/blogging", - "../modules/audit-logging", - "../modules/background-jobs", - "../modules/client-simulation", - "../modules/virtual-file-explorer", - "../templates/module/aspnet-core", - "../templates/app/aspnet-core", - "../abp_io/AbpIoLocalization" -) \ No newline at end of file +if ($full -eq "-f") +{ + # List of additional solutions required for full build + $solutionPaths += ( + "../modules/client-simulation", + "../modules/virtual-file-explorer", + "../modules/docs", + "../modules/blogging", + "../templates/module/aspnet-core", + "../templates/app/aspnet-core", + "../abp_io/AbpIoLocalization" + ) +}else{ + Write-host "" + Write-host ":::::::::::::: !!! You are in development mode !!! ::::::::::::::" -ForegroundColor red -BackgroundColor yellow + Write-host "" +} diff --git a/build/test-all.ps1 b/build/test-all.ps1 index 1465bff6c3..9a94ec3760 100644 --- a/build/test-all.ps1 +++ b/build/test-all.ps1 @@ -1,4 +1,6 @@ -. ".\common.ps1" +$full = $args[0] + +. ".\common.ps1" $full # Test all solutions diff --git a/common.props b/common.props index f834d1ab49..eea8755d42 100644 --- a/common.props +++ b/common.props @@ -1,7 +1,7 @@ - + latest - 3.1.0 + 3.1.0 $(NoWarn);CS1591 https://abp.io/assets/abp_nupkg.png https://abp.io diff --git a/docs/docs-langs.json b/docs/docs-langs.json index a73882bd51..3986dc8618 100644 --- a/docs/docs-langs.json +++ b/docs/docs-langs.json @@ -14,11 +14,6 @@ "DisplayName" : "简体中文", "Code" : "zh-Hans", "IsDefault": false - }, - { - "DisplayName" : "Čeština", - "Code" : "cs", - "IsDefault": false - } + } ] -} \ No newline at end of file +} diff --git a/docs/en/API/Application-Configuration.md b/docs/en/API/Application-Configuration.md index db624987c5..3aea468f8c 100644 --- a/docs/en/API/Application-Configuration.md +++ b/docs/en/API/Application-Configuration.md @@ -2,12 +2,12 @@ ABP Framework provides a pre-built and standard endpoint that contains some useful information about the application/service. Here, the list of some fundamental information at this endpoint: -* [Localization](Localization.md) values, supported and the current language of the application. -* Available and granted [policies](Authorization.md) (permissions) 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 [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. +* [Localization](../Localization.md) values, supported and the current language of the application. +* Available and granted [policies](../Authorization.md) (permissions) 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 [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. ## HTTP API diff --git a/docs/en/Application-Services.md b/docs/en/Application-Services.md index d28a7290fb..9cfec614cb 100644 --- a/docs/en/Application-Services.md +++ b/docs/en/Application-Services.md @@ -367,6 +367,84 @@ public class DistrictKey } ```` +### Authorization (for CRUD App Services) + +There are two ways of authorizing the base application service methods; + +1. You can set the policy properties (xxxPolicyName) in the constructor of your service. Example: + +```csharp +public class MyPeopleAppService : CrudAppService +{ + public MyPeopleAppService(IRepository repository) + : base(repository) + { + GetPolicyName = "..."; + GetListPolicyName = "..."; + CreatePolicyName = "..."; + UpdatePolicyName = "..."; + DeletePolicyName = "..."; + } +} +``` + +`CreatePolicyName` is checked by the `CreateAsync` method and so on... You should specify a policy (permission) name defined in your application. + +2. You can override the check methods (CheckXxxPolicyAsync) in your service. Example: + +```csharp +public class MyPeopleAppService : CrudAppService +{ + public MyPeopleAppService(IRepository repository) + : base(repository) + { + } + + protected override async Task CheckDeletePolicyAsync() + { + await AuthorizationService.CheckAsync("..."); + } +} +``` + +You can perform any logic in the `CheckDeletePolicyAsync` method. It is expected to throw an `AbpAuthorizationException` in any unauthorized case, like `AuthorizationService.CheckAsync` already does. + +### Base Properties & Methods + +CRUD application service base class provides many useful base methods that **you can override** to customize it based on your requirements. + +#### CRUD Methods + +These are the essential CRUD methods. You can override any of them to completely customize the operation. Here, the definitions of the methods: + +````csharp +Task GetAsync(TKey id); +Task> GetListAsync(TGetListInput input); +Task CreateAsync(TCreateInput input); +Task UpdateAsync(TKey id, TUpdateInput input); +Task DeleteAsync(TKey id); +```` + +#### Querying + +These methods are low level methods those can be control how to query entities from the database. + +* `CreateFilteredQuery` can be overridden to create an `IQueryable` that is filtered by the given input. If your `TGetListInput` class contains any filter, it is proper to override this method and filter the query. It returns the (unfiltered) repository (which is already `IQueryable`) by default. +* `ApplyPaging` is used to make paging on the query. If your `TGetListInput` already implements `IPagedResultRequest`, you don't need to override this since the ABP Framework automatically understands it and performs the paging. +* `ApplySorting` is used to sort (order by...) the query. If your `TGetListInput` already implements the `ISortedResultRequest`, ABP Framework automatically sorts the query. If not, it fallbacks to the `ApplyDefaultSorting` which tries to sort by creating time, if your entity implements the standard `IHasCreationTime` interface. +* `GetEntityByIdAsync` is used to get an entity by id, which calls `Repository.GetAsync(id)` by default. +* `DeleteByIdAsync` is used to delete an entity by id, which calls `Repository.DeleteAsync(id)` by default. + +#### Object to Object Mapping + +These methods are used to convert Entities to DTOs and vice verse. They uses the [IObjectMapper](Object-To-Object-Mapping.md) by default. + +* `MapToGetOutputDtoAsync` is used to map the entity to the DTO returned from the `GetAsync`, `CreateAsync` and `UpdateAsync` methods. Alternatively, you can override the `MapToGetOutputDto` if you don't need to perform any async operation. +* `MapToGetListOutputDtosAsync` is used to map a list of entities to a list of DTOs returned from the `GetListAsync` method. It uses the `MapToGetListOutputDtoAsync` to map each entity in the list. You can override one of them based on your case. Alternatively, you can override the `MapToGetListOutputDto` if you don't need to perform any async operation. +* `MapToEntityAsync` method has two overloads; + * `MapToEntityAsync(TCreateInput)` is used to create an entity from `TCreateInput`. + * `MapToEntityAsync(TUpdateInput, TEntity)` is used to update an existing entity from `TUpdateInput`. + ## Lifetime Lifetime of application services are [transient](Dependency-Injection.md) and they are automatically registered to the dependency injection system. diff --git a/docs/en/Authentication/Social-External-Logins.md b/docs/en/Authentication/Social-External-Logins.md new file mode 100644 index 0000000000..c48763a92a --- /dev/null +++ b/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. \ No newline at end of file diff --git a/docs/en/Authorization.md b/docs/en/Authorization.md index 1522fa586a..4b7502f437 100644 --- a/docs/en/Authorization.md +++ b/docs/en/Authorization.md @@ -284,8 +284,6 @@ You may need to check a policy/permission on the client side. For ASP.NET Core M abp.auth.isGranted('MyPermissionName'); ``` -See [abp.auth](API/JavaScript-API/Auth.md) API documentation for details. - ## Permission Management Permission management is normally done by an admin user using the permission management modal: diff --git a/docs/en/Background-Workers-Quartz.md b/docs/en/Background-Workers-Quartz.md index f8495b27a4..e1d8287019 100644 --- a/docs/en/Background-Workers-Quartz.md +++ b/docs/en/Background-Workers-Quartz.md @@ -1,6 +1,6 @@ # Quartz Background Worker Manager -[Quartz](https://www.quartz-scheduler.net/) is an advanced background worker manager. You can integrate Quartz with the ABP Framework to use it instead of the [default background worker manager](Background-Worker.md). ABP simply integrates quartz. +[Quartz](https://www.quartz-scheduler.net/) is an advanced background worker manager. You can integrate Quartz with the ABP Framework to use it instead of the [default background worker manager](Background-Workers.md). ABP simply integrates quartz. ## Installation @@ -36,6 +36,9 @@ public class YourModule : AbpModule } ```` +> Quartz background worker integration provided `QuartzPeriodicBackgroundWorkerAdapter` to adapt `PeriodicBackgroundWorkerBase` and `AsyncPeriodicBackgroundWorkerBase` derived class. So, you can still fllow the [background workers document](Background-Workers.md) to define the background worker. +> `BackgroundJobWorker` checks todo jobs every 5 seconds, but quartz will not block when long time jobs are executing. So,after Added Quartz background worker integration, you also need to add [Quartz Background Jobs](Background-Jobs-Quartz.md) or [Hangfire Background Jobs](Background-Jobs-Hangfire.md) to avoid duplicate execution jobs. + ## Configuration See [Configuration](Background-Jobs-Quartz#Configuration). diff --git a/docs/en/Best-Practices/Application-Services.md b/docs/en/Best-Practices/Application-Services.md index 876105c214..3332ce08ea 100644 --- a/docs/en/Best-Practices/Application-Services.md +++ b/docs/en/Best-Practices/Application-Services.md @@ -206,7 +206,7 @@ This method votes a question and returns the current score of the question. #### Extra Properties -* **Do** use either `MapExtraPropertiesTo` extension method ([see](Object-Extensions.md)) or configure the object mapper (`MapExtraProperties`) to allow application developers to be able to extend the objects and services. +* **Do** use either `MapExtraPropertiesTo` extension method ([see](../Object-Extensions.md)) or configure the object mapper (`MapExtraProperties`) to allow application developers to be able to extend the objects and services. #### Manipulating / Deleting Entities diff --git a/docs/en/Blob-Storing-Aliyun.md b/docs/en/Blob-Storing-Aliyun.md index 6621e2dbd2..62e57e58c7 100644 --- a/docs/en/Blob-Storing-Aliyun.md +++ b/docs/en/Blob-Storing-Aliyun.md @@ -23,18 +23,21 @@ Configuration is done in the `ConfigureServices` method of your [module](Module- ````csharp Configure(options => { - options.Containerscontainer.UseAliyun(aliyun => + options.Containers.ConfigureDefault(container => { - aliyun.AccessKeyId = "your aliyun access key id"; - aliyun.AccessKeySecret = "your aliyun access key secret"; - aliyun.Endpoint = "your oss endpoint"; - aliyun.RegionId = "your sts region id"; - aliyun.RoleArn = "the arn of ram role"; - aliyun.RoleSessionName = "the name of the certificate"; - aliyun.Policy = "policy"; - aliyun.DurationSeconds = "expiration date"; - aliyun.ContainerName = "your aliyun container name"; - aliyun.CreateContainerIfNotExists = false; + container.UseAliyun(aliyun => + { + aliyun.AccessKeyId = "your aliyun access key id"; + aliyun.AccessKeySecret = "your aliyun access key secret"; + aliyun.Endpoint = "your oss endpoint"; + aliyun.RegionId = "your sts region id"; + aliyun.RoleArn = "the arn of ram role"; + aliyun.RoleSessionName = "the name of the certificate"; + aliyun.Policy = "policy"; + aliyun.DurationSeconds = "expiration date"; + aliyun.ContainerName = "your aliyun container name"; + aliyun.CreateContainerIfNotExists = false; + }); }); }); ```` @@ -45,17 +48,19 @@ Configure(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. +* **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. **note**: Using subaccounts operated OSS,if the value is 0. +* **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 @@ -70,4 +75,4 @@ Aliyun Blob Provider organizes BLOB name and implements some conventions. The fu * `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. \ No newline at end of file +* `IOssClientFactory` is used create OSS client. It is implemented by the `DefaultOssClientFactory` by default. You can override/replace it,if you want customize. diff --git a/docs/en/Blob-Storing-Aws.md b/docs/en/Blob-Storing-Aws.md new file mode 100644 index 0000000000..cfee5c3f57 --- /dev/null +++ b/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(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/` 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. \ No newline at end of file diff --git a/docs/en/Blob-Storing-Azure.md b/docs/en/Blob-Storing-Azure.md index d43f8b3a43..9d033013da 100644 --- a/docs/en/Blob-Storing-Azure.md +++ b/docs/en/Blob-Storing-Azure.md @@ -23,11 +23,14 @@ Configuration is done in the `ConfigureServices` method of your [module](Module- ````csharp Configure(options => { - options.Containerscontainer.UseAzure(azure => + options.Containers.ConfigureDefault(container => { - azure.ConnectionString = "your azure connection string"; - azure.ContainerName = "your azure container name"; - azure.CreateContainerIfNotExists = false; + container.UseAzure(azure => + { + azure.ConnectionString = "your azure connection string"; + azure.ContainerName = "your azure container name"; + azure.CreateContainerIfNotExists = false; + }); }); }); ```` diff --git a/docs/en/Blob-Storing-Database.md b/docs/en/Blob-Storing-Database.md index 69b9232ee8..c14086a2f4 100644 --- a/docs/en/Blob-Storing-Database.md +++ b/docs/en/Blob-Storing-Database.md @@ -27,7 +27,7 @@ This command adds all the NuGet packages to corresponding layers of your solutio Here, all the NuGet packages defined by this provider; -* [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.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) diff --git a/docs/en/Blob-Storing-Minio.md b/docs/en/Blob-Storing-Minio.md new file mode 100644 index 0000000000..505c6721b0 --- /dev/null +++ b/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(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/` 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. diff --git a/docs/en/Blob-Storing.md b/docs/en/Blob-Storing.md index 2b49a896cd..23b4f80560 100644 --- a/docs/en/Blob-Storing.md +++ b/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. * [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/). +* [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. diff --git a/docs/en/CLI.md b/docs/en/CLI.md index 8551d64505..f7b5ab88aa 100644 --- a/docs/en/CLI.md +++ b/docs/en/CLI.md @@ -90,6 +90,7 @@ abp new Acme.BookStore * **`console`**: [Console template](Startup-Templates/Console.md). * `--output-folder` or `-o`: Specifies the output folder. Default value is the current directory. * `--version` or `-v`: Specifies the ABP & template version. It can be a [release tag](https://github.com/abpframework/abp/releases) or a [branch name](https://github.com/abpframework/abp/branches). Uses the latest release if not specified. Most of the times, you will want to use the latest version. +* `--preview`: Use latest 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`). * `--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). @@ -196,12 +197,12 @@ abp generate-proxy --apiUrl https://localhost:44305 --ui angular --module all ### 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: ````bash -abp switch-to-preview [options] +abp switch-to-nightly [options] ```` #### Options @@ -210,7 +211,7 @@ abp switch-to-preview [options] ### 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: diff --git a/docs/en/How-To/Customize-SignIn-Manager.md b/docs/en/Community-Articles/2020-04-19-Customize-the-SignIn-Manager/POST.md similarity index 83% rename from docs/en/How-To/Customize-SignIn-Manager.md rename to docs/en/Community-Articles/2020-04-19-Customize-the-SignIn-Manager/POST.md index 4fc6291bc4..d46049526d 100644 --- a/docs/en/How-To/Customize-SignIn-Manager.md +++ b/docs/en/Community-Articles/2020-04-19-Customize-the-SignIn-Manager/POST.md @@ -1,6 +1,6 @@ # How to Customize the SignIn Manager for ABP Applications -After creating a new application using the [application startup template](../Startup-Templates/Application.md), you may want extend or change the default behavior of the SignIn Manager for your authentication and registration flow needs. ABP [Account Module](../Modules/Account.md) uses the [Identity Management Module](../Modules/Identity.md) for SignIn Manager and the [Identity Management Module](../Modules/Identity.md) uses default [Microsoft Identity SignIn Manager](https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs) ([see here](https://github.com/abpframework/abp/blob/be32a55449e270d2d456df3dabdc91f3ffdd4fa9/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreModule.cs#L17)). +After creating a new application using the [application startup template](https://docs.abp.io/en/abp/latest/Startup-Templates/Application), you may want extend or change the default behavior of the SignIn Manager for your authentication and registration flow needs. ABP [Account Module](https://docs.abp.io/en/abp/latest/Modules/Account) uses the [Identity Management Module](https://docs.abp.io/en/abp/latest/Modules/Identity) for SignIn Manager and the [Identity Management Module](https://docs.abp.io/en/abp/latest/Modules/Identity) uses default [Microsoft Identity SignIn Manager](https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs) ([see here](https://github.com/abpframework/abp/blob/be32a55449e270d2d456df3dabdc91f3ffdd4fa9/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreModule.cs#L17)). To write your Custom SignIn Manager, you need to extend [Microsoft Identity SignIn Manager](https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs) class and register it to the DI container. @@ -76,7 +76,7 @@ public override async Task GetE } ```` -To get your overridden method invoked and your customized SignIn Manager class to work, you need to register your class to the [Dependency Injection System](../Dependency-Injection.md). +To get your overridden method invoked and your customized SignIn Manager class to work, you need to register your class to the [Dependency Injection System](https://docs.abp.io/en/abp/latest/Dependency-Injection). ## Register to Dependency Injection @@ -93,9 +93,4 @@ PreConfigure(identityBuilder => ## The Source Code -You can find the source code of the completed example [here](https://github.com/abpframework/abp-samples/tree/master/Authentication-Customization). - -## See Also - -* [How to Customize the Login Page for MVC / Razor Page Applications](Customize-Login-Page-MVC.md). -* [Identity Management Module](../Modules/Identity.md). \ No newline at end of file +You can find the source code of the completed example [here](https://github.com/abpframework/abp-samples/tree/master/Authentication-Customization). \ No newline at end of file diff --git a/docs/en/How-To/Azure-Active-Directory-Authentication-MVC.md b/docs/en/Community-Articles/2020-04-27-Use-Azure-Active-Directory-Authentication-for-MVC-Razor-Page-Applications/POST.md similarity index 95% rename from docs/en/How-To/Azure-Active-Directory-Authentication-MVC.md rename to docs/en/Community-Articles/2020-04-27-Use-Azure-Active-Directory-Authentication-for-MVC-Razor-Page-Applications/POST.md index 7e6319839a..c7b0258d55 100644 --- a/docs/en/How-To/Azure-Active-Directory-Authentication-MVC.md +++ b/docs/en/Community-Articles/2020-04-27-Use-Azure-Active-Directory-Authentication-for-MVC-Razor-Page-Applications/POST.md @@ -80,7 +80,7 @@ private void ConfigureAuthentication(ServiceConfigurationContext context, IConfi > * Add `JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear()`. This will disable the default Microsoft claim type mapping. > * Add `JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier)`. Mapping this to [ClaimTypes.NameIdentifier](https://github.com/dotnet/runtime/blob/6d395de48ac718a913e567ae80961050f2a9a4fa/src/libraries/System.Security.Claims/src/System/Security/Claims/ClaimTypes.cs#L59) is important since default SignIn Manager behavior uses this claim type for external login information. > * Add `options.SignInScheme = IdentityConstants.ExternalScheme` since [default signin scheme is `AzureADOpenID`](https://github.com/dotnet/aspnetcore/blob/c56aa320c32ee5429d60647782c91d53ac765865/src/Azure/AzureAD/Authentication.AzureAD.UI/src/AzureADOpenIdConnectOptionsConfiguration.cs#L35). -> * Add `options.Scope.Add("email")` if you are using **v2.0** endpoint of AzureAD since v2.0 endpoint doesn't return the `email` claim as default. The [Account Module](../Modules/Account.md) uses `email` claim to [register external users](https://github.com/abpframework/abp/blob/be32a55449e270d2d456df3dabdc91f3ffdd4fa9/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs#L215). +> * Add `options.Scope.Add("email")` if you are using **v2.0** endpoint of AzureAD since v2.0 endpoint doesn't return the `email` claim as default. The [Account Module](https://docs.abp.io/en/abp/latest/Modules/Account) uses `email` claim to [register external users](https://github.com/abpframework/abp/blob/be32a55449e270d2d456df3dabdc91f3ffdd4fa9/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs#L215). You are done and integration is completed. @@ -205,8 +205,3 @@ You can find the source code of the completed example [here](https://github.com/ await Task.CompletedTask; }); ```` - -## See Also - -* [How to Customize the Login Page for MVC / Razor Page Applications](Customize-Login-Page-MVC.md). -* [How to Customize the SignIn Manager for ABP Applications](Customize-SignIn-Manager.md). \ No newline at end of file diff --git a/docs/en/How-To/Customize-Login-Page-MVC.md b/docs/en/Community-Articles/2020-05-09-Customize-the-Login-Page-for-MVC-Razor-Page-Applications/POST.md similarity index 90% rename from docs/en/How-To/Customize-Login-Page-MVC.md rename to docs/en/Community-Articles/2020-05-09-Customize-the-Login-Page-for-MVC-Razor-Page-Applications/POST.md index 89c280cd5a..562d545ac6 100644 --- a/docs/en/How-To/Customize-Login-Page-MVC.md +++ b/docs/en/Community-Articles/2020-05-09-Customize-the-Login-Page-for-MVC-Razor-Page-Applications/POST.md @@ -1,6 +1,6 @@ # How to Customize the Login Page for MVC / Razor Page Applications -When you create a new application using the [application startup template](../Startup-Templates/Application.md), source code of the login page will not be inside your solution, so you can not directly change it. The login page comes from the [Account Module](../Modules/Account.md) that is used a [NuGet package](https://www.nuget.org/packages/Volo.Abp.Account.Web) reference. +When you create a new application using the [application startup template](https://docs.abp.io/en/abp/latest/Startup-Templates/Application), source code of the login page will not be inside your solution, so you can not directly change it. The login page comes from the [Account Module](https://docs.abp.io/en/abp/latest/Modules/Account) that is used a [NuGet package](https://www.nuget.org/packages/Volo.Abp.Account.Web) reference. This document explains how to customize the login page for your own application. @@ -20,7 +20,7 @@ public class CustomLoginModel : LoginModel } ```` -> Naming convention is important here. If your class name doesn't end with `LoginModel`, you need to manually replace the `LoginModel` using the [dependency injection](../Dependency-Injection.md) system. +> Naming convention is important here. If your class name doesn't end with `LoginModel`, you need to manually replace the `LoginModel` using the [dependency injection](https://docs.abp.io/en/abp/latest/Dependency-Injection) system. Then you can override any method you need and add new methods and properties needed by the UI. @@ -105,8 +105,4 @@ Just changed the `@model` to `Acme.BookStore.Web.Pages.Account.CustomLoginModel` ## The Source Code -You can find the source code of the completed example [here](https://github.com/abpframework/abp-samples/tree/master/Authentication-Customization). - -## See Also - -* [ASP.NET Core (MVC / Razor Pages) User Interface Customization Guide](../UI/AspNetCore/Customization-User-Interface.md). +You can find the source code of the completed example [here](https://github.com/abpframework/abp-samples/tree/master/Authentication-Customization). \ No newline at end of file diff --git a/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/POST.md b/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/POST.md new file mode 100644 index 0000000000..915d4ef5c1 --- /dev/null +++ b/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, + ITransientDependency + { + private readonly IHubContext _hubContext; + + public ReceivedMessageEventHandler(IHubContext 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 { + +} +@section scripts { + + +} +

Chat

+ +
+ + +
All Messages:
+
    +
+
+ +
+ + + + + + + + + + + + + + + + + +
+
+
+
+```` + + **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('
  • ' + message + '
  • '); + }); + + 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('
  • ' + abp.currentUser.userName + ': ' + message + '
  • '); + }); + + }); +}); +```` + +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). diff --git a/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/chat.png b/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/chat.png new file mode 100644 index 0000000000..b29daa9492 Binary files /dev/null and b/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/chat.png differ diff --git a/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/dataflow-diagram.png b/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/dataflow-diagram.png new file mode 100644 index 0000000000..bb81ddf37a Binary files /dev/null and b/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/dataflow-diagram.png differ diff --git a/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/header.png b/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/header.png new file mode 100644 index 0000000000..6e5a46b780 Binary files /dev/null and b/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/header.png differ diff --git a/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/login1.png b/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/login1.png new file mode 100644 index 0000000000..4e371e2ee9 Binary files /dev/null and b/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/login1.png differ diff --git a/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/login2.png b/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/login2.png new file mode 100644 index 0000000000..68c5a3259d Binary files /dev/null and b/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/login2.png differ diff --git a/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/new-user.png b/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/new-user.png new file mode 100644 index 0000000000..2ffdf99d45 Binary files /dev/null and b/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/new-user.png differ diff --git a/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/POST.md b/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/POST.md new file mode 100644 index 0000000000..560e15e288 --- /dev/null +++ b/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 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 _fileContainer; + + public FileAppService(IBlobContainer fileContainer) + { + _fileContainer = fileContainer; + } + + public async Task SaveBlobAsync(SaveBlobInputDto input) + { + await _fileContainer.SaveAsync(input.Name, input.Content, true); + } + + public async Task 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` to our app service. It will handle all blob actions for us. + +- `SaveBlobAsync` method uses `SaveAsync` of `IBlobContainer` 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` 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 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 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{ + +} + + + +

    File Upload and Download

    +
    + + + +

    Upload File

    +
    +
    + + + + + +
    +
    + + +

    Download File

    +
    +
    +
    + * + +
    + + +
    +
    +
    +
    +
    +``` + +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) diff --git a/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/application-contracts-project.png b/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/application-contracts-project.png new file mode 100644 index 0000000000..924adab6f6 Binary files /dev/null and b/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/application-contracts-project.png differ diff --git a/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/file-upload-result.gif b/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/file-upload-result.gif new file mode 100644 index 0000000000..e59ee890c6 Binary files /dev/null and b/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/file-upload-result.gif differ diff --git a/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/initial-project.png b/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/initial-project.png new file mode 100644 index 0000000000..0e611deebe Binary files /dev/null and b/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/initial-project.png differ diff --git a/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/web-project.png b/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/web-project.png new file mode 100644 index 0000000000..f22d2dbdcb Binary files /dev/null and b/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/web-project.png differ diff --git a/docs/en/Community-Articles/2020-08-07-Passwordless-Authentication/POST.md b/docs/en/Community-Articles/2020-08-07-Passwordless-Authentication/POST.md new file mode 100644 index 0000000000..1b7cef0867 --- /dev/null +++ b/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 : TotpSecurityStampBasedTokenProvider + where TUser : class + { + public override Task CanGenerateTwoFactorTokenAsync(UserManager manager, TUser user) + { + return Task.FromResult(false); + } + + //We need to override this method as well. + public override async Task GetUserModifierAsync(string purpose, UserManager 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() + .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 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 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; +} + + + +
    + + Generate passwordless token link + + @if (Model.PasswordlessLoginUrl != null) + { + + [@Model.PasswordlessLoginUrl](/en/commercial/latest/how-to/@Model.PasswordlessLoginUrl) + + } + +
    +
    +
    +``` +## 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 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 CreateClaims(IUser user, IEnumerable roles) + { + var claims = new List + { + 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). \ No newline at end of file diff --git a/docs/en/Contribution/Index.md b/docs/en/Contribution/Index.md index 871ec0b985..83ec0bf05d 100644 --- a/docs/en/Contribution/Index.md +++ b/docs/en/Contribution/Index.md @@ -1,8 +1,12 @@ -## Contribution Guide +# Contribution Guide ABP is an [open source](https://github.com/abpframework) and community driven project. This guide is aims to help anyone wants to contribute to the project. -### Code Contribution +## community.abp.io + +If you want to write articles or "how to" guides related to the ABP Framework and ASP.NET Core, please submit your article to the [community.abp.io](https://community.abp.io/) web site. + +## Code Contribution You can always send pull requests to the Github repository. @@ -12,15 +16,15 @@ You can always send pull requests to the Github repository. Before making any change, please discuss it on the [Github issues](https://github.com/abpframework/abp/issues). In this way, no other developer will work on the same issue and your PR will have a better chance to be accepted. -#### Bug Fixes & Enhancements +### Bug Fixes & Enhancements You may want to fix a known bug or work on a planned enhancement. See [the issue list](https://github.com/abpframework/abp/issues) on Github. -#### Feature Requests +### Feature Requests If you have a feature idea for the framework or modules, [create an issue](https://github.com/abpframework/abp/issues/new) on Github or attend to an existing discussion. Then you can implement it if it's embraced by the community. -### Document Translation +## Document Translation You may want to translate the complete [documentation](https://abp.io/documents/) (including this one) to your mother language. If so, follow these steps: @@ -37,13 +41,13 @@ There are some fundamental documents need to be translated before publishing a l A new language is published after these minimum translations have been completed. -### Resource Localization +## Resource Localization ABP framework has a flexible [localization system](../Localization.md). You can create localized user interfaces for your own application. In addition to that, the framework and the [pre-build modules](https://docs.abp.io/en/abp/latest/Modules/Index) have localized texts. As an example, see [the localization texts for the Volo.Abp.UI package](https://github.com/abpframework/abp/blob/master/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json). -#### Using the "abp translate" command +### Using the "abp translate" command This is the recommended approach, since it automatically finds all missing texts for a specific culture and lets you to translate in one place. @@ -54,14 +58,10 @@ This is the recommended approach, since it automatically finds all missing texts * Once you done the translation, use `abp translate -a` command to apply changes to the related files. * Send a pull request on GitHub. -#### Manual Translation +### Manual Translation If you want to make a change on a specific resource file, you can find the file yourself, make the necessary change (or create a new file for your language) and send a pull request on GitHub. -### Blog Posts & Tutorials - -If you decide to create some tutorials or blog posts on ABP, please inform us (by creating a [Github issue](https://github.com/abpframework/abp/issues)), so we may add a link to your tutorial/post in the official documentation and we can announce it on our [Twitter account](https://twitter.com/abpframework). - -### Bug Report +## Bug Report If you find any bug, please [create an issue on the Github repository](https://github.com/abpframework/abp/issues/new). diff --git a/docs/en/Distributed-Event-Bus.md b/docs/en/Distributed-Event-Bus.md index 947b0f3142..bd85613011 100644 --- a/docs/en/Distributed-Event-Bus.md +++ b/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` interface for each event type. +If you perform **database operations** and use the [repositories](Repositories.md) inside the event handler, you may need to create a [unit of work](Unit-Of-Work.md), because some repository methods need to work inside an **active unit of work**. Make the handle method `virtual` and add a `[UnitOfWork]` attribute for the method, or manually use the `IUnitOfWorkManager` to create a unit of work scope. + > The handler class must be registered to the dependency injection (DI). The sample above uses the `ITransientDependency` to accomplish it. See the [DI document](Dependency-Injection.md) for more options. + ## Pre-Defined Events ABP Framework **automatically publishes** distributed events for **create, update and delete** operations for an [entity](Entities.md) once you configure it. diff --git a/docs/en/Entities.md b/docs/en/Entities.md index 538d298461..a5acba0546 100644 --- a/docs/en/Entities.md +++ b/docs/en/Entities.md @@ -234,6 +234,12 @@ ABP Framework does not force you to apply any DDD rule or patterns. However, it While it's not common (and not suggested) for aggregate roots, it is in fact possible to define composite keys in the same way as defined for the mentioned entities above. Use non-generic `AggregateRoot` base class in that case. +### BasicAggregateRoot Class + +`AggregateRoot` class implements the `IHasExtraProperties` and `IHasConcurrencyStamp` interfaces which brings two properties to the derived class. `IHasExtraProperties` makes the entity extensible (see the *Extra Properties* section below) and `IHasConcurrencyStamp` adds a `ConcurrencyStamp` property that is managed by the ABP Framework to implement the [optimistic concurrency](https://docs.microsoft.com/en-us/ef/core/saving/concurrency). In most cases, these are wanted features for aggregate roots. + +However, if you don't need these features, you can inherit from the `BasicAggregateRoot` (or `BasicAggregateRoot`) for your aggregate root. + ## Base Classes & Interfaces for Audit Properties There are some properties like `CreationTime`, `CreatorId`, `LastModificationTime`... which are very common in all applications. ABP Framework provides some interfaces and base classes to **standardize** these properties and also **sets their values automatically**. @@ -293,7 +299,7 @@ While you can manually implement any of the interfaces defined above, it is sugg All these base classes also have non-generic versions to take `AuditedEntity` and `FullAuditedAggregateRoot` to support the composite primary keys. -All these base classes also have `...WithUser` pairs, like `FullAuditedAggregateRootWithUser` and`FullAuditedAggregateRootWithUser`. 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` and `FullAuditedAggregateRootWithUser`. 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 diff --git a/docs/en/Entity-Framework-Core-Migrations.md b/docs/en/Entity-Framework-Core-Migrations.md index 00e791a94e..d59f9f96c7 100644 --- a/docs/en/Entity-Framework-Core-Migrations.md +++ b/docs/en/Entity-Framework-Core-Migrations.md @@ -2,7 +2,7 @@ This document begins by **introducing the default structure** provided by [the application startup template](Startup-Templates/Application.md) and **discusses various scenarios** you may want to implement for your own application. -> This document is for who want to fully understand and customize the database structure comes with [the application startup template](Startup-Templates/Application.md). If you simply want to create entities and manage your code first migrations, just follow [the startup tutorials](Tutorials/Index.md). +> This document is for who want to fully understand and customize the database structure comes with [the application startup template](Startup-Templates/Application.md). If you simply want to create entities and manage your code first migrations, just follow [the startup tutorials](Tutorials/Part-1.md). ### Source Code diff --git a/docs/en/Getting-Started-Console-Application.md b/docs/en/Getting-Started-Console-Application.md deleted file mode 100644 index d5f3c8519b..0000000000 --- a/docs/en/Getting-Started-Console-Application.md +++ /dev/null @@ -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()) - { - 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()) - { - application.Initialize(); - - //Resolve a service and use it - var helloWorldService = - application.ServiceProvider.GetService(); - 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(options => - { - options.UseAutofac(); //Autofac integration - })) - { - application.Initialize(); - - //Resolve a service and use it - var helloWorldService = - application.ServiceProvider.GetService(); - 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). diff --git a/docs/en/How-To/Index.md b/docs/en/How-To/Index.md deleted file mode 100644 index da209d4bcb..0000000000 --- a/docs/en/How-To/Index.md +++ /dev/null @@ -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) \ No newline at end of file diff --git a/docs/en/Local-Event-Bus.md b/docs/en/Local-Event-Bus.md index d0f494faea..d937a1caff 100644 --- a/docs/en/Local-Event-Bus.md +++ b/docs/en/Local-Event-Bus.md @@ -154,6 +154,8 @@ That's all. `MyHandler` is **automatically discovered** by the ABP Framework and * **Zero or more handlers** can subscribe to the same event. * A single event handler class can **subscribe to multiple events** but implementing the `ILocalEventHandler` interface for each event type. +If you perform **database operations** and use the [repositories](Repositories.md) inside the event handler, you may need to create a [unit of work](Unit-Of-Work.md), because some repository methods need to work inside an **active unit of work**. Make the handle method `virtual` and add a `[UnitOfWork]` attribute for the method, or manually use the `IUnitOfWorkManager` to create a unit of work scope. + > The handler class must be registered to the dependency injection (DI). The sample above uses the `ITransientDependency` to accomplish it. See the [DI document](Dependency-Injection.md) for more options. ## Transaction & Exception Behavior diff --git a/docs/en/Modules/Docs.md b/docs/en/Modules/Docs.md index b8f678dc48..e788a4c0f8 100644 --- a/docs/en/Modules/Docs.md +++ b/docs/en/Modules/Docs.md @@ -613,7 +613,7 @@ Configure(options => The `Index` is automatically created after the application starts if the `Index` does not exist. `DefaultElasticClientProvider` is responsible for creating `IElasticClient`. By default, it reads Elastic Search's `Url` from `IConfiguration`. -If your `IElasticClient` needs additional configuration, please use override `IElasticClientProvider` service and replace it in the [dependency injection](Dependency-Injection.md) system. +If your `IElasticClient` needs additional configuration, please use override `IElasticClientProvider` service and replace it in the [dependency injection](../Dependency-Injection.md) system. ``` { diff --git a/docs/en/Multi-Tenancy.md b/docs/en/Multi-Tenancy.md index 77ca49961b..901a5c5cff 100644 --- a/docs/en/Multi-Tenancy.md +++ b/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: diff --git a/docs/en/Nightly-Builds.md b/docs/en/Nightly-Builds.md index 1a66af32e1..315f5109b9 100644 --- a/docs/en/Nightly-Builds.md +++ b/docs/en/Nightly-Builds.md @@ -29,13 +29,13 @@ Now, you can install preview / nightly packages to your project from Nuget Brows The latest version of preview NPM packages can be installed by the running below command in the root folder of application: ```bash -abp switch-to-preview +abp switch-to-preview --npm ``` If you're using the ABP Framework preview packages, you can switch back to stable version using this command: ```bash -abp switch-to-stable +abp switch-to-stable --npm ``` -See the [ABP CLI documentation](./CLI.md) for more information. \ No newline at end of file +See the [ABP CLI documentation](./CLI.md) for more information. diff --git a/docs/en/Options.md b/docs/en/Options.md index 20168bdbe3..b4f4f09140 100644 --- a/docs/en/Options.md +++ b/docs/en/Options.md @@ -80,7 +80,7 @@ If you are developing a module, you may need to allow developers to set some opt For such cases, ABP introduces the `PreConfigure` and the `ExecutePreConfiguredActions` extension methods for the `IServiceCollection`. The pattern works as explained below. -1. Define a plan option class in your module. Example: +Define a pre option class in your module. Example: ````csharp public class MyPreOptions diff --git a/docs/en/Samples/Index.md b/docs/en/Samples/Index.md index e3fc0263ad..6e7d751b99 100644 --- a/docs/en/Samples/Index.md +++ b/docs/en/Samples/Index.md @@ -58,7 +58,4 @@ While there is no Razor Pages & MongoDB combination, you can check both document * [Customize the SignIn Manager](../How-To/Customize-SignIn-Manager.md) * **Empty ASP.NET Core Application**: The most basic ASP.NET Core application with the ABP Framework installed. * [Source code](https://github.com/abpframework/abp-samples/tree/master/BasicAspNetCoreApplication) - * [Documentation](../Getting-Started-AspNetCore-Application.md) -* **Empty Console Application**: The most basic console application with the ABP Framework installed. - * [Source code](https://github.com/abpframework/abp-samples/tree/master/BasicConsoleApplication) - * [Documentation](../Getting-Started-Console-Application.md) \ No newline at end of file + * [Documentation](../Getting-Started-AspNetCore-Application.md) \ No newline at end of file diff --git a/docs/en/Samples/Microservice-Demo.md b/docs/en/Samples/Microservice-Demo.md index 30059a9379..9b4c622c4e 100644 --- a/docs/en/Samples/Microservice-Demo.md +++ b/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+ * [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 the `samples\MicroserviceDemo\MicroserviceDemo.sln` in Visual Studio 2017 (15.9.0+). diff --git a/docs/en/Startup-Templates/Application.md b/docs/en/Startup-Templates/Application.md index af76bb14ff..7495b8f006 100644 --- a/docs/en/Startup-Templates/Application.md +++ b/docs/en/Startup-Templates/Application.md @@ -437,7 +437,11 @@ See the [Testing Overview](https://reactjs.org/docs/testing.html) document. * [Formik](https://github.com/jaredpalmer/formik) is used to build forms. * [Yup](https://github.com/jquense/yup) is used for form validations. +## Social / External Logins + +If you want to configure social/external logins for your application, please follow the [Social/External Logins](../Authentication/Social-External-Logins.md) document. + ## What's Next? -- [The getting started document](../Getting-Started-With-Startup-Templates.md) explains how to create a new application in a few minutes. -- [The application development tutorial](../Tutorials/Part-1) explains step by step application development. +- [The getting started document](../Getting-Started.md) explains how to create a new application in a few minutes. +- [The application development tutorial](../Tutorials/Part-1.md) explains step by step application development. diff --git a/docs/en/Startup-Templates/Console.md b/docs/en/Startup-Templates/Console.md index d42d6f913a..b002eb3e5e 100644 --- a/docs/en/Startup-Templates/Console.md +++ b/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. -### \ No newline at end of file +## 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. \ No newline at end of file diff --git a/docs/en/Startup-Templates/Module.md b/docs/en/Startup-Templates/Module.md index 45e123971b..ea2fe5acca 100644 --- a/docs/en/Startup-Templates/Module.md +++ b/docs/en/Startup-Templates/Module.md @@ -56,7 +56,7 @@ An `IssueType` enum and an `IssueConsts` class (which may have some constant fie ### .Domain Project -This is the domain layer of the solution. It mainly contains [entities, aggregate roots](../Entities.md), [domain services](../Domain-Services.md), [value types](../Value-Types.md), [repository interfaces](../Repositories.md) and other domain objects. +This is the domain layer of the solution. It mainly contains [entities, aggregate roots](../Entities.md), [domain services](../Domain-Services.md), value types, [repository interfaces](../Repositories.md) and other domain objects. An `Issue` entity, an `IssueManager` domain service and an `IIssueRepository` interface are good candidates for this project. diff --git a/docs/en/Text-Templating.md b/docs/en/Text-Templating.md index c71787f598..3008db5c41 100644 --- a/docs/en/Text-Templating.md +++ b/docs/en/Text-Templating.md @@ -4,7 +4,7 @@ ABP Framework provides a simple, yet efficient text template system. Text templating is used to dynamically render contents based on a template and a model (a data object): -***TEMPLATE + MODEL ==render==> RENDERED CONTENT*** +Template + Model =renderer=> Rendered Content It is very similar to an ASP.NET Core Razor View (or Page): @@ -454,4 +454,4 @@ Return `null` if your source can not find the content, so `ITemplateContentProvi * [The source code of the sample application](https://github.com/abpframework/abp-samples/tree/master/TextTemplateDemo) developed and referred through this document. * [Localization system](Localization.md). -* [Virtual File System](Virtual-File-System.md). \ No newline at end of file +* [Virtual File System](Virtual-File-System.md). diff --git a/docs/en/Tutorials/Part-1.md b/docs/en/Tutorials/Part-1.md index 5a38e821bc..98263f64f7 100644 --- a/docs/en/Tutorials/Part-1.md +++ b/docs/en/Tutorials/Part-1.md @@ -27,7 +27,7 @@ end In this tutorial series, you will build an ABP based web application named `Acme.BookStore`. This application is used to manage a list of books and their authors. It is developed using the following technologies: -* **{{DB_Text}}** as the ORM provider. +* **{{DB_Text}}** as the ORM provider. * **{{UI_Value}}** as the UI Framework. This tutorial is organized as the following parts; @@ -37,14 +37,27 @@ This tutorial is organized as the following parts; - [Part 3: Creating, updating and deleting books](Part-3.md) - [Part 4: Integration tests](Part-4.md) - [Part 5: Authorization](Part-5.md) +- [Part 6: Authors: Domain layer](Part-6.md) +- [Part 7: Authors: Database Integration](Part-7.md) +- [Part 8: Authors: Application Layer](Part-8.md) +- [Part 9: Authors: User Interface](Part-9.md) +- [Part 10: Book to Author Relation](Part-10.md) ### Download the Source Code -This tutorials has multiple versions based on your **UI** and **Database** preferences. We've prepared two combinations of the source code to be downloaded: +This tutorial has multiple versions based on your **UI** and **Database** preferences. We've prepared two combinations of the source code to be downloaded: * [MVC (Razor Pages) UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore) * [Angular UI with MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb) +{{if UI == "MVC" && DB == "EF"}} + +### Video Tutorial + +This part is also recorded as a video tutorial and **published on YouTube**. + +{{end}} + ## Creating the Solution Before starting to the development, create a new solution named `Acme.BookStore` and run it by following the [getting started tutorial](../Getting-Started.md). @@ -87,7 +100,7 @@ namespace Acme.BookStore.Books ### BookType Enum -The `Book` entity uses the `BookType` enum. Create the `BookType` in the `Acme.BookStore.Domain.Shared` project: +The `Book` entity uses the `BookType` enum. Create a `Books` folder (namespace) in the `Acme.BookStore.Domain.Shared` project and add a `BookType` inside it: ````csharp namespace Acme.BookStore.Books @@ -228,32 +241,30 @@ namespace Acme.BookStore public async Task SeedAsync(DataSeedContext context) { - if (await _bookRepository.GetCountAsync() > 0) + if (await _bookRepository.GetCountAsync() <= 0) { - return; + await _bookRepository.InsertAsync( + new Book + { + Name = "1984", + Type = BookType.Dystopia, + PublishDate = new DateTime(1949, 6, 8), + Price = 19.84f + }, + autoSave: true + ); + + await _bookRepository.InsertAsync( + new Book + { + Name = "The Hitchhiker's Guide to the Galaxy", + Type = BookType.ScienceFiction, + PublishDate = new DateTime(1995, 9, 27), + Price = 42.0f + }, + autoSave: true + ); } - - await _bookRepository.InsertAsync( - new Book - { - Name = "1984", - Type = BookType.Dystopia, - PublishDate = new DateTime(1949, 6, 8), - Price = 19.84f - }, - autoSave: true - ); - - await _bookRepository.InsertAsync( - new Book - { - Name = "The Hitchhiker's Guide to the Galaxy", - Type = BookType.ScienceFiction, - PublishDate = new DateTime(1995, 9, 27), - Price = 42.0f - }, - autoSave: true - ); } } } @@ -290,7 +301,7 @@ In this section, you will create an application service to get, create, update a ### BookDto -`CrudAppService` base class requires to define the fundamental DTOs for the entity. Create a DTO class named `BookDto` into the `Acme.BookStore.Application.Contracts` project: +`CrudAppService` base class requires to define the fundamental DTOs for the entity. Create a `Books` folder (namespace) in the `Acme.BookStore.Application.Contracts` project and add a `BookDto` class inside it: ````csharp using System; @@ -337,7 +348,7 @@ namespace Acme.BookStore ### CreateUpdateBookDto -Create another DTO class named `CreateUpdateBookDto` into the `Acme.BookStore.Application.Contracts` project: +Create a `CreateUpdateBookDto` class in the `Books` folder (namespace) of the `Acme.BookStore.Application.Contracts` project: ````csharp using System; @@ -388,7 +399,7 @@ namespace Acme.BookStore ### IBookAppService -Next step is to define an interface for the application service. Create an interface named `IBookAppService` in the `Acme.BookStore.Application.Contracts` project: +Next step is to define an interface for the application service. Create an `IBookAppService` interface in the `Books` folder (namespace) of the `Acme.BookStore.Application.Contracts` project: ````csharp using System; @@ -415,7 +426,7 @@ namespace Acme.BookStore.Books ### BookAppService -Implement the `IBookAppService`, as named `BookAppService`, in the `Acme.BookStore.Application` project: +It is time to implement the `IBookAppService` interface. Create a new class, named `BookAppService` in the `Books` namespace (folder) of the Acme.BookStore.Application project: ````csharp using System; diff --git a/docs/en/Tutorials/Part-10.md b/docs/en/Tutorials/Part-10.md new file mode 100644 index 0000000000..0fa4892298 --- /dev/null +++ b/docs/en/Tutorials/Part-10.md @@ -0,0 +1,920 @@ +# Web Application Development Tutorial - Part 10: Book to Author Relation +````json +//[doc-params] +{ + "UI": ["MVC","NG"], + "DB": ["EF","Mongo"] +} +```` +{{ +if UI == "MVC" + UI_Text="mvc" +else if UI == "NG" + UI_Text="angular" +else + UI_Text="?" +end +if DB == "EF" + DB_Text="Entity Framework Core" +else if DB == "Mongo" + DB_Text="MongoDB" +else + DB_Text="?" +end +}} + +## About This Tutorial + +In this tutorial series, you will build an ABP based web application named `Acme.BookStore`. This application is used to manage a list of books and their authors. It is developed using the following technologies: + +* **{{DB_Text}}** as the ORM provider. +* **{{UI_Value}}** as the UI Framework. + +This tutorial is organized as the following parts; + +- [Part 1: Creating the server side](Part-1.md) +- [Part 2: The book list page](Part-2.md) +- [Part 3: Creating, updating and deleting books](Part-3.md) +- [Part 4: Integration tests](Part-4.md) +- [Part 5: Authorization](Part-5.md) +- [Part 6: Authors: Domain layer](Part-6.md) +- [Part 7: Authors: Database Integration](Part-7.md) +- [Part 8: Authors: Application Layer](Part-8.md) +- [Part 9: Authors: User Interface](Part-9.md) +- **Part 10: Book to Author Relation (this part)** + +### 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: + +* [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) + +## Introduction + +We have created `Book` and `Author` functionalities for the book store application. However, currently there is no relation between these entities. + +In this tutorial, we will establish a **1 to N** relation between the `Book` and the `Author`. + +## Add Relation to The Book Entity + +Open the `Books/Book.cs` in the `Acme.BookStore.Domain` project and add the following property to the `Book` entity: + +````csharp +public Guid AuthorId { get; set; } +```` + +{{if DB=="EF"}} + +> In this tutorial, we preferred to not add a **navigation property** to the `Author` entity (like `public Author Author { get; set; }`). This is due to follow the DDD best practices (rule: refer to other aggregates only by id). However, you can add such a navigation property and configure it for the EF Core. In this way, you don't need to write join queries while getting books with their entities (just like we will done below) which makes your application code simpler. + +{{end}} + +## Database & Data Migration + +Added a new, required `AuthorId` property to the `Book` entity. But, what about the existing books on the database? They currently don't have `AuthorId`s and this will be a problem when we try to run the application. + +This is a typical migration problem and the decision depends on your case; + +* If you haven't published your application to the production yet, you can just delete existing books in the database, or you can even delete the entire database in your development environment. +* You can do it programmatically on data migration or seed phase. +* You can manually handle it on the database. + +We prefer to **delete the database** {{if DB=="EF"}}(run the `Drop-Database` in the *Package Manager Console*){{end}} since this is just an example project and data loss is not important. Since this topic is not related to the ABP Framework, we don't go deeper for all the scenarios. + +{{if DB=="EF"}} + +### Update the EF Core Mapping + +Open the `BookStoreDbContextModelCreatingExtensions` class under the `EntityFrameworkCore` folder of the `Acme.BookStore.EntityFrameworkCore` project and change the `builder.Entity` part as shown below: + +````csharp +builder.Entity(b => +{ + b.ToTable(BookStoreConsts.DbTablePrefix + "Books", BookStoreConsts.DbSchema); + b.ConfigureByConvention(); //auto configure for the base class props + b.Property(x => x.Name).IsRequired().HasMaxLength(128); + + // ADD THE MAPPING FOR THE RELATION + b.HasOne().WithMany().HasForeignKey(x => x.AuthorId).IsRequired(); +}); +```` + +### Add New EF Core Migration + +Run the following command in the Package Manager Console (of the Visual Studio) to add a new database migration: + +````bash +Add-Migration "Added_AuthorId_To_Book" +```` + +This should create a new migration class with the following code in its `Up` method: + +````csharp +migrationBuilder.AddColumn( + name: "AuthorId", + table: "AppBooks", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); + +migrationBuilder.CreateIndex( + name: "IX_AppBooks_AuthorId", + table: "AppBooks", + column: "AuthorId"); + +migrationBuilder.AddForeignKey( + name: "FK_AppBooks_AppAuthors_AuthorId", + table: "AppBooks", + column: "AuthorId", + principalTable: "AppAuthors", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); +```` + +* Adds an `AuthorId` field to the `AppBooks` table. +* Creates an index on the `AuthorId` field. +* Declares the foreign key to the `AppAuthors` table. + +{{end}} + +## Change the Data Seeder + +Since the `AuthorId` is a required property of the `Book` entity, current data seeder code can not work. Open the `BookStoreDataSeederContributor` in the `Acme.BookStore.Domain` project and change as the following: + +````csharp +using System; +using System.Threading.Tasks; +using Acme.BookStore.Authors; +using Acme.BookStore.Books; +using Volo.Abp.Data; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Repositories; + +namespace Acme.BookStore +{ + public class BookStoreDataSeederContributor + : IDataSeedContributor, ITransientDependency + { + private readonly IRepository _bookRepository; + private readonly IAuthorRepository _authorRepository; + private readonly AuthorManager _authorManager; + + public BookStoreDataSeederContributor( + IRepository bookRepository, + IAuthorRepository authorRepository, + AuthorManager authorManager) + { + _bookRepository = bookRepository; + _authorRepository = authorRepository; + _authorManager = authorManager; + } + + public async Task SeedAsync(DataSeedContext context) + { + if (await _bookRepository.GetCountAsync() > 0) + { + return; + } + + var orwell = await _authorRepository.InsertAsync( + await _authorManager.CreateAsync( + "George Orwell", + new DateTime(1903, 06, 25), + "Orwell produced literary criticism and poetry, fiction and polemical journalism; and is best known for the allegorical novella Animal Farm (1945) and the dystopian novel Nineteen Eighty-Four (1949)." + ) + ); + + var douglas = await _authorRepository.InsertAsync( + await _authorManager.CreateAsync( + "Douglas Adams", + new DateTime(1952, 03, 11), + "Douglas Adams was an English author, screenwriter, essayist, humorist, satirist and dramatist. Adams was an advocate for environmentalism and conservation, a lover of fast cars, technological innovation and the Apple Macintosh, and a self-proclaimed 'radical atheist'." + ) + ); + + await _bookRepository.InsertAsync( + new Book + { + AuthorId = orwell.Id, // SET THE AUTHOR + Name = "1984", + Type = BookType.Dystopia, + PublishDate = new DateTime(1949, 6, 8), + Price = 19.84f + }, + autoSave: true + ); + + await _bookRepository.InsertAsync( + new Book + { + AuthorId = douglas.Id, // SET THE AUTHOR + Name = "The Hitchhiker's Guide to the Galaxy", + Type = BookType.ScienceFiction, + PublishDate = new DateTime(1995, 9, 27), + Price = 42.0f + }, + autoSave: true + ); + } + } +} +```` + +The only change is that we set the `AuthorId` properties of the `Book` entities. + +{{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}} + +## Application Layer + +We will change the `BookAppService` to support the Author relation. + +### Data Transfer Objects + +Let's begin from the DTOs. + +#### BookDto + +Open the `BookDto` class in the `Books` folder of the `Acme.BookStore.Application.Contracts` project and add the following properties: + +```csharp +public Guid AuthorId { get; set; } +public string AuthorName { get; set; } +``` + +The final `BookDto` class should be following: + +```csharp +using System; +using Volo.Abp.Application.Dtos; + +namespace Acme.BookStore.Books +{ + public class BookDto : AuditedEntityDto + { + public Guid AuthorId { get; set; } + + public string AuthorName { get; set; } + + public string Name { get; set; } + + public BookType Type { get; set; } + + public DateTime PublishDate { get; set; } + + public float Price { get; set; } + } +} +``` + +#### CreateUpdateBookDto + +Open the `CreateUpdateBookDto` class in the `Books` folder of the `Acme.BookStore.Application.Contracts` project and add an `AuthorId` property as shown: + +````csharp +public Guid AuthorId { get; set; } +```` + +#### AuthorLookupDto + +Create a new class, `AuthorLookupDto`, inside the `Books` folder of the `Acme.BookStore.Application.Contracts` project: + +````csharp +using System; +using Volo.Abp.Application.Dtos; + +namespace Acme.BookStore.Books +{ + public class AuthorLookupDto : EntityDto + { + public string Name { get; set; } + } +} +```` + +This will be used in a new method will be added to the `IBookAppService`. + +### IBookAppService + +Open the `IBookAppService` interface in the `Books` folder of the `Acme.BookStore.Application.Contracts` project and add a new method, named `GetAuthorLookupAsync`, as shown below: + +````csharp +using System; +using System.Threading.Tasks; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Application.Services; + +namespace Acme.BookStore.Books +{ + public interface IBookAppService : + ICrudAppService< //Defines CRUD methods + BookDto, //Used to show books + Guid, //Primary key of the book entity + PagedAndSortedResultRequestDto, //Used for paging/sorting + CreateUpdateBookDto> //Used to create/update a book + { + // ADD the NEW METHOD + Task> GetAuthorLookupAsync(); + } +} +```` + +This new method will be used from the UI to get a list of authors and fill a dropdown list to select the author of a book. + +### BookAppService + +Open the `BookAppService` interface in the `Books` folder of the `Acme.BookStore.Application` project and replace the file content with the following code: + +{{if DB=="EF"}} + +```csharp +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Acme.BookStore.Authors; +using Acme.BookStore.Permissions; +using Microsoft.AspNetCore.Authorization; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Application.Services; +using Volo.Abp.Domain.Entities; +using Volo.Abp.Domain.Repositories; + +namespace Acme.BookStore.Books +{ + [Authorize(BookStorePermissions.Books.Default)] + public class BookAppService : + CrudAppService< + Book, //The Book entity + BookDto, //Used to show books + Guid, //Primary key of the book entity + PagedAndSortedResultRequestDto, //Used for paging/sorting + CreateUpdateBookDto>, //Used to create/update a book + IBookAppService //implement the IBookAppService + { + private readonly IAuthorRepository _authorRepository; + + public BookAppService( + IRepository repository, + IAuthorRepository authorRepository) + : base(repository) + { + _authorRepository = authorRepository; + GetPolicyName = BookStorePermissions.Books.Default; + GetListPolicyName = BookStorePermissions.Books.Default; + CreatePolicyName = BookStorePermissions.Books.Create; + UpdatePolicyName = BookStorePermissions.Books.Edit; + DeletePolicyName = BookStorePermissions.Books.Create; + } + + public override async Task GetAsync(Guid id) + { + //Prepare a query to join books and authors + var query = from book in Repository + join author in _authorRepository on book.AuthorId equals author.Id + where book.Id == id + select new { book, author }; + + //Execute the query and get the book with author + var queryResult = await AsyncExecuter.FirstOrDefaultAsync(query); + if (queryResult == null) + { + throw new EntityNotFoundException(typeof(Book), id); + } + + var bookDto = ObjectMapper.Map(queryResult.book); + bookDto.AuthorName = queryResult.author.Name; + return bookDto; + } + + public override async Task> + GetListAsync(PagedAndSortedResultRequestDto input) + { + //Prepare a query to join books and authors + var query = from book in Repository + join author in _authorRepository on book.AuthorId equals author.Id + orderby input.Sorting + select new {book, author}; + + query = query + .Skip(input.SkipCount) + .Take(input.MaxResultCount); + + //Execute the query and get a list + var queryResult = await AsyncExecuter.ToListAsync(query); + + //Convert the query result to a list of BookDto objects + var bookDtos = queryResult.Select(x => + { + var bookDto = ObjectMapper.Map(x.book); + bookDto.AuthorName = x.author.Name; + return bookDto; + }).ToList(); + + //Get the total count with another query + var totalCount = await Repository.GetCountAsync(); + + return new PagedResultDto( + totalCount, + bookDtos + ); + } + + public async Task> GetAuthorLookupAsync() + { + var authors = await _authorRepository.GetListAsync(); + + return new ListResultDto( + ObjectMapper.Map, List>(authors) + ); + } + } +} +``` + +Let's see the changes we've done: + +* Added `[Authorize(BookStorePermissions.Books.Default)]` to authorize the methods we've newly added/overrode (remember, authorize attribute is valid for all the methods of the class when it is declared for a class). +* Injected `IAuthorRepository` to query from the authors. +* Overrode the `GetAsync` method of the base `CrudAppService`, which returns a single `BookDto` object with the given `id`. + * Used a simple LINQ expression to join books and authors and query them together for the given book id. + * Used `AsyncExecuter.FirstOrDefaultAsync(...)` to execute the query and get a result. `AsyncExecuter` was previously used in the `AuthorAppService`. Check the [repository documentation](../Repositories.md) to understand why we've used it. + * Throws an `EntityNotFoundException` which results an `HTTP 404` (not found) result if requested book was not present in the database. + * Finally, created a `BookDto` object using the `ObjectMapper`, then assigning the `AuthorName` manually. +* Overrode the `GetListAsync` method of the base `CrudAppService`, which returns a list of books. The logic is similar to the previous method, so you can easily understand the code. +* Created a new method: `GetAuthorLookupAsync`. This simple gets all the authors. The UI uses this method to fill a dropdown list and select and author while creating/editing books. + +{{else if DB=="Mongo"}} + +```csharp +using System; +using System.Collections.Generic; +using System.Linq.Dynamic.Core; +using System.Linq; +using System.Threading.Tasks; +using Acme.BookStore.Authors; +using Acme.BookStore.Permissions; +using Microsoft.AspNetCore.Authorization; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Application.Services; +using Volo.Abp.Domain.Repositories; + +namespace Acme.BookStore.Books +{ + [Authorize(BookStorePermissions.Books.Default)] + public class BookAppService : + CrudAppService< + Book, //The Book entity + BookDto, //Used to show books + Guid, //Primary key of the book entity + PagedAndSortedResultRequestDto, //Used for paging/sorting + CreateUpdateBookDto>, //Used to create/update a book + IBookAppService //implement the IBookAppService + { + private readonly IAuthorRepository _authorRepository; + + public BookAppService( + IRepository repository, + IAuthorRepository authorRepository) + : base(repository) + { + _authorRepository = authorRepository; + GetPolicyName = BookStorePermissions.Books.Default; + GetListPolicyName = BookStorePermissions.Books.Default; + CreatePolicyName = BookStorePermissions.Books.Create; + UpdatePolicyName = BookStorePermissions.Books.Edit; + DeletePolicyName = BookStorePermissions.Books.Create; + } + + public override async Task GetAsync(Guid id) + { + var book = await Repository.GetAsync(id); + var bookDto = ObjectMapper.Map(book); + + var author = await _authorRepository.GetAsync(book.AuthorId); + bookDto.AuthorName = author.Name; + + return bookDto; + } + + public override async Task> + GetListAsync(PagedAndSortedResultRequestDto input) + { + //Set a default sorting, if not provided + if (input.Sorting.IsNullOrWhiteSpace()) + { + input.Sorting = nameof(Book.Name); + } + + //Get the books + var books = await AsyncExecuter.ToListAsync( + Repository + .OrderBy(input.Sorting) + .Skip(input.SkipCount) + .Take(input.MaxResultCount) + ); + + //Convert to DTOs + var bookDtos = ObjectMapper.Map, List>(books); + + //Get a lookup dictionary for the related authors + var authorDictionary = await GetAuthorDictionaryAsync(books); + + //Set AuthorName for the DTOs + bookDtos.ForEach(bookDto => bookDto.AuthorName = + authorDictionary[bookDto.AuthorId].Name); + + //Get the total count with another query (required for the paging) + var totalCount = await Repository.GetCountAsync(); + + return new PagedResultDto( + totalCount, + bookDtos + ); + } + + public async Task> GetAuthorLookupAsync() + { + var authors = await _authorRepository.GetListAsync(); + + return new ListResultDto( + ObjectMapper.Map, List>(authors) + ); + } + + private async Task> + GetAuthorDictionaryAsync(List books) + { + var authorIds = books + .Select(b => b.AuthorId) + .Distinct() + .ToArray(); + + var authors = await AsyncExecuter.ToListAsync( + _authorRepository.Where(a => authorIds.Contains(a.Id)) + ); + + return authors.ToDictionary(x => x.Id, x => x); + } + } +} +``` + +Let's see the changes we've done: + +* Added `[Authorize(BookStorePermissions.Books.Default)]` to authorize the methods we've newly added/overrode (remember, authorize attribute is valid for all the methods of the class when it is declared for a class). +* Injected `IAuthorRepository` to query from the authors. +* Overrode the `GetAsync` method of the base `CrudAppService`, which returns a single `BookDto` object with the given `id`. +* Overrode the `GetListAsync` method of the base `CrudAppService`, which returns a list of books. This code separately queries the authors from database and sets the name of the authors in the application code. Instead, you could create a custom repository method and perform a join query or take the power of the MongoDB API to get the books and their authors in a single query, which would be more performant. +* Created a new method: `GetAuthorLookupAsync`. This simple gets all the authors. The UI uses this method to fill a dropdown list and select and author while creating/editing books. + +{{end}} + +### Object to Object Mapping Configuration + +Introduced the `AuthorLookupDto` class and used object mapping inside the `GetAuthorLookupAsync` method. So, we need to add a new mapping definition inside the `BookStoreApplicationAutoMapperProfile.cs` file of the `Acme.BookStore.Application` project: + +````csharp +CreateMap(); +```` + +## Unit Tests + +Some of the unit tests will fail since we made some changed on the `AuthorAppService`. Open the `BookAppService_Tests` in the `Books` folder of the `Acme.BookStore.Application.Tests` project and change the content as the following: + +```csharp +using System; +using System.Linq; +using System.Threading.Tasks; +using Acme.BookStore.Authors; +using Shouldly; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Validation; +using Xunit; + +namespace Acme.BookStore.Books +{ {{if DB=="Mongo"}} + [Collection(BookStoreTestConsts.CollectionDefinitionName)]{{end}} + public class BookAppService_Tests : BookStoreApplicationTestBase + { + private readonly IBookAppService _bookAppService; + private readonly IAuthorAppService _authorAppService; + + public BookAppService_Tests() + { + _bookAppService = GetRequiredService(); + _authorAppService = GetRequiredService(); + } + + [Fact] + public async Task Should_Get_List_Of_Books() + { + //Act + var result = await _bookAppService.GetListAsync( + new PagedAndSortedResultRequestDto() + ); + + //Assert + result.TotalCount.ShouldBeGreaterThan(0); + result.Items.ShouldContain(b => b.Name == "1984" && + b.AuthorName == "George Orwell"); + } + + [Fact] + public async Task Should_Create_A_Valid_Book() + { + var authors = await _authorAppService.GetListAsync(new GetAuthorListDto()); + var firstAuthor = authors.Items.First(); + + //Act + var result = await _bookAppService.CreateAsync( + new CreateUpdateBookDto + { + AuthorId = firstAuthor.Id, + Name = "New test book 42", + Price = 10, + PublishDate = System.DateTime.Now, + Type = BookType.ScienceFiction + } + ); + + //Assert + result.Id.ShouldNotBe(Guid.Empty); + result.Name.ShouldBe("New test book 42"); + } + + [Fact] + public async Task Should_Not_Create_A_Book_Without_Name() + { + var exception = await Assert.ThrowsAsync(async () => + { + await _bookAppService.CreateAsync( + new CreateUpdateBookDto + { + Name = "", + Price = 10, + PublishDate = DateTime.Now, + Type = BookType.ScienceFiction + } + ); + }); + + exception.ValidationErrors + .ShouldContain(err => err.MemberNames.Any(m => m == "Name")); + } + } +} +``` + +* Changed the assertion condition in the `Should_Get_List_Of_Books` from `b => b.Name == "1984"` to `b => b.Name == "1984" && b.AuthorName == "George Orwell"` to check if the author name was filled. +* Changed the `Should_Create_A_Valid_Book` method to set the `AuthorId` while creating a new book, since it is required anymore. + +## The User Interface + +{{if UI=="MVC"}} + +### The Book List + +Book list page change is trivial. Open the `Pages/Books/Index.js` in the `Acme.BookStore.Web` project and add the following column definition between the `name` and `type` columns: + +````js +... +{ + title: l('Name'), + data: "name" +}, + +// ADDED the NEW AUTHOR NAME COLUMN +{ + title: l('Author'), + data: "authorName" +}, + +{ + title: l('Type'), + data: "type", + render: function (data) { + return l('Enum:BookType:' + data); + } +}, +... +```` + +When you run the application, you can see the *Author* column on the table: + +![bookstore-added-author-to-book-list](images/bookstore-added-author-to-book-list.png) + +### Create Modal + +Open the `Pages/Books/CreateModal.cshtml.cs` in the `Acme.BookStore.Web` project and change the file content as shown below: + +```csharp +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; +using Acme.BookStore.Books; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Rendering; +using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form; + +namespace Acme.BookStore.Web.Pages.Books +{ + public class CreateModalModel : BookStorePageModel + { + [BindProperty] + public CreateBookViewModel Book { get; set; } + + public List Authors { get; set; } + + private readonly IBookAppService _bookAppService; + + public CreateModalModel( + IBookAppService bookAppService) + { + _bookAppService = bookAppService; + } + + public async Task OnGetAsync() + { + Book = new CreateBookViewModel(); + + var authorLookup = await _bookAppService.GetAuthorLookupAsync(); + Authors = authorLookup.Items + .Select(x => new SelectListItem(x.Name, x.Id.ToString())) + .ToList(); + } + + public async Task OnPostAsync() + { + await _bookAppService.CreateAsync( + ObjectMapper.Map(Book) + ); + return NoContent(); + } + + public class CreateBookViewModel + { + [SelectItems(nameof(Authors))] + [DisplayName("Author")] + public Guid AuthorId { get; set; } + + [Required] + [StringLength(128)] + public string Name { get; set; } + + [Required] + public BookType Type { get; set; } = BookType.Undefined; + + [Required] + [DataType(DataType.Date)] + public DateTime PublishDate { get; set; } = DateTime.Now; + + [Required] + public float Price { get; set; } + } + } +} +``` + +* Changed type of the `Book` property from `CreateUpdateBookDto` to the new `CreateBookViewModel` class defined in this file. The main motivation of this change to customize the model class based on the User Interface (UI) requirements. We didn't want to use UI-related `[SelectItems(nameof(Authors))]` and `[DisplayName("Author")]` attributes inside the `CreateUpdateBookDto` class. +* Added `Authors` property that is filled inside the `OnGetAsync` method using the `IBookAppService.GetAuthorLookupAsync` method defined before. +* Changed the `OnPostAsync` method to map `CreateBookViewModel` object to a `CreateUpdateBookDto` object since `IBookAppService.CreateAsync` expects a parameter of this type. + +### Edit Modal + +Open the `Pages/Books/EditModal.cshtml.cs` in the `Acme.BookStore.Web` project and change the file content as shown below: + +```csharp +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; +using Acme.BookStore.Books; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Rendering; +using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form; + +namespace Acme.BookStore.Web.Pages.Books +{ + public class EditModalModel : BookStorePageModel + { + [BindProperty] + public EditBookViewModel Book { get; set; } + + public List Authors { get; set; } + + private readonly IBookAppService _bookAppService; + + public EditModalModel(IBookAppService bookAppService) + { + _bookAppService = bookAppService; + } + + public async Task OnGetAsync(Guid id) + { + var bookDto = await _bookAppService.GetAsync(id); + Book = ObjectMapper.Map(bookDto); + + var authorLookup = await _bookAppService.GetAuthorLookupAsync(); + Authors = authorLookup.Items + .Select(x => new SelectListItem(x.Name, x.Id.ToString())) + .ToList(); + } + + public async Task OnPostAsync() + { + await _bookAppService.UpdateAsync( + Book.Id, + ObjectMapper.Map(Book) + ); + + return NoContent(); + } + + public class EditBookViewModel + { + [HiddenInput] + public Guid Id { get; set; } + + [SelectItems(nameof(Authors))] + [DisplayName("Author")] + public Guid AuthorId { get; set; } + + [Required] + [StringLength(128)] + public string Name { get; set; } + + [Required] + public BookType Type { get; set; } = BookType.Undefined; + + [Required] + [DataType(DataType.Date)] + public DateTime PublishDate { get; set; } = DateTime.Now; + + [Required] + public float Price { get; set; } + } + } +} +``` + +* Changed type of the `Book` property from `CreateUpdateBookDto` to the new `EditBookViewModel` class defined in this file, just like done before for the create modal above. +* Moved the `Id` property inside the new `EditBookViewModel` class. +* Added `Authors` property that is filled inside the `OnGetAsync` method using the `IBookAppService.GetAuthorLookupAsync` method. +* Changed the `OnPostAsync` method to map `EditBookViewModel` object to a `CreateUpdateBookDto` object since `IBookAppService.UpdateAsync` expects a parameter of this type. + +These changes require a small change in the `EditModal.cshtml`. Remove the `` tag since we no longer need to it (since moved it to the `EditBookViewModel`). The final content of the `EditModal.cshtml` should be following: + +````html +@page +@using Acme.BookStore.Localization +@using Acme.BookStore.Web.Pages.Books +@using Microsoft.Extensions.Localization +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal +@model EditModalModel +@inject IStringLocalizer L +@{ + Layout = null; +} + + + + + + + + + +```` + +### Object to Object Mapping Configuration + +The changes above requires to define some object to object mappings. Open the `BookStoreWebAutoMapperProfile.cs` in the `Acme.BookStore.Web` project and add the following mapping definitions inside the constructor: + +```csharp +CreateMap(); +CreateMap(); +CreateMap(); +``` + +You can run the application and try to create a new book or update an existing book. You will see a drop down list on the create/update form to select the author of the book: + +![bookstore-added-authors-to-modals](images/bookstore-added-authors-to-modals.png) + +{{else if UI=="NG"}} + +***Angular UI is being prepared...*** + +{{end}} \ No newline at end of file diff --git a/docs/en/Tutorials/Part-2.md b/docs/en/Tutorials/Part-2.md index 690394264c..c947f4495d 100644 --- a/docs/en/Tutorials/Part-2.md +++ b/docs/en/Tutorials/Part-2.md @@ -37,21 +37,34 @@ This tutorial is organized as the following parts; - [Part 3: Creating, updating and deleting books](Part-3.md) - [Part 4: Integration tests](Part-4.md) - [Part 5: Authorization](Part-5.md) +- [Part 6: Authors: Domain layer](Part-6.md) +- [Part 7: Authors: Database Integration](Part-7.md) +- [Part 8: Authors: Application Layer](Part-8.md) +- [Part 9: Authors: User Interface](Part-9.md) +- [Part 10: Book to Author Relation](Part-10.md) ### Download the Source Code -This tutorials has multiple versions based on your **UI** and **Database** preferences. We've prepared two combinations of the source code to be downloaded: +This tutorial has multiple versions based on your **UI** and **Database** preferences. We've prepared two combinations of the source code to be downloaded: * [MVC (Razor Pages) UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore) * [Angular UI with MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb) +{{if UI == "MVC" && DB == "EF"}} + +### Video Tutorial + +This part is also recorded as a video tutorial and **published on YouTube**. + +{{end}} + {{if UI == "MVC"}} ## Dynamic JavaScript Proxies It's common to call the HTTP API endpoints via AJAX from the **JavaScript** side. You can use `$.ajax` or another tool to call the endpoints. However, ABP offers a better way. -ABP **dynamically** creates **[JavaScript Proxies](../UI/AspNetCore/)** for all API endpoints. So, you can use any **endpoint** just like calling a **JavaScript function**. +ABP **dynamically** creates **[JavaScript Proxies](../UI/AspNetCore/Dynamic-JavaScript-Proxies.md)** for all API endpoints. So, you can use any **endpoint** just like calling a **JavaScript function**. ### Testing in the Developer Console @@ -457,12 +470,9 @@ For more information, see the [RoutesService document](https://docs.abp.io/en/ab Run the following command in the `angular` folder: ```bash -abp generate-proxy --apiUrl https://localhost:XXXXX +abp generate-proxy ``` -* XXXXX should be replaced with the backend port of your application. -* If you don't specify the `--apiUrl` parameter, it will try to get the URL from the `src/environments/environment.ts` file. - The generated files looks like below: ![Generated files](./images/generated-proxies-2.png) @@ -474,7 +484,7 @@ Open the `/src/app/book/book.component.ts` file and replace the content as below ```js import { ListService, PagedResultDto } from '@abp/ng.core'; import { Component, OnInit } from '@angular/core'; -import { BookDto, BookType } from './models'; +import { BookDto } from './models'; import { BookService } from './services'; @Component({ @@ -486,8 +496,6 @@ import { BookService } from './services'; export class BookComponent implements OnInit { book = { items: [], totalCount: 0 } as PagedResultDto; - booksType = BookType; - constructor(public readonly list: ListService, private bookService: BookService) {} ngOnInit() { diff --git a/docs/en/Tutorials/Part-3.md b/docs/en/Tutorials/Part-3.md index 3b31ec1c88..13aa5df8c3 100644 --- a/docs/en/Tutorials/Part-3.md +++ b/docs/en/Tutorials/Part-3.md @@ -32,19 +32,32 @@ In this tutorial series, you will build an ABP based web application named `Acme This tutorial is organized as the following parts; -- [Part 1: Creating the project and book list page](Part-1.md) +- [Part 1: Creating the server side](Part-1.md) - [Part 2: The book list page](Part-2.md) - **Part 3: Creating, updating and deleting books (this part)** - [Part 4: Integration tests](Part-4.md) - [Part 5: Authorization](Part-5.md) +- [Part 6: Authors: Domain layer](Part-6.md) +- [Part 7: Authors: Database Integration](Part-7.md) +- [Part 8: Authors: Application Layer](Part-8.md) +- [Part 9: Authors: User Interface](Part-9.md) +- [Part 10: Book to Author Relation](Part-10.md) ### Download the Source Code -This tutorials has multiple versions based on your **UI** and **Database** preferences. We've prepared two combinations of the source code to be downloaded: +This tutorial has multiple versions based on your **UI** and **Database** preferences. We've prepared two combinations of the source code to be downloaded: * [MVC (Razor Pages) UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore) * [Angular UI with MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb) +{{if UI == "MVC" && DB == "EF"}} + +### Video Tutorial + +This part is also recorded as a video tutorial and **published on YouTube**. + +{{end}} + {{if UI == "MVC"}} ## Creating a New Book @@ -643,7 +656,7 @@ Open `/src/app/book/book.component.ts` and replace the content as below: ```js import { ListService, PagedResultDto } from '@abp/ng.core'; import { Component, OnInit } from '@angular/core'; -import { BookDto, BookType } from './models'; +import { BookDto } from './models'; import { BookService } from './services'; @Component({ @@ -655,8 +668,6 @@ import { BookService } from './services'; export class BookComponent implements OnInit { book = { items: [], totalCount: 0 } as PagedResultDto; - booksType = BookType; - isModalOpen = false; // add this line constructor(public readonly list: ListService, private bookService: BookService) {} @@ -738,7 +749,7 @@ Open `/src/app/book/book.component.ts` and replace the content as below: ```js import { ListService, PagedResultDto } from '@abp/ng.core'; import { Component, OnInit } from '@angular/core'; -import { BookDto, BookType } from './models'; +import { BookDto, BookType } from './models'; // add BookType import { BookService } from './services'; import { FormGroup, FormBuilder, Validators } from '@angular/forms'; // add this @@ -751,13 +762,13 @@ import { FormGroup, FormBuilder, Validators } from '@angular/forms'; // add this export class BookComponent implements OnInit { book = { items: [], totalCount: 0 } as PagedResultDto; - booksType = BookType; - form: FormGroup; // add this line - // add bookTypes as a list of enum members - bookTypes = Object.keys(BookType).filter( - (bookType) => typeof this.booksType[bookType] === 'number' + bookType = BookType; // add this line + + // add bookTypes as a list of BookType enum members + bookTypes = Object.keys(this.bookType).filter( + (key) => typeof this.bookType[key] === 'number' ); isModalOpen = false; @@ -808,7 +819,8 @@ export class BookComponent implements OnInit { * Imported `FormGroup`, `FormBuilder` and `Validators` from `@angular/forms`. * Added `form: FormGroup` property. -* Add `bookTypes` as a list of `BookType` enum members. +* Added `bookType` property so that you can reach `BookType` enum members from template. +* Added `bookTypes` property as a list of `BookType` enum members. That will be used in form options. * Injected `FormBuilder` into the constructor. [FormBuilder](https://angular.io/api/forms/FormBuilder) provides convenient methods for generating form controls. It reduces the amount of boilerplate needed to build complex forms. * Added `buildForm` method to the end of the file and executed the `buildForm()` in the `createBook` method. * Added `save` method. @@ -832,7 +844,7 @@ Open `/src/app/book/book.component.html` and replace ` Type * @@ -917,13 +929,12 @@ import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap export class BookComponent implements OnInit { book = { items: [], totalCount: 0 } as PagedResultDto; - booksType = BookType; - form: FormGroup; - // <== added bookTypes array ==> - bookTypes = Object.keys(BookType).filter( - (bookType) => typeof this.booksType[bookType] === 'number' + bookType = BookType; + + bookTypes = Object.keys(this.bookType).filter( + (key) => typeof this.bookType[key] === 'number' ); isModalOpen = false; @@ -998,14 +1009,14 @@ import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap export class BookComponent implements OnInit { book = { items: [], totalCount: 0 } as PagedResultDto; - booksType = BookType; + selectedBook = new BookDto(); // declare selectedBook form: FormGroup; - selectedBook = new BookDto(); // declare selectedBook + bookType = BookType; - bookTypes = Object.keys(BookType).filter( - (bookType) => typeof this.booksType[bookType] === 'number' + bookTypes = Object.keys(this.bookType).filter( + (key) => typeof this.bookType[key] === 'number' ); isModalOpen = false; @@ -1182,4 +1193,4 @@ Clicking the "Delete" action calls the `delete` method which then shows a confir ## The Next Part -See the [next part](part-4.md) of this tutorial. +See the [next part](Part-4.md) of this tutorial. diff --git a/docs/en/Tutorials/Part-4.md b/docs/en/Tutorials/Part-4.md index 5e87d18653..2868df8ba6 100644 --- a/docs/en/Tutorials/Part-4.md +++ b/docs/en/Tutorials/Part-4.md @@ -32,19 +32,32 @@ In this tutorial series, you will build an ABP based web application named `Acme This tutorial is organized as the following parts; -- [Part 1: Creating the project and book list page](Part-1.md) +- [Part 1: Creating the server side](Part-1.md) - [Part 2: The book list page](Part-2.md) - [Part 3: Creating, updating and deleting books](Part-3.md) - **Part 4: Integration tests (this part)** - [Part 5: Authorization](Part-5.md) +- [Part 6: Authors: Domain layer](Part-6.md) +- [Part 7: Authors: Database Integration](Part-7.md) +- [Part 8: Authors: Application Layer](Part-8.md) +- [Part 9: Authors: User Interface](Part-9.md) +- [Part 10: Book to Author Relation](Part-10.md) ### Download the Source Code -This tutorials has multiple versions based on your **UI** and **Database** preferences. We've prepared two combinations of the source code to be downloaded: +This tutorial has multiple versions based on your **UI** and **Database** preferences. We've prepared two combinations of the source code to be downloaded: * [MVC (Razor Pages) UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore) * [Angular UI with MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb) +{{if UI == "MVC" && DB == "EF"}} + +### Video Tutorial + +This part is also recorded as a video tutorial and **published on YouTube**. + +{{end}} + ## Test Projects in the Solution This part covers the **server side** tests. There are several test projects in the solution: @@ -73,7 +86,7 @@ If you had created a data seed contributor as described in the [first part](Part ## Testing the BookAppService -Create a test class named `BookAppService_Tests` in the `Acme.BookStore.Application.Tests` project: +Add a new test class, named `BookAppService_Tests` in the `Books` namespace (folder) of the `Acme.BookStore.Application.Tests` project: ````csharp using System.Threading.Tasks; @@ -247,4 +260,4 @@ Congratulations, the **green icons** indicates that the tests have been successf ## The Next Part -See the [next part](part-5.md) of this tutorial. \ No newline at end of file +See the [next part](Part-5.md) of this tutorial. \ No newline at end of file diff --git a/docs/en/Tutorials/Part-5.md b/docs/en/Tutorials/Part-5.md index 537f79ca79..5b2dfc0431 100644 --- a/docs/en/Tutorials/Part-5.md +++ b/docs/en/Tutorials/Part-5.md @@ -32,19 +32,32 @@ In this tutorial series, you will build an ABP based web application named `Acme This tutorial is organized as the following parts; -- [Part 1: Creating the project and book list page](Part-1.md) +- [Part 1: Creating the server side](Part-1.md) - [Part 2: The book list page](Part-2.md) - [Part 3: Creating, updating and deleting books](Part-3.md) - [Part 4: Integration tests](Part-4.md) - **Part 5: Authorization (this part)** +- [Part 6: Authors: Domain layer](Part-6.md) +- [Part 7: Authors: Database Integration](Part-7.md) +- [Part 8: Authors: Application Layer](Part-8.md) +- [Part 9: Authors: User Interface](Part-9.md) +- [Part 10: Book to Author Relation](Part-10.md) ### Download the Source Code -This tutorials has multiple versions based on your **UI** and **Database** preferences. We've prepared two combinations of the source code to be downloaded: +This tutorial has multiple versions based on your **UI** and **Database** preferences. We've prepared two combinations of the source code to be downloaded: * [MVC (Razor Pages) UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore) * [Angular UI with MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb) +{{if UI == "MVC" && DB == "EF"}} + +### Video Tutorial + +This part is also recorded as a video tutorial and **published on YouTube**. + +{{end}} + ## Permissions ABP Framework provides an [authorization system](../Authorization.md) based on the ASP.NET Core's [authorization infrastructure](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/introduction). One major feature added on top of the standard authorization infrastructure is the **permission system** which allows to define permissions and enable/disable per role, user or client. @@ -53,7 +66,7 @@ ABP Framework provides an [authorization system](../Authorization.md) based on t A permission must have a unique name (a `string`). The best way is to define it as a `const`, so we can reuse the permission name. -Open the `BookStorePermissions` class inside the `Acme.BookStore.Application.Contracts` project and change the content as shown below: +Open the `BookStorePermissions` class inside the `Acme.BookStore.Application.Contracts` project (in the `Permissions` folder) and change the content as shown below: ````csharp namespace Acme.BookStore.Permissions @@ -79,7 +92,7 @@ This is a hierarchical way of defining permission names. For example, "create bo You should define permissions before using them. -Open the `BookStorePermissionDefinitionProvider` class inside the `Acme.BookStore.Application.Contracts` project and change the content as shown below: +Open the `BookStorePermissionDefinitionProvider` class inside the `Acme.BookStore.Application.Contracts` project (in the `Permissions` folder) and change the content as shown below: ````csharp using Acme.BookStore.Localization; @@ -399,3 +412,6 @@ Open the `/src/app/book/book.component.html` file and replace the edit and delet {{end}} +## The Next Part + +See the [next part](Part-6.md) of this tutorial. \ No newline at end of file diff --git a/docs/en/Tutorials/Part-6.md b/docs/en/Tutorials/Part-6.md new file mode 100644 index 0000000000..b1b38f18dc --- /dev/null +++ b/docs/en/Tutorials/Part-6.md @@ -0,0 +1,298 @@ +# Web Application Development Tutorial - Part 6: Authors: Domain Layer +````json +//[doc-params] +{ + "UI": ["MVC","NG"], + "DB": ["EF","Mongo"] +} +```` +{{ +if UI == "MVC" + UI_Text="mvc" +else if UI == "NG" + UI_Text="angular" +else + UI_Text="?" +end +if DB == "EF" + DB_Text="Entity Framework Core" +else if DB == "Mongo" + DB_Text="MongoDB" +else + DB_Text="?" +end +}} + +## About This Tutorial + +In this tutorial series, you will build an ABP based web application named `Acme.BookStore`. This application is used to manage a list of books and their authors. It is developed using the following technologies: + +* **{{DB_Text}}** as the ORM provider. +* **{{UI_Value}}** as the UI Framework. + +This tutorial is organized as the following parts; + +- [Part 1: Creating the server side](Part-1.md) +- [Part 2: The book list page](Part-2.md) +- [Part 3: Creating, updating and deleting books](Part-3.md) +- [Part 4: Integration tests](Part-4.md) +- [Part 5: Authorization](Part-5.md) +- **Part 6: Authors: Domain layer (this part)** +- [Part 7: Authors: Database Integration](Part-7.md) +- [Part 8: Authors: Application Layer](Part-8.md) +- [Part 9: Authors: User Interface](Part-9.md) +- [Part 10: Book to Author Relation](Part-10.md) + +### 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: + +* [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) + +## Introduction + +In the previous parts, we've used the ABP infrastructure to easily build some services; + +* Used the [CrudAppService](../Application-Services.md) base class instead of manually developing an application service for standard create, read, update and delete operations. +* Used [generic repositories](../Repositories.md) to completely automate the database layer. + +For the "Authors" part; + +* We will **do some of the things manually** to show how you can do it in case of need. +* We will implement some **Domain Driven Design (DDD) best practices**. + +> **The development will be done layer by layer to concentrate on an individual layer in one time. In a real project, you will develop your application feature by feature (vertical) as done in the previous parts. In this way, you will experience both approaches.** + +## The Author Entity + +Create an `Authors` folder (namespace) in the `Acme.BookStore.Domain` project and add an `Author` class inside it: + +````csharp +using System; +using JetBrains.Annotations; +using Volo.Abp; +using Volo.Abp.Domain.Entities.Auditing; + +namespace Acme.BookStore.Authors +{ + public class Author : FullAuditedAggregateRoot + { + public string Name { get; private set; } + public DateTime BirthDate { get; set; } + public string ShortBio { get; set; } + + private Author() + { + /* This constructor is for deserialization / ORM purpose */ + } + + internal Author( + Guid id, + [NotNull] string name, + DateTime birthDate, + [CanBeNull] string shortBio = null) + : base(id) + { + SetName(name); + BirthDate = birthDate; + ShortBio = shortBio; + } + + internal Author ChangeName([NotNull] string name) + { + SetName(name); + return this; + } + + private void SetName([NotNull] string name) + { + Name = Check.NotNullOrWhiteSpace( + name, + nameof(name), + maxLength: AuthorConsts.MaxNameLength + ); + } + } +} +```` + +* Inherited from `FullAuditedAggregateRoot` which makes the entity [soft delete](../Data-Filtering.md) (that means when you delete it, it is not deleted in the database, but just marked as deleted) with all the [auditing](../Entities.md) properties. +* `private set` for the `Name` property restricts to set this property from out of this class. There are two ways of setting the name (in both cases, we validate the name): + * In the constructor, while creating a new author. + * Using the `ChangeName` method to update the name later. +* The `constructor` and the `ChangeName` method is `internal` to force to use these methods only in the domain layer, using the `AuthorManager` that will be explained later. +* `Check` class is an ABP Framework utility class to help you while checking method arguments (it throws `ArgumentException` on an invalid case). + +`AuthorConsts` is a simple class that is located under the `Authors` namespace (folder) of the `Acme.BookStore.Domain.Shared` project: + +````csharp +namespace Acme.BookStore.Authors +{ + public static class AuthorConsts + { + public const int MaxNameLength = 64; + } +} +```` + +Created this class inside the `Acme.BookStore.Domain.Shared` project since we will re-use it on the [Data Transfer Objects](../Data-Transfer-Objects.md) (DTOs) later. + +## AuthorManager: The Domain Service + +`Author` constructor and `ChangeName` method is `internal`, so they can be usable only in the domain layer. Create an `AuthorManager` class in the `Authors` folder (namespace) of the `Acme.BookStore.Domain` project: + +````csharp +using System; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Volo.Abp; +using Volo.Abp.Domain.Services; + +namespace Acme.BookStore.Authors +{ + public class AuthorManager : DomainService + { + private readonly IAuthorRepository _authorRepository; + + public AuthorManager(IAuthorRepository authorRepository) + { + _authorRepository = authorRepository; + } + + public async Task CreateAsync( + [NotNull] string name, + DateTime birthDate, + [CanBeNull] string shortBio = null) + { + Check.NotNullOrWhiteSpace(name, nameof(name)); + + var existingAuthor = await _authorRepository.FindByNameAsync(name); + if (existingAuthor != null) + { + throw new AuthorAlreadyExistsException(name); + } + + return new Author( + GuidGenerator.Create(), + name, + birthDate, + shortBio + ); + } + + public async Task ChangeNameAsync( + [NotNull] Author author, + [NotNull] string newName) + { + Check.NotNull(author, nameof(author)); + Check.NotNullOrWhiteSpace(newName, nameof(newName)); + + var existingAuthor = await _authorRepository.FindByNameAsync(newName); + if (existingAuthor != null && existingAuthor.Id != author.Id) + { + throw new AuthorAlreadyExistsException(newName); + } + + author.ChangeName(newName); + } + } +} +```` + +* `AuthorManager` forces to create an author and change name of an author in a controlled way. The application layer (will be introduced later) will use these methods. + +> **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 (in the `Authors` folder) as shown below: + +````csharp +using Volo.Abp; + +namespace Acme.BookStore.Authors +{ + public class AuthorAlreadyExistsException : BusinessException + { + public AuthorAlreadyExistsException(string name) + : base(BookStoreDomainErrorCodes.AuthorAlreadyExists) + { + WithData("name", name); + } + } +} +```` + +`BusinessException` is a special exception type. It is a good practice to throw domain related exceptions when needed. It is automatically handled by the ABP Framework and can be easily localized. `WithData(...)` method is used to provide additional data to the exception object that will later be used on the localization message or for some other purpose. + +Open the `BookStoreDomainErrorCodes` in the `Acme.BookStore.Domain.Shared` project and change as shown below: + +````csharp +namespace Acme.BookStore +{ + public static class BookStoreDomainErrorCodes + { + public const string AuthorAlreadyExists = "BookStore:00001"; + } +} +```` + +This is a unique string represents the error code thrown by your application and can be handled by client applications. For users, you probably want to localize it. Open the `Localization/BookStore/en.json` inside the `Acme.BookStore.Domain.Shared` project and add the following entry: + +````json +"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(options => +{ + options.MapCodeNamespace("BookStore", typeof(BookStoreResource)); +}); +```` + +Whenever you throw an `AuthorAlreadyExistsException`, the end use will see a nice error message on the UI. + +## IAuthorRepository + +`AuthorManager` injects the `IAuthorRepository`, so we need to define it. Create this new interface in the `Authors` folder (namespace) of the `Acme.BookStore.Domain` project: + +````csharp +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Volo.Abp.Domain.Repositories; + +namespace Acme.BookStore.Authors +{ + public interface IAuthorRepository : IRepository + { + Task FindByNameAsync(string name); + + Task> GetListAsync( + int skipCount, + int maxResultCount, + string sorting, + string filter = null + ); + } +} +```` + +* `IAuthorRepository` extends the standard `IRepository` interface, so all the standard [repository](../Repositories.md) methods will also be available for the `IAuthorRepository`. +* `FindByNameAsync` was used in the `AuthorManager` to query an author by name. +* `GetListAsync` will be used in the application layer to get a listed, sorted and filtered list of authors to show on the UI. + +We will implement this repository in the next part. + +> Both of these methods might **seem unnecessary** since the standard repositories already `IQueryable` and you can directly use them instead of defining such custom methods. You're right and do it like in a real application. However, for this **"learning" tutorial**, it is useful to explain how to create custom repository methods when you really need it. + +## Conclusion + +This part covered the domain layer of the authors functionality of the book store application. The main files created/updated in this part was highlighted in the picture below: + +![bookstore-author-domain-layer](images/bookstore-author-domain-layer.png) + +## The Next Part + +See the [next part](Part-7.md) of this tutorial. \ No newline at end of file diff --git a/docs/en/Tutorials/Part-7.md b/docs/en/Tutorials/Part-7.md new file mode 100644 index 0000000000..81fd07f1bf --- /dev/null +++ b/docs/en/Tutorials/Part-7.md @@ -0,0 +1,236 @@ +# Web Application Development Tutorial - Part 7: Authors: Database Integration +````json +//[doc-params] +{ + "UI": ["MVC","NG"], + "DB": ["EF","Mongo"] +} +```` +{{ +if UI == "MVC" + UI_Text="mvc" +else if UI == "NG" + UI_Text="angular" +else + UI_Text="?" +end +if DB == "EF" + DB_Text="Entity Framework Core" +else if DB == "Mongo" + DB_Text="MongoDB" +else + DB_Text="?" +end +}} + +## About This Tutorial + +In this tutorial series, you will build an ABP based web application named `Acme.BookStore`. This application is used to manage a list of books and their authors. It is developed using the following technologies: + +* **{{DB_Text}}** as the ORM provider. +* **{{UI_Value}}** as the UI Framework. + +This tutorial is organized as the following parts; + +- [Part 1: Creating the server side](Part-1.md) +- [Part 2: The book list page](Part-2.md) +- [Part 3: Creating, updating and deleting books](Part-3.md) +- [Part 4: Integration tests](Part-4.md) +- [Part 5: Authorization](Part-5.md) +- [Part 6: Authors: Domain layer](Part-6.md) +- **Part 7: Authors: Database Integration (this part)** +- [Part 8: Authors: Application Layer](Part-8.md) +- [Part 9: Authors: User Interface](Part-9.md) +- [Part 10: Book to Author Relation](Part-10.md) + +### 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: + +* [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) + +## Introduction + +This part explains how to configure the database integration for the `Author` entity introduced in the previous part. + +{{if DB=="EF"}} + +## DB Context + +Open the `BookStoreDbContext` in the `Acme.BookStore.EntityFrameworkCore` project and add the following `DbSet` property: + +````csharp +public DbSet Authors { get; set; } +```` + +Then open the `BookStoreDbContextModelCreatingExtensions` class in the same project and add the following lines to the end of the `ConfigureBookStore` method: + +````csharp +builder.Entity(b => +{ + b.ToTable(BookStoreConsts.DbTablePrefix + "Authors", + BookStoreConsts.DbSchema); + + b.ConfigureByConvention(); + + b.Property(x => x.Name) + .IsRequired() + .HasMaxLength(AuthorConsts.MaxNameLength); + + b.HasIndex(x => x.Name); +}); +```` + +This is just like done for the `Book` entity before, so no need to explain again. + +## Create a new Database Migration + +Open the **Package Manager Console** on Visual Studio and ensure that the **Default project** is `Acme.BookStore.EntityFrameworkCore.DbMigrations` in the Package Manager Console, as shown on the picture below. Also, set the `Acme.BookStore.Web` as the startup project (right click it on the solution explorer and click to "Set as Startup Project"). + +Run the following command to create a new database migration: + +![bookstore-add-migration-authors](images/bookstore-add-migration-authors.png) + +This will create a new migration class. Then run the `Update-Database` command to create the table on the database. + +> See the [Microsoft's documentation](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/) for more about the EF Core database migrations. + +{{else if DB=="Mongo"}} + +## DB Context + +Open the `BookStoreMongoDbContext` in the `MongoDb` folder of the `Acme.BookStore.MongoDB` project and add the following property to the class: + +````csharp +public IMongoCollection Authors => Collection(); +```` + +{{end}} + +## Implementing the IAuthorRepository + +{{if DB=="EF"}} + +Create a new class, named `EfCoreAuthorRepository` inside the `Acme.BookStore.EntityFrameworkCore` project (in the `Authors` folder) and paste the following code: + +````csharp +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Dynamic.Core; +using System.Threading.Tasks; +using Acme.BookStore.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Volo.Abp.Domain.Repositories.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore; + +namespace Acme.BookStore.Authors +{ + public class EfCoreAuthorRepository + : EfCoreRepository, + IAuthorRepository + { + public EfCoreAuthorRepository( + IDbContextProvider dbContextProvider) + : base(dbContextProvider) + { + } + + public async Task FindByNameAsync(string name) + { + return await DbSet.FirstOrDefaultAsync(author => author.Name == name); + } + + public async Task> GetListAsync( + int skipCount, + int maxResultCount, + string sorting, + string filter = null) + { + return await DbSet + .WhereIf( + !filter.IsNullOrWhiteSpace(), + author => author.Name.Contains(filter) + ) + .OrderBy(sorting) + .Skip(skipCount) + .Take(maxResultCount) + .ToListAsync(); + } + } +} +```` + +* Inherited from the `EfCoreAuthorRepository`, so it inherits the standard repository method implementations. +* `WhereIf` is a shortcut extension method of the ABP Framework. It adds the `Where` condition only if the first condition meets (it filters by name, only if the filter was provided). You could do the same yourself, but these type of shortcut methods makes our life easier. +* `sorting` can be a string like `Name`, `Name ASC` or `Name DESC`. It is possible by using the [System.Linq.Dynamic.Core](https://www.nuget.org/packages/System.Linq.Dynamic.Core) NuGet package. + +> See the [EF Core Integration document](../Entity-Framework-Core.md) for more information on the EF Core based repositories. + +{{else if DB=="Mongo"}} + +Create a new class, named `MongoDbAuthorRepository` inside the `Acme.BookStore.MongoDB` project (in the `Authors` folder) and paste the following code: + +```csharp +using System; +using System.Linq; +using System.Linq.Dynamic.Core; +using System.Collections.Generic; +using System.Threading.Tasks; +using Acme.BookStore.MongoDB; +using MongoDB.Driver; +using MongoDB.Driver.Linq; +using Volo.Abp.Domain.Repositories.MongoDB; +using Volo.Abp.MongoDB; + +namespace Acme.BookStore.Authors +{ + public class MongoDbAuthorRepository + : MongoDbRepository, + IAuthorRepository + { + public MongoDbAuthorRepository( + IMongoDbContextProvider dbContextProvider + ) : base(dbContextProvider) + { + } + + public async Task FindByNameAsync(string name) + { + return await GetMongoQueryable() + .FirstOrDefaultAsync(author => author.Name == name); + } + + public async Task> GetListAsync( + int skipCount, + int maxResultCount, + string sorting, + string filter = null) + { + return await GetMongoQueryable() + .WhereIf>( + !filter.IsNullOrWhiteSpace(), + author => author.Name.Contains(filter) + ) + .OrderBy(sorting) + .As>() + .Skip(skipCount) + .Take(maxResultCount) + .ToListAsync(); + } + } +} +``` + +* Inherited from the `MongoDbAuthorRepository`, so it inherits the standard repository method implementations. +* `WhereIf` is a shortcut extension method of the ABP Framework. It adds the `Where` condition only if the first condition meets (it filters by name, only if the filter was provided). You could do the same yourself, but these type of shortcut methods makes our life easier. +* `sorting` can be a string like `Name`, `Name ASC` or `Name DESC`. It is possible by using the [System.Linq.Dynamic.Core](https://www.nuget.org/packages/System.Linq.Dynamic.Core) NuGet package. + +> See the [MongoDB Integration document](../MongoDB.md) for more information on the MongoDB based repositories. + +{{end}} + +## The Next Part + +See the [next part](Part-8.md) of this tutorial. \ No newline at end of file diff --git a/docs/en/Tutorials/Part-8.md b/docs/en/Tutorials/Part-8.md new file mode 100644 index 0000000000..fc10a799f2 --- /dev/null +++ b/docs/en/Tutorials/Part-8.md @@ -0,0 +1,586 @@ +# Web Application Development Tutorial - Part 8: Authors: Application Layer +````json +//[doc-params] +{ + "UI": ["MVC","NG"], + "DB": ["EF","Mongo"] +} +```` +{{ +if UI == "MVC" + UI_Text="mvc" +else if UI == "NG" + UI_Text="angular" +else + UI_Text="?" +end +if DB == "EF" + DB_Text="Entity Framework Core" +else if DB == "Mongo" + DB_Text="MongoDB" +else + DB_Text="?" +end +}} + +## About This Tutorial + +In this tutorial series, you will build an ABP based web application named `Acme.BookStore`. This application is used to manage a list of books and their authors. It is developed using the following technologies: + +* **{{DB_Text}}** as the ORM provider. +* **{{UI_Value}}** as the UI Framework. + +This tutorial is organized as the following parts; + +- [Part 1: Creating the server side](Part-1.md) +- [Part 2: The book list page](Part-2.md) +- [Part 3: Creating, updating and deleting books](Part-3.md) +- [Part 4: Integration tests](Part-4.md) +- [Part 5: Authorization](Part-5.md) +- [Part 6: Authors: Domain layer](Part-6.md) +- [Part 7: Authors: Database Integration](Part-7.md) +- **Part 8: Author: Application Layer (this part)** +- [Part 9: Authors: User Interface](Part-9.md) +- [Part 10: Book to Author Relation](Part-10.md) + +### 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: + +* [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) + +## Introduction + +This part explains to create an application layer for the `Author` entity created before. + +## IAuthorAppService + +We will first create the [application service](../Application-Services.md) interface and the related [DTO](../Data-Transfer-Objects.md)s. Create a new interface, named `IAuthorAppService`, in the `Authors` namespace (folder) of the `Acme.BookStore.Application.Contracts` project: + +````csharp +using System; +using System.Threading.Tasks; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Application.Services; + +namespace Acme.BookStore.Authors +{ + public interface IAuthorAppService : IApplicationService + { + Task GetAsync(Guid id); + + Task> GetListAsync(GetAuthorListDto input); + + Task CreateAsync(CreateAuthorDto input); + + Task UpdateAsync(Guid id, UpdateAuthorDto input); + + Task DeleteAsync(Guid id); + } +} +```` + +* `IApplicationService` is a conventional interface that is inherited by all the application services, so the ABP Framework can identify the service. +* Defined standard methods to perform CRUD operations on the `Author` entity. +* `PagedResultDto` is a pre-defined DTO class in the ABP Framework. It has an `Items` collection and a `TotalCount` property to return a paged result. +* Preferred to return an `AuthorDto` (for the newly created author) from the `CreateAsync` method, while it is not used by this application - just to show a different usage. + +This interface is using the DTOs defined below (create them for your project). + +### AuthorDto + +````csharp +using System; +using Volo.Abp.Application.Dtos; + +namespace Acme.BookStore.Authors +{ + public class AuthorDto : EntityDto + { + public string Name { get; set; } + + public DateTime BirthDate { get; set; } + + public string ShortBio { get; set; } + } +} +```` + +* `EntityDto` simply has an `Id` property with the given generic argument. You could create an `Id` property yourself instead of inheriting the `EntityDto`. + +### GetAuthorListDto + +````csharp +using Volo.Abp.Application.Dtos; + +namespace Acme.BookStore.Authors +{ + public class GetAuthorListDto : PagedAndSortedResultRequestDto + { + public string Filter { get; set; } + } +} +```` + +* `Filter` is used to search authors. It can be `null` (or empty string) to get all the authors. +* `PagedAndSortedResultRequestDto` has the standard paging and sorting properties: `int MaxResultCount`, `int SkipCount` and `string Sorting`. + +> ABP Framework has such base DTO classes to simplify and standardize your DTOs. See the [DTO documentation](../Data-Transfer-Objects.md) for all. + +### CreateAuthorDto + +````csharp +using System; +using System.ComponentModel.DataAnnotations; + +namespace Acme.BookStore.Authors +{ + public class CreateAuthorDto + { + [Required] + [StringLength(AuthorConsts.MaxNameLength)] + public string Name { get; set; } + + [Required] + public DateTime BirthDate { get; set; } + + public string ShortBio { get; set; } + } +} +```` + +Data annotation attributes can be used to validate the DTO. See the [validation document](../Validation.md) for details. + +### UpdateAuthorDto + +````csharp +using System; +using System.ComponentModel.DataAnnotations; + +namespace Acme.BookStore.Authors +{ + public class UpdateAuthorDto + { + [Required] + [StringLength(AuthorConsts.MaxNameLength)] + public string Name { get; set; } + + [Required] + public DateTime BirthDate { get; set; } + + public string ShortBio { get; set; } + } +} +```` + +> We could share (re-use) the same DTO among the create and the update operations. While you can do it, we prefer to create different DTOs for these operations since we see they generally be different by the time. So, code duplication is reasonable here compared to a tightly coupled design. + +## AuthorAppService + +It is time to implement the `IAuthorAppService` interface. Create a new class, named `AuthorAppService` in the `Authors` namespace (folder) of the `Acme.BookStore.Application` project: + +````csharp +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Acme.BookStore.Permissions; +using Microsoft.AspNetCore.Authorization; +using Volo.Abp.Application.Dtos; + +namespace Acme.BookStore.Authors +{ + [Authorize(BookStorePermissions.Authors.Default)] + public class AuthorAppService : BookStoreAppService, IAuthorAppService + { + private readonly IAuthorRepository _authorRepository; + private readonly AuthorManager _authorManager; + + public AuthorAppService( + IAuthorRepository authorRepository, + AuthorManager authorManager) + { + _authorRepository = authorRepository; + _authorManager = authorManager; + } + + //...SERVICE METHODS WILL COME HERE... + } +} +```` + +* `[Authorize(BookStorePermissions.Authors.Default)]` is a declarative way to check a permission (policy) to authorize the current user. See the [authorization document](../Authorization.md) for more. `BookStorePermissions` class will be updated below, don't worry for the compile error for now. +* Derived from the `BookStoreAppService`, which is a simple base class comes with the startup template. It is derived from the standard `ApplicationService` class. +* Implemented the `IAuthorAppService` which was defined above. +* Injected the `IAuthorRepository` and `AuthorManager` to use in the service methods. + +Now, we will introduce the service methods one by one. Copy the explained method into the `AuthorAppService` class. + +### GetAsync + +````csharp +public async Task GetAsync(Guid id) +{ + var author = await _authorRepository.GetAsync(id); + return ObjectMapper.Map(author); +} +```` + +This method simply gets the `Author` entity by its `Id`, converts to the `AuthorDto` using the [object to object mapper](../Object-To-Object-Mapping.md). This requires to configure the AutoMapper, which will be explained later. + +### GetListAsync + +````csharp +public async Task> GetListAsync(GetAuthorListDto input) +{ + if (input.Sorting.IsNullOrWhiteSpace()) + { + input.Sorting = nameof(Author.Name); + } + + var authors = await _authorRepository.GetListAsync( + input.SkipCount, + input.MaxResultCount, + input.Sorting, + input.Filter + ); + + var totalCount = await AsyncExecuter.CountAsync( + _authorRepository.WhereIf( + !input.Filter.IsNullOrWhiteSpace(), + author => author.Name.Contains(input.Filter) + ) + ); + + return new PagedResultDto( + totalCount, + ObjectMapper.Map, List>(authors) + ); +} +```` + +* Default sorting is "by author name" which is done in the beginning of the method in case of it wasn't sent by the client. +* Used the `IAuthorRepository.GetListAsync` to get a paged, sorted and filtered list of authors from the database. We had implemented it in the previous part of this tutorial. Again, it actually was not needed to create such a method since we could directly query over the repository, but wanted to demonstrate how to create custom repository methods. +* Directly queried from the `AuthorRepository` while getting the count of the authors. We preferred to use the `AsyncExecuter` service which allows us to perform async queries without depending on the EF Core. However, you could depend on the EF Core package and directly use the `_authorRepository.WhereIf(...).ToListAsync()` method. See the [repository document](../Repositories.md) to read the alternative approaches and the discussion. +* Finally, returning a paged result by mapping the list of `Author`s to a list of `AuthorDto`s. + +### CreateAsync + +````csharp +[Authorize(BookStorePermissions.Authors.Create)] +public async Task CreateAsync(CreateAuthorDto input) +{ + var author = await _authorManager.CreateAsync( + input.Name, + input.BirthDate, + input.ShortBio + ); + + await _authorRepository.InsertAsync(author); + + return ObjectMapper.Map(author); +} +```` + +* `CreateAsync` requires the `BookStorePermissions.Authors.Create` permission (in addition to the `BookStorePermissions.Authors.Default` declared for the `AuthorAppService` class). +* Used the `AuthorManeger` (domain service) to create a new author. +* Used the `IAuthorRepository.InsertAsync` to insert the new author to the database. +* Used the `ObjectMapper` to return an `AuthorDto` representing the newly created author. + +> **DDD tip**: Some developers may find useful to insert the new entity inside the `_authorManager.CreateAsync`. We think it is a better design to leave it to the application layer since it better knows when to insert it to the database (maybe it requires additional works on the entity before insert, which would require to an additional update if we perform the insert in the domain service). However, it is completely up to you. + +### UpdateAsync + +````csharp +[Authorize(BookStorePermissions.Authors.Edit)] +public async Task UpdateAsync(Guid id, UpdateAuthorDto input) +{ + var author = await _authorRepository.GetAsync(id); + + if (author.Name != input.Name) + { + await _authorManager.ChangeNameAsync(author, input.Name); + } + + author.BirthDate = input.BirthDate; + author.ShortBio = input.ShortBio; + + await _authorRepository.UpdateAsync(author); +} +```` + +* `UpdateAsync` requires the additional `BookStorePermissions.Authors.Edit` permission. +* Used the `IAuthorRepository.GetAsync` to get the author entity from the database. `GetAsync` throws `EntityNotFoundException` if there is no author with the given id, which results a `404` HTTP status code in a web application. It is a good practice to always bring the entity on an update operation. +* Used the `AuthorManager.ChangeNameAsync` (domain service method) to change the author name if it was requested to change by the client. +* Directly updated the `BirthDate` and `ShortBio` since there is not any business rule to change these properties, they accept any value. +* Finally, called the `IAuthorRepository.UpdateAsync` method to update the entity on the database. + +{{if DB == "EF"}} + +> **EF Core tip**: Entity Framework Core has a **change tracking** system and **automatically saves** any change to an entity at the end of the unit of work (You can simply think that the ABP Framework automatically calls `SaveChanges` at the end of the method). So, it will work as expected even if you don't call the `_authorRepository.UpdateAsync(...)` in the end of the method. If you don't consider to change the EF Core later, you can just remove this line. + +{{end}} + +### DeleteAsync + +````csharp +[Authorize(BookStorePermissions.Authors.Delete)] +public async Task DeleteAsync(Guid id) +{ + await _authorRepository.DeleteAsync(id); +} +```` + +* `DeleteAsync` requires the additional `BookStorePermissions.Authors.Delete` permission. +* It simply uses the `DeleteAsync` method of the repository. + +## Permission Definitions + +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 (in the `Permissions` folder) and change the content as shown below: + +````csharp +namespace Acme.BookStore.Permissions +{ + public static class BookStorePermissions + { + public const string GroupName = "BookStore"; + + public static class Books + { + public const string Default = GroupName + ".Books"; + public const string Create = Default + ".Create"; + public const string Edit = Default + ".Edit"; + public const string Delete = Default + ".Delete"; + } + + // *** ADDED a NEW NESTED CLASS *** + public static class Authors + { + public const string Default = GroupName + ".Authors"; + public const string Create = Default + ".Create"; + public const string Edit = Default + ".Edit"; + public const string Delete = Default + ".Delete"; + } + } +} +```` + +Then open the `BookStorePermissionDefinitionProvider` in the same project and add the following lines at the end of the `Define` method: + +````csharp +var authorsPermission = bookStoreGroup.AddPermission( + BookStorePermissions.Authors.Default, L("Permission:Authors")); + +authorsPermission.AddChild( + BookStorePermissions.Authors.Create, L("Permission:Authors.Create")); + +authorsPermission.AddChild( + BookStorePermissions.Authors.Edit, L("Permission:Authors.Edit")); + +authorsPermission.AddChild( + BookStorePermissions.Authors.Delete, L("Permission:Authors.Delete")); +```` + +Finally, add the following entries to the `Localization/BookStore/en.json` inside the `Acme.BookStore.Domain.Shared` project, to localize the permission names: + +````csharp +"Permission:Authors": "Author Management", +"Permission:Authors.Create": "Creating new authors", +"Permission:Authors.Edit": "Editing the authors", +"Permission:Authors.Delete": "Deleting the authors" +```` + +## Object to Object Mapping + +`AuthorAppService` is using the `ObjectMapper` to convert the `Author` objects to `AuthorDto` objects. So, we need to define this mapping in the AutoMapper configuration. + +Open the `BookStoreApplicationAutoMapperProfile` class inside the `Acme.BookStore.Application` project and add the following line to the constructor: + +````csharp +CreateMap(); +```` + +## Data Seeder + +As just done for the books before, it would be good to have some initial author entities in the database. This will be good while running the application first time, but also it is very useful for the automated tests. + +Open the `BookStoreDataSeederContributor` in the `Acme.BookStore.Domain` project and change the file content with the code below: + +````csharp +using System; +using System.Threading.Tasks; +using Acme.BookStore.Authors; +using Acme.BookStore.Books; +using Volo.Abp.Data; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Repositories; + +namespace Acme.BookStore +{ + public class BookStoreDataSeederContributor + : IDataSeedContributor, ITransientDependency + { + private readonly IRepository _bookRepository; + private readonly IAuthorRepository _authorRepository; + private readonly AuthorManager _authorManager; + + public BookStoreDataSeederContributor( + IRepository bookRepository, + IAuthorRepository authorRepository, + AuthorManager authorManager) + { + _bookRepository = bookRepository; + _authorRepository = authorRepository; + _authorManager = authorManager; + } + + public async Task SeedAsync(DataSeedContext context) + { + if (await _bookRepository.GetCountAsync() <= 0) + { + await _bookRepository.InsertAsync( + new Book + { + Name = "1984", + Type = BookType.Dystopia, + PublishDate = new DateTime(1949, 6, 8), + Price = 19.84f + }, + autoSave: true + ); + + await _bookRepository.InsertAsync( + new Book + { + Name = "The Hitchhiker's Guide to the Galaxy", + Type = BookType.ScienceFiction, + PublishDate = new DateTime(1995, 9, 27), + Price = 42.0f + }, + autoSave: true + ); + } + + // ADDED SEED DATA FOR AUTHORS + + if (await _authorRepository.GetCountAsync() <= 0) + { + await _authorRepository.InsertAsync( + await _authorManager.CreateAsync( + "George Orwell", + new DateTime(1903, 06, 25), + "Orwell produced literary criticism and poetry, fiction and polemical journalism; and is best known for the allegorical novella Animal Farm (1945) and the dystopian novel Nineteen Eighty-Four (1949)." + ) + ); + + await _authorRepository.InsertAsync( + await _authorManager.CreateAsync( + "Douglas Adams", + new DateTime(1952, 03, 11), + "Douglas Adams was an English author, screenwriter, essayist, humorist, satirist and dramatist. Adams was an advocate for environmentalism and conservation, a lover of fast cars, technological innovation and the Apple Macintosh, and a self-proclaimed 'radical atheist'." + ) + ); + } + } + } +} +```` + +{{if DB=="EF"}} + +You can now run the `.DbMigrator` console application to **migrate** the **database schema** and **seed** the initial data. + +{{else if DB=="Mongo"}} + +You can now run the `.DbMigrator` console application to **seed** the initial data. + +{{end}} + +## Testing the Author Application Service + +Finally, we can write some tests for the `IAuthorAppService`. Add a new class, named `AuthorAppService_Tests` in the `Authors` namespace (folder) of the `Acme.BookStore.Application.Tests` project: + +````csharp +using System; +using System.Threading.Tasks; +using Shouldly; +using Xunit; + +namespace Acme.BookStore.Authors +{ {{if DB=="Mongo"}} + [Collection(BookStoreTestConsts.CollectionDefinitionName)]{{end}} + public class AuthorAppService_Tests : BookStoreApplicationTestBase + { + private readonly IAuthorAppService _authorAppService; + + public AuthorAppService_Tests() + { + _authorAppService = GetRequiredService(); + } + + [Fact] + public async Task Should_Get_All_Authors_Without_Any_Filter() + { + var result = await _authorAppService.GetListAsync(new GetAuthorListDto()); + + result.TotalCount.ShouldBeGreaterThanOrEqualTo(2); + result.Items.ShouldContain(author => author.Name == "George Orwell"); + result.Items.ShouldContain(author => author.Name == "Douglas Adams"); + } + + [Fact] + public async Task Should_Get_Filtered_Authors() + { + var result = await _authorAppService.GetListAsync( + new GetAuthorListDto {Filter = "George"}); + + result.TotalCount.ShouldBeGreaterThanOrEqualTo(1); + result.Items.ShouldContain(author => author.Name == "George Orwell"); + result.Items.ShouldNotContain(author => author.Name == "Douglas Adams"); + } + + [Fact] + public async Task Should_Create_A_New_Author() + { + var authorDto = await _authorAppService.CreateAsync( + new CreateAuthorDto + { + Name = "Edward Bellamy", + BirthDate = new DateTime(1850, 05, 22), + ShortBio = "Edward Bellamy was an American author..." + } + ); + + authorDto.Id.ShouldNotBe(Guid.Empty); + authorDto.Name.ShouldBe("Edward Bellamy"); + } + + [Fact] + public async Task Should_Not_Allow_To_Create_Duplicate_Author() + { + await Assert.ThrowsAsync(async () => + { + await _authorAppService.CreateAsync( + new CreateAuthorDto + { + Name = "Douglas Adams", + BirthDate = DateTime.Now, + ShortBio = "..." + } + ); + }); + } + + //TODO: Test other methods... + } +} +```` + +Created some tests for the application service methods, which should be clear to understand. + +## The Next Part + +See the [next part](Part-9.md) of this tutorial. \ No newline at end of file diff --git a/docs/en/Tutorials/Part-9.md b/docs/en/Tutorials/Part-9.md new file mode 100644 index 0000000000..1b9451700d --- /dev/null +++ b/docs/en/Tutorials/Part-9.md @@ -0,0 +1,854 @@ +# Web Application Development Tutorial - Part 9: Authors: User Interface +````json +//[doc-params] +{ + "UI": ["MVC","NG"], + "DB": ["EF","Mongo"] +} +```` +{{ +if UI == "MVC" + UI_Text="mvc" +else if UI == "NG" + UI_Text="angular" +else + UI_Text="?" +end +if DB == "EF" + DB_Text="Entity Framework Core" +else if DB == "Mongo" + DB_Text="MongoDB" +else + DB_Text="?" +end +}} + +## About This Tutorial + +In this tutorial series, you will build an ABP based web application named `Acme.BookStore`. This application is used to manage a list of books and their authors. It is developed using the following technologies: + +* **{{DB_Text}}** as the ORM provider. +* **{{UI_Value}}** as the UI Framework. + +This tutorial is organized as the following parts; + +- [Part 1: Creating the server side](Part-1.md) +- [Part 2: The book list page](Part-2.md) +- [Part 3: Creating, updating and deleting books](Part-3.md) +- [Part 4: Integration tests](Part-4.md) +- [Part 5: Authorization](Part-5.md) +- [Part 6: Authors: Domain layer](Part-6.md) +- [Part 7: Authors: Database Integration](Part-7.md) +- [Part 8: Authors: Application Layer](Part-8.md) +- **Part 9: Authors: User Interface (this part)** +- [Part 10: Book to Author Relation](Part-10.md) + +### 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: + +* [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) + +## Introduction + +This part explains how to create a CRUD page for the `Author` entity introduced in previous parts. + +{{if UI == "MVC"}} + +## The Book List Page + +Create a new razor page, `Index.cshtml` under the `Pages/Authors` folder of the `Acme.BookStore.Web` project and change the content as given below. + +### Index.cshtml + +````html +@page +@using Acme.BookStore.Localization +@using Acme.BookStore.Permissions +@using Acme.BookStore.Web.Pages.Authors +@using Microsoft.AspNetCore.Authorization +@using Microsoft.Extensions.Localization +@inject IStringLocalizer L +@inject IAuthorizationService AuthorizationService +@model IndexModel + +@section scripts +{ + +} + + + + + + @L["Authors"] + + + @if (await AuthorizationService + .IsGrantedAsync(BookStorePermissions.Authors.Create)) + { + + } + + + + + + + +```` + +This is a simple page similar to the Books page we had created before. It imports a JavaScript file which will be introduced below. + +### IndexModel.cshtml.cs + +````csharp +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace Acme.BookStore.Web.Pages.Authors +{ + public class IndexModel : PageModel + { + public void OnGet() + { + + } + } +} +```` + +### Index.js + +````js +$(function () { + var l = abp.localization.getResource('BookStore'); + var createModal = new abp.ModalManager(abp.appPath + 'Authors/CreateModal'); + var editModal = new abp.ModalManager(abp.appPath + 'Authors/EditModal'); + + var dataTable = $('#AuthorsTable').DataTable( + abp.libs.datatables.normalizeConfiguration({ + serverSide: true, + paging: true, + order: [[1, "asc"]], + searching: false, + scrollX: true, + ajax: abp.libs.datatables.createAjax(acme.bookStore.authors.author.getList), + columnDefs: [ + { + title: l('Actions'), + rowAction: { + items: + [ + { + text: l('Edit'), + visible: + abp.auth.isGranted('BookStore.Authors.Edit'), + action: function (data) { + editModal.open({ id: data.record.id }); + } + }, + { + text: l('Delete'), + visible: + abp.auth.isGranted('BookStore.Authors.Delete'), + confirmMessage: function (data) { + return l( + 'AuthorDeletionConfirmationMessage', + data.record.name + ); + }, + action: function (data) { + acme.bookStore.authors.author + .delete(data.record.id) + .then(function() { + abp.notify.info( + l('SuccessfullyDeleted') + ); + dataTable.ajax.reload(); + }); + } + } + ] + } + }, + { + title: l('Name'), + data: "name" + }, + { + title: l('BirthDate'), + data: "birthDate", + render: function (data) { + return luxon + .DateTime + .fromISO(data, { + locale: abp.localization.currentCulture.name + }).toLocaleString(); + } + } + ] + }) + ); + + createModal.onResult(function () { + dataTable.ajax.reload(); + }); + + editModal.onResult(function () { + dataTable.ajax.reload(); + }); + + $('#NewAuthorButton').click(function (e) { + e.preventDefault(); + createModal.open(); + }); +}); +```` + +Briefly, this JavaScript page; + +* Creates a Data table with `Actions`, `Name` and `BirthDate` columns. + * `Actions` column is used to add *Edit* and *Delete* actions. + * `BirthDate` provides a `render` function to format the `DateTime` value using the [luxon](https://moment.github.io/luxon/) library. +* Uses the `abp.ModalManager` to open *Create* and *Edit* modal forms. + +This code is very similar to the Books page created before, so we will not explain it more. + +### Localizations + +This page uses some localization keys we need to declare. Open the `en.json` file under the `Localization/BookStore` folder of the `Acme.BookStore.Domain.Shared` project and add the following entries: + +````json +"Menu:Authors": "Authors", +"Authors": "Authors", +"AuthorDeletionConfirmationMessage": "Are you sure to delete the author '{0}'?", +"BirthDate": "Birth date", +"NewAuthor": "New author" +```` + +Notice that we've added more keys. They will be used in the next sections. + +### Add to the Main Menu + +Open the `BookStoreMenuContributor.cs` in the `Menus` folder of the `Acme.BookStore.Web` project and add the following code in the end of the `ConfigureMainMenuAsync` method: + +````csharp +if (await context.IsGrantedAsync(BookStorePermissions.Authors.Default)) +{ + bookStoreMenu.AddItem(new ApplicationMenuItem( + "BooksStore.Authors", + l["Menu:Authors"], + url: "/Authors" + )); +} +```` + +### Run the Application + +Run and login to the application. **You can not see the menu item since you don't have permission yet.** Go to the `Identity/Roles` page, click to the *Actions* button and select the *Permissions* action for the **admin role**: + +![bookstore-author-permissions](images/bookstore-author-permissions.png) + +As you see, the admin role has no *Author Management* permissions yet. Click to the checkboxes and save the modal to grant the necessary permissions. You will see the *Authors* menu item under the *Book Store* in the main menu, after **refreshing the page**: + +![bookstore-authors-page](images/bookstore-authors-page.png) + +The page is fully working except *New author* and *Actions/Edit* since we haven't implemented them yet. + +> **Tip**: If you run the `.DbMigrator` console application after defining a new permission, it automatically grants these new permissions to the admin role and you don't need to manually grant the permissions yourself. + +## Create Modal + +Create a new razor page, `CreateModal.cshtml` under the `Pages/Authors` folder of the `Acme.BookStore.Web` project and change the content as given below. + +### CreateModal.cshtml + +```html +@page +@using Acme.BookStore.Localization +@using Acme.BookStore.Web.Pages.Authors +@using Microsoft.Extensions.Localization +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal +@model CreateModalModel +@inject IStringLocalizer L +@{ + Layout = null; +} +
    + + + + + + + + + +
    +``` + +We had used [dynamic forms](../UI/AspNetCore/Tag-Helpers/Dynamic-Forms.md) of the ABP Framework for the books page before. We could use the same approach here, but we wanted to show how to do it manually. Actually, not so manually, because we've used `abp-input` tag helper in this case to simplify creating the form elements. + +You can definitely use the standard Bootstrap HTML structure, but it requires to write a lot of code. `abp-input` automatically adds validation, localization and other standard elements based on the data type. + +### CreateModal.cshtml.cs + +```csharp +using System; +using System.ComponentModel.DataAnnotations; +using System.Threading.Tasks; +using Acme.BookStore.Authors; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form; + +namespace Acme.BookStore.Web.Pages.Authors +{ + public class CreateModalModel : BookStorePageModel + { + [BindProperty] + public CreateAuthorViewModel Author { get; set; } + + private readonly IAuthorAppService _authorAppService; + + public CreateModalModel(IAuthorAppService authorAppService) + { + _authorAppService = authorAppService; + } + + public void OnGet() + { + Author = new CreateAuthorViewModel(); + } + + public async Task OnPostAsync() + { + var dto = ObjectMapper.Map(Author); + await _authorAppService.CreateAsync(dto); + return NoContent(); + } + + public class CreateAuthorViewModel + { + [Required] + [StringLength(AuthorConsts.MaxNameLength)] + public string Name { get; set; } + + [Required] + [DataType(DataType.Date)] + public DateTime BirthDate { get; set; } + + [TextArea] + public string ShortBio { get; set; } + } + } +} +``` + +This page model class simply injects and uses the `IAuthorAppService` to create a new author. The main difference between the book creation model class is that this one is declaring a new class, `CreateAuthorViewModel`, for the view model instead of re-using the `CreateAuthorDto`. + +The main reason of this decision was to show you how to use a different model class inside the page. But there is one more benefit: We added two attributes to the class members, which were not present in the `CreateAuthorDto`: + +* Added `[DataType(DataType.Date)]` attribute to the `BirthDate` which shows a date picker on the UI for this property. +* Added `[TextArea]` attribute to the `ShortBio` which shows a multi-line text area instead of a standard textbox. + +In this way, you can specialize the view model class based on your UI requirements without touching to the DTO. As a result of this decision, we have used `ObjectMapper` to map `CreateAuthorViewModel` to `CreateAuthorDto`. To be able to do that, you need to add a new mapping code to the `BookStoreWebAutoMapperProfile` constructor: + +````csharp +using Acme.BookStore.Authors; // ADDED NAMESPACE IMPORT +using Acme.BookStore.Books; +using AutoMapper; + +namespace Acme.BookStore.Web +{ + public class BookStoreWebAutoMapperProfile : Profile + { + public BookStoreWebAutoMapperProfile() + { + CreateMap(); + + // ADD a NEW MAPPING + CreateMap(); + } + } +} +```` + +"New author" button will work as expected and open a new model when you run the application again: + +![bookstore-new-author-modal](images/bookstore-new-author-modal.png) + +## Edit Modal + +Create a new razor page, `EditModal.cshtml` under the `Pages/Authors` folder of the `Acme.BookStore.Web` project and change the content as given below. + +### EditModal.cshtml + +````html +@page +@using Acme.BookStore.Localization +@using Acme.BookStore.Web.Pages.Authors +@using Microsoft.Extensions.Localization +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal +@model EditModalModel +@inject IStringLocalizer L +@{ + Layout = null; +} +
    + + + + + + + + + + +
    +```` + +### EditModal.cshtml.cs + +```csharp +using System; +using System.ComponentModel.DataAnnotations; +using System.Threading.Tasks; +using Acme.BookStore.Authors; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form; + +namespace Acme.BookStore.Web.Pages.Authors +{ + public class EditModalModel : BookStorePageModel + { + [BindProperty] + public EditAuthorViewModel Author { get; set; } + + private readonly IAuthorAppService _authorAppService; + + public EditModalModel(IAuthorAppService authorAppService) + { + _authorAppService = authorAppService; + } + + public async Task OnGetAsync(Guid id) + { + var authorDto = await _authorAppService.GetAsync(id); + Author = ObjectMapper.Map(authorDto); + } + + public async Task OnPostAsync() + { + await _authorAppService.UpdateAsync( + Author.Id, + ObjectMapper.Map(Author) + ); + + return NoContent(); + } + + public class EditAuthorViewModel + { + [HiddenInput] + public Guid Id { get; set; } + + [Required] + [StringLength(AuthorConsts.MaxNameLength)] + public string Name { get; set; } + + [Required] + [DataType(DataType.Date)] + public DateTime BirthDate { get; set; } + + [TextArea] + public string ShortBio { get; set; } + } + } +} +``` + +This class is similar to the `CreateModal.cshtml.cs` while there are some main differences; + +* Uses the `IAuthorAppService.GetAsync(...)` method to get the editing author from the application layer. +* `EditAuthorViewModel` has an additional `Id` property which is marked with the `[HiddenInput]` attribute that creates a hidden input for this property. + +This class requires to add two object mapping declarations to the `BookStoreWebAutoMapperProfile` class: + +```csharp +using Acme.BookStore.Authors; +using Acme.BookStore.Books; +using AutoMapper; + +namespace Acme.BookStore.Web +{ + public class BookStoreWebAutoMapperProfile : Profile + { + public BookStoreWebAutoMapperProfile() + { + CreateMap(); + + CreateMap(); + + // ADD THESE NEW MAPPINGS + CreateMap(); + CreateMap(); + } + } +} +``` + +That's all! You can run the application and try to edit an author. + +{{else if UI == "NG"}} + +## The Author List Page, Create & Delete Authors + +Run the following command line to create a new module, named `AuthorModule` in the root folder of the angular application: + +```bash +yarn ng generate module author --module app --routing --route authors +``` + +This command should produce the following output: + +```bash +> yarn ng generate module author --module app --routing --route authors + +yarn run v1.19.1 +$ ng generate module author --module app --routing --route authors +CREATE src/app/author/author-routing.module.ts (344 bytes) +CREATE src/app/author/author.module.ts (349 bytes) +CREATE src/app/author/author.component.html (21 bytes) +CREATE src/app/author/author.component.spec.ts (628 bytes) +CREATE src/app/author/author.component.ts (276 bytes) +CREATE src/app/author/author.component.scss (0 bytes) +UPDATE src/app/app-routing.module.ts (1396 bytes) +Done in 2.22s. +``` + +### AuthorModule + +Open the `/src/app/author/author.module.ts` and replace the content as shown below: + +```js +import { NgModule } from '@angular/core'; +import { SharedModule } from '../shared/shared.module'; +import { AuthorRoutingModule } from './author-routing.module'; +import { AuthorComponent } from './author.component'; +import { NgbDatepickerModule } from '@ng-bootstrap/ng-bootstrap'; + +@NgModule({ + declarations: [AuthorComponent], + imports: [SharedModule, AuthorRoutingModule, NgbDatepickerModule], +}) +export class AuthorModule {} +``` + +- Added the `SharedModule`. `SharedModule` exports some common modules needed to create user interfaces. +- `SharedModule` already exports the `CommonModule`, so we've removed the `CommonModule`. +- Added `NgbDatepickerModule` that will be used later on the author create and edit forms. + +### Menu Definition + +Open the `src/app/route.provider.ts` file and add the following menu definition: + +````js +{ + path: '/authors', + name: '::Menu:Authors', + parentName: '::Menu:BookStore', + layout: eLayoutType.application, + requiredPolicy: 'BookStore.Authors', +} +```` + +The final `configureRoutes` function declaration should be following: + +```js +function configureRoutes(routes: RoutesService) { + return () => { + routes.add([ + { + path: '/', + name: '::Menu:Home', + iconClass: 'fas fa-home', + order: 1, + layout: eLayoutType.application, + }, + { + path: '/book-store', + name: '::Menu:BookStore', + iconClass: 'fas fa-book', + order: 2, + layout: eLayoutType.application, + }, + { + path: '/books', + name: '::Menu:Books', + parentName: '::Menu:BookStore', + layout: eLayoutType.application, + requiredPolicy: 'BookStore.Books', + }, + { + path: '/authors', + name: '::Menu:Authors', + parentName: '::Menu:BookStore', + layout: eLayoutType.application, + requiredPolicy: 'BookStore.Authors', + }, + ]); + }; +} +``` + +### Service Proxy Generation + +[ABP CLI](https://docs.abp.io/en/abp/latest/CLI) provides `generate-proxy` command that generates client proxies for your HTTP APIs to make easy to consume your HTTP APIs from the client side. Before running `generate-proxy` command, your host must be up and running. + +Run the following command in the `angular` folder: + +```bash +abp generate-proxy +``` + +This command generates the service proxy for the author service and the related model (DTO) classes: + +![bookstore-angular-service-proxy-author](images/bookstore-angular-service-proxy-author.png) + +### AuthorComponent + +Open the `/src/app/author/author.component.ts` file and replace the content as below: + +```js +import { Component, OnInit } from '@angular/core'; +import { ListService, PagedResultDto } from '@abp/ng.core'; +import { AuthorDto } from './models'; +import { AuthorService } from './services'; +import { FormGroup, FormBuilder, Validators } from '@angular/forms'; +import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap'; +import { ConfirmationService, Confirmation } from '@abp/ng.theme.shared'; + +@Component({ + selector: 'app-author', + templateUrl: './author.component.html', + styleUrls: ['./author.component.scss'], + providers: [ListService, { provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }], +}) +export class AuthorComponent implements OnInit { + author = { items: [], totalCount: 0 } as PagedResultDto; + + isModalOpen = false; + + form: FormGroup; + + selectedAuthor = new AuthorDto(); + + constructor( + public readonly list: ListService, + private authorService: AuthorService, + private fb: FormBuilder, + private confirmation: ConfirmationService + ) {} + + ngOnInit(): void { + const authorStreamCreator = (query) => this.authorService.getListByInput(query); + + this.list.hookToQuery(authorStreamCreator).subscribe((response) => { + this.author = response; + }); + } + + createAuthor() { + this.selectedAuthor = new AuthorDto(); + this.buildForm(); + this.isModalOpen = true; + } + + editAuthor(id: string) { + this.authorService.getById(id).subscribe((author) => { + this.selectedAuthor = author; + this.buildForm(); + this.isModalOpen = true; + }); + } + + buildForm() { + this.form = this.fb.group({ + name: [this.selectedAuthor.name || '', Validators.required], + birthDate: [ + this.selectedAuthor.birthDate ? new Date(this.selectedAuthor.birthDate) : null, + Validators.required, + ], + }); + } + + save() { + if (this.form.invalid) { + return; + } + + if (this.selectedAuthor.id) { + this.authorService + .updateByIdAndInput(this.form.value, this.selectedAuthor.id) + .subscribe(() => { + this.isModalOpen = false; + this.form.reset(); + this.list.get(); + }); + } else { + this.authorService.createByInput(this.form.value).subscribe(() => { + this.isModalOpen = false; + this.form.reset(); + this.list.get(); + }); + } + } + + delete(id: string) { + this.confirmation.warn('::AreYouSureToDelete', '::AreYouSure') + .subscribe((status) => { + if (status === Confirmation.Status.confirm) { + this.authorService.deleteById(id).subscribe(() => this.list.get()); + } + }); + } +} +``` + +Open the `/src/app/author/author.component.html` and replace the content as below: + +````html +
    +
    +
    +
    +
    + {%{{{ '::Menu:Authors' | abpLocalization }}}%} +
    +
    +
    +
    + +
    +
    +
    +
    +
    + + + +
    + +
    + + +
    +
    +
    +
    + + + + {%{{{ row.birthDate | date }}}%} + + +
    +
    +
    + + + +

    {%{{{ (selectedAuthor.id ? '::Edit' : '::NewAuthor') | abpLocalization }}}%}

    +
    + + +
    +
    + * + +
    + +
    + * + +
    +
    +
    + + + + + + +
    +```` + +### Localizations + +This page uses some localization keys we need to declare. Open the `en.json` file under the `Localization/BookStore` folder of the `Acme.BookStore.Domain.Shared` project and add the following entries: + +````json +"Menu:Authors": "Authors", +"Authors": "Authors", +"AuthorDeletionConfirmationMessage": "Are you sure to delete the author '{0}'?", +"BirthDate": "Birth date", +"NewAuthor": "New author" +```` + +### Run the Application + +Run and login to the application. **You can not see the menu item since you don't have permission yet.** Go to the `identity/roles` page, click to the *Actions* button and select the *Permissions* action for the **admin role**: + +![bookstore-author-permissions](images/bookstore-author-permissions.png) + +As you see, the admin role has no *Author Management* permissions yet. Click to the checkboxes and save the modal to grant the necessary permissions. You will see the *Authors* menu item under the *Book Store* in the main menu, after **refreshing the page**: + +![bookstore-authors-page](images/bookstore-angular-authors-page.png) + +That's all! This is a fully working CRUD page, you can create, edit and delete authors. + +> **Tip**: If you run the `.DbMigrator` console application after defining a new permission, it automatically grants these new permissions to the admin role and you don't need to manually grant the permissions yourself. + +{{end}} + +## The Next Part + +See the [next part](Part-10.md) of this tutorial. \ No newline at end of file diff --git a/docs/en/Tutorials/images/bookstore-add-migration-authors.png b/docs/en/Tutorials/images/bookstore-add-migration-authors.png new file mode 100644 index 0000000000..c24e33c628 Binary files /dev/null and b/docs/en/Tutorials/images/bookstore-add-migration-authors.png differ diff --git a/docs/en/Tutorials/images/bookstore-added-author-to-book-list-angular.png b/docs/en/Tutorials/images/bookstore-added-author-to-book-list-angular.png new file mode 100644 index 0000000000..9fba94d5bf Binary files /dev/null and b/docs/en/Tutorials/images/bookstore-added-author-to-book-list-angular.png differ diff --git a/docs/en/Tutorials/images/bookstore-added-author-to-book-list.png b/docs/en/Tutorials/images/bookstore-added-author-to-book-list.png new file mode 100644 index 0000000000..f318ddfd31 Binary files /dev/null and b/docs/en/Tutorials/images/bookstore-added-author-to-book-list.png differ diff --git a/docs/en/Tutorials/images/bookstore-added-authors-to-modals.png b/docs/en/Tutorials/images/bookstore-added-authors-to-modals.png new file mode 100644 index 0000000000..5fe4046794 Binary files /dev/null and b/docs/en/Tutorials/images/bookstore-added-authors-to-modals.png differ diff --git a/docs/en/Tutorials/images/bookstore-angular-authors-page.png b/docs/en/Tutorials/images/bookstore-angular-authors-page.png new file mode 100644 index 0000000000..bbd865e44b Binary files /dev/null and b/docs/en/Tutorials/images/bookstore-angular-authors-page.png differ diff --git a/docs/en/Tutorials/images/bookstore-angular-service-proxy-author.png b/docs/en/Tutorials/images/bookstore-angular-service-proxy-author.png new file mode 100644 index 0000000000..ac231ef7a8 Binary files /dev/null and b/docs/en/Tutorials/images/bookstore-angular-service-proxy-author.png differ diff --git a/docs/en/Tutorials/images/bookstore-author-domain-layer.png b/docs/en/Tutorials/images/bookstore-author-domain-layer.png new file mode 100644 index 0000000000..fde53834bb Binary files /dev/null and b/docs/en/Tutorials/images/bookstore-author-domain-layer.png differ diff --git a/docs/en/Tutorials/images/bookstore-author-permissions.png b/docs/en/Tutorials/images/bookstore-author-permissions.png new file mode 100644 index 0000000000..a20093d2c4 Binary files /dev/null and b/docs/en/Tutorials/images/bookstore-author-permissions.png differ diff --git a/docs/en/Tutorials/images/bookstore-authors-page.png b/docs/en/Tutorials/images/bookstore-authors-page.png new file mode 100644 index 0000000000..6b8959df54 Binary files /dev/null and b/docs/en/Tutorials/images/bookstore-authors-page.png differ diff --git a/docs/en/Tutorials/images/bookstore-new-author-modal.png b/docs/en/Tutorials/images/bookstore-new-author-modal.png new file mode 100644 index 0000000000..dda0dab4c1 Binary files /dev/null and b/docs/en/Tutorials/images/bookstore-new-author-modal.png differ diff --git a/docs/en/Tutorials/images/youtube.png b/docs/en/Tutorials/images/youtube.png new file mode 100644 index 0000000000..cf9a67548d Binary files /dev/null and b/docs/en/Tutorials/images/youtube.png differ diff --git a/docs/en/UI/Angular/Config-State.md b/docs/en/UI/Angular/Config-State.md index 089697f064..bda3250b32 100644 --- a/docs/en/UI/Angular/Config-State.md +++ b/docs/en/UI/Angular/Config-State.md @@ -127,6 +127,17 @@ const defaultLang = this.config.getSetting("Abp.Localization.DefaultLanguage"); // '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 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. diff --git a/docs/en/UI/Angular/Localization.md b/docs/en/UI/Angular/Localization.md index 78822a16de..eec4db0cb4 100644 --- a/docs/en/UI/Angular/Localization.md +++ b/docs/en/UI/Angular/Localization.md @@ -193,6 +193,32 @@ import { Component } from '@angular/core'; export class AppComponent {} ``` +## Mapping of Culture Name to Angular Locale File Name + +Some of the culture names defined in .NET do not match Angular locales. In such cases, the Angular app throws an error like below at runtime: + +![locale-error](./images/locale-error.png) + +If you see an error like this, you should pass the `cultureNameLocaleFileMap` property like below to CoreModule's forRoot static method. + +```js +// app.module.ts + +@NgModule({ + imports: [ + // other imports + CoreModule.forRoot({ + // other options + cultureNameLocaleFileMap: { + "DotnetCultureName": "AngularLocaleFileName", + "pt-BR": "pt" // example + } + }) + //... +``` + +See [all locale files in Angular](https://github.com/angular/angular/tree/master/packages/common/locales). + ## See Also @@ -200,4 +226,4 @@ export class AppComponent {} ## What's Next? -* [Permission Management](./Permission-Management.md) \ No newline at end of file +* [Permission Management](./Permission-Management.md) diff --git a/docs/en/UI/Angular/Multi-Tenancy.md b/docs/en/UI/Angular/Multi-Tenancy.md new file mode 100644 index 0000000000..94b3954a03 --- /dev/null +++ b/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) +

    Tenants page

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

    Tenant Switching Component

    + +You can switch between existing tenants by using the tenant switching component in the child pages of the `AccountLayoutComponent` (like Login page). Angular UI sends the selected tenant id sends to the backend as `__tenant` header on each request. + + +## Domain Tenant Resolver + +Angular UI can get the tenant name from the app running URL. You can determine the current tenant by subdomain (like mytenant1.mydomain.com) or by the whole domain (like mytenant.com). To do this, you need to set the `application.baseUrl` property in the environment: + +Subdomain resolver: + +```js +// environment.prod.ts + +export const environment = { + //... + application: { + baseUrl: 'https://{0}.mydomain.com/' + }, + //... +} +``` + +**{0}** is the placeholder to determine current tenant's unique name. + +After the configuration above, if your app runs on the `mytenant1.mydomain.com`, the app will get the tenant name as **mytenant1**. Then, the app will call the `/api/abp/multi-tenancy/tenants/by-name/mytenant1` endpoint to check if the tenant exists. If the tenant (mytenant1) exists, the app will keep this tenant data and send its `id` as `__tenant` header to the backend on each request. If the tenant does not exist, the app will not send `__tenant` header to the backend. + +> **Important Note:** If you define the `baseUrl` with the placeholder (**{0}**), the tenant switching component in the child pages of the `AccountLayoutComponent` (like Login page) will be hidden. + + +Domain resolver: + +```js +// environment.prod.ts + +export const environment = { + //... + application: { + baseUrl: 'https://{0}.com/' + }, + //... +} +``` + +After the configuration above, if your app runs on the `mytenant.com`, the app will get the tenant name as **mytenant**. + +### Tenant Specific Remote Endpoints + +The **{0}** placeholder can be put to the API URLs in the environment to determine tenant specific endpoints. + +```js +// environment.prod.ts + +export const environment = { + //... + application: { + baseUrl: 'https://{0}.mydomain.com/', + //... + }, + oAuthConfig: { + issuer: 'https://{0}.ids.mydomain.com', + //... + }, + apis: { + default: { + url: 'https://{0}.api.mydomain.com', + }, + AbpIdentity: { + url: 'https://{0}.identity.mydomain.com', + }, + }, +} +``` + +> **Important Note:** The `application.baseUrl` and the `{0}` placeholder in the value of the `baseUrl` property are required to be able to get tenant from running URL. Other placeholders in API URLs are optional. + +After the configuration above, if your app runs on the `mytenant1.mydomain.com`, the app will get tenant name as **mytenant1** and replace the environment object in `ConfigState` on app initialization as follows: + + +```js +// environment object in ConfigState + +{ + //... + application: { + baseUrl: 'https://mytenant1.mydomain.com/', + //... + }, + oAuthConfig: { + issuer: 'https://mytenant1.ids.mydomain.com', + //... + }, + apis: { + default: { + url: 'https://mytenant1.api.mydomain.com', + }, + AbpIdentity: { + url: 'https://mytenant1.identity.mydomain.com', + }, + }, +} +``` + +After this replacement, the app will use the following URLs: + +- `https://mytenant1.ids.mydomain.com` as IdentityServer URL. +- `https://mytenant1.api.mydomain.com` as default URL. +- `https://mytenant1.identity.mydomain.com` as `AbpIdentity` remote endpoint URL. + +The app sends the `__tenant` header that contains the current tenant id on each request. + +## See Also + +* [Multi Tenancy in ABP](../../Multi-Tenancy.md) + +## What's Next? + +- [Confirmation Popup](./Confirmation-Service.md) diff --git a/docs/en/UI/Angular/Permission-Management.md b/docs/en/UI/Angular/Permission-Management.md index 61169c85b8..8cc7325afd 100644 --- a/docs/en/UI/Angular/Permission-Management.md +++ b/docs/en/UI/Angular/Permission-Management.md @@ -78,4 +78,4 @@ Granted Policies are stored in the `auth` property of `ConfigState`. ## What's Next? -- [Confirmation Popup](./Confirmation-Service.md) \ No newline at end of file +* [Multi Tenancy](./Multi-Tenancy.md) \ No newline at end of file diff --git a/docs/en/UI/Angular/Subscription-Service.md b/docs/en/UI/Angular/Subscription-Service.md new file mode 100644 index 0000000000..5d920df96e --- /dev/null +++ b/docs/en/UI/Angular/Subscription-Service.md @@ -0,0 +1,203 @@ +# Easy Unsubscription for Your Observables + +`SubscriptionService` is a utility service to provide an easy unsubscription from RxJS observables in Angular components and directives. Please see [why you should unsubscribe from observables on instance destruction](https://angular.io/guide/lifecycle-hooks#cleaning-up-on-instance-destruction). + +## Getting Started + +You have to provide the `SubscriptionService` at component or directive level, because it is **not provided in root** and it works in sync with component/directive lifecycle. Only after then you can inject and start using it. + +```js +import { SubscriptionService } from '@abp/ng.core'; + +@Component({ + /* class metadata here */ + providers: [SubscriptionService], +}) +class DemoComponent { + count$ = interval(1000); + + constructor(private subscription: SubscriptionService) { + this.subscription.addOne(this.count$, console.log); + } +} +``` + +The values emitted by the `count$` will be logged until the component is destroyed. You will not have to unsubscribe manually. + +> Please do not try to use a singleton `SubscriptionService`. It simply will not work. + +## Usage + +### How to Subscribe to Observables + +You can pass a `next` function and an `error` function. + +```js +@Component({ + /* class metadata here */ + providers: [SubscriptionService], +}) +class DemoComponent implements OnInit { + constructor(private subscription: SubscriptionService) {} + + ngOnInit() { + const source$ = interval(1000); + const nextFn = value => console.log(value * 2); + const errorFn = error => { + console.error(error); + return of(null); + }; + + this.subscription.addOne(source$, nextFn, errorFn); + } +} +``` + +Or, you can pass an observer. + +```js +@Component({ + /* class metadata here */ + providers: [SubscriptionService], +}) +class DemoComponent implements OnInit { + constructor(private subscription: SubscriptionService) {} + + ngOnInit() { + const source$ = interval(1000); + const observer = { + next: value => console.log(value * 2), + complete: () => console.log('DONE'), + }; + + this.subscription.addOne(source$, observer); + } +} +``` + +The `addOne` method returns the individual subscription, so that you may use it later on. Please see topics below for details. + +### How to Unsubscribe Before Instance Destruction + +There are two ways to do that. If you are not going to subscribe again, you may use the `closeAll` method. + +```js +@Component({ + /* class metadata here */ + providers: [SubscriptionService], +}) +class DemoComponent implements OnInit { + constructor(private subscription: SubscriptionService) {} + + ngOnInit() { + this.subscription.addOne(interval(1000), console.log); + } + + onSomeEvent() { + this.subscription.closeAll(); + } +} +``` + +This will clear all subscriptions, but you will not be able to subscribe again. If you are planning to add another subscription, you may use the `reset` method instead. + +```js +@Component({ + /* class metadata here */ + providers: [SubscriptionService], +}) +class DemoComponent implements OnInit { + constructor(private subscription: SubscriptionService) {} + + ngOnInit() { + this.subscription.addOne(interval(1000), console.log); + } + + onSomeEvent() { + this.subscription.reset(); + this.subscription.addOne(interval(1000), console.warn); + } +} +``` + +### How to Unsubscribe From a Single Subscription + +Sometimes, you may need to unsubscribe from a particular subscription but leave others alive. In such a case, you may use the `closeOne` method. + +```js +@Component({ + /* class metadata here */ + providers: [SubscriptionService], +}) +class DemoComponent implements OnInit { + countSubscription: Subscription; + + constructor(private subscription: SubscriptionService) {} + + ngOnInit() { + this.countSubscription = this.subscription.addOne( + interval(1000), + console.log + ); + } + + onSomeEvent() { + this.subscription.closeOne(this.countSubscription); + console.log(this.countSubscription.closed); // true + } +} +``` + +### How to Remove a Single Subscription From Tracked Subscriptions + +You may want to take control of a particular subscription. In such a case, you may use the `removeOne` method to remove it from tracked subscriptions. + +```js +@Component({ + /* class metadata here */ + providers: [SubscriptionService], +}) +class DemoComponent implements OnInit { + countSubscription: Subscription; + + constructor(private subscription: SubscriptionService) {} + + ngOnInit() { + this.countSubscription = this.subscription.addOne( + interval(1000), + console.log + ); + } + + onSomeEvent() { + this.subscription.removeOne(this.countSubscription); + console.log(this.countSubscription.closed); // false + } +} +``` + +### How to Check If Unsubscribed From All + +Please use `isClosed` getter to check if `closeAll` was called before. + +```js +@Component({ + /* class metadata here */ + providers: [SubscriptionService], +}) +class DemoComponent implements OnInit { + constructor(private subscription: SubscriptionService) {} + + ngOnInit() { + this.subscription.addOne(interval(1000), console.log); + } + + onSomeEvent() { + console.log(this.subscription.isClosed); // false + } +} +``` + +## What's Next? + +- [ListService](./List-Service.md) diff --git a/docs/en/UI/Angular/Toaster-Service.md b/docs/en/UI/Angular/Toaster-Service.md index b194811d9a..faa5632ab8 100644 --- a/docs/en/UI/Angular/Toaster-Service.md +++ b/docs/en/UI/Angular/Toaster-Service.md @@ -26,7 +26,7 @@ You can use the `success`, `warn`, `error`, and `info` methods of `ToasterServic ### How to Display a Toast Overlay ```js -this.toast.success('Message', 'Title'); +this.toaster.success('Message', 'Title'); ``` - The `ToasterService` methods accept three parameters that are `message`, `title`, and `options`. @@ -72,17 +72,18 @@ With the options above, the toast overlay looks like this: The open toast overlay can be removed manually via the `remove` method by passing the `id` of toast: ```js -const toastId = this.toast.success('Message', 'Title') +const toastId = this.toaster.success('Message', 'Title') -this.toast.remove(toastId); +this.toaster.remove(toastId); ``` -### How to Remove All Toasts + +### How to Remove All Toasts The all open toasts can be removed manually via the `clear` method: ```js -this.toast.clear(); +this.toaster.clear(); ``` ## API diff --git a/docs/en/UI/Angular/Track-By-Service.md b/docs/en/UI/Angular/Track-By-Service.md index 447cc4505a..050706accd 100644 --- a/docs/en/UI/Angular/Track-By-Service.md +++ b/docs/en/UI/Angular/Track-By-Service.md @@ -116,4 +116,4 @@ class DemoComponent { ## What's Next? -- [ListService](./List-Service.md) +- [SubscriptionService](./Subscription-Service.md) diff --git a/docs/en/UI/Angular/images/locale-error.png b/docs/en/UI/Angular/images/locale-error.png new file mode 100644 index 0000000000..de385a59c0 Binary files /dev/null and b/docs/en/UI/Angular/images/locale-error.png differ diff --git a/docs/en/UI/Angular/images/tenant-switching-box.png b/docs/en/UI/Angular/images/tenant-switching-box.png new file mode 100644 index 0000000000..ed4d299c55 Binary files /dev/null and b/docs/en/UI/Angular/images/tenant-switching-box.png differ diff --git a/docs/en/UI/Angular/images/tenants-page.png b/docs/en/UI/Angular/images/tenants-page.png new file mode 100644 index 0000000000..8882d58d2f Binary files /dev/null and b/docs/en/UI/Angular/images/tenants-page.png differ diff --git a/docs/en/UI/AspNetCore/Dynamic-JavaScript-Proxies.md b/docs/en/UI/AspNetCore/Dynamic-JavaScript-Proxies.md new file mode 100644 index 0000000000..391a63a910 --- /dev/null +++ b/docs/en/UI/AspNetCore/Dynamic-JavaScript-Proxies.md @@ -0,0 +1,3 @@ +# Dynamic JavaScript HTTP API Proxies + +TODO \ No newline at end of file diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index eaeaad8346..9bf7083d0d 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -4,21 +4,16 @@ "text": "Getting Started", "items": [ { - "text": "From Startup Templates", + "text": "Web Application", "path": "Getting-Started.md" }, { - "text": "From Empty Projects", - "items": [ - { - "text": "With ASP.NET Core Web Application", - "path": "Getting-Started-AspNetCore-Application.md" - }, - { - "text": "With Console Application", - "path": "Getting-Started-Console-Application.md" - } - ] + "text": "Console Application", + "path": "Startup-Templates/Console.md" + }, + { + "text": "Empty Web Project", + "path": "Getting-Started-AspNetCore-Application.md" } ] }, @@ -47,6 +42,26 @@ { "text": "5: Authorization", "path": "Tutorials/Part-5.md" + }, + { + "text": "6: Authors: Domain layer", + "path": "Tutorials/Part-6.md" + }, + { + "text": "7: Authors: Database Integration", + "path": "Tutorials/Part-7.md" + }, + { + "text": "8: Authors: Application Layer", + "path": "Tutorials/Part-8.md" + }, + { + "text": "9: Authors: User Interface", + "path": "Tutorials/Part-9.md" + }, + { + "text": "10: Book to Author Relation", + "path": "Tutorials/Part-10.md" } ] } @@ -73,10 +88,6 @@ } ] }, - { - "text": "\"How to\" Guides", - "path": "How-To/Index.md" - }, { "text": "Migrating from the ASP.NET Boilerplate", "path": "AspNet-Boilerplate-Migration-Guide.md" @@ -87,6 +98,15 @@ "text": "CLI", "path": "CLI.md" }, + { + "text": "Authentication", + "items": [ + { + "text": "Social/External Logins", + "path": "Authentication/Social-External-Logins.md" + } + ] + }, { "text": "Fundamentals", "items": [ @@ -220,12 +240,20 @@ "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", "path": "Blob-Storing-Custom-Provider.md" } ] - } + } ] }, { @@ -398,6 +426,10 @@ "text": "Permission Management", "path": "UI/Angular/Permission-Management.md" }, + { + "text": "Multi Tenancy", + "path": "UI/Angular/Multi-Tenancy.md" + }, { "text": "Confirmation Popup", "path": "UI/Angular/Confirmation-Service.md" @@ -438,6 +470,10 @@ "text": "TrackByService", "path": "UI/Angular/Track-By-Service.md" }, + { + "text": "SubscriptionService", + "path": "UI/Angular/Subscription-Service.md" + }, { "text": "ListService", "path": "UI/Angular/List-Service.md" diff --git a/docs/en/images/basic-console-application-solution.png b/docs/en/images/basic-console-application-solution.png new file mode 100644 index 0000000000..788bad0381 Binary files /dev/null and b/docs/en/images/basic-console-application-solution.png differ diff --git a/docs/zh-Hans/API/Application-Configuration.md b/docs/zh-Hans/API/Application-Configuration.md index ba062d9cef..50e74d5bd7 100644 --- a/docs/zh-Hans/API/Application-Configuration.md +++ b/docs/zh-Hans/API/Application-Configuration.md @@ -2,12 +2,12 @@ ABP框架提供了一个预构建的标准端点,其中包含一些有关应用程序/服务的有用信息. 这里是此端点的一些基本信息的列表: -* [本地化](Localization.md)值, 支持应用程序的当前语言. -* 当前用户可用和已授予的[策略](Authorization.md)(权限). -* 当前用户的[设置](Settings.md)值. -* 关于[当前用户](CurrentUser.md)的信息 (如 id 和用户名). -* 关于当前[租户](Multi-Tenancy.md)的信息 (如 id 和名称). -* 当前用户的[时区](Timing.md)信息和应用程序的[时钟](Timing.md)类型. +* [本地化](../Localization.md)值, 支持应用程序的当前语言. +* 当前用户可用和已授予的[策略](../Authorization.md)(权限). +* 当前用户的[设置](../Settings.md)值. +* 关于[当前用户](../CurrentUser.md)的信息 (如 id 和用户名). +* 关于当前[租户](../Multi-Tenancy.md)的信息 (如 id 和名称). +* 当前用户的[时区](../Timing.md)信息和应用程序的[时钟](../Timing.md)类型. ## HTTP API diff --git a/docs/zh-Hans/Authentication/Social-External-Logins.md b/docs/zh-Hans/Authentication/Social-External-Logins.md new file mode 100644 index 0000000000..3d599a962d --- /dev/null +++ b/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)文档了解如何使用用户机密. \ No newline at end of file diff --git a/docs/zh-Hans/Authorization.md b/docs/zh-Hans/Authorization.md index 99139be3ac..96caae1404 100644 --- a/docs/zh-Hans/Authorization.md +++ b/docs/zh-Hans/Authorization.md @@ -285,8 +285,6 @@ public async Task CreateAsync(CreateAuthorDto input) abp.auth.isGranted('MyPermissionName'); ```` -参阅 [abp.auth](AspNetCore/JavaScript-API/Auth.md) API 文档了解详情. - ## 权限管理 通常权限管理是管理员用户使用权限管理模态框进行授权: diff --git a/docs/zh-Hans/Background-Workers-Quartz.md b/docs/zh-Hans/Background-Workers-Quartz.md index 196d0e1d07..9c4a0721e4 100644 --- a/docs/zh-Hans/Background-Workers-Quartz.md +++ b/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#配置). diff --git a/docs/zh-Hans/Best-Practices/Application-Services.md b/docs/zh-Hans/Best-Practices/Application-Services.md index 6c681edde1..82a5dc8aba 100644 --- a/docs/zh-Hans/Best-Practices/Application-Services.md +++ b/docs/zh-Hans/Best-Practices/Application-Services.md @@ -203,7 +203,7 @@ Task VoteAsync(Guid id, VoteType type); #### 额外的属性 -* **推荐** 使用 `MapExtraPropertiesTo` 扩展方法 ([参阅](Object-Extensions.md)) 或配置对象映射 (`MapExtraProperties`) 以允许应用开发人员能够扩展对象和服务. +* **推荐** 使用 `MapExtraPropertiesTo` 扩展方法 ([参阅](../Object-Extensions.md)) 或配置对象映射 (`MapExtraProperties`) 以允许应用开发人员能够扩展对象和服务. #### 操作/删除 实体 diff --git a/docs/zh-Hans/Blob-Storing-Aliyun.md b/docs/zh-Hans/Blob-Storing-Aliyun.md index adeb902830..ded28fbc93 100644 --- a/docs/zh-Hans/Blob-Storing-Aliyun.md +++ b/docs/zh-Hans/Blob-Storing-Aliyun.md @@ -23,18 +23,21 @@ BLOB存储Aliyun提供程序可以将BLOB存储在[Aliyun Blob storage](https:// ````csharp Configure(options => { - options.Containerscontainer.UseAliyun(aliyun => + options.Containers.ConfigureDefault(container => { - aliyun.AccessKeyId = "your aliyun access key id"; - aliyun.AccessKeySecret = "your aliyun access key secret"; - aliyun.Endpoint = "your oss endpoint"; - aliyun.RegionId = "your sts region id"; - aliyun.RoleArn = "the arn of ram role"; - aliyun.RoleSessionName = "the name of the certificate"; - aliyun.Policy = "policy"; - aliyun.DurationSeconds = "expiration date"; - aliyun.ContainerName = "your aliyun container name"; - aliyun.CreateContainerIfNotExists = false; + container.UseAliyun(aliyun => + { + aliyun.AccessKeyId = "your aliyun access key id"; + aliyun.AccessKeySecret = "your aliyun access key secret"; + aliyun.Endpoint = "your oss endpoint"; + aliyun.RegionId = "your sts region id"; + aliyun.RoleArn = "the arn of ram role"; + aliyun.RoleSessionName = "the name of the certificate"; + aliyun.Policy = "policy"; + aliyun.DurationSeconds = "expiration date"; + aliyun.ContainerName = "your aliyun container name"; + aliyun.CreateContainerIfNotExists = false; + }); }); }); ```` @@ -46,16 +49,18 @@ Configure(options => * **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. [STS临时授权访问OSS](https://help.aliyun.com/document_detail/100624.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. **注**:为0则使用子账号操作OSS. +* **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 名称计算器 diff --git a/docs/zh-Hans/Blob-Storing-Aws.md b/docs/zh-Hans/Blob-Storing-Aws.md new file mode 100644 index 0000000000..0ecb59bbf5 --- /dev/null +++ b/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(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/` 字符串. +* 追加 BLOB 名称. + +## 其他服务 + +* `AwsBlobProvider` 是实现Aws BLOB存储提供程序的主要服务,如果你想要通过[依赖注入](Dependency-Injection.md)覆盖/替换它(不要替换 `IBlobProvider` 接口,而是替换 `AwsBlobProvider` 类). +* `IAwsBlobNameCalculator` 服务用于计算文件路径. 默认实现是 `DefaultAwsBlobNameCalculator`. 如果你想自定义文件路径计算,可以替换/覆盖它. +* `IAmazonS3ClientFactory` 服务用于生成AWS S3客户端. 默认实现是 `DefaultAmazonS3ClientFactory` . 如果你想自定义AWS S3客户端生成,可以替换/覆盖它. diff --git a/docs/zh-Hans/Blob-Storing-Azure.md b/docs/zh-Hans/Blob-Storing-Azure.md index 892d299fc0..b6f54cd72c 100644 --- a/docs/zh-Hans/Blob-Storing-Azure.md +++ b/docs/zh-Hans/Blob-Storing-Azure.md @@ -23,11 +23,14 @@ BLOB存储Azure提供程序可以将BLOB存储在[Azure Blob storage](https://az ````csharp Configure(options => { - options.Containerscontainer.UseAzure(azure => + options.Containers.ConfigureDefault(container => { - azure.ConnectionString = "your azure connection string"; - azure.ContainerName = "your azure container name"; - azure.CreateContainerIfNotExists = false; + container.UseAzure(azure => + { + azure.ConnectionString = "your azure connection string"; + azure.ContainerName = "your azure container name"; + azure.CreateContainerIfNotExists = false; + }); }); }); ```` diff --git a/docs/zh-Hans/Blob-Storing-Database.md b/docs/zh-Hans/Blob-Storing-Database.md index e45c79d179..cc688e4396 100644 --- a/docs/zh-Hans/Blob-Storing-Database.md +++ b/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.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) diff --git a/docs/zh-Hans/Blob-Storing-Minio.md b/docs/zh-Hans/Blob-Storing-Minio.md new file mode 100644 index 0000000000..3c4bec721d --- /dev/null +++ b/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(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/` 字符串, +* 追加 BLOB 名称, + +## 其他服务 + +* `MinioBlobProvider` 是实现Minio BLOB存储提供程序的主要服务,如果你想要通过[依赖注入](Dependency-Injection.md)覆盖/替换它(不要替换 `IBlobProvider` 接口,而是替换 `MinioBlobProvider` 类). +* `IMinioBlobNameCalculator` 服务用于计算文件路径. 默认实现是 `DefaultMinioBlobNameCalculator`. 如果你想自定义文件路径计算,可以替换/覆盖它. diff --git a/docs/zh-Hans/Blob-Storing.md b/docs/zh-Hans/Blob-Storing.md index 43b2ebb96b..99688ca47a 100644 --- a/docs/zh-Hans/Blob-Storing.md +++ b/docs/zh-Hans/Blob-Storing.md @@ -19,6 +19,9 @@ ABP框架已经有以下存储提供程序的实现; * [File System](Blob-Storing-File-System.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/)中. +* [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框架. diff --git a/docs/zh-Hans/CLI.md b/docs/zh-Hans/CLI.md index e733b6e2e5..4af252e89f 100644 --- a/docs/zh-Hans/CLI.md +++ b/docs/zh-Hans/CLI.md @@ -90,9 +90,10 @@ abp new Acme.BookStore * **`console`**: [Console template](Startup-Templates/Console.md). * `--output-folder` 或者 `-o`: 指定输出文件夹,默认是当前目录. * `--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://.zip`). +* `--preview`: 使用最新的预发行版本 (仅在未指定 `--version` 且最新稳定版本之后至少有一个预发行版时). +* `--template-source` 或者 `-ts`: 指定自定义模板源用于生成项目,可以使用本地源和网络源(例如 `D:\local-templat` 或 `https://.../my-template-file.zip`). * `--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包引用. ### add-package @@ -173,15 +174,15 @@ abp update [options] ### 切换到每晚构建(预览)包 -想要切换到ABP框架的最新预览版可以使用此命令. +想要切换到ABP框架的最新**每晚构建**预览版可以使用此命令. 用法: ````bash -abp switch-to-preview [options] +abp switch-to-nightly [options] ```` -你也可以使用切换回稳定版本: +你也可以使用切换回最新稳定版本: ````bash abp switch-to-stable [options] diff --git a/docs/zh-Hans/Entity-Framework-Core-Migrations.md b/docs/zh-Hans/Entity-Framework-Core-Migrations.md index 77f5ac5cd7..876d98721b 100644 --- a/docs/zh-Hans/Entity-Framework-Core-Migrations.md +++ b/docs/zh-Hans/Entity-Framework-Core-Migrations.md @@ -3,7 +3,7 @@ 本文首先介绍[应用程序启动模板](Startup-Templates/Application.md)提供的**默认结构**,并讨论你可能希望为自己的应用程序实现的**各种场景**. -> 本文档适用于希望完全理解和自定义[应用程序启动模板](Startup-Templates/Application.md)附带的数据库结构的人员. 如果你只是想创建实体和管理代码优先(code first)迁移,只需要遵循[启动教程](Tutorials/Index.md). +> 本文档适用于希望完全理解和自定义[应用程序启动模板](Startup-Templates/Application.md)附带的数据库结构的人员. 如果你只是想创建实体和管理代码优先(code first)迁移,只需要遵循[启动教程](Tutorials/Part-1.md). ### 源码 diff --git a/docs/zh-Hans/Getting-Started-Console-Application.md b/docs/zh-Hans/Getting-Started-Console-Application.md index cac3d2f153..1bf48cefd4 100644 --- a/docs/zh-Hans/Getting-Started-Console-Application.md +++ b/docs/zh-Hans/Getting-Started-Console-Application.md @@ -1,124 +1,3 @@ ## 在控制台应用中使用ABP -本教程将介绍如何从头开始以最小的依赖关系启动ABP. 你通常希望以 **[启动模板](https://abp.io/Templates)** 开头. - -### 创建一个新项目 - -使用Visual Studio创建一个新的.Net Core Console应用程序: - -![](images/create-new-net-core-console-application.png) - -### 安装 Volo.Abp 包 - -Volo.Abp.Core是创建基于ABP的应用程序的核心nuget包. 所以,将它安装到你的项目中: - -```` -Install-Package Volo.Abp.Core -```` - -### 创建第一个ABP模块 - -ABP是一个模块化框架, 它需要一个从``AbpModule``类派生的 **启动(根)模块** 类: - -````C# -using Microsoft.Extensions.DependencyInjection; -using Volo.Abp.Modularity; - -namespace AbpConsoleDemo -{ - public class AppModule : AbpModule - { - - } -} -```` - -``AppModule`` 是应用程序启动模块的好名称. - -### 初始化应用程序 - -下一步是使用上面创建的启动模块引导应用程序: - -````C# -using System; -using Volo.Abp; - -namespace AbpConsoleDemo -{ - class Program - { - static void Main(string[] args) - { - using (var application = AbpApplicationFactory.Create()) - { - 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()) - { - application.Initialize(); - - // 解析服务并使用它 - var helloWorldService = - application.ServiceProvider.GetService(); - 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)获取本教程中创建的示例项目的源代码. \ No newline at end of file +ABP提供了控制台应用程序启动模板. 参阅[控制台应用程序启动模板]文档了解更多信息. \ No newline at end of file diff --git a/docs/zh-Hans/Getting-Started-React-Native.md b/docs/zh-Hans/Getting-Started-React-Native.md new file mode 100644 index 0000000000..54d093cb7c --- /dev/null +++ b/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*** 登录到应用程序. + +应用程序已经启动并执行,你可以基于该启动模板开发应用程序. \ No newline at end of file diff --git a/docs/zh-Hans/Getting-Started.md b/docs/zh-Hans/Getting-Started.md index adf9517eec..fe1a9ec6f9 100644 --- a/docs/zh-Hans/Getting-Started.md +++ b/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/). -* [.NET Core 3.0+](https://www.microsoft.com/net/download/dotnet-core/) +* [Visual Studio 2019 (v16.4+)](https://visualstudio.microsoft.com/vs/) for Windows / [Visual Studio for Mac](https://visualstudio.microsoft.com/vs/mac/).[1](#f-editor) +* [.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.20+ (not v2)](https://classic.yarnpkg.com/en/docs/install) [2](#f-yarn) 或 npm v6+ (与Node一起安装) {{ if Tiered == "Yes" }} -* [Redis](https://redis.io/): 应用程序将Redis用作[分布式缓存](../Caching.md). 因此你需要安装并运行Redis. +* [Redis](https://redis.io/): 入门解决方案将Redis用作[分布式缓存](Caching.md). 因此你需要安装并运行Redis. {{ end }} -> 你可以也使用其他支持.NET Core 和 ASP.NET Core的编辑器. +1 _只要支持.NET Core和ASP.NET Core,就可以使用其他编辑器代替Visual Studio._ [↩](#a-editor) + +2 _Yarn v2 的工作方式不同,不受支持._ [↩](#a-yarn) ### 安装ABP CLI -[ABP CLI](./CLI.md)是一个命令行页面,用于为基于ABP的应用程序验证和自动化一些任务. +[ABP CLI](./CLI.md)是一个命令行页面,用于自动执行一些基于ABP的应用程序的常见任务. -> ABP CLI是[ABP框架](https://abp.io/)一个免费开源的工具. +> ABP CLI是ABP框架一个免费开源的工具. 你需要使用以下命令安排ABP CLI: @@ -59,10 +62,10 @@ dotnet tool update -g Volo.Abp.Cli 使用ABP CLI的 `new` 命令创建新项目: ````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" }} @@ -70,7 +73,7 @@ abp new Acme.BookStore -t app{{if UI == "NG"}} -u angular {{end}}{{if DB == "Mon {{ if Tiered == "Yes" }} -* `--separate-identity-server` 参数用于将Identity服务器应用程序与API主机应用程序分隔开. 如果未指定,你将只有一个端点. +* `--separate-identity-server` 参数用于将Identity服务器应用程序与API主机应用程序分隔开. 如果未指定,则服务器上将只有一个端点. {{ 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. +#### 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" }} @@ -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` 文件中的 **链接字符串**: @@ -214,7 +223,9 @@ Ef Core具有`Update-Database`命令, 可根据需要创建数据库并应用挂 这将基于配置的连接字符串创建新数据库. -> 使用`.Migrator`工具是建议的方法, 因为它还能初始化初始数据能够正确运行Web应用程序. +> **使用`.DbMigrator`工具是建议的方法**, 因为它能初始化初始数据能够正确运行Web应用程序. +> +> 如果你只是使用 `Update-Database` 命令,你会得到一个空数据库,所以你无法登录到应用程序因为数据库中没有初始管理用户. 不需要种子数据库时,可以在开发期间使用 `Update-Database` 命令. 但是使用 `.DbMigrator` 应用程序会更简单,你始终可以使用它来迁移模式并为数据库添加种子. {{ else if DB == "Mongo" }} @@ -240,7 +251,7 @@ Ef Core具有`Update-Database`命令, 可根据需要创建数据库并应用挂 ![db-migrator-output](images/db-migrator-output.png) -> 数据库创建后会初始化种子数据, 其中包含用于登录的 `admin` 用户. 所以你至少使用 `.DbMigrator` 一次. +> 数据库创建后会初始化[种子数据](Data-Seeding.md), 其中包含用于登录的 `admin` 用户. 所以你至少使用 `.DbMigrator` 一次. {{ end }} @@ -336,83 +347,18 @@ yarn start {{ end }} -输入用户名 **admin**,密码 **1q2w3E*** 登录到应用程序. - -![bookstore-home](images/bookstore-home.png) - -应用程序已经启动并执行,你可以基于该启动模板开发应用程序. +输入用户名 **admin**,密码 **1q2w3E*** 登录到应用程序,应用程序已经启动并执行,你可以基于此启动模板开始开发应用程序. #### 移动开发 -ABP平台提供了[React Native](https://reactnative.dev/)模板用于开发移动应用程序. - -> 该解决方案默认 `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) +当你创建一个新的应用程序时.该解决方案默认包含 `react-native`文件夹. 这是一个基础的[React Native](https://reactnative.dev/)启动模板,用于开发与基于ABP的后端集成的移动应用程序. -输入用户名 **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) diff --git a/docs/zh-Hans/Index.md b/docs/zh-Hans/Index.md index 3dd728c846..d409f1e8ab 100644 --- a/docs/zh-Hans/Index.md +++ b/docs/zh-Hans/Index.md @@ -4,23 +4,17 @@ ABP是一个**开源应用程序框架**,专注于基于ASP.NET Core的Web应用程序开发,但也支持开发其他类型的应用程序. -浏览左侧导航菜单以深入了解文档. +浏览导航菜单以深入了解文档. ## 入门 -使用ABP开发新项目的最简单方法是使用启动模板: +使用ABP开发新Web应用程序的最简单方法是使用[入门](Getting-Started.md)教程: -* [ASP.NET Core MVC (Razor Pages) UI 启动模板](Getting-Started.md?UI=MVC&DB=EF&Tiered=No) -* [Angular UI 启动模板](Getting-Started.md?UI=NG&DB=EF&Tiered=No) - -如果你想从头开始(使用空项目),请手动安装ABP框架并使用以下教程: - -* [控制台应用程序](Getting-Started-Console-Application.md) -* [ASP.NET Core Web 应用程序](Getting-Started-AspNetCore-Application.md) +然后你可以继续进行[Web应用程序开发教程](Tutorials/Part-1.md). ## 源码 -ABP托管在GitHub上, 参见[源代码](https://github.com/abpframework/abp). +ABP托管在GitHub上, 参见[源代码](https://github.com/abpframework). ## 贡献代码 diff --git a/docs/zh-Hans/Modules/Docs.md b/docs/zh-Hans/Modules/Docs.md index 63759d1462..acc956be1b 100644 --- a/docs/zh-Hans/Modules/Docs.md +++ b/docs/zh-Hans/Modules/Docs.md @@ -611,7 +611,7 @@ Configure(options => 应用程序启动后如果`Index`不存在则会自动创建`Index`. `DefaultElasticClientProvider`负责创建`IElasticClient`, 默认情况下它会从`IConfiguration`中读取Elastic Search的`Url`. -如果你的IElasticClient需要其它配置请使用重写IElasticClientProvider服务并在依赖注入系统中替换它. +如果你的 `IElasticClient` 需要其它配置请使用重写 `IElasticClientProvider` 服务并在[依赖注入](../Dependency-Injection.md)系统中替换它. ``` { "ElasticSearch": { diff --git a/docs/zh-Hans/Modules/Identity.md b/docs/zh-Hans/Modules/Identity.md index ea50006de9..f396de00c1 100644 --- a/docs/zh-Hans/Modules/Identity.md +++ b/docs/zh-Hans/Modules/Identity.md @@ -1,3 +1,5 @@ # 身份管理模块 +身份模块基于Microsoft Identity 库用于管理[组织单元](Organization-Units.md), 角色, 用户和他们的权限. + 参阅 [源码](https://github.com/abpframework/abp/tree/dev/modules/identity). 文档很快会被完善. \ No newline at end of file diff --git a/docs/zh-Hans/Modules/Index.md b/docs/zh-Hans/Modules/Index.md index a24a95c121..dab2f838df 100644 --- a/docs/zh-Hans/Modules/Index.md +++ b/docs/zh-Hans/Modules/Index.md @@ -17,7 +17,7 @@ ABP是一个 **模块化的应用程序框架** 由十多个 **nuget packages** * **Blogging**: 用于创建精美的博客. ABP的[博客](https://blog.abp.io/) 就使用了此模块. * [**Docs**](Docs.md): 用于创建技术文档页面. ABP的[文档](https://abp.io/documents/) 就使用了此模块. * **Feature Management**: 用于保存和管理功能. -* **Identity**: 基于Microsoft Identity管理角色,用户和他们的权限. +* [**Identity**](Identity.md): 基于Microsoft Identity管理组织单元,角色,用户和他们的权限. * **Identity Server**: 集成了IdentityServer4. * **Permission Management**: 用于保存权限. * **Setting Management**: 用于保存设置. diff --git a/docs/zh-Hans/Modules/Organization-Units.md b/docs/zh-Hans/Modules/Organization-Units.md new file mode 100644 index 0000000000..8047b6a48d --- /dev/null +++ b/docs/zh-Hans/Modules/Organization-Units.md @@ -0,0 +1,47 @@ +# 组织单元管理 + +组织单元(OU)是"身份模块"的一部分,用于**对用户和实体进行分层分组**. + +### OrganizationUnit 实体 + +OU由 **OrganizationUnit** 实体表示. 实体有以下基本属性: + +- **TenantId**: OU租户的ID,为null代表是宿主OU. +- **ParentId**: OU的父亲Id,为null为根OU. +- **Code**: 租户唯一的分层字符串代码. +- **DisplayName**: OU的显示名称. + +OrganizationUnit实体的主键(Id)是 **Guid** 类型,派生自[**FullAuditedAggregateRoot**](../Entities.md)类. + +#### Organization 树 + +因为OU可以有父亲,租户所有的OU是一个**树**结构. 树有一些规则: + +- 可以有多个根(`ParentId` 为 `null`). +- OU的第一级子级数有限制(面说明的固定OU代码单位长度). + +#### OU Code + +OU代码由OrganizationUnit Manager自动生成和维护. 看起来像这样的字符串: + +"**00001.00042.00005**" + +此代码可用于轻松查询数据库中OU的所有子级(递归). 代码有一些规则: + +- 必须[租户](../Multi-Tenancy.md)**唯一**的. +- 同一OU的所有子代均以**父OU的代码开头**. +- 它是**固定长度**的,并且基于树中OU的级别,如示例中所示. +- 虽然OU代码是唯一的,但是如果移动OU,它是**可更改的**. +- 你必须通过Id而不是代码引用OU. + +### OrganizationUnit Manager + +可以注入 **OrganizationUnitManager** 管理OU. 常见的用例有: + +- 创建,更改或删除OU. +- 在OU树中移动OU. +- 获取有关OU树及其项的信息 + +#### 多租户 + +`OrganizationUnitManager` 设置为一次性为 **单个租户** 工作,默认是 **当前租户**. \ No newline at end of file diff --git a/docs/zh-Hans/Nightly-Builds.md b/docs/zh-Hans/Nightly-Builds.md index 84ea243b05..020d69e02e 100644 --- a/docs/zh-Hans/Nightly-Builds.md +++ b/docs/zh-Hans/Nightly-Builds.md @@ -29,13 +29,13 @@ 预览NPM包的最新版本可以通过在应用程序的根文件夹命令运行命令安装: ```bash -abp switch-to-preview +abp switch-to-preview --npm ``` 如果你正在使用ABP框架预览包,你可以使用此命令切换回稳定版本: ```bash -abp switch-to-stable +abp switch-to-stable --npm ``` 参阅 [ABP CLI 文档](./CLI.md) 了解更多信息. \ No newline at end of file diff --git a/docs/zh-Hans/Options.md b/docs/zh-Hans/Options.md index c88ff0adda..5bb4b29a8b 100644 --- a/docs/zh-Hans/Options.md +++ b/docs/zh-Hans/Options.md @@ -82,7 +82,7 @@ public class MyService : ITransientDependency 对于此类情况,ABP为 `IServiceCollection` 引入了 `PreConfigure` 和 `ExecutePreConfiguredActions` 扩展方法. 该模式的工作原理如下所述. -1. 你的模块中定义计划选项类. 例: +在你的模块中定义预先选项类. 例: ````csharp public class MyPreOptions diff --git a/docs/zh-Hans/Repositories.md b/docs/zh-Hans/Repositories.md index 18bfa5f77d..ee48186985 100644 --- a/docs/zh-Hans/Repositories.md +++ b/docs/zh-Hans/Repositories.md @@ -193,8 +193,6 @@ var people = ((IMongoQueryable)_personRepository ### 选项-3: IAsyncQueryableExecuter -> 注意,此功能在ABP框架3.0以之后的版本可用,虽然它也可以用于较早的版本,但它提供的方法非常有限. - `IAsyncQueryableExecuter` 是一个用于异步执行 `IQueryable` 对象的服务,**不依赖于实际的数据库提供程序**. **示例: 注入并使用 `IAsyncQueryableExecuter.ToListAsync()` 方法** diff --git a/docs/zh-Hans/Road-Map.md b/docs/zh-Hans/Road-Map.md index f18441cb41..d8a277ac0e 100644 --- a/docs/zh-Hans/Road-Map.md +++ b/docs/zh-Hans/Road-Map.md @@ -9,7 +9,6 @@ 除了中期目录,还有一些[积压](https://github.com/abpframework/abp/milestone/2)的功能, 这里积压中重要功能的列表: -* [#4098](https://github.com/abpframework/abp/issues/4098) / Blob存储Azure提供者. * [#2882](https://github.com/abpframework/abp/issues/2882) / 提供 **gRPC集成** 基础设施 (虽然[已经可以](https://github.com/abpframework/abp-samples/tree/master/GrpcDemo)为你的应用程序创建和使用gRPC端点,但我们计划为所有[标准应用程序模块](https://docs.abp.io/en/abp/latest/Modules/Index)创建端点) * [#236](https://github.com/abpframework/abp/issues/236) 基于权限系统的资源 * [#1754](https://github.com/abpframework/abp/issues/1754) / 多语言实体 diff --git a/docs/zh-Hans/Samples/Index.md b/docs/zh-Hans/Samples/Index.md index c54b2e8ba6..94e5392b51 100644 --- a/docs/zh-Hans/Samples/Index.md +++ b/docs/zh-Hans/Samples/Index.md @@ -7,7 +7,7 @@ 演示如何基于微服务体系结构构建系统的完整解决方案. * [示例的文档](Microservice-Demo.md) -* [源码](https://github.com/abpframework/abp/tree/dev/samples/MicroserviceDemo) +* [源码](https://github.com/abpframework/abp-samples/tree/master/MicroserviceDemo) * [微服务架构文档](../Microservice-Architecture.md) ### Book Store @@ -48,6 +48,8 @@ * **文本模板 Demo**: 文本模板系统的不同用例. * [源码](https://github.com/abpframework/abp-samples/tree/master/TextTemplateDemo) * [文本模板文档](../Text-Templating.md) +* **存储过程 Demo**: 演示如何以最佳实践使用存储过程,数据库视图和函数. + * [源码](https://github.com/abpframework/abp-samples/tree/master/StoredProcedureDemo) * **自定义认证**: 如何为ASP.NET Core MVC / Razor Pages应用程序自定义身份验证的解决方案. * [源码](https://github.com/abpframework/abp-samples/tree/master/Authentication-Customization) * 相关 "[How To](../How-To/Index.md)" 文档: diff --git a/docs/zh-Hans/Samples/Microservice-Demo.md b/docs/zh-Hans/Samples/Microservice-Demo.md index b0134eddde..59977908a1 100644 --- a/docs/zh-Hans/Samples/Microservice-Demo.md +++ b/docs/zh-Hans/Samples/Microservice-Demo.md @@ -28,7 +28,7 @@ ABP框架的主要目标之一就是提供[便捷的基础设施来创建微服 ### 源码 -你可以从[GitHub仓库](https://github.com/abpframework/abp/tree/master/samples/MicroserviceDemo)获取源码. +你可以从[GitHub仓库](https://github.com/abpframework/abp-samples/tree/master/MicroserviceDemo)获取源码. ## 运行解决方案 @@ -43,6 +43,12 @@ ABP框架的主要目标之一就是提供[便捷的基础设施来创建微服 * [ElasticSearch](https://www.elastic.co/downloads/elasticsearch) 6.6+ * [Kibana](https://www.elastic.co/downloads/kibana) 6.6+ (可选,建议显示日志) +### 运行基础设施 + +- 默认情况下运行Docker-compose可以轻松运行预先要求. 如果你没有安装,在windows环境下你可以在[这里](https://docs.docker.com/docker-for-windows/install/)下载并使用[Docker for Windows](https://docs.docker.com/docker-for-windows/). +* 在 `MicroserviceDemo` 目录下运行命令 `docker-compose -f docker-compose.infrastructure.yml -f docker-compose.infrastructure.override.yml up -d` 或使用powershell运行 `MicroserviceDemo/_run` 文件夹下的 `__Run_Infrastructure.ps1` 脚本. +* 如果你不想使用docker运行预先要求的服务在你的本地环境,你需要更改微服务示例解决方案项目中的 `appsettings.json` 文件. + ### 打开并构建Visual Studio解决方案 * 在Visual Studio 2017 (15.9.0+)中打开`samples\MicroserviceDemo\MicroserviceDemo.sln`. diff --git a/docs/zh-Hans/SignalR-Integration.md b/docs/zh-Hans/SignalR-Integration.md index 5e17b1c75c..bdf4a7a786 100644 --- a/docs/zh-Hans/SignalR-Integration.md +++ b/docs/zh-Hans/SignalR-Integration.md @@ -115,7 +115,7 @@ public class MessagingHub : Hub } ```` -`MessasingHub` 集线器的路由为 `/signalr-hubs/messasing`: +`MessagingHub` 集线器的路由为 `/signalr-hubs/messaging`: * 添加了标准 `/signalr-hubs/` 前缀. * 使用**驼峰命名**集线器名称,不包含 `Hub` 后缀. @@ -123,7 +123,7 @@ public class MessagingHub : Hub 如果你想指定路由,你可以使用 `HubRoute` attribute: ````csharp -[HubRoute("/my-messasing-hub")] +[HubRoute("/my-messaging-hub")] public class MessagingHub : Hub { //... diff --git a/docs/zh-Hans/Startup-Templates/Application.md b/docs/zh-Hans/Startup-Templates/Application.md index 4e2c3f6700..44ea5adfaf 100644 --- a/docs/zh-Hans/Startup-Templates/Application.md +++ b/docs/zh-Hans/Startup-Templates/Application.md @@ -423,7 +423,11 @@ Screens 是通过在 `src/screens` 文件夹中创建将名称分开的文件夹 * [Formik](https://github.com/jaredpalmer/formik) 用于构建表单. * [Yup](https://github.com/jquense/yup) 用于表单验证. +## 社交/外部登录 + +如果你想要为你的应用程序配置社交/外部登录,请参阅[社交/外部登录](../Authentication/Social-External-Logins.md)文档. + ## 下一步是什么? -* 参阅[ASP.NET Core MVC 模板入门](../Getting-Started-AspNetCore-MVC-Template.md)创建此模板的新解决方案并运行它. -* 参阅[ASP.NET Core MVC 教程](../Tutorials/AspNetCore-Mvc/Part-I.md)学习使用此模板开发应用程序. +* [入门文档](../Getting-Started.md)介绍了如何在几分钟内创建新应用程序. +* [Web应用程序开发教程](../Tutorials/Part-1.md)逐步介绍了应用程序开发. diff --git a/docs/zh-Hans/Startup-Templates/Console.md b/docs/zh-Hans/Startup-Templates/Console.md index caeb036d3d..c7299cd938 100644 --- a/docs/zh-Hans/Startup-Templates/Console.md +++ b/docs/zh-Hans/Startup-Templates/Console.md @@ -16,4 +16,12 @@ dotnet tool install -g Volo.Abp.Cli abp new Acme.MyConsoleApp -t console ```` -`Acme.MyConsoleApp` 是解决方案的名称, 如*YourCompany.YourProduct*. 你可以使用单级或多级名称. \ No newline at end of file +`Acme.MyConsoleApp` 是解决方案的名称, 如*YourCompany.YourProduct*. 你可以使用单级或多级名称. + +## 解决方案结构 + +使用以上命令创建解决方案后,你会得到如下所示的解决方案: + +![basic-console-application-solution](../images/basic-console-application-solution.png) + +* `HelloWorldService` 是一个实现了 `ITransientDependency` 接口的示例服务. 它会自动注册到[依赖注入](../Dependency-Injection.md)系统. \ No newline at end of file diff --git a/docs/zh-Hans/Startup-Templates/Module.md b/docs/zh-Hans/Startup-Templates/Module.md index a6706ad783..8341670af4 100644 --- a/docs/zh-Hans/Startup-Templates/Module.md +++ b/docs/zh-Hans/Startup-Templates/Module.md @@ -56,7 +56,7 @@ abp new Acme.IssueManagement -t module --no-ui ### .Domain 项目 -解决方案的领域层. 它主要包含 [实体, 集合根](../Entities.md), [领域服务](../Domain-Services.md), [值类型](../Value-Types.md), [仓储接口](../Repositories.md) 和解决方案的其他领域对象. +解决方案的领域层. 它主要包含 [实体, 集合根](../Entities.md), [领域服务](../Domain-Services.md), 值类型, [仓储接口](../Repositories.md) 和解决方案的其他领域对象. 例如 `Issue` 实体, `IssueManager` 领域服务和 `IIssueRepository` 接口都适合放在这个项目中. diff --git a/docs/zh-Hans/Text-Templating.md b/docs/zh-Hans/Text-Templating.md index 0785dbba29..be9cbb3a79 100644 --- a/docs/zh-Hans/Text-Templating.md +++ b/docs/zh-Hans/Text-Templating.md @@ -4,7 +4,7 @@ ABP框架提供了一个简单有效的文本模板系统,文本模板用于动态渲染基于模板和模型(数据对象)内容: -***TEMPLATE + MODEL ==render==> RENDERED CONTENT*** +Template + Model =renderer=> Rendered Content 它非常类似于 ASP.NET Core Razor View (或 Page): diff --git a/docs/zh-Hans/Tutorials/Part-1.md b/docs/zh-Hans/Tutorials/Part-1.md index bcf4e21b9f..101835559b 100644 --- a/docs/zh-Hans/Tutorials/Part-1.md +++ b/docs/zh-Hans/Tutorials/Part-1.md @@ -1,128 +1,75 @@ -## ASP.NET Core {{UI_Value}} 教程 - 第一章 +# Web应用程序开发教程 - 第一章: 创建服务端 ````json //[doc-params] { - "UI": ["MVC","NG"] + "UI": ["MVC","NG"], + "DB": ["EF","Mongo"] } ```` {{ if UI == "MVC" - DB="ef" - DB_Text="Entity Framework Core" UI_Text="mvc" else if UI == "NG" - DB="mongodb" - DB_Text="MongoDB" UI_Text="angular" else - DB ="?" UI_Text="?" end +if DB == "EF" + DB_Text="Entity Framework Core" +else if DB == "Mongo" + DB_Text="MongoDB" +else + DB_Text="?" +end }} -### 关于本教程 - -在本系列教程中, 你将构建一个名为 `Acme.BookStore` 的用于管理书籍及其作者列表的应用程序. **{{DB_Text}}**将用作ORM提供者,前端使用{{UI_Value}} 和 JavaScript. - -ASP.NET Core {{UI_Value}} 系列教程包括三个3个部分: - -- **Part-1: 创建项目和书籍列表页面(本章)** -- [Part-2: 创建,编辑,删除书籍](Part-2.md) -- [Part-3: 集成测试](Part-3.md) - -> 你也可以观看由ABP社区成员为本教程录制的[视频课程](https://amazingsolutions.teachable.com/p/lets-build-the-bookstore-application). - -### 创建新项目 - -创建一个名为 `Acme.BookStore` 的新项目,其中 `Acme` 是公司名 `BookStore` 是项目名. 你可以参阅[入门](../Getting-Started?UI={{UI}}#run-the-application) 文档了解如何创建新项目. 我们将使用CLI创建新项目. - -#### 创建项目 - -使用以下命令创建一个新的ABP项目,使用 `{{DB_Text}}` 做为数据库提供者, UI选项使用 `{{UI_Value}}`. 其他CLI选项请参考[ABP CLI](https://docs.abp.io/en/abp/latest/CLI)文档. - -```bash -abp new Acme.BookStore --template app --database-provider {{DB}} --ui {{UI_Text}} --mobile none -``` - -![Creating project](./images/bookstore-create-project-{{UI_Text}}.png) - -### 应用迁移 - -项目创建后,需要应用初始化迁移创建数据库. 运行 `Acme.BookStore.DbMigrator` 应用程序. 它会应用所有迁移,完成流程后你会看到以下结果,数据库已经准备好了! - -![Migrations applied](./images/bookstore-migrations-applied-{{UI_Text}}.png) - -> 另外你也可以在 Visual Studio 包管理控制台运行 `Update-Database` 命令应用迁移. +## 关于本教程 -#### 初始化数据库表 +在本系列教程中, 你将构建一个名为 `Acme.BookStore` 的用于管理书籍及其作者列表的基于ABP的应用程序. 它是使用以下技术开发的: -![Initial database tables](./images/bookstore-database-tables-{{DB}}.png) +* **{{DB_Text}}** 做为ORM提供程序. +* **{{UI_Value}}** 做为UI框架. -### 运行应用程序 +本教程分为以下部分: -右键单击{{if UI == "MVC"}} `Acme.BookStore.Web`{{end}} {{if UI == "NG"}} `Acme.BookStore.HttpApi.Host` {{end}} 项目**设置为启动项**. 使用 **CTRL+F5** 或 **F5** 运行应用程序. {{if UI == "NG"}}你会看到BookStore API的Swagger UI.{{end}} +- **Part 1: 创建服务端 (本章)** +- [Part 2: 图书列表页面](Part-2.md) +- [Part 3: 创建,更新和删除图书](Part-3.md) +- [Part 4: 集成测试](Part-4.md) +- [Part 5: 授权](Part-5.md) +- [Part 6: 作者: 领域层](Part-6.md) +- [Part 7: 作者: 数据库集成](Part-7.md) +- [Part 8: 作者: 应用服务层](Part-8.md) +- [Part 9: 作者: 用户页面](Part-9.md) +- [Part 10: 图书到作者的关系](Part-10.md) -更多信息,参阅[入门教程](../../Getting-Started?UI={{UI}})的运行应用程序部分. +## 下载源码 -![Set as startup project](./images/bookstore-start-project-{{UI_Text}}.png) +本教程根据你的**UI** 和 **Database**偏好有多个版,我们准备了两种可供下载的源码组合: -{{if UI == "NG"}} - -在 `angular` 下打开命令行终端,执行 `yarn` 命令: - -```bash -yarn -``` +* [MVC (Razor Pages) UI 与 EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore) +* [Angular UI 与 MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb) -所有的模块加载后,执行 `yarn start` 命令: +## 创建解决方案 -```bash -yarn start -``` - -默认网站从以下URL访问: - -http://localhost:4200/ - -如果你成功看到登录页面,可以按 `ctrl-c` 退出Angular托管.(我们稍后再运行). - -> 注意, Firefox不使用Windows凭据存储,你需要手动将自签名的开发人员证书导入到Firefox. 打开Firefox并导航到以下网址: -> -> https://localhost:44322/api/abp/application-configuration -> -> 如果你看到下图,单击 **Accept the Risk 和 Continue** 按钮绕过警告. -> -> ![Set as startup project](./images/mozilla-self-signed-cert-error.png) - -{{end}} +在开始开发之前,请按照[入门教程](../Getting-Started.md)创建名为 `Acme.BookStore` 的新解决方案. -默认的登录凭证: +## 创建Book实体 -* **Username**: admin -* **Password**: 1q2w3E* - -### 解决方案的结构 - -下面的图片展示了从启动模板创建的项目是如何分层的. - -![bookstore-visual-studio-solution](./images/bookstore-solution-structure-{{UI_Text}}.png) - -> 你可以查看[应用程序模板文档](../startup-templates/application#solution-structure)以详细了解解决方案结构. - -### 创建Book实体 - -启动模板中的域层分为两个项目: +启动模板中的**领域层**分为两个项目: - `Acme.BookStore.Domain`包含你的[实体](https://docs.abp.io/zh-Hans/abp/latest/Entities), [领域服务](https://docs.abp.io/zh-Hans/abp/latest/Domain-Services)和其他核心域对象. - `Acme.BookStore.Domain.Shared`包含可与客户共享的常量,枚举或其他域相关对象. -在解决方案的**领域层**(`Acme.BookStore.Domain`项目)中定义[实体](https://docs.abp.io/zh-Hans/abp/latest/Entities). 该应用程序的主要实体是`Book`. 在`Acme.BookStore.Domain`项目中创建一个名为`Book`的类,如下所示: +在解决方案的**领域层**(`Acme.BookStore.Domain`项目)中定义你的实体. -````C# +该应用程序的主要实体是`Book`. 在`Acme.BookStore.Domain`项目中创建一个 `Books` 文件夹并在其中添加一个名为 `Book` 的类,如下所示: + +````csharp using System; using Volo.Abp.Domain.Entities.Auditing; -namespace Acme.BookStore +namespace Acme.BookStore.Books { public class Book : AuditedAggregateRoot { @@ -133,33 +80,22 @@ namespace Acme.BookStore public DateTime PublishDate { get; set; } public float Price { get; set; } - - protected Book() - { - } - public Book(Guid id, string name, BookType type, DateTime publishDate, float price) - :base(id) - { - Name = name; - Type = type; - PublishDate = publishDate; - Price = price; - } } } ```` -* ABP为实体提供了两个基本的基类: `AggregateRoot`和`Entity`. **Aggregate Root**是**域驱动设计(DDD)** 概念之一. 有关详细信息和最佳做法,请参阅[实体文档](https://docs.abp.io/zh-Hans/abp/latest/Entities). -* `Book`实体继承了`AuditedAggregateRoot`,`AuditedAggregateRoot`类在`AggregateRoot`类的基础上添加了一些审计属性(`CreationTime`, `CreatorId`, `LastModificationTime` 等). +* ABP为实体提供了两个基本的基类: `AggregateRoot`和`Entity`. **Aggregate Root**是**[领域驱动设计](./Domain-Driven-Design.md)** 概念之一. 可以视为直接查询和处理的根实体(请参阅[实体文档](./Entities.md)). +* `Book`实体继承了`AuditedAggregateRoot`,`AuditedAggregateRoot`类在`AggregateRoot`类的基础上添加了一些审计属性(`CreationTime`, `CreatorId`, `LastModificationTime` 等). ABP框架自动为你管理这些属性. * `Guid`是`Book`实体的主键类型. -* 使用 **数据注解** 为EF Core添加映射.或者你也可以使用 EF Core 自带的[fluent mapping API](https://docs.microsoft.com/en-us/ef/core/modeling). -#### BookType枚举 +> 为了保持简单,本教程将实体属性保留为 **public get/set** . 如果你想了解关于DDD最佳实践,请参阅[实体文档](../Entities.md). -上面所用到的`BookType`枚举定义如下: +### BookType枚举 -````C# -namespace Acme.BookStore +上面所用到了 `BookType` 枚举,在 `Acme.BookStore.Domain.Shared` 项目创建 `BookType`. + +````csharp +namespace Acme.BookStore.Books { public enum BookType { @@ -176,69 +112,112 @@ namespace Acme.BookStore } ```` -#### 将Book实体添加到DbContext中 +最终的文件夹/文件结构应该如下所示: + +![bookstore-book-and-booktype](images/bookstore-book-and-booktype.png) + +### 将Book实体添加到DbContext中 -{{if DB == "ef"}} +{{if DB == "EF"}} EF Core需要你将实体和 `DbContext` 建立关联.最简单的做法是在`Acme.BookStore.EntityFrameworkCore`项目的`BookStoreDbContext`类中添加`DbSet`属性.如下所示: -````C# +````csharp public class BookStoreDbContext : AbpDbContext { - public DbSet Book { get; set; } - ... + public DbSet Books { get; set; } + //... } ```` {{end}} -{{if DB == "mongodb"}} +{{if DB == "Mongo"}} 添加 `IMongoCollection Book` 属性到 `Acme.BookStore.MongoDB` 项目的 `BookStoreMongoDbContext` 中. ```csharp public class BookStoreMongoDbContext : AbpMongoDbContext { - public IMongoCollection Users => Collection(); - public IMongoCollection Book => Collection();//<--added this line--> - //... + public IMongoCollection Books => Collection(); + //... } ``` {{end}} -{{if DB == "ef"}} +{{if DB == "EF"}} -#### 配置你的Book实体 +### 将Book实体映射到数据库表 -在 `Acme.BookStore.EntityFrameworkCore` 项目中打开 `BookStoreDbContextModelCreatingExtensions.cs` 文件,并将以下代码添加到 `ConfigureBookStore` 方法的末尾以配置Book实体: +在 `Acme.BookStore.EntityFrameworkCore` 项目中打开 `BookStoreDbContextModelCreatingExtensions.cs` 文件,添加 `Book` 实体的映射代码. 最终类应为: ````csharp -builder.Entity(b => +using Acme.BookStore.Books; +using Microsoft.EntityFrameworkCore; +using Volo.Abp; +using Volo.Abp.EntityFrameworkCore.Modeling; + +namespace Acme.BookStore.EntityFrameworkCore { - b.ToTable(BookStoreConsts.DbTablePrefix + "Book", BookStoreConsts.DbSchema); - b.ConfigureByConvention(); //auto configure for the base class props - b.Property(x => x.Name).IsRequired().HasMaxLength(128); -}); + public static class BookStoreDbContextModelCreatingExtensions + { + public static void ConfigureBookStore(this ModelBuilder builder) + { + Check.NotNull(builder, nameof(builder)); + + /* Configure your own tables/entities inside here */ + + builder.Entity(b => + { + b.ToTable(BookStoreConsts.DbTablePrefix + "Books", + BookStoreConsts.DbSchema); + b.ConfigureByConvention(); //auto configure for the base class props + b.Property(x => x.Name).IsRequired().HasMaxLength(128); + }); + } + } +} ```` -添加 `using Volo.Abp.EntityFrameworkCore.Modeling;` 以使用 `ConfigureByConvention` 扩展方法. +* `BookStoreConsts` 含有用于表的架构和表前缀的常量值. 你不必使用它,但建议在单点控制表前缀. +* `ConfigureByConvention()` 方法优雅的配置/映射继承的属性,应始终对你所有的实体使用它. -{{end}} +### 添加数据迁移 -{{if DB == "mongodb"}} +启动模板使用[EF Core Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/)创建和维护数据库架构. 打开菜单*工具 > NuGet包管理器*下的**程序包管理控制台 (PMC)**. + +![Open Package Manager Console](images/bookstore-open-package-manager-console.png) + +选择 `Acme.BookStore.EntityFrameworkCore.DbMigrations` 做为**默认项目**然后执行以下命令: + +```bash +Add-Migration "Created_Book_Entity" +``` + +![bookstore-pmc-add-book-migration](./images/bookstore-pmc-add-book-migration-v2.png) + +它会在 `Acme.BookStore.EntityFrameworkCore.DbMigrations` 项目中的 `Migrations` 文件内创建一个新的迁移类. + +在更新数据库之前,请阅读下面的部分了解如何将一些初始数据插入到数据库. + +> 如果你使用其他IDE而不是Visual Studio, 你可以使用 [`dotnet-ef]`(https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/?tabs=dotnet-core-cli#create-a-migration) 工具. + +{{end}} #### 添加种子数据 -添加种子数据是可选的,但第一次运行时最好将初始数据添加到数据库中. ABP提供了[数据种子系统](https://docs.abp.io/en/abp/latest/Data-Seeding). 在 `*.Domain` 项目下创建派生 `IDataSeedContributor` 的类: +> >在运行应用程序之前最好将初始数据添加到数据库中. 本节介绍ABP框架的[数据种子系统](../Data-Seeding.md). 如果你不想创建种子数据可以跳过本节,但是建议你遵循它来学习这个有用的ABP Framework功能。 + +在 `*.Domain` 项目下创建派生 `IDataSeedContributor` 的类,并且拷贝以下代码: ```csharp using System; using System.Threading.Tasks; +using Acme.BookStore.Books; using Volo.Abp.Data; using Volo.Abp.DependencyInjection; using Volo.Abp.Domain.Repositories; -using Volo.Abp.Guids; namespace Acme.BookStore { @@ -246,102 +225,75 @@ namespace Acme.BookStore : IDataSeedContributor, ITransientDependency { private readonly IRepository _bookRepository; - private readonly IGuidGenerator _guidGenerator; - public BookStoreDataSeederContributor( - IRepository bookRepository, - IGuidGenerator guidGenerator) + public BookStoreDataSeederContributor(IRepository bookRepository) { _bookRepository = bookRepository; - _guidGenerator = guidGenerator; } public async Task SeedAsync(DataSeedContext context) { - if (await _bookRepository.GetCountAsync() > 0) + if (await _bookRepository.GetCountAsync() <= 0) { - return; + await _bookRepository.InsertAsync( + new Book + { + Name = "1984", + Type = BookType.Dystopia, + PublishDate = new DateTime(1949, 6, 8), + Price = 19.84f + }, + autoSave: true + ); + + await _bookRepository.InsertAsync( + new Book + { + Name = "The Hitchhiker's Guide to the Galaxy", + Type = BookType.ScienceFiction, + PublishDate = new DateTime(1995, 9, 27), + Price = 42.0f + }, + autoSave: true + ); } - - await _bookRepository.InsertAsync( - new Book( - id: _guidGenerator.Create(), - name: "1984", - type: BookType.Dystopia, - publishDate: new DateTime(1949, 6, 8), - price: 19.84f - ) - ); - - await _bookRepository.InsertAsync( - new Book( - id: _guidGenerator.Create(), - name: "The Hitchhiker's Guide to the Galaxy", - type: BookType.ScienceFiction, - publishDate: new DateTime(1995, 9, 27), - price: 42.0f - ) - ); } } } ``` -{{end}} +* 如果数据库中当前没有图书,则此代码使用 `IRepository`(默认为[repository](../Repositories.md)将两本书插入数据库. -{{if DB == "ef"}} +### 更新数据库 -#### 添加新的Migration并更新数据库 +运行 `Acme.BookStore.DbMigrator` 应用程序来更新数据库: -这个启动模板使用了[EF Core Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/)来创建并维护数据库结构.打开 **程序包管理器控制台(Package Manager Console) (PMC)** (工具/Nuget包管理器菜单) +![bookstore-dbmigrator-on-solution](images/bookstore-dbmigrator-on-solution.png) -![Open Package Manager Console](./images/bookstore-open-package-manager-console.png) +{{if DB == "EF"}} -选择 `Acme.BookStore.EntityFrameworkCore.DbMigrations`作为默认的项目然后执行下面的命令: +`.DbMigrator` 是一个控制台使用程序,可以在**开发**和**生产**环境**迁移数据库架构**和**初始化种子数据**. -```bash -Add-Migration "Created_Book_Entity" -``` - -![bookstore-pmc-add-book-migration](./images/bookstore-pmc-add-book-migration-v2.png) - -这样就会在 `Migrations` 文件夹中创建一个新的migration类.然后执行 `Update-Database` 命令更新数据库结构: - -````bash -Update-Database -```` - -![bookstore-update-database-after-book-entity](./images/bookstore-update-database-after-book-entity.png) - -#### 添加示例数据 - -`Update-Database`命令在数据库中创建了`AppBook`表. 打开数据库并输入几个示例行,以便在页面上显示它们: - -```mssql -INSERT INTO AppBook (Id,CreationTime,[Name],[Type],PublishDate,Price) VALUES -('f3c04764-6bfd-49e2-859e-3f9bfda6183e', '2018-07-01', '1984',3,'1949-06-08','19.84') - -INSERT INTO AppBook (Id,CreationTime,[Name],[Type],PublishDate,Price) VALUES -('13024066-35c9-473c-997b-83cd8d3e29dc', '2018-07-01', 'The Hitchhiker`s Guide to the Galaxy',7,'1995-09-27','42') +{{end}} -INSERT INTO AppBook (Id,CreationTime,[Name],[Type],PublishDate,Price) VALUES -('4fa024a1-95ac-49c6-a709-6af9e4d54b54', '2018-07-02', 'Pet Sematary',5,'1983-11-14','23.7') -``` +{{if DB == "Mongo"}} -![bookstore-book-table](./images/bookstore-book-table.png) +尽管MongoDB**不需要**数据库架构迁移,但仍建议运行该应用程序,因为它可以在数据库上播种初始数据. 可以在**开发**和**生产**环境中使用. {{end}} -### 创建应用服务 +## 创建应用程序 -下一步是创建[应用服务](../Application-Services.md)来管理(创建,列出,更新,删除)书籍. 启动模板中的应用程序层分为两个项目: +应用程序层由两个分离的项目组成: -* `Acme.BookStore.Application.Contracts`主要包含你的DTO和应用程序服务接口. -* `Acme.BookStore.Application`包含应用程序服务的实现. +* `Acme.BookStore.Application.Contracts 包含你的[DTO](../Data-Transfer-Objects.md)和[应用服务](../Application-Services.md)接口. +* `Acme.BookStore.Application` 包含你的应用服务实现. -#### BookDto +在本部分中,你将创建一个应用程序服务,使用ABP Framework的 `CrudAppService` 基类来获取,创建,更新和删除书籍. -在`Acme.BookStore.Application.Contracts`项目中创建一个名为`BookDto`的DTO类: +### BookDto + +`CrudAppService` 基类需要定义实体的基本DTO. 在 `Acme.BookStore.Application.Contracts` 项目中创建一个名为 `BookDto` 的DTO类: ````C# using System; @@ -364,11 +316,12 @@ namespace Acme.BookStore * **DTO**类被用来在 **表示层** 和 **应用层** **传递数据**.查看[DTO文档](https://docs.abp.io/zh-Hans/abp/latest/Data-Transfer-Objects)查看更多信息. * 为了在页面上展示书籍信息,`BookDto`被用来将书籍数据传递到表示层. -* `BookDto`继承自 `AuditedEntityDto`.跟上面定义的`Book`类一样具有一些审计属性. +* `BookDto`继承自 `AuditedEntityDto`.跟上面定义的 `Book` 实体一样具有一些审计属性. 在将书籍返回到表示层时,需要将`Book`实体转换为`BookDto`对象. [AutoMapper](https://automapper.org)库可以在定义了正确的映射时自动执行此转换. 启动模板配置了AutoMapper,因此你只需在`Acme.BookStore.Application`项目的`BookStoreApplicationAutoMapperProfile`类中定义映射: ````csharp +using Acme.BookStore.Books; using AutoMapper; namespace Acme.BookStore @@ -383,16 +336,17 @@ namespace Acme.BookStore } ```` -#### CreateUpdateBookDto +> 参阅 [对象对对象映射](../Object-To-Object-Mapping.md) 文档了解详情. + +### CreateUpdateBookDto -在`Acme.BookStore.Application.Contracts`项目中创建一个名为`CreateUpdateBookDto`的DTO类: +在`Acme.BookStore.Application.Contracts`项目中创建一个名为 `CreateUpdateBookDto` 的DTO类: -````c# +````csharp using System; using System.ComponentModel.DataAnnotations; -using Volo.Abp.AutoMapper; -namespace Acme.BookStore +namespace Acme.BookStore.Books { public class CreateUpdateBookDto { @@ -404,7 +358,8 @@ namespace Acme.BookStore public BookType Type { get; set; } = BookType.Undefined; [Required] - public DateTime PublishDate { get; set; } + [DataType(DataType.Date)] + public DateTime PublishDate { get; set; } = DateTime.Now; [Required] public float Price { get; set; } @@ -415,9 +370,11 @@ namespace Acme.BookStore * 这个DTO类被用于在创建或更新书籍的时候从用户界面获取图书信息. * 它定义了数据注释属性(如`[Required]`)来定义属性的验证. DTO由ABP框架[自动验证](https://docs.abp.io/zh-Hans/abp/latest/Validation). -就像上面的`BookDto`一样,创建一个从`CreateUpdateBookDto`对象到`Book`实体的映射: +就像上面的`BookDto`一样,创建一个从`CreateUpdateBookDto`对象到`Book`实体的映射,最终映射配置类如下: + ````csharp +using Acme.BookStore.Books; using AutoMapper; namespace Acme.BookStore @@ -427,30 +384,29 @@ namespace Acme.BookStore public BookStoreApplicationAutoMapperProfile() { CreateMap(); - CreateMap(); //<--added this line--> + CreateMap(); } } } ```` -#### IBookAppService +### IBookAppService -在`Acme.BookStore.Application.Contracts`项目中定义一个名为`IBookAppService`的接口: +下一步是为应用程序定义接口,在`Acme.BookStore.Application.Contracts`项目中定义一个名为`IBookAppService`的接口: -````C# +````csharp using System; using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Services; -namespace Acme.BookStore +namespace Acme.BookStore.Books { public interface IBookAppService : - ICrudAppService< //定义了CRUD方法 - BookDto, //用来展示书籍 - Guid, //Book实体的主键 - PagedAndSortedResultRequestDto, //获取书籍的时候用于分页和排序 - CreateUpdateBookDto, //用于创建书籍 - CreateUpdateBookDto> //用于更新书籍 + ICrudAppService< //Defines CRUD methods + BookDto, //Used to show books + Guid, //Primary key of the book entity + PagedAndSortedResultRequestDto, //Used for paging/sorting + CreateUpdateBookDto> //Used to create/update a book { } @@ -458,25 +414,29 @@ namespace Acme.BookStore ```` * 框架定义应用程序服务的接口**不是必需的**. 但是,它被建议作为最佳实践. -* `ICrudAppService`定义了常见的**CRUD**方法:`GetAsync`,`GetListAsync`,`CreateAsync`,`UpdateAsync`和`DeleteAsync`. 你可以从空的`IApplicationService`接口继承并手动定义自己的方法. -* `ICrudAppService`有一些变体, 你可以在每个方法中使用单独的DTO,也可以分别单独指定. +* `ICrudAppService`定义了常见的**CRUD**方法:`GetAsync`,`GetListAsync`,`CreateAsync`,`UpdateAsync`和`DeleteAsync`. 你可以从空的`IApplicationService`接口继承并手动定义自己的方法(将在下一部分中完成). +* `ICrudAppService`有一些变体, 你可以在每个方法中使用单独的DTO,也可以分别单独指定(例如使用不同的DTO进行创建和更新). -#### BookAppService +### BookAppService -在`Acme.BookStore.Application`项目中实现名为`BookAppService`的`IBookAppService`: +在`Acme.BookStore.Application`项目中创建名为 `BookAppService` 的 `IBookAppService` 实现: -````C# +````csharp using System; using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Services; using Volo.Abp.Domain.Repositories; -namespace Acme.BookStore +namespace Acme.BookStore.Books { public class BookAppService : - CrudAppService, - IBookAppService + CrudAppService< + Book, //The Book entity + BookDto, //Used to show books + Guid, //Primary key of the book entity + PagedAndSortedResultRequestDto, //Used for paging/sorting + CreateUpdateBookDto>, //Used to create/update a book + IBookAppService //implement the IBookAppService { public BookAppService(IRepository repository) : base(repository) @@ -487,15 +447,17 @@ namespace Acme.BookStore } ```` -* `BookAppService`继承了`CrudAppService<...>`.它实现了上面定义的CRUD方法. +* `BookAppService`继承了`CrudAppService<...>`.它实现了 `ICrudAppService` 定义的CRUD方法. * `BookAppService`注入`IRepository `,这是`Book`实体的默认仓储. ABP自动为每个聚合根(或实体)创建默认仓储. 请参阅[仓储文档](https://docs.abp.io/zh-Hans/abp/latest/Repositories) -* `BookAppService`使用`IObjectMapper`将`Book`对象转换为`BookDto`对象, 将`CreateUpdateBookDto`对象转换为`Book`对象. 启动模板使用[AutoMapper](http://automapper.org/)库作为对象映射提供程序. 你之前定义了映射, 因此它将按预期工作. +* `BookAppService`使用[`IObjectMapper`](../Object-To-Object-Mapping.md)将`Book`对象转换为`BookDto`对象, 将`CreateUpdateBookDto`对象转换为`Book`对象. 启动模板使用[AutoMapper](http://automapper.org/)库作为对象映射提供程序. 我们之前定义了映射, 因此它将按预期工作. ### 自动生成API Controllers -你通常创建**Controller**以将应用程序服务公开为**HTTP API**端点. 因此允许浏览器或第三方客户端通过AJAX调用它们. ABP可以[**自动**](https://docs.abp.io/zh-Hans/abp/latest/API/Auto-API-Controllers)按照惯例将你的应用程序服务配置为MVC API控制器. +你通常创建**Controller**以将应用程序服务公开为**HTTP API**端点. 因此允许浏览器或第三方客户端通过AJAX调用它们. + +ABP可以[**自动**](../API/Auto-API-Controllers.md)按照惯例将你的应用程序服务配置为MVC API控制器. -#### Swagger UI +### Swagger UI 启动模板配置为使用[Swashbuckle.AspNetCore](https://github.com/domaindrivendev/Swashbuckle.AspNetCore)运行[swagger UI](https://swagger.io/tools/swagger-ui/). 运行应用程序并在浏览器中输入`https://localhost:XXXX/swagger/`(用你自己的端口替换XXXX)作为URL. @@ -503,485 +465,42 @@ namespace Acme.BookStore ![bookstore-swagger](images/bookstore-swagger.png) -Swagger有一个很好的UI来测试API. 你可以尝试执行`[GET] /api/app/book` API来获取书籍列表. - -{{if UI == "MVC"}} - -### 动态JavaScript代理 - -在Javascript端通过AJAX的方式调用HTTP API接口是很常见的,你可以使用`$.ajax`或者其他的工具来调用接口.当然,ABP中提供了更好的方式. - -ABP **自动** 为所有的API接口创建了JavaScript **代理**.因此,你可以像调用 **JavaScript function**一样调用任何接口. - -#### 在浏览器的开发者控制台中测试接口 - -你可以使用你钟爱的浏览器的 **开发者控制台** 中轻松测试JavaScript代理.运行程序,并打开浏览器的 **开发者工具**(快捷键:F12),切换到 **Console** 标签,输入下面的代码并回车: - -````js -acme.bookStore.book.getList({}).done(function (result) { console.log(result); }); -```` - -* `acme.bookStore`是`BookAppService`的命名空间,转换成了[驼峰命名](https://en.wikipedia.org/wiki/Camel_case). -* `book`是`BookAppService`转换后的名字(去除了AppService后缀并转成了驼峰命名). -* `getList`是定义在`AsyncCrudAppService`基类中的`GetListAsync`方法转换后的名字(去除了Async后缀并转成了驼峰命名). -* `{}`参数用于将空对象发送到`GetListAsync`方法,该方法通常需要一个类型为`PagedAndSortedResultRequestDto`的对象,用于向服务器发送分页和排序选项(所有属性都是可选的,所以你可以发送一个空对象). -* `getList`方法返回了一个`promise`.因此,你可以传递一个回调函数到`done`(或者`then`)方法中来获取服务返回的结果. - -运行这段代码会产生下面的输出: - -![bookstore-test-js-proxy-getlist](./images/bookstore-test-js-proxy-getlist.png) - -你可以看到服务器返回的 **book list**.你还可以切换到开发者工具的 **network** 查看客户端到服务器端的通讯信息: - -![bookstore-test-js-proxy-getlist-network](./images/bookstore-test-js-proxy-getlist-network.png) - -我们使用`create`方法 **创建一本新书**: - -````js -acme.bookStore.book.create({ name: 'Foundation', type: 7, publishDate: '1951-05-24', price: 21.5 }).done(function (result) { console.log('successfully created the book with id: ' + result.id); }); -```` - -你会看到控制台会显示类似这样的输出: - -````text -successfully created the book with id: 439b0ea8-923e-8e1e-5d97-39f2c7ac4246 -```` - -检查数据库中的`Book`表以查看新书. 你可以自己尝试`get`,`update`和`delete`功能. - -### 创建书籍页面 - -现在我们来创建一些可见和可用的东西,取代经典的MVC,我们使用微软推荐的[Razor Pages UI](https://docs.microsoft.com/en-us/aspnet/core/tutorials/razor-pages/razor-pages-start). +Swagger有一个很好的UI来测试API. -在 `Acme.BookStore.Web`项目的`Pages`文件夹下创建一个新的文件夹叫`Book`并添加一个名为`Index.cshtml`的Razor Page. +你可以尝试执行`[GET] /api/app/book` API来获取书籍列表, 服务端会返回以下JSON结果: -![bookstore-add-index-page](./images/bookstore-add-index-page-v2.png) - -打开`Index.cshtml`并把内容修改成下面这样: - -**Index.cshtml:** - -````html -@page -@using Acme.BookStore.Web.Pages.Book -@model IndexModel - -

    Book

    -```` - -* 确保`IndexModel`(Index.cshtml.cs)具有`Acme.BookStore.Web.Pages.Book`命名空间,或者在`Index.cshtml`中更新它. - -**Index.cshtml.cs:** - -```csharp -using Microsoft.AspNetCore.Mvc.RazorPages; - -namespace Acme.BookStore.Web.Pages.Book +````json { - public class IndexModel : PageModel + "totalCount": 2, + "items": [ { - public void OnGet() - { - - } - } -} -``` - -#### 将Book页面添加到主菜单 - -打开`Menus`文件夹中的 `BookStoreMenuContributor` 类,在`ConfigureMainMenuAsync`方法的底部添加如下代码: - -````csharp -//... -namespace Acme.BookStore.Web.Menus -{ - public class BookStoreMenuContributor : IMenuContributor + "name": "The Hitchhiker's Guide to the Galaxy", + "type": 7, + "publishDate": "1995-09-27T00:00:00", + "price": 42, + "lastModificationTime": null, + "lastModifierId": null, + "creationTime": "2020-07-03T21:04:18.4607218", + "creatorId": null, + "id": "86100bb6-cbc1-25be-6643-39f62806969c" + }, { - private async Task ConfigureMainMenuAsync(MenuConfigurationContext context) - { - //<-- added the below code - context.Menu.AddItem( - new ApplicationMenuItem("BookStore", l["Menu:BookStore"]) - .AddItem( - new ApplicationMenuItem("BookStore.Book", l["Menu:Book"], url: "/Book") - ) - ); - //--> - } + "name": "1984", + "type": 3, + "publishDate": "1949-06-08T00:00:00", + "price": 19.84, + "lastModificationTime": null, + "lastModifierId": null, + "creationTime": "2020-07-03T21:04:18.3174016", + "creatorId": null, + "id": "41055277-cce8-37d7-bb37-39f62806960b" } + ] } ```` -{{end}} - -#### 本地化菜单 - -本地化文本位于`Acme.BookStore.Domain.Shared`项目的`Localization/BookStore`文件夹下: - -![bookstore-localization-files](./images/bookstore-localization-files-v2.png) - -打开`en.json`文件,将`Menu:BookStore`和`Menu:Book`键的本地化文本添加到文件末尾: - -````json -{ - "Culture": "en", - "Texts": { - "Menu:Home": "Home", - "Welcome": "Welcome", - "LongWelcomeMessage": "Welcome to the application. This is a startup project based on the ABP framework. For more information, visit abp.io.", - - "Menu:BookStore": "Book Store", - "Menu:Book": "Book", - "Actions": "Actions", - "Edit": "Edit", - "PublishDate": "Publish date", - "NewBook": "New book", - "Name": "Name", - "Type": "Type", - "Price": "Price", - "CreationTime": "Creation time", - "AreYouSureToDelete": "Are you sure you want to delete this item?" - } -} -```` - -* ABP的本地化功能建立在[ASP.NET Core's standard localization]((https://docs.microsoft.com/en-us/aspnet/core/fundamentals/localization))之上并增加了一些扩展.查看[本地化文档](https://docs.abp.io/zh-Hans/abp/latest/Localization). -* 本地化key是任意的. 你可以设置任何名称. 我们更喜欢为菜单项添加`Menu:`前缀以区别于其他文本. 如果未在本地化文件中定义文本,则它将**返回**到本地化的key(ASP.NET Core的标准行为). - -运行该应用程序,看到新菜单项已添加到顶部栏: - -![bookstore-menu-items](./images/bookstore-new-menu-item.png) - -点击BookStore下Book子菜单项就会跳转到新增的书籍页面. - -#### 书籍列表 - -我们将使用[Datatables.net](https://datatables.net/)JQuery插件来显示页面上的表格列表. [Datatables](https://datatables.net/)可以完全通过AJAX工作,速度快,并提供良好的用户体验. Datatables插件在启动模板中配置,因此你可以直接在任何页面中使用它,而需要在页面中引用样式和脚本文件. - -##### Index.cshtml - -将`Pages/Book/Index.cshtml`改成下面的样子: - -````html -@page -@model Acme.BookStore.Web.Pages.Book.IndexModel -@section scripts -{ - -} - - -

    @L["Book"]

    -
    - - - - - @L["Name"] - @L["Type"] - @L["PublishDate"] - @L["Price"] - @L["CreationTime"] - - - - -
    -```` - -* `abp-script` [tag helper](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/tag-helpers/intro)用于将外部的 **脚本** 添加到页面中.它比标准的`script`标签多了很多额外的功能.它可以处理 **最小化**和 **版本**.查看[捆绑 & 压缩文档](https://docs.abp.io/zh-Hans/abp/latest/UI/AspNetCore/Bundling-Minification)获取更多信息. -* `abp-card` 和 `abp-table` 是为Twitter Bootstrap的[card component](http://getbootstrap.com/docs/4.1/components/card/)封装的 **tag helpers**.ABP中有很多tag helpers,可以很方便的使用大多数[bootstrap](https://getbootstrap.com/)组件.你也可以使用原生的HTML标签代替tag helpers.使用tag helper可以通过智能提示和编译时类型检查减少HTML代码并防止错误.查看[tag helpers 文档](https://docs.abp.io/zh-Hans/abp/latest/UI/AspNetCore/Tag-Helpers/Index). -* 你可以像上面本地化菜单一样 **本地化** 列名. - -#### 添加脚本文件 - -在`Pages/Book/`文件夹中创建 `index.js`文件 - -![bookstore-index-js-file](./images/bookstore-index-js-file-v2.png) - -`index.js`的内容如下: - -````js -$(function () { - var l = abp.localization.getResource('BookStore'); - var dataTable = $('#BooksTable').DataTable(abp.libs.datatables.normalizeConfiguration({ - ajax: abp.libs.datatables.createAjax(acme.bookStore.book.getList), - columnDefs: [ - { data: "name" }, - { data: "type" }, - { data: "publishDate" }, - { data: "price" }, - { data: "creationTime" } - ] - })); -}); -```` - -* `abp.libs.datatables.createAjax`是帮助ABP的动态JavaScript API代理跟[Datatable](https://datatables.net/)的格式相适应的辅助方法. -* `abp.libs.datatables.normalizeConfiguration`是另一个辅助方法.不是必须的, 但是它通过为缺少的选项提供常规值来简化数据表配置. -* `acme.bookStore.book.getList`是获取书籍列表的方法(上面已经介绍过了) -* 查看 [Datatable文档](https://datatables.net/manual/) 了解更多配置项. - -最终的页面如下: - -![Book list](./images/bookstore-book-list-2.png) - -{{end}} - -{{if UI == "NG"}} - -### Angular 开发 - -#### 创建book页面 - -是时候创建可见和可用的东西了!开发ABP Angular前端应用程序时,需要使用一些工具: - -- [Angular CLI](https://angular.io/cli) 用于创建模块,组件和服务. -- [Ng Bootstrap](https://ng-bootstrap.github.io/#/home) 用做UI组件库. -- [ngx-datatable](https://swimlane.gitbook.io/ngx-datatable/) 用做 datatable 类库. -- [Visual Studio Code](https://code.visualstudio.com/) 用做代码编辑器 (你可以选择自己喜欢的编辑器). - -#### 安装 NPM 包 - -在 `angular` 目录下打开命令行窗口,选择 `yarn` 命令安装NPM包: - -```bash -yarn -``` - -#### BookModule - -运行以下命令创建一个名为 `BookModule` 的新模块: - -```bash -yarn ng generate module book --routing true -``` - -![Generating book module](./images/bookstore-creating-book-module-terminal.png) - -#### 路由 - -打开位于 `src\app` 目录下的 `app-routing.module.ts` 文件. 添加新的路由: - -```js -const routes: Routes = [ -// ... -// added a new route to the routes array - { - path: 'books', - loadChildren: () => import('./book/book.module').then(m => m.BookModule) - } -] -``` - -* 我们添加了一个懒加载路由. 参阅 [嬾加載功能模块](https://angular.io/guide/lazy-loading-ngmodules#lazy-loading-feature-modules). - -打开位于 `src\app` 目录下的 `route.provider.ts` 文件,用以下内容替换它: - -```js -import { RoutesService, eLayoutType } from '@abp/ng.core'; -import { APP_INITIALIZER } from '@angular/core'; - -export const APP_ROUTE_PROVIDER = [ - { provide: APP_INITIALIZER, useFactory: configureRoutes, deps: [RoutesService], multi: true }, -]; - -function configureRoutes(routes: RoutesService) { - return () => { - routes.add([ - //... - // added below element - { - path: '/books', - name: '::Menu:Books', - iconClass: 'fas fa-book', - order: 101, - layout: eLayoutType.application, - }, - ]); - }; -} -``` - -* 我们添加了一个新的路由元素在菜单上显示为 "Books" 的导航元素. - * `path` 路由的URL. - * `name` 菜单项的名称,可以使用本地化Key. - * `iconClass` 菜单项的图标. - * `order` 菜单项的排序.我们定义了101,它显示在 "Administration" 项的后面. - * `layout` BooksModule路由的布局. 可以定义 `eLayoutType.application`, `eLayoutType.account` 或 `eLayoutType.empty`. - -更多信息请参阅[RoutesService 文档](https://docs.abp.io/en/abp/latest/UI/Angular/Modifying-the-Menu.md#via-routesservice). - -#### Book 列表组件 - -在命令行运行以下命令,生成名为 book-list 的新组件: - -```bash -yarn ng generate component book/book-list -``` - -![Creating book list](./images/bookstore-creating-book-list-terminal.png) - -打开 `app\book` 目录下的 `book.module.ts` 文件,使用以下内容替换它: - -```js -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { BookRoutingModule } from './book-routing.module'; -import { BookListComponent } from './book-list/book-list.component'; -import { SharedModule } from '../shared/shared.module'; //<== added this line ==> - -@NgModule({ - declarations: [BookListComponent], - imports: [ - CommonModule, - BookRoutingModule, - SharedModule, //<== added this line ==> - ], -}) -export class BookModule {} -``` - -* 我们导入了 `SharedModule` 并添加到 `imports` 数组. - -打开 `app\book` 目录下的 `book-routing.module.ts` 文件用以下内容替换它: - -```js -import { NgModule } from '@angular/core'; -import { Routes, RouterModule } from '@angular/router'; -import { BookListComponent } from './book-list/book-list.component'; // <== added this line ==> - -// <== replaced routes ==> -const routes: Routes = [ - { - path: '', - component: BookListComponent, - }, -]; - -@NgModule({ - imports: [RouterModule.forChild(routes)], - exports: [RouterModule] -}) -export class BookRoutingModule { } -``` - -* 我们导入了 `BookListComponent` 并替换 `routes` 常量. - -运行 `yarn start`,等待Angular启动服务: - -```bash -yarn start -``` - -我们将看到book页面的 **book-list works!**: - -![Initial book list page](./images/bookstore-initial-book-list-page.png) - -#### 生成代理 - -ABP CLI提供了 `generate-proxy` 命令为你的服务HTTP API生成客户端代理简化客户端使用服务的成本. 运行 `generate-proxy` 命令前你的host必须正在运行. 参阅 [CLI 文档](../CLI.md). - -在 `angular` 文件夹下运行以下命令: - -```bash -abp generate-proxy --module app -``` - -![Generate proxy command](./images/generate-proxy-command.png) - -生成的文件如下: - -![Generated files](./images/generated-proxies.png) - -#### BookListComponent - -打开 `app\book\book-list` 目录下的 `book-list.component.ts` 用以下内容替换它: - -```js -import { ListService, PagedResultDto } from '@abp/ng.core'; -import { Component, OnInit } from '@angular/core'; -import { BookDto, BookType } from '../models'; -import { BookService } from '../services'; - -@Component({ - selector: 'app-book-list', - templateUrl: './book-list.component.html', - styleUrls: ['./book-list.component.scss'], - providers: [ListService], -}) -export class BookListComponent implements OnInit { - book = { items: [], totalCount: 0 } as PagedResultDto; - - booksType = BookType; - - constructor(public readonly list: ListService, private bookService: BookService) {} - - ngOnInit() { - const bookStreamCreator = (query) => this.bookService.getListByInput(query); - - this.list.hookToQuery(bookStreamCreator).subscribe((response) => { - this.book = response; - }); - } -} -``` - -* 我们注入了生成的 `BookService`. -* 我们实现了 [ListService](https://docs.abp.io/en/abp/latest/UI/Angular/List-Service),它是一个公用服务,提供了简单的分页,排序和搜索. - -打开 `app\book\book-list` 目录下的 `book-list.component.html` 用以下内容替换它: - -```html -
    -
    -
    -
    -
    - {%{{{ '::Menu:Books' | abpLocalization }}}%} -
    -
    -
    -
    -
    -
    - - - - - {%{{{ booksType[row.type] }}}%} - - - - - {%{{{ row.publishDate | date }}}%} - - - - - {%{{{ row.price | currency }}}%} - - - -
    -
    -``` - -* 我们添加了图书列表页面的HTML代码. - -现在你可以在浏览器看到最终结果: - -![Book list final result](./images/bookstore-book-list.png) - -项目的文件系统结构: - -![Book list final result](./images/bookstore-angular-file-tree.png) - -在本教程中我们遵循了官方的[Angular风格指南](https://angular.io/guide/styleguide#file-tree). - -{{end}} +这很酷,因为我们没有编写任何代码来创建API控制器,但是现在我们有了一个可以正常使用的REST API! -### 下一章 +## 下一章 -参阅[第二章](part-2.md)了解创建,更新和删除图书. +参阅教程的[下一章](part-2.md). diff --git a/docs/zh-Hans/Tutorials/Part-10.md b/docs/zh-Hans/Tutorials/Part-10.md new file mode 100644 index 0000000000..602306c484 --- /dev/null +++ b/docs/zh-Hans/Tutorials/Part-10.md @@ -0,0 +1 @@ +TODO.. \ No newline at end of file diff --git a/docs/zh-Hans/Tutorials/Part-2.md b/docs/zh-Hans/Tutorials/Part-2.md index 751bd85ece..decddfebff 100644 --- a/docs/zh-Hans/Tutorials/Part-2.md +++ b/docs/zh-Hans/Tutorials/Part-2.md @@ -1,8 +1,9 @@ -## ASP.NET Core {{UI_Value}} 教程 - 第二章 +# Web应用程序开发教程 - 第二章: 图书列表页面 ````json //[doc-params] { - "UI": ["MVC","NG"] + "UI": ["MVC","NG"], + "DB": ["EF","Mongo"] } ```` @@ -21,904 +22,476 @@ else end }} -### 关于本教程 +## 关于本教程 -这是ASP.NET Core{{UI_Value}}系列教程的第二章. 共有三章: +在本系列教程中, 你将构建一个名为 `Acme.BookStore` 的用于管理书籍及其作者列表的基于ABP的应用程序. 它是使用以下技术开发的: -- [Part-1: 创建项目和书籍列表页面](Part-1.md) -- **Part 2: 创建,编辑,删除书籍(本章)** -- [Part-3: 集成测试](Part-3.md) +* **{{DB_Text}}** 做为ORM提供程序. +* **{{UI_Value}}** 做为UI框架. -> 你也可以观看由ABP社区成员为本教程录制的[视频课程](https://amazingsolutions.teachable.com/p/lets-build-the-bookstore-application). +本教程分为以下部分: -{{if UI == "MVC"}} - -### 新增 Book 实体 +- [Part 1: 创建服务端](Part-1.md) +- **Part 2: 图书列表页面**(本章) +- [Part 3: 创建,更新和删除图书](Part-3.md) +- [Part 4: 集成测试](Part-4.md) +- [Part 5: 授权](Part-5.md) +- [Part 6: 作者: 领域层](Part-6.md) +- [Part 7: 作者: 数据库集成](Part-7.md) +- [Part 8: 作者: 应用服务层](Part-8.md) +- [Part 9: 作者: 用户页面](Part-9.md) +- [Part 10: 图书到作者的关系](Part-10.md) -通过本节, 你将会了解如何创建一个 modal form 来实现新增书籍的功能. 最终成果如下图所示: +## 下载源码 -![bookstore-create-dialog](./images/bookstore-create-dialog-2.png) +本教程根据你的**UI** 和 **Database**偏好有多个版,我们准备了两种可供下载的源码组合: -#### 新建 modal form +* [MVC (Razor Pages) UI 与 EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore) +* [Angular UI 与 MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb) -在 `Acme.BookStore.Web` 项目的 `Pages/Books` 目录下新建一个 `CreateModal.cshtml` Razor页面: +{{if UI == "MVC"}} -![bookstore-add-create-dialog](./images/bookstore-add-create-dialog-v2.png) +## 动态JavaScript代理 -##### CreateModal.cshtml.cs +通常在 **JavaScript** 端通过AJAX调用HTTP API端点. 你可以使用 `$.ajax` 或其他工具来调用端点. 但是ABP提供了更好的方法. -打开 `CreateModal.cshtml.cs` 代码文件,用如下代码替换 `CreateModalModel` 类的实现: +ABP**动态**为所有API端点创建 **[JavaScript代理](../UI/AspNetCore/Dynamic-JavaScript-Proxies.md)**. 所以你可以像调用**Javascript本地方法**一样使用任何**端点**. -````C# -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; +### 在开发者控制台中进行测试 -namespace Acme.BookStore.Web.Pages.Books -{ - public class CreateModalModel : BookStorePageModel - { - [BindProperty] - public CreateUpdateBookDto Book { get; set; } +你可以在自己喜欢的浏览器的**开发者控制台**轻松的测试JavaScript代理. 运行应用程序,打开浏览器的**开发者人员工具**(*快捷键通常是F12*),切换到**控制台**选项卡,输入以下代码然后按回车: - private readonly IBookAppService _bookAppService; - - public CreateModalModel(IBookAppService bookAppService) - { - _bookAppService = bookAppService; - } - - public async Task OnPostAsync() - { - await _bookAppService.CreateAsync(Book); - return NoContent(); - } - } -} +````js +acme.bookStore.books.book.getList({}).done(function (result) { console.log(result); }); ```` -* 该类派生于 `BookStorePageModel` 而非默认的 `PageModel`. `BookStorePageModel` 继承了 `PageModel` 并且添加了一些可以被你的page model类使用的通用属性和方法. -* `Book` 属性上的 `[BindProperty]` 特性将post请求提交上来的数据绑定到该属性上. -* 该类通过构造函数注入了 `IBookAppService` 应用服务,并且在 `OnPostAsync` 处理程序中调用了服务的 `CreateAsync` 方法. +* `acme.bookStore.books` 是 `BookAppService` 的命令空间转换成[小驼峰](https://en.wikipedia.org/wiki/Camel_case)形式. +* `book` 是 `BookAppService` 的约定名称(删除`AppService`后缀并且转换为小驼峰). +* `getList` 是 `CrudAppService` 基类定义的 `GetListAsync` 方法的约定名称(删除`Async`后缀并且转换为小驼峰). +* `{}` 参数将空对象发送到 `GetListAsync` 方法,该方法通常需要一个类型为 `PagedAndSortedResultRequestDto` 的对象,该对象用于将分页和排序选项发送到服务器(所有属性都是可选的,具有默认值. 因此你可以发送一个空对象). +* `getList` 函数返回一个 `promise`. 你可以传递一个回调到 `then`(或`done`)函数来获取从服务器返回的结果. -##### CreateModal.cshtml +运行该代码会产生以下输出: -打开 `CreateModal.cshtml` 文件并粘贴如下代码: +![bookstore-javascript-proxy-console](images/bookstore-javascript-proxy-console.png) -````html -@page -@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal -@model Acme.BookStore.Web.Pages.Books.CreateModalModel -@{ - Layout = null; -} - - - - - - - - - -```` +你可以看到服务端返回的 **图书列表**. 你也可以在开发者人员工具的 **网络** 选项卡查看客户端到服务端的通信: -* 这个 modal 使用 `abp-dynamic-form` Tag Helper 根据 `CreateBookViewModel` 类自动构建了表单. - * `abp-model` 指定了 `Book` 属性为模型对象. - * `data-ajaxForm` 设置了表单通过AJAX提交,而不是经典的页面回发. - * `abp-form-content` tag helper 作为表单控件渲染位置的占位符 (这是可选的,只有你在 `abp-dynamic-form` 中像本示例这样添加了其他内容才需要). +![bookstore-getlist-result-network](images/bookstore-getlist-result-network.png) -#### 添加 "New book" 按钮 +Let's **create a new book** using the `create` function: -打开 `Pages/Books/Index.cshtml` 并按如下代码修改 `abp-card-header` : +让我们使用 `create` 函数**创建一本书**: -````html - - - -

    @L["Books"]

    -
    - - - -
    -
    +````js +acme.bookStore.books.book.create({ + name: 'Foundation', + type: 7, + publishDate: '1951-05-24', + price: 21.5 + }).then(function (result) { + console.log('successfully created the book with id: ' + result.id); + }); ```` -如下图所示,只是在表格 **右上方** 添加了 **New book** 按钮: +您应该在控制台中看到类似以下的消息: -![bookstore-new-book-button](./images/bookstore-new-book-button.png) +````text +successfully created the book with id: 439b0ea8-923e-8e1e-5d97-39f2c7ac4246 +```` -打开 `Pages/book/index.js` 在 `datatable` 配置代码后面添加如下代码: +检查数据库中的 `Books` 表你会看到新的一行. 你可以自己尝试使用 `get`, `update` 和 `delete` 函数. -````js -var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal'); +我们将利用这些动态代理功能在接下来的章节来与服务器通信. -createModal.onResult(function () { - dataTable.ajax.reload(); -}); +{{end}} -$('#NewBookButton').click(function (e) { - e.preventDefault(); - createModal.open(); -}); -```` +## 本地化 -* `abp.ModalManager` 是一个在客户端打开和管理modal的辅助类.它基于Twitter Bootstrap的标准modal组件通过简化的API抽象隐藏了许多细节. +开始的UI开发之前,我们首先要准备本地化的文本(这是你通常在开发应用程序时需要做的). -现在,你可以 **运行程序** 通过新的 modal form 来创建书籍了. +本地化文本位于 `Acme.BookStore.Domain.Shared` 项目的 `Localization/BookStore` 文件夹下: -### 编辑更新已存在的 Book 实体 +![bookstore-localization-files](./images/bookstore-localization-files-v2.png) -在 `Acme.BookStore.Web` 项目的 `Pages/Books` 目录下新建一个名叫 `EditModal.cshtml` 的Razor页面: +打开 `en.json` (*英文翻译*)文件并更改内容,如下所示: -![bookstore-add-edit-dialog](./images/bookstore-add-edit-dialog.png) +````json +{ + "Culture": "en", + "Texts": { + "Menu:Home": "Home", + "Welcome": "Welcome", + "LongWelcomeMessage": "Welcome to the application. This is a startup project based on the ABP framework. For more information, visit abp.io.", + "Menu:BookStore": "Book Store", + "Menu:Books": "Books", + "Actions": "Actions", + "Close": "Close", + "Delete": "Delete", + "Edit": "Edit", + "PublishDate": "Publish date", + "NewBook": "New book", + "Name": "Name", + "Type": "Type", + "Price": "Price", + "CreationTime": "Creation time", + "AreYouSure": "Are you sure?", + "AreYouSureToDelete": "Are you sure you want to delete this item?", + "Enum:BookType:0": "Undefined", + "Enum:BookType:1": "Adventure", + "Enum:BookType:2": "Biography", + "Enum:BookType:3": "Dystopia", + "Enum:BookType:4": "Fantastic", + "Enum:BookType:5": "Horror", + "Enum:BookType:6": "Science", + "Enum:BookType:7": "Science fiction", + "Enum:BookType:8": "Poetry" + } +} +```` -#### EditModal.cshtml.cs +* 本地化关键字名称是任意的. 你可以设置任何名称. 对于特定的文本类型,我们更喜欢遵循一些约定: + * 为按钮项添加 `Menu:` 前缀. + * 使用 `Enum::` 命名约定来本地化枚举成员. 当您这样做时ABP可以在某些适当的情况下自动将枚举本地化. -展开 `EditModal.cshtml`,打开 `EditModal.cshtml.cs` 文件( `EditModalModel` 类) 并替换成以下代码: +如果未在本地化文件中定义文本,则文本将**回退**到本地化键(作为ASP.NET Core的标准行为). -````csharp -using System; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; +> ABP本地化系统建立在[ASP.NET Core标准本地化](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/localization)系统之上,并以多种方式进行了扩展. 有关详细信息请参见[本地化文档](../Localization.md). -namespace Acme.BookStore.Web.Pages.Books -{ - public class EditModalModel : BookStorePageModel - { - [HiddenInput] - [BindProperty(SupportsGet = true)] - public Guid Id { get; set; } +{{if UI == "MVC"}} - [BindProperty] - public CreateUpdateBookDto Book { get; set; } +## 创建图书页面 - private readonly IBookAppService _bookAppService; +是时候创建可见的和可用的东西了! 代替经典的MVC,我们将使用微软推荐的[Razor Pages UI](https://docs.microsoft.com/zh-cn/aspnet/core/tutorials/razor-pages/razor-pages-start). - public EditModalModel(IBookAppService bookAppService) - { - _bookAppService = bookAppService; - } +在 `Acme.BookStore.Web` 项目的 `Pages` 文件夹下创建一个名为新的 `Books` 的文件夹. 然后在文件夹右键选择 **添加 > Razor Page** 菜单. 输入名称 `Index`: - public async Task OnGetAsync() - { - var bookDto = await _bookAppService.GetAsync(Id); - Book = ObjectMapper.Map(bookDto); - } +![bookstore-add-index-page](./images/bookstore-add-index-page-v2.png) - public async Task OnPostAsync() - { - await _bookAppService.UpdateAsync(Id, Book); - return NoContent(); - } - } -} -```` +打开 `Index.cshtml` 并把内容修改成下面这样: -* `[HiddenInput]` 和 `[BindProperty]` 是标准的 ASP.NET Core MVC 特性.这里启用 `SupportsGet` 从Http请求的查询字符串中获取Id的值. -* 在 `OnGetAsync` 方法中,将 `BookAppService.GetAsync` 方法返回的 `BookDto` 映射成 `CreateUpdateBookDto` 并赋值给Book属性. -* `OnPostAsync` 方法直接使用 `BookAppService.UpdateAsync` 来更新实体. +````html +@page +@using Acme.BookStore.Web.Pages.Books +@model IndexModel -#### BookDto到CreateUpdateBookDto对象映射 +

    Books

    +```` -为了执行`BookDto`到`CreateUpdateBookDto`对象映射,请打开`Acme.BookStore.Web`项目中的`BookStoreWebAutoMapperProfile.cs`并更改它,如下所示: +`Index.cshtml.cs` 内容应该是: -````csharp -using AutoMapper; +```csharp +using Microsoft.AspNetCore.Mvc.RazorPages; -namespace Acme.BookStore.Web +namespace Acme.BookStore.Web.Pages.Books { - public class BookStoreWebAutoMapperProfile : Profile + public class IndexModel : PageModel { - public BookStoreWebAutoMapperProfile() + public void OnGet() { - CreateMap(); + } } } -```` - -* 刚刚添加了`CreateMap();`作为映射定义. +``` -#### EditModal.cshtml +### 将Book页面添加到主菜单 -将 `EditModal.cshtml` 页面内容替换成如下代码: +打开 `Menus` 文件夹中的 `BookStoreMenuContributor` 类,在 `ConfigureMainMenuAsync` 方法的底部添加如下代码: -````html -@page -@using Acme.BookStore.Web.Pages.Books -@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal -@model EditModalModel -@{ - Layout = null; -} - - - - - - - - - - +````csharp +context.Menu.AddItem( + new ApplicationMenuItem( + "BooksStore", + l["Menu:BookStore"], + icon: "fa fa-book" + ).AddItem( + new ApplicationMenuItem( + "BooksStore.Books", + l["Menu:Books"], + url: "/Books" + ) + ) +); ```` -这个页面内容和 `CreateModal.cshtml` 非常相似,除了以下几点: +运行项目,使用用户名 `admin` 和密码 `1q2w3E*` 登录到应用程序. 看到新菜单项已添加到顶部栏: -* 它包含`id`属性的`abp-input`, 用于存储编辑书的 `id` (它是隐藏的Input) -* 此页面指定的post地址是`Books/EditModal`, 并用文本 *Update* 作为 modal 标题. +![bookstore-menu-items](./images/bookstore-new-menu-item.png) -#### 为表格添加 "操作(Actions)" 下拉菜单 - -我们将为表格每行添加下拉按钮 ("Actions") . 最终效果如下: - -![bookstore-book-table-actions](images/bookstore-book-table-actions.png) - -打开 `Pages/Books/Index.cshtml` 页面,并按下方所示修改表格部分的代码: - -````html - - - - @L["Actions"] - @L["Name"] - @L["Type"] - @L["PublishDate"] - @L["Price"] - @L["CreationTime"] - - - -```` +点击BookStore下的Books子菜单项就会跳转到空的图书页面. -* 只是为"Actions"增加了一个 `th` 标签. +### 图书列表 -打开 `Pages/book/index.js` 并用以下内容进行替换: +We will use the [Datatables.net](https://datatables.net/) jQuery library to show the book list. Datatables library completely work via AJAX, it is fast, popular and provides a good user experience. -````js -$(function () { +我们将使用[Datatables.net](https://datatables.net/)JQuery插件来显示页面上的表格列表. [Datatables](https://datatables.net/)可以完全通过AJAX工作,速度快,并提供良好的用户体验. - var l = abp.localization.getResource('BookStore'); +> Datatables插件在启动模板中配置,因此你可以直接在任何页面中使用它,无需在页面中引用样式和脚本文件. - var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal'); - var editModal = new abp.ModalManager(abp.appPath + 'Books/EditModal'); - - var dataTable = $('#BooksTable').DataTable(abp.libs.datatables.normalizeConfiguration({ - processing: true, - serverSide: true, - paging: true, - searching: false, - autoWidth: false, - scrollCollapse: true, - order: [[1, "asc"]], - ajax: abp.libs.datatables.createAjax(acme.bookStore.book.getList), - columnDefs: [ - { - rowAction: { - items: - [ - { - text: l('Edit'), - action: function (data) { - editModal.open({ id: data.record.id }); - } - } - ] - } - }, - { data: "name" }, - { data: "type" }, - { data: "publishDate" }, - { data: "price" }, - { data: "creationTime" } - ] - })); - - createModal.onResult(function () { - dataTable.ajax.reload(); - }); +#### Index.cshtml - editModal.onResult(function () { - dataTable.ajax.reload(); - }); +将 `Pages/Book/Index.cshtml` 改成下面的样子: - $('#NewBookButton').click(function (e) { - e.preventDefault(); - createModal.open(); - }); -}); +````html +@page +@using Acme.BookStore.Localization +@using Acme.BookStore.Web.Pages.Books +@using Microsoft.Extensions.Localization +@model IndexModel +@inject IStringLocalizer L +@section scripts +{ + +} + + +

    @L["Books"]

    +
    + + + +
    ```` -* 通过 `abp.localization.getResource('BookStore')` 可以在客户端使用服务器端定义的相同的本地化语言文本. -* 添加了一个名为 `createModal` 的新的 `ModalManager` 来打开创建用的 modal 对话框. -* 添加了一个名为 `editModal` 的新的 `ModalManager` 来打开编辑用的 modal 对话框. -* 在 `columnDefs` 起始处新增一列用于显示 "Actions" 下拉按钮. -* "New Book"动作只需调用`createModal.open`来打开创建对话框. -* "Edit" 操作只是简单调用 `editModal.open` 来打开编辑对话框. +* `abp-script` [tag helper](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/tag-helpers/intro)用于将外部的 **脚本** 添加到页面中.它比标准的`script`标签多了很多额外的功能.它可以处理 **最小化**和 **版本**.查看[捆绑 & 压缩文档](../UI/AspNetCore/Bundling-Minification.md)获取更多信息. +* `abp-card` 和 `abp-table` 是为Twitter Bootstrap的[card component](http://getbootstrap.com/docs/4.5/components/card/)封装的 **tag helpers**.ABP中有很多tag helpers,可以很方便的使用大多数[bootstrap](https://getbootstrap.com/)组件.你也可以使用原生的HTML标签代替tag helpers.使用tag helper可以通过智能提示和编译时类型检查减少HTML代码并防止错误.查看[tag helpers 文档](../UI/AspNetCore/Tag-Helpers/Index.md). -现在,你可以运行程序,通过编辑操作来更新任一个book实体. +#### Index.js -### 删除一个已有的Book实体 +在 `Pages/Books/` 文件夹中创建 `index.js`文件 -打开 `Pages/book/index.js` 文件,在 `rowAction` `items` 下新增一项: +![bookstore-index-js-file](./images/bookstore-index-js-file-v3.png) -````js -{ - text: l('Delete'), - confirmMessage: function (data) { - return l('BookDeletionConfirmationMessage', data.record.name); - }, - action: function (data) { - acme.bookStore.book - .delete(data.record.id) - .then(function() { - abp.notify.info(l('SuccessfullyDeleted')); - dataTable.ajax.reload(); - }); - } -} -```` - -* `confirmMessage` 用来在实际执行 `action` 之前向用户进行确认. -* 通过javascript代理方法 `acme.bookStore.book.delete` 执行一个AJAX请求来删除一个book实体. -* `abp.notify.info` 用来在执行删除操作后显示一个toastr通知信息. - -最终的 `index.js` 文件内容如下所示: +`index.js` 的内容如下: ````js $(function () { - var l = abp.localization.getResource('BookStore'); - var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal'); - var editModal = new abp.ModalManager(abp.appPath + 'Books/EditModal'); - - var dataTable = $('#BooksTable').DataTable(abp.libs.datatables.normalizeConfiguration({ - processing: true, - serverSide: true, - paging: true, - searching: false, - autoWidth: false, - scrollCollapse: true, - order: [[1, "asc"]], - ajax: abp.libs.datatables.createAjax(acme.bookStore.book.getList), - columnDefs: [ - { - rowAction: { - items: - [ - { - text: l('Edit'), - action: function (data) { - editModal.open({ id: data.record.id }); - } - }, - { - text: l('Delete'), - confirmMessage: function (data) { - return l('BookDeletionConfirmationMessage', data.record.name); - }, - action: function (data) { - acme.bookStore.book - .delete(data.record.id) - .then(function() { - abp.notify.info(l('SuccessfullyDeleted')); - dataTable.ajax.reload(); - }); - } - } - ] + var dataTable = $('#BooksTable').DataTable( + abp.libs.datatables.normalizeConfiguration({ + serverSide: true, + paging: true, + order: [[1, "asc"]], + searching: false, + scrollX: true, + ajax: abp.libs.datatables.createAjax(acme.bookStore.books.book.getList), + columnDefs: [ + { + title: l('Name'), + data: "name" + }, + { + title: l('Type'), + data: "type", + render: function (data) { + return l('Enum:BookType:' + data); + } + }, + { + title: l('PublishDate'), + data: "publishDate", + render: function (data) { + return luxon + .DateTime + .fromISO(data, { + locale: abp.localization.currentCulture.name + }).toLocaleString(); + } + }, + { + title: l('Price'), + data: "price" + }, + { + title: l('CreationTime'), data: "creationTime", + render: function (data) { + return luxon + .DateTime + .fromISO(data, { + locale: abp.localization.currentCulture.name + }).toLocaleString(luxon.DateTime.DATETIME_SHORT); + } } - }, - { data: "name" }, - { data: "type" }, - { data: "publishDate" }, - { data: "price" }, - { data: "creationTime" } - ] - })); - - createModal.onResult(function () { - dataTable.ajax.reload(); - }); - - editModal.onResult(function () { - dataTable.ajax.reload(); - }); - - $('#NewBookButton').click(function (e) { - e.preventDefault(); - createModal.open(); - }); + ] + }) + ); }); ```` -打开`Acme.BookStore.Domain.Shared`项目中的`en.json`并添加以下行: - -````json -"BookDeletionConfirmationMessage": "Are you sure to delete the book {0}?", -"SuccessfullyDeleted": "Successfully deleted" -```` - -运行程序并尝试删除一个book实体. - -{{end}} - -{{if UI == "NG"}} - -### 新增 Book 实体 - -下面的章节中,你将学习到如何创建一个新的模态对话框来新增Book实体. - -#### 添加 modal 到 BookListComponent - - -Open `book-list.component.ts` file in `app\book\book-list` folder and replace the content as below: - -```js -import { ListService, PagedResultDto } from '@abp/ng.core'; -import { Component, OnInit } from '@angular/core'; -import { BookDto, BookType } from '../models'; -import { BookService } from '../services'; - -@Component({ - selector: 'app-book-list', - templateUrl: './book-list.component.html', - styleUrls: ['./book-list.component.scss'], - providers: [ListService], -}) -export class BookListComponent implements OnInit { - book = { items: [], totalCount: 0 } as PagedResultDto; - - booksType = BookType; - - isModalOpen = false; // <== added this line ==> - - constructor(public readonly list: ListService, private bookService: BookService) {} - - ngOnInit() { - const bookStreamCreator = (query) => this.bookService.getListByInput(query); - - this.list.hookToQuery(bookStreamCreator).subscribe((response) => { - this.book = response; - }); - } - - // added createBook method - createBook() { - this.isModalOpen = true; - } -} -``` - -* 我们定义了一个名为 `isModalOpen` 的变量和 `createBook` 方法. +* `abp.localization.getResource` 获取一个函数,该函数用于使用服务器端定义的相同JSON文件对文本进行本地化. 通过这种方式你可以与客户端共享本地化值. +* `abp.libs.datatables.normalizeConfiguration`是另一个辅助方法.不是必须的, 但是它通过为缺少的选项提供常规值来简化数据表配置. +* `abp.libs.datatables.createAjax`是帮助ABP的动态JavaScript API代理跟[Datatable](https://datatables.net/)的格式相适应的辅助方法. +* `acme.bookStore.books.book.getList` 是动态JavaScript代理函数(上面已经介绍过了) +* [luxon](https://moment.github.io/luxon/) 库也是该解决方案中预先配置的标准库,你可以轻松地执行日期/时间操作. -打开 `app\book\book-list` 文件夹内的 `book-list.component.html` 文件,使用以下内容替换它: +> 查看 [Datatable文档](https://datatables.net/manual/) 了解更多配置项. -```html -
    -
    -
    -
    -
    {%{{{ '::Menu:Books' | abpLocalization }}}%}
    -
    - -
    -
    - -
    -
    -
    -
    -
    - - - - - {%{{{ booksType[row.type] }}}%} - - - - - {%{{{ row.publishDate | date }}}%} - - - - - {%{{{ row.price | currency }}}%} - - - -
    -
    - - - - -

    {%{{{ '::NewBook' | abpLocalization }}}%}

    -
    - - - - - - -
    -``` +## 运行最终应用程序 -* 我们添加了 `abp-modal` 渲染模态框,允许用户创建新书. -* `abp-modal` 是显示模态框的预构建组件. 你也可以使用其它方法显示模态框,但 `abp-modal` 提供了一些附加的好处. -* 我们添加了 `New book` 按钮到 `AbpContentToolbar`. - -你可以打开浏览器,点击**New book**按钮看到模态框. - -![Empty modal for new book](./images/bookstore-empty-new-book-modal.png) - -#### 添加响应式表单 - -[响应式表单](https://angular.io/guide/reactive-forms) 提供一种模型驱动的方法来处理其值随时间变化的表单输入. - -打开 `app\book\book-list` 文件夹下的 `book-list.component.ts` 文件,使用以下内容替换它: - -```js -import { ListService, PagedResultDto } from '@abp/ng.core'; -import { Component, OnInit } from '@angular/core'; -import { BookDto, BookType } from '../models'; -import { BookService } from '../services'; -import { FormGroup, FormBuilder, Validators } from '@angular/forms'; // <== added this line ==> - -@Component({ - selector: 'app-book-list', - templateUrl: './book-list.component.html', - styleUrls: ['./book-list.component.scss'], - providers: [ListService], -}) -export class BookListComponent implements OnInit { - book = { items: [], totalCount: 0 } as PagedResultDto; +你可以运行应用程序!该部分的最终用户界面如下所示: - booksType = BookType; +![Book list](images/bookstore-book-list-3.png) - isModalOpen = false; +这是一个完全正常工作的服务端分页,排序和本地化的图书列表. - form: FormGroup; // <== added this line ==> +{{end}} - constructor( - public readonly list: ListService, - private bookService: BookService, - private fb: FormBuilder // <== injected FormBuilder ==> - ) {} +{{if UI == "NG"}} - ngOnInit() { - const bookStreamCreator = (query) => this.bookService.getListByInput(query); +## 安装NPM包 - this.list.hookToQuery(bookStreamCreator).subscribe((response) => { - this.book = response; - }); - } +>注意: 本教程基于ABP Framework v3.0.3+. 如果你的项目版本较旧,请升级您的解决方案. 如果要升级现有的v2.x项目,请参阅[迁移指南](../UI/Angular/Migration-Guide-v3.md). - createBook() { - this.buildForm(); // <== added this line ==> - this.isModalOpen = true; - } +在 `angular` 目录下打开命令行窗口,选择 `yarn` 命令安装NPM包: - // added buildForm method - buildForm() { - this.form = this.fb.group({ - name: ['', Validators.required], - type: [null, Validators.required], - publishDate: [null, Validators.required], - price: [null, Validators.required], - }); - } -} +```bash +yarn ``` -* 我们导入了 `FormGroup, FormBuilder and Validators`. -* 我们添加了 `form: FormGroup` 变量. -* 我们注入了 `fb: FormBuilder` 服务到构造函数. [FormBuilder](https://angular.io/api/forms/FormBuilder) 服务为生成控件提供了方便的方法. 它减少了构建复杂表单所需的样板文件的数量. -* 我们添加了 `buildForm` 方法到文件末尾, 在 `createBook` 方法调用 `buildForm()` 方法. 该方法创建一个响应式表单去创建新书. - * `FormBuilder` 内的 `fb.group` 方法创建一个 `FormGroup`. - * 添加了 `Validators.required` 静态方法用于验证表单元素. +## 创建图书页面 -#### 创建表单的DOM元素 +是时候创建可见和可用的东西了!开发ABP Angular前端应用程序时,需要使用一些工具: -打开 `app\book\book-list` 文件夹下的 `book-list.component.html` 文件,使用以下内容替换 ` `: +- [Ng Bootstrap](https://ng-bootstrap.github.io/#/home) 用做UI组件库. +- [ngx-datatable](https://swimlane.gitbook.io/ngx-datatable/) 用做 datatable 类库. -```html - -
    -
    - * - -
    +### BookModule -
    - * - -
    - -
    - * - -
    +运行以下命令创建一个名为 `BookModule` 的新模块: -
    - * - -
    -
    -
    +```bash +yarn ng generate module book --module app --routing --route books ``` -- 模板创建了带有 `Name`, `Price`, `Type` 和 `Publish` 时间字段的表单. -- 我们在组件中使用了 [NgBootstrap datepicker](https://ng-bootstrap.github.io/#/components/datepicker/overview). +该命令应该产生以下的输出: + +````bash +> yarn ng generate module book --module app --routing --route books + +yarn run v1.19.1 +$ ng generate module book --module app --routing --route books +CREATE src/app/book/book-routing.module.ts (336 bytes) +CREATE src/app/book/book.module.ts (335 bytes) +CREATE src/app/book/book.component.html (19 bytes) +CREATE src/app/book/book.component.spec.ts (614 bytes) +CREATE src/app/book/book.component.ts (268 bytes) +CREATE src/app/book/book.component.scss (0 bytes) +UPDATE src/app/app-routing.module.ts (1289 bytes) +Done in 3.88s. +```` -#### Datepicker 要求 +### BookModule -打开 `app\book` 文件夹下的 `book.module.ts` 文件,使用以下内容替换它: +打开 `/src/app/book/book.module.ts` 并使用以下内容替换: -```js +````js import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; - -import { BooksRoutingModule } from './book-routing.module'; -import { BooksComponent } from './book.component'; -import { BookListComponent } from './book-list/book-list.component'; import { SharedModule } from '../shared/shared.module'; -import { NgbDatepickerModule } from '@ng-bootstrap/ng-bootstrap'; //<== added this line ==> +import { BookRoutingModule } from './book-routing.module'; +import { BookComponent } from './book.component'; @NgModule({ - declarations: [BooksComponent, BookListComponent], + declarations: [BookComponent], imports: [ - CommonModule, - BooksRoutingModule, - SharedModule, - NgbDatepickerModule //<== added this line ==> + BookRoutingModule, + SharedModule ] }) -export class BooksModule { } -``` - -* 我们导入了 `NgbDatepickerModule` 来使用日期选择器. - -打开 `app\book\book-list` 文件夹下的 `book-list.component.ts` 文件,使用以下内容替换它: - -```js -import { ListService, PagedResultDto } from '@abp/ng.core'; -import { Component, OnInit } from '@angular/core'; -import { BookDto, BookType } from '../models'; -import { BookService } from '../services'; -import { FormGroup, FormBuilder, Validators } from '@angular/forms'; -import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap'; // <== added this line ==> - -@Component({ - selector: 'app-book-list', - templateUrl: './book-list.component.html', - styleUrls: ['./book-list.component.scss'], - providers: [ListService, { provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }], // <== added a provide ==> -}) -export class BookListComponent implements OnInit { - book = { items: [], totalCount: 0 } as PagedResultDto; +export class BookModule { } - booksType = BookType; - - // <== added bookTypes array ==> - bookTypes = Object.keys(BookType).filter( - (bookType) => typeof this.booksType[bookType] === 'number' - ); +```` - isModalOpen = false; +* 添加了 `SharedModule`. `SharedModule` 导出了一些创建用户界面所需的通用模块. +* `SharedModule` 已经导出了 `CommonModule`,所以我们删除了 `CommonModule`. - form: FormGroup; +### 路由 - constructor( - public readonly list: ListService, - private bookService: BookService, - private fb: FormBuilder - ) {} +生成的代码将新的路由定义放在 `src/app/app-routing.module.ts` 文件中,如下所示: - ngOnInit() { - const bookStreamCreator = (query) => this.bookService.getListByInput(query); - - this.list.hookToQuery(bookStreamCreator).subscribe((response) => { - this.book = response; - }); - } +````js +const routes: Routes = [ + // other route definitions... + { path: 'books', loadChildren: () => import('./book/book.module').then(m => m.BookModule) }, +]; +```` - createBook() { - this.buildForm(); - this.isModalOpen = true; - } +现在打开 `src/app/route.provider.ts` 以下替换 `configureRoutes` 函数: - buildForm() { - this.form = this.fb.group({ - name: ['', Validators.required], - type: [null, Validators.required], - publishDate: [null, Validators.required], - price: [null, Validators.required], - }); - } +```js +function configureRoutes(routes: RoutesService) { + return () => { + routes.add([ + { + path: '/', + name: '::Menu:Home', + iconClass: 'fas fa-home', + order: 1, + layout: eLayoutType.application, + }, + { + path: '/book-store', + name: '::Menu:BookStore', + iconClass: 'fas fa-book', + order: 2, + layout: eLayoutType.application, + }, + { + path: '/books', + name: '::Menu:Books', + parentName: '::Menu:BookStore', + layout: eLayoutType.application, + }, + ]); + }; } ``` -* 我们导入了 ` NgbDateNativeAdapter, NgbDateAdapter` - -* 我们添加了一个新的 `NgbDateAdapter` 提供程序,它将Datepicker值转换为Date类型. 有关更多详细信息,请参见[datepicker adapters](https://ng-bootstrap.github.io/#/components/datepicker/overview). - -* 我们添加了 `bookTypes` 数组,以便能够在combobox值中使用它. `bookTypes` 包含 `BookType` 枚举的字段. 得到的数组如下所示: - - ```js - ['Adventure', 'Biography', 'Dystopia', 'Fantastic' ...] - ``` - - 在先前的表单模板中 用 `ngFor` 使用这个数组. - -现在你可以打开浏览器看到以下变化: - -![New book modal](./images/bookstore-new-book-form.png) - -#### 保存图书 - -打开 `app\book\book-list` 文件夹下的 `book-list.component.ts` 文件,使用以下内容替换它: - -```js -import { ListService, PagedResultDto } from '@abp/ng.core'; -import { Component, OnInit } from '@angular/core'; -import { BookDto, BookType } from '../models'; -import { BookService } from '../services'; -import { FormGroup, FormBuilder, Validators } from '@angular/forms'; -import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap'; - -@Component({ - selector: 'app-book-list', - templateUrl: './book-list.component.html', - styleUrls: ['./book-list.component.scss'], - providers: [ListService, { provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }], -}) -export class BookListComponent implements OnInit { - book = { items: [], totalCount: 0 } as PagedResultDto; - - booksType = BookType; - - bookTypes = Object.keys(BookType).filter( - (bookType) => typeof this.booksType[bookType] === 'number' - ); - - isModalOpen = false; - - form: FormGroup; - - constructor( - public readonly list: ListService, - private bookService: BookService, - private fb: FormBuilder - ) {} - - ngOnInit() { - const bookStreamCreator = (query) => this.bookService.getListByInput(query); - - this.list.hookToQuery(bookStreamCreator).subscribe((response) => { - this.book = response; - }); - } - - createBook() { - this.buildForm(); - this.isModalOpen = true; - } +`RoutesService` 是ABP框架提供的用于配置主菜单和路由的服务. - buildForm() { - this.form = this.fb.group({ - name: ['', Validators.required], - type: [null, Validators.required], - publishDate: [null, Validators.required], - price: [null, Validators.required], - }); - } - - // <== added save ==> - save() { - if (this.form.invalid) { - return; - } - - this.bookService.createByInput(this.form.value).subscribe(() => { - this.isModalOpen = false; - this.form.reset(); - this.list.get(); - }); - } -} -``` +* `path` 路由的URL. +* `name` 菜单项的名称(参阅[本地化文档](../UI/Angular/Localization.md)了解更多). +* `iconClass` 菜单项的图标(你可以使用默认的[Font Awesome](https://fontawesome.com/)图标). +* `order` 菜单项的排序.我们定义了101,它显示在 "Administration" 项的后面. +* `layout` BooksModule路由的布局. 可以定义 `eLayoutType.application`, `eLayoutType.account` 或 `eLayoutType.empty`. -* 我们添加了 `save` 方法. +更多信息请参阅[RoutesService 文档](https://docs.abp.io/en/abp/latest/UI/Angular/Modifying-the-Menu.md#via-routesservice). -打开 `app\book\book-list` 文件夹下的 `book-list.component.html` 文件, 找到 `` 元素,使用下面元素替换它: +### 生成代理 -```html - - - - - - -``` +ABP CLI提供了 `generate-proxy` 命令为你的服务HTTP API生成客户端代理简化客户端使用服务的成本. 运行 `generate-proxy` 命令前你的host必须正在运行. 参阅 [CLI 文档](../CLI.md). -使用以下内容替换 `
    ` 标签: +在 `angular` 文件夹下运行以下命令: -```html - +```bash +abp generate-proxy ``` -* 我们添加了 `(ngSubmit)="save()"` 到 `` 元素,当按下enter时保存图书. -* 我们在模态框的底部添加了 `abp-button` 来保存图书. +生成的文件如下: -模态框最终看起来像这样: +![Generated files](./images/generated-proxies-2.png) -![Save button to the modal](./images/bookstore-new-book-form-v2.png) +### BookComponent -### 更新图书 - -打开 `app\book\book-list` 文件夹下的 `book-list.component.ts` 文件并且添加名为 `selectedBook` 的变量. +打开 `/src/app/book/book.component.ts` 用以下内容替换它: ```js import { ListService, PagedResultDto } from '@abp/ng.core'; import { Component, OnInit } from '@angular/core'; -import { BookDto, BookType } from '../models'; -import { BookService } from '../services'; -import { FormGroup, FormBuilder, Validators } from '@angular/forms'; -import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap'; +import { BookDto } from './models'; +import { BookService } from './services'; @Component({ - selector: 'app-book-list', - templateUrl: './book-list.component.html', - styleUrls: ['./book-list.component.scss'], - providers: [ListService, { provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }], + selector: 'app-book', + templateUrl: './book.component.html', + styleUrls: ['./book.component.scss'], + providers: [ListService], }) -export class BookListComponent implements OnInit { +export class BookComponent implements OnInit { book = { items: [], totalCount: 0 } as PagedResultDto; - booksType = BookType; - - bookTypes = Object.keys(BookType).filter( - (bookType) => typeof this.booksType[bookType] === 'number' - ); - - isModalOpen = false; - - form: FormGroup; - - selectedBook = {} as BookDto; // <== declared selectedBook ==> - - constructor( - public readonly list: ListService, - private bookService: BookService, - private fb: FormBuilder - ) {} + constructor(public readonly list: ListService, private bookService: BookService) {} ngOnInit() { const bookStreamCreator = (query) => this.bookService.getListByInput(query); @@ -927,196 +500,55 @@ export class BookListComponent implements OnInit { this.book = response; }); } - - // <== this method is replaced ==> - createBook() { - this.selectedBook = {} as BookDto; // <== added ==> - this.buildForm(); - this.isModalOpen = true; - } - - // <== added editBook method ==> - editBook(id: string) { - this.bookService.getById(id).subscribe((book) => { - this.selectedBook = book; - this.buildForm(); - this.isModalOpen = true; - }); - } - - // <== this method is replaced ==> - buildForm() { - this.form = this.fb.group({ - name: [this.selectedBook.name || '', Validators.required], - type: [this.selectedBook.type || null, Validators.required], - publishDate: [ - this.selectedBook.publishDate ? new Date(this.selectedBook.publishDate) : null, - Validators.required, - ], - price: [this.selectedBook.price || null, Validators.required], - }); - } - - // <== this method is replaced ==> - save() { - if (this.form.invalid) { - return; - } - - // <== added request ==> - const request = this.selectedBook.id - ? this.bookService.updateByIdAndInput(this.form.value, this.selectedBook.id) - : this.bookService.createByInput(this.form.value); - - request.subscribe(() => { - this.isModalOpen = false; - this.form.reset(); - this.list.get(); - }); - } -} -``` - -* 我们声明了类型为 `BookDto` 的 `selectedBook` 变量. -* 我们添加了 `editBook` 方法, 根据给定图书 `Id` 设置 `selectedBook` 对象. -* 我们替换了 `buildForm` 方法使用 `selectedBook` 数据创建表单. -* 我们替换了 `createBook` 方法,设置 `selectedBook` 为空对象. -* 我们替换了 `save` 方法. - -#### 添加 "Actions" 下拉框到表格 - -打开 `app\book\book-list` 文件夹下的 `book-list.component.html` 文件,使用以下内容替换 `
    ` 标签: - -```html -
    - - - - -
    - -
    - -
    -
    -
    -
    - - - - {%{{{ booksType[row.type] }}}%} - - - - - {%{{{ row.publishDate | date }}}%} - - - - - {%{{{ row.price | currency }}}%} - - -
    -
    -``` - -- 我们添加了 "Actions" 栏的 `th`. -- 我们为 "Actions" 栏添加了 `ngx-datatable-column`. -- 我们添加了带有 `ngbDropdownToggle` 的 `button`,在点击按钮时打开操作. -- 我们习惯于将[NgbDropdown](https://ng-bootstrap.github.io/#/components/dropdown/examples)用于操作的下拉菜单. - -UI最终看起来像这样: - -![Action buttons](./images/bookstore-actions-buttons.png) - -打开 `app\book\book-list` 文件夹下的 `book-list.component.html` 文件,使用以下内容替换 `` 标签: - -```html - -

    {%{{{ (selectedBook.id ? 'AbpIdentity::Edit' : '::NewBook' ) | abpLocalization }}}%}

    -
    -``` - -* **Edit** 文本做为编辑记录操作的标题, **New Book** 做为添加记录操作的标题. - -### 删除图书 - -#### 删除确认弹层 - -打开 `app\book\book-list` 文件夹下的 `book-list.component.ts` 文件,注入 `ConfirmationService`. - -替换构造函数: - -```js -import { ConfirmationService } from '@abp/ng.theme.shared'; -//... - -constructor( - public readonly list: ListService, - private bookService: BookService, - private fb: FormBuilder, - private confirmation: ConfirmationService // <== added this line ==> -) {} -``` - -* We imported `ConfirmationService`. -* We injected `ConfirmationService` to the constructor. - -See the [Confirmation Popup documentation](https://docs.abp.io/en/abp/latest/UI/Angular/Confirmation-Service) - -In the `book-list.component.ts` add a delete method: - -```js -import { ConfirmationService, Confirmation } from '@abp/ng.theme.shared'; //<== imported Confirmation namespace ==> - -//... - -delete(id: string) { - this.confirmation.warn('::AreYouSureToDelete', 'AbpAccount::AreYouSure').subscribe((status) => { - if (status === Confirmation.Status.confirm) { - this.bookService.deleteById(id).subscribe(() => this.list.get()); - } - }); } ``` -`delete` 方法会显示一个确认弹层并订阅用户响应. 只在用户点击 `Yes` 按钮时调用 `BookService` 的 `deleteById` 方法. 确认弹层看起来如下: - -![bookstore-confirmation-popup](./images/bookstore-confirmation-popup.png) +* 我们注入了生成的 `BookService`. +* 我们实现了 [ListService](https://docs.abp.io/en/abp/latest/UI/Angular/List-Service),它是一个公用服务,提供了简单的分页,排序和搜索. -#### 添加删除按钮 - -打开 `app\book\book-list` 文件夹下的 `app\book\book-list` 文件,修改 `ngbDropdownMenu` 添加删除按钮: +打开 `/src/app/book/book.component.html` 用以下内容替换它: ```html -
    - - +
    +
    +
    +
    +
    + {%{{{ '::Menu:Books' | abpLocalization }}}%} +
    +
    +
    +
    +
    +
    + + + + + {%{{{ '::Enum:BookType:' + row.type | abpLocalization }}}%} + + + + + {%{{{ row.publishDate | date }}}%} + + + + + {%{{{ row.price | currency }}}%} + + + +
    ``` -最终操作下拉框UI看起来如下: +现在你可以在浏览器看到最终结果: -![bookstore-final-actions-dropdown](./images/bookstore-final-actions-dropdown.png) +![Book list final result](./images/bookstore-book-list.png) {{end}} -### 下一章 +## 下一章 -查看本教程的 [下一章](Part-3.md) . +查看本教程的[下一章](Part-3.md). diff --git a/docs/zh-Hans/Tutorials/Part-3.md b/docs/zh-Hans/Tutorials/Part-3.md index cded954e6b..7ad96a0465 100644 --- a/docs/zh-Hans/Tutorials/Part-3.md +++ b/docs/zh-Hans/Tutorials/Part-3.md @@ -1,198 +1,1187 @@ -## ASP.NET Core {{UI_Value}} 教程 - 第三章 +# Web应用程序开发教程 - 第三章: 创建,更新和删除图书 ````json //[doc-params] { - "UI": ["MVC","NG"] + "UI": ["MVC","NG"], + "DB": ["EF","Mongo"] } ```` - {{ if UI == "MVC" - DB="ef" - DB_Text="Entity Framework Core" UI_Text="mvc" else if UI == "NG" - DB="mongodb" - DB_Text="MongoDB" UI_Text="angular" -else - DB ="?" +else UI_Text="?" end +if DB == "EF" + DB_Text="Entity Framework Core" +else if DB == "Mongo" + DB_Text="MongoDB" +else + DB_Text="?" +end }} -### 关于本教程 +## 关于本教程 -这是ASP.NET Core{{UI_Value}}系列教程的第二章. 共有三章: +在本系列教程中, 你将构建一个名为 `Acme.BookStore` 的用于管理书籍及其作者列表的基于ABP的应用程序. 它是使用以下技术开发的: -- [Part-1: 创建项目和书籍列表页面](Part-1.md) -- [Part 2: 创建,编辑,删除书籍](Part-2.md) -- **Part-3: 集成测试(本章)** +* **{{DB_Text}}** 做为ORM提供程序. +* **{{UI_Value}}** 做为UI框架. -> 你也可以观看由ABP社区成员为本教程录制的[视频课程](https://amazingsolutions.teachable.com/p/lets-build-the-bookstore-application). +本教程分为以下部分: -### 解决方案中的测试项目 +- [Part 1: 创建服务端](Part-1.md) +- [Part 2: 图书列表页面](Part-2.md) +- **Part 3: 创建,更新和删除图书**(本章) +- [Part 4: 集成测试](Part-4.md) +- [Part 5: 授权](Part-5.md) +- [Part 6: 作者: 领域层](Part-6.md) +- [Part 7: 作者: 数据库集成](Part-7.md) +- [Part 8: 作者: 应用服务层](Part-8.md) +- [Part 9: 作者: 用户页面](Part-9.md) +- [Part 10: 图书到作者的关系](Part-10.md) -解决方案中有多个测试项目: +## 下载源码 -![bookstore-test-projects-v2](./images/bookstore-test-projects-{{UI_Text}}.png) +本教程根据你的**UI** 和 **Database**偏好有多个版,我们准备了两种可供下载的源码组合: -每个项目用于测试相关的应用程序项目.测试项目使用以下库进行测试: +* [MVC (Razor Pages) UI 与 EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore) +* [Angular UI 与 MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb) -* [xunit](https://xunit.github.io/) 作为主测试框架. -* [Shoudly](http://shouldly.readthedocs.io/en/latest/) 作为断言库. -* [NSubstitute](http://nsubstitute.github.io/) 作为模拟库. +{{if UI == "MVC"}} -### 添加测试数据 +## 创建新书籍 -启动模板包含`Acme.BookStore.TestBase`项目中的`BookStoreTestDataSeedContributor`类,它创建一些数据来运行测试. -更改`BookStoreTestDataSeedContributor`类如下所示: +通过本节, 你将会了解如何创建一个 modal form 来实现新增书籍的功能. 最终成果如下图所示: -````csharp -using System; +![bookstore-create-dialog](./images/bookstore-create-dialog-2.png) + +### 创建 modal form + +在 `Acme.BookStore.Web` 项目的 `Pages/Books` 目录下新建一个 `CreateModal.cshtml` Razor页面: + +![bookstore-add-create-dialog](./images/bookstore-add-create-dialog-v2.png) + +#### CreateModal.cshtml.cs + +打开 `CreateModal.cshtml.cs` 代码文件,用如下代码替换 `CreateModalModel` 类的实现: + +````C# using System.Threading.Tasks; -using Volo.Abp.Data; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Domain.Repositories; -using Volo.Abp.Guids; +using Acme.BookStore.Books; +using Microsoft.AspNetCore.Mvc; -namespace Acme.BookStore +namespace Acme.BookStore.Web.Pages.Books { - public class BookStoreTestDataSeedContributor - : IDataSeedContributor, ITransientDependency + public class CreateModalModel : BookStorePageModel { - private readonly IRepository _bookRepository; - private readonly IGuidGenerator _guidGenerator; + [BindProperty] + public CreateUpdateBookDto Book { get; set; } + + private readonly IBookAppService _bookAppService; + + public CreateModalModel(IBookAppService bookAppService) + { + _bookAppService = bookAppService; + } - public BookStoreTestDataSeedContributor( - IRepository bookRepository, - IGuidGenerator guidGenerator) + public void OnGet() { - _bookRepository = bookRepository; - _guidGenerator = guidGenerator; + Book = new CreateUpdateBookDto(); } - public async Task SeedAsync(DataSeedContext context) + public async Task OnPostAsync() { - await _bookRepository.InsertAsync( - new Book(id: _guidGenerator.Create(), - name: "Test book 1", - type: BookType.Fantastic, - publishDate: new DateTime(2015, 05, 24), - price: 21 - ) - ); - - await _bookRepository.InsertAsync( - new Book(id: _guidGenerator.Create(), - name: "Test book 2", - type: BookType.Science, - publishDate: new DateTime(2014, 02, 11), - price: 15 - ) - ); + await _bookAppService.CreateAsync(Book); + return NoContent(); } } } ```` -* 注入`IRepository`并在`SeedAsync`中使用它来创建两个书实体作为测试数据. -* 使用`IGuidGenerator`服务创建GUID. 虽然`Guid.NewGuid()`非常适合测试,但`IGuidGenerator`在使用真实数据库时还有其他特别重要的功能(参见[Guid生成文档](../Guid-Generation.md)了解更多信息). +* 该类派生于 `BookStorePageModel` 而非默认的 `PageModel`. `BookStorePageModel` 继承了 `PageModel` 并且添加了一些可以被你的page model类使用的通用属性和方法. +* `Book` 属性上的 `[BindProperty]` 特性将post请求提交上来的数据绑定到该属性上. +* 该类通过构造函数注入了 `IBookAppService` 应用服务,并且在 `OnPostAsync` 处理程序中调用了服务的 `CreateAsync` 方法. +* 它在 `OnGet` 方法中创建一个新的 `CreateUpdateBookDto` 对象。 ASP.NET Core不需要像这样创建一个新实例就可以正常工作. 但是它不会为你创建实例,并且如果你的类在类构造函数中具有一些默认值分配或代码执行,它们将无法工作. 对于这种情况,我们为某些 `CreateUpdateBookDto` 属性设置了默认值. + +#### CreateModal.cshtml + +打开 `CreateModal.cshtml` 文件并粘贴如下代码: + +````html +@page +@using Acme.BookStore.Localization +@using Acme.BookStore.Web.Pages.Books +@using Microsoft.Extensions.Localization +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal +@model CreateModalModel +@inject IStringLocalizer L +@{ + Layout = null; +} + + + + + + + + + +```` + +* 这个 modal 使用 `abp-dynamic-form` [tag Helper](../UI/AspNetCore/Tag-Helpers/Dynamic-Forms.md) 根据 `CreateBookViewModel` 类自动构建了表单. +* `abp-model` 指定了 `Book` 属性为模型对象. +* `data-ajaxForm` 设置了表单通过AJAX提交,而不是经典的页面回发. +* `abp-form-content` tag helper 作为表单控件渲染位置的占位符 (这是可选的,只有你在 `abp-dynamic-form` 中像本示例这样添加了其他内容才需要). + +> 提示: 就像在本示例中一样,`Layout` 应该为 `null`,因为当通过AJAX加载模态时,我们不希望包括所有布局. + +### 添加 "New book" 按钮 -### 测试 BookAppService +打开 `Pages/Books/Index.cshtml` 并按如下代码修改 `abp-card-header` : -在 `Acme.BookStore.Application.Tests` 项目中创建一个名叫 `BookAppService_Tests` 的测试类: +````html + + + + @L["Books"] + + + + + + +```` + +`Index.cshtml` 的内容最终如下所示: + +````html +@page +@using Acme.BookStore.Localization +@using Acme.BookStore.Web.Pages.Books +@using Microsoft.Extensions.Localization +@model IndexModel +@inject IStringLocalizer L +@section scripts +{ + +} + + + + + + @L["Books"] + + + + + + + + + + +```` + +如下图所示,只是在表格 **右上方** 添加了 **New book** 按钮: + +![bookstore-new-book-button](./images/bookstore-new-book-button-2.png) + +打开 `Pages/book/index.js` 在 `datatable` 配置代码后面添加如下代码: + +````js +var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal'); + +createModal.onResult(function () { + dataTable.ajax.reload(); +}); + +$('#NewBookButton').click(function (e) { + e.preventDefault(); + createModal.open(); +}); +```` + +* `abp.ModalManager` 是一个在客户端打开和管理modal的辅助类.它基于Twitter Bootstrap的标准modal组件通过简化的API抽象隐藏了许多细节. +* `createModal.onResult(...)` 用于在创建书籍后刷新数据表格. +* `createModal.open();` 用于打开模态创建新书籍. + +`Index.js` 的内容最终如下所示: + +````js +$(function () { + var l = abp.localization.getResource('BookStore'); + + var dataTable = $('#BooksTable').DataTable( + abp.libs.datatables.normalizeConfiguration({ + serverSide: true, + paging: true, + order: [[1, "asc"]], + searching: false, + scrollX: true, + ajax: abp.libs.datatables.createAjax(acme.bookStore.books.book.getList), + columnDefs: [ + { + title: l('Name'), + data: "name" + }, + { + title: l('Type'), + data: "type", + render: function (data) { + return l('Enum:BookType:' + data); + } + }, + { + title: l('PublishDate'), + data: "publishDate", + render: function (data) { + return luxon + .DateTime + .fromISO(data, { + locale: abp.localization.currentCulture.name + }).toLocaleString(); + } + }, + { + title: l('Price'), + data: "price" + }, + { + title: l('CreationTime'), data: "creationTime", + render: function (data) { + return luxon + .DateTime + .fromISO(data, { + locale: abp.localization.currentCulture.name + }).toLocaleString(luxon.DateTime.DATETIME_SHORT); + } + } + ] + }) + ); + + var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal'); + + createModal.onResult(function () { + dataTable.ajax.reload(); + }); + + $('#NewBookButton').click(function (e) { + e.preventDefault(); + createModal.open(); + }); +}); +```` + +现在,你可以 **运行程序** 通过新的 modal form 来创建书籍了. + +## 更新书籍 + +在 `Acme.BookStore.Web` 项目的 `Pages/Books` 目录下新建一个名叫 `EditModal.cshtml` 的Razor页面: + +![bookstore-add-edit-dialog](./images/bookstore-add-edit-dialog.png) + +### EditModal.cshtml.cs + +打开 `EditModal.cshtml.cs` 文件(`EditModalModel`类) 并替换成以下代码: ````csharp using System; -using System.Linq; using System.Threading.Tasks; -using Xunit; -using Shouldly; -using Volo.Abp.Application.Dtos; -using Volo.Abp.Validation; -using Microsoft.EntityFrameworkCore.Internal; +using Acme.BookStore.Books; +using Microsoft.AspNetCore.Mvc; -namespace Acme.BookStore +namespace Acme.BookStore.Web.Pages.Books { - public class BookAppService_Tests : BookStoreApplicationTestBase + public class EditModalModel : BookStorePageModel { + [HiddenInput] + [BindProperty(SupportsGet = true)] + public Guid Id { get; set; } + + [BindProperty] + public CreateUpdateBookDto Book { get; set; } + private readonly IBookAppService _bookAppService; - public BookAppService_Tests() + public EditModalModel(IBookAppService bookAppService) + { + _bookAppService = bookAppService; + } + + public async Task OnGetAsync() { - _bookAppService = GetRequiredService(); + var bookDto = await _bookAppService.GetAsync(Id); + Book = ObjectMapper.Map(bookDto); } - [Fact] - public async Task Should_Get_List_Of_Books() + public async Task OnPostAsync() { - //Act - var result = await _bookAppService.GetListAsync( - new PagedAndSortedResultRequestDto() - ); - - //Assert - result.TotalCount.ShouldBeGreaterThan(0); - result.Items.ShouldContain(b => b.Name == "Test book 1"); + await _bookAppService.UpdateAsync(Id, Book); + return NoContent(); } } } ```` -* 测试方法 `Should_Get_List_Of_Books` 直接使用 `BookAppService.GetListAsync` 方法来获取用户列表,并执行检查. +* `[HiddenInput]` 和 `[BindProperty]` 是标准的 ASP.NET Core MVC 特性.这里启用 `SupportsGet` 从Http请求的查询字符串中获取Id的值. +* 在 `OnGetAsync` 方法中,将 `BookAppService.GetAsync` 方法返回的 `BookDto` 映射成 `CreateUpdateBookDto` 并赋值给Book属性. +* `OnPostAsync` 方法直接使用 `BookAppService.UpdateAsync` 来更新实体. -新增测试方法,用以测试创建一个合法book实体的场景: +### BookDto 到 CreateUpdateBookDto 对象映射 -````C# -[Fact] -public async Task Should_Create_A_Valid_Book() +为了执行 `BookDto` 到 `CreateUpdateBookDto` 对象映射,请打开 `Acme.BookStore.Web` 项目中的 `BookStoreWebAutoMapperProfile.cs` 并更改它,如下所示: + +````csharp +using AutoMapper; + +namespace Acme.BookStore.Web { - //Act - var result = await _bookAppService.CreateAsync( - new CreateUpdateBookDto + public class BookStoreWebAutoMapperProfile : Profile + { + public BookStoreWebAutoMapperProfile() { - Name = "New test book 42", - Price = 10, - PublishDate = DateTime.Now, - Type = BookType.ScienceFiction + CreateMap(); } - ); + } +} +```` + +* 我们添加了 `CreateMap();` 作为映射定义. + +> 请注意,我们在Web层中进行映射定义是一种最佳实践,因为仅在该层中需要它. - //Assert - result.Id.ShouldNotBe(Guid.Empty); - result.Name.ShouldBe("New test book 42"); +### EditModal.cshtml + +将 `EditModal.cshtml` 页面内容替换成如下代码: + +````html +@page +@using Acme.BookStore.Localization +@using Acme.BookStore.Web.Pages.Books +@using Microsoft.Extensions.Localization +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal +@model EditModalModel +@inject IStringLocalizer L +@{ + Layout = null; } + + + + + + + + + + ```` -新增测试方法,用以测试创建一个非法book实体失败的场景: +这个页面内容和 `CreateModal.cshtml` 非常相似,除了以下几点: -````csharp -[Fact] -public async Task Should_Not_Create_A_Book_Without_Name() +* 它包含`id`属性的`abp-input`, 用于存储编辑书的 `id` (它是隐藏的Input) +* 此页面指定的post地址是`Books/EditModal`. + +### 为表格添加 "操作(Actions)" 下拉菜单 + +我们将为表格每行添加下拉按钮 ("Actions"): + +打开 `Pages/Books/Index.cshtml` 页面,并按下方所示修改表格部分的代码: + +````js +$(function () { + var l = abp.localization.getResource('BookStore'); + var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal'); + var editModal = new abp.ModalManager(abp.appPath + 'Books/EditModal'); + + var dataTable = $('#BooksTable').DataTable( + abp.libs.datatables.normalizeConfiguration({ + serverSide: true, + paging: true, + order: [[1, "asc"]], + searching: false, + scrollX: true, + ajax: abp.libs.datatables.createAjax(acme.bookStore.books.book.getList), + columnDefs: [ + { + title: l('Actions'), + rowAction: { + items: + [ + { + text: l('Edit'), + action: function (data) { + editModal.open({ id: data.record.id }); + } + } + ] + } + }, + { + title: l('Name'), + data: "name" + }, + { + title: l('Type'), + data: "type", + render: function (data) { + return l('Enum:BookType:' + data); + } + }, + { + title: l('PublishDate'), + data: "publishDate", + render: function (data) { + return luxon + .DateTime + .fromISO(data, { + locale: abp.localization.currentCulture.name + }).toLocaleString(); + } + }, + { + title: l('Price'), + data: "price" + }, + { + title: l('CreationTime'), data: "creationTime", + render: function (data) { + return luxon + .DateTime + .fromISO(data, { + locale: abp.localization.currentCulture.name + }).toLocaleString(luxon.DateTime.DATETIME_SHORT); + } + } + ] + }) + ); + + createModal.onResult(function () { + dataTable.ajax.reload(); + }); + + editModal.onResult(function () { + dataTable.ajax.reload(); + }); + + $('#NewBookButton').click(function (e) { + e.preventDefault(); + createModal.open(); + }); +}); +```` + +* 增加了一个新的 `ModalManager` 名为 `editModal` 打开编辑模态框. +* 在 `columnDefs` 部分的开头添加了一个新列,用于"*Actions*"下拉按钮. +* "*Edit*" 动作简单地调用 `editModal.open()` 打开编辑模态框. +* `editModal.onResult(...)` 当你关闭编程模态框时进行回调刷新数据表格. + +你可以运行应用程序,并通过选择一本书的编辑操作编辑任何一本书. + +最终的UI看起来如下: + +![bookstore-books-table-actions](./images/bookstore-edit-button-2.png) + +## 删除书籍 + +打开 `Pages/book/index.js` 文件,在 `rowAction` `items` 下新增一项: + +````js { - var exception = await Assert.ThrowsAsync(async () => - { - await _bookAppService.CreateAsync( - new CreateUpdateBookDto - { - Name = "", - Price = 10, - PublishDate = DateTime.Now, - Type = BookType.ScienceFiction - } - ); - }); - - exception.ValidationErrors - .ShouldContain(err => err.MemberNames.Any(mem => mem == "Name")); + text: l('Delete'), + confirmMessage: function (data) { + return l('BookDeletionConfirmationMessage', data.record.name); + }, + action: function (data) { + acme.bookStore.books.book + .delete(data.record.id) + .then(function() { + abp.notify.info(l('SuccessfullyDeleted')); + dataTable.ajax.reload(); + }); + } } ```` -* 由于 `Name` 是空值, ABP 抛出一个 `AbpValidationException` 异常. +* `confirmMessage` 用来在实际执行 `action` 之前向用户进行确认. +* 通过javascript代理方法 `acme.bookStore.books.book.delete(...)` 执行一个AJAX请求来删除一个book实体. +* `abp.notify.info` 用来在执行删除操作后显示一个toastr通知信息. + +由于我们使用了两个新的本地化文本(`BookDeletionConfirmationMessage`和`SuccesslyDeleted`),因此你需要将它们添加到本地化文件(`Acme.BookStore.Domain.Shared`项目的`Localization/BookStore`文件夹下的`en.json`): + +````json +"BookDeletionConfirmationMessage": "Are you sure to delete the book '{0}'?", +"SuccessfullyDeleted": "Successfully deleted!" +```` + +`Index.js` 的内容最终如下所示: + +````js +$(function () { + var l = abp.localization.getResource('BookStore'); + var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal'); + var editModal = new abp.ModalManager(abp.appPath + 'Books/EditModal'); + + var dataTable = $('#BooksTable').DataTable( + abp.libs.datatables.normalizeConfiguration({ + serverSide: true, + paging: true, + order: [[1, "asc"]], + searching: false, + scrollX: true, + ajax: abp.libs.datatables.createAjax(acme.bookStore.books.book.getList), + columnDefs: [ + { + title: l('Actions'), + rowAction: { + items: + [ + { + text: l('Edit'), + action: function (data) { + editModal.open({ id: data.record.id }); + } + }, + { + text: l('Delete'), + confirmMessage: function (data) { + return l( + 'BookDeletionConfirmationMessage', + data.record.name + ); + }, + action: function (data) { + acme.bookStore.books.book + .delete(data.record.id) + .then(function() { + abp.notify.info( + l('SuccessfullyDeleted') + ); + dataTable.ajax.reload(); + }); + } + } + ] + } + }, + { + title: l('Name'), + data: "name" + }, + { + title: l('Type'), + data: "type", + render: function (data) { + return l('Enum:BookType:' + data); + } + }, + { + title: l('PublishDate'), + data: "publishDate", + render: function (data) { + return luxon + .DateTime + .fromISO(data, { + locale: abp.localization.currentCulture.name + }).toLocaleString(); + } + }, + { + title: l('Price'), + data: "price" + }, + { + title: l('CreationTime'), data: "creationTime", + render: function (data) { + return luxon + .DateTime + .fromISO(data, { + locale: abp.localization.currentCulture.name + }).toLocaleString(luxon.DateTime.DATETIME_SHORT); + } + } + ] + }) + ); + + createModal.onResult(function () { + dataTable.ajax.reload(); + }); + + editModal.onResult(function () { + dataTable.ajax.reload(); + }); + + $('#NewBookButton').click(function (e) { + e.preventDefault(); + createModal.open(); + }); +}); +```` + +你可以运行程序并尝试删除一本书. + +{{end}} + +{{if UI == "NG"}} + +## 创建新书籍 + +下面的章节中,你将学习到如何创建一个新的模态对话框来新增书籍. + +### BookComponent + +打开 `/src/app/book/book.component.ts` 使用以下内容替换: + +```js +import { ListService, PagedResultDto } from '@abp/ng.core'; +import { Component, OnInit } from '@angular/core'; +import { BookDto } from './models'; +import { BookService } from './services'; + +@Component({ + selector: 'app-book', + templateUrl: './book.component.html', + styleUrls: ['./book.component.scss'], + providers: [ListService], +}) +export class BookComponent implements OnInit { + book = { items: [], totalCount: 0 } as PagedResultDto; + + isModalOpen = false; // add this line + + constructor(public readonly list: ListService, private bookService: BookService) {} + + ngOnInit() { + const bookStreamCreator = (query) => this.bookService.getListByInput(query); + + this.list.hookToQuery(bookStreamCreator).subscribe((response) => { + this.book = response; + }); + } + + // add new method + createBook() { + this.isModalOpen = true; + } +} +``` + +* 我们定义了一个名为 `isModalOpen` 的变量和 `createBook` 方法. + +打开 `/src/app/book/book.component.html` 做以下更改: + +```html +
    +
    +
    +
    +
    {%{{{ '::Menu:Books' | abpLocalization }}}%}
    +
    +
    + + +
    + +
    + +
    +
    +
    +
    + +
    +
    + + + + +

    {%{{{ '::NewBook' | abpLocalization }}}%}

    +
    + + + + + + +
    +``` + +* 添加了 `New book` 按钮到卡片头部. +* 添加了 `abp-modal` 渲染模态框,允许用户创建新书. `abp-modal` 是显示模态框的预构建组件. 你也可以使用其它方法显示模态框,但 `abp-modal` 提供了一些附加的好处. + +你可以打开浏览器,点击**New book**按钮看到模态框. + +![Empty modal for new book](./images/bookstore-empty-new-book-modal.png) + +### 添加响应式表单 + +[响应式表单](https://angular.io/guide/reactive-forms) 提供一种模型驱动的方法来处理其值随时间变化的表单输入. + +打开 `/src/app/book/book.component.ts` 使用以下内容替换: + +```js +import { ListService, PagedResultDto } from '@abp/ng.core'; +import { Component, OnInit } from '@angular/core'; +import { BookDto, BookType } from './models'; // add BookType +import { BookService } from './services'; +import { FormGroup, FormBuilder, Validators } from '@angular/forms'; // add this + +@Component({ + selector: 'app-book', + templateUrl: './book.component.html', + styleUrls: ['./book.component.scss'], + providers: [ListService], +}) +export class BookComponent implements OnInit { + book = { items: [], totalCount: 0 } as PagedResultDto; + + form: FormGroup; // add this line + + bookType = BookType; // add this line + + // add bookTypes as a list of BookType enum members + bookTypes = Object.keys(this.bookType).filter( + (key) => typeof this.bookType[key] === 'number' + ); + + isModalOpen = false; + + constructor( + public readonly list: ListService, + private bookService: BookService, + private fb: FormBuilder // inject FormBuilder + ) {} + + ngOnInit() { + const bookStreamCreator = (query) => this.bookService.getListByInput(query); + + this.list.hookToQuery(bookStreamCreator).subscribe((response) => { + this.book = response; + }); + } + + createBook() { + this.buildForm(); // add this line + this.isModalOpen = true; + } + + // add buildForm method + buildForm() { + this.form = this.fb.group({ + name: ['', Validators.required], + type: [null, Validators.required], + publishDate: [null, Validators.required], + price: [null, Validators.required], + }); + } + + // add save method + save() { + if (this.form.invalid) { + return; + } + + this.bookService.createByInput(this.form.value).subscribe(() => { + this.isModalOpen = false; + this.form.reset(); + this.list.get(); + }); + } +} +``` + +* 导入了 `FormGroup, FormBuilder and Validators`. +* 添加了 `form: FormGroup` 变量. +* 添加了 `bookType` 属性,你可以从模板中获取 `BookType` 枚举成员. +* 添加了 `bookTypes` 属性作为 `BookType` 枚举成员列表. 将在表单选项中使用. +* 我们注入了 `fb: FormBuilder` 服务到构造函数. [FormBuilder](https://angular.io/api/forms/FormBuilder) 服务为生成控件提供了方便的方法. 它减少了构建复杂表单所需的样板文件的数量. +* 我们添加了 `buildForm` 方法到文件末尾, 在 `createBook` 方法调用 `buildForm()` 方法. 该方法创建一个响应式表单去创建新书. +* 添加了`save` 方法. + +打开 `/src/app/book/book.component.html`,使用以下内容替换 ` `: + +```html + + +
    + * + +
    + +
    + * + +
    + +
    + * + +
    + +
    + * + +
    + +
    +``` + +同时使用下面的代码部分替换 ` `: + +````html + + + + + + +```` + +### Datepicker + +我们在这个组件中使用了[NgBootstrap datepicker](https://ng-bootstrap.github.io/#/components/datepicker/overview). 因此需要添加与此组件相关的依赖项. + +打开 `/src/app/book/book.module.ts` 使用以下内容替换: + +```js +import { NgModule } from '@angular/core'; +import { SharedModule } from '../shared/shared.module'; +import { BookRoutingModule } from './book-routing.module'; +import { BookComponent } from './book.component'; +import { NgbDatepickerModule } from '@ng-bootstrap/ng-bootstrap'; // add this line + +@NgModule({ + declarations: [BookComponent], + imports: [ + BookRoutingModule, + SharedModule, + NgbDatepickerModule, // add this line + ] +}) +export class BookModule { } +``` + +* 我们导入了 `NgbDatepickerModule` 来使用日期选择器. + +打开 `/src/app/book/book.component.ts` 使用以内内容替换: + +```js +import { ListService, PagedResultDto } from '@abp/ng.core'; +import { Component, OnInit } from '@angular/core'; +import { BookDto, BookType } from './models'; +import { BookService } from './services'; +import { FormGroup, FormBuilder, Validators } from '@angular/forms'; + +// added this line +import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap'; + +@Component({ + selector: 'app-book', + templateUrl: './book.component.html', + styleUrls: ['./book.component.scss'], + providers: [ + ListService, + { provide: NgbDateAdapter, useClass: NgbDateNativeAdapter } // add this line + ], +}) +export class BookComponent implements OnInit { + book = { items: [], totalCount: 0 } as PagedResultDto; + + form: FormGroup; + + bookType = BookType; + + bookTypes = Object.keys(this.bookType).filter( + (key) => typeof this.bookType[key] === 'number' + ); + + isModalOpen = false; + + constructor( + public readonly list: ListService, + private bookService: BookService, + private fb: FormBuilder + ) {} + + ngOnInit() { + const bookStreamCreator = (query) => this.bookService.getListByInput(query); + + this.list.hookToQuery(bookStreamCreator).subscribe((response) => { + this.book = response; + }); + } + + createBook() { + this.buildForm(); + this.isModalOpen = true; + } + + buildForm() { + this.form = this.fb.group({ + name: ['', Validators.required], + type: [null, Validators.required], + publishDate: [null, Validators.required], + price: [null, Validators.required], + }); + } + + save() { + if (this.form.invalid) { + return; + } + + this.bookService.createByInput(this.form.value).subscribe(() => { + this.isModalOpen = false; + this.form.reset(); + this.list.get(); + }); + } +} +``` + +* 导入了 `NgbDateNativeAdapter` 和 `NgbDateAdapter`. +* 我们添加了一个新的 `NgbDateAdapter` 提供程序,它将Datepicker值转换为 `Date` 类型. 有关更多详细信息,请参见[datepicker adapters](https://ng-bootstrap.github.io/#/components/datepicker/overview). + +现在你可以打开浏览器看到以下变化: + +![Save button to the modal](./images/bookstore-new-book-form-v2.png) + +## 更新书籍 + +打开 `/src/app/book/book.component.ts` 使用以下内容替换: + +```js +import { ListService, PagedResultDto } from '@abp/ng.core'; +import { Component, OnInit } from '@angular/core'; +import { BookDto, BookType, CreateUpdateBookDto } from './models'; +import { BookService } from './services'; +import { FormGroup, FormBuilder, Validators } from '@angular/forms'; +import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap'; + +@Component({ + selector: 'app-book', + templateUrl: './book.component.html', + styleUrls: ['./book.component.scss'], + providers: [ListService, { provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }], +}) +export class BookComponent implements OnInit { + book = { items: [], totalCount: 0 } as PagedResultDto; + + selectedBook = new BookDto(); // declare selectedBook + + form: FormGroup; + + bookType = BookType; + + bookTypes = Object.keys(this.bookType).filter( + (key) => typeof this.bookType[key] === 'number' + ); + + isModalOpen = false; + + constructor( + public readonly list: ListService, + private bookService: BookService, + private fb: FormBuilder + ) {} + + ngOnInit() { + const bookStreamCreator = (query) => this.bookService.getListByInput(query); + + this.list.hookToQuery(bookStreamCreator).subscribe((response) => { + this.book = response; + }); + } + + createBook() { + this.selectedBook = new BookDto(); // reset the selected book + this.buildForm(); + this.isModalOpen = true; + } + + // Add editBook method + editBook(id: string) { + this.bookService.getById(id).subscribe((book) => { + this.selectedBook = book; + this.buildForm(); + this.isModalOpen = true; + }); + } + + buildForm() { + this.form = this.fb.group({ + name: [this.selectedBook.name || '', Validators.required], + type: [this.selectedBook.type || null, Validators.required], + publishDate: [ + this.selectedBook.publishDate ? new Date(this.selectedBook.publishDate) : null, + Validators.required, + ], + price: [this.selectedBook.price || null, Validators.required], + }); + } + + // change the save method + save() { + if (this.form.invalid) { + return; + } + + const request = this.selectedBook.id + ? this.bookService.updateByIdAndInput(this.form.value, this.selectedBook.id) + : this.bookService.createByInput(this.form.value); + + request.subscribe(() => { + this.isModalOpen = false; + this.form.reset(); + this.list.get(); + }); + } +} +``` + +* 我们声明了类型为 `BookDto` 的 `selectedBook` 变量. +* 我们添加了 `editBook` 方法, 根据给定图书 `Id` 设置 `selectedBook` 对象. +* 我们替换了 `buildForm` 方法使用 `selectedBook` 数据创建表单. +* 我们替换了 `createBook` 方法,设置 `selectedBook` 为空对象. +* 我们替换了 `save` 方法. + +### 添加 "Actions" 下拉框到表格 + +打开 `/src/app/book/book.component.html` 在 `ngx-datatable` 第一列添加 `ngx-datatable-column` 定义: + +```html + + +
    + +
    + +
    +
    +
    +
    +``` + +在表格的第一列添加了一个 "Actions" 下拉菜单,如下图所示: + +![Action buttons](./images/bookstore-actions-buttons.png) + +同时如下所示更改 `ng-template #abpHeader` 部分: + +```html + +

    {%{{{ (selectedBook.id ? '::Edit' : '::NewBook' ) | abpLocalization }}}%}

    +
    +``` + +模板将在标题中显示 **Edit** 文本用于编辑记录操作, **New Book** 用于添加新记录操作. + +## 删除书籍 + +打开 `/src/app/book/book.component.ts` 注入 `ConfirmationService`. + +所示替换构造函数: + +```js +// ... + +// add new imports +import { ConfirmationService, Confirmation } from '@abp/ng.theme.shared'; + +//change the constructor +constructor( + public readonly list: ListService, + private bookService: BookService, + private fb: FormBuilder, + private confirmation: ConfirmationService // inject the ConfirmationService +) {} + +// Add a delete method +delete(id: string) { + this.confirmation.warn('::AreYouSureToDelete', '::AreYouSure').subscribe((status) => { + if (status === Confirmation.Status.confirm) { + this.bookService.deleteById(id).subscribe(() => this.list.get()); + } + }); +} +``` + +* 我们注入了 `ConfirmationService`. +* 我们注入了 `ConfirmationService` 到构造函数. +* 添加了 `delete` 方法. + +> 参阅[确认弹层文档](../UI/Angular/Confirmation-Service)了解该服务的更多信息. + +### 添加删除按钮: + +打开 `/src/app/book/book.component.html` 修改 `ngbDropdownMenu` 添加删除按钮: + +```html +
    + + +
    +``` + +最终操作下拉框UI看起来如下: + +![bookstore-final-actions-dropdown](./images/bookstore-final-actions-dropdown.png) + +点击 `delete` 动作调用 `delete` 方法,然后无法显示一个确认弹层如下图所示. + +![bookstore-confirmation-popup](./images/bookstore-confirmation-popup.png) -打开**测试资源管理器**(测试 -> Windows -> 测试资源管理器)并**执行**所有测试: +{{end}} -![bookstore-appservice-tests](./images/bookstore-appservice-tests.png) +## 下一章 -恭喜, 绿色图标表示测试已成功通过! +查看本教程的[下一章](Part-4.md). diff --git a/docs/zh-Hans/Tutorials/Part-4.md b/docs/zh-Hans/Tutorials/Part-4.md new file mode 100644 index 0000000000..151c7aab90 --- /dev/null +++ b/docs/zh-Hans/Tutorials/Part-4.md @@ -0,0 +1,255 @@ +# Web应用程序开发教程 - 第三章: 集成测试 +````json +//[doc-params] +{ + "UI": ["MVC","NG"], + "DB": ["EF","Mongo"] +} +```` +{{ +if UI == "MVC" + UI_Text="mvc" +else if UI == "NG" + UI_Text="angular" +else + UI_Text="?" +end +if DB == "EF" + DB_Text="Entity Framework Core" +else if DB == "Mongo" + DB_Text="MongoDB" +else + DB_Text="?" +end +}} + +## 关于本教程 + +在本系列教程中, 你将构建一个名为 `Acme.BookStore` 的用于管理书籍及其作者列表的基于ABP的应用程序. 它是使用以下技术开发的: + +* **{{DB_Text}}** 做为ORM提供程序. +* **{{UI_Value}}** 做为UI框架. + +本教程分为以下部分: + +- [Part 1: 创建服务端](Part-1.md) +- [Part 2: 图书列表页面](Part-2.md) +- [Part 3: 创建,更新和删除图书](Part-3.md) +- **Part 4: 集成测试**(本章) +- [Part 5: 授权](Part-5.md) +- [Part 6: 作者: 领域层](Part-6.md) +- [Part 7: 作者: 数据库集成](Part-7.md) +- [Part 8: 作者: 应用服务层](Part-8.md) +- [Part 9: 作者: 用户页面](Part-9.md) +- [Part 10: 图书到作者的关系](Part-10.md) + +## 下载源码 + +本教程根据你的**UI** 和 **Database**偏好有多个版,我们准备了两种可供下载的源码组合: + +* [MVC (Razor Pages) UI 与 EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore) +* [Angular UI 与 MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb) + +## 解决方案中的测试项目 + +这一部分涵盖了 **服务器端** 测试. 解决方案中有多个测试项目: + +![bookstore-test-projects-v2](./images/bookstore-test-projects-{{UI_Text}}.png) + +每个项目用于测试相关的应用程序项目.测试项目使用以下库进行测试: + +* [xunit](https://xunit.github.io/) 作为主测试框架. +* [Shoudly](http://shouldly.readthedocs.io/en/latest/) 作为断言库. +* [NSubstitute](http://nsubstitute.github.io/) 作为模拟库. + +{{if DB=="EF"}} + +> 测试项目配置为使用 **SQLite内存** 作为数据库. 创建一个单独的数据库实例并使用数据种子系统进行初始化种子数据,为每个测试准备一个新的数据库. + +{{else if DB=="Mongo"}} + +> **[Mongo2Go](https://github.com/Mongo2Go/Mongo2Go)**库用于模拟MongoDB数据库. 创建一个单独的数据库实例并使用数据种子系统进行初始化种子数据,为每个测试准备一个新的数据库. + +{{end}} + +## 添加测试数据 + +如果你已经按照[第一部分](Part-1.md)中的描述创建了数据种子贡献者,则相同的数据也在测试中可用. 因此你可以跳过此部分. 如果你尚未创建种子贡献者,可以使用 `BookStoreTestDataSeedContributor` 来为要在以下测试中使用的相同数据提供种子. + +## 测试 BookAppService + +在 `Acme.BookStore.Application.Tests` 项目中创建一个名叫 `BookAppService_Tests` 的测试类: + +````csharp +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.Application.Dtos; +using Xunit; + +namespace Acme.BookStore.Books +{ {{if DB=="Mongo"}} + [Collection(BookStoreTestConsts.CollectionDefinitionName)]{{end}} + public class BookAppService_Tests : BookStoreApplicationTestBase + { + private readonly IBookAppService _bookAppService; + + public BookAppService_Tests() + { + _bookAppService = GetRequiredService(); + } + + [Fact] + public async Task Should_Get_List_Of_Books() + { + //Act + var result = await _bookAppService.GetListAsync( + new PagedAndSortedResultRequestDto() + ); + + //Assert + result.TotalCount.ShouldBeGreaterThan(0); + result.Items.ShouldContain(b => b.Name == "1984"); + } + } +} +```` + +* 测试方法 `Should_Get_List_Of_Books` 直接使用 `BookAppService.GetListAsync` 方法来获取用户列表,并执行检查. +* 我们可以安全地检查 "1984" 这本书的名称,因为我们知道这本书可以在数据库中找到,我们已将其添加到种子数据中. + +新增测试方法,用以测试创建一个**合法**book实体的场景: + +````csharp +[Fact] +public async Task Should_Create_A_Valid_Book() +{ + //Act + var result = await _bookAppService.CreateAsync( + new CreateUpdateBookDto + { + Name = "New test book 42", + Price = 10, + PublishDate = System.DateTime.Now, + Type = BookType.ScienceFiction + } + ); + + //Assert + result.Id.ShouldNotBe(Guid.Empty); + result.Name.ShouldBe("New test book 42"); +} +```` + +新增测试方法,用以测试创建一个非法book实体失败的场景: + +````csharp +[Fact] +public async Task Should_Not_Create_A_Book_Without_Name() +{ + var exception = await Assert.ThrowsAsync(async () => + { + await _bookAppService.CreateAsync( + new CreateUpdateBookDto + { + Name = "", + Price = 10, + PublishDate = DateTime.Now, + Type = BookType.ScienceFiction + } + ); + }); + + exception.ValidationErrors + .ShouldContain(err => err.MemberNames.Any(mem => mem == "Name")); +} +```` + +* 由于 `Name` 是空值, ABP 抛出一个 `AbpValidationException` 异常. + +最终的测试类如下所示: + +````csharp +using System; +using System.Linq; +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Validation; +using Xunit; + +namespace Acme.BookStore.Books +{ {{if DB=="Mongo"}} + [Collection(BookStoreTestConsts.CollectionDefinitionName)]{{end}} + public class BookAppService_Tests : BookStoreApplicationTestBase + { + private readonly IBookAppService _bookAppService; + + public BookAppService_Tests() + { + _bookAppService = GetRequiredService(); + } + + [Fact] + public async Task Should_Get_List_Of_Books() + { + //Act + var result = await _bookAppService.GetListAsync( + new PagedAndSortedResultRequestDto() + ); + + //Assert + result.TotalCount.ShouldBeGreaterThan(0); + result.Items.ShouldContain(b => b.Name == "1984"); + } + + [Fact] + public async Task Should_Create_A_Valid_Book() + { + //Act + var result = await _bookAppService.CreateAsync( + new CreateUpdateBookDto + { + Name = "New test book 42", + Price = 10, + PublishDate = System.DateTime.Now, + Type = BookType.ScienceFiction + } + ); + + //Assert + result.Id.ShouldNotBe(Guid.Empty); + result.Name.ShouldBe("New test book 42"); + } + + [Fact] + public async Task Should_Not_Create_A_Book_Without_Name() + { + var exception = await Assert.ThrowsAsync(async () => + { + await _bookAppService.CreateAsync( + new CreateUpdateBookDto + { + Name = "", + Price = 10, + PublishDate = DateTime.Now, + Type = BookType.ScienceFiction + } + ); + }); + + exception.ValidationErrors + .ShouldContain(err => err.MemberNames.Any(mem => mem == "Name")); + } + } +} +```` + +打开**测试资源管理器**(测试 -> Windows -> 测试资源管理器)并**执行**所有测试: + +![bookstore-appservice-tests](./images/bookstore-appservice-tests.png) + +恭喜你, **绿色图标**表示测试已成功通过! + +## 下一章 + +查看本教程的[下一章](Part-5.md). \ No newline at end of file diff --git a/docs/zh-Hans/Tutorials/Part-5.md b/docs/zh-Hans/Tutorials/Part-5.md new file mode 100644 index 0000000000..602306c484 --- /dev/null +++ b/docs/zh-Hans/Tutorials/Part-5.md @@ -0,0 +1 @@ +TODO.. \ No newline at end of file diff --git a/docs/zh-Hans/Tutorials/Part-6.md b/docs/zh-Hans/Tutorials/Part-6.md new file mode 100644 index 0000000000..602306c484 --- /dev/null +++ b/docs/zh-Hans/Tutorials/Part-6.md @@ -0,0 +1 @@ +TODO.. \ No newline at end of file diff --git a/docs/zh-Hans/Tutorials/Part-7.md b/docs/zh-Hans/Tutorials/Part-7.md new file mode 100644 index 0000000000..602306c484 --- /dev/null +++ b/docs/zh-Hans/Tutorials/Part-7.md @@ -0,0 +1 @@ +TODO.. \ No newline at end of file diff --git a/docs/zh-Hans/Tutorials/Part-8.md b/docs/zh-Hans/Tutorials/Part-8.md new file mode 100644 index 0000000000..602306c484 --- /dev/null +++ b/docs/zh-Hans/Tutorials/Part-8.md @@ -0,0 +1 @@ +TODO.. \ No newline at end of file diff --git a/docs/zh-Hans/Tutorials/Part-9.md b/docs/zh-Hans/Tutorials/Part-9.md new file mode 100644 index 0000000000..602306c484 --- /dev/null +++ b/docs/zh-Hans/Tutorials/Part-9.md @@ -0,0 +1 @@ +TODO.. \ No newline at end of file diff --git a/docs/zh-Hans/Tutorials/images/bookstore-add-migration-authors.png b/docs/zh-Hans/Tutorials/images/bookstore-add-migration-authors.png new file mode 100644 index 0000000000..c24e33c628 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-add-migration-authors.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-added-author-to-book-list-angular.png b/docs/zh-Hans/Tutorials/images/bookstore-added-author-to-book-list-angular.png new file mode 100644 index 0000000000..9fba94d5bf Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-added-author-to-book-list-angular.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-added-author-to-book-list.png b/docs/zh-Hans/Tutorials/images/bookstore-added-author-to-book-list.png new file mode 100644 index 0000000000..f318ddfd31 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-added-author-to-book-list.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-added-authors-to-modals.png b/docs/zh-Hans/Tutorials/images/bookstore-added-authors-to-modals.png new file mode 100644 index 0000000000..5fe4046794 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-added-authors-to-modals.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-angular-authors-page.png b/docs/zh-Hans/Tutorials/images/bookstore-angular-authors-page.png new file mode 100644 index 0000000000..bbd865e44b Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-angular-authors-page.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-angular-service-proxy-author.png b/docs/zh-Hans/Tutorials/images/bookstore-angular-service-proxy-author.png new file mode 100644 index 0000000000..ac231ef7a8 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-angular-service-proxy-author.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-author-domain-layer.png b/docs/zh-Hans/Tutorials/images/bookstore-author-domain-layer.png new file mode 100644 index 0000000000..fde53834bb Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-author-domain-layer.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-author-permissions.png b/docs/zh-Hans/Tutorials/images/bookstore-author-permissions.png new file mode 100644 index 0000000000..a20093d2c4 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-author-permissions.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-authors-page.png b/docs/zh-Hans/Tutorials/images/bookstore-authors-page.png new file mode 100644 index 0000000000..6b8959df54 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-authors-page.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-book-and-booktype.png b/docs/zh-Hans/Tutorials/images/bookstore-book-and-booktype.png new file mode 100644 index 0000000000..1620ebf150 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-book-and-booktype.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-book-list-3.png b/docs/zh-Hans/Tutorials/images/bookstore-book-list-3.png new file mode 100644 index 0000000000..cbd7ec4a6a Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-book-list-3.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-book-list.png b/docs/zh-Hans/Tutorials/images/bookstore-book-list.png index ecdb87c737..4982032e64 100644 Binary files a/docs/zh-Hans/Tutorials/images/bookstore-book-list.png and b/docs/zh-Hans/Tutorials/images/bookstore-book-list.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-dbmigrator-on-solution.png b/docs/zh-Hans/Tutorials/images/bookstore-dbmigrator-on-solution.png new file mode 100644 index 0000000000..0b81900307 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-dbmigrator-on-solution.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-edit-button-2.png b/docs/zh-Hans/Tutorials/images/bookstore-edit-button-2.png new file mode 100644 index 0000000000..507bdd9406 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-edit-button-2.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-edit-delete-actions.png b/docs/zh-Hans/Tutorials/images/bookstore-edit-delete-actions.png new file mode 100644 index 0000000000..9c275c6d15 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-edit-delete-actions.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-getlist-result-network.png b/docs/zh-Hans/Tutorials/images/bookstore-getlist-result-network.png new file mode 100644 index 0000000000..141f9d14ee Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-getlist-result-network.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-index-js-file-v3.png b/docs/zh-Hans/Tutorials/images/bookstore-index-js-file-v3.png new file mode 100644 index 0000000000..add95788a8 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-index-js-file-v3.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-javascript-proxy-console.png b/docs/zh-Hans/Tutorials/images/bookstore-javascript-proxy-console.png new file mode 100644 index 0000000000..f393c47875 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-javascript-proxy-console.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-new-author-modal.png b/docs/zh-Hans/Tutorials/images/bookstore-new-author-modal.png new file mode 100644 index 0000000000..dda0dab4c1 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-new-author-modal.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-new-book-button-2.png b/docs/zh-Hans/Tutorials/images/bookstore-new-book-button-2.png new file mode 100644 index 0000000000..12f1819688 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-new-book-button-2.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-new-book-button-small.png b/docs/zh-Hans/Tutorials/images/bookstore-new-book-button-small.png new file mode 100644 index 0000000000..13f7da9285 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-new-book-button-small.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-permissions-ui.png b/docs/zh-Hans/Tutorials/images/bookstore-permissions-ui.png new file mode 100644 index 0000000000..749f7a013f Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-permissions-ui.png differ diff --git a/docs/zh-Hans/Tutorials/images/generated-proxies-2.png b/docs/zh-Hans/Tutorials/images/generated-proxies-2.png new file mode 100644 index 0000000000..adad417c3d Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/generated-proxies-2.png differ diff --git a/docs/zh-Hans/UI/Angular/Custom-Setting-Page.md b/docs/zh-Hans/UI/Angular/Custom-Setting-Page.md index f7854ac6bf..2b888e8d75 100644 --- a/docs/zh-Hans/UI/Angular/Custom-Setting-Page.md +++ b/docs/zh-Hans/UI/Angular/Custom-Setting-Page.md @@ -2,38 +2,33 @@ 不同的模块提供它们的设置选项卡. 你可以通过3个步骤在项目中自定义设置页面. -1. 创建一个组件 +1. 使用以下命令创建一个组件 -```js -import { Select } from '@ngxs/store'; -import { Component } from '@angular/core'; - -@Component({ - selector: 'app-your-custom-settings', - template: ` - custom-settings works! - `, -}) -export class YourCustomSettingsComponent { - // Your component logic -} +```bash +yarn ng generate component my-settings ``` -2. 添加 `YourCustomSettingsComponent` 到 `AppModule` 中的 `declarations`和 `entryComponents` 数组中. - -3. 打开 `app.component.ts` 在 `ngOnInit` 添加以下内容: +2. 打开 `app.component.ts` 做以下修改: ```js -import { addSettingTab } from '@abp/ng.theme.shared'; -// ... - -ngOnInit() { - addSettingTab({ - component: YourCustomSettingsComponent, - name: 'Type here the setting tab title (you can type a localization key, e.g: AbpAccount::Login', - order: 4, - requiredPolicy: 'type here a policy key' - }); +import { Component } from '@angular/core'; +import { SettingTabsService } from '@abp/ng.core'; // imported SettingTabsService +import { MySettingsComponent } from './my-settings/my-settings.component'; // imported MySettingsComponent + +@Component(/* component metadata */) +export class AppComponent { + constructor(private settingTabs: SettingTabsService) // injected MySettingsComponent + { + // added below + settingTabs.add([ + { + name: 'MySettings', + order: 1, + requiredPolicy: 'policy key here', + component: MySettingsComponent, + }, + ]); + } } ``` diff --git a/docs/zh-Hans/UI/Angular/Localization.md b/docs/zh-Hans/UI/Angular/Localization.md index 60af06a976..9ddb414234 100644 --- a/docs/zh-Hans/UI/Angular/Localization.md +++ b/docs/zh-Hans/UI/Angular/Localization.md @@ -146,16 +146,6 @@ this.store.selectSnapshot( "build": { "options": { "styles": [ - { - "input": "node_modules/@abp/ng.theme.shared/styles/bootstrap-rtl.min.css", - "inject": false, - "bundleName": "bootstrap-rtl.min" - }, - { - "input": "node_modules/bootstrap/dist/css/bootstrap.min.css", - "inject": true, - "bundleName": "bootstrap-ltr.min" - }, { "input": "node_modules/@fortawesome/fontawesome-free/css/all.min.css", "inject": true, @@ -166,6 +156,16 @@ this.store.selectSnapshot( "inject": true, "bundleName": "fontawesome-v4-shims.min" }, + { + "input": "node_modules/@abp/ng.theme.shared/styles/bootstrap-rtl.min.css", + "inject": false, + "bundleName": "bootstrap-rtl.min" + }, + { + "input": "node_modules/bootstrap/dist/css/bootstrap.min.css", + "inject": true, + "bundleName": "bootstrap-ltr.min" + }, "apps/dev-app/src/styles.scss" ], } @@ -174,6 +174,7 @@ this.store.selectSnapshot( } } } +``` #### 步骤 2. 清除AppComponent中延迟加载的Fontawesome @@ -192,6 +193,32 @@ import { Component } from '@angular/core'; export class AppComponent {} ``` +## 文化名称到语言环境文件名的映射 + +.NET中定义的某些文化名称与Angular语言环境不匹配. 在这种情况下Angular应用程序在运行时会引发如下错误: + +![locale-error](./images/locale-error.png) + +如果你看到这样的错误,你应该像下面这样传递 `cultureNameLocaleFileMap` 属性到CoreModule的forRoot静态方法. + +```js +// app.module.ts + +@NgModule({ + imports: [ + // other imports + CoreModule.forRoot({ + // other options + cultureNameLocaleFileMap: { + "DotnetCultureName": "AngularLocaleFileName", + "pt-BR": "pt" // example + } + }) + //... +``` + +查看 [Angular中所有的语言环境文件](https://github.com/angular/angular/tree/master/packages/common/locales). + ## 另请参阅 * [ASP.NET Core中的本地化](../../Localization.md) diff --git a/docs/zh-Hans/UI/Angular/Migration-Guide-v3.md b/docs/zh-Hans/UI/Angular/Migration-Guide-v3.md index 36afffa54f..eb16c21e03 100644 --- a/docs/zh-Hans/UI/Angular/Migration-Guide-v3.md +++ b/docs/zh-Hans/UI/Angular/Migration-Guide-v3.md @@ -18,7 +18,7 @@ yarn ng update @angular/cli @angular/core --force - 更新你的package.json并安装新的软件包 - 修改tsconfig.json文件创建一个"Solution Style"配置 -- 重命名 `browserlist` 为 `.browserlistrc` +- 重命名 `browserslist` 为 `.browserslistrc` 另一方面,如果你单独使用 `yarn ng update` 命令检查首先要更新哪些包会更好. Angular会给你一个要更新的包列表. @@ -144,42 +144,33 @@ export class AppModule {} AppRoutingModule: ```js -import { DynamicLayoutComponent } from '@abp/ng.core'; import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; const routes: Routes = [ { path: '', - component: DynamicLayoutComponent, - children: [ - { - path: '', - pathMatch: 'full', - loadChildren: () => import('./home/home.module') - .then(m => m.HomeModule), - }, - { - path: 'account', - loadChildren: () => import('@abp/ng.account') - .then(m => m.AccountModule.forLazy({ redirectUrl: '/' })), - }, - { - path: 'identity', - loadChildren: () => import('@abp/ng.identity') - .then(m => m.IdentityModule.forLazy()), - }, - { - path: 'tenant-management', - loadChildren: () => import('@abp/ng.tenant-management') - .then(m => m.TenantManagementModule.forLazy()), - }, - { - path: 'setting-management', - loadChildren: () => import('@abp/ng.setting-management') - .then(m => m.SettingManagementModule.forLazy()), - }, - ], + pathMatch: 'full', + loadChildren: () => import('./home/home.module').then(m => m.HomeModule), + }, + { + path: 'account', + loadChildren: () => + import('@abp/ng.account').then(m => m.AccountModule.forLazy({ redirectUrl: '/' })), + }, + { + path: 'identity', + loadChildren: () => import('@abp/ng.identity').then(m => m.IdentityModule.forLazy()), + }, + { + path: 'tenant-management', + loadChildren: () => + import('@abp/ng.tenant-management').then(m => m.TenantManagementModule.forLazy()), + }, + { + path: 'setting-management', + loadChildren: () => + import('@abp/ng.setting-management').then(m => m.SettingManagementModule.forLazy()), }, ]; @@ -190,7 +181,7 @@ const routes: Routes = [ export class AppRoutingModule {} ``` -> 你可能已经注意到我们在top级别路由组件上使用了 `DynamicLayoutComponent`. 我们这样做是为了避免不必要的渲染和闪烁. 这不是强制的,但是我们建议在你的应用程序路由中做同样的事情. +> 你可能已经注意到我们在AppComponent模板中使用了 `` 而不是 ``. 我们这样做是为了避免不必要的渲染和闪烁. 这不是强制的,但是我们建议在你的应用程序路由中做同样的事情. #### 如何迁移? @@ -200,7 +191,7 @@ export class AppRoutingModule {} - 调用 `ThemeBasicModule` 的静态 `forRoot` 方法(或商业上的 `ThemeLeptonModule`),并从导入中删除 `SharedModule`(除非已在其中添加了根模块所需的任何内容). - 在app路由模块中直接导入延迟ABP模块 (如 `() => import('@abp/ng.identity').then(...)`). - 在所有延迟模块 `then` 中调用的静态 `forLazy` 方法,即使配置没有被传递. -- [可选]使用 `DynamicLayoutComponent` 添加空的父路由,获得更好的性能和UX. +- [可选]添加 `` 到AppComponent模板并且删除 ``,获得更好的性能和UX. ### RoutesService @@ -233,9 +224,10 @@ export class AppRoutingModule {} 从ABP v3开始,我们已切换到经过严格测试,执行良好的数据表格:[ngx-datatable](https://github.com/swimlane/ngx-datatable). 所有的ABP模块都已经实现了ngx-datatable. `ThemeSharedModule` 已经导出了 `NgxDatatableModule`. 因此如果你在终端运行 `yarn add @swimlane/ngx-datatable` 来安装这个包,它将在你的应用的所有模块中可用. -为了正确设置样式,你需要在angular.json文件的样式部分中添加以下内容: +为了正确设置样式,你需要在angular.json文件的样式部分中添加以下内容(在其他所有元素之上): ```json +"styles": [ { "input": "node_modules/@swimlane/ngx-datatable/index.css", "inject": true, @@ -250,7 +242,9 @@ export class AppRoutingModule {} "input": "node_modules/@swimlane/ngx-datatable/themes/material.css", "inject": true, "bundleName": "ngx-datatable-material" - } + }, + // other styles +] ``` 由于尚未删除 `abp-table`, 因此以前由ABP v2.x构建的模块不会突然丢失所有. 但是它们的外观与内置ABP v3模块有所不同, 因此你可能希望将这些模块中的表转换为ngx-datatable. 为了减少将abp-table转换为ngx-datatable所需的工作量,我们修改了 `ListService` 以使其与 `ngx-datatable` 一起很好地工作,并引入了两个新指令: `NgxDatatableListDirective` 和 `NgxDatatableDefaultDirective`. @@ -348,7 +342,103 @@ export class SomeComponent { - 如果可以的话,根据上面的例子更新你的模. - 如果你稍后需要这样做,并且打算保留abp-table一段时间,请确保根据此处描述的[破坏性更改](List-Service.md)更新分页. -**重要说明:**abp-table没有被删除,但已被弃用并在以后的版本中删除. 请考虑切换到ngx-datatable。 +**重要说明:**abp-table没有被删除,但已被弃用并在以后的版本中删除. 请考虑切换到ngx-datatable. + +### 扩展系统[商业版] + +扩展程序系统现在是开源的, 可以从 `@abp/ng.theme.shared/extensions` 而不是从 `@volo/abp.commercial.ng.ui` 中获取. 同样,根据config软件包的新结构,如上所述通过 `forLazy` 静态方法进行配置. + +#### 如何迁移? + +如果你以前从未使用过扩展系统,则无需执行任何操作. 否则请再次检查文档以查看更改. 扩展系统本身的工作原理与以前相同,唯一的变化是你从中导入的包,静态方法以及您将贡献者传递给的模块. + + +### Lepton 主题Logo [商业版] + +在ABP v2.x中,Lepton每个颜色主题都有一个亮徽标和一个暗徽标. 我们意识到我们可以使它仅使用一个浅色和一个深色徽标. 因此我们更改了Lepton查找徽标图像的方式,现在你只需要在项目中包含 `logo-light.png` 和 `logo-dark.png`. + +#### 如何迁移? + +如果你之前已切换模板徽标PNG,则更改很简单: + +- 转到 `/assets/images/logo` 目录. +- 重命名 `theme1.png` 为 `logo-light.png` 并且重命名 `theme1-reverse.png` 为 `logo-dark.png`. +- 删除所有其他 `theme*.png` 文件. + +如果你更换了徽标组件,则更改有些不同,但仍然很简单. `LayoutStateService` 有两个新成员: `primaryLogoColor` 和 `secondaryLogoColor`. 它们有 `'light'` 和 `'dark'` 设置值做为可观察流. 你可以使用 `async` 管道在自定义徽标组件模板中使用它们的值. 这是一个完整的示例,其中涵盖了主要和辅助(帐户)布局徽标. + +```js +import { AddReplaceableComponent } from '@abp/ng.core'; +import { CommonModule } from '@angular/common'; +import { APP_INITIALIZER, Component, Injector, NgModule } from '@angular/core'; +import { Store } from '@ngxs/store'; +import { eAccountComponents } from '@volo/abp.ng.account'; +import { + AccountLayoutComponent, + eThemeLeptonComponents, + LayoutStateService, +} from '@volo/abp.ng.theme.lepton'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; + +@Component({ + template: ` + + + + + + `, +}) +export class LogoComponent { + isAccount: boolean; + + logoColor: Observable<'dark' | 'light'>; + + get logoUrl() { + return this.logoColor.pipe(map(color => `url(/assets/images/logo/logo-${color}.png)`)); + } + + constructor(injector: Injector) { + const layout = injector.get(LayoutStateService); + this.isAccount = Boolean(injector.get(AccountLayoutComponent, false)); + this.logoColor = this.isAccount ? layout.secondaryLogoColor : layout.primaryLogoColor; + } +} + +@NgModule({ + imports: [CommonModule], + declarations: [LogoComponent], + exports: [LogoComponent], +}) +export class LogoModule {} + +export const APP_LOGO_PROVIDER = [ + { provide: APP_INITIALIZER, useFactory: switchLogos, multi: true, deps: [Store] }, +]; + +export function switchLogos(store: Store) { + return () => { + store.dispatch( + new AddReplaceableComponent({ + component: LogoComponent, + key: eThemeLeptonComponents.Logo, + }), + ); + + store.dispatch( + new AddReplaceableComponent({ + component: LogoComponent, + key: eAccountComponents.Logo, + }), + ); + }; +} +``` + +只要将 `APP_LOGO_PROVIDER` 添加到根模块的提供程序(通常是 `AppModule` ),你就会有一个调整主题颜色的自定义徽标组件. ### 过时的接口 diff --git a/docs/zh-Hans/UI/Angular/Modifying-the-Menu.md b/docs/zh-Hans/UI/Angular/Modifying-the-Menu.md index 1f4e626bd3..82c1fe41ad 100644 --- a/docs/zh-Hans/UI/Angular/Modifying-the-Menu.md +++ b/docs/zh-Hans/UI/Angular/Modifying-the-Menu.md @@ -2,8 +2,6 @@ 菜单在 @abp/ng.theme.basic包 `ApplicationLayoutComponent` 内部. 有几种修改菜单的方法,本文档介绍了这些方法. 如果你想完全替换菜单,请参考[组件替换文档]了解如何替换布局. - - ## 如何添加Logo 环境变量中的 `logoUrl` 是logo的url. @@ -212,51 +210,65 @@ this.routes.remove(['Your navigation']); ## 如何在菜单的右侧添加元素 -右侧的元素存储在 @abp/ng.theme.basic 包的 `LayoutState` 中. - -`LayoutStateService` 的 `dispatchAddNavigationElement` 方法添加元素到右侧的菜单. - -你可以通过将模板添加到 `app.component` 调用 `dispatchAddNavigationElement` 方法来插入元素: +你可以通过调用 `NavItemsService` 的 `addItems` 方法将元素添加到菜单的右侧. 这是一个单例服务,即以根身份提供. 因此你可以立即注入并使用它. ```js -import { Layout, LayoutStateService } from '@abp/ng.theme.basic'; // added this line +import { NavItemsService } from '@abp/ng.theme.shared'; +import { Component } from '@angular/core'; @Component({ - selector: 'app-root', template: ` - - - + `, }) -export class AppComponent { - // Added ViewChild - @ViewChild('search', { static: false, read: TemplateRef }) searchElementRef: TemplateRef; - - constructor(private layout: LayoutStateService) {} // injected LayoutStateService +export class MySearchInputComponent {} - // Added ngAfterViewInit - ngAfterViewInit() { - const newElement = { - name: 'Search', - element: this.searchElementRef, - order: 1, - } as Layout.NavigationElement; - this.layout.dispatchAddNavigationElement(newElement); +@Component(/* component metadata */) +export class AppComponent { + constructor(private navItems: NavItemsService) { + navItems.addItems([ + { + id: 'MySearchInput', + order: 1, + component: MySearchInputComponent, + }, + { + id: 'SignOutIcon', + html: '', + action: () => console.log('Clicked the sign out icon'), + order: 101, // puts as last element + }, + ]); } } ``` -上面我们在菜单添加了一个搜索输入,最终UI如下: +上面我们在菜单添加了一个搜索输入和退出登录图标,最终UI如下: ![navigation-menu-search-input](./images/navigation-menu-search-input.png) -## 如何删除右侧菜单元素 +> 默认元素的排序为 `100`. 如果要将自定义元素放在默认值之前,请指定一个排序,最高为 `99`. 如果要将自定义元素放在默认值之后,请指定排序从 `101` 开始. 最后如果必须在默认值之间放置一个项目,请按如下所述修补默认元素顺序. 但有一个警告:我们将来可能会添加另一个默认元素,排序也为 `100`. + +## 如何修补或删除右侧部分元素 + +`NavItemsService` 的 `patchItem` 方法通过 `id` 查找元素,并将配置替换为第二个参数传递的新配置. `removeItem` 方法会找到一个元素并删除. + +```js +export class AppComponent { + constructor(private navItems: NavItemsService) { + navItems.patchItem(eThemeBasicComponents.Languages, { + requiredPolicy: 'new policy here', + order: 1, + }); + + navItems.removeItem(eThemeBasicComponents.CurrentUser); + } +} +``` -TODO +* 使用新的 `requiredPolicy` 和新的 `order` 修补了语言下拉菜单元素. +* 删除了当前用户的下拉菜单元素. ## 下一步是什么? diff --git a/docs/zh-Hans/UI/Angular/Permission-Management.md b/docs/zh-Hans/UI/Angular/Permission-Management.md index 5c1ba5b4fb..7f28035105 100644 --- a/docs/zh-Hans/UI/Angular/Permission-Management.md +++ b/docs/zh-Hans/UI/Angular/Permission-Management.md @@ -55,7 +55,9 @@ export class YourComponent { 如果你想要在导航过程中控制经过身份验证的用户对路由的访问权限,可以使用 `PermissionGuard`. -将 `requiredPolicy` 添加到路由模块中的 `routes`属性. +* 从@abp/ng.core导入PermissionGuard. +* 添加 `canActivate: [PermissionGuard]` 到你的路由对象. +* 添加 `requiredPolicy` 到路由模块路由的 `data` 属性. ```js import { PermissionGuard } from '@abp/ng.core'; @@ -66,9 +68,7 @@ const routes: Routes = [ component: YourComponent, canActivate: [PermissionGuard], data: { - routes: { - requiredPolicy: 'AbpIdentity.Roles.Create', - }, + requiredPolicy: 'YourProjectName.YourComponent', // policy key for your component }, }, ]; diff --git a/docs/zh-Hans/UI/Angular/Service-Proxies.md b/docs/zh-Hans/UI/Angular/Service-Proxies.md index 29aafebdae..2824bc44a7 100644 --- a/docs/zh-Hans/UI/Angular/Service-Proxies.md +++ b/docs/zh-Hans/UI/Angular/Service-Proxies.md @@ -33,7 +33,7 @@ abp generate-proxy 服务的 `providerIn` 属性定义为 `'root'`. 因此无需将服务作为提供程序添加到模块. 你可以通过将服务注入到构造函数中来使用它,如下所示: ```js -import { AbpApplicationConfigurationService } from '../app/shared/services'; +import { AbpApplicationConfigurationService } from '../abp/applicationconfiguration/services'; //... export class HomeComponent{ @@ -49,14 +49,14 @@ Angular编译器会从最终输出中删除那些没有被注入的服务. 参 ### Models -生成的模型与后端中的dto匹配. 每个模型在 `src/app/*/shared/models` 文件夹生成一个类. +生成的模型与后端中的dto匹配. 每个模型在 `src/app/*/models` 文件夹生成一个类. `@abp/ng.core` 包有一些[基类](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/dtos.ts). 一些模型扩展了这些类. 可以如下所示创建一个类的实例: ```js -import { IdentityRoleCreateDto } from '../identity/shared/models'; +import { IdentityRoleCreateDto } from '../identity/role/models' //... const instance = new IdentityRoleCreateDto({name: 'Role 1', isDefault: false, isPublic: true}) ``` diff --git a/docs/zh-Hans/UI/Angular/Subscription-Service.md b/docs/zh-Hans/UI/Angular/Subscription-Service.md new file mode 100644 index 0000000000..fb248d4d95 --- /dev/null +++ b/docs/zh-Hans/UI/Angular/Subscription-Service.md @@ -0,0 +1,205 @@ +# 轻松取消将你的观察取消订阅 + +`SubscriptionService` 是一个实用工具服务,它提供了一个简单的取消订阅Angular组件和指令中的RxJS可观察对象的功能. 请参见[为什么在实例销毁时要取消订阅可观察对象](https://angular.io/guide/lifecycle-hooks#cleaning-up-on-instance-destruction). + +## 入门 + +你必须在组件或指令级别提供 `SubscriptionService`,因为它没有在**根中提供**,而且它与组件/指令的生命周期同步. 只有在此之后,您才能注入并开始使用它。 + +```js +import { SubscriptionService } from '@abp/ng.core'; + +@Component({ + /* class metadata here */ + providers: [SubscriptionService], +}) +class DemoComponent { + count$ = interval(1000); + + constructor(private subscription: SubscriptionService) { + this.subscription.addOne(this.count$, console.log); + } +} +``` + +由 `count$` 发出的值将被记录下来,直到组件被销毁. 你不必手动退订. + +> 请不要尝试使用单例 `SubscriptionService`. 这是行不通的. + +## 用法 + +## 如何订阅可观察对象 + +你可以传递 `next` 函数和 `error` 函数. + + + +```js +@Component({ + /* class metadata here */ + providers: [SubscriptionService], +}) +class DemoComponent implements OnInit { + constructor(private subscription: SubscriptionService) {} + + ngOnInit() { + const source$ = interval(1000); + const nextFn = value => console.log(value * 2); + const errorFn = error => { + console.error(error); + return of(null); + }; + + this.subscription.addOne(source$, nextFn, errorFn); + } +} +``` + +或者,你可以传递一个观察者. + +```js +@Component({ + /* class metadata here */ + providers: [SubscriptionService], +}) +class DemoComponent implements OnInit { + constructor(private subscription: SubscriptionService) {} + + ngOnInit() { + const source$ = interval(1000); + const observer = { + next: value => console.log(value * 2), + complete: () => console.log('DONE'), + }; + + this.subscription.addOne(source$, observer); + } +} +``` + +`addOne` 方法返回单个订阅以便你以后使用它. 有关详细信息,请参见下面的主题. + +### 实例销毁之前如何退订 + +有两种方法可以做到这一点. 如果你不想再次订阅.可以使用 `closeAll` 方法. + +```js +@Component({ + /* class metadata here */ + providers: [SubscriptionService], +}) +class DemoComponent implements OnInit { + constructor(private subscription: SubscriptionService) {} + + ngOnInit() { + this.subscription.addOne(interval(1000), console.log); + } + + onSomeEvent() { + this.subscription.closeAll(); + } +} +``` + +这将清除所有订阅,你将无法再次订阅. 如果你打算添加另一个订阅,可以使用`reset`方法. + +```js +@Component({ + /* class metadata here */ + providers: [SubscriptionService], +}) +class DemoComponent implements OnInit { + constructor(private subscription: SubscriptionService) {} + + ngOnInit() { + this.subscription.addOne(interval(1000), console.log); + } + + onSomeEvent() { + this.subscription.reset(); + this.subscription.addOne(interval(1000), console.warn); + } +} +``` + +### 如何取消单个订阅 + +有时你可能需要取消订阅特定的订阅,但保留其他订阅. 在这种情况下,你可以使用 `closeOne` 方法. + +```js +@Component({ + /* class metadata here */ + providers: [SubscriptionService], +}) +class DemoComponent implements OnInit { + countSubscription: Subscription; + + constructor(private subscription: SubscriptionService) {} + + ngOnInit() { + this.countSubscription = this.subscription.addOne( + interval(1000), + console.log + ); + } + + onSomeEvent() { + this.subscription.closeOne(this.countSubscription); + console.log(this.countSubscription.closed); // true + } +} +``` + +### 如何从跟踪的订阅中删除单个订阅 + +你可能需要控制特定的订阅. 在这种情况下你可以使用 `removeOne` 方法将其从跟踪的订阅中删除. + +```js +@Component({ + /* class metadata here */ + providers: [SubscriptionService], +}) +class DemoComponent implements OnInit { + countSubscription: Subscription; + + constructor(private subscription: SubscriptionService) {} + + ngOnInit() { + this.countSubscription = this.subscription.addOne( + interval(1000), + console.log + ); + } + + onSomeEvent() { + this.subscription.removeOne(this.countSubscription); + console.log(this.countSubscription.closed); // false + } +} +``` + +### 如何检查是否全部取消订阅 + +使用 `isClosed` 检查 `closeAll` 是否被调用. + +```js +@Component({ + /* class metadata here */ + providers: [SubscriptionService], +}) +class DemoComponent implements OnInit { + constructor(private subscription: SubscriptionService) {} + + ngOnInit() { + this.subscription.addOne(interval(1000), console.log); + } + + onSomeEvent() { + console.log(this.subscription.isClosed); // false + } +} +``` + +## 下一步是什么? + +- [ListService](./List-Service.md) diff --git a/docs/zh-Hans/UI/Angular/Track-By-Service.md b/docs/zh-Hans/UI/Angular/Track-By-Service.md index 53c9dcbf1c..f941b1f0ca 100644 --- a/docs/zh-Hans/UI/Angular/Track-By-Service.md +++ b/docs/zh-Hans/UI/Angular/Track-By-Service.md @@ -99,4 +99,4 @@ class DemoComponent { ## 下一步是什么? -- [ListService](./List-Service.md) \ No newline at end of file +- [SubscriptionService](./Subscription-Service.md) \ No newline at end of file diff --git a/docs/zh-Hans/UI/Angular/images/custom-settings.png b/docs/zh-Hans/UI/Angular/images/custom-settings.png index 05dd3f695a..dc40ad3fa9 100644 Binary files a/docs/zh-Hans/UI/Angular/images/custom-settings.png and b/docs/zh-Hans/UI/Angular/images/custom-settings.png differ diff --git a/docs/zh-Hans/UI/Angular/images/generated-files-via-generate-proxy.png b/docs/zh-Hans/UI/Angular/images/generated-files-via-generate-proxy.png index 77a120a35d..0d062cc2d3 100644 Binary files a/docs/zh-Hans/UI/Angular/images/generated-files-via-generate-proxy.png and b/docs/zh-Hans/UI/Angular/images/generated-files-via-generate-proxy.png differ diff --git a/docs/zh-Hans/UI/Angular/images/locale-error.png b/docs/zh-Hans/UI/Angular/images/locale-error.png new file mode 100644 index 0000000000..de385a59c0 Binary files /dev/null and b/docs/zh-Hans/UI/Angular/images/locale-error.png differ diff --git a/docs/zh-Hans/UI/Angular/images/navigation-menu-search-input.png b/docs/zh-Hans/UI/Angular/images/navigation-menu-search-input.png index ebdc05e3e0..e93b7927d1 100644 Binary files a/docs/zh-Hans/UI/Angular/images/navigation-menu-search-input.png and b/docs/zh-Hans/UI/Angular/images/navigation-menu-search-input.png differ diff --git a/docs/zh-Hans/UI/AspNetCore/Dynamic-JavaScript-Proxies.md b/docs/zh-Hans/UI/AspNetCore/Dynamic-JavaScript-Proxies.md new file mode 100644 index 0000000000..391a63a910 --- /dev/null +++ b/docs/zh-Hans/UI/AspNetCore/Dynamic-JavaScript-Proxies.md @@ -0,0 +1,3 @@ +# Dynamic JavaScript HTTP API Proxies + +TODO \ No newline at end of file diff --git a/docs/zh-Hans/Unit-Of-Work.md b/docs/zh-Hans/Unit-Of-Work.md index 0fbf5762f0..b937c86ac0 100644 --- a/docs/zh-Hans/Unit-Of-Work.md +++ b/docs/zh-Hans/Unit-Of-Work.md @@ -21,7 +21,7 @@ ABP的UOW系统是; UOW自动针对这些方法开始,除非**周围已经有一个(环境)**UOW在运行.示例; -* 如果你调用一个[仓储]方法(Repositories.md),但还没有启动UOW,它将自动**启动一个新的事务UOW**,其中包括在仓储方法中完成的所有操作,如果仓储方法没有抛出任何异常,则**提交事务**. 仓储方法根本不知道UOW或事务. 它只在一个常规的数据库对象上工作(例如用于[EF Core](Entity-Framework-Core.md)的`DbContext`),而UOW由ABP框架处理. +* 如果你调用一个[仓储](Repositories.md)方法,但还没有启动UOW,它将自动**启动一个新的事务UOW**,其中包括在仓储方法中完成的所有操作,如果仓储方法没有抛出任何异常,则**提交事务**. 仓储方法根本不知道UOW或事务. 它只在一个常规的数据库对象上工作(例如用于[EF Core](Entity-Framework-Core.md)的`DbContext`),而UOW由ABP框架处理. * 如果调用[应用服务](Application-Services.md)方法,则相同的UOW系统将按上述说明工作. 如果应用服务方法使用某些仓储,这些仓储**不会开始新的UOW**,而是**参与由ABP框架为应用程序服务方法启动的当前工作单元中**. * ASP.NET Core控制器操作也是如此. 如果操作以控制器action开始,**UOW范围是控制器action的方法主体**. @@ -352,4 +352,4 @@ public async Task CreateAsync(string name) ````csharp app.UseUnitOfWork(); app.UseConfiguredEndpoints(); -```` \ No newline at end of file +```` diff --git a/docs/zh-Hans/docs-nav.json b/docs/zh-Hans/docs-nav.json index 726ff8816c..26a526dd10 100644 --- a/docs/zh-Hans/docs-nav.json +++ b/docs/zh-Hans/docs-nav.json @@ -80,6 +80,15 @@ "text": "CLI", "path": "CLI.md" }, + { + "text": "认证", + "items": [ + { + "text": "社交/外部登录", + "path": "Authentication/Social-External-Logins.md" + } + ] + }, { "text": "基础知识", "items": [ @@ -214,6 +223,14 @@ "text": "Aliyun提供程序", "path": "Blob-Storing-Aliyun.md" }, + { + "text": "Minio提供程序", + "path": "Blob-Storing-Minio.md" + }, + { + "text": "AWS提供程序", + "path": "Blob-Storing-Aws.md" + }, { "text": "创建自定义提供程序", "path": "Blob-Storing-Custom-Provider.md" @@ -432,12 +449,25 @@ "text": "TrackByService", "path": "UI/Angular/Track-By-Service.md" }, + { + "text": "SubscriptionService", + "path": "UI/Angular/Subscription-Service.md" + }, { "text": "ListService", "path": "UI/Angular/List-Service.md" } ] }, + { + "text": "React Native", + "items": [ + { + "text": "入门", + "path": "Getting-Started-React-Native.md" + } + ] + }, { "text": "通用", "items": [ @@ -616,6 +646,10 @@ { "text": "API文档", "path": "{ApiDocumentationUrl}" + }, + { + "text": "官方包", + "path": "https://abp.io/packages" } ] } diff --git a/docs/zh-Hans/images/basic-console-application-solution.png b/docs/zh-Hans/images/basic-console-application-solution.png new file mode 100644 index 0000000000..788bad0381 Binary files /dev/null and b/docs/zh-Hans/images/basic-console-application-solution.png differ diff --git a/framework/Volo.Abp.sln b/framework/Volo.Abp.sln index 5a83ae1460..5778b054ed 100644 --- a/framework/Volo.Abp.sln +++ b/framework/Volo.Abp.sln @@ -319,6 +319,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.BlobStoring.Aliyun EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.BlobStoring.Aliyun.Tests", "test\Volo.Abp.BlobStoring.Aliyun.Tests\Volo.Abp.BlobStoring.Aliyun.Tests.csproj", "{8E49687A-E69F-49F2-8DB0-428D0883A937}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlobStoring.Aws", "src\Volo.Abp.BlobStoring.Aws\Volo.Abp.BlobStoring.Aws.csproj", "{50968CDE-1029-4051-B2E5-B69D0ECF2A18}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlobStoring.Aws.Tests", "test\Volo.Abp.BlobStoring.Aws.Tests\Volo.Abp.BlobStoring.Aws.Tests.csproj", "{2CD3B26A-CA81-4279-8D5D-6A594517BB3F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -949,6 +953,14 @@ Global {8E49687A-E69F-49F2-8DB0-428D0883A937}.Debug|Any CPU.Build.0 = Debug|Any CPU {8E49687A-E69F-49F2-8DB0-428D0883A937}.Release|Any CPU.ActiveCfg = Release|Any CPU {8E49687A-E69F-49F2-8DB0-428D0883A937}.Release|Any CPU.Build.0 = Release|Any CPU + {50968CDE-1029-4051-B2E5-B69D0ECF2A18}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {50968CDE-1029-4051-B2E5-B69D0ECF2A18}.Debug|Any CPU.Build.0 = Debug|Any CPU + {50968CDE-1029-4051-B2E5-B69D0ECF2A18}.Release|Any CPU.ActiveCfg = Release|Any CPU + {50968CDE-1029-4051-B2E5-B69D0ECF2A18}.Release|Any CPU.Build.0 = Release|Any CPU + {2CD3B26A-CA81-4279-8D5D-6A594517BB3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2CD3B26A-CA81-4279-8D5D-6A594517BB3F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2CD3B26A-CA81-4279-8D5D-6A594517BB3F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2CD3B26A-CA81-4279-8D5D-6A594517BB3F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1110,6 +1122,8 @@ Global {60D0E384-965E-4F81-9D71-B28F419254FC} = {447C8A77-E5F0-4538-8687-7383196D04EA} {845E6A13-D1B5-4DDC-A16C-68D807E3B4C7} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {8E49687A-E69F-49F2-8DB0-428D0883A937} = {447C8A77-E5F0-4538-8687-7383196D04EA} + {50968CDE-1029-4051-B2E5-B69D0ECF2A18} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} + {2CD3B26A-CA81-4279-8D5D-6A594517BB3F} = {447C8A77-E5F0-4538-8687-7383196D04EA} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5} diff --git a/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo.Abp.AspNetCore.Authentication.JwtBearer.csproj b/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo.Abp.AspNetCore.Authentication.JwtBearer.csproj index af317c263c..1d30366e67 100644 --- a/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo.Abp.AspNetCore.Authentication.JwtBearer.csproj +++ b/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo.Abp.AspNetCore.Authentication.JwtBearer.csproj @@ -19,7 +19,7 @@ - + diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Blockquote/AbpBlockquoteFooterTagHelper.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Blockquote/AbpBlockquoteFooterTagHelper.cs index 3b76fa2627..d4619390ef 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Blockquote/AbpBlockquoteFooterTagHelper.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Blockquote/AbpBlockquoteFooterTagHelper.cs @@ -2,7 +2,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Blockquote { - [HtmlTargetElement("footer", ParentTag = "abp-blockquote")] + [HtmlTargetElement("footer", ParentTag = "blockquote")] public class AbpBlockquoteFooterTagHelper : AbpTagHelper { public AbpBlockquoteFooterTagHelper(AbpBlockquoteFooterTagHelperService tagHelperService) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Blockquote/AbpBlockquoteParagraphTagHelper.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Blockquote/AbpBlockquoteParagraphTagHelper.cs index 99bcceb4fe..0e915f2a36 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Blockquote/AbpBlockquoteParagraphTagHelper.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Blockquote/AbpBlockquoteParagraphTagHelper.cs @@ -2,7 +2,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Blockquote { - [HtmlTargetElement("p", ParentTag = "abp-blockquote")] + [HtmlTargetElement("p", ParentTag = "blockquote")] public class AbpBlockquoteParagraphTagHelper : AbpTagHelper { public AbpBlockquoteParagraphTagHelper(AbpBlockquoteParagraphTagHelperService tagHelperService) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Blockquote/AbpBlockquoteTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Blockquote/AbpBlockquoteTagHelperService.cs index a60f3a4145..6c26747d11 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Blockquote/AbpBlockquoteTagHelperService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Blockquote/AbpBlockquoteTagHelperService.cs @@ -8,6 +8,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Blockquote public override void Process(TagHelperContext context, TagHelperOutput output) { output.Attributes.AddClass("blockquote"); + output.TagName = "blockquote"; } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Breadcrumb/AbpBreadcrumbTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Breadcrumb/AbpBreadcrumbTagHelperService.cs index 8eeeb1e929..1d0476c5ad 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Breadcrumb/AbpBreadcrumbTagHelperService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Breadcrumb/AbpBreadcrumbTagHelperService.cs @@ -13,9 +13,12 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Breadcrumb var list = InitilizeFormGroupContentsContext(context, output); await output.GetChildContentAsync(); - + SetInnerOlTag(context, output); SetInnerList(context, output, list); + + output.TagName = "nav"; + output.Attributes.Add("aria-label", "breadcrumb"); } protected virtual void SetInnerOlTag(TagHelperContext context, TagHelperOutput output) @@ -63,4 +66,4 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Breadcrumb } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Button/AbpButtonGroupTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Button/AbpButtonGroupTagHelperService.cs index 602e956e1e..3250fe6877 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Button/AbpButtonGroupTagHelperService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Button/AbpButtonGroupTagHelperService.cs @@ -11,6 +11,8 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Button AddButtonGroupClass(context, output); AddSizeClass(context, output); AddAttributes(context, output); + + output.TagName = "div"; } protected virtual void AddSizeClass(TagHelperContext context, TagHelperOutput output) @@ -52,4 +54,4 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Button output.Attributes.Add("role", "group"); } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Button/AbpButtonToolbarTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Button/AbpButtonToolbarTagHelperService.cs index 87b378e559..1581cc8bd3 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Button/AbpButtonToolbarTagHelperService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Button/AbpButtonToolbarTagHelperService.cs @@ -7,8 +7,9 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Button { public override void Process(TagHelperContext context, TagHelperOutput output) { + output.TagName = "div"; output.Attributes.AddClass("btn-toolbar"); output.Attributes.Add("role","toolbar"); } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Collapse/AbpAccordionItemTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Collapse/AbpAccordionItemTagHelperService.cs index 9dc279334b..74365b0c8a 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Collapse/AbpAccordionItemTagHelperService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Collapse/AbpAccordionItemTagHelperService.cs @@ -23,8 +23,6 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Collapse output.SuppressOutput(); } - - protected virtual string GetAccordionHeaderItem(TagHelperContext context, TagHelperOutput output) { return "
    " + Environment.NewLine + @@ -64,4 +62,4 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Collapse } } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelper.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelper.cs index efd3af2d2c..711e8dfb6c 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelper.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelper.cs @@ -36,6 +36,8 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form public string Value { get; set; } + public bool SuppressLabel { get; set; } + public AbpInputTagHelper(AbpInputTagHelperService tagHelperService) : base(tagHelperService) { diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs index 4ddf44d2d6..58a88c676f 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs @@ -247,7 +247,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form protected virtual async Task GetLabelAsHtmlAsync(TagHelperContext context, TagHelperOutput output, TagHelperOutput inputTag, bool isCheckbox) { - if (IsOutputHidden(inputTag)) + if (IsOutputHidden(inputTag) || TagHelper.SuppressLabel) { return ""; } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/AbpAspNetCoreMvcUiPackagesModule.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/AbpAspNetCoreMvcUiPackagesModule.cs index a5ee47a753..93fa193bc7 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/AbpAspNetCoreMvcUiPackagesModule.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/AbpAspNetCoreMvcUiPackagesModule.cs @@ -1,5 +1,7 @@ using Volo.Abp.AspNetCore.Mvc.UI.Bundling; using Volo.Abp.AspNetCore.Mvc.UI.Packages.BootstrapDatepicker; +using Volo.Abp.AspNetCore.Mvc.UI.Packages.JQueryValidation; +using Volo.Abp.AspNetCore.Mvc.UI.Packages.Timeago; using Volo.Abp.Localization; using Volo.Abp.Modularity; @@ -12,6 +14,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Packages { Configure(options => { + //BootstrapDatepicker options.AddLanguagesMapOrUpdate(BootstrapDatepickerScriptContributor.PackageName, new NameValue("zh-Hans", "zh-CN"), new NameValue("zh-Hant", "zh-TW")); @@ -19,6 +22,16 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Packages options.AddLanguageFilesMapOrUpdate(BootstrapDatepickerScriptContributor.PackageName, new NameValue("zh-Hans", "zh-CN"), new NameValue("zh-Hant", "zh-TW")); + + //Timeago + options.AddLanguageFilesMapOrUpdate(TimeagoScriptContributor.PackageName, + new NameValue("zh-Hans", "zh-CN"), + new NameValue("zh-Hant", "zh-TW")); + + //JQueryValidation + options.AddLanguageFilesMapOrUpdate(JQueryValidationScriptContributor.PackageName, + new NameValue("zh-Hans", "zh"), + new NameValue("zh-Hant", "zh_TW")); }); } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/BootstrapDatepicker/BootstrapDatepickerScriptContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/BootstrapDatepicker/BootstrapDatepickerScriptContributor.cs index ca852972d8..751fb8c53e 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/BootstrapDatepicker/BootstrapDatepickerScriptContributor.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/BootstrapDatepicker/BootstrapDatepickerScriptContributor.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Globalization; using Volo.Abp.AspNetCore.Mvc.UI.Bundling; using Volo.Abp.AspNetCore.Mvc.UI.Packages.JQuery; using Volo.Abp.Localization; @@ -19,26 +18,12 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.BootstrapDatepicker public override void ConfigureDynamicResources(BundleConfigurationContext context) { - var cultureName = CultureInfo.CurrentUICulture.DateTimeFormat.Calendar.AlgorithmType == - CalendarAlgorithmType.LunarCalendar - ? "en" - : CultureInfo.CurrentUICulture.Name; - - TryAddCultureFile(context, cultureName); - } - - protected virtual bool TryAddCultureFile(BundleConfigurationContext context, string cultureName) - { - var fileName = context.LocalizationOptions.GetLanguageFilesMap(PackageName, cultureName); + var fileName = context.LocalizationOptions.GetCurrentUICultureLanguageFilesMap(PackageName); var filePath = $"/libs/bootstrap-datepicker/locales/bootstrap-datepicker.{fileName}.min.js"; - - if (!context.FileProvider.GetFileInfo(filePath).Exists) + if (context.FileProvider.GetFileInfo(filePath).Exists) { - return false; + context.Files.AddIfNotContains(filePath); } - - context.Files.AddIfNotContains(filePath); - return true; } } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/JQueryValidation/JQueryValidationScriptContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/JQueryValidation/JQueryValidationScriptContributor.cs index 586d8864cd..c5387b233c 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/JQueryValidation/JQueryValidationScriptContributor.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/JQueryValidation/JQueryValidationScriptContributor.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Globalization; using Volo.Abp.AspNetCore.Mvc.UI.Bundling; using Volo.Abp.AspNetCore.Mvc.UI.Packages.JQuery; using Volo.Abp.Localization; @@ -10,8 +9,6 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.JQueryValidation [DependsOn(typeof(JQueryScriptContributor))] public class JQueryValidationScriptContributor : BundleContributor { - public const string DefaultLocalizationFolder = "/libs/jquery-validation/localization/"; - public const string PackageName = "jquery-validation"; public override void ConfigureBundle(BundleConfigurationContext context) @@ -21,41 +18,12 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.JQueryValidation public override void ConfigureDynamicResources(BundleConfigurationContext context) { - //TODO: Can we optimize these points: - // - Can we get rid of context.FileProvider.GetFileInfo call? - // - What if the same Contributor is used twice for a page. - // Duplication is prevented by the bundle manager, however the logic below will execute twice - - var cultureName = CultureInfo.CurrentUICulture.TwoLetterISOLanguageName.Replace('-', '_'); - - var fileName = context.LocalizationOptions.GetLanguageFilesMap(PackageName, cultureName); - if (TryAddCultureFile(context, fileName)) - { - return; - } - - if (!cultureName.Contains("_")) - { - return; - } - - fileName = context.LocalizationOptions.GetLanguageFilesMap(PackageName, - cultureName.Substring(0, cultureName.IndexOf('_'))); - TryAddCultureFile(context, fileName); - } - - protected virtual bool TryAddCultureFile(BundleConfigurationContext context, string cultureName) - { - var filePath = DefaultLocalizationFolder + "messages_" + cultureName + ".js"; - var fileInfo = context.FileProvider.GetFileInfo(filePath); - - if (!fileInfo.Exists) + var fileName = context.LocalizationOptions.GetCurrentUICultureLanguageFilesMap(PackageName); + var filePath = $"/libs/jquery-validation/localization/messages_{fileName}.js"; + if (context.FileProvider.GetFileInfo(filePath).Exists) { - return false; + context.Files.AddIfNotContains(filePath); } - - context.Files.AddIfNotContains(filePath); - return true; } } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Timeago/TimeagoScriptContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Timeago/TimeagoScriptContributor.cs index 9edbf065cb..56adbe5deb 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Timeago/TimeagoScriptContributor.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Timeago/TimeagoScriptContributor.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; -using System.Globalization; using Volo.Abp.AspNetCore.Mvc.UI.Bundling; using Volo.Abp.AspNetCore.Mvc.UI.Packages.JQuery; +using Volo.Abp.Localization; using Volo.Abp.Modularity; namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.Timeago @@ -9,6 +9,8 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.Timeago [DependsOn(typeof(JQueryScriptContributor))] public class TimeagoScriptContributor : BundleContributor { + public const string PackageName = "jquery.timeago"; + public override void ConfigureBundle(BundleConfigurationContext context) { context.Files.AddIfNotContains("/libs/timeago/jquery.timeago.js"); @@ -16,17 +18,11 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.Timeago public override void ConfigureDynamicResources(BundleConfigurationContext context) { - var cultureName = CultureInfo.CurrentUICulture.TwoLetterISOLanguageName; - if (cultureName.StartsWith("en")) - { - return; - } - - var cultureFileName = $"/libs/timeago/locales/jquery.timeago.{cultureName}.js"; - - if (context.FileProvider.GetFileInfo(cultureFileName).Exists) + var fileName = context.LocalizationOptions.GetCurrentUICultureLanguageFilesMap(PackageName); + var filePath = $"/libs/timeago/locales/jquery.timeago.{fileName}.js"; + if (context.FileProvider.GetFileInfo(filePath).Exists) { - context.Files.Add(cultureFileName); + context.Files.Add(filePath); } } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Toolbar/LanguageSwitch/LanguageSwitchViewComponent.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Toolbar/LanguageSwitch/LanguageSwitchViewComponent.cs index c6921d3210..3dd1ba2b9d 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Toolbar/LanguageSwitch/LanguageSwitchViewComponent.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Toolbar/LanguageSwitch/LanguageSwitchViewComponent.cs @@ -1,7 +1,9 @@ -using System.Globalization; +using System.Globalization; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.RequestLocalization; +using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Localization; namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Themes.Basic.Components.Toolbar.LanguageSwitch @@ -23,12 +25,32 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Themes.Basic.Components.Toolbar CultureInfo.CurrentUICulture.Name ); + if (currentLanguage == null) + { + var abpRequestLocalizationOptionsProvider = HttpContext.RequestServices.GetRequiredService(); + var localizationOptions = await abpRequestLocalizationOptionsProvider.GetLocalizationOptionsAsync(); + if (localizationOptions.DefaultRequestCulture != null) + { + currentLanguage = new LanguageInfo( + localizationOptions.DefaultRequestCulture.Culture.Name, + localizationOptions.DefaultRequestCulture.UICulture.Name, + localizationOptions.DefaultRequestCulture.UICulture.DisplayName); + } + else + { + currentLanguage = new LanguageInfo( + CultureInfo.CurrentCulture.Name, + CultureInfo.CurrentUICulture.Name, + CultureInfo.CurrentUICulture.DisplayName); + } + } + var model = new LanguageSwitchViewComponentModel { CurrentLanguage = currentLanguage, OtherLanguages = languages.Where(l => l != currentLanguage).ToList() }; - + return View("~/Themes/Basic/Components/Toolbar/LanguageSwitch/Default.cshtml", model); } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Account.cshtml b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Account.cshtml index b4ae34d8fb..5fcd5951cd 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Account.cshtml +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Account.cshtml @@ -39,8 +39,10 @@ @(ViewBag.Title == null ? BrandingProvider.AppName : ViewBag.Title) - - + @if (ViewBag.Description != null) + { + + } @await RenderSectionAsync("styles", false) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.csproj b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.csproj index 8ce2077235..111a6a898a 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.csproj +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.csproj @@ -24,11 +24,11 @@ - + - + - + diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js index 824e376934..9a2052b267 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js @@ -299,6 +299,13 @@ column.targets = i; } + if (!column.render && column.dataFormat) { + var render = datatables.defaultRenderers[column.dataFormat]; + if (render) { + column.render = render; + } + } + if (column.rowAction) { customizeRowActionColumn(column); } @@ -330,18 +337,23 @@ } }; + var ISOStringToDateTimeLocaleString = function (format) { + return function(data) { + var date = luxon + .DateTime + .fromISO(data, { + locale: abp.localization.currentCulture.name + }); + return format ? date.toLocaleString(format) : date.toLocaleString(); + }; + }; + datatables.defaultRenderers['date'] = function (value) { - return luxon - .DateTime - .fromISO(value, { locale: abp.localization.currentCulture.name }) - .toLocaleString(); + return (ISOStringToDateTimeLocaleString())(value); }; datatables.defaultRenderers['datetime'] = function (value) { - return luxon - .DateTime - .fromISO(value, { locale: abp.localization.currentCulture.name }) - .toLocaleString(luxon.DateTime.DATETIME_SHORT); + return (ISOStringToDateTimeLocaleString(luxon.DateTime.DATETIME_SHORT))(value); }; /************************************************************************ diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/jquery/widget-manager.js b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/jquery/widget-manager.js index 0b746b5e37..1dd7717070 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/jquery/widget-manager.js +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/jquery/widget-manager.js @@ -25,11 +25,16 @@ opts.filterForm = $(opts.filterForm); } - var getFilters = function ($widgetWrapperDiv) { + var publicApi = { + init: init, + refresh: refresh + }; + + function getFilters($widget) { var filters = {}; - + if (opts.filterForm) { - opts.filterForm.each(function() { + opts.filterForm.each(function () { filters = $.extend(filters, opts.filterForm.serializeFormToObject()); }); } @@ -38,55 +43,66 @@ filters = $.extend(filters, opts.filterCallback()); } - var widgetApi = $widgetWrapperDiv.data('abp-widget-api'); + var widgetApi = $widget.data('abp-widget-api'); if (widgetApi && widgetApi.getFilters) { filters = $.extend(filters, widgetApi.getFilters()); } return filters; - }; + } - var init = function () { - opts.wrapper.find('.abp-widget-wrapper').each(function () { - var $widgetWrapperDiv = $(this); - var widgetName = $widgetWrapperDiv.attr('data-widget-name'); + function init($widgets) { + if(!$widgets){ + $widgets = opts.wrapper.findWithSelf('.abp-widget-wrapper'); + } + + $widgets.each(function () { + var $widget = $(this); + $widget.data('abp-widget-manager', publicApi); + var widgetName = $widget.attr('data-widget-name'); var widgetApiClass = abp.widgets[widgetName]; if (widgetApiClass) { - var widgetApi = new widgetApiClass($widgetWrapperDiv); - $widgetWrapperDiv.data('abp-widget-api', widgetApi); + var widgetApi = new widgetApiClass($widget); + $widget.data('abp-widget-api', widgetApi); if (widgetApi.init) { - widgetApi.init(getFilters($widgetWrapperDiv)); + widgetApi.init(getFilters($widget)); } } }); - }; + } + + function refresh($widgets) { + if(!$widgets){ + $widgets = opts.wrapper.findWithSelf('.abp-widget-wrapper'); + } - var refresh = function () { - opts.wrapper.find('.abp-widget-wrapper').each(function () { - var $widgetWrapperDiv = $(this); + $widgets.each(function () { + var $widget = $(this); - var refreshUrl = $widgetWrapperDiv.attr('data-refresh-url'); + var refreshUrl = $widget.attr('data-refresh-url'); if (refreshUrl) { abp.ajax({ url: refreshUrl, type: 'GET', dataType: 'html', contentType: 'application/x-www-form-urlencoded; charset=UTF-8', - data: getFilters($widgetWrapperDiv) + data: getFilters($widget) }).then(function (result) { - $widgetWrapperDiv.replaceWith($(result)); + var $result = $(result); + $widget.replaceWith($result); + init($result); }); } else { - var widgetApi = $widgetWrapperDiv.data('abp-widget-api'); + var widgetApi = $widget.data('abp-widget-api'); if (widgetApi && widgetApi.refresh) { - widgetApi.refresh(getFilters($widgetWrapperDiv)); + widgetApi.refresh(getFilters($widget)); } } }); - }; + } if (opts.filterForm) { - opts.filterForm.each(function() { + opts.filterForm.each(function () { $(this).submit(function (e) { e.preventDefault(); refresh(); @@ -94,14 +110,9 @@ }); } - var publicApi = { - init: init, - refresh: refresh - }; - opts.wrapper.data('abp-widget-manager', publicApi); return publicApi; }; -})(jQuery); \ No newline at end of file +})(jQuery); diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo.Abp.AspNetCore.Mvc.csproj b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo.Abp.AspNetCore.Mvc.csproj index 47899625d4..e190116b2f 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo.Abp.AspNetCore.Mvc.csproj +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo.Abp.AspNetCore.Mvc.csproj @@ -26,9 +26,9 @@ - + - + diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs index 1042ee4d08..617650d0e2 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; -using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Abstractions; @@ -13,7 +12,6 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Volo.Abp.Application.Services; -using Volo.Abp.AspNetCore.Mvc.ApiExploring; using Volo.Abp.AspNetCore.Mvc.Conventions; using Volo.Abp.AspNetCore.Mvc.Utils; using Volo.Abp.DependencyInjection; @@ -67,34 +65,51 @@ namespace Volo.Abp.AspNetCore.Mvc private void AddApiDescriptionToModel( ApiDescription apiDescription, - ApplicationApiDescriptionModel applicationModel, + ApplicationApiDescriptionModel applicationModel, ApplicationApiDescriptionModelRequestDto input) { - var controllerType = apiDescription.ActionDescriptor.AsControllerActionDescriptor().ControllerTypeInfo.AsType(); + var controllerType = apiDescription + .ActionDescriptor + .AsControllerActionDescriptor() + .ControllerTypeInfo; + var setting = FindSetting(controllerType); - var moduleModel = applicationModel.GetOrAddModule(GetRootPath(controllerType, setting), GetRemoteServiceName(controllerType, setting)); + var moduleModel = applicationModel.GetOrAddModule( + GetRootPath(controllerType, setting), + GetRemoteServiceName(controllerType, setting) + ); - var controllerModel = moduleModel.GetOrAddController(controllerType.FullName, CalculateControllerName(controllerType, setting), controllerType, _modelOptions.IgnoredInterfaces); + var controllerModel = moduleModel.GetOrAddController( + controllerType.FullName, + CalculateControllerName(controllerType, setting), + controllerType, + _modelOptions.IgnoredInterfaces + ); var method = apiDescription.ActionDescriptor.GetMethodInfo(); var uniqueMethodName = GetUniqueActionName(method); if (controllerModel.Actions.ContainsKey(uniqueMethodName)) { - Logger.LogWarning($"Controller '{controllerModel.ControllerName}' contains more than one action with name '{uniqueMethodName}' for module '{moduleModel.RootPath}'. Ignored: " + method); + Logger.LogWarning( + $"Controller '{controllerModel.ControllerName}' contains more than one action with name '{uniqueMethodName}' for module '{moduleModel.RootPath}'. Ignored: " + + method); return; } Logger.LogDebug($"ActionApiDescriptionModel.Create: {controllerModel.ControllerName}.{uniqueMethodName}"); - var actionModel = controllerModel.AddAction(uniqueMethodName, ActionApiDescriptionModel.Create( + var actionModel = controllerModel.AddAction( uniqueMethodName, - method, - apiDescription.RelativePath, - apiDescription.HttpMethod, - GetSupportedVersions(controllerType, method, setting) - )); + ActionApiDescriptionModel.Create( + uniqueMethodName, + method, + apiDescription.RelativePath, + apiDescription.HttpMethod, + GetSupportedVersions(controllerType, method, setting) + ) + ); if (input.IncludeTypes) { @@ -106,11 +121,14 @@ namespace Volo.Abp.AspNetCore.Mvc private static string CalculateControllerName(Type controllerType, ConventionalControllerSetting setting) { - var controllerName = controllerType.Name.RemovePostFix("Controller").RemovePostFix(ApplicationService.CommonPostfixes); + var controllerName = controllerType.Name.RemovePostFix("Controller") + .RemovePostFix(ApplicationService.CommonPostfixes); if (setting?.UrlControllerNameNormalizer != null) { - controllerName = setting.UrlControllerNameNormalizer(new UrlControllerNameNormalizerContext(setting.RootPath, controllerName)); + controllerName = + setting.UrlControllerNameNormalizer( + new UrlControllerNameNormalizerContext(setting.RootPath, controllerName)); } return controllerName; @@ -139,7 +157,8 @@ namespace Volo.Abp.AspNetCore.Mvc return methodNameBuilder.ToString(); } - private static List GetSupportedVersions(Type controllerType, MethodInfo method, ConventionalControllerSetting setting) + private static List GetSupportedVersions(Type controllerType, MethodInfo method, + ConventionalControllerSetting setting) { var supportedVersions = new List(); @@ -172,17 +191,23 @@ namespace Volo.Abp.AspNetCore.Mvc AddCustomTypesToModel(applicationModel, method.ReturnType); } - private static void AddCustomTypesToModel(ApplicationApiDescriptionModel applicationModel, [CanBeNull] Type type) + private static void AddCustomTypesToModel(ApplicationApiDescriptionModel applicationModel, + [CanBeNull] Type type) { if (type == null) { return; } + if (type.IsGenericParameter) + { + return; + } + type = AsyncHelper.UnwrapTask(type); if (type == typeof(object) || - type == typeof(void) || + type == typeof(void) || type == typeof(Enum) || type == typeof(ValueType) || TypeHelper.IsPrimitiveExtended(type)) @@ -190,41 +215,67 @@ namespace Volo.Abp.AspNetCore.Mvc return; } - if (TypeHelper.IsEnumerable(type, out var itemType)) + if (TypeHelper.IsDictionary(type, out var keyType, out var valueType)) { - AddCustomTypesToModel(applicationModel, itemType); + AddCustomTypesToModel(applicationModel, keyType); + AddCustomTypesToModel(applicationModel, valueType); return; } - if (TypeHelper.IsDictionary(type, out var keyType, out var valueType)) + if (TypeHelper.IsEnumerable(type, out var itemType)) { - AddCustomTypesToModel(applicationModel, keyType); - AddCustomTypesToModel(applicationModel, valueType); + AddCustomTypesToModel(applicationModel, itemType); return; } - /* TODO: Add interfaces - */ + if (type.IsGenericType && !type.IsGenericTypeDefinition) + { + var genericTypeDefinition = type.GetGenericTypeDefinition(); + + AddCustomTypesToModel(applicationModel, genericTypeDefinition); + + foreach (var genericArgument in type.GetGenericArguments()) + { + AddCustomTypesToModel(applicationModel, genericArgument); + } - var typeName = TypeHelper.GetFullNameHandlingNullableAndGenerics(type); + return; + } + var typeName = CalculateTypeName(type); if (applicationModel.Types.ContainsKey(typeName)) { return; } - - var typeModel = TypeApiDescriptionModel.Create(type); - applicationModel.Types[typeName] = typeModel; + + applicationModel.Types[typeName] = TypeApiDescriptionModel.Create(type); AddCustomTypesToModel(applicationModel, type.BaseType); - foreach (var propertyInfo in type.GetProperties()) + foreach (var propertyInfo in type.GetProperties().Where(p => p.DeclaringType == type)) { AddCustomTypesToModel(applicationModel, propertyInfo.PropertyType); } } - private void AddParameterDescriptionsToModel(ActionApiDescriptionModel actionModel, MethodInfo method, ApiDescription apiDescription) + private static string CalculateTypeName(Type type) + { + if (!type.IsGenericTypeDefinition) + { + return TypeHelper.GetFullNameHandlingNullableAndGenerics(type); + } + + var i = 0; + var argumentList = type + .GetGenericArguments() + .Select(_ => "T" + i++) + .JoinAsString(","); + + return $"{type.FullName.Left(type.FullName.IndexOf('`'))}<{argumentList}>"; + } + + private void AddParameterDescriptionsToModel(ActionApiDescriptionModel actionModel, MethodInfo method, + ApiDescription apiDescription) { if (!apiDescription.ParameterDescriptions.Any()) { @@ -240,8 +291,8 @@ namespace Volo.Abp.AspNetCore.Mvc { var parameterDescription = apiDescription.ParameterDescriptions[i]; var matchedMethodParamName = matchedMethodParamNames.Length > i - ? matchedMethodParamNames[i] - : parameterDescription.Name; + ? matchedMethodParamNames[i] + : parameterDescription.Name; actionModel.AddParameter(ParameterApiDescriptionModel.Create( parameterDescription.Name, @@ -270,10 +321,11 @@ namespace Volo.Abp.AspNetCore.Mvc return parameterInfo.Name; } - return modelNameProvider.Name; + return modelNameProvider.Name ?? parameterInfo.Name; } - private static string GetRootPath([NotNull] Type controllerType, [CanBeNull] ConventionalControllerSetting setting) + private static string GetRootPath([NotNull] Type controllerType, + [CanBeNull] ConventionalControllerSetting setting) { if (setting != null) { @@ -296,7 +348,8 @@ namespace Volo.Abp.AspNetCore.Mvc return setting.RemoteServiceName; } - var remoteServiceAttr = controllerType.GetCustomAttributes().OfType().FirstOrDefault(); + var remoteServiceAttr = + controllerType.GetCustomAttributes().OfType().FirstOrDefault(); if (remoteServiceAttr?.Name != null) { return remoteServiceAttr.Name; diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionFilter.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionFilter.cs index 1ce177ba44..009d52b42f 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionFilter.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionFilter.cs @@ -1,4 +1,5 @@ using System; +using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -25,7 +26,7 @@ namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling public AbpExceptionFilter( IExceptionToErrorInfoConverter errorInfoConverter, - IHttpExceptionStatusCodeFinder statusCodeFinder, + IHttpExceptionStatusCodeFinder statusCodeFinder, IJsonSerializer jsonSerializer) { _errorInfoConverter = errorInfoConverter; @@ -54,7 +55,7 @@ namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling { return true; } - + if (context.HttpContext.Request.CanAccept(MimeTypes.Application.Json)) { return true; @@ -81,8 +82,11 @@ namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling var logLevel = context.Exception.GetLogLevel(); - Logger.LogWithLevel(logLevel, $"---------- {nameof(RemoteServiceErrorInfo)} ----------"); - Logger.LogWithLevel(logLevel, _jsonSerializer.Serialize(remoteServiceErrorInfo, indented: true)); + var remoteServiceErrorInfoBuilder = new StringBuilder(); + remoteServiceErrorInfoBuilder.AppendLine($"---------- {nameof(RemoteServiceErrorInfo)} ----------"); + remoteServiceErrorInfoBuilder.AppendLine( _jsonSerializer.Serialize(remoteServiceErrorInfo, indented: true)); + Logger.LogWithLevel(logLevel, remoteServiceErrorInfoBuilder.ToString()); + Logger.LogException(context.Exception, logLevel); await context.HttpContext diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionPageFilter.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionPageFilter.cs index 9e449f0474..8e2356ff20 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionPageFilter.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionPageFilter.cs @@ -1,4 +1,5 @@ using System; +using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -93,8 +94,11 @@ namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling var logLevel = context.Exception.GetLogLevel(); - Logger.LogWithLevel(logLevel, $"---------- {nameof(RemoteServiceErrorInfo)} ----------"); - Logger.LogWithLevel(logLevel, _jsonSerializer.Serialize(remoteServiceErrorInfo, indented: true)); + var remoteServiceErrorInfoBuilder = new StringBuilder(); + remoteServiceErrorInfoBuilder.AppendLine($"---------- {nameof(RemoteServiceErrorInfo)} ----------"); + remoteServiceErrorInfoBuilder.AppendLine( _jsonSerializer.Serialize(remoteServiceErrorInfo, indented: true)); + Logger.LogWithLevel(logLevel, remoteServiceErrorInfoBuilder.ToString()); + Logger.LogException(context.Exception, logLevel); await context.HttpContext diff --git a/framework/src/Volo.Abp.AspNetCore.TestBase/Volo.Abp.AspNetCore.TestBase.csproj b/framework/src/Volo.Abp.AspNetCore.TestBase/Volo.Abp.AspNetCore.TestBase.csproj index a4dcd1c8c5..ce02e1f0a0 100644 --- a/framework/src/Volo.Abp.AspNetCore.TestBase/Volo.Abp.AspNetCore.TestBase.csproj +++ b/framework/src/Volo.Abp.AspNetCore.TestBase/Volo.Abp.AspNetCore.TestBase.csproj @@ -24,7 +24,7 @@ - + diff --git a/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/VirtualFileSystemApplicationBuilderExtensions.cs b/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/VirtualFileSystemApplicationBuilderExtensions.cs index 0557c37380..da9f65059f 100644 --- a/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/VirtualFileSystemApplicationBuilderExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/VirtualFileSystemApplicationBuilderExtensions.cs @@ -1,18 +1,22 @@ -using Microsoft.Extensions.DependencyInjection; +using System; +using Microsoft.Extensions.DependencyInjection; using Volo.Abp.AspNetCore.VirtualFileSystem; namespace Microsoft.AspNetCore.Builder { public static class VirtualFileSystemApplicationBuilderExtensions { - public static IApplicationBuilder UseVirtualFiles(this IApplicationBuilder app) + public static IApplicationBuilder UseVirtualFiles(this IApplicationBuilder app, Action configure = null) { - return app.UseStaticFiles( - new StaticFileOptions - { - FileProvider = app.ApplicationServices.GetRequiredService() - } - ); + var staticFileOptions = new StaticFileOptions + { + FileProvider = app.ApplicationServices.GetRequiredService(), + ContentTypeProvider = app.ApplicationServices.GetRequiredService() + }; + + configure?.Invoke(staticFileOptions); + + return app.UseStaticFiles(staticFileOptions); } } } diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AspNetCoreAuditLogContributor.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AspNetCoreAuditLogContributor.cs index 378b92a14a..04238a9a45 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AspNetCoreAuditLogContributor.cs +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AspNetCoreAuditLogContributor.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; +using Volo.Abp.AspNetCore.WebClientInfo; using Volo.Abp.Auditing; using Volo.Abp.DependencyInjection; @@ -35,14 +36,15 @@ namespace Volo.Abp.AspNetCore.Auditing context.AuditInfo.Url = BuildUrl(httpContext); } + var clientInfoProvider = context.ServiceProvider.GetRequiredService(); if (context.AuditInfo.ClientIpAddress == null) { - context.AuditInfo.ClientIpAddress = GetClientIpAddress(httpContext); + context.AuditInfo.ClientIpAddress = clientInfoProvider.ClientIpAddress; } if (context.AuditInfo.BrowserInfo == null) { - context.AuditInfo.BrowserInfo = GetBrowserInfo(httpContext); + context.AuditInfo.BrowserInfo = clientInfoProvider.BrowserInfo; } //TODO: context.AuditInfo.ClientName @@ -62,24 +64,6 @@ namespace Volo.Abp.AspNetCore.Auditing } } - protected virtual string GetBrowserInfo(HttpContext httpContext) - { - return httpContext.Request?.Headers?["User-Agent"]; - } - - protected virtual string GetClientIpAddress(HttpContext httpContext) - { - try - { - return httpContext.Connection?.RemoteIpAddress?.ToString(); - } - catch (Exception ex) - { - Logger.LogException(ex, LogLevel.Warning); - return null; - } - } - protected virtual string BuildUrl(HttpContext httpContext) { //TODO: Add options to include/exclude query, schema and host diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/SecurityLog/AspNetCoreSecurityLogManager.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/SecurityLog/AspNetCoreSecurityLogManager.cs new file mode 100644 index 0000000000..f0fc0aa347 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/SecurityLog/AspNetCoreSecurityLogManager.cs @@ -0,0 +1,73 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Volo.Abp.AspNetCore.WebClientInfo; +using Volo.Abp.Clients; +using Volo.Abp.DependencyInjection; +using Volo.Abp.MultiTenancy; +using Volo.Abp.SecurityLog; +using Volo.Abp.Timing; +using Volo.Abp.Tracing; +using Volo.Abp.Users; + +namespace Volo.Abp.AspNetCore.SecurityLog +{ + [Dependency(ReplaceServices = true)] + public class AspNetCoreSecurityLogManager : DefaultSecurityLogManager + { + protected ILogger Logger { get; } + protected IClock Clock { get; } + protected ICurrentUser CurrentUser { get; } + protected ICurrentTenant CurrentTenant { get; } + protected ICurrentClient CurrentClient { get; } + protected IHttpContextAccessor HttpContextAccessor { get; } + protected ICorrelationIdProvider CorrelationIdProvider { get; } + protected IWebClientInfoProvider WebClientInfoProvider { get; } + + public AspNetCoreSecurityLogManager( + IOptions securityLogOptions, + ISecurityLogStore securityLogStore, + ILogger logger, + IClock clock, + ICurrentUser currentUser, + ICurrentTenant currentTenant, + ICurrentClient currentClient, + IHttpContextAccessor httpContextAccessor, + ICorrelationIdProvider correlationIdProvider, + IWebClientInfoProvider webClientInfoProvider) + : base(securityLogOptions, securityLogStore) + { + Logger = logger; + Clock = clock; + CurrentUser = currentUser; + CurrentTenant = currentTenant; + CurrentClient = currentClient; + HttpContextAccessor = httpContextAccessor; + CorrelationIdProvider = correlationIdProvider; + WebClientInfoProvider = webClientInfoProvider; + } + + protected override async Task CreateAsync() + { + var securityLogInfo = await base.CreateAsync(); + + securityLogInfo.CreationTime = Clock.Now; + + securityLogInfo.TenantId = CurrentTenant.Id; + securityLogInfo.TenantName = CurrentTenant.Name; + + securityLogInfo.UserId = CurrentUser.Id; + securityLogInfo.UserName = CurrentUser.UserName; + + securityLogInfo.ClientId = CurrentClient.Id; + + securityLogInfo.CorrelationId = CorrelationIdProvider.Get(); + + securityLogInfo.ClientIpAddress = WebClientInfoProvider.ClientIpAddress; + securityLogInfo.BrowserInfo = WebClientInfoProvider.BrowserInfo; + + return securityLogInfo; + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/VirtualFileSystem/AbpAspNetCoreContentOptions.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/VirtualFileSystem/AbpAspNetCoreContentOptions.cs index ce6e454f54..f4b7d631cc 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/VirtualFileSystem/AbpAspNetCoreContentOptions.cs +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/VirtualFileSystem/AbpAspNetCoreContentOptions.cs @@ -1,14 +1,401 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Linq; namespace Volo.Abp.AspNetCore.VirtualFileSystem { public class AbpAspNetCoreContentOptions { + public Dictionary ContentTypeMaps { get; } + public List AllowedExtraWebContentFolders { get; } + public List AllowedExtraWebContentFileExtensions { get; } public AbpAspNetCoreContentOptions() { + ContentTypeMaps = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + {".323", "text/h323"}, + {".3g2", "video/3gpp2"}, + {".3gp2", "video/3gpp2"}, + {".3gp", "video/3gpp"}, + {".3gpp", "video/3gpp"}, + {".aac", "audio/aac"}, + {".aaf", "application/octet-stream"}, + {".aca", "application/octet-stream"}, + {".accdb", "application/msaccess"}, + {".accde", "application/msaccess"}, + {".accdt", "application/msaccess"}, + {".acx", "application/internet-property-stream"}, + {".adt", "audio/vnd.dlna.adts"}, + {".adts", "audio/vnd.dlna.adts"}, + {".afm", "application/octet-stream"}, + {".ai", "application/postscript"}, + {".aif", "audio/x-aiff"}, + {".aifc", "audio/aiff"}, + {".aiff", "audio/aiff"}, + {".appcache", "text/cache-manifest"}, + {".application", "application/x-ms-application"}, + {".art", "image/x-jg"}, + {".asd", "application/octet-stream"}, + {".asf", "video/x-ms-asf"}, + {".asi", "application/octet-stream"}, + {".asm", "text/plain"}, + {".asr", "video/x-ms-asf"}, + {".asx", "video/x-ms-asf"}, + {".atom", "application/atom+xml"}, + {".au", "audio/basic"}, + {".avi", "video/x-msvideo"}, + {".axs", "application/olescript"}, + {".bas", "text/plain"}, + {".bcpio", "application/x-bcpio"}, + {".bin", "application/octet-stream"}, + {".bmp", "image/bmp"}, + {".c", "text/plain"}, + {".cab", "application/vnd.ms-cab-compressed"}, + {".calx", "application/vnd.ms-office.calx"}, + {".cat", "application/vnd.ms-pki.seccat"}, + {".cdf", "application/x-cdf"}, + {".chm", "application/octet-stream"}, + {".class", "application/x-java-applet"}, + {".clp", "application/x-msclip"}, + {".cmx", "image/x-cmx"}, + {".cnf", "text/plain"}, + {".cod", "image/cis-cod"}, + {".cpio", "application/x-cpio"}, + {".cpp", "text/plain"}, + {".crd", "application/x-mscardfile"}, + {".crl", "application/pkix-crl"}, + {".crt", "application/x-x509-ca-cert"}, + {".csh", "application/x-csh"}, + {".css", "text/css"}, + {".csv", "application/octet-stream"}, + {".cur", "application/octet-stream"}, + {".dcr", "application/x-director"}, + {".deploy", "application/octet-stream"}, + {".der", "application/x-x509-ca-cert"}, + {".dib", "image/bmp"}, + {".dir", "application/x-director"}, + {".disco", "text/xml"}, + {".dlm", "text/dlm"}, + {".doc", "application/msword"}, + {".docm", "application/vnd.ms-word.document.macroEnabled.12"}, + {".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"}, + {".dot", "application/msword"}, + {".dotm", "application/vnd.ms-word.template.macroEnabled.12"}, + {".dotx", "application/vnd.openxmlformats-officedocument.wordprocessingml.template"}, + {".dsp", "application/octet-stream"}, + {".dtd", "text/xml"}, + {".dvi", "application/x-dvi"}, + {".dvr-ms", "video/x-ms-dvr"}, + {".dwf", "drawing/x-dwf"}, + {".dwp", "application/octet-stream"}, + {".dxr", "application/x-director"}, + {".eml", "message/rfc822"}, + {".emz", "application/octet-stream"}, + {".eot", "application/vnd.ms-fontobject"}, + {".eps", "application/postscript"}, + {".etx", "text/x-setext"}, + {".evy", "application/envoy"}, + {".fdf", "application/vnd.fdf"}, + {".fif", "application/fractals"}, + {".fla", "application/octet-stream"}, + {".flr", "x-world/x-vrml"}, + {".flv", "video/x-flv"}, + {".gif", "image/gif"}, + {".gtar", "application/x-gtar"}, + {".gz", "application/x-gzip"}, + {".h", "text/plain"}, + {".hdf", "application/x-hdf"}, + {".hdml", "text/x-hdml"}, + {".hhc", "application/x-oleobject"}, + {".hhk", "application/octet-stream"}, + {".hhp", "application/octet-stream"}, + {".hlp", "application/winhlp"}, + {".hqx", "application/mac-binhex40"}, + {".hta", "application/hta"}, + {".htc", "text/x-component"}, + {".htm", "text/html"}, + {".html", "text/html"}, + {".htt", "text/webviewhtml"}, + {".hxt", "text/html"}, + {".ical", "text/calendar"}, + {".icalendar", "text/calendar"}, + {".ico", "image/x-icon"}, + {".ics", "text/calendar"}, + {".ief", "image/ief"}, + {".ifb", "text/calendar"}, + {".iii", "application/x-iphone"}, + {".inf", "application/octet-stream"}, + {".ins", "application/x-internet-signup"}, + {".isp", "application/x-internet-signup"}, + {".IVF", "video/x-ivf"}, + {".jar", "application/java-archive"}, + {".java", "application/octet-stream"}, + {".jck", "application/liquidmotion"}, + {".jcz", "application/liquidmotion"}, + {".jfif", "image/pjpeg"}, + {".jpb", "application/octet-stream"}, + {".jpe", "image/jpeg"}, + {".jpeg", "image/jpeg"}, + {".jpg", "image/jpeg"}, + {".js", "application/javascript"}, + {".json", "application/json"}, + {".jsx", "text/jscript"}, + {".latex", "application/x-latex"}, + {".lit", "application/x-ms-reader"}, + {".lpk", "application/octet-stream"}, + {".lsf", "video/x-la-asf"}, + {".lsx", "video/x-la-asf"}, + {".lzh", "application/octet-stream"}, + {".m13", "application/x-msmediaview"}, + {".m14", "application/x-msmediaview"}, + {".m1v", "video/mpeg"}, + {".m2ts", "video/vnd.dlna.mpeg-tts"}, + {".m3u", "audio/x-mpegurl"}, + {".m4a", "audio/mp4"}, + {".m4v", "video/mp4"}, + {".man", "application/x-troff-man"}, + {".manifest", "application/x-ms-manifest"}, + {".map", "text/plain"}, + {".markdown", "text/markdown"}, + {".md", "text/markdown"}, + {".mdb", "application/x-msaccess"}, + {".mdp", "application/octet-stream"}, + {".me", "application/x-troff-me"}, + {".mht", "message/rfc822"}, + {".mhtml", "message/rfc822"}, + {".mid", "audio/mid"}, + {".midi", "audio/mid"}, + {".mix", "application/octet-stream"}, + {".mmf", "application/x-smaf"}, + {".mno", "text/xml"}, + {".mny", "application/x-msmoney"}, + {".mov", "video/quicktime"}, + {".movie", "video/x-sgi-movie"}, + {".mp2", "video/mpeg"}, + {".mp3", "audio/mpeg"}, + {".mp4", "video/mp4"}, + {".mp4v", "video/mp4"}, + {".mpa", "video/mpeg"}, + {".mpe", "video/mpeg"}, + {".mpeg", "video/mpeg"}, + {".mpg", "video/mpeg"}, + {".mpp", "application/vnd.ms-project"}, + {".mpv2", "video/mpeg"}, + {".ms", "application/x-troff-ms"}, + {".msi", "application/octet-stream"}, + {".mso", "application/octet-stream"}, + {".mvb", "application/x-msmediaview"}, + {".mvc", "application/x-miva-compiled"}, + {".nc", "application/x-netcdf"}, + {".nsc", "video/x-ms-asf"}, + {".nws", "message/rfc822"}, + {".ocx", "application/octet-stream"}, + {".oda", "application/oda"}, + {".odc", "text/x-ms-odc"}, + {".ods", "application/oleobject"}, + {".oga", "audio/ogg"}, + {".ogg", "video/ogg"}, + {".ogv", "video/ogg"}, + {".ogx", "application/ogg"}, + {".one", "application/onenote"}, + {".onea", "application/onenote"}, + {".onetoc", "application/onenote"}, + {".onetoc2", "application/onenote"}, + {".onetmp", "application/onenote"}, + {".onepkg", "application/onenote"}, + {".osdx", "application/opensearchdescription+xml"}, + {".otf", "font/otf"}, + {".p10", "application/pkcs10"}, + {".p12", "application/x-pkcs12"}, + {".p7b", "application/x-pkcs7-certificates"}, + {".p7c", "application/pkcs7-mime"}, + {".p7m", "application/pkcs7-mime"}, + {".p7r", "application/x-pkcs7-certreqresp"}, + {".p7s", "application/pkcs7-signature"}, + {".pbm", "image/x-portable-bitmap"}, + {".pcx", "application/octet-stream"}, + {".pcz", "application/octet-stream"}, + {".pdf", "application/pdf"}, + {".pfb", "application/octet-stream"}, + {".pfm", "application/octet-stream"}, + {".pfx", "application/x-pkcs12"}, + {".pgm", "image/x-portable-graymap"}, + {".pko", "application/vnd.ms-pki.pko"}, + {".pma", "application/x-perfmon"}, + {".pmc", "application/x-perfmon"}, + {".pml", "application/x-perfmon"}, + {".pmr", "application/x-perfmon"}, + {".pmw", "application/x-perfmon"}, + {".png", "image/png"}, + {".pnm", "image/x-portable-anymap"}, + {".pnz", "image/png"}, + {".pot", "application/vnd.ms-powerpoint"}, + {".potm", "application/vnd.ms-powerpoint.template.macroEnabled.12"}, + {".potx", "application/vnd.openxmlformats-officedocument.presentationml.template"}, + {".ppam", "application/vnd.ms-powerpoint.addin.macroEnabled.12"}, + {".ppm", "image/x-portable-pixmap"}, + {".pps", "application/vnd.ms-powerpoint"}, + {".ppsm", "application/vnd.ms-powerpoint.slideshow.macroEnabled.12"}, + {".ppsx", "application/vnd.openxmlformats-officedocument.presentationml.slideshow"}, + {".ppt", "application/vnd.ms-powerpoint"}, + {".pptm", "application/vnd.ms-powerpoint.presentation.macroEnabled.12"}, + {".pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"}, + {".prf", "application/pics-rules"}, + {".prm", "application/octet-stream"}, + {".prx", "application/octet-stream"}, + {".ps", "application/postscript"}, + {".psd", "application/octet-stream"}, + {".psm", "application/octet-stream"}, + {".psp", "application/octet-stream"}, + {".pub", "application/x-mspublisher"}, + {".qt", "video/quicktime"}, + {".qtl", "application/x-quicktimeplayer"}, + {".qxd", "application/octet-stream"}, + {".ra", "audio/x-pn-realaudio"}, + {".ram", "audio/x-pn-realaudio"}, + {".rar", "application/octet-stream"}, + {".ras", "image/x-cmu-raster"}, + {".rf", "image/vnd.rn-realflash"}, + {".rgb", "image/x-rgb"}, + {".rm", "application/vnd.rn-realmedia"}, + {".rmi", "audio/mid"}, + {".roff", "application/x-troff"}, + {".rpm", "audio/x-pn-realaudio-plugin"}, + {".rtf", "application/rtf"}, + {".rtx", "text/richtext"}, + {".scd", "application/x-msschedule"}, + {".sct", "text/scriptlet"}, + {".sea", "application/octet-stream"}, + {".setpay", "application/set-payment-initiation"}, + {".setreg", "application/set-registration-initiation"}, + {".sgml", "text/sgml"}, + {".sh", "application/x-sh"}, + {".shar", "application/x-shar"}, + {".sit", "application/x-stuffit"}, + {".sldm", "application/vnd.ms-powerpoint.slide.macroEnabled.12"}, + {".sldx", "application/vnd.openxmlformats-officedocument.presentationml.slide"}, + {".smd", "audio/x-smd"}, + {".smi", "application/octet-stream"}, + {".smx", "audio/x-smd"}, + {".smz", "audio/x-smd"}, + {".snd", "audio/basic"}, + {".snp", "application/octet-stream"}, + {".spc", "application/x-pkcs7-certificates"}, + {".spl", "application/futuresplash"}, + {".spx", "audio/ogg"}, + {".src", "application/x-wais-source"}, + {".ssm", "application/streamingmedia"}, + {".sst", "application/vnd.ms-pki.certstore"}, + {".stl", "application/vnd.ms-pki.stl"}, + {".sv4cpio", "application/x-sv4cpio"}, + {".sv4crc", "application/x-sv4crc"}, + {".svg", "image/svg+xml"}, + {".svgz", "image/svg+xml"}, + {".swf", "application/x-shockwave-flash"}, + {".t", "application/x-troff"}, + {".tar", "application/x-tar"}, + {".tcl", "application/x-tcl"}, + {".tex", "application/x-tex"}, + {".texi", "application/x-texinfo"}, + {".texinfo", "application/x-texinfo"}, + {".tgz", "application/x-compressed"}, + {".thmx", "application/vnd.ms-officetheme"}, + {".thn", "application/octet-stream"}, + {".tif", "image/tiff"}, + {".tiff", "image/tiff"}, + {".toc", "application/octet-stream"}, + {".tr", "application/x-troff"}, + {".trm", "application/x-msterminal"}, + {".ts", "video/vnd.dlna.mpeg-tts"}, + {".tsv", "text/tab-separated-values"}, + {".ttc", "application/x-font-ttf"}, + {".ttf", "application/x-font-ttf"}, + {".tts", "video/vnd.dlna.mpeg-tts"}, + {".txt", "text/plain"}, + {".u32", "application/octet-stream"}, + {".uls", "text/iuls"}, + {".ustar", "application/x-ustar"}, + {".vbs", "text/vbscript"}, + {".vcf", "text/x-vcard"}, + {".vcs", "text/plain"}, + {".vdx", "application/vnd.ms-visio.viewer"}, + {".vml", "text/xml"}, + {".vsd", "application/vnd.visio"}, + {".vss", "application/vnd.visio"}, + {".vst", "application/vnd.visio"}, + {".vsto", "application/x-ms-vsto"}, + {".vsw", "application/vnd.visio"}, + {".vsx", "application/vnd.visio"}, + {".vtx", "application/vnd.visio"}, + {".wasm", "application/wasm"}, + {".wav", "audio/wav"}, + {".wax", "audio/x-ms-wax"}, + {".wbmp", "image/vnd.wap.wbmp"}, + {".wcm", "application/vnd.ms-works"}, + {".wdb", "application/vnd.ms-works"}, + {".webm", "video/webm"}, + {".webp", "image/webp"}, + {".wks", "application/vnd.ms-works"}, + {".wm", "video/x-ms-wm"}, + {".wma", "audio/x-ms-wma"}, + {".wmd", "application/x-ms-wmd"}, + {".wmf", "application/x-msmetafile"}, + {".wml", "text/vnd.wap.wml"}, + {".wmlc", "application/vnd.wap.wmlc"}, + {".wmls", "text/vnd.wap.wmlscript"}, + {".wmlsc", "application/vnd.wap.wmlscriptc"}, + {".wmp", "video/x-ms-wmp"}, + {".wmv", "video/x-ms-wmv"}, + {".wmx", "video/x-ms-wmx"}, + {".wmz", "application/x-ms-wmz"}, + {".woff", "application/font-woff"}, // https://www.w3.org/TR/WOFF/#appendix-b + {".woff2", "font/woff2"}, // https://www.w3.org/TR/WOFF2/#IMT + {".wps", "application/vnd.ms-works"}, + {".wri", "application/x-mswrite"}, + {".wrl", "x-world/x-vrml"}, + {".wrz", "x-world/x-vrml"}, + {".wsdl", "text/xml"}, + {".wtv", "video/x-ms-wtv"}, + {".wvx", "video/x-ms-wvx"}, + {".x", "application/directx"}, + {".xaf", "x-world/x-vrml"}, + {".xaml", "application/xaml+xml"}, + {".xap", "application/x-silverlight-app"}, + {".xbap", "application/x-ms-xbap"}, + {".xbm", "image/x-xbitmap"}, + {".xdr", "text/plain"}, + {".xht", "application/xhtml+xml"}, + {".xhtml", "application/xhtml+xml"}, + {".xla", "application/vnd.ms-excel"}, + {".xlam", "application/vnd.ms-excel.addin.macroEnabled.12"}, + {".xlc", "application/vnd.ms-excel"}, + {".xlm", "application/vnd.ms-excel"}, + {".xls", "application/vnd.ms-excel"}, + {".xlsb", "application/vnd.ms-excel.sheet.binary.macroEnabled.12"}, + {".xlsm", "application/vnd.ms-excel.sheet.macroEnabled.12"}, + {".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}, + {".xlt", "application/vnd.ms-excel"}, + {".xltm", "application/vnd.ms-excel.template.macroEnabled.12"}, + {".xltx", "application/vnd.openxmlformats-officedocument.spreadsheetml.template"}, + {".xlw", "application/vnd.ms-excel"}, + {".xml", "text/xml"}, + {".xof", "x-world/x-vrml"}, + {".xpm", "image/x-xpixmap"}, + {".xps", "application/vnd.ms-xpsdocument"}, + {".xsd", "text/xml"}, + {".xsf", "text/xml"}, + {".xsl", "text/xml"}, + {".xslt", "text/xml"}, + {".xsn", "application/octet-stream"}, + {".xtp", "application/octet-stream"}, + {".xwd", "image/x-xwindowdump"}, + {".z", "application/x-compress"}, + {".zip", "application/x-zip-compressed"}, + }; + AllowedExtraWebContentFolders = new List { "/Pages", @@ -16,18 +403,7 @@ namespace Volo.Abp.AspNetCore.VirtualFileSystem "/Themes" }; - AllowedExtraWebContentFileExtensions = new List - { - ".js", - ".css", - ".png", - ".jpg", - ".jpeg", - ".woff", - ".woff2", - ".tff", - ".otf" - }; + AllowedExtraWebContentFileExtensions = ContentTypeMaps.Select(x => x.Key).ToList(); } } } diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/VirtualFileSystem/AbpFileExtensionContentTypeProvider.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/VirtualFileSystem/AbpFileExtensionContentTypeProvider.cs new file mode 100644 index 0000000000..82ca1c5539 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/VirtualFileSystem/AbpFileExtensionContentTypeProvider.cs @@ -0,0 +1,39 @@ +using Microsoft.AspNetCore.StaticFiles; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.AspNetCore.VirtualFileSystem +{ + public class AbpFileExtensionContentTypeProvider : IContentTypeProvider, ITransientDependency + { + protected AbpAspNetCoreContentOptions Options { get; } + + public AbpFileExtensionContentTypeProvider(IOptions abpAspNetCoreContentOptions) + { + Options = abpAspNetCoreContentOptions.Value; + } + + public bool TryGetContentType(string subpath, out string contentType) + { + var extension = GetExtension(subpath); + if (extension == null) + { + contentType = null; + return false; + } + + return Options.ContentTypeMaps.TryGetValue(extension, out contentType); + } + + protected virtual string GetExtension(string path) + { + if (string.IsNullOrWhiteSpace(path)) + { + return null; + } + + var index = path.LastIndexOf('.'); + return index < 0 ? null : path.Substring(index); + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/WebClientInfo/HttpContextWebClientInfoProvider.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/WebClientInfo/HttpContextWebClientInfoProvider.cs new file mode 100644 index 0000000000..503771c938 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/WebClientInfo/HttpContextWebClientInfoProvider.cs @@ -0,0 +1,43 @@ +using System; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.AspNetCore.WebClientInfo +{ + public class HttpContextWebClientInfoProvider : IWebClientInfoProvider, ITransientDependency + { + protected ILogger Logger { get; } + protected IHttpContextAccessor HttpContextAccessor { get; } + + public HttpContextWebClientInfoProvider( + ILogger logger, + IHttpContextAccessor httpContextAccessor) + { + Logger = logger; + HttpContextAccessor = httpContextAccessor; + } + + public string BrowserInfo => GetBrowserInfo(); + + public string ClientIpAddress => GetClientIpAddress(); + + protected virtual string GetBrowserInfo() + { + return HttpContextAccessor.HttpContext?.Request?.Headers?["User-Agent"]; + } + + protected virtual string GetClientIpAddress() + { + try + { + return HttpContextAccessor.HttpContext?.Connection?.RemoteIpAddress?.ToString(); + } + catch (Exception ex) + { + Logger.LogException(ex, LogLevel.Warning); + return null; + } + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/WebClientInfo/IWebClientInfoProvider.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/WebClientInfo/IWebClientInfoProvider.cs new file mode 100644 index 0000000000..3a15ac2f93 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/WebClientInfo/IWebClientInfoProvider.cs @@ -0,0 +1,9 @@ +namespace Volo.Abp.AspNetCore.WebClientInfo +{ + public interface IWebClientInfoProvider + { + string BrowserInfo { get; } + + string ClientIpAddress { get; } + } +} diff --git a/framework/src/Volo.Abp.Authorization/Volo.Abp.Authorization.csproj b/framework/src/Volo.Abp.Authorization/Volo.Abp.Authorization.csproj index b8ff62572b..a7e76714c2 100644 --- a/framework/src/Volo.Abp.Authorization/Volo.Abp.Authorization.csproj +++ b/framework/src/Volo.Abp.Authorization/Volo.Abp.Authorization.csproj @@ -15,7 +15,7 @@ - + diff --git a/framework/src/Volo.Abp.AutoMapper/Volo.Abp.AutoMapper.csproj b/framework/src/Volo.Abp.AutoMapper/Volo.Abp.AutoMapper.csproj index 2fb39be54f..bd5cffe848 100644 --- a/framework/src/Volo.Abp.AutoMapper/Volo.Abp.AutoMapper.csproj +++ b/framework/src/Volo.Abp.AutoMapper/Volo.Abp.AutoMapper.csproj @@ -21,7 +21,7 @@ - + diff --git a/framework/src/Volo.Abp.Autofac/Volo.Abp.Autofac.csproj b/framework/src/Volo.Abp.Autofac/Volo.Abp.Autofac.csproj index 3c5c275117..85a8541e26 100644 --- a/framework/src/Volo.Abp.Autofac/Volo.Abp.Autofac.csproj +++ b/framework/src/Volo.Abp.Autofac/Volo.Abp.Autofac.csproj @@ -17,7 +17,7 @@ - + diff --git a/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunBlobProviderConfiguration.cs b/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunBlobProviderConfiguration.cs index 058f65412c..757ad4b54c 100644 --- a/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunBlobProviderConfiguration.cs +++ b/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunBlobProviderConfiguration.cs @@ -25,6 +25,12 @@ namespace Volo.Abp.BlobStoring.Aliyun set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.Endpoint, Check.NotNullOrWhiteSpace(value, nameof(value))); } + public bool UseSecurityTokenService + { + get => _containerConfiguration.GetConfigurationOrDefault(AliyunBlobProviderConfigurationNames.UseSecurityTokenService, false); + set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.UseSecurityTokenService, value); + } + public string RegionId { get => _containerConfiguration.GetConfiguration(AliyunBlobProviderConfigurationNames.RegionId); @@ -88,18 +94,19 @@ namespace Volo.Abp.BlobStoring.Aliyun set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.CreateContainerIfNotExists, value); } - private readonly BlobContainerConfiguration _containerConfiguration; - - public AliyunBlobProviderConfiguration(BlobContainerConfiguration containerConfiguration) + private readonly string _temporaryCredentialsCacheKey; + public string TemporaryCredentialsCacheKey { - _containerConfiguration = containerConfiguration; + get => _containerConfiguration.GetConfigurationOrDefault(AliyunBlobProviderConfigurationNames.TemporaryCredentialsCacheKey, _temporaryCredentialsCacheKey); + set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.TemporaryCredentialsCacheKey, value); } + private readonly BlobContainerConfiguration _containerConfiguration; - public string ToKeyString() + public AliyunBlobProviderConfiguration(BlobContainerConfiguration containerConfiguration) { - Uri uPoint = new Uri(Endpoint); - return $"blobstoring:aliyun:id:{AccessKeyId},sec:{AccessKeySecret},ept:{uPoint.Host.ToLower()},rid:{RegionId},ra:{RoleArn},rsn:{RoleSessionName},pl:{Policy}"; + _containerConfiguration = containerConfiguration; + _temporaryCredentialsCacheKey = Guid.NewGuid().ToString("N"); } } } diff --git a/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunBlobProviderConfigurationNames.cs b/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunBlobProviderConfigurationNames.cs index c889b374cc..0f8585f12b 100644 --- a/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunBlobProviderConfigurationNames.cs +++ b/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunBlobProviderConfigurationNames.cs @@ -5,12 +5,14 @@ public const string AccessKeyId = "Aliyun.AccessKeyId"; public const string AccessKeySecret = "Aliyun.AccessKeySecret"; public const string Endpoint = "Aliyun.Endpoint"; + public const string UseSecurityTokenService = "Aliyun.UseSecurityTokenService"; public const string RegionId = "Aliyun.RegionId"; public const string RoleArn = "Aliyun.RoleArn"; public const string RoleSessionName = "Aliyun.RoleSessionName"; public const string DurationSeconds = "Aliyun.DurationSeconds"; public const string Policy = "Aliyun.Policy"; - public const string ContainerName = "Aliyun:ContainerName"; + public const string ContainerName = "Aliyun.ContainerName"; public const string CreateContainerIfNotExists = "Aliyun.CreateContainerIfNotExists"; + public const string TemporaryCredentialsCacheKey = "Aliyun.TemporaryCredentialsCacheKey"; } } diff --git a/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AssumeRoleCredentialsCacheItem.cs b/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunTemporaryCredentialsCacheItem.cs similarity index 66% rename from framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AssumeRoleCredentialsCacheItem.cs rename to framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunTemporaryCredentialsCacheItem.cs index e0d75f2954..508d94b743 100644 --- a/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AssumeRoleCredentialsCacheItem.cs +++ b/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunTemporaryCredentialsCacheItem.cs @@ -6,8 +6,7 @@ using Volo.Abp.Caching; namespace Volo.Abp.BlobStoring.Aliyun { [Serializable] - [CacheName("AssumeRoleCredentials")] - public class AssumeRoleCredentialsCacheItem + public class AliyunTemporaryCredentialsCacheItem { public string AccessKeyId { get; set; } @@ -15,12 +14,12 @@ namespace Volo.Abp.BlobStoring.Aliyun public string SecurityToken { get; set; } - public AssumeRoleCredentialsCacheItem() + public AliyunTemporaryCredentialsCacheItem() { } - public AssumeRoleCredentialsCacheItem(string accessKeyId,string accessKeySecret,string securityToken) + public AliyunTemporaryCredentialsCacheItem(string accessKeyId,string accessKeySecret,string securityToken) { AccessKeyId = accessKeyId; AccessKeySecret = accessKeySecret; diff --git a/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/DefaultOssClientFactory.cs b/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/DefaultOssClientFactory.cs index 8f69603f7c..ea0c4cbf28 100644 --- a/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/DefaultOssClientFactory.cs +++ b/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/DefaultOssClientFactory.cs @@ -6,8 +6,11 @@ using Aliyun.OSS; using Microsoft.Extensions.Caching.Distributed; using System; using System.Collections.Generic; +using System.Threading.Tasks; using Volo.Abp.Caching; using Volo.Abp.DependencyInjection; +using Volo.Abp.Security.Encryption; +using static Aliyun.Acs.Core.Auth.Sts.AssumeRoleResponse; namespace Volo.Abp.BlobStoring.Aliyun { @@ -16,53 +19,82 @@ namespace Volo.Abp.BlobStoring.Aliyun /// public class DefaultOssClientFactory : IOssClientFactory, ITransientDependency { - protected IDistributedCache Cache { get; } + protected IDistributedCache Cache { get; } + + protected IStringEncryptionService StringEncryptionService { get; } + public DefaultOssClientFactory( - IDistributedCache cache) + IDistributedCache cache, + IStringEncryptionService stringEncryptionService) { Cache = cache; + StringEncryptionService = stringEncryptionService; } - public virtual IOss Create(AliyunBlobProviderConfiguration aliyunConfig) + public virtual IOss Create(AliyunBlobProviderConfiguration configuration) { - //Sub-account - if (aliyunConfig.DurationSeconds <= 0) + Check.NotNullOrWhiteSpace(configuration.AccessKeyId, nameof(configuration.AccessKeyId)); + Check.NotNullOrWhiteSpace(configuration.AccessKeySecret, nameof(configuration.AccessKeySecret)); + Check.NotNullOrWhiteSpace(configuration.Endpoint, nameof(configuration.Endpoint)); + if (configuration.UseSecurityTokenService) { - return new OssClient(aliyunConfig.Endpoint, aliyunConfig.AccessKeyId, aliyunConfig.AccessKeySecret); + //STS temporary authorization to access OSS + return GetSecurityTokenClient(configuration); } - else + //Sub-account + return new OssClient(configuration.Endpoint, configuration.AccessKeyId, configuration.AccessKeySecret); + } + + protected virtual IOss GetSecurityTokenClient(AliyunBlobProviderConfiguration configuration) + { + Check.NotNullOrWhiteSpace(configuration.RoleArn, nameof(configuration.RoleArn)); + Check.NotNullOrWhiteSpace(configuration.RoleSessionName, nameof(configuration.RoleSessionName)); + var cacheItem = Cache.Get(configuration.TemporaryCredentialsCacheKey); + if (cacheItem == null) { - //STS temporary authorization to access OSS - var key = aliyunConfig.ToKeyString(); - var cacheItem = Cache.Get(key); - if (cacheItem == null) + IClientProfile profile = DefaultProfile.GetProfile( + configuration.RegionId, + configuration.AccessKeyId, + configuration.AccessKeySecret); + DefaultAcsClient client = new DefaultAcsClient(profile); + AssumeRoleRequest request = new AssumeRoleRequest { - IClientProfile profile = DefaultProfile.GetProfile( - aliyunConfig.RegionId, - aliyunConfig.AccessKeyId, - aliyunConfig.AccessKeySecret); - DefaultAcsClient client = new DefaultAcsClient(profile); - AssumeRoleRequest request = new AssumeRoleRequest - { - AcceptFormat = FormatType.JSON, - //eg:acs:ram::$accountID:role/$roleName - RoleArn = aliyunConfig.RoleArn, - RoleSessionName = aliyunConfig.RoleSessionName, - //Set the validity period of the temporary access credential, the unit is s, the minimum is 900, and the maximum is 3600. default 3600 - DurationSeconds = aliyunConfig.DurationSeconds, - //Set additional permission policy of Token; when acquiring Token, further reduce the permission of Token by setting an additional permission policy - Policy = aliyunConfig.Policy.IsNullOrEmpty() ? null : aliyunConfig.Policy, - }; - var response = client.GetAcsResponse(request); - cacheItem = new AssumeRoleCredentialsCacheItem(response.Credentials.AccessKeyId, response.Credentials.AccessKeySecret, response.Credentials.SecurityToken); - Cache.Set(key, cacheItem, new DistributedCacheEntryOptions() - { - //Subtract 10 seconds of network request time. - AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(aliyunConfig.DurationSeconds - 10) - }); - } - return new OssClient(aliyunConfig.Endpoint, cacheItem.AccessKeyId, cacheItem.AccessKeySecret, cacheItem.SecurityToken); + AcceptFormat = FormatType.JSON, + //eg:acs:ram::$accountID:role/$roleName + RoleArn = configuration.RoleArn, + RoleSessionName = configuration.RoleSessionName, + //Set the validity period of the temporary access credential, the unit is s, the minimum is 900, and the maximum is 3600. default 3600 + DurationSeconds = configuration.DurationSeconds, + //Set additional permission policy of Token; when acquiring Token, further reduce the permission of Token by setting an additional permission policy + Policy = configuration.Policy.IsNullOrEmpty() ? null : configuration.Policy, + }; + var response = client.GetAcsResponse(request); + cacheItem = SetTemporaryCredentialsCache(configuration, response.Credentials); } + return new OssClient( + configuration.Endpoint, + StringEncryptionService.Decrypt(cacheItem.AccessKeyId), + StringEncryptionService.Decrypt(cacheItem.AccessKeySecret), + StringEncryptionService.Decrypt(cacheItem.SecurityToken)); + } + + private AliyunTemporaryCredentialsCacheItem SetTemporaryCredentialsCache( + AliyunBlobProviderConfiguration configuration, + AssumeRole_Credentials credentials) + { + var temporaryCredentialsCache = new AliyunTemporaryCredentialsCacheItem( + StringEncryptionService.Encrypt(credentials.AccessKeyId), + StringEncryptionService.Encrypt(credentials.AccessKeySecret), + StringEncryptionService.Encrypt(credentials.SecurityToken)); + + Cache.Set(configuration.TemporaryCredentialsCacheKey, temporaryCredentialsCache, + new DistributedCacheEntryOptions + { + AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(configuration.DurationSeconds - 10) + }); + + return temporaryCredentialsCache; } + } } diff --git a/framework/src/Volo.Abp.BlobStoring.Aws/FodyWeavers.xml b/framework/src/Volo.Abp.BlobStoring.Aws/FodyWeavers.xml new file mode 100644 index 0000000000..be0de3a908 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aws/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.BlobStoring.Aws/FodyWeavers.xsd b/framework/src/Volo.Abp.BlobStoring.Aws/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aws/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.BlobStoring.Aws/Volo.Abp.BlobStoring.Aws.csproj b/framework/src/Volo.Abp.BlobStoring.Aws/Volo.Abp.BlobStoring.Aws.csproj new file mode 100644 index 0000000000..d30601d1e8 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aws/Volo.Abp.BlobStoring.Aws.csproj @@ -0,0 +1,24 @@ + + + + + + + netstandard2.0 + false + false + false + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AbpBlobStoringAwsModule.cs b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AbpBlobStoringAwsModule.cs new file mode 100644 index 0000000000..28426321c8 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AbpBlobStoringAwsModule.cs @@ -0,0 +1,14 @@ +using Volo.Abp.Caching; +using Volo.Abp.Modularity; + +namespace Volo.Abp.BlobStoring.Aws +{ + [DependsOn(typeof(AbpBlobStoringModule), + typeof(AbpCachingModule))] + public class AbpBlobStoringAwsModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + } + } +} diff --git a/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobContainerConfigurationExtensions.cs b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobContainerConfigurationExtensions.cs new file mode 100644 index 0000000000..d63e408aab --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobContainerConfigurationExtensions.cs @@ -0,0 +1,25 @@ +using System; + +namespace Volo.Abp.BlobStoring.Aws +{ + public static class AwsBlobContainerConfigurationExtensions + { + public static AwsBlobProviderConfiguration GetAwsConfiguration( + this BlobContainerConfiguration containerConfiguration) + { + return new AwsBlobProviderConfiguration(containerConfiguration); + } + + public static BlobContainerConfiguration UseAws( + this BlobContainerConfiguration containerConfiguration, + Action awsConfigureAction) + { + containerConfiguration.ProviderType = typeof(AwsBlobProvider); + containerConfiguration.NamingNormalizers.TryAdd(); + + awsConfigureAction(new AwsBlobProviderConfiguration(containerConfiguration)); + + return containerConfiguration; + } + } +} diff --git a/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobNamingNormalizer.cs b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobNamingNormalizer.cs new file mode 100644 index 0000000000..cd8eca021e --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobNamingNormalizer.cs @@ -0,0 +1,52 @@ +using System.Text.RegularExpressions; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.BlobStoring.Aws +{ + public class AwsBlobNamingNormalizer : IBlobNamingNormalizer, ITransientDependency + { + /// + ///https://docs.aws.amazon.com/AmazonS3/latest/dev/BucketRestrictions.html + /// + public virtual string NormalizeContainerName(string containerName) + { + // All letters in a container name must be lowercase. + containerName = containerName.ToLower(); + + // Container names can contain only letters, numbers, and the dash (-) character. + containerName = Regex.Replace(containerName, "[^a-z0-9-]", string.Empty); + + // Every dash (-) character must be immediately preceded and followed by a letter or number; + // consecutive dashes are not permitted in container names. + // Container names must start or end with a letter or number + containerName = Regex.Replace(containerName, "-{2,}", "-"); + containerName = Regex.Replace(containerName, "^-", string.Empty); + containerName = Regex.Replace(containerName, "-$", string.Empty); + + // Container names must be from 3 through 63 characters long. + if (containerName.Length < 3) + { + var length = containerName.Length; + for (var i = 0; i < 3 - length; i++) + { + containerName += "0"; + } + } + + if (containerName.Length > 63) + { + containerName = containerName.Substring(0, 63); + } + + return containerName; + } + + /// + /// https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMetadata.html + /// + public virtual string NormalizeBlobName(string blobName) + { + return blobName; + } + } +} diff --git a/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobProvider.cs b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobProvider.cs new file mode 100644 index 0000000000..6acb91aea9 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobProvider.cs @@ -0,0 +1,158 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using Amazon.S3; +using Amazon.S3.Model; +using Amazon.S3.Util; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.BlobStoring.Aws +{ + public class AwsBlobProvider : BlobProviderBase, ITransientDependency + { + protected IAwsBlobNameCalculator AwsBlobNameCalculator { get; } + protected IAmazonS3ClientFactory AmazonS3ClientFactory { get; } + + public AwsBlobProvider(IAwsBlobNameCalculator awsBlobNameCalculator, + IAmazonS3ClientFactory amazonS3ClientFactory) + { + AwsBlobNameCalculator = awsBlobNameCalculator; + AmazonS3ClientFactory = amazonS3ClientFactory; + } + + public override async Task SaveAsync(BlobProviderSaveArgs args) + { + var blobName = AwsBlobNameCalculator.Calculate(args); + var configuration = args.Configuration.GetAwsConfiguration(); + var containerName = GetContainerName(args); + + using (var amazonS3Client = await GetAmazonS3Client(args)) + { + if (!args.OverrideExisting && await BlobExistsAsync(amazonS3Client, containerName, blobName)) + { + throw new BlobAlreadyExistsException( + $"Saving BLOB '{args.BlobName}' does already exists in the container '{containerName}'! Set {nameof(args.OverrideExisting)} if it should be overwritten."); + } + + if (configuration.CreateContainerIfNotExists) + { + await CreateContainerIfNotExists(amazonS3Client, containerName); + } + + await amazonS3Client.PutObjectAsync(new PutObjectRequest + { + BucketName = containerName, + Key = blobName, + InputStream = args.BlobStream + }); + } + } + + public override async Task DeleteAsync(BlobProviderDeleteArgs args) + { + var blobName = AwsBlobNameCalculator.Calculate(args); + var containerName = GetContainerName(args); + + using (var amazonS3Client = await GetAmazonS3Client(args)) + { + if (!await BlobExistsAsync(amazonS3Client, containerName, blobName)) + { + return false; + } + + await amazonS3Client.DeleteObjectAsync(new DeleteObjectRequest + { + BucketName = containerName, + Key = blobName + }); + + return true; + } + } + + public override async Task ExistsAsync(BlobProviderExistsArgs args) + { + var blobName = AwsBlobNameCalculator.Calculate(args); + var containerName = GetContainerName(args); + + using (var amazonS3Client = await GetAmazonS3Client(args)) + { + return await BlobExistsAsync(amazonS3Client, containerName, blobName); + } + } + + public override async Task GetOrNullAsync(BlobProviderGetArgs args) + { + var blobName = AwsBlobNameCalculator.Calculate(args); + var containerName = GetContainerName(args); + + using (var amazonS3Client = await GetAmazonS3Client(args)) + { + if (!await BlobExistsAsync(amazonS3Client, containerName, blobName)) + { + return null; + } + + var response = await amazonS3Client.GetObjectAsync(new GetObjectRequest + { + BucketName = containerName, + Key = blobName + }); + + var memoryStream = new MemoryStream(); + await response.ResponseStream.CopyToAsync(memoryStream); + return memoryStream; + } + } + + protected virtual async Task GetAmazonS3Client(BlobProviderArgs args) + { + var configuration = args.Configuration.GetAwsConfiguration(); + return await AmazonS3ClientFactory.GetAmazonS3Client(configuration); + } + + private async Task BlobExistsAsync(AmazonS3Client amazonS3Client, string containerName, string blobName) + { + // Make sure Blob Container exists. + if (!await AmazonS3Util.DoesS3BucketExistV2Async(amazonS3Client, containerName)) + { + return false; + } + + try + { + await amazonS3Client.GetObjectMetadataAsync(containerName, blobName); + } + catch (Exception ex) + { + if (ex is AmazonS3Exception) + { + return false; + } + + throw; + } + + return true; + } + + protected virtual async Task CreateContainerIfNotExists(AmazonS3Client amazonS3Client, string containerName) + { + if (!await AmazonS3Util.DoesS3BucketExistV2Async(amazonS3Client, containerName)) + { + await amazonS3Client.PutBucketAsync(new PutBucketRequest + { + BucketName = containerName + }); + } + } + + private static string GetContainerName(BlobProviderArgs args) + { + var configuration = args.Configuration.GetAwsConfiguration(); + return configuration.ContainerName.IsNullOrWhiteSpace() + ? args.ContainerName + : configuration.ContainerName; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobProviderConfiguration.cs b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobProviderConfiguration.cs new file mode 100644 index 0000000000..c2d3297f4e --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobProviderConfiguration.cs @@ -0,0 +1,112 @@ +using System; + +namespace Volo.Abp.BlobStoring.Aws +{ + public class AwsBlobProviderConfiguration + { + public string AccessKeyId + { + get => _containerConfiguration.GetConfigurationOrDefault(AwsBlobProviderConfigurationNames.AccessKeyId); + set => _containerConfiguration.SetConfiguration(AwsBlobProviderConfigurationNames.AccessKeyId, value); + } + + public string SecretAccessKey + { + get => _containerConfiguration.GetConfigurationOrDefault(AwsBlobProviderConfigurationNames.SecretAccessKey); + set => _containerConfiguration.SetConfiguration(AwsBlobProviderConfigurationNames.SecretAccessKey, value); + } + + public bool UseCredentials + { + get => _containerConfiguration.GetConfigurationOrDefault(AwsBlobProviderConfigurationNames.UseCredentials,false); + set => _containerConfiguration.SetConfiguration(AwsBlobProviderConfigurationNames.UseCredentials, value); + } + + public bool UseTemporaryCredentials + { + get => _containerConfiguration.GetConfigurationOrDefault(AwsBlobProviderConfigurationNames.UseTemporaryCredentials,false); + set => _containerConfiguration.SetConfiguration(AwsBlobProviderConfigurationNames.UseTemporaryCredentials, value); + } + + public bool UseTemporaryFederatedCredentials + { + get => _containerConfiguration.GetConfigurationOrDefault(AwsBlobProviderConfigurationNames.UseTemporaryFederatedCredentials,false); + set => _containerConfiguration.SetConfiguration(AwsBlobProviderConfigurationNames.UseTemporaryFederatedCredentials, value); + } + + public string ProfileName + { + get => _containerConfiguration.GetConfigurationOrDefault(AwsBlobProviderConfigurationNames.ProfileName); + set => _containerConfiguration.SetConfiguration(AwsBlobProviderConfigurationNames.ProfileName, value); + } + + public string ProfilesLocation + { + get => _containerConfiguration.GetConfigurationOrDefault(AwsBlobProviderConfigurationNames.ProfilesLocation); + set => _containerConfiguration.SetConfiguration(AwsBlobProviderConfigurationNames.ProfilesLocation, value); + } + + /// + /// Set the validity period of the temporary access credential, the unit is s, the minimum is 900, and the maximum is 129600. + /// + public int DurationSeconds + { + get => _containerConfiguration.GetConfigurationOrDefault(AwsBlobProviderConfigurationNames.DurationSeconds, 0); + set => _containerConfiguration.SetConfiguration(AwsBlobProviderConfigurationNames.DurationSeconds, value); + } + + public string Name + { + get => _containerConfiguration.GetConfiguration(AwsBlobProviderConfigurationNames.Name); + set => _containerConfiguration.SetConfiguration(AwsBlobProviderConfigurationNames.Name, value); + } + + public string Policy + { + get => _containerConfiguration.GetConfigurationOrDefault(AwsBlobProviderConfigurationNames.Policy); + set => _containerConfiguration.SetConfiguration(AwsBlobProviderConfigurationNames.Policy, value); + } + + public string Region + { + get => _containerConfiguration.GetConfiguration(AwsBlobProviderConfigurationNames.Region); + set => _containerConfiguration.SetConfiguration(AwsBlobProviderConfigurationNames.Region, Check.NotNull(value, nameof(value))); + } + + /// + /// This name may only contain lowercase letters, numbers, and hyphens, and must begin with a letter or a number. + /// Each hyphen must be preceded and followed by a non-hyphen character. + /// The name must also be between 3 and 63 characters long. + /// If this parameter is not specified, the ContainerName of the will be used. + /// + public string ContainerName + { + get => _containerConfiguration.GetConfiguration(AwsBlobProviderConfigurationNames.ContainerName); + set => _containerConfiguration.SetConfiguration(AwsBlobProviderConfigurationNames.ContainerName, Check.NotNullOrWhiteSpace(value, nameof(value))); + } + + /// + /// Default value: false. + /// + public bool CreateContainerIfNotExists + { + get => _containerConfiguration.GetConfigurationOrDefault(AwsBlobProviderConfigurationNames.CreateContainerIfNotExists, false); + set => _containerConfiguration.SetConfiguration(AwsBlobProviderConfigurationNames.CreateContainerIfNotExists, value); + } + + private readonly string _temporaryCredentialsCacheKey; + public string TemporaryCredentialsCacheKey + { + get => _containerConfiguration.GetConfigurationOrDefault(AwsBlobProviderConfigurationNames.TemporaryCredentialsCacheKey, _temporaryCredentialsCacheKey); + set => _containerConfiguration.SetConfiguration(AwsBlobProviderConfigurationNames.TemporaryCredentialsCacheKey, value); + } + + private readonly BlobContainerConfiguration _containerConfiguration; + + public AwsBlobProviderConfiguration(BlobContainerConfiguration containerConfiguration) + { + _containerConfiguration = containerConfiguration; + _temporaryCredentialsCacheKey = Guid.NewGuid().ToString("N"); + } + } +} diff --git a/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobProviderConfigurationNames.cs b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobProviderConfigurationNames.cs new file mode 100644 index 0000000000..24dfca53d2 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobProviderConfigurationNames.cs @@ -0,0 +1,20 @@ +namespace Volo.Abp.BlobStoring.Aws +{ + public static class AwsBlobProviderConfigurationNames + { + public const string AccessKeyId = "Aws.AccessKeyId"; + public const string SecretAccessKey = "Aws.SecretAccessKey"; + public const string UseCredentials = "Aws.UseCredentials"; + public const string UseTemporaryCredentials = "Aws.UseTemporaryCredentials"; + public const string UseTemporaryFederatedCredentials = "Aws.UseTemporaryFederatedCredentials"; + public const string ProfileName = "Aws.ProfileName"; + public const string ProfilesLocation = "Aws.ProfilesLocation"; + public const string DurationSeconds = "Aws.DurationSeconds"; + public const string TemporaryCredentialsCacheKey = "Aws.TemporaryCredentialsCacheKey"; + public const string Name = "Aws.Name"; + public const string Policy = "Aws.Policy"; + public const string Region = "Aws.Region"; + public const string ContainerName = "Aws.ContainerName"; + public const string CreateContainerIfNotExists = "Aws.CreateContainerIfNotExists"; + } +} diff --git a/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsTemporaryCredentialsCacheItem.cs b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsTemporaryCredentialsCacheItem.cs new file mode 100644 index 0000000000..887acddc5d --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsTemporaryCredentialsCacheItem.cs @@ -0,0 +1,26 @@ +using System; + +namespace Volo.Abp.BlobStoring.Aws +{ + [Serializable] + public class AwsTemporaryCredentialsCacheItem + { + public string AccessKeyId { get; set; } + + public string SecretAccessKey { get; set; } + + public string SessionToken { get; set; } + + public AwsTemporaryCredentialsCacheItem() + { + + } + + public AwsTemporaryCredentialsCacheItem(string accessKeyId,string secretAccessKey,string sessionToken) + { + AccessKeyId = accessKeyId; + SecretAccessKey = secretAccessKey; + SessionToken = sessionToken; + } + } +} diff --git a/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/DefaultAmazonS3ClientFactory.cs b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/DefaultAmazonS3ClientFactory.cs new file mode 100644 index 0000000000..739818c0d9 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/DefaultAmazonS3ClientFactory.cs @@ -0,0 +1,193 @@ +using System; +using System.Threading.Tasks; +using Amazon; +using Amazon.Runtime; +using Amazon.Runtime.CredentialManagement; +using Amazon.S3; +using Amazon.SecurityToken; +using Amazon.SecurityToken.Model; +using Microsoft.Extensions.Caching.Distributed; +using Volo.Abp.Caching; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Security.Encryption; + +namespace Volo.Abp.BlobStoring.Aws +{ + public class DefaultAmazonS3ClientFactory : IAmazonS3ClientFactory, ITransientDependency + { + protected IDistributedCache Cache { get; } + + protected IStringEncryptionService StringEncryptionService { get; } + + public DefaultAmazonS3ClientFactory( + IDistributedCache cache, + IStringEncryptionService stringEncryptionService) + { + Cache = cache; + StringEncryptionService = stringEncryptionService; + } + + public virtual async Task GetAmazonS3Client( + AwsBlobProviderConfiguration configuration) + { + var region = RegionEndpoint.GetBySystemName(configuration.Region); + + if (configuration.UseCredentials) + { + var awsCredentials = GetAwsCredentials(configuration); + return awsCredentials == null + ? new AmazonS3Client(region) + : new AmazonS3Client(awsCredentials, region); + } + + if (configuration.UseTemporaryCredentials) + { + return new AmazonS3Client(await GetTemporaryCredentialsAsync(configuration), region); + } + + if (configuration.UseTemporaryFederatedCredentials) + { + return new AmazonS3Client(await GetTemporaryFederatedCredentialsAsync(configuration), + region); + } + + Check.NotNullOrWhiteSpace(configuration.AccessKeyId, nameof(configuration.AccessKeyId)); + Check.NotNullOrWhiteSpace(configuration.SecretAccessKey, nameof(configuration.SecretAccessKey)); + + return new AmazonS3Client(configuration.AccessKeyId, configuration.SecretAccessKey, configuration.Region); + } + + protected virtual AWSCredentials GetAwsCredentials( + AwsBlobProviderConfiguration configuration) + { + if (configuration.ProfileName.IsNullOrWhiteSpace()) + { + return null; + } + + var chain = new CredentialProfileStoreChain(configuration.ProfilesLocation); + + if (chain.TryGetAWSCredentials(configuration.ProfileName, out var awsCredentials)) + { + return awsCredentials; + } + + throw new AmazonS3Exception("Not found aws credentials"); + } + + protected virtual async Task GetTemporaryCredentialsAsync( + AwsBlobProviderConfiguration configuration) + { + var temporaryCredentialsCache = await Cache.GetAsync(configuration.TemporaryCredentialsCacheKey); + + if (temporaryCredentialsCache == null) + { + AmazonSecurityTokenServiceClient stsClient; + + if (!configuration.AccessKeyId.IsNullOrEmpty() && !configuration.SecretAccessKey.IsNullOrEmpty()) + { + stsClient = new AmazonSecurityTokenServiceClient(configuration.AccessKeyId, + configuration.SecretAccessKey); + } + else + { + var awsCredentials = GetAwsCredentials(configuration); + stsClient = awsCredentials == null + ? new AmazonSecurityTokenServiceClient() + : new AmazonSecurityTokenServiceClient(awsCredentials); + } + + using (stsClient) + { + var getSessionTokenRequest = new GetSessionTokenRequest + { + DurationSeconds = configuration.DurationSeconds + }; + + var sessionTokenResponse = + await stsClient.GetSessionTokenAsync(getSessionTokenRequest); + + var credentials = sessionTokenResponse.Credentials; + + temporaryCredentialsCache = + await SetTemporaryCredentialsCache(configuration, credentials); + } + } + + var sessionCredentials = new SessionAWSCredentials( + StringEncryptionService.Decrypt(temporaryCredentialsCache.AccessKeyId), + StringEncryptionService.Decrypt(temporaryCredentialsCache.SecretAccessKey), + StringEncryptionService.Decrypt(temporaryCredentialsCache.SessionToken)); + return sessionCredentials; + } + + protected virtual async Task GetTemporaryFederatedCredentialsAsync( + AwsBlobProviderConfiguration configuration) + { + Check.NotNullOrWhiteSpace(configuration.Name, nameof(configuration.Name)); + Check.NotNullOrWhiteSpace(configuration.Policy, nameof(configuration.Policy)); + + var temporaryCredentialsCache = await Cache.GetAsync(configuration.TemporaryCredentialsCacheKey); + + if (temporaryCredentialsCache == null) + { + AmazonSecurityTokenServiceClient stsClient; + + if (!configuration.AccessKeyId.IsNullOrEmpty() && !configuration.SecretAccessKey.IsNullOrEmpty()) + { + stsClient = new AmazonSecurityTokenServiceClient(configuration.AccessKeyId, + configuration.SecretAccessKey); + } + else + { + var awsCredentials = GetAwsCredentials(configuration); + stsClient = awsCredentials == null + ? new AmazonSecurityTokenServiceClient() + : new AmazonSecurityTokenServiceClient(awsCredentials); + } + + using (stsClient) + { + var federationTokenRequest = + new GetFederationTokenRequest + { + DurationSeconds = configuration.DurationSeconds, + Name = configuration.Name, + Policy = configuration.Policy + }; + + var federationTokenResponse = + await stsClient.GetFederationTokenAsync(federationTokenRequest); + var credentials = federationTokenResponse.Credentials; + + temporaryCredentialsCache = + await SetTemporaryCredentialsCache(configuration, credentials); + } + } + + var sessionCredentials = new SessionAWSCredentials( + StringEncryptionService.Decrypt(temporaryCredentialsCache.AccessKeyId), + StringEncryptionService.Decrypt(temporaryCredentialsCache.SecretAccessKey), + StringEncryptionService.Decrypt(temporaryCredentialsCache.SessionToken)); + return sessionCredentials; + } + + private async Task SetTemporaryCredentialsCache( + AwsBlobProviderConfiguration configuration, + Credentials credentials) + { + var temporaryCredentialsCache = new AwsTemporaryCredentialsCacheItem( + StringEncryptionService.Encrypt(credentials.AccessKeyId), + StringEncryptionService.Encrypt(credentials.SecretAccessKey), + StringEncryptionService.Encrypt(credentials.SessionToken)); + + await Cache.SetAsync(configuration.TemporaryCredentialsCacheKey, temporaryCredentialsCache, + new DistributedCacheEntryOptions + { + AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(configuration.DurationSeconds - 10) + }); + + return temporaryCredentialsCache; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/DefaultAwsBlobNameCalculator.cs b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/DefaultAwsBlobNameCalculator.cs new file mode 100644 index 0000000000..9ff3511e6f --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/DefaultAwsBlobNameCalculator.cs @@ -0,0 +1,22 @@ +using Volo.Abp.DependencyInjection; +using Volo.Abp.MultiTenancy; + +namespace Volo.Abp.BlobStoring.Aws +{ + public class DefaultAwsBlobNameCalculator : IAwsBlobNameCalculator, ITransientDependency + { + protected ICurrentTenant CurrentTenant { get; } + + public DefaultAwsBlobNameCalculator(ICurrentTenant currentTenant) + { + CurrentTenant = currentTenant; + } + + public virtual string Calculate(BlobProviderArgs args) + { + return CurrentTenant.Id == null + ? $"host/{args.BlobName}" + : $"tenants/{CurrentTenant.Id.Value.ToString("D")}/{args.BlobName}"; + } + } +} diff --git a/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/IAmazonS3ClientFactory.cs b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/IAmazonS3ClientFactory.cs new file mode 100644 index 0000000000..92d7ddc906 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/IAmazonS3ClientFactory.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using Amazon.S3; + +namespace Volo.Abp.BlobStoring.Aws +{ + public interface IAmazonS3ClientFactory + { + Task GetAmazonS3Client(AwsBlobProviderConfiguration configuration); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/IAwsBlobNameCalculator.cs b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/IAwsBlobNameCalculator.cs new file mode 100644 index 0000000000..e4c470c505 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/IAwsBlobNameCalculator.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.BlobStoring.Aws +{ + public interface IAwsBlobNameCalculator + { + string Calculate(BlobProviderArgs args); + } +} diff --git a/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/MinioBlobProvider.cs b/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/MinioBlobProvider.cs index 142363d14e..204b1a6a1a 100644 --- a/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/MinioBlobProvider.cs +++ b/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/MinioBlobProvider.cs @@ -72,11 +72,11 @@ namespace Volo.Abp.BlobStoring.Minio } var memoryStream = new MemoryStream(); - await client.GetObjectAsync(containerName, blobName, stream => + await client.GetObjectAsync(containerName, blobName, async stream => { if (stream != null) { - stream.CopyTo(memoryStream); + await stream.CopyToAsync(memoryStream); } else { diff --git a/framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo.Abp.Caching.StackExchangeRedis.csproj b/framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo.Abp.Caching.StackExchangeRedis.csproj index c362c16bff..95a15ddc8e 100644 --- a/framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo.Abp.Caching.StackExchangeRedis.csproj +++ b/framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo.Abp.Caching.StackExchangeRedis.csproj @@ -19,7 +19,7 @@ - + diff --git a/framework/src/Volo.Abp.Caching/Volo.Abp.Caching.csproj b/framework/src/Volo.Abp.Caching/Volo.Abp.Caching.csproj index 5ee096a493..19d5b456ec 100644 --- a/framework/src/Volo.Abp.Caching/Volo.Abp.Caching.csproj +++ b/framework/src/Volo.Abp.Caching/Volo.Abp.Caching.csproj @@ -15,7 +15,7 @@ - + diff --git a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/AbpCachingModule.cs b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/AbpCachingModule.cs index ba59bf039e..d133ac110b 100644 --- a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/AbpCachingModule.cs +++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/AbpCachingModule.cs @@ -5,12 +5,14 @@ using Volo.Abp.Modularity; using Volo.Abp.MultiTenancy; using Volo.Abp.Serialization; using Volo.Abp.Threading; +using Volo.Abp.Uow; namespace Volo.Abp.Caching { [DependsOn( typeof(AbpThreadingModule), typeof(AbpSerializationModule), + typeof(AbpUnitOfWorkModule), typeof(AbpMultiTenancyModule), typeof(AbpJsonModule))] public class AbpCachingModule : AbpModule diff --git a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs index 5bb1dd2481..b26feec136 100644 --- a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs +++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs @@ -14,6 +14,7 @@ using Volo.Abp.DependencyInjection; using Volo.Abp.ExceptionHandling; using Volo.Abp.MultiTenancy; using Volo.Abp.Threading; +using Volo.Abp.Uow; namespace Volo.Abp.Caching { @@ -30,13 +31,15 @@ namespace Volo.Abp.Caching ICancellationTokenProvider cancellationTokenProvider, IDistributedCacheSerializer serializer, IDistributedCacheKeyNormalizer keyNormalizer, - IHybridServiceScopeFactory serviceScopeFactory) : base( - distributedCacheOption: distributedCacheOption, - cache: cache, - cancellationTokenProvider: cancellationTokenProvider, - serializer: serializer, - keyNormalizer: keyNormalizer, - serviceScopeFactory: serviceScopeFactory) + IHybridServiceScopeFactory serviceScopeFactory, + IUnitOfWorkManager unitOfWorkManager) : base( + distributedCacheOption: distributedCacheOption, + cache: cache, + cancellationTokenProvider: cancellationTokenProvider, + serializer: serializer, + keyNormalizer: keyNormalizer, + serviceScopeFactory: serviceScopeFactory, + unitOfWorkManager:unitOfWorkManager) { } } @@ -50,6 +53,8 @@ namespace Volo.Abp.Caching public class DistributedCache : IDistributedCache where TCacheItem : class { + public const string UowCacheName = "AbpDistributedCache"; + public ILogger> Logger { get; set; } protected string CacheName { get; set; } @@ -66,6 +71,8 @@ namespace Volo.Abp.Caching protected IHybridServiceScopeFactory ServiceScopeFactory { get; } + protected IUnitOfWorkManager UnitOfWorkManager { get; } + protected SemaphoreSlim SyncSemaphore { get; } protected DistributedCacheEntryOptions DefaultCacheOptions; @@ -78,7 +85,8 @@ namespace Volo.Abp.Caching ICancellationTokenProvider cancellationTokenProvider, IDistributedCacheSerializer serializer, IDistributedCacheKeyNormalizer keyNormalizer, - IHybridServiceScopeFactory serviceScopeFactory) + IHybridServiceScopeFactory serviceScopeFactory, + IUnitOfWorkManager unitOfWorkManager) { _distributedCacheOption = distributedCacheOption.Value; Cache = cache; @@ -87,6 +95,7 @@ namespace Volo.Abp.Caching Serializer = serializer; KeyNormalizer = keyNormalizer; ServiceScopeFactory = serviceScopeFactory; + UnitOfWorkManager = unitOfWorkManager; SyncSemaphore = new SemaphoreSlim(1, 1); @@ -134,13 +143,24 @@ namespace Volo.Abp.Caching /// /// The key of cached item to be retrieved from the cache. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// The cache item, or null. public virtual TCacheItem Get( TCacheKey key, - bool? hideErrors = null) + bool? hideErrors = null, + bool considerUow = false) { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; + if (ShouldConsiderUow(considerUow)) + { + var value = GetUnitOfWorkCache().GetOrDefault(key)?.GetUnRemovedValueOrNull(); + if (value != null) + { + return value; + } + } + byte[] cachedBytes; try @@ -163,7 +183,8 @@ namespace Volo.Abp.Caching public virtual KeyValuePair[] GetMany( IEnumerable keys, - bool? hideErrors = null) + bool? hideErrors = null, + bool considerUow = false) { var keyArray = keys.ToArray(); @@ -172,16 +193,39 @@ namespace Volo.Abp.Caching { return GetManyFallback( keyArray, - hideErrors + hideErrors, + considerUow ); } + var notCachedKeys = new List(); + var cachedValues = new List>(); + if (ShouldConsiderUow(considerUow)) + { + var uowCache = GetUnitOfWorkCache(); + foreach (var key in keyArray) + { + var value = uowCache.GetOrDefault(key)?.GetUnRemovedValueOrNull(); + if (value != null) + { + cachedValues.Add(new KeyValuePair(key, value)); + } + } + + notCachedKeys = keyArray.Except(cachedValues.Select(x => x.Key)).ToList(); + if (!notCachedKeys.Any()) + { + return cachedValues.ToArray(); + } + } + hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; byte[][] cachedBytes; + var readKeys = notCachedKeys.Any() ? notCachedKeys.ToArray() : keyArray; try { - cachedBytes = cacheSupportsMultipleItems.GetMany(keyArray.Select(NormalizeKey)); + cachedBytes = cacheSupportsMultipleItems.GetMany(readKeys.Select(NormalizeKey)); } catch (Exception ex) { @@ -193,13 +237,14 @@ namespace Volo.Abp.Caching throw; } - - return ToCacheItems(cachedBytes, keyArray); + + return cachedValues.Concat(ToCacheItems(cachedBytes, readKeys)).ToArray(); } protected virtual KeyValuePair[] GetManyFallback( TCacheKey[] keys, - bool? hideErrors = null) + bool? hideErrors = null, + bool considerUow = false) { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; @@ -208,7 +253,7 @@ namespace Volo.Abp.Caching return keys .Select(key => new KeyValuePair( key, - Get(key, hideErrors: false) + Get(key, false, considerUow) ) ).ToArray(); } @@ -227,6 +272,7 @@ namespace Volo.Abp.Caching public virtual async Task[]> GetManyAsync( IEnumerable keys, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default) { var keyArray = keys.ToArray(); @@ -237,17 +283,41 @@ namespace Volo.Abp.Caching return await GetManyFallbackAsync( keyArray, hideErrors, + considerUow, token ); } + var notCachedKeys = new List(); + var cachedValues = new List>(); + if (ShouldConsiderUow(considerUow)) + { + var uowCache = GetUnitOfWorkCache(); + foreach (var key in keyArray) + { + var value = uowCache.GetOrDefault(key)?.GetUnRemovedValueOrNull(); + if (value != null) + { + cachedValues.Add(new KeyValuePair(key, value)); + } + } + + notCachedKeys = keyArray.Except(cachedValues.Select(x => x.Key)).ToList(); + if (!notCachedKeys.Any()) + { + return cachedValues.ToArray(); + } + } + hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; byte[][] cachedBytes; + var readKeys = notCachedKeys.Any() ? notCachedKeys.ToArray() : keyArray; + try { cachedBytes = await cacheSupportsMultipleItems.GetManyAsync( - keyArray.Select(NormalizeKey), + readKeys.Select(NormalizeKey), CancellationTokenProvider.FallbackToProvider(token) ); } @@ -261,13 +331,14 @@ namespace Volo.Abp.Caching throw; } - - return ToCacheItems(cachedBytes, keyArray); + + return cachedValues.Concat(ToCacheItems(cachedBytes, readKeys)).ToArray(); } protected virtual async Task[]> GetManyFallbackAsync( TCacheKey[] keys, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default) { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; @@ -280,7 +351,7 @@ namespace Volo.Abp.Caching { result.Add(new KeyValuePair( key, - await GetAsync(key, false, token)) + await GetAsync(key, false, considerUow, token: token)) ); } @@ -303,15 +374,26 @@ namespace Volo.Abp.Caching /// /// The key of cached item to be retrieved from the cache. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// The for the task. /// The cache item, or null. public virtual async Task GetAsync( TCacheKey key, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default) { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; + if (ShouldConsiderUow(considerUow)) + { + var value = GetUnitOfWorkCache().GetOrDefault(key)?.GetUnRemovedValueOrNull(); + if (value != null) + { + return value; + } + } + byte[] cachedBytes; try @@ -348,14 +430,16 @@ namespace Volo.Abp.Caching /// The factory delegate is used to provide the cache item when no cache item is found for the given . /// The cache options for the factory delegate. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// The cache item. public virtual TCacheItem GetOrAdd( TCacheKey key, Func factory, Func optionsFactory = null, - bool? hideErrors = null) + bool? hideErrors = null, + bool considerUow = false) { - var value = Get(key, hideErrors); + var value = Get(key, hideErrors, considerUow); if (value != null) { return value; @@ -363,14 +447,28 @@ namespace Volo.Abp.Caching using (SyncSemaphore.Lock()) { - value = Get(key, hideErrors); + value = Get(key, hideErrors, considerUow); if (value != null) { return value; } value = factory(); - Set(key, value, optionsFactory?.Invoke(), hideErrors); + + if (ShouldConsiderUow(considerUow)) + { + var uowCache = GetUnitOfWorkCache(); + if (uowCache.TryGetValue(key, out var item)) + { + item.SetValue(value); + } + else + { + uowCache.Add(key, new UnitOfWorkCacheItem(value)); + } + } + + Set(key, value, optionsFactory?.Invoke(), hideErrors, considerUow); } return value; @@ -384,6 +482,7 @@ namespace Volo.Abp.Caching /// The factory delegate is used to provide the cache item when no cache item is found for the given . /// The cache options for the factory delegate. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// The for the task. /// The cache item. public virtual async Task GetOrAddAsync( @@ -391,10 +490,11 @@ namespace Volo.Abp.Caching Func> factory, Func optionsFactory = null, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default) { token = CancellationTokenProvider.FallbackToProvider(token); - var value = await GetAsync(key, hideErrors, token); + var value = await GetAsync(key, hideErrors, considerUow, token); if (value != null) { return value; @@ -402,14 +502,28 @@ namespace Volo.Abp.Caching using (await SyncSemaphore.LockAsync(token)) { - value = await GetAsync(key, hideErrors, token); + value = await GetAsync(key, hideErrors, considerUow, token); if (value != null) { return value; } value = await factory(); - await SetAsync(key, value, optionsFactory?.Invoke(), hideErrors, token); + + if (ShouldConsiderUow(considerUow)) + { + var uowCache = GetUnitOfWorkCache(); + if (uowCache.TryGetValue(key, out var item)) + { + item.SetValue(value); + } + else + { + uowCache.Add(key, new UnitOfWorkCacheItem(value)); + } + } + + await SetAsync(key, value, optionsFactory?.Invoke(), hideErrors, considerUow, token); } return value; @@ -422,34 +536,62 @@ namespace Volo.Abp.Caching /// The cache item value to set in the cache. /// The cache options for the value. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. public virtual void Set( TCacheKey key, TCacheItem value, DistributedCacheEntryOptions options = null, - bool? hideErrors = null) + bool? hideErrors = null, + bool considerUow = false) { - hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; - - try + void SetRealCache() { - Cache.Set( - NormalizeKey(key), - Serializer.Serialize(value), - options ?? DefaultCacheOptions - ); + hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; + + try + { + Cache.Set( + NormalizeKey(key), + Serializer.Serialize(value), + options ?? DefaultCacheOptions + ); + } + catch (Exception ex) + { + if (hideErrors == true) + { + HandleException(ex); + return; + } + + throw; + } } - catch (Exception ex) + + if (ShouldConsiderUow(considerUow)) { - if (hideErrors == true) + var uowCache = GetUnitOfWorkCache(); + if (uowCache.TryGetValue(key, out _)) { - HandleException(ex); - return; + uowCache[key].SetValue(value); + } + else + { + uowCache.Add(key, new UnitOfWorkCacheItem(value)); } - throw; + // ReSharper disable once PossibleNullReferenceException + UnitOfWorkManager.Current.OnCompleted(() => + { + SetRealCache(); + return Task.CompletedTask; + }); + } + else + { + SetRealCache(); } } - /// /// Sets the cache item value for the provided key. /// @@ -457,6 +599,7 @@ namespace Volo.Abp.Caching /// The cache item value to set in the cache. /// The cache options for the value. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// The for the task. /// The indicating that the operation is asynchronous. public virtual async Task SetAsync( @@ -464,35 +607,60 @@ namespace Volo.Abp.Caching TCacheItem value, DistributedCacheEntryOptions options = null, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default) { - hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; - - try + async Task SetRealCache() { - await Cache.SetAsync( - NormalizeKey(key), - Serializer.Serialize(value), - options ?? DefaultCacheOptions, - CancellationTokenProvider.FallbackToProvider(token) - ); - } - catch (Exception ex) - { - if (hideErrors == true) + hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; + + try { - await HandleExceptionAsync(ex); - return; + await Cache.SetAsync( + NormalizeKey(key), + Serializer.Serialize(value), + options ?? DefaultCacheOptions, + CancellationTokenProvider.FallbackToProvider(token) + ); } + catch (Exception ex) + { + if (hideErrors == true) + { + await HandleExceptionAsync(ex); + return; + } - throw; + throw; + } } + + if (ShouldConsiderUow(considerUow)) + { + var uowCache = GetUnitOfWorkCache(); + if (uowCache.TryGetValue(key, out _)) + { + uowCache[key].SetValue(value); + } + else + { + uowCache.Add(key, new UnitOfWorkCacheItem(value)); + } + + // ReSharper disable once PossibleNullReferenceException + UnitOfWorkManager.Current.OnCompleted(SetRealCache); + } + else + { + await SetRealCache(); + } } public void SetMany( IEnumerable> items, DistributedCacheEntryOptions options = null, - bool? hideErrors = null) + bool? hideErrors = null, + bool considerUow = false) { var itemsArray = items.ToArray(); @@ -502,40 +670,73 @@ namespace Volo.Abp.Caching SetManyFallback( itemsArray, options, - hideErrors + hideErrors, + considerUow ); - + return; } - - hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; - try + void SetRealCache() { - cacheSupportsMultipleItems.SetMany( - ToRawCacheItems(itemsArray), - options ?? DefaultCacheOptions - ); + hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; + + try + { + cacheSupportsMultipleItems.SetMany( + ToRawCacheItems(itemsArray), + options ?? DefaultCacheOptions + ); + } + catch (Exception ex) + { + if (hideErrors == true) + { + HandleException(ex); + return; + } + + throw; + } } - catch (Exception ex) + + if (ShouldConsiderUow(considerUow)) { - if (hideErrors == true) + var uowCache = GetUnitOfWorkCache(); + + foreach (var pair in itemsArray) { - HandleException(ex); - return; + if (uowCache.TryGetValue(pair.Key, out _)) + { + uowCache[pair.Key].SetValue(pair.Value); + } + else + { + uowCache.Add(pair.Key, new UnitOfWorkCacheItem(pair.Value)); + } } - throw; + // ReSharper disable once PossibleNullReferenceException + UnitOfWorkManager.Current.OnCompleted(() => + { + SetRealCache(); + return Task.CompletedTask; + }); + } + else + { + SetRealCache(); } } - + protected virtual void SetManyFallback( KeyValuePair[] items, DistributedCacheEntryOptions options = null, - bool? hideErrors = null) + bool? hideErrors = null, + bool considerUow = false) { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; - + try { foreach (var item in items) @@ -543,8 +744,9 @@ namespace Volo.Abp.Caching Set( item.Key, item.Value, - options: options, - hideErrors: false + options, + false, + considerUow ); } } @@ -564,6 +766,7 @@ namespace Volo.Abp.Caching IEnumerable> items, DistributedCacheEntryOptions options = null, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default) { var itemsArray = items.ToArray(); @@ -575,38 +778,67 @@ namespace Volo.Abp.Caching itemsArray, options, hideErrors, + considerUow, token ); - + return; } - - hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; - try + async Task SetRealCache() { - await cacheSupportsMultipleItems.SetManyAsync( - ToRawCacheItems(itemsArray), - options ?? DefaultCacheOptions, - CancellationTokenProvider.FallbackToProvider(token) - ); + hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; + + try + { + await cacheSupportsMultipleItems.SetManyAsync( + ToRawCacheItems(itemsArray), + options ?? DefaultCacheOptions, + CancellationTokenProvider.FallbackToProvider(token) + ); + } + catch (Exception ex) + { + if (hideErrors == true) + { + await HandleExceptionAsync(ex); + return; + } + + throw; + } } - catch (Exception ex) + + if (ShouldConsiderUow(considerUow)) { - if (hideErrors == true) + var uowCache = GetUnitOfWorkCache(); + + foreach (var pair in itemsArray) { - await HandleExceptionAsync(ex); - return; + if (uowCache.TryGetValue(pair.Key, out _)) + { + uowCache[pair.Key].SetValue(pair.Value); + } + else + { + uowCache.Add(pair.Key, new UnitOfWorkCacheItem(pair.Value)); + } } - throw; + // ReSharper disable once PossibleNullReferenceException + UnitOfWorkManager.Current.OnCompleted(SetRealCache); + } + else + { + await SetRealCache(); } } - + protected virtual async Task SetManyFallbackAsync( KeyValuePair[] items, DistributedCacheEntryOptions options = null, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default) { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; @@ -618,8 +850,9 @@ namespace Volo.Abp.Caching await SetAsync( item.Key, item.Value, - options: options, - hideErrors: false, + options, + false, + considerUow, token: token ); } @@ -636,9 +869,14 @@ namespace Volo.Abp.Caching } } + /// + /// Refreshes the cache value of the given key, and resets its sliding expiration timeout. + /// + /// The key of cached item to be retrieved from the cache. + /// Indicates to throw or hide the exceptions for the distributed cache. public virtual void Refresh( - TCacheKey key, bool? - hideErrors = null) + TCacheKey key, + bool? hideErrors = null) { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; @@ -658,6 +896,13 @@ namespace Volo.Abp.Caching } } + /// + /// Refreshes the cache value of the given key, and resets its sliding expiration timeout. + /// + /// The key of cached item to be retrieved from the cache. + /// Indicates to throw or hide the exceptions for the distributed cache. + /// The for the task. + /// The indicating that the operation is asynchronous. public virtual async Task RefreshAsync( TCacheKey key, bool? hideErrors = null, @@ -681,48 +926,106 @@ namespace Volo.Abp.Caching } } + /// + /// Removes the cache item for given key from cache. + /// + /// The key of cached item to be retrieved from the cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. + /// Indicates to throw or hide the exceptions for the distributed cache. public virtual void Remove( TCacheKey key, - bool? hideErrors = null) + bool? hideErrors = null, + bool considerUow = false) { - hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; - - try + void RemoveRealCache() { - Cache.Remove(NormalizeKey(key)); + hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; + + try + { + Cache.Remove(NormalizeKey(key)); + } + catch (Exception ex) + { + if (hideErrors == true) + { + HandleException(ex); + return; + } + + throw; + } } - catch (Exception ex) + + if (ShouldConsiderUow(considerUow)) { - if (hideErrors == true) + var uowCache = GetUnitOfWorkCache(); + if (uowCache.TryGetValue(key, out _)) { - HandleException(ex); - return; + uowCache[key].RemoveValue(); } - throw; + // ReSharper disable once PossibleNullReferenceException + UnitOfWorkManager.Current.OnCompleted(() => + { + RemoveRealCache(); + return Task.CompletedTask; + }); + } + else + { + RemoveRealCache(); } } + /// + /// Removes the cache item for given key from cache. + /// + /// The key of cached item to be retrieved from the cache. + /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. + /// The for the task. + /// The indicating that the operation is asynchronous. public virtual async Task RemoveAsync( TCacheKey key, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default) { - hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; - - try + async Task RemoveRealCache() { - await Cache.RemoveAsync(NormalizeKey(key), CancellationTokenProvider.FallbackToProvider(token)); + hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; + + try + { + await Cache.RemoveAsync(NormalizeKey(key), CancellationTokenProvider.FallbackToProvider(token)); + } + catch (Exception ex) + { + if (hideErrors == true) + { + await HandleExceptionAsync(ex); + return; + } + + throw; + } } - catch (Exception ex) + + if (ShouldConsiderUow(considerUow)) { - if (hideErrors == true) + var uowCache = GetUnitOfWorkCache(); + if (uowCache.TryGetValue(key, out _)) { - await HandleExceptionAsync(ex); - return; + uowCache[key].RemoveValue(); } - throw; + // ReSharper disable once PossibleNullReferenceException + UnitOfWorkManager.Current.OnCompleted(RemoveRealCache); + } + else + { + await RemoveRealCache(); } } @@ -730,7 +1033,7 @@ namespace Volo.Abp.Caching { AsyncHelper.RunSync(() => HandleExceptionAsync(ex)); } - + protected virtual async Task HandleExceptionAsync(Exception ex) { Logger.LogException(ex, LogLevel.Warning); @@ -742,7 +1045,7 @@ namespace Volo.Abp.Caching .NotifyAsync(new ExceptionNotificationContext(ex, LogLevel.Warning)); } } - + protected virtual KeyValuePair[] ToCacheItems(byte[][] itemBytes, TCacheKey[] itemKeys) { if (itemBytes.Length != itemKeys.Length) @@ -764,7 +1067,7 @@ namespace Volo.Abp.Caching return result.ToArray(); } - + [CanBeNull] protected virtual TCacheItem ToCacheItem([CanBeNull] byte[] bytes) { @@ -786,12 +1089,33 @@ namespace Volo.Abp.Caching ) ).ToArray(); } - + private static KeyValuePair[] ToCacheItemsWithDefaultValues(TCacheKey[] keys) { return keys .Select(key => new KeyValuePair(key, default)) .ToArray(); } + + protected virtual bool ShouldConsiderUow(bool considerUow) + { + return considerUow && UnitOfWorkManager.Current != null; + } + + protected virtual string GetUnitOfWorkCacheKey() + { + return UowCacheName + CacheName; + } + + protected virtual Dictionary> GetUnitOfWorkCache() + { + if (UnitOfWorkManager.Current == null) + { + throw new AbpException($"There is no active UOW."); + } + + return UnitOfWorkManager.Current.GetOrAddItem(GetUnitOfWorkCacheKey(), + key => new Dictionary>()); + } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/IDistributedCache.cs b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/IDistributedCache.cs index 80fe124785..2f74fd678b 100644 --- a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/IDistributedCache.cs +++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/IDistributedCache.cs @@ -30,42 +30,48 @@ namespace Volo.Abp.Caching /// /// The key of cached item to be retrieved from the cache. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// The cache item, or null. TCacheItem Get( TCacheKey key, - bool? hideErrors = null + bool? hideErrors = null, + bool considerUow = false ); - + /// /// Gets multiple cache items with the given keys. /// /// The returned list contains exactly the same count of items specified in the given keys. /// An item in the return list can not be null, but an item in the list has null value - /// if the related key not found in the cache. + /// if the related key not found in the cache. /// /// The keys of cached items to be retrieved from the cache. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// List of cache items. KeyValuePair[] GetMany( IEnumerable keys, - bool? hideErrors = null + bool? hideErrors = null, + bool considerUow = false ); - + /// /// Gets multiple cache items with the given keys. /// /// The returned list contains exactly the same count of items specified in the given keys. /// An item in the return list can not be null, but an item in the list has null value /// if the related key not found in the cache. - /// + /// /// /// The keys of cached items to be retrieved from the cache. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// /// The for the task. /// List of cache items. Task[]> GetManyAsync( IEnumerable keys, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default ); @@ -74,11 +80,13 @@ namespace Volo.Abp.Caching /// /// The key of cached item to be retrieved from the cache. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// The for the task. /// The cache item, or null. Task GetAsync( [NotNull] TCacheKey key, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default ); @@ -90,12 +98,14 @@ namespace Volo.Abp.Caching /// The factory delegate is used to provide the cache item when no cache item is found for the given . /// The cache options for the factory delegate. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// The cache item. TCacheItem GetOrAdd( TCacheKey key, Func factory, Func optionsFactory = null, - bool? hideErrors = null + bool? hideErrors = null, + bool considerUow = false ); /// @@ -106,6 +116,7 @@ namespace Volo.Abp.Caching /// The factory delegate is used to provide the cache item when no cache item is found for the given . /// The cache options for the factory delegate. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// The for the task. /// The cache item. Task GetOrAddAsync( @@ -113,6 +124,7 @@ namespace Volo.Abp.Caching Func> factory, Func optionsFactory = null, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default ); @@ -123,11 +135,13 @@ namespace Volo.Abp.Caching /// The cache item value to set in the cache. /// The cache options for the value. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. void Set( TCacheKey key, TCacheItem value, DistributedCacheEntryOptions options = null, - bool? hideErrors = null + bool? hideErrors = null, + bool considerUow = false ); /// @@ -137,6 +151,7 @@ namespace Volo.Abp.Caching /// The cache item value to set in the cache. /// The cache options for the value. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// The for the task. /// The indicating that the operation is asynchronous. Task SetAsync( @@ -144,6 +159,7 @@ namespace Volo.Abp.Caching [NotNull] TCacheItem value, [CanBeNull] DistributedCacheEntryOptions options = null, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default ); @@ -154,12 +170,14 @@ namespace Volo.Abp.Caching /// Items to set on the cache /// The cache options for the value. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. void SetMany( IEnumerable> items, DistributedCacheEntryOptions options = null, - bool? hideErrors = null + bool? hideErrors = null, + bool considerUow = false ); - + /// /// Sets multiple cache items. /// Based on the implementation, this can be more efficient than setting multiple items individually. @@ -167,12 +185,14 @@ namespace Volo.Abp.Caching /// Items to set on the cache /// The cache options for the value. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// The for the task. /// The indicating that the operation is asynchronous. Task SetManyAsync( IEnumerable> items, DistributedCacheEntryOptions options = null, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default ); @@ -204,9 +224,11 @@ namespace Volo.Abp.Caching /// /// The key of cached item to be retrieved from the cache. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. void Remove( TCacheKey key, - bool? hideErrors = null + bool? hideErrors = null, + bool considerUow = false ); /// @@ -214,11 +236,13 @@ namespace Volo.Abp.Caching /// /// The key of cached item to be retrieved from the cache. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// The for the task. /// The indicating that the operation is asynchronous. Task RemoveAsync( TCacheKey key, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default ); } diff --git a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/UnitOfWorkCacheItem.cs b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/UnitOfWorkCacheItem.cs new file mode 100644 index 0000000000..c37475d022 --- /dev/null +++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/UnitOfWorkCacheItem.cs @@ -0,0 +1,43 @@ +using System; + +namespace Volo.Abp.Caching +{ + [Serializable] + public class UnitOfWorkCacheItem + where TValue : class + { + public bool IsRemoved { get; set; } + + public TValue Value { get; set; } + + public UnitOfWorkCacheItem() + { + + } + + public UnitOfWorkCacheItem(TValue value) + { + Value = value; + } + + public UnitOfWorkCacheItem(TValue value, bool isRemoved) + { + Value = value; + IsRemoved = isRemoved; + } + + public UnitOfWorkCacheItem SetValue(TValue value) + { + Value = value; + IsRemoved = false; + return this; + } + + public UnitOfWorkCacheItem RemoveValue() + { + Value = null; + IsRemoved = true; + return this; + } + } +} diff --git a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/UnitOfWorkCacheItemExtensions.cs b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/UnitOfWorkCacheItemExtensions.cs new file mode 100644 index 0000000000..46757f80cf --- /dev/null +++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/UnitOfWorkCacheItemExtensions.cs @@ -0,0 +1,11 @@ +namespace Volo.Abp.Caching +{ + public static class UnitOfWorkCacheItemExtensions + { + public static TValue GetUnRemovedValueOrNull(this UnitOfWorkCacheItem item) + where TValue : class + { + return item != null && !item.IsRemoved ? item.Value : null; + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs index dbffa820c8..0244d46c47 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs @@ -1,5 +1,4 @@ -using System; -using System.Text; +using System.Text; using Volo.Abp.Cli.Commands; using Volo.Abp.Domain; using Volo.Abp.IdentityModel; @@ -31,8 +30,9 @@ namespace Volo.Abp.Cli options.Commands["logout"] = typeof(LogoutCommand); options.Commands["generate-proxy"] = typeof(GenerateProxyCommand); options.Commands["suite"] = typeof(SuiteCommand); - options.Commands["switch-to-preview"] = typeof(SwitchNightlyPreviewCommand); - options.Commands["switch-to-stable"] = typeof(SwitchStableCommand); + options.Commands["switch-to-preview"] = typeof(SwitchToPreviewCommand); + options.Commands["switch-to-stable"] = typeof(SwitchToStableCommand); + options.Commands["switch-to-nightly"] = typeof(SwitchToNightlyCommand); options.Commands["translate"] = typeof(TranslateCommand); }); } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliService.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliService.cs index 436e169aad..ef6eb79042 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliService.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliService.cs @@ -158,7 +158,7 @@ namespace Volo.Abp.Cli return await NuGetService.GetLatestVersionOrNullAsync("Volo.Abp.Cli"); case UpdateChannel.Prerelease: - return await NuGetService.GetLatestVersionOrNullAsync("Volo.Abp.Cli", includePreviews: true); + return await NuGetService.GetLatestVersionOrNullAsync("Volo.Abp.Cli", includeReleaseCandidates: true); case UpdateChannel.Nightly: return await NuGetService.GetLatestVersionOrNullAsync("Volo.Abp.Cli", includeNightly: true); diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddModuleCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddModuleCommand.cs index b37f67fd1a..5e690d8bb0 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddModuleCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddModuleCommand.cs @@ -75,6 +75,7 @@ namespace Volo.Abp.Cli.Commands sb.AppendLine(" -s|--solution Specify the solution file explicitly."); sb.AppendLine(" --skip-db-migrations Specify if a new migration will be added or not."); sb.AppendLine(" -sp|--startup-project Relative path to the project folder of the startup project. Default value is the current folder."); + sb.AppendLine(" -v|--version Specify the version of the module. Default is your project's ABP version."); sb.AppendLine(""); sb.AppendLine("Examples:"); sb.AppendLine(""); @@ -152,7 +153,7 @@ namespace Volo.Abp.Cli.Commands public const string Skip = "skip-db-migrations"; } - public static class StartupProject + public static class StartupProject { public const string Short = "sp"; public const string Long = "startup-project"; diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddPackageCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddPackageCommand.cs index 03e228f255..7ea20eccf4 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddPackageCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddPackageCommand.cs @@ -34,9 +34,12 @@ namespace Volo.Abp.Cli.Commands ); } + var version = commandLineArgs.Options.GetOrNull(Options.Version.Short, Options.Version.Long); + await ProjectNugetPackageAdder.AddAsync( GetProjectFile(commandLineArgs), - commandLineArgs.Target + commandLineArgs.Target, + version ); } @@ -55,6 +58,7 @@ namespace Volo.Abp.Cli.Commands sb.AppendLine("Options:"); sb.AppendLine(""); sb.AppendLine(" -p|--project Specify the project file explicitly."); + sb.AppendLine(" -v|--version Specify the version of the package. Default is your project's ABP version or latest ABP version."); sb.AppendLine(""); sb.AppendLine("Examples:"); sb.AppendLine(""); @@ -119,6 +123,12 @@ namespace Volo.Abp.Cli.Commands public const string Short = "p"; public const string Long = "project"; } + + public static class Version + { + public const string Short = "v"; + public const string Long = "version"; + } } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/GetSourceCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/GetSourceCommand.cs index 0a6155fa67..7cda59b999 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/GetSourceCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/GetSourceCommand.cs @@ -61,7 +61,7 @@ namespace Volo.Abp.Cli.Commands } commandLineArgs.Options.Add(CliConsts.Command, commandLineArgs.Command); - + await _sourceCodeDownloadService.DownloadAsync( commandLineArgs.Target, outputFolder, version, gitHubAbpLocalRepositoryPath, gitHubVoloLocalRepositoryPath, commandLineArgs.Options); } @@ -99,6 +99,7 @@ namespace Volo.Abp.Cli.Commands sb.AppendLine(""); sb.AppendLine("-o|--output-folder (default: current folder)"); sb.AppendLine("-v|--version (default: latest version)"); + sb.AppendLine("--preview (Use latest pre-release version if there is at least one pre-release after latest stable version)"); sb.AppendLine(""); sb.AppendLine("Examples:"); sb.AppendLine(""); @@ -138,6 +139,11 @@ namespace Volo.Abp.Cli.Commands public const string Short = "v"; public const string Long = "version"; } + + public static class Preview + { + public const string Long = "preview"; + } } } } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs index 8a6d944226..4a713ceb9e 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs @@ -62,6 +62,12 @@ namespace Volo.Abp.Cli.Commands Logger.LogInformation("Tiered: yes"); } + var preview = commandLineArgs.Options.ContainsKey(Options.Preview.Long); + if (preview) + { + Logger.LogInformation("Preview: yes if any exist for next version."); + } + var databaseProvider = GetDatabaseProvider(commandLineArgs); if (databaseProvider != DatabaseProvider.NotSpecified) { @@ -218,6 +224,7 @@ namespace Volo.Abp.Cli.Commands sb.AppendLine("-d|--database-provider (if supported by the template)"); sb.AppendLine("-o|--output-folder (default: current folder)"); sb.AppendLine("-v|--version (default: latest version)"); + sb.AppendLine("--preview (Use latest pre-release version if there is at least one pre-release after latest stable version)"); sb.AppendLine("-ts|--template-source (your local or network abp template source)"); sb.AppendLine("-csf|--create-solution-folder (default: true)"); sb.AppendLine("-cs|--connection-string (your database connection string)"); @@ -369,6 +376,11 @@ namespace Volo.Abp.Cli.Commands { public const string Long = "tiered"; } + + public static class Preview + { + public const string Long = "preview"; + } } } } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchNightlyPreviewCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchToNightlyCommand.cs similarity index 64% rename from framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchNightlyPreviewCommand.cs rename to framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchToNightlyCommand.cs index 883ac4db2f..473489990f 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchNightlyPreviewCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchToNightlyCommand.cs @@ -6,18 +6,18 @@ using Volo.Abp.DependencyInjection; namespace Volo.Abp.Cli.Commands { - public class SwitchNightlyPreviewCommand : IConsoleCommand, ITransientDependency + public class SwitchToNightlyCommand : IConsoleCommand, ITransientDependency { - private readonly PackageSourceSwitcher _packageSourceSwitcher; + private readonly PackagePreviewSwitcher _packagePreviewSwitcher; - public SwitchNightlyPreviewCommand(PackageSourceSwitcher packageSourceSwitcher) + public SwitchToNightlyCommand(PackagePreviewSwitcher packagePreviewSwitcher) { - _packageSourceSwitcher = packageSourceSwitcher; + _packagePreviewSwitcher = packagePreviewSwitcher; } public async Task ExecuteAsync(CommandLineArgs commandLineArgs) { - await _packageSourceSwitcher.SwitchToPreview(commandLineArgs); + await _packagePreviewSwitcher.SwitchToNightlyPreview(commandLineArgs); } public string GetUsageInfo() @@ -26,8 +26,8 @@ namespace Volo.Abp.Cli.Commands sb.AppendLine(""); sb.AppendLine("Usage:"); - sb.AppendLine(" abp switch-to-preview [options]"); - sb.AppendLine(""); + sb.AppendLine(" abp switch-to-nightly [options]"); + sb.AppendLine(""); sb.AppendLine("Options:"); sb.AppendLine("-sd|--solution-directory"); sb.AppendLine(""); @@ -41,4 +41,4 @@ namespace Volo.Abp.Cli.Commands return "Switches packages to nightly preview ABP version."; } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchToPreviewCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchToPreviewCommand.cs new file mode 100644 index 0000000000..e6c401a8b5 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchToPreviewCommand.cs @@ -0,0 +1,44 @@ +using System.Text; +using System.Threading.Tasks; +using Volo.Abp.Cli.Args; +using Volo.Abp.Cli.ProjectModification; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Cli.Commands +{ + public class SwitchToPreviewCommand : IConsoleCommand, ITransientDependency + { + private readonly PackagePreviewSwitcher _packagePreviewSwitcher; + + public SwitchToPreviewCommand(PackagePreviewSwitcher packagePreviewSwitcher) + { + _packagePreviewSwitcher = packagePreviewSwitcher; + } + + public async Task ExecuteAsync(CommandLineArgs commandLineArgs) + { + await _packagePreviewSwitcher.SwitchToPreview(commandLineArgs); + } + + public string GetUsageInfo() + { + var sb = new StringBuilder(); + + sb.AppendLine(""); + sb.AppendLine("Usage:"); + sb.AppendLine(" abp switch-to-preview [options]"); + sb.AppendLine(""); + sb.AppendLine("Options:"); + sb.AppendLine("-sd|--solution-directory"); + sb.AppendLine(""); + sb.AppendLine("See the documentation for more info: https://docs.abp.io/en/abp/latest/CLI"); + + return sb.ToString(); + } + + public string GetShortDescription() + { + return "Switches packages to preview ABP version."; + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchStableCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchToStableCommand.cs similarity index 72% rename from framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchStableCommand.cs rename to framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchToStableCommand.cs index 54db7bc082..61545a3da5 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchStableCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchToStableCommand.cs @@ -6,18 +6,18 @@ using Volo.Abp.DependencyInjection; namespace Volo.Abp.Cli.Commands { - public class SwitchStableCommand : IConsoleCommand, ITransientDependency + public class SwitchToStableCommand : IConsoleCommand, ITransientDependency { - private readonly PackageSourceSwitcher _packageSourceSwitcher; + private readonly PackagePreviewSwitcher _packagePreviewSwitcher; - public SwitchStableCommand(PackageSourceSwitcher packageSourceSwitcher) + public SwitchToStableCommand(PackagePreviewSwitcher packagePreviewSwitcher) { - _packageSourceSwitcher = packageSourceSwitcher; + _packagePreviewSwitcher = packagePreviewSwitcher; } public async Task ExecuteAsync(CommandLineArgs commandLineArgs) { - await _packageSourceSwitcher.SwitchToStable(commandLineArgs); + await _packagePreviewSwitcher.SwitchToStable(commandLineArgs); } public string GetUsageInfo() @@ -41,4 +41,4 @@ namespace Volo.Abp.Cli.Commands return "Switches packages to stable ABP version from preview version."; } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/NuGet/NuGetService.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/NuGet/NuGetService.cs index 0fd35582bf..c962ceaf92 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/NuGet/NuGetService.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/NuGet/NuGetService.cs @@ -39,7 +39,7 @@ namespace Volo.Abp.Cli.NuGet Logger = NullLogger.Instance; } - public async Task GetLatestVersionOrNullAsync(string packageId, bool includePreviews = false, bool includeNightly = false) + public async Task GetLatestVersionOrNullAsync(string packageId, bool includeNightly = false, bool includeReleaseCandidates = false) { if (AuthService.IsLoggedIn()) { @@ -78,11 +78,12 @@ namespace Volo.Abp.Cli.NuGet var versions = JsonSerializer .Deserialize(responseContent) .Versions - .Select(SemanticVersion.Parse); + .Select(SemanticVersion.Parse) + .OrderByDescending(v=> v, new VersionComparer()).ToList(); - if (!includePreviews && !includeNightly) + if (!includeNightly && !includeReleaseCandidates) { - versions = versions.Where(x => !x.IsPrerelease); + versions = versions.Where(x => !x.IsPrerelease).ToList(); } var semanticVersions = versions.ToList(); diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs index f221186636..106d7b5bcc 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs @@ -52,10 +52,11 @@ namespace Volo.Abp.Cli.ProjectBuilding string name, string type, string version = null, - string templateSource = null) + string templateSource = null, + bool includePreReleases = false) { DirectoryHelper.CreateIfNotExists(CliPaths.TemplateCache); - var latestVersion = version ?? await GetLatestSourceCodeVersionAsync(name, type); + var latestVersion = version ?? await GetLatestSourceCodeVersionAsync(name, type, null, includePreReleases); if (version == null) { @@ -110,7 +111,8 @@ namespace Volo.Abp.Cli.ProjectBuilding Name = name, Type = type, TemplateSource = templateSource, - Version = version + Version = version, + IncludePreReleases = includePreReleases } ); @@ -122,7 +124,7 @@ namespace Volo.Abp.Cli.ProjectBuilding return new TemplateFile(fileContent, version, latestVersion, nugetVersion); } - private async Task GetLatestSourceCodeVersionAsync(string name, string type, string url = null) + private async Task GetLatestSourceCodeVersionAsync(string name, string type, string url = null, bool includePreReleases = false) { if (url == null) { @@ -137,7 +139,7 @@ namespace Volo.Abp.Cli.ProjectBuilding url, new StringContent( JsonSerializer.Serialize( - new GetLatestSourceCodeVersionDto { Name = name } + new GetLatestSourceCodeVersionDto { Name = name, IncludePreReleases = includePreReleases } ), Encoding.UTF8, MimeTypes.Application.Json @@ -171,7 +173,7 @@ namespace Volo.Abp.Cli.ProjectBuilding url, new StringContent( JsonSerializer.Serialize( - new GetTemplateNugetVersionDto { Name = name, Version = version } + new GetTemplateNugetVersionDto { Name = name, Version = version} ), Encoding.UTF8, MimeTypes.Application.Json @@ -260,11 +262,15 @@ namespace Volo.Abp.Cli.ProjectBuilding public string Type { get; set; } public string TemplateSource { get; set; } + + public bool IncludePreReleases { get; set; } } public class GetLatestSourceCodeVersionDto { public string Name { get; set; } + + public bool IncludePreReleases { get; set; } } public class GetTemplateNugetVersionDto @@ -272,6 +278,8 @@ namespace Volo.Abp.Cli.ProjectBuilding public string Name { get; set; } public string Version { get; set; } + + public bool IncludePreReleases { get; set; } } public class GetVersionResultDto diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ISourceCodeStore.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ISourceCodeStore.cs index de082d4bcf..af03a6095b 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ISourceCodeStore.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ISourceCodeStore.cs @@ -9,7 +9,8 @@ namespace Volo.Abp.Cli.ProjectBuilding string name, string type, [CanBeNull] string version = null, - [CanBeNull] string templateSource = null + [CanBeNull] string templateSource = null, + bool includePreReleases = false ); } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ModuleProjectBuilder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ModuleProjectBuilder.cs index 6a7132769a..17b9585f16 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ModuleProjectBuilder.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ModuleProjectBuilder.cs @@ -48,7 +48,9 @@ namespace Volo.Abp.Cli.ProjectBuilding var templateFile = await SourceCodeStore.GetAsync( args.TemplateName, SourceCodeTypes.Module, - args.Version + args.Version, + null, + args.ExtraProperties.ContainsKey(GetSourceCommand.Options.Preview.Long) ); var apiKeyResult = await ApiKeyService.GetApiKeyOrNullAsync(); diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/TemplateProjectBuilder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/TemplateProjectBuilder.cs index e8ddc804c2..02b26eba39 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/TemplateProjectBuilder.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/TemplateProjectBuilder.cs @@ -57,7 +57,8 @@ namespace Volo.Abp.Cli.ProjectBuilding args.TemplateName, SourceCodeTypes.Template, args.Version, - args.TemplateSource + args.TemplateSource, + args.ExtraProperties.ContainsKey(NewCommand.Options.Preview.Long) ); DeveloperApiKeyResult apiKeyResult = null; @@ -119,6 +120,7 @@ namespace Volo.Abp.Cli.ProjectBuilding var options = args.ExtraProperties .Where(x => !x.Key.Equals(CliConsts.Command, StringComparison.InvariantCultureIgnoreCase)) .Where(x => !x.Key.Equals(NewCommand.Options.Tiered.Long, StringComparison.InvariantCultureIgnoreCase)) + .Where(x => !x.Key.Equals(NewCommand.Options.Preview.Long, StringComparison.InvariantCultureIgnoreCase)) .Where(x => !x.Key.Equals(NewCommand.Options.DatabaseProvider.Long, StringComparison.InvariantCultureIgnoreCase) && !x.Key.Equals(NewCommand.Options.DatabaseProvider.Short, StringComparison.InvariantCultureIgnoreCase)) .Where(x => !x.Key.Equals(NewCommand.Options.OutputFolder.Long, StringComparison.InvariantCultureIgnoreCase) && diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmApplicationType.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmApplicationType.cs index 9f58aa52e9..0e05a1ffb0 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmApplicationType.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmApplicationType.cs @@ -5,6 +5,7 @@ namespace Volo.Abp.Cli.ProjectModification [Flags] public enum NpmApplicationType { - Mvc = 1 + Mvc = 1, + Angular = 2 } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmPackagesUpdater.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmPackagesUpdater.cs index 4730fed899..359ad09684 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmPackagesUpdater.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmPackagesUpdater.cs @@ -10,6 +10,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using NuGet.Versioning; using Volo.Abp.Cli.Http; using Volo.Abp.Cli.Utils; using Volo.Abp.DependencyInjection; @@ -42,7 +43,7 @@ namespace Volo.Abp.Cli.ProjectModification Logger = NullLogger.Instance; } - public async Task Update(string rootDirectory, bool includePreviews = false, bool switchToStable = false) + public async Task Update(string rootDirectory, bool includePreviews = false, bool includeReleaseCandidates = false, bool switchToStable = false) { var fileList = _packageJsonFileFinder.Find(rootDirectory); @@ -57,7 +58,7 @@ namespace Volo.Abp.Cli.ProjectModification async Task UpdateAsync(string file) { - var updated = await UpdatePackagesInFile(file, includePreviews, switchToStable); + var updated = await UpdatePackagesInFile(file, includePreviews,includeReleaseCandidates, switchToStable); packagesUpdated.TryAdd(file, updated); } @@ -176,7 +177,10 @@ namespace Volo.Abp.Cli.ProjectModification return File.Exists(Path.Combine(fileDirectory, "angular.json")); } - protected virtual async Task UpdatePackagesInFile(string filePath, bool includePreviews = false, + protected virtual async Task UpdatePackagesInFile( + string filePath, + bool includePreviews = false, + bool includeReleaseCandidates = false, bool switchToStable = false) { var packagesUpdated = false; @@ -191,7 +195,7 @@ namespace Volo.Abp.Cli.ProjectModification foreach (var abpPackage in abpPackages) { - var updated = await TryUpdatingPackage(filePath, abpPackage, includePreviews, switchToStable); + var updated = await TryUpdatingPackage(filePath, abpPackage, includePreviews, includeReleaseCandidates, switchToStable); if (updated) { @@ -210,18 +214,26 @@ namespace Volo.Abp.Cli.ProjectModification string filePath, JProperty package, bool includePreviews = false, + bool includeReleaseCandidates = false, bool switchToStable = false) { var currentVersion = (string) package.Value; var version = ""; - if (includePreviews || (!switchToStable && currentVersion.Contains("-preview"))) + if ((includePreviews || (!switchToStable && currentVersion.Contains("-preview"))) && !includeReleaseCandidates) { version = "preview"; } else { - version = await GetLatestVersion(package); + if (!switchToStable && SemanticVersion.Parse(currentVersion).IsPrerelease) + { + version = await GetLatestVersion(package, true); + } + else + { + version = await GetLatestVersion(package, includeReleaseCandidates); + } } if (version == currentVersion) @@ -237,14 +249,22 @@ namespace Volo.Abp.Cli.ProjectModification } protected virtual async Task GetLatestVersion( - JProperty package) + JProperty package, + bool includeReleaseCandidates = false) { if (_fileVersionStorage.ContainsKey(package.Name)) { return _fileVersionStorage[package.Name]; } - var newVersion = CmdHelper.RunCmdAndGetOutput($"npm show {package.Name} version"); + var versionListAsJson = CmdHelper.RunCmdAndGetOutput($"npm show {package.Name} versions"); + var versionList = JsonConvert.DeserializeObject(versionListAsJson) + .OrderByDescending(SemanticVersion.Parse, new VersionComparer()).ToList(); + + var newVersion = includeReleaseCandidates + ? versionList.First() + : versionList.FirstOrDefault(v => !SemanticVersion.Parse(v).IsPrerelease); + var newVersionWithPrefix = $"~{newVersion}"; _fileVersionStorage[package.Name] = newVersionWithPrefix; diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/PackageSourceSwitcher.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/PackagePreviewSwitcher.cs similarity index 73% rename from framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/PackageSourceSwitcher.cs rename to framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/PackagePreviewSwitcher.cs index f6c89be039..60c7fd21b9 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/PackageSourceSwitcher.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/PackagePreviewSwitcher.cs @@ -9,31 +9,46 @@ using Volo.Abp.DependencyInjection; namespace Volo.Abp.Cli.ProjectModification { - public class PackageSourceSwitcher : ITransientDependency + public class PackagePreviewSwitcher : ITransientDependency { - private readonly PackageSourceAdder _packageSourceAdder; + private readonly PackageSourceManager _packageSourceManager; private readonly NpmPackagesUpdater _npmPackagesUpdater; private readonly VoloNugetPackagesVersionUpdater _nugetPackagesVersionUpdater; - public ILogger Logger { get; set; } + public ILogger Logger { get; set; } - public PackageSourceSwitcher(PackageSourceAdder packageSourceAdder, + public PackagePreviewSwitcher(PackageSourceManager packageSourceManager, NpmPackagesUpdater npmPackagesUpdater, VoloNugetPackagesVersionUpdater nugetPackagesVersionUpdater) { - _packageSourceAdder = packageSourceAdder; + _packageSourceManager = packageSourceManager; _npmPackagesUpdater = npmPackagesUpdater; _nugetPackagesVersionUpdater = nugetPackagesVersionUpdater; - Logger = NullLogger.Instance; + Logger = NullLogger.Instance; } public async Task SwitchToPreview(CommandLineArgs commandLineArgs) { - _packageSourceAdder.Add("ABP Nightly", "https://www.myget.org/F/abp-nightly/api/v3/index.json"); + var solutionPath = GetSolutionPath(commandLineArgs); + var solutionFolder = GetSolutionFolder(commandLineArgs); + + await _nugetPackagesVersionUpdater.UpdateSolutionAsync( + solutionPath, + includeReleaseCandidates: true); + + await _npmPackagesUpdater.Update( + solutionFolder, + false, + true); + } + public async Task SwitchToNightlyPreview(CommandLineArgs commandLineArgs) + { var solutionPath = GetSolutionPath(commandLineArgs); var solutionFolder = GetSolutionFolder(commandLineArgs); + _packageSourceManager.Add(solutionFolder, "ABP Nightly", "https://www.myget.org/F/abp-nightly/api/v3/index.json"); + await _nugetPackagesVersionUpdater.UpdateSolutionAsync( solutionPath, true); @@ -48,14 +63,18 @@ namespace Volo.Abp.Cli.ProjectModification var solutionPath = GetSolutionPath(commandLineArgs); var solutionFolder = GetSolutionFolder(commandLineArgs); + _packageSourceManager.Remove(solutionFolder, "ABP Nightly"); + await _nugetPackagesVersionUpdater.UpdateSolutionAsync( solutionPath, false, + false, true); await _npmPackagesUpdater.Update( solutionFolder, false, + false, true); } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/PackageSourceAdder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/PackageSourceAdder.cs deleted file mode 100644 index 3e651b3bf1..0000000000 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/PackageSourceAdder.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System; -using System.IO; -using System.Xml; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; -using Volo.Abp.DependencyInjection; - -namespace Volo.Abp.Cli.ProjectModification -{ - public class PackageSourceAdder: ITransientDependency - { - public ILogger Logger { get; set; } - - public PackageSourceAdder() - { - Logger = NullLogger.Instance; - } - - public void Add(string sourceKey, string sourceValue) - { - var nugetConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), - "NuGet", "NuGet.Config"); - - if (!File.Exists(nugetConfigPath)) - { - return; - } - - var fileContent = File.ReadAllText(nugetConfigPath); - - if (fileContent.Contains($"\"{sourceValue}\"")) - { - return; - } - - Logger.LogInformation($"Adding \"{sourceValue}\" ({sourceKey}) to nuget sources..."); - - try - { - var doc = new XmlDocument() { PreserveWhitespace = true }; - - doc.Load(GenerateStreamFromString(fileContent)); - - var sourceNodes = doc.SelectNodes("/configuration/packageSources"); - - var newNode = doc.CreateElement("add"); - - var includeAttr = doc.CreateAttribute("key"); - includeAttr.Value = sourceKey; - newNode.Attributes.Append(includeAttr); - - var versionAttr = doc.CreateAttribute("value"); - versionAttr.Value = sourceValue; - newNode.Attributes.Append(versionAttr); - - sourceNodes?[0]?.AppendChild(newNode); - - File.WriteAllText(nugetConfigPath, doc.OuterXml); - } - catch - { - Logger.LogWarning($"Adding \"{sourceValue}\" ({sourceKey}) to nuget sources FAILED."); - } - } - - private static Stream GenerateStreamFromString(string s) - { - var stream = new MemoryStream(); - var writer = new StreamWriter(stream); - writer.Write(s); - writer.Flush(); - stream.Position = 0; - return stream; - } - } -} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/PackageSourceManager.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/PackageSourceManager.cs new file mode 100644 index 0000000000..48c6e80030 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/PackageSourceManager.cs @@ -0,0 +1,126 @@ +using System; +using System.IO; +using System.Xml; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Cli.ProjectModification +{ + public class PackageSourceManager: ITransientDependency + { + public ILogger Logger { get; set; } + + public PackageSourceManager() + { + Logger = NullLogger.Instance; + } + + public void Add(string solutionFolder, string sourceKey, string sourceValue) + { + var nugetConfigPath = GetNugetConfigPath(solutionFolder); + + Logger.LogInformation($"Adding \"{sourceValue}\" ({sourceKey}) to nuget sources..."); + + if (!File.Exists(nugetConfigPath)) + { + File.WriteAllText(nugetConfigPath, "\n" + + "\n" + + " \n" + + " \n" + + $" \n" + + " \n" + + ""); + return; + } + + var fileContent = File.ReadAllText(nugetConfigPath); + + if (fileContent.Contains($"\"{sourceValue}\"")) + { + return; + } + + try + { + var doc = new XmlDocument() { PreserveWhitespace = true }; + + doc.Load(GenerateStreamFromString(fileContent)); + + var sourceNodes = doc.SelectNodes("/configuration/packageSources"); + + var newNode = doc.CreateElement("add"); + + var includeAttr = doc.CreateAttribute("key"); + includeAttr.Value = sourceKey; + newNode.Attributes.Append(includeAttr); + + var versionAttr = doc.CreateAttribute("value"); + versionAttr.Value = sourceValue; + newNode.Attributes.Append(versionAttr); + + sourceNodes?[0]?.AppendChild(newNode); + + File.WriteAllText(nugetConfigPath, doc.OuterXml); + } + catch + { + Logger.LogWarning($"Adding \"{sourceValue}\" ({sourceKey}) to nuget sources FAILED."); + } + } + + public void Remove(string solutionFolder, string sourceKey) + { + var nugetConfigPath = GetNugetConfigPath(solutionFolder); + + if (!File.Exists(nugetConfigPath)) + { + return; + } + + var fileContent = File.ReadAllText(nugetConfigPath); + + if (!fileContent.Contains($"\"{sourceKey}\"")) + { + return; + } + + Logger.LogInformation($"Removing \"{sourceKey}\" from nuget sources..."); + + try + { + var doc = new XmlDocument() { PreserveWhitespace = true }; + + doc.Load(GenerateStreamFromString(fileContent)); + + var nodes = doc.SelectNodes($"/configuration/packageSources/add[@key='{sourceKey}']"); + + if (nodes != null && nodes.Count > 0) + { + nodes[0].ParentNode.RemoveChild(nodes[0]); + } + + File.WriteAllText(nugetConfigPath, doc.OuterXml); + } + catch + { + Logger.LogWarning($"Removing \"{sourceKey}\" from nuget sources FAILED."); + } + } + + private static string GetNugetConfigPath(string solutionFolder) + { + return Path.Combine(solutionFolder, "NuGet.Config"); + } + + private static Stream GenerateStreamFromString(string s) + { + var stream = new MemoryStream(); + var writer = new StreamWriter(stream); + writer.Write(s); + writer.Flush(); + stream.Position = 0; + return stream; + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ProjectNugetPackageAdder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ProjectNugetPackageAdder.cs index a69957a497..5f1e18efbd 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ProjectNugetPackageAdder.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ProjectNugetPackageAdder.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Net; using System.Threading.Tasks; +using System.Xml; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Volo.Abp.Cli.Http; @@ -40,26 +41,36 @@ namespace Volo.Abp.Cli.ProjectModification Logger = NullLogger.Instance; } - public async Task AddAsync(string projectFile, string packageName) + public async Task AddAsync(string projectFile, string packageName, string version = null) { await AddAsync( projectFile, - await FindNugetPackageInfoAsync(packageName) + await FindNugetPackageInfoAsync(packageName), + version ); } - public Task AddAsync(string projectFile, NugetPackageInfo package) + public Task AddAsync(string projectFile, NugetPackageInfo package, string version = null) { - if (File.ReadAllText(projectFile).Contains($"\"{package.Name}\"")) + var projectFileContent = File.ReadAllText(projectFile); + + if (projectFileContent.Contains($"\"{package.Name}\"")) { return Task.CompletedTask; } + if (version == null) + { + version = GetAbpVersionOrNull(projectFileContent); + } + using (DirectoryHelper.ChangeCurrentDirectory(Path.GetDirectoryName(projectFile))) { Logger.LogInformation($"Installing '{package.Name}' package to the project '{Path.GetFileNameWithoutExtension(projectFile)}'..."); - CmdHelper.Run("dotnet", "add package " + package.Name); + var versionOption = version == null ? "" : $" -v {version}"; + + CmdHelper.Run("dotnet", $"add package {package.Name}{versionOption}"); var moduleFiles = ModuleClassFinder.Find(projectFile, "AbpModule"); if (moduleFiles.Count == 0) @@ -80,11 +91,22 @@ namespace Volo.Abp.Cli.ProjectModification return Task.CompletedTask; } - protected virtual async Task FindNugetPackageInfoAsync(string moduleName) + private string GetAbpVersionOrNull(string projectFileContent) + { + var doc = new XmlDocument() { PreserveWhitespace = true }; + + doc.Load(StreamHelper.GenerateStreamFromString(projectFileContent)); + + var nodes = doc.SelectNodes("/Project/ItemGroup/PackageReference[starts-with(@Include, 'Volo.')]"); + + return nodes?[0]?.Attributes?["Version"]?.Value; + } + + protected virtual async Task FindNugetPackageInfoAsync(string packageName) { using (var client = new CliHttpClient()) { - var url = $"{CliUrls.WwwAbpIo}api/app/nugetPackage/byName/?name=" + moduleName; + var url = $"{CliUrls.WwwAbpIo}api/app/nugetPackage/byName/?name=" + packageName; var response = await client.GetAsync(url); @@ -92,7 +114,7 @@ namespace Volo.Abp.Cli.ProjectModification { if (response.StatusCode == HttpStatusCode.NotFound) { - throw new CliUsageException($"'{moduleName}' nuget package could not be found!"); + throw new CliUsageException($"'{packageName}' nuget package could not be found!"); } await RemoteServiceExceptionHandler.EnsureSuccessfulHttpResponseAsync(response); @@ -103,4 +125,4 @@ namespace Volo.Abp.Cli.ProjectModification } } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionModuleAdder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionModuleAdder.cs index acf4231520..a19af6f52c 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionModuleAdder.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionModuleAdder.cs @@ -39,7 +39,7 @@ namespace Volo.Abp.Cli.ProjectModification EfCoreMigrationAdder efCoreMigrationAdder, DerivedClassFinder derivedClassFinder, ProjectNpmPackageAdder projectNpmPackageAdder, - NpmGlobalPackagesChecker npmGlobalPackagesChecker, + NpmGlobalPackagesChecker npmGlobalPackagesChecker, IRemoteServiceExceptionHandler remoteServiceExceptionHandler, SourceCodeDownloadService sourceCodeDownloadService, SolutionFileModifier solutionFileModifier, @@ -159,7 +159,7 @@ namespace Volo.Abp.Cli.ProjectModification { Directory.Delete(demoFolder, true); } - + var hostFolder = Path.Combine(targetModuleFolder, "host"); if (Directory.Exists(hostFolder)) { @@ -186,7 +186,9 @@ namespace Volo.Abp.Cli.ProjectModification await ProjectNugetPackageAdder.AddAsync(targetProjectFile, nugetPackage); } - if (!module.NpmPackages.IsNullOrEmpty()) + var mvcNpmPackages = module.NpmPackages?.Where(p => p.ApplicationType.HasFlag(NpmApplicationType.Mvc)).ToList(); + + if (!mvcNpmPackages.IsNullOrEmpty()) { var targetProjects = ProjectFinder.FindNpmTargetProjectFile(projectFiles); if (targetProjects.Any()) @@ -195,8 +197,7 @@ namespace Volo.Abp.Cli.ProjectModification foreach (var targetProject in targetProjects) { - foreach (var npmPackage in module.NpmPackages.Where(p => - p.ApplicationType.HasFlag(NpmApplicationType.Mvc))) + foreach (var npmPackage in mvcNpmPackages) { await ProjectNpmPackageAdder.AddAsync(Path.GetDirectoryName(targetProject), npmPackage); } @@ -222,7 +223,7 @@ namespace Volo.Abp.Cli.ProjectModification } var dbMigrationsProject = projectFiles.FirstOrDefault(p => p.EndsWith(".DbMigrations.csproj")); - + if (dbMigrationsProject == null) { Logger.LogDebug("Solution doesn't have a \".DbMigrations\" project."); @@ -241,7 +242,7 @@ namespace Volo.Abp.Cli.ProjectModification if (addedNewBuilder && !skipDbMigrations) { - EfCoreMigrationAdder.AddMigration(dbMigrationsProject, module.Name, startupProject); + EfCoreMigrationAdder.AddMigration(dbMigrationsProject, module.Name, startupProject); } } @@ -274,4 +275,4 @@ namespace Volo.Abp.Cli.ProjectModification .Any(p => p.EndsWith(".IdentityServer") || p.EndsWith(".HttpApi.Host")); } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/VoloNugetPackagesVersionUpdater.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/VoloNugetPackagesVersionUpdater.cs index 9661dceddd..275bf23c76 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/VoloNugetPackagesVersionUpdater.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/VoloNugetPackagesVersionUpdater.cs @@ -25,23 +25,30 @@ namespace Volo.Abp.Cli.ProjectModification Logger = NullLogger.Instance; } - public async Task UpdateSolutionAsync(string solutionPath, bool includePreviews = false, bool switchToStable = false, bool checkAll = false) + public async Task UpdateSolutionAsync(string solutionPath, bool includePreviews = false, bool includeReleaseCandidates = false, bool switchToStable = false, bool checkAll = false) { var projectPaths = ProjectFinder.GetProjectFiles(solutionPath); if (checkAll) { - Task.WaitAll(projectPaths.Select(projectPath => UpdateInternalAsync(projectPath, includePreviews, switchToStable)).ToArray()); + Task.WaitAll(projectPaths.Select(projectPath => UpdateInternalAsync(projectPath, includePreviews, includeReleaseCandidates, switchToStable)).ToArray()); } else { - var latestVersionFromNuget = await _nuGetService.GetLatestVersionOrNullAsync("Volo.Abp.Core"); + var latestVersionFromNuget = await _nuGetService.GetLatestVersionOrNullAsync("Volo.Abp.Core", includeReleaseCandidates : includeReleaseCandidates); + var latestReleaseCandidateVersionFromNuget = await _nuGetService.GetLatestVersionOrNullAsync("Volo.Abp.Core", includeReleaseCandidates : true); var latestVersionFromMyGet = await GetLatestVersionFromMyGet("Volo.Abp.Core"); async Task UpdateAsync(string filePath) { var fileContent = File.ReadAllText(filePath); - var updatedContent = await UpdateVoloPackagesAsync(fileContent, includePreviews, switchToStable, latestVersionFromNuget, latestVersionFromMyGet); + var updatedContent = await UpdateVoloPackagesAsync(fileContent, + includePreviews, + includeReleaseCandidates, + switchToStable, + latestVersionFromNuget, + latestReleaseCandidateVersionFromNuget, + latestVersionFromMyGet); File.WriteAllText(filePath, updatedContent); } @@ -50,33 +57,47 @@ namespace Volo.Abp.Cli.ProjectModification } } - public async Task UpdateProjectAsync(string projectPath, bool includePreviews = false, bool switchToStable = false, bool checkAll = false) + public async Task UpdateProjectAsync(string projectPath, bool includeNightlyPreviews = false, bool includeReleaseCandidates = false, bool switchToStable = false, bool checkAll = false) { if (checkAll) { - await UpdateInternalAsync(projectPath, includePreviews, switchToStable); + await UpdateInternalAsync(projectPath, includeNightlyPreviews, includeReleaseCandidates, switchToStable); } else { var latestVersionFromNuget = await _nuGetService.GetLatestVersionOrNullAsync("Volo.Abp.Core"); + var latestReleaseCandidateVersionFromNuget = await _nuGetService.GetLatestVersionOrNullAsync("Volo.Abp.Core", includeReleaseCandidates : true); var latestVersionFromMyGet = await GetLatestVersionFromMyGet("Volo.Abp.Core"); var fileContent = File.ReadAllText(projectPath); - var updatedContent = await UpdateVoloPackagesAsync(fileContent, includePreviews, switchToStable, latestVersionFromNuget, latestVersionFromMyGet); + + var updatedContent = await UpdateVoloPackagesAsync(fileContent, + includeNightlyPreviews, + includeReleaseCandidates, + switchToStable, + latestVersionFromNuget, + latestReleaseCandidateVersionFromNuget, + latestVersionFromMyGet); File.WriteAllText(projectPath, updatedContent); } } - protected virtual async Task UpdateInternalAsync(string projectPath, bool includePreviews = false, bool switchToStable = false) + protected virtual async Task UpdateInternalAsync(string projectPath, bool includeNightlyPreviews = false, bool includeReleaseCandidates = false, bool switchToStable = false) { var fileContent = File.ReadAllText(projectPath); - var updatedContent = await UpdateVoloPackagesAsync(fileContent, includePreviews, switchToStable); + var updatedContent = await UpdateVoloPackagesAsync(fileContent, includeNightlyPreviews, includeReleaseCandidates, switchToStable); File.WriteAllText(projectPath, updatedContent); } - private async Task UpdateVoloPackagesAsync(string content, bool includePreviews = false, bool switchToStable = false, SemanticVersion latestNugetVersion = null, string latestMyGetVersion = null) + private async Task UpdateVoloPackagesAsync(string content, + bool includeNightlyPreviews = false, + bool includeReleaseCandidates = false, + bool switchToStable = false, + SemanticVersion latestNugetVersion = null, + SemanticVersion latestNugetReleaseCandidateVersion = null, + string latestMyGetVersion = null) { string packageId = null; @@ -108,7 +129,7 @@ namespace Volo.Abp.Cli.ProjectModification Logger.LogDebug("Checking package: \"{0}\" - Current version: {1}", packageId, currentSemanticVersion); - if (includePreviews || (currentVersion.Contains("-preview") && !switchToStable)) + if ((includeNightlyPreviews || (currentVersion.Contains("-preview") && !switchToStable)) && !includeReleaseCandidates) { var latestVersion = latestMyGetVersion ?? await GetLatestVersionFromMyGet(packageId); @@ -124,9 +145,17 @@ namespace Volo.Abp.Cli.ProjectModification } else { - var latestVersion = latestNugetVersion ?? await _nuGetService.GetLatestVersionOrNullAsync(packageId); + SemanticVersion latestVersion; + if (currentSemanticVersion.IsPrerelease && !switchToStable) + { + latestVersion = latestNugetReleaseCandidateVersion ?? await _nuGetService.GetLatestVersionOrNullAsync(packageId, includeReleaseCandidates: true); + } + else + { + latestVersion = latestNugetVersion ?? await _nuGetService.GetLatestVersionOrNullAsync(packageId, includeReleaseCandidates: includeReleaseCandidates); + } - if (latestVersion != null && (currentVersion.Contains("-preview") || currentSemanticVersion < latestVersion)) + if (latestVersion != null && (currentSemanticVersion < latestVersion || (currentSemanticVersion.IsPrerelease && switchToStable))) { Logger.LogInformation("Updating package \"{0}\" from v{1} to v{2}.", packageId, currentSemanticVersion.ToString(), latestVersion.ToString()); versionAttribute.Value = latestVersion.ToString(); diff --git a/framework/src/Volo.Abp.Cli/Volo.Abp.Cli.csproj b/framework/src/Volo.Abp.Cli/Volo.Abp.Cli.csproj index 424e09b115..489ea99ac5 100644 --- a/framework/src/Volo.Abp.Cli/Volo.Abp.Cli.csproj +++ b/framework/src/Volo.Abp.Cli/Volo.Abp.Cli.csproj @@ -18,12 +18,12 @@ - - + + - \ No newline at end of file + diff --git a/framework/src/Volo.Abp.Core/Microsoft/Extensions/Logging/AbpLoggerExtensions.cs b/framework/src/Volo.Abp.Core/Microsoft/Extensions/Logging/AbpLoggerExtensions.cs index a711271cd2..5d975b2e58 100644 --- a/framework/src/Volo.Abp.Core/Microsoft/Extensions/Logging/AbpLoggerExtensions.cs +++ b/framework/src/Volo.Abp.Core/Microsoft/Extensions/Logging/AbpLoggerExtensions.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Text; using Volo.Abp.ExceptionHandling; using Volo.Abp.Logging; @@ -87,12 +88,14 @@ namespace Microsoft.Extensions.Logging return; } - logger.LogWithLevel(logLevel, "---------- Exception Data ----------"); - + var exceptionData = new StringBuilder(); + exceptionData.AppendLine("---------- Exception Data ----------"); foreach (var key in exception.Data.Keys) { - logger.LogWithLevel(logLevel, $"{key} = {exception.Data[key]}"); + exceptionData.AppendLine($"{key} = {exception.Data[key]}"); } + + logger.LogWithLevel(logLevel, exceptionData.ToString()); } private static void LogSelfLogging(ILogger logger, Exception exception) diff --git a/framework/src/Volo.Abp.Core/System/Collections/Generic/AbpListExtensions.cs b/framework/src/Volo.Abp.Core/System/Collections/Generic/AbpListExtensions.cs index ec7a640a39..ac71649793 100644 --- a/framework/src/Volo.Abp.Core/System/Collections/Generic/AbpListExtensions.cs +++ b/framework/src/Volo.Abp.Core/System/Collections/Generic/AbpListExtensions.cs @@ -9,6 +9,14 @@ namespace System.Collections.Generic /// public static class AbpListExtensions { + public static void InsertRange(this IList source, int index, IEnumerable items) + { + foreach (var item in items) + { + source.Insert(index++, item); + } + } + public static int FindIndex(this IList source, Predicate selector) { for (var i = 0; i < source.Count; ++i) @@ -182,7 +190,7 @@ namespace System.Collections.Generic /// Function to resolve the dependencies /// /// Returns a new list ordered by dependencies. - /// If A depends on B, then B will come before than A in the resulting list. + /// If A depends on B, then B will come before than A in the resulting list. /// public static List SortByDependencies(this IEnumerable source, Func> getDependencies) { @@ -202,14 +210,15 @@ namespace System.Collections.Generic } /// - /// + /// /// /// The type of the members of values. /// Item to resolve /// Function to resolve the dependencies /// List with the sortet items /// Dictionary with the visited items - private static void SortByDependenciesVisit(T item, Func> getDependencies, List sorted, Dictionary visited) + private static void SortByDependenciesVisit(T item, Func> getDependencies, List sorted, + Dictionary visited) { bool inProcess; var alreadyVisited = visited.TryGetValue(item, out inProcess); diff --git a/framework/src/Volo.Abp.Core/Volo.Abp.Core.csproj b/framework/src/Volo.Abp.Core/Volo.Abp.Core.csproj index 000433c83d..e1842d0447 100644 --- a/framework/src/Volo.Abp.Core/Volo.Abp.Core.csproj +++ b/framework/src/Volo.Abp.Core/Volo.Abp.Core.csproj @@ -16,15 +16,15 @@ - - - - - - - - - + + + + + + + + + diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/TypeHelper.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/TypeHelper.cs index 83c2c4c38e..cf3805249d 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/TypeHelper.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/TypeHelper.cs @@ -71,7 +71,7 @@ namespace Volo.Abp.Reflection return true; } - if (includeNullables && IsNullable(type)) + if (includeNullables && IsNullable(type) && type.GenericTypeArguments.Any()) { return IsPrimitiveExtendedInternal(type.GenericTypeArguments[0], includeEnums); } @@ -197,7 +197,7 @@ namespace Volo.Abp.Reflection return $"{genericType.FullName.Left(genericType.FullName.IndexOf('`'))}<{type.GenericTypeArguments.Select(GetFullNameHandlingNullableAndGenerics).JoinAsString(",")}>"; } - return type.FullName; + return type.FullName ?? type.Name; } public static string GetSimplifiedName([NotNull] Type type) @@ -295,8 +295,12 @@ namespace Volo.Abp.Reflection { return "number"; } + else if (type == typeof(object)) + { + return "object"; + } - return type.FullName; + return type.FullName ?? type.Name; } public static object ConvertFromString(string value) diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyCrudAppService.cs b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyCrudAppService.cs index 11550c456f..91879f030e 100644 --- a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyCrudAppService.cs +++ b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyCrudAppService.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; using Volo.Abp.Application.Dtos; using Volo.Abp.Domain.Entities; @@ -51,6 +52,11 @@ namespace Volo.Abp.Application.Services } + protected override Task MapToGetListOutputDtoAsync(TEntity entity) + { + return MapToGetOutputDtoAsync(entity); + } + protected override TEntityDto MapToGetListOutputDto(TEntity entity) { return MapToGetOutputDto(entity); @@ -80,13 +86,13 @@ namespace Volo.Abp.Application.Services { await CheckCreatePolicyAsync(); - var entity = MapToEntity(input); + var entity = await MapToEntityAsync(input); TryToSetTenantId(entity); await Repository.InsertAsync(entity, autoSave: true); - return MapToGetOutputDto(entity); + return await MapToGetOutputDtoAsync(entity); } public virtual async Task UpdateAsync(TKey id, TUpdateInput input) @@ -95,10 +101,10 @@ namespace Volo.Abp.Application.Services var entity = await GetEntityByIdAsync(id); //TODO: Check if input has id different than given id and normalize if it's default value, throw ex otherwise - MapToEntity(input, entity); + await MapToEntityAsync(input, entity); await Repository.UpdateAsync(entity, autoSave: true); - return MapToGetOutputDto(entity); + return await MapToGetOutputDtoAsync(entity); } public virtual async Task DeleteAsync(TKey id) @@ -125,6 +131,17 @@ namespace Volo.Abp.Application.Services await CheckPolicyAsync(DeletePolicyName); } + /// + /// Maps to to create a new entity. + /// It uses by default. + /// It can be overriden for custom mapping. + /// Overriding this has higher priority than overriding the + /// + protected virtual Task MapToEntityAsync(TCreateInput createInput) + { + return Task.FromResult(MapToEntity(createInput)); + } + /// /// Maps to to create a new entity. /// It uses by default. @@ -153,6 +170,18 @@ namespace Volo.Abp.Application.Services } } + /// + /// Maps to to update the entity. + /// It uses by default. + /// It can be overriden for custom mapping. + /// Overriding this has higher priority than overriding the + /// + protected virtual Task MapToEntityAsync(TUpdateInput updateInput, TEntity entity) + { + MapToEntity(updateInput, entity); + return Task.CompletedTask; + } + /// /// Maps to to update the entity. /// It uses by default. @@ -190,4 +219,4 @@ namespace Volo.Abp.Application.Services return entity.GetType().GetProperty(nameof(IMultiTenant.TenantId)) != null; } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyReadOnlyAppService.cs b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyReadOnlyAppService.cs index 9085434211..b9ffe3359b 100644 --- a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyReadOnlyAppService.cs +++ b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyReadOnlyAppService.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Linq.Dynamic.Core; using System.Threading.Tasks; @@ -6,6 +7,7 @@ using Volo.Abp.Application.Dtos; using Volo.Abp.Auditing; using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Repositories; +using Volo.Abp.ObjectMapping; namespace Volo.Abp.Application.Services { @@ -52,7 +54,8 @@ namespace Volo.Abp.Application.Services await CheckGetPolicyAsync(); var entity = await GetEntityByIdAsync(id); - return MapToGetOutputDto(entity); + + return await MapToGetOutputDtoAsync(entity); } public virtual async Task> GetListAsync(TGetListInput input) @@ -67,10 +70,11 @@ namespace Volo.Abp.Application.Services query = ApplyPaging(query, input); var entities = await AsyncExecuter.ToListAsync(query); + var entityDtos = await MapToGetListOutputDtosAsync(entities); return new PagedResultDto( totalCount, - entities.Select(MapToGetListOutputDto).ToList() + entityDtos ); } @@ -118,9 +122,9 @@ namespace Volo.Abp.Application.Services /// The query. protected virtual IQueryable ApplyDefaultSorting(IQueryable query) { - if (typeof(TEntity).IsAssignableTo()) + if (typeof(TEntity).IsAssignableTo()) { - return query.OrderByDescending(e => ((ICreationAuditedObject)e).CreationTime); + return query.OrderByDescending(e => ((IHasCreationTime)e).CreationTime); } throw new AbpException("No sorting specified but this query requires sorting. Override the ApplyDefaultSorting method for your application service derived from AbstractKeyReadOnlyAppService!"); @@ -161,6 +165,17 @@ namespace Volo.Abp.Application.Services return ReadOnlyRepository; } + /// + /// Maps to . + /// It internally calls the by default. + /// It can be overriden for custom mapping. + /// Overriding this has higher priority than overriding the + /// + protected virtual Task MapToGetOutputDtoAsync(TEntity entity) + { + return Task.FromResult(MapToGetOutputDto(entity)); + } + /// /// Maps to . /// It uses by default. @@ -171,6 +186,33 @@ namespace Volo.Abp.Application.Services return ObjectMapper.Map(entity); } + /// + /// Maps a list of to objects. + /// It uses method for each item in the list. + /// + protected virtual async Task> MapToGetListOutputDtosAsync(List entities) + { + var dtos = new List(); + + foreach (var entity in entities) + { + dtos.Add(await MapToGetListOutputDtoAsync(entity)); + } + + return dtos; + } + + /// + /// Maps to . + /// It internally calls the by default. + /// It can be overriden for custom mapping. + /// Overriding this has higher priority than overriding the + /// + protected virtual Task MapToGetListOutputDtoAsync(TEntity entity) + { + return Task.FromResult(MapToGetListOutputDto(entity)); + } + /// /// Maps to . /// It uses by default. diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/CrudAppService.cs b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/CrudAppService.cs index 7832e8a4ab..db66881fb7 100644 --- a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/CrudAppService.cs +++ b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/CrudAppService.cs @@ -55,6 +55,11 @@ namespace Volo.Abp.Application.Services } + protected override Task MapToGetListOutputDtoAsync(TEntity entity) + { + return base.MapToGetOutputDtoAsync(entity); + } + protected override TEntityDto MapToGetListOutputDto(TEntity entity) { return MapToGetOutputDto(entity); @@ -97,9 +102,9 @@ namespace Volo.Abp.Application.Services protected override IQueryable ApplyDefaultSorting(IQueryable query) { - if (typeof(TEntity).IsAssignableTo()) + if (typeof(TEntity).IsAssignableTo()) { - return query.OrderByDescending(e => ((ICreationAuditedObject)e).CreationTime); + return query.OrderByDescending(e => ((IHasCreationTime)e).CreationTime); } else { diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/AggregateRoot.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/AggregateRoot.cs index b0c20131ef..09af979e93 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/AggregateRoot.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/AggregateRoot.cs @@ -9,9 +9,7 @@ using Volo.Abp.ObjectExtending; namespace Volo.Abp.Domain.Entities { [Serializable] - public abstract class AggregateRoot : Entity, - IAggregateRoot, - IGeneratesDomainEvents, + public abstract class AggregateRoot : BasicAggregateRoot, IHasExtraProperties, IHasConcurrencyStamp { @@ -20,9 +18,6 @@ namespace Volo.Abp.Domain.Entities [DisableAuditing] public virtual string ConcurrencyStamp { get; set; } - private readonly ICollection _localEvents = new Collection(); - private readonly ICollection _distributedEvents = new Collection(); - protected AggregateRoot() { ConcurrencyStamp = Guid.NewGuid().ToString("N"); @@ -30,36 +25,6 @@ namespace Volo.Abp.Domain.Entities this.SetDefaultsForExtraProperties(); } - protected virtual void AddLocalEvent(object eventData) - { - _localEvents.Add(eventData); - } - - protected virtual void AddDistributedEvent(object eventData) - { - _distributedEvents.Add(eventData); - } - - public virtual IEnumerable GetLocalEvents() - { - return _localEvents; - } - - public virtual IEnumerable GetDistributedEvents() - { - return _distributedEvents; - } - - public virtual void ClearLocalEvents() - { - _localEvents.Clear(); - } - - public virtual void ClearDistributedEvents() - { - _distributedEvents.Clear(); - } - public virtual IEnumerable Validate(ValidationContext validationContext) { return ExtensibleObjectValidator.GetValidationErrors( @@ -70,9 +35,7 @@ namespace Volo.Abp.Domain.Entities } [Serializable] - public abstract class AggregateRoot : Entity, - IAggregateRoot, - IGeneratesDomainEvents, + public abstract class AggregateRoot : BasicAggregateRoot, IHasExtraProperties, IHasConcurrencyStamp { @@ -81,9 +44,6 @@ namespace Volo.Abp.Domain.Entities [DisableAuditing] public virtual string ConcurrencyStamp { get; set; } - private readonly ICollection _localEvents = new Collection(); - private readonly ICollection _distributedEvents = new Collection(); - protected AggregateRoot() { ConcurrencyStamp = Guid.NewGuid().ToString("N"); @@ -99,36 +59,6 @@ namespace Volo.Abp.Domain.Entities this.SetDefaultsForExtraProperties(); } - protected virtual void AddLocalEvent(object eventData) - { - _localEvents.Add(eventData); - } - - protected virtual void AddDistributedEvent(object eventData) - { - _distributedEvents.Add(eventData); - } - - public virtual IEnumerable GetLocalEvents() - { - return _localEvents; - } - - public virtual IEnumerable GetDistributedEvents() - { - return _distributedEvents; - } - - public virtual void ClearLocalEvents() - { - _localEvents.Clear(); - } - - public virtual void ClearDistributedEvents() - { - _distributedEvents.Clear(); - } - public virtual IEnumerable Validate(ValidationContext validationContext) { return ExtensibleObjectValidator.GetValidationErrors( @@ -137,4 +67,4 @@ namespace Volo.Abp.Domain.Entities ); } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/BasicAggregateRoot.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/BasicAggregateRoot.cs new file mode 100644 index 0000000000..42b0ba1320 --- /dev/null +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/BasicAggregateRoot.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; + +namespace Volo.Abp.Domain.Entities +{ + [Serializable] + public abstract class BasicAggregateRoot : Entity, + IAggregateRoot, + IGeneratesDomainEvents + { + private readonly ICollection _distributedEvents = new Collection(); + private readonly ICollection _localEvents = new Collection(); + + public virtual IEnumerable GetLocalEvents() + { + return _localEvents; + } + + public virtual IEnumerable GetDistributedEvents() + { + return _distributedEvents; + } + + public virtual void ClearLocalEvents() + { + _localEvents.Clear(); + } + + public virtual void ClearDistributedEvents() + { + _distributedEvents.Clear(); + } + + protected virtual void AddLocalEvent(object eventData) + { + _localEvents.Add(eventData); + } + + protected virtual void AddDistributedEvent(object eventData) + { + _distributedEvents.Add(eventData); + } + } + + [Serializable] + public abstract class BasicAggregateRoot : Entity, + IAggregateRoot, + IGeneratesDomainEvents + { + private readonly ICollection _distributedEvents = new Collection(); + private readonly ICollection _localEvents = new Collection(); + + protected BasicAggregateRoot() + { + + } + + protected BasicAggregateRoot(TKey id) + : base(id) + { + + } + + public virtual IEnumerable GetLocalEvents() + { + return _localEvents; + } + + public virtual IEnumerable GetDistributedEvents() + { + return _distributedEvents; + } + + public virtual void ClearLocalEvents() + { + _localEvents.Clear(); + } + + public virtual void ClearDistributedEvents() + { + _distributedEvents.Clear(); + } + + protected virtual void AddLocalEvent(object eventData) + { + _localEvents.Add(eventData); + } + + protected virtual void AddDistributedEvent(object eventData) + { + _distributedEvents.Add(eventData); + } + } +} diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/EntityCreatedEto.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/EntityCreatedEto.cs index d39b69c857..c96b5cc366 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/EntityCreatedEto.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/EntityCreatedEto.cs @@ -1,11 +1,12 @@ using System; using Volo.Abp.EventBus; +using Volo.Abp.MultiTenancy; namespace Volo.Abp.Domain.Entities.Events.Distributed { [Serializable] [GenericEventName(Postfix = ".Created")] - public class EntityCreatedEto + public class EntityCreatedEto : IEventDataMayHaveTenantId { public TEntityEto Entity { get; set; } @@ -13,5 +14,17 @@ namespace Volo.Abp.Domain.Entities.Events.Distributed { Entity = entity; } + + public virtual bool IsMultiTenant(out Guid? tenantId) + { + if (Entity is IMultiTenant multiTenantEntity) + { + tenantId = multiTenantEntity.TenantId; + return true; + } + + tenantId = null; + return false; + } } } diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/EntityDeletedEto.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/EntityDeletedEto.cs index 9cf930d7db..6f9594427a 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/EntityDeletedEto.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/EntityDeletedEto.cs @@ -1,11 +1,12 @@ using System; using Volo.Abp.EventBus; +using Volo.Abp.MultiTenancy; namespace Volo.Abp.Domain.Entities.Events.Distributed { [Serializable] [GenericEventName(Postfix = ".Deleted")] - public class EntityDeletedEto + public class EntityDeletedEto : IEventDataMayHaveTenantId { public TEntityEto Entity { get; set; } @@ -13,5 +14,17 @@ namespace Volo.Abp.Domain.Entities.Events.Distributed { Entity = entity; } + + public virtual bool IsMultiTenant(out Guid? tenantId) + { + if (Entity is IMultiTenant multiTenantEntity) + { + tenantId = multiTenantEntity.TenantId; + return true; + } + + tenantId = null; + return false; + } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/EntityUpdatedEto.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/EntityUpdatedEto.cs index 654dd4243f..54b0454903 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/EntityUpdatedEto.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/EntityUpdatedEto.cs @@ -1,11 +1,12 @@ using System; using Volo.Abp.EventBus; +using Volo.Abp.MultiTenancy; namespace Volo.Abp.Domain.Entities.Events.Distributed { [Serializable] [GenericEventName(Postfix = ".Updated")] - public class EntityUpdatedEto + public class EntityUpdatedEto : IEventDataMayHaveTenantId { public TEntityEto Entity { get; set; } @@ -13,5 +14,17 @@ namespace Volo.Abp.Domain.Entities.Events.Distributed { Entity = entity; } + + public virtual bool IsMultiTenant(out Guid? tenantId) + { + if (Entity is IMultiTenant multiTenantEntity) + { + tenantId = multiTenantEntity.TenantId; + return true; + } + + tenantId = null; + return false; + } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/EntityEventData.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/EntityEventData.cs index 698648ead4..d8ade9c01c 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/EntityEventData.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/EntityEventData.cs @@ -1,5 +1,6 @@ using System; using Volo.Abp.EventBus; +using Volo.Abp.MultiTenancy; namespace Volo.Abp.Domain.Entities.Events { @@ -8,7 +9,7 @@ namespace Volo.Abp.Domain.Entities.Events /// /// Entity type [Serializable] - public class EntityEventData : IEventDataWithInheritableGenericArgument + public class EntityEventData : IEventDataWithInheritableGenericArgument, IEventDataMayHaveTenantId { /// /// Related entity with this event. @@ -28,5 +29,17 @@ namespace Volo.Abp.Domain.Entities.Events { return new object[] { Entity }; } + + public virtual bool IsMultiTenant(out Guid? tenantId) + { + if (Entity is IMultiTenant multiTenantEntity) + { + tenantId = multiTenantEntity.TenantId; + return true; + } + + tenantId = null; + return false; + } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Localization/ar.json b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Localization/ar.json index 23b8e152d7..a4bcaee0a2 100644 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Localization/ar.json +++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Localization/ar.json @@ -1,4 +1,4 @@ -{ +{ "culture": "ar", "texts": { "DisplayName:Abp.Mailing.DefaultFromAddress": "العنوان الإفتراضي", @@ -18,6 +18,8 @@ "Description:Abp.Mailing.Smtp.Password": "كلمة المرور لاسم المستخدم المرتبط ببيانات الاعتماد.", "Description:Abp.Mailing.Smtp.Domain": "اسم المجال أو الكمبيوتر الذي يتحقق من بيانات الاعتماد.", "Description:Abp.Mailing.Smtp.EnableSsl": "ما إذا كان SmtpClient يستخدم (SSL) لتشفير الاتصال.", - "Description:Abp.Mailing.Smtp.UseDefaultCredentials": "إرسال الصلاحيات الافتراضية مع الطلب." + "Description:Abp.Mailing.Smtp.UseDefaultCredentials": "إرسال الصلاحيات الافتراضية مع الطلب.", + "TextTemplate:StandardEmailTemplates.Layout": "نموذج تخطيط إفتراضي للبريد الإلكتروني", + "TextTemplate:StandardEmailTemplates.Message": "نموذج بسيط للرسائل الإلكترونية" } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.SqlServer/Volo.Abp.EntityFrameworkCore.SqlServer.csproj b/framework/src/Volo.Abp.EntityFrameworkCore.SqlServer/Volo.Abp.EntityFrameworkCore.SqlServer.csproj index 988c3688ee..366958b213 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore.SqlServer/Volo.Abp.EntityFrameworkCore.SqlServer.csproj +++ b/framework/src/Volo.Abp.EntityFrameworkCore.SqlServer/Volo.Abp.EntityFrameworkCore.SqlServer.csproj @@ -19,7 +19,7 @@ - + diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo.Abp.EntityFrameworkCore.Sqlite.csproj b/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo.Abp.EntityFrameworkCore.Sqlite.csproj index ce5c9a1716..6a8fd26db2 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo.Abp.EntityFrameworkCore.Sqlite.csproj +++ b/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo.Abp.EntityFrameworkCore.Sqlite.csproj @@ -2,7 +2,7 @@ - + netstandard2.0 Volo.Abp.EntityFrameworkCore.Sqlite @@ -13,11 +13,11 @@ false - + - + - + diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo.Abp.EntityFrameworkCore.csproj b/framework/src/Volo.Abp.EntityFrameworkCore/Volo.Abp.EntityFrameworkCore.csproj index da97fb5977..4c6fb7fbbc 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo.Abp.EntityFrameworkCore.csproj +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo.Abp.EntityFrameworkCore.csproj @@ -20,8 +20,8 @@ - - + + diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs index 9515c72b6e..4f2ce6b434 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs @@ -11,10 +11,11 @@ using Microsoft.Extensions.Options; using Volo.Abp.Domain.Entities; using Volo.Abp.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore.DependencyInjection; +using Volo.Abp.Guids; namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore { - public class EfCoreRepository : RepositoryBase, IEfCoreRepository + public class EfCoreRepository : RepositoryBase, IEfCoreRepository, IAsyncEnumerable where TDbContext : IEfCoreDbContext where TEntity : class, IEntity { @@ -29,9 +30,12 @@ namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore private readonly IDbContextProvider _dbContextProvider; private readonly Lazy> _entityOptionsLazy; + protected virtual IGuidGenerator GuidGenerator { get; set; } + public EfCoreRepository(IDbContextProvider dbContextProvider) { _dbContextProvider = dbContextProvider; + GuidGenerator = SimpleGuidGenerator.Instance; _entityOptionsLazy = new Lazy>( () => ServiceProvider @@ -40,9 +44,11 @@ namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore .GetOrNull() ?? AbpEntityOptions.Empty ); } - + public override async Task InsertAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default) { + CheckAndSetId(entity); + var savedEntity = DbSet.Add(entity).Entity; if (autoSave) @@ -66,7 +72,7 @@ namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore return updatedEntity; } - + public override async Task DeleteAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default) { DbSet.Remove(entity); @@ -110,7 +116,7 @@ namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore } public override async Task FindAsync( - Expression> predicate, + Expression> predicate, bool includeDetails = true, CancellationToken cancellationToken = default) { @@ -188,16 +194,43 @@ namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore return query; } + + public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) + { + return DbSet.AsAsyncEnumerable().GetAsyncEnumerator(cancellationToken); + } + + protected virtual void CheckAndSetId(TEntity entity) + { + if (entity is IEntity entityWithGuidId) + { + TrySetGuidId(entityWithGuidId); + } + } + + protected virtual void TrySetGuidId(IEntity entity) + { + if (entity.Id != default) + { + return; + } + + EntityHelper.TrySetId( + entity, + () => GuidGenerator.Create(), + true + ); + } } - public class EfCoreRepository : EfCoreRepository, + public class EfCoreRepository : EfCoreRepository, IEfCoreRepository, ISupportsExplicitLoading where TDbContext : IEfCoreDbContext where TEntity : class, IEntity { - public EfCoreRepository(IDbContextProvider dbContextProvider) + public EfCoreRepository(IDbContextProvider dbContextProvider) : base(dbContextProvider) { diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs index adfb530ce2..c82912f0f1 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; +using System.Globalization; using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -182,6 +183,14 @@ namespace Volo.Abp.EntityFrameworkCore } } + /// + /// This method will call the DbContext method directly of EF Core, which doesn't apply concepts of abp. + /// + public virtual Task SaveChangesOnDbContextAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default) + { + return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken); + } + public virtual void Initialize(AbpEfCoreDbContextInitializationContext initializationContext) { if (initializationContext.UnitOfWork.Options.Timeout.HasValue && @@ -314,7 +323,25 @@ namespace Volo.Abp.EntityFrameworkCore continue; } - entry.Property(property.Name).CurrentValue = entity.GetProperty(property.Name); + var entryProperty = entry.Property(property.Name); + var entityProperty = entity.GetProperty(property.Name); + if (entityProperty == null) + { + entryProperty.CurrentValue = null; + continue; + } + + if (entryProperty.Metadata.ClrType == entityProperty.GetType()) + { + entryProperty.CurrentValue = entityProperty; + } + else + { + if (TypeHelper.IsPrimitiveExtended(entryProperty.Metadata.ClrType, includeEnums: true)) + { + entryProperty.CurrentValue = Convert.ChangeType(entityProperty, entryProperty.Metadata.ClrType, CultureInfo.InvariantCulture); + } + } } } diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs index 796c6e20fb..50de4f1b56 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs @@ -242,7 +242,7 @@ namespace Volo.Abp.EntityFrameworkCore.EntityHistory } } - if (IsBaseAuditProperty(propertyInfo, entityType)) + if (propertyInfo != null && IsBaseAuditProperty(propertyInfo, entityType)) { return false; } diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/IEfCoreDbContext.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/IEfCoreDbContext.cs index 761e15b6f2..931cbd6d04 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/IEfCoreDbContext.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/IEfCoreDbContext.cs @@ -24,6 +24,11 @@ namespace Volo.Abp.EntityFrameworkCore Task SaveChangesAsync(CancellationToken cancellationToken = default); + /// + /// This method will call the DbContext method directly of EF Core, which doesn't apply concepts of abp. + /// + Task SaveChangesOnDbContextAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default); + DbSet Set() where T: class; @@ -50,7 +55,7 @@ namespace Volo.Abp.EntityFrameworkCore void AttachRange([NotNull] IEnumerable entities); void AttachRange([NotNull] params object[] entities); - + EntityEntry Entry([NotNull] TEntity entity) where TEntity : class; EntityEntry Entry([NotNull] object entity); diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionInfoExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionInfoExtensions.cs index ef3d87119a..91184ecdeb 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionInfoExtensions.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionInfoExtensions.cs @@ -37,5 +37,37 @@ namespace Volo.Abp.ObjectExtending } ); } + + public static ObjectExtensionInfo MapEfCoreProperty( + [NotNull] this ObjectExtensionInfo objectExtensionInfo, + [NotNull] string propertyName, + [CanBeNull] Action entityTypeAndPropertyBuildAction) + { + return objectExtensionInfo.MapEfCoreProperty( + typeof(TProperty), + propertyName, + entityTypeAndPropertyBuildAction + ); + } + + public static ObjectExtensionInfo MapEfCoreProperty( + [NotNull] this ObjectExtensionInfo objectExtensionInfo, + [NotNull] Type propertyType, + [NotNull] string propertyName, + [CanBeNull] Action entityTypeAndPropertyBuildAction) + { + Check.NotNull(objectExtensionInfo, nameof(objectExtensionInfo)); + + return objectExtensionInfo.AddOrUpdateProperty( + propertyType, + propertyName, + options => + { + options.MapEfCore( + entityTypeAndPropertyBuildAction + ); + } + ); + } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionManagerExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionManagerExtensions.cs index 52dca0c105..cf6d0e1368 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionManagerExtensions.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionManagerExtensions.cs @@ -8,10 +8,41 @@ namespace Volo.Abp.ObjectExtending { public static class EfCoreObjectExtensionManagerExtensions { + public static ObjectExtensionManager MapEfCoreProperty( + [NotNull] this ObjectExtensionManager objectExtensionManager, + [NotNull] string propertyName) + where TEntity : IHasExtraProperties, IEntity + { + return objectExtensionManager.MapEfCoreProperty( + typeof(TEntity), + typeof(TProperty), + propertyName + ); + } + + public static ObjectExtensionManager MapEfCoreProperty( + [NotNull] this ObjectExtensionManager objectExtensionManager, + [NotNull] Type entityType, + [NotNull] Type propertyType, + [NotNull] string propertyName) + { + Check.NotNull(objectExtensionManager, nameof(objectExtensionManager)); + + return objectExtensionManager.AddOrUpdateProperty( + entityType, + propertyType, + propertyName, + options => + { + options.MapEfCore(); + } + ); + } + public static ObjectExtensionManager MapEfCoreProperty( [NotNull] this ObjectExtensionManager objectExtensionManager, [NotNull] string propertyName, - [CanBeNull] Action propertyBuildAction = null) + [CanBeNull] Action propertyBuildAction) where TEntity : IHasExtraProperties, IEntity { return objectExtensionManager.MapEfCoreProperty( @@ -27,7 +58,7 @@ namespace Volo.Abp.ObjectExtending [NotNull] Type entityType, [NotNull] Type propertyType, [NotNull] string propertyName, - [CanBeNull] Action propertyBuildAction = null) + [CanBeNull] Action propertyBuildAction) { Check.NotNull(objectExtensionManager, nameof(objectExtensionManager)); @@ -44,6 +75,42 @@ namespace Volo.Abp.ObjectExtending ); } + public static ObjectExtensionManager MapEfCoreProperty( + [NotNull] this ObjectExtensionManager objectExtensionManager, + [NotNull] string propertyName, + [CanBeNull] Action entityTypeAndPropertyBuildAction) + where TEntity : IHasExtraProperties, IEntity + { + return objectExtensionManager.MapEfCoreProperty( + typeof(TEntity), + typeof(TProperty), + propertyName, + entityTypeAndPropertyBuildAction + ); + } + + public static ObjectExtensionManager MapEfCoreProperty( + [NotNull] this ObjectExtensionManager objectExtensionManager, + [NotNull] Type entityType, + [NotNull] Type propertyType, + [NotNull] string propertyName, + [CanBeNull] Action entityTypeAndPropertyBuildAction) + { + Check.NotNull(objectExtensionManager, nameof(objectExtensionManager)); + + return objectExtensionManager.AddOrUpdateProperty( + entityType, + propertyType, + propertyName, + options => + { + options.MapEfCore( + entityTypeAndPropertyBuildAction + ); + } + ); + } + public static void ConfigureEfCoreEntity( [NotNull] this ObjectExtensionManager objectExtensionManager, [NotNull] EntityTypeBuilder typeBuilder) @@ -73,6 +140,7 @@ namespace Volo.Abp.ObjectExtending var propertyBuilder = typeBuilder.Property(property.Type, property.Name); + efCoreMapping.EntityTypeAndPropertyBuildAction?.Invoke(typeBuilder, propertyBuilder); efCoreMapping.PropertyBuildAction?.Invoke(propertyBuilder); } } diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionPropertyInfoExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionPropertyInfoExtensions.cs index fda5d88ceb..9fb466c4cd 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionPropertyInfoExtensions.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionPropertyInfoExtensions.cs @@ -9,10 +9,24 @@ namespace Volo.Abp.ObjectExtending { public const string EfCorePropertyConfigurationName = "EfCoreMapping"; + [NotNull] + public static ObjectExtensionPropertyInfo MapEfCore( + [NotNull] this ObjectExtensionPropertyInfo propertyExtension) + { + Check.NotNull(propertyExtension, nameof(propertyExtension)); + + propertyExtension.Configuration[EfCorePropertyConfigurationName] = + new ObjectExtensionPropertyInfoEfCoreMappingOptions( + propertyExtension + ); + + return propertyExtension; + } + [NotNull] public static ObjectExtensionPropertyInfo MapEfCore( [NotNull] this ObjectExtensionPropertyInfo propertyExtension, - [CanBeNull] Action propertyBuildAction = null) + [CanBeNull] Action propertyBuildAction) { Check.NotNull(propertyExtension, nameof(propertyExtension)); @@ -25,6 +39,22 @@ namespace Volo.Abp.ObjectExtending return propertyExtension; } + [NotNull] + public static ObjectExtensionPropertyInfo MapEfCore( + [NotNull] this ObjectExtensionPropertyInfo propertyExtension, + [CanBeNull] Action entityTypeAndPropertyBuildAction) + { + Check.NotNull(propertyExtension, nameof(propertyExtension)); + + propertyExtension.Configuration[EfCorePropertyConfigurationName] = + new ObjectExtensionPropertyInfoEfCoreMappingOptions( + propertyExtension, + entityTypeAndPropertyBuildAction: entityTypeAndPropertyBuildAction + ); + + return propertyExtension; + } + [CanBeNull] public static ObjectExtensionPropertyInfoEfCoreMappingOptions GetEfCoreMappingOrNull( [NotNull] this ObjectExtensionPropertyInfo propertyExtension) diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfoEfCoreMappingOptions.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfoEfCoreMappingOptions.cs index 4189c87962..15a818478b 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfoEfCoreMappingOptions.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfoEfCoreMappingOptions.cs @@ -15,13 +15,18 @@ namespace Volo.Abp.ObjectExtending [CanBeNull] public Action PropertyBuildAction { get; set; } + [CanBeNull] + public Action EntityTypeAndPropertyBuildAction { get; set; } + public ObjectExtensionPropertyInfoEfCoreMappingOptions( [NotNull] ObjectExtensionPropertyInfo extensionProperty, - [CanBeNull] Action propertyBuildAction = null) + [CanBeNull] Action propertyBuildAction = null, + [CanBeNull] Action entityTypeAndPropertyBuildAction = null) { ExtensionProperty = Check.NotNull(extensionProperty, nameof(extensionProperty)); - + PropertyBuildAction = propertyBuildAction; + EntityTypeAndPropertyBuildAction = entityTypeAndPropertyBuildAction; } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs index ed7d37db20..e8c7fc6c5b 100644 --- a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs @@ -9,6 +9,7 @@ using RabbitMQ.Client; using RabbitMQ.Client.Events; using Volo.Abp.DependencyInjection; using Volo.Abp.EventBus.Distributed; +using Volo.Abp.MultiTenancy; using Volo.Abp.RabbitMQ; using Volo.Abp.Threading; @@ -26,7 +27,7 @@ namespace Volo.Abp.EventBus.RabbitMq protected AbpDistributedEventBusOptions AbpDistributedEventBusOptions { get; } protected IConnectionPool ConnectionPool { get; } protected IRabbitMqSerializer Serializer { get; } - + //TODO: Accessing to the List may not be thread-safe! protected ConcurrentDictionary> HandlerFactories { get; } protected ConcurrentDictionary EventTypes { get; } @@ -37,17 +38,18 @@ namespace Volo.Abp.EventBus.RabbitMq IOptions options, IConnectionPool connectionPool, IRabbitMqSerializer serializer, - IServiceScopeFactory serviceScopeFactory, + IServiceScopeFactory serviceScopeFactory, IOptions distributedEventBusOptions, - IRabbitMqMessageConsumerFactory messageConsumerFactory) - : base(serviceScopeFactory) + IRabbitMqMessageConsumerFactory messageConsumerFactory, + ICurrentTenant currentTenant) + : base(serviceScopeFactory, currentTenant) { ConnectionPool = connectionPool; Serializer = serializer; MessageConsumerFactory = messageConsumerFactory; AbpDistributedEventBusOptions = distributedEventBusOptions.Value; AbpRabbitMqEventBusOptions = options.Value; - + HandlerFactories = new ConcurrentDictionary>(); EventTypes = new ConcurrentDictionary(); } @@ -178,7 +180,7 @@ namespace Volo.Abp.EventBus.RabbitMq "direct", durable: true ); - + var properties = channel.CreateBasicProperties(); properties.DeliveryMode = RabbitMqConsts.DeliveryModes.Persistent; diff --git a/framework/src/Volo.Abp.EventBus/Volo.Abp.EventBus.csproj b/framework/src/Volo.Abp.EventBus/Volo.Abp.EventBus.csproj index 566767e111..a1799c6673 100644 --- a/framework/src/Volo.Abp.EventBus/Volo.Abp.EventBus.csproj +++ b/framework/src/Volo.Abp.EventBus/Volo.Abp.EventBus.csproj @@ -16,6 +16,7 @@ + diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusModule.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusModule.cs index c5a5022f77..81c3393a50 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusModule.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusModule.cs @@ -4,10 +4,12 @@ using System.Collections.Generic; using Volo.Abp.EventBus.Distributed; using Volo.Abp.EventBus.Local; using Volo.Abp.Modularity; +using Volo.Abp.MultiTenancy; using Volo.Abp.Reflection; namespace Volo.Abp.EventBus { + [DependsOn(typeof(AbpMultiTenancyModule))] public class AbpEventBusModule : AbpModule { public override void PreConfigureServices(ServiceConfigurationContext context) diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventBusBase.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventBusBase.cs index a7883950d1..80d4134db2 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventBusBase.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventBusBase.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Collections; using Volo.Abp.EventBus.Distributed; +using Volo.Abp.MultiTenancy; using Volo.Abp.Reflection; namespace Volo.Abp.EventBus @@ -16,9 +17,12 @@ namespace Volo.Abp.EventBus { protected IServiceScopeFactory ServiceScopeFactory { get; } - protected EventBusBase(IServiceScopeFactory serviceScopeFactory) + protected ICurrentTenant CurrentTenant { get; } + + protected EventBusBase(IServiceScopeFactory serviceScopeFactory, ICurrentTenant currentTenant) { ServiceScopeFactory = serviceScopeFactory; + CurrentTenant = currentTenant; } /// @@ -162,31 +166,34 @@ namespace Volo.Abp.EventBus { var handlerType = eventHandlerWrapper.EventHandler.GetType(); - if (ReflectionHelper.IsAssignableToGenericType(handlerType, typeof(ILocalEventHandler<>))) - { - var method = typeof(ILocalEventHandler<>) - .MakeGenericType(eventType) - .GetMethod( - nameof(ILocalEventHandler.HandleEventAsync), - new[] { eventType } - ); - - await ((Task)method.Invoke(eventHandlerWrapper.EventHandler, new[] { eventData })); - } - else if (ReflectionHelper.IsAssignableToGenericType(handlerType, typeof(IDistributedEventHandler<>))) - { - var method = typeof(IDistributedEventHandler<>) - .MakeGenericType(eventType) - .GetMethod( - nameof(IDistributedEventHandler.HandleEventAsync), - new[] { eventType } - ); - - await ((Task)method.Invoke(eventHandlerWrapper.EventHandler, new[] { eventData })); - } - else + using (CurrentTenant.Change(GetEventDataTenantId(eventData))) { - throw new AbpException("The object instance is not an event handler. Object type: " + handlerType.AssemblyQualifiedName); + if (ReflectionHelper.IsAssignableToGenericType(handlerType, typeof(ILocalEventHandler<>))) + { + var method = typeof(ILocalEventHandler<>) + .MakeGenericType(eventType) + .GetMethod( + nameof(ILocalEventHandler.HandleEventAsync), + new[] { eventType } + ); + + await ((Task)method.Invoke(eventHandlerWrapper.EventHandler, new[] { eventData })); + } + else if (ReflectionHelper.IsAssignableToGenericType(handlerType, typeof(IDistributedEventHandler<>))) + { + var method = typeof(IDistributedEventHandler<>) + .MakeGenericType(eventType) + .GetMethod( + nameof(IDistributedEventHandler.HandleEventAsync), + new[] { eventType } + ); + + await ((Task)method.Invoke(eventHandlerWrapper.EventHandler, new[] { eventData })); + } + else + { + throw new AbpException("The object instance is not an event handler. Object type: " + handlerType.AssemblyQualifiedName); + } } } catch (TargetInvocationException ex) @@ -200,6 +207,16 @@ namespace Volo.Abp.EventBus } } + protected virtual Guid? GetEventDataTenantId(object eventData) + { + return eventData switch + { + IMultiTenant multiTenantEventData => multiTenantEventData.TenantId, + IEventDataMayHaveTenantId eventDataMayHaveTenantId when eventDataMayHaveTenantId.IsMultiTenant(out var tenantId) => tenantId, + _ => CurrentTenant.Id + }; + } + protected class EventTypeWithEventHandlerFactories { public Type EventType { get; } @@ -246,4 +263,4 @@ namespace Volo.Abp.EventBus } } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/IEventDataMayHaveTenantId.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/IEventDataMayHaveTenantId.cs new file mode 100644 index 0000000000..72a8c753cd --- /dev/null +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/IEventDataMayHaveTenantId.cs @@ -0,0 +1,28 @@ +using System; + +namespace Volo.Abp.EventBus +{ + /// + /// An event data object (or event transfer object) can implement this interface + /// to indicate that this event may be related to a tenant. + /// + /// If an event data class is always related to a tenant, then directly implement the + /// interface instead of this one. + /// + /// This interface is typically implemented by generic event handlers where the generic + /// parameter may implement or not. + /// + public interface IEventDataMayHaveTenantId + { + /// + /// Returns true if this event data has a Tenant Id information. + /// If so, it should set the our parameter. + /// Otherwise, the our parameter value should not be informative + /// (it will be null as expected, but doesn't indicate a tenant with null tenant id). + /// + /// + /// The tenant id that is set if this method returns true. + /// + bool IsMultiTenant(out Guid? tenantId); + } +} diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventBus.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventBus.cs index 7db5c18f01..8c7bef6f2d 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventBus.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventBus.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.DependencyInjection; +using Volo.Abp.MultiTenancy; using Volo.Abp.Threading; namespace Volo.Abp.EventBus.Local @@ -29,8 +30,9 @@ namespace Volo.Abp.EventBus.Local public LocalEventBus( IOptions options, - IServiceScopeFactory serviceScopeFactory) - : base(serviceScopeFactory) + IServiceScopeFactory serviceScopeFactory, + ICurrentTenant currentTenant) + : base(serviceScopeFactory, currentTenant) { Options = options.Value; Logger = NullLogger.Instance; @@ -166,4 +168,4 @@ namespace Volo.Abp.EventBus.Local return false; } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Http.Client/Volo.Abp.Http.Client.csproj b/framework/src/Volo.Abp.Http.Client/Volo.Abp.Http.Client.csproj index 4441e0bc17..75dbce3b03 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo.Abp.Http.Client.csproj +++ b/framework/src/Volo.Abp.Http.Client/Volo.Abp.Http.Client.csproj @@ -15,7 +15,7 @@ - + diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs index 533b76aa6c..af9eb59b7f 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs @@ -17,7 +17,6 @@ using Volo.Abp.Http.Modeling; using Volo.Abp.Http.ProxyScripting.Generators; using Volo.Abp.Json; using Volo.Abp.MultiTenancy; -using Volo.Abp.Reflection; using Volo.Abp.Threading; using Volo.Abp.Tracing; diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/UrlBuilder.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/UrlBuilder.cs index 162da88f16..e4b82b21e1 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/UrlBuilder.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/UrlBuilder.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -100,7 +101,20 @@ namespace Volo.Abp.Http.Client.DynamicProxying { urlBuilder.Append(isFirstParam ? "?" : "&"); - urlBuilder.Append(name + "=" + System.Net.WebUtility.UrlEncode(ConvertValueToString(value))); + if (value.GetType().IsArray || (value.GetType().IsGenericType && value is IEnumerable)) + { + var index = 0; + foreach (var item in (IEnumerable) value) + { + urlBuilder.Append(name + $"[{index++}]=" + System.Net.WebUtility.UrlEncode(ConvertValueToString(item)) + "&"); + } + //remove & at the end of the urlBuilder. + urlBuilder.Remove(urlBuilder.Length - 1, 1); + } + else + { + urlBuilder.Append(name + "=" + System.Net.WebUtility.UrlEncode(ConvertValueToString(value))); + } } private static string ConvertValueToString([NotNull] object value) diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ApiTypeNameHelper.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ApiTypeNameHelper.cs new file mode 100644 index 0000000000..46eb9e903d --- /dev/null +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ApiTypeNameHelper.cs @@ -0,0 +1,38 @@ +using System; +using Volo.Abp.Reflection; + +namespace Volo.Abp.Http.Modeling +{ + public static class ApiTypeNameHelper + { + public static string GetTypeName(Type type) + { + if (TypeHelper.IsDictionary(type, out var keyType, out var valueType)) + { + return $"{{{GetTypeName(keyType)}:{GetTypeName(valueType)}}}"; + } + + if (TypeHelper.IsEnumerable(type, out var itemType, includePrimitives: false)) + { + return $"[{GetTypeName(itemType)}]"; + } + + return TypeHelper.GetFullNameHandlingNullableAndGenerics(type); + } + + public static string GetSimpleTypeName(Type type) + { + if (TypeHelper.IsDictionary(type, out var keyType, out var valueType)) + { + return $"{{{GetSimpleTypeName(keyType)}:{GetSimpleTypeName(valueType)}}}"; + } + + if (TypeHelper.IsEnumerable(type, out var itemType, includePrimitives: false)) + { + return $"[{GetSimpleTypeName(itemType)}]"; + } + + return TypeHelper.GetSimplifiedName(type); + } + } +} diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/MethodParameterApiDescriptionModel.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/MethodParameterApiDescriptionModel.cs index 211359d789..b4e272c2f3 100644 --- a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/MethodParameterApiDescriptionModel.cs +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/MethodParameterApiDescriptionModel.cs @@ -30,11 +30,11 @@ namespace Volo.Abp.Http.Modeling { Name = parameterInfo.Name, TypeAsString = parameterInfo.ParameterType.GetFullNameWithAssemblyName(), - Type = parameterInfo.ParameterType != null ? TypeHelper.GetFullNameHandlingNullableAndGenerics(parameterInfo.ParameterType) : null, - TypeSimple = parameterInfo.ParameterType != null ? TypeHelper.GetSimplifiedName(parameterInfo.ParameterType) : null, + Type = TypeHelper.GetFullNameHandlingNullableAndGenerics(parameterInfo.ParameterType), + TypeSimple = ApiTypeNameHelper.GetSimpleTypeName(parameterInfo.ParameterType), IsOptional = parameterInfo.IsOptional, DefaultValue = parameterInfo.HasDefaultValue ? parameterInfo.DefaultValue : null }; } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ParameterApiDescriptionModel.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ParameterApiDescriptionModel.cs index 0450861a41..675ba8bf54 100644 --- a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ParameterApiDescriptionModel.cs +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ParameterApiDescriptionModel.cs @@ -26,7 +26,7 @@ namespace Volo.Abp.Http.Modeling private ParameterApiDescriptionModel() { - + } public static ParameterApiDescriptionModel Create(string name, string nameOnMethod, Type type, bool isOptional = false, object defaultValue = null, string[] constraintTypes = null, string bindingSourceId = null, string descriptorName = null) @@ -36,7 +36,7 @@ namespace Volo.Abp.Http.Modeling Name = name, NameOnMethod = nameOnMethod, Type = type != null ? TypeHelper.GetFullNameHandlingNullableAndGenerics(type) : null, - TypeSimple = type != null ? TypeHelper.GetSimplifiedName(type) : null, + TypeSimple = type != null ? ApiTypeNameHelper.GetSimpleTypeName(type) : null, IsOptional = isOptional, DefaultValue = defaultValue, ConstraintTypes = constraintTypes, @@ -45,4 +45,4 @@ namespace Volo.Abp.Http.Modeling }; } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/PropertyApiDescriptionModel.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/PropertyApiDescriptionModel.cs index 820d9a46f9..610b6bd347 100644 --- a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/PropertyApiDescriptionModel.cs +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/PropertyApiDescriptionModel.cs @@ -1,6 +1,5 @@ using System; using System.Reflection; -using Volo.Abp.Reflection; namespace Volo.Abp.Http.Modeling { @@ -16,31 +15,12 @@ namespace Volo.Abp.Http.Modeling //TODO: Validation rules for this property public static PropertyApiDescriptionModel Create(PropertyInfo propertyInfo) { - string typeName; - string simpleTypeName; - - if (TypeHelper.IsEnumerable(propertyInfo.PropertyType, out var itemType, includePrimitives: false)) - { - typeName = $"[{TypeHelper.GetFullNameHandlingNullableAndGenerics(itemType)}]"; - simpleTypeName = $"[{TypeHelper.GetSimplifiedName(itemType)}]"; - } - else if (TypeHelper.IsDictionary(propertyInfo.PropertyType, out var keyType, out var valueType)) - { - typeName = $"{{{TypeHelper.GetFullNameHandlingNullableAndGenerics(keyType)}:{TypeHelper.GetFullNameHandlingNullableAndGenerics(valueType)}}}"; - simpleTypeName = $"{{{TypeHelper.GetSimplifiedName(keyType)}:{TypeHelper.GetSimplifiedName(valueType)}}}"; - } - else - { - typeName = TypeHelper.GetFullNameHandlingNullableAndGenerics(propertyInfo.PropertyType); - simpleTypeName = TypeHelper.GetSimplifiedName(propertyInfo.PropertyType); - } - return new PropertyApiDescriptionModel { Name = propertyInfo.Name, - Type = typeName, - TypeSimple = simpleTypeName + Type = ApiTypeNameHelper.GetTypeName(propertyInfo.PropertyType), + TypeSimple = ApiTypeNameHelper.GetSimpleTypeName(propertyInfo.PropertyType) }; } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ReturnValueApiDescriptionModel.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ReturnValueApiDescriptionModel.cs index 2e0862ccce..83fa4bb6be 100644 --- a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ReturnValueApiDescriptionModel.cs +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ReturnValueApiDescriptionModel.cs @@ -23,8 +23,8 @@ namespace Volo.Abp.Http.Modeling return new ReturnValueApiDescriptionModel { Type = TypeHelper.GetFullNameHandlingNullableAndGenerics(unwrappedType), - TypeSimple = TypeHelper.GetSimplifiedName(unwrappedType) + TypeSimple = ApiTypeNameHelper.GetSimpleTypeName(unwrappedType) }; } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/TypeApiDescriptionModel.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/TypeApiDescriptionModel.cs index 161584279b..4b0b0b870c 100644 --- a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/TypeApiDescriptionModel.cs +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/TypeApiDescriptionModel.cs @@ -15,11 +15,13 @@ namespace Volo.Abp.Http.Modeling public object[] EnumValues { get; set; } + public string[] GenericArguments { get; set; } + public PropertyApiDescriptionModel[] Properties { get; set; } private TypeApiDescriptionModel() { - + } public static TypeApiDescriptionModel Create(Type type) @@ -45,11 +47,17 @@ namespace Volo.Abp.Http.Modeling { typeModel.Properties = type .GetProperties() + .Where(p => p.DeclaringType == type) .Select(PropertyApiDescriptionModel.Create) .ToArray(); + + if (type.IsGenericTypeDefinition) + { + typeModel.GenericArguments = type.GetGenericArguments().Select(a => a.Name).ToArray(); + } } return typeModel; } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.IdentityModel/Volo.Abp.IdentityModel.csproj b/framework/src/Volo.Abp.IdentityModel/Volo.Abp.IdentityModel.csproj index b96da9be3e..3cdc6f6c98 100644 --- a/framework/src/Volo.Abp.IdentityModel/Volo.Abp.IdentityModel.csproj +++ b/framework/src/Volo.Abp.IdentityModel/Volo.Abp.IdentityModel.csproj @@ -17,7 +17,10 @@ +<<<<<<< maliming/CachetheAccessToken +======= +>>>>>>> dev diff --git a/framework/src/Volo.Abp.Json/Volo/Abp/Json/AbpJsonModule.cs b/framework/src/Volo.Abp.Json/Volo/Abp/Json/AbpJsonModule.cs index dcb8f28dff..c3ea13da43 100644 --- a/framework/src/Volo.Abp.Json/Volo/Abp/Json/AbpJsonModule.cs +++ b/framework/src/Volo.Abp.Json/Volo/Abp/Json/AbpJsonModule.cs @@ -1,4 +1,5 @@ -using Volo.Abp.Modularity; +using Volo.Abp.Json.Newtonsoft; +using Volo.Abp.Modularity; using Volo.Abp.Timing; namespace Volo.Abp.Json @@ -6,6 +7,12 @@ namespace Volo.Abp.Json [DependsOn(typeof(AbpTimingModule))] public class AbpJsonModule : AbpModule { - + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.Converters.Add(); + }); + } } } diff --git a/framework/src/Volo.Abp.Json/Volo/Abp/Json/Newtonsoft/AbpNewtonsoftJsonSerializerOptions.cs b/framework/src/Volo.Abp.Json/Volo/Abp/Json/Newtonsoft/AbpNewtonsoftJsonSerializerOptions.cs new file mode 100644 index 0000000000..cb1c3d9f65 --- /dev/null +++ b/framework/src/Volo.Abp.Json/Volo/Abp/Json/Newtonsoft/AbpNewtonsoftJsonSerializerOptions.cs @@ -0,0 +1,15 @@ +using Newtonsoft.Json; +using Volo.Abp.Collections; + +namespace Volo.Abp.Json.Newtonsoft +{ + public class AbpNewtonsoftJsonSerializerOptions + { + public ITypeList Converters { get; } + + public AbpNewtonsoftJsonSerializerOptions() + { + Converters = new TypeList(); + } + } +} diff --git a/framework/src/Volo.Abp.Json/Volo/Abp/Json/Newtonsoft/NewtonsoftJsonSerializer.cs b/framework/src/Volo.Abp.Json/Volo/Abp/Json/Newtonsoft/NewtonsoftJsonSerializer.cs index 99252b8448..20e49ac817 100644 --- a/framework/src/Volo.Abp.Json/Volo/Abp/Json/Newtonsoft/NewtonsoftJsonSerializer.cs +++ b/framework/src/Volo.Abp.Json/Volo/Abp/Json/Newtonsoft/NewtonsoftJsonSerializer.cs @@ -1,4 +1,8 @@ using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using Volo.Abp.DependencyInjection; @@ -7,14 +11,19 @@ namespace Volo.Abp.Json.Newtonsoft { public class NewtonsoftJsonSerializer : IJsonSerializer, ITransientDependency { - private readonly AbpJsonIsoDateTimeConverter _dateTimeConverter; - private static readonly CamelCaseExceptDictionaryKeysResolver SharedCamelCaseExceptDictionaryKeysResolver = new CamelCaseExceptDictionaryKeysResolver(); - public NewtonsoftJsonSerializer(AbpJsonIsoDateTimeConverter dateTimeConverter) + protected List Converters { get; } + + public NewtonsoftJsonSerializer( + IOptions options, + IServiceProvider serviceProvider) { - _dateTimeConverter = dateTimeConverter; + Converters = options.Value + .Converters + .Select(c => (JsonConverter) serviceProvider.GetRequiredService(c)) + .ToList(); } public string Serialize(object obj, bool camelCase = true, bool indented = false) @@ -36,8 +45,8 @@ namespace Volo.Abp.Json.Newtonsoft { var settings = new JsonSerializerSettings(); - settings.Converters.Insert(0, _dateTimeConverter); - + settings.Converters.InsertRange(0, Converters); + if (camelCase) { settings.ContractResolver = SharedCamelCaseExceptDictionaryKeysResolver; @@ -47,7 +56,7 @@ namespace Volo.Abp.Json.Newtonsoft { settings.Formatting = Formatting.Indented; } - + return settings; } @@ -63,4 +72,4 @@ namespace Volo.Abp.Json.Newtonsoft } } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Ldap/Volo.Abp.Ldap.csproj b/framework/src/Volo.Abp.Ldap/Volo.Abp.Ldap.csproj index 229f3c6c7d..849fe3ce3d 100644 --- a/framework/src/Volo.Abp.Ldap/Volo.Abp.Ldap.csproj +++ b/framework/src/Volo.Abp.Ldap/Volo.Abp.Ldap.csproj @@ -15,11 +15,19 @@ - + - + + + + + + - + + + + diff --git a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/AbpAbpLdapOptionsFactory.cs b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/AbpAbpLdapOptionsFactory.cs new file mode 100644 index 0000000000..9579e169be --- /dev/null +++ b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/AbpAbpLdapOptionsFactory.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using Volo.Abp.Options; +using Volo.Abp.Settings; +using Volo.Abp.Threading; + +namespace Volo.Abp.Ldap +{ + public class AbpAbpLdapOptionsFactory : AbpOptionsFactory + { + protected ISettingProvider SettingProvider { get; } + + public AbpAbpLdapOptionsFactory( + IEnumerable> setups, + IEnumerable> postConfigures, + ISettingProvider settingProvider) + : base(setups, postConfigures) + { + SettingProvider = settingProvider; + } + + public override AbpLdapOptions Create(string name) + { + var options = base.Create(name); + + AsyncHelper.RunSync(() => OverrideOptionsAsync(options)); + + return options; + } + + protected virtual async Task OverrideOptionsAsync(AbpLdapOptions options) + { + options.ServerHost = await GetSettingOrDefaultValue(LdapSettingNames.ServerHost, options.ServerHost); + options.ServerPort = await SettingProvider.GetAsync(LdapSettingNames.ServerPort, options.ServerPort); + options.BaseDc = await GetSettingOrDefaultValue(LdapSettingNames.BaseDc, options.BaseDc); + options.UserName = await GetSettingOrDefaultValue(LdapSettingNames.UserName, options.UserName); + options.Password = await GetSettingOrDefaultValue(LdapSettingNames.Password, options.Password); + } + + protected virtual async Task GetSettingOrDefaultValue(string name, string defaultValue) + { + var value = await SettingProvider.GetOrNullAsync(name); + return value.IsNullOrWhiteSpace() ? defaultValue : value; + } + } +} diff --git a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/AbpLdapModule.cs b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/AbpLdapModule.cs index b35e1b1d59..c842d4d76b 100644 --- a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/AbpLdapModule.cs +++ b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/AbpLdapModule.cs @@ -1,18 +1,44 @@ -using Microsoft.Extensions.DependencyInjection; -using Volo.Abp.Autofac; +using System; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; +using Volo.Abp.Ldap.Localization; +using Volo.Abp.Localization; using Volo.Abp.Modularity; +using Volo.Abp.Settings; +using Volo.Abp.VirtualFileSystem; namespace Volo.Abp.Ldap { [DependsOn( - typeof(AbpAutofacModule) - )] + typeof(AbpSettingsModule), + typeof(AbpVirtualFileSystemModule), + typeof(AbpLocalizationModule))] public class AbpLdapModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) { + context.Services.Replace(ServiceDescriptor.Transient, AbpAbpLdapOptionsFactory>()); + context.Services.Replace(ServiceDescriptor.Scoped, OptionsManager>()); + var configuration = context.Services.GetConfiguration(); - Configure(configuration.GetSection("LDAP")); + var ldapConfiguration = configuration["Ldap"]; + if (!ldapConfiguration.IsNullOrEmpty()) + { + Configure(configuration.GetSection("Ldap")); + } + + Configure(options => + { + options.FileSets.AddEmbedded(); + }); + + Configure(options => + { + options.Resources + .Add("en") + .AddVirtualJson("/Volo/Abp/Ldap/Localization"); + }); } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/AbpLdapOptions.cs b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/AbpLdapOptions.cs index 671d623ff7..ab9c2d12f2 100644 --- a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/AbpLdapOptions.cs +++ b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/AbpLdapOptions.cs @@ -6,19 +6,16 @@ public int ServerPort { get; set; } - public bool UseSsl { get; set; } + public string BaseDc { get; set; } - public string SearchBase { get; set; } + /// + /// BindDN + /// + public string UserName { get; set; } - public string DomainName { get; set; } - - public string DomainDistinguishedName { get; set; } - - public LdapCredentials Credentials { get; set; } - - public AbpLdapOptions() - { - Credentials = new LdapCredentials(); - } + /// + ///BindPassword + /// + public string Password { get; set; } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Exceptions/OrganizationNotExistException.cs b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Exceptions/OrganizationNotExistException.cs deleted file mode 100644 index 9d7fb220cc..0000000000 --- a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Exceptions/OrganizationNotExistException.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Volo.Abp.Ldap.Exceptions -{ - public class OrganizationNotExistException : BusinessException - { - public OrganizationNotExistException(string distinguishedName) - : base("LDAP:000001", $"the organization distinguished named {distinguishedName} does not exist.") - { - - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/ILdapManager.cs b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/ILdapManager.cs index f14e88b409..6ad66b0cb3 100644 --- a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/ILdapManager.cs +++ b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/ILdapManager.cs @@ -1,72 +1,7 @@ -using System.Collections.Generic; -using Volo.Abp.Ldap.Modeling; - -namespace Volo.Abp.Ldap +namespace Volo.Abp.Ldap { public interface ILdapManager { - /// - /// query the specified organizations. - /// - /// filter: (&(name=xxx)(objectClass=organizationalUnit)) when name is not null - /// filter: (&(name=*)(objectClass=organizationalUnit)) when name is null - /// - /// - /// - /// - IList GetOrganizations(string name = null); - - /// - /// query the specified organization. - /// - /// filter: (&(distinguishedName=xxx)(objectClass=organizationalUnit)) when organizationName is not null - /// - /// - /// - /// - LdapOrganization GetOrganization(string distinguishedName); - - void AddSubOrganization(string organizationName, LdapOrganization parentOrganization); - void AddSubOrganization(string organizationName, string parentDistinguishedName); - - /// - /// query the specified users. - /// - /// filter: (&(name=xxx)(objectCategory=person)(objectClass=user)) when name is not null - /// filter: (&(name=*)(objectCategory=person)(objectClass=user)) when name is null - /// - /// filter: (&(displayName=xxx)(objectCategory=person)(objectClass=user)) when displayName is not null - /// filter: (&(displayName=*)(objectCategory=person)(objectClass=user)) when displayName is null - /// - /// filter: (&(cn=xxx)(objectCategory=person)(objectClass=user)) when commonName is not null - /// filter: (&(cn=*)(objectCategory=person)(objectClass=user)) when commonName is null - /// - /// - /// - /// - /// - /// - IList GetUsers(string name = null, string displayName = null, string commonName = null); - - /// - /// query the specified User. - /// - /// filter: (&(distinguishedName=xxx)(objectCategory=person)(objectClass=user)) when distinguishedName is not null - /// - /// - /// - /// - LdapUser GetUser(string distinguishedName); - - void AddUserToOrganization(string userName, string password, LdapOrganization parentOrganization); - void AddUserToOrganization(string userName, string password, string parentDistinguishedName); - - /// - /// Authenticate - /// - /// E.g administrator@yourdomain.com.cn - /// - /// - bool Authenticate(string userDomainName, string password); + bool Authenticate(string username, string password); } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/LdapCredentials.cs b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/LdapCredentials.cs deleted file mode 100644 index a2d2eefd74..0000000000 --- a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/LdapCredentials.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Volo.Abp.Ldap -{ - public class LdapCredentials - { - public string DomainUserName { get; set; } - - public string Password { get; set; } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/LdapHelps.cs b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/LdapHelps.cs deleted file mode 100644 index 49a60a964f..0000000000 --- a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/LdapHelps.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Collections.Generic; - -namespace Volo.Abp.Ldap -{ - public static class LdapHelps - { - public static string BuildCondition(string name, string value) - { - return string.IsNullOrWhiteSpace(value) ? "" : $"({name}={value})"; - } - - public static string BuildFilter(Dictionary conditions) - { - if (null == conditions ) - { - conditions = new Dictionary(); - } - - if (conditions.Keys.Count == 0) - { - conditions.Add("objectClass", "*"); // add default condition - } - - var subFilter = string.Empty; - foreach (var keyValuePair in conditions) - { - subFilter += BuildCondition(keyValuePair.Key, keyValuePair.Value); - } - - return $"(&{subFilter})"; - } - - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/LdapManager.cs b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/LdapManager.cs index 9e0b8f04a7..2d890c10f7 100644 --- a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/LdapManager.cs +++ b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/LdapManager.cs @@ -1,343 +1,57 @@ using System; using Microsoft.Extensions.Options; using Novell.Directory.Ldap; -using System.Collections.Generic; -using System.Text; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using Volo.Abp.DependencyInjection; -using Volo.Abp.ExceptionHandling; -using Volo.Abp.Ldap.Exceptions; -using Volo.Abp.Ldap.Modeling; namespace Volo.Abp.Ldap { public class LdapManager : ILdapManager, ITransientDependency { - private readonly string _searchBase; - private readonly AbpLdapOptions _ldapOptions; - private readonly IHybridServiceScopeFactory _hybridServiceScopeFactory; + public ILogger Logger { get; set; } + protected AbpLdapOptions LdapOptions { get; } - private readonly string[] _attributes = + public LdapManager(IOptions ldapSettingsOptions) { - "objectCategory", "objectClass", "cn", "name", "distinguishedName", - "ou", - "sAMAccountName", "userPrincipalName", "telephoneNumber", "mail" - }; + LdapOptions = ldapSettingsOptions.Value; - public LdapManager(IOptions ldapSettingsOptions, IHybridServiceScopeFactory hybridServiceScopeFactory) - { - _hybridServiceScopeFactory = hybridServiceScopeFactory; - _ldapOptions = ldapSettingsOptions.Value; - _searchBase = _ldapOptions.SearchBase; - } - - #region Organization - /// - /// query the specified organizations. - /// - /// filter: (&(name=xxx)(objectClass=organizationalUnit)) when name is not null - /// filter: (&(objectClass=organizationalUnit)) when name is null - /// - /// - /// - /// - public IList GetOrganizations(string name = null) - { - var conditions = new Dictionary - { - {"name", name}, - {"objectClass", "organizationalUnit"}, - }; - return Query(_searchBase, conditions); - } - - /// - /// query the specified organization. - /// - /// filter: (&(distinguishedName=xxx)(objectClass=organizationalUnit)) when organizationName is not null - /// - /// - /// - /// - public LdapOrganization GetOrganization(string distinguishedName) - { - distinguishedName = Check.NotNullOrWhiteSpace(distinguishedName, nameof(distinguishedName)); - var conditions = new Dictionary - { - {"distinguishedName", distinguishedName}, - {"objectClass", "organizationalUnit"}, - }; - return QueryOne(_searchBase, conditions); - } - - public void AddSubOrganization(string organizationName, LdapOrganization parentOrganization) - { - organizationName = Check.NotNullOrWhiteSpace(organizationName, nameof(organizationName)); - var dn = $"OU={organizationName},{parentOrganization.DistinguishedName}"; - - var attributeSet = new LdapAttributeSet - { - new LdapAttribute("objectCategory", $"CN=Organizational-Unit,CN=Schema,CN=Configuration,{_ldapOptions.DomainDistinguishedName}"), - new LdapAttribute("objectClass", new[] {"top", "organizationalUnit"}), - new LdapAttribute("name", organizationName), - }; - - var newEntry = new LdapEntry(dn, attributeSet); - - using (var ldapConnection = GetConnection()) - { - ldapConnection.Add(newEntry); - } - } - - public void AddSubOrganization(string organizationName, string parentDistinguishedName) - { - organizationName = Check.NotNullOrWhiteSpace(organizationName, nameof(organizationName)); - parentDistinguishedName = - Check.NotNullOrWhiteSpace(parentDistinguishedName, nameof(parentDistinguishedName)); - - var parentOrganization = GetOrganization(parentDistinguishedName); - if (null == parentOrganization) - { - throw new OrganizationNotExistException(parentDistinguishedName); - } - - AddSubOrganization(organizationName, parentOrganization); - } - - #endregion - - #region User - /// - /// query the specified users. - /// - /// filter: (&(name=xxx)(objectCategory=person)(objectClass=user)) when name is not null - /// filter: (&(objectCategory=person)(objectClass=user)) when name is null - /// - /// filter: (&(displayName=xxx)(objectCategory=person)(objectClass=user)) when displayName is not null - /// filter: (&(objectCategory=person)(objectClass=user)) when displayName is null - /// - /// filter: (&(cn=xxx)(objectCategory=person)(objectClass=user)) when commonName is not null - /// filter: (&(objectCategory=person)(objectClass=user)) when commonName is null - /// - /// - /// - /// - /// - /// - public IList GetUsers(string name = null, string displayName = null, string commonName = null) - { - var conditions = new Dictionary - { - {"objectCategory", "person"}, - {"objectClass", "user"}, - {"name", name}, - {"displayName", displayName}, - {"cn", commonName}, - }; - return Query(_searchBase, conditions); + Logger = NullLogger.Instance; } - /// - /// query the specified User. - /// - /// filter: (&(distinguishedName=xxx)(objectCategory=person)(objectClass=user)) when distinguishedName is not null - /// - /// - /// - /// - public LdapUser GetUser(string distinguishedName) - { - distinguishedName = Check.NotNullOrWhiteSpace(distinguishedName, nameof(distinguishedName)); - var conditions = new Dictionary - { - {"objectCategory", "person"}, - {"objectClass", "user"}, - {"distinguishedName", distinguishedName}, - }; - return QueryOne(_searchBase, conditions); - } - - public void AddUserToOrganization(string userName, string password, LdapOrganization parentOrganization) - { - var dn = $"CN={userName},{parentOrganization.DistinguishedName}"; - var mail = $"{userName}@{_ldapOptions.DomainName}"; - sbyte[] encodedBytes = SupportClass.ToSByteArray(Encoding.Unicode.GetBytes($"\"{password}\"")); - - var attributeSet = new LdapAttributeSet - { - new LdapAttribute("instanceType", "4"), - new LdapAttribute("objectCategory", $"CN=Person,CN=Schema,CN=Configuration,{_ldapOptions.DomainDistinguishedName}"), - new LdapAttribute("objectClass", new[] {"top", "person", "organizationalPerson", "user"}), - new LdapAttribute("name", userName), - new LdapAttribute("cn", userName), - new LdapAttribute("sAMAccountName", userName), - new LdapAttribute("userPrincipalName", userName), - new LdapAttribute("sn", userName), - new LdapAttribute("displayName", userName), - new LdapAttribute("unicodePwd", encodedBytes), - new LdapAttribute("userAccountControl", "512"), - new LdapAttribute("mail", mail), - }; - var newEntry = new LdapEntry(dn, attributeSet); - - using (var ldapConnection = GetConnection()) - { - ldapConnection.Add(newEntry); - } - } - - public void AddUserToOrganization(string userName, string password, string parentDistinguishedName) - { - var dn = $"CN={userName},{parentDistinguishedName}"; - var mail = $"{userName}@{_ldapOptions.DomainName}"; - sbyte[] encodedBytes = SupportClass.ToSByteArray(Encoding.Unicode.GetBytes($"\"{password}\"")); - - var attributeSet = new LdapAttributeSet - { - new LdapAttribute("instanceType", "4"), - new LdapAttribute("objectCategory", $"CN=Person,CN=Schema,CN=Configuration,{_ldapOptions.DomainDistinguishedName}"), - new LdapAttribute("objectClass", new[] {"top", "person", "organizationalPerson", "user"}), - new LdapAttribute("name", userName), - new LdapAttribute("cn", userName), - new LdapAttribute("sAMAccountName", userName), - new LdapAttribute("userPrincipalName", userName), - new LdapAttribute("sn", userName), - new LdapAttribute("displayName", userName), - new LdapAttribute("unicodePwd", encodedBytes), - new LdapAttribute("userAccountControl", "512"), - new LdapAttribute("mail", mail), - }; - var newEntry = new LdapEntry(dn, attributeSet); - - using (var ldapConnection = GetConnection()) - { - ldapConnection.Add(newEntry); - } - } - - #endregion - - #region Authenticate - - /// - /// Authenticate - /// - /// E.g administrator@yourdomain.com.cn - /// - /// - public bool Authenticate(string userDomainName, string password) + public bool Authenticate(string username, string password) { try { - using (GetConnection(userDomainName, password)) + using (var conn = CreateLdapConnection()) { + AuthenticateLdapConnection(conn, username, password); return true; } } catch (Exception ex) { - using (var scope = _hybridServiceScopeFactory.CreateScope()) - { - scope.ServiceProvider - .GetRequiredService() - .NotifyAsync(ex); - } - + Logger.LogException(ex); return false; } } - #endregion - - private ILdapConnection GetConnection(string bindUserName = null, string bindUserPassword = null) + protected virtual ILdapConnection CreateLdapConnection() { - // bindUserName/bindUserPassword only be used when authenticate - bindUserName = bindUserName ?? _ldapOptions.Credentials.DomainUserName; - bindUserPassword = bindUserPassword ?? _ldapOptions.Credentials.Password; - - var ldapConnection = new LdapConnection() { SecureSocketLayer = _ldapOptions.UseSsl }; - if (_ldapOptions.UseSsl) - { - ldapConnection.UserDefinedServerCertValidationDelegate += (sender, certificate, chain, sslPolicyErrors) => true; - } - ldapConnection.Connect(_ldapOptions.ServerHost, _ldapOptions.ServerPort); - - if (_ldapOptions.UseSsl) - { - ldapConnection.Bind(LdapConnection.Ldap_V3, bindUserName, bindUserPassword); - } - else - { - ldapConnection.Bind(bindUserName, bindUserPassword); - } + var ldapConnection = new LdapConnection(); + ConfigureLdapConnection(ldapConnection); + ldapConnection.Connect(LdapOptions.ServerHost, LdapOptions.ServerPort); return ldapConnection; } - private IList Query(string searchBase, Dictionary conditions) where T : class, ILdapEntry + protected virtual void ConfigureLdapConnection(ILdapConnection connection) { - var filter = LdapHelps.BuildFilter(conditions); - - var result = new List(); - using (var ldapConnection = GetConnection()) - { - var search = ldapConnection.Search(searchBase, LdapConnection.SCOPE_SUB, filter, - _attributes, false, null, null); - - LdapMessage message; - while ((message = search.getResponse()) != null) - { - if (!(message is LdapSearchResult searchResultMessage)) - { - continue; - } - var entry = searchResultMessage.Entry; - if (typeof(T) == typeof(LdapOrganization)) - { - result.Add(new LdapOrganization(entry.getAttributeSet()) as T); - } - - if (typeof(T) == typeof(LdapUser)) - { - result.Add(new LdapUser(entry.getAttributeSet()) as T); - } - } - } - return result; } - private T QueryOne(string searchBase, Dictionary conditions) where T : class, ILdapEntry + protected virtual void AuthenticateLdapConnection(ILdapConnection connection, string username, string password) { - var filter = LdapHelps.BuildFilter(conditions); - - using (var ldapConnection = GetConnection()) - { - var search = ldapConnection.Search(searchBase, LdapConnection.SCOPE_SUB, filter, - _attributes, false, null, null); - - LdapMessage message; - while ((message = search.getResponse()) != null) - { - if (!(message is LdapSearchResult searchResultMessage)) - { - continue; - } - var entry = searchResultMessage.Entry; - if (typeof(T) == typeof(LdapOrganization)) - { - return new LdapOrganization(entry.getAttributeSet()) as T; - } - - if (typeof(T) == typeof(LdapUser)) - { - return new LdapUser(entry.getAttributeSet()) as T; - } - return null; - } - } - return null; + connection.Bind(username, password); } - } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/LdapSettingNames.cs b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/LdapSettingNames.cs new file mode 100644 index 0000000000..14b2546bbf --- /dev/null +++ b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/LdapSettingNames.cs @@ -0,0 +1,15 @@ +namespace Volo.Abp.Ldap +{ + public static class LdapSettingNames + { + public const string ServerHost = "Abp.Ldap.ServerHost"; + + public const string ServerPort = "Abp.Ldap.ServerPort"; + + public const string BaseDc = "Abp.Ldap.BaseDc"; + + public const string UserName = "Abp.Ldap.UserName"; + + public const string Password = "Abp.Ldap.Password"; + } +} diff --git a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/LdapSettingProvider.cs b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/LdapSettingProvider.cs new file mode 100644 index 0000000000..edcfc2e4ef --- /dev/null +++ b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/LdapSettingProvider.cs @@ -0,0 +1,50 @@ +using Volo.Abp.Ldap.Localization; +using Volo.Abp.Localization; +using Volo.Abp.Settings; + +namespace Volo.Abp.Ldap +{ + public class LdapSettingProvider : SettingDefinitionProvider + { + public override void Define(ISettingDefinitionContext context) + { + context.Add( + new SettingDefinition( + LdapSettingNames.ServerHost, + "", + L("DisplayName:Abp.Ldap.ServerHost"), + L("Description:Abp.Ldap.ServerHost")), + + new SettingDefinition( + LdapSettingNames.ServerPort, + "389", + L("DisplayName:Abp.Ldap.ServerPort"), + L("Description:Abp.Ldap.ServerPort")), + + new SettingDefinition( + LdapSettingNames.BaseDc, + "", + L("DisplayName:Abp.Ldap.BaseDc"), + L("Description:Abp.Ldap.BaseDc")), + + new SettingDefinition( + LdapSettingNames.UserName, + "", + L("DisplayName:Abp.Ldap.UserName"), + L("Description:Abp.Ldap.UserName")), + + new SettingDefinition( + LdapSettingNames.Password, + "", + L("DisplayName:Abp.Ldap.Password"), + L("Description:Abp.Ldap.Password"), + isEncrypted: true) + ); + } + + private static LocalizableString L(string name) + { + return LocalizableString.Create(name); + } + } +} diff --git a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Localization/LdapResource.cs b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Localization/LdapResource.cs new file mode 100644 index 0000000000..e45b6797b5 --- /dev/null +++ b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Localization/LdapResource.cs @@ -0,0 +1,10 @@ +using Volo.Abp.Localization; + +namespace Volo.Abp.Ldap.Localization +{ + [LocalizationResourceName("AbpLdap")] + public class LdapResource + { + + } +} diff --git a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Localization/en.json b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Localization/en.json new file mode 100644 index 0000000000..1d720fc26f --- /dev/null +++ b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Localization/en.json @@ -0,0 +1,19 @@ +{ + "culture": "en", + "texts": { + "DisplayName:Abp.Ldap.ServerHost": "Server host", + "Description:Abp.Ldap.ServerHost": "Server host", + + "DisplayName:Abp.Ldap.ServerPort": "Server port", + "Description:Abp.Ldap.ServerPort": "Server port", + + "DisplayName:Abp.Ldap.BaseDc": "Base domain component", + "Description:Abp.Ldap.BaseDc": "Base domain component", + + "DisplayName:Abp.Ldap.UserName": "Username", + "Description:Abp.Ldap.UserName": "Username", + + "DisplayName:Abp.Ldap.Password": "Password", + "Description:Abp.Ldap.Password": "Password" + } +} diff --git a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Localization/tr.json b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Localization/tr.json new file mode 100644 index 0000000000..7d04b9aa50 --- /dev/null +++ b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Localization/tr.json @@ -0,0 +1,19 @@ +{ + "culture": "tr", + "texts": { + "DisplayName:Abp.Ldap.ServerHost": "Sunucu Ana Bilgisayarı", + "Description:Abp.Ldap.ServerHost": "Sunucu Ana Bilgisayarı", + + "DisplayName:Abp.Ldap.ServerPort": "Sunucu portu", + "Description:Abp.Ldap.ServerPort": "Sunucu portu", + + "DisplayName:Abp.Ldap.BaseDc": "Temel alan bileşeni", + "Description:Abp.Ldap.BaseDc": "Temel alan bileşeni", + + "DisplayName:Abp.Ldap.UserName": "Kullanıcı adı", + "Description:Abp.Ldap.UserName": "Kullanıcı adı", + + "DisplayName:Abp.Ldap.Password": "parola", + "Description:Abp.Ldap.Password": "parola" + } +} diff --git a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Localization/zh-Hans.json b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Localization/zh-Hans.json new file mode 100644 index 0000000000..7efddf2682 --- /dev/null +++ b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Localization/zh-Hans.json @@ -0,0 +1,19 @@ +{ + "culture": "zh-Hans", + "texts": { + "DisplayName:Abp.Ldap.ServerHost": "服务器主机", + "Description:Abp.Ldap.ServerHost": "服务器主机", + + "DisplayName:Abp.Ldap.ServerPort": "服务器端口", + "Description:Abp.Ldap.ServerPort": "服务器端口", + + "DisplayName:Abp.Ldap.BaseDc": "基域组件", + "Description:Abp.Ldap.BaseDc": "基域组件", + + "DisplayName:Abp.Ldap.UserName": "用户名", + "Description:Abp.Ldap.UserName": "用户名", + + "DisplayName:Abp.Ldap.Password": "密码", + "Description:Abp.Ldap.Password": "密码" + } +} diff --git a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Localization/zh-Hant.json b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Localization/zh-Hant.json new file mode 100644 index 0000000000..d8e30ea362 --- /dev/null +++ b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Localization/zh-Hant.json @@ -0,0 +1,19 @@ +{ + "culture": "zh-Hant", + "texts": { + "DisplayName:Abp.Ldap.ServerHost": "服務器主機", + "Description:Abp.Ldap.ServerHost": "服務器主機", + + "DisplayName:Abp.Ldap.ServerPort": "服務器端口", + "Description:Abp.Ldap.ServerPort": "服務器端口", + + "DisplayName:Abp.Ldap.BaseDc": "基域組件", + "Description:Abp.Ldap.BaseDc": "基域組件", + + "DisplayName:Abp.Ldap.UserName": "用戶名", + "Description:Abp.Ldap.UserName": "用戶名", + + "DisplayName:Abp.Ldap.Password": "密碼", + "Description:Abp.Ldap.Password": "密碼" + } +} diff --git a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Modeling/ILdapEntry.cs b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Modeling/ILdapEntry.cs deleted file mode 100644 index f15775fc06..0000000000 --- a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Modeling/ILdapEntry.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Volo.Abp.Ldap.Modeling -{ - public interface ILdapEntry - { - string ObjectCategory { get; set; } - string[] ObjectClass { get; set; } - string Name { get; set; } - string DistinguishedName { get; set; } - string CommonName { get; set; } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Modeling/ILdapOrganization.cs b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Modeling/ILdapOrganization.cs deleted file mode 100644 index 07c11000e8..0000000000 --- a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Modeling/ILdapOrganization.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Volo.Abp.Ldap.Modeling -{ - public interface ILdapOrganization : ILdapEntry - { - string OrganizationUnit { get; set; } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Modeling/ILdapUser.cs b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Modeling/ILdapUser.cs deleted file mode 100644 index 8ef68f6607..0000000000 --- a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Modeling/ILdapUser.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Volo.Abp.Ldap.Modeling -{ - public interface ILdapUser : ILdapEntry - { - string SamAccountName { get; set; } - string UserPrincipalName { get; set; } - string DisplayName { get; set; } - string Email { get; set; } - string Phone { get; set; } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Modeling/LdapEntryBase.cs b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Modeling/LdapEntryBase.cs deleted file mode 100644 index 6658a983d0..0000000000 --- a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Modeling/LdapEntryBase.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Novell.Directory.Ldap; - -namespace Volo.Abp.Ldap.Modeling -{ - public abstract class LdapEntryBase : ILdapEntry - { - public string ObjectCategory { get; set; } - public string[] ObjectClass { get; set; } - public string Name { get; set; } - public string CommonName { get; set; } - public string DistinguishedName { get; set; } - - protected LdapEntryBase() { } - - protected LdapEntryBase(LdapAttributeSet attributeSet) - { - ObjectCategory = attributeSet.getAttribute("objectCategory")?.StringValue; - ObjectClass = attributeSet.getAttribute("objectClass")?.StringValueArray; - Name = attributeSet.getAttribute("name")?.StringValue; - CommonName = attributeSet.getAttribute("cn")?.StringValue; - DistinguishedName = attributeSet.getAttribute("distinguishedName")?.StringValue; - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Modeling/LdapOrganization.cs b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Modeling/LdapOrganization.cs deleted file mode 100644 index e401a2514d..0000000000 --- a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Modeling/LdapOrganization.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Novell.Directory.Ldap; - -namespace Volo.Abp.Ldap.Modeling -{ - public class LdapOrganization : LdapEntryBase, ILdapOrganization - { - public string OrganizationUnit { get; set; } - - public LdapOrganization() { } - - public LdapOrganization(LdapAttributeSet attributeSet) - : base(attributeSet) - { - OrganizationUnit = attributeSet.getAttribute("ou")?.StringValue; - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Modeling/LdapUser.cs b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Modeling/LdapUser.cs deleted file mode 100644 index d9f554d512..0000000000 --- a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Modeling/LdapUser.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Novell.Directory.Ldap; - -namespace Volo.Abp.Ldap.Modeling -{ - public class LdapUser : LdapEntryBase, ILdapUser - { - public string SamAccountName { get; set; } - public string UserPrincipalName { get; set; } - public string DisplayName { get; set; } - public string Email { get; set; } - public string Phone { get; set; } - - public LdapUser() { } - - public LdapUser( LdapAttributeSet attributeSet) - : base(attributeSet) - { - SamAccountName = attributeSet.getAttribute("sAMAccountName")?.StringValue; - UserPrincipalName = attributeSet.getAttribute("userPrincipalName")?.StringValue; - DisplayName = attributeSet.getAttribute("displayName")?.StringValue; - Email = attributeSet.getAttribute("mail")?.StringValue; - Phone = attributeSet.getAttribute("telephoneNumber")?.StringValue; - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Ldap/readme.md b/framework/src/Volo.Abp.Ldap/readme.md deleted file mode 100644 index dd9b84087d..0000000000 --- a/framework/src/Volo.Abp.Ldap/readme.md +++ /dev/null @@ -1,171 +0,0 @@ -# Volo.Abp.Ldap - -# Only Authenticate(not read/write AD) - -## Configure - -add section in `appsettings.json` - -### use SSL - -```json -"LDAP": { - "ServerHost": "192.168.101.54", - "ServerPort": 636, - "UseSsl": true -} -``` - -### not use SSL - -```json -"LDAP": { - "ServerHost": "192.168.101.54", - "ServerPort": 389, - "UseSsl": false -} -``` - -## Authenticate - - Injecting `ILdapManager` into a class. For example: - -```csharp -public class TaxAppService : ApplicationService -{ - private readonly ILdapManager _ldapManager; - - public TaxAppService(ILdapManager ldapManager) - { - _ldapManager = ldapManager; - } - - public void Authenticate(string userName, string password) - { - var result = _ldapManager.Authenticate(userName, password); - } -} -``` - -- `userName` must be full domain name. E.g abc@abc.com - -# Read/Write AD - -## Configure - -### use SSL - -```json -"LDAP": { - "ServerHost": "192.168.101.54", - "ServerPort": 636, - "UseSsl": true, - "Credentials": { - "DomainUserName": "administrator@yourdomain.com.cn", - "Password": "yH.20190528" - }, - "SearchBase": "DC=yourdomain,DC=com,DC=cn", - "DomainName": "yourdomain.com.cn", - "DomainDistinguishedName": "DC=yourdomain,DC=com,DC=cn" -} -``` - -### not use SSL - -```json -"LDAP": { - "ServerHost": "192.168.101.54", - "ServerPort": 389, - "UseSsl": false, - "Credentials": { - "DomainUserName": "administrator@yourdomain.com.cn", - "Password": "yH.20190528" - }, - "SearchBase": "DC=yourdomain,DC=com,DC=cn", - "DomainName": "yourdomain.com.cn", - "DomainDistinguishedName": "DC=yourdomain,DC=com,DC=cn" -} -``` - -- `Credentials:DomainUserName` a administrator of AD. - -- `Credentials:Password` the password for the administrator. -- `SearchBase`: where search from AD. -- `DomainName`: name of you domain. no need `www`. -- `DomainDistinguishedName`: distinguished name of root domain. - -## Query Organizations - -```cs -// query all organizations -// filter: (&(objectClass=organizationalUnit)) -_ldapManager.GetOrganizations(); - -// query organizations by name -// filter: (&(name=abc)(objectClass=organizationalUnit)) -_ldapManager.GetOrganizations("abc"); - -``` - -## Query Organization - -```csharp -// query organization by distinguished name -// filter: (&(distinguishedName=abc)(objectClass=organizationalUnit)) -_ldapManager.GetOrganization("abc"); - -``` - -## Add Organization - -```csharp -// use LdapOrganization -_ldapManager.AddSubOrganization("nameA", parentOrganization); - -// or use OrganizationDistinguishedName -_ldapManager.AddSubOrganization("nameA", "OU=Domain Controllers,DC=yourdomain,DC=com,DC=cn"); -``` - -## Query Users - -```cs -// query all users -// filter: (&(objectCategory=person)(objectClass=user)) -_ldapManager.GetUsers(); - -// query organizations by name -// filter: (&(name=abc)(objectCategory=person)(objectClass=user)) -_ldapManager.GetUsers(name : "abc"); - -// query organizations by displayName -// filter: (&(displayName=abc)(objectCategory=person)(objectClass=user)) -_ldapManager.GetUsers(displayName : "abc"); - -// query organization by commonName -// filter: (&(cn=abc)(objectCategory=person)(objectClass=user)) -_ldapManager.GetUsers(commonName : "abc"); - -``` - -## Query User - -```csharp -// query a user by distinguished name -// filter: (&(distinguishedName=abc)(objectCategory=person)(objectClass=user)) -_ldapManager.GetUser("abc"); - -``` - -## Add User - -```csharp -// use LdapOrganization -_ldapManager.AddUserToOrganization("nameA", "passwordA", parentOrganization); - -// or use OrganizationDistinguishedName -_ldapManager.AddUserToOrganization("nameA", "passwordA", "OU=Domain Controllers,DC=yourdomain,DC=com,DC=cn"); -``` - -# More - -See [unit test](../../test/Volo.Abp.Ldap.Tests) \ No newline at end of file diff --git a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpLocalizationOptionsExtensions.cs b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpLocalizationOptionsExtensions.cs index 36355027db..410e4684cc 100644 --- a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpLocalizationOptionsExtensions.cs +++ b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpLocalizationOptionsExtensions.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Globalization; using System.Linq; namespace Volo.Abp.Localization @@ -24,6 +25,11 @@ namespace Volo.Abp.Localization : language; } + public static string GetCurrentUICultureLanguagesMap(this AbpLocalizationOptions localizationOptions, string packageName) + { + return GetLanguagesMap(localizationOptions, packageName, CultureInfo.CurrentUICulture.Name); + } + public static AbpLocalizationOptions AddLanguageFilesMapOrUpdate(this AbpLocalizationOptions localizationOptions, string packageName, params NameValue[] maps) { @@ -43,6 +49,11 @@ namespace Volo.Abp.Localization : language; } + public static string GetCurrentUICultureLanguageFilesMap(this AbpLocalizationOptions localizationOptions, string packageName) + { + return GetLanguageFilesMap(localizationOptions, packageName, CultureInfo.CurrentUICulture.Name); + } + private static void AddOrUpdate(IDictionary> maps, string packageName, NameValue value) { if (maps.TryGetValue(packageName, out var existMaps)) diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs index 132801b203..8676ac0f34 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs @@ -50,6 +50,7 @@ namespace Volo.Abp.Domain.Repositories.MongoDB LocalEventBus = NullLocalEventBus.Instance; DistributedEventBus = NullDistributedEventBus.Instance; EntityChangeEventHelper = NullEntityChangeEventHelper.Instance; + GuidGenerator = SimpleGuidGenerator.Instance; } public override async Task InsertAsync( diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/AbpSecurityLogOptions.cs b/framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/AbpSecurityLogOptions.cs new file mode 100644 index 0000000000..a1cc6406f4 --- /dev/null +++ b/framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/AbpSecurityLogOptions.cs @@ -0,0 +1,21 @@ +namespace Volo.Abp.SecurityLog +{ + public class AbpSecurityLogOptions + { + /// + /// Default: true. + /// + public bool IsEnabled { get; set; } + + /// + /// The name of the application or service writing security log. + /// Default: null. + /// + public string ApplicationName { get; set; } + + public AbpSecurityLogOptions() + { + IsEnabled = true; + } + } +} diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/DefaultSecurityLogManager.cs b/framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/DefaultSecurityLogManager.cs new file mode 100644 index 0000000000..f628958bc4 --- /dev/null +++ b/framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/DefaultSecurityLogManager.cs @@ -0,0 +1,42 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.SecurityLog +{ + public class DefaultSecurityLogManager : ISecurityLogManager, ITransientDependency + { + protected AbpSecurityLogOptions SecurityLogOptions { get; } + + protected ISecurityLogStore SecurityLogStore { get; } + + public DefaultSecurityLogManager( + IOptions securityLogOptions, + ISecurityLogStore securityLogStore) + { + SecurityLogStore = securityLogStore; + SecurityLogOptions = securityLogOptions.Value; + } + + public async Task SaveAsync(Action saveAction = null) + { + if (!SecurityLogOptions.IsEnabled) + { + return; + } + + var securityLogInfo = await CreateAsync(); + saveAction?.Invoke(securityLogInfo); + await SecurityLogStore.SaveAsync(securityLogInfo); + } + + protected virtual Task CreateAsync() + { + return Task.FromResult(new SecurityLogInfo + { + ApplicationName = SecurityLogOptions.ApplicationName + }); + } + } +} diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/ISecurityLogManager.cs b/framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/ISecurityLogManager.cs new file mode 100644 index 0000000000..f368489fb0 --- /dev/null +++ b/framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/ISecurityLogManager.cs @@ -0,0 +1,10 @@ +using System; +using System.Threading.Tasks; + +namespace Volo.Abp.SecurityLog +{ + public interface ISecurityLogManager + { + Task SaveAsync(Action saveAction = null); + } +} diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/ISecurityLogStore.cs b/framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/ISecurityLogStore.cs new file mode 100644 index 0000000000..df2496f307 --- /dev/null +++ b/framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/ISecurityLogStore.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.SecurityLog +{ + public interface ISecurityLogStore + { + Task SaveAsync(SecurityLogInfo securityLogInfo); + } +} diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/SecurityLogInfo.cs b/framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/SecurityLogInfo.cs new file mode 100644 index 0000000000..b187b5bfa2 --- /dev/null +++ b/framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/SecurityLogInfo.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; + +namespace Volo.Abp.SecurityLog +{ + [Serializable] + public class SecurityLogInfo + { + public string ApplicationName { get; set; } + + public string Identity { get; set; } + + public string Action { get; set; } + + public Dictionary ExtraProperties { get; } + + public Guid? UserId { get; set; } + + public string UserName { get; set; } + + public Guid? TenantId { get; set; } + + public string TenantName { get; set; } + + public string ClientId { get; set; } + + public string CorrelationId { get; set; } + + public string ClientIpAddress { get; set; } + + public string BrowserInfo { get; set; } + + public DateTime CreationTime { get; set; } + + public SecurityLogInfo() + { + ExtraProperties = new Dictionary(); + } + + public override string ToString() + { + return $"SECURITY LOG: [{ApplicationName} - {Identity} - {Action}]"; + } + } +} diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/SimpleSecurityLogStore.cs b/framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/SimpleSecurityLogStore.cs new file mode 100644 index 0000000000..fe6496ef40 --- /dev/null +++ b/framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/SimpleSecurityLogStore.cs @@ -0,0 +1,30 @@ +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.SecurityLog +{ + public class SimpleSecurityLogStore : ISecurityLogStore, ITransientDependency + { + public ILogger Logger { get; set; } + protected AbpSecurityLogOptions SecurityLogOptions { get; } + + public SimpleSecurityLogStore(ILogger logger, IOptions securityLogOptions) + { + Logger = logger; + SecurityLogOptions = securityLogOptions.Value; + } + + public Task SaveAsync(SecurityLogInfo securityLogInfo) + { + if (!SecurityLogOptions.IsEnabled) + { + return Task.CompletedTask; + } + + Logger.LogInformation(securityLogInfo.ToString()); + return Task.CompletedTask; + } + } +} diff --git a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Localization/ar.json b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Localization/ar.json new file mode 100644 index 0000000000..7b8ddf6919 --- /dev/null +++ b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Localization/ar.json @@ -0,0 +1,7 @@ +{ + "culture": "ar", + "texts": { + "DisplayName:Abp.Timing.Timezone": "المنطقة زمنية", + "Description:Abp.Timing.Timezone": "المنطقة الزمنية للتطبيق" + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ar.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ar.json index ea35628dc7..ec51f85510 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ar.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ar.json @@ -1,4 +1,4 @@ -{ +{ "culture": "ar", "texts": { "InternalServerErrorMessage": "حدث خطأ داخلي أثناء طلبك!", @@ -47,6 +47,8 @@ "PagerInfoEmpty": "عرض 0 الي 0 من 0 إدخالات", "PagerInfoFiltered": "(تمت تصفيته من _MAX_ مجموع الإدخالات)", "NoDataAvailableInDatatable": "لا توجد بيانات متاحة في الجدول", + "Total": "مجموع", + "Selected": "مختارة", "PagerShowMenuEntries": "عرض _MENU_ إدخالات", "DatatableActionDropdownDefaultText": "أجراءات", "ChangePassword": "تغيير كلمة السر", @@ -61,4 +63,4 @@ "GoBack": "عد", "Search": "بحث" } -} +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWork.cs b/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWork.cs index b42b745fb4..b4158229fe 100644 --- a/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWork.cs +++ b/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWork.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; @@ -316,4 +316,4 @@ namespace Volo.Abp.Uow return $"[UnitOfWork {Id}]"; } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWorkExtensions.cs b/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWorkExtensions.cs index 5c01f54a99..c183a70390 100644 --- a/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWorkExtensions.cs +++ b/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWorkExtensions.cs @@ -1,4 +1,7 @@ -using JetBrains.Annotations; +using System; +using System.Collections.Generic; +using System.Linq; +using JetBrains.Annotations; namespace Volo.Abp.Uow { @@ -10,5 +13,43 @@ namespace Volo.Abp.Uow return unitOfWork.IsReserved && unitOfWork.ReservationName == reservationName; } + + public static void AddItem([NotNull] this IUnitOfWork unitOfWork, string key, TValue value) + where TValue : class + { + Check.NotNull(unitOfWork, nameof(unitOfWork)); + + if (!unitOfWork.Items.ContainsKey(key)) + { + unitOfWork.Items[key] = value; + } + else + { + unitOfWork.Items.Add(key, value); + } + } + + public static TValue GetItemOrDefault([NotNull] this IUnitOfWork unitOfWork, string key) + where TValue : class + { + Check.NotNull(unitOfWork, nameof(unitOfWork)); + + return unitOfWork.Items.FirstOrDefault(x => x.Key == key).As(); + } + + public static TValue GetOrAddItem([NotNull] this IUnitOfWork unitOfWork, string key, Func factory) + where TValue : class + { + Check.NotNull(unitOfWork, nameof(unitOfWork)); + + return unitOfWork.Items.GetOrAdd(key, factory).As(); + } + + public static void RemoveItem([NotNull] this IUnitOfWork unitOfWork, string key) + { + Check.NotNull(unitOfWork, nameof(unitOfWork)); + + unitOfWork.Items.RemoveAll(x => x.Key == key); + } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Validation.Abstractions/Volo/Abp/Validation/AbpValidationException.cs b/framework/src/Volo.Abp.Validation.Abstractions/Volo/Abp/Validation/AbpValidationException.cs index fb3be504c5..5781eaecca 100644 --- a/framework/src/Volo.Abp.Validation.Abstractions/Volo/Abp/Validation/AbpValidationException.cs +++ b/framework/src/Volo.Abp.Validation.Abstractions/Volo/Abp/Validation/AbpValidationException.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Runtime.Serialization; +using System.Text; using Microsoft.Extensions.Logging; using Volo.Abp.Logging; @@ -12,9 +13,9 @@ namespace Volo.Abp.Validation /// This exception type is used to throws validation exceptions. /// [Serializable] - public class AbpValidationException : AbpException, - IHasLogLevel, - IHasValidationErrors, + public class AbpValidationException : AbpException, + IHasLogLevel, + IHasValidationErrors, IExceptionWithSelfLogging { /// @@ -99,7 +100,8 @@ namespace Volo.Abp.Validation return; } - logger.LogWithLevel(LogLevel, "There are " + ValidationErrors.Count + " validation errors:"); + var validationErrors = new StringBuilder(); + validationErrors.AppendLine("There are " + ValidationErrors.Count + " validation errors:"); foreach (var validationResult in ValidationErrors) { var memberNames = ""; @@ -108,8 +110,10 @@ namespace Volo.Abp.Validation memberNames = " (" + string.Join(", ", validationResult.MemberNames) + ")"; } - logger.LogWithLevel(LogLevel, validationResult.ErrorMessage + memberNames); + validationErrors.AppendLine(validationResult.ErrorMessage + memberNames); } + + logger.LogWithLevel(LogLevel, validationErrors.ToString()); } } } diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/ar.json b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/ar.json index 1ef7e01b34..2cdb6999b5 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/ar.json +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/ar.json @@ -1,34 +1,34 @@ { "culture": "ar", "texts": { - "'{0}' and '{1}' do not match.": "'{0}' and '{1}' do not match.", - "The {0} field is not a valid credit card number.": "The {0} field is not a valid credit card number.", - "{0} is not valid.": "{0} is not valid.", - "The {0} field is not a valid e-mail address.": "The {0} field is not a valid e-mail address.", - "The {0} field only accepts files with the following extensions: {1}": "The {0} field only accepts files with the following extensions: {1}", - "The field {0} must be a string or array type with a maximum length of '{1}'.": "The field {0} must be a string or array type with a maximum length of '{1}'.", - "The field {0} must be a string or array type with a minimum length of '{1}'.": "The field {0} must be a string or array type with a minimum length of '{1}'.", - "The {0} field is not a valid phone number.": "The {0} field is not a valid phone number.", - "The field {0} must be between {1} and {2}.": "The field {0} must be between {1} and {2}.", - "The field {0} must match the regular expression '{1}'.": "The field {0} must match the regular expression '{1}'.", - "The {0} field is required.": "The {0} field is required.", - "The field {0} must be a string with a maximum length of {1}.": "The field {0} must be a string with a maximum length of {1}.", - "The field {0} must be a string with a minimum length of {2} and a maximum length of {1}.": "The field {0} must be a string with a minimum length of {2} and a maximum length of {1}.", - "The {0} field is not a valid fully-qualified http, https, or ftp URL.": "The {0} field is not a valid fully-qualified http, https, or ftp URL.", - "The field {0} is invalid.": "The field {0} is invalid.", - "ThisFieldIsNotAValidCreditCardNumber.": "This field is not a valid credit card number.", - "ThisFieldIsNotValid.": "This field is not valid.", - "ThisFieldIsNotAValidEmailAddress.": "This field is not a valid e-mail address.", - "ThisFieldOnlyAcceptsFilesWithTheFollowingExtensions:{0}": "This field only accepts files with the following extensions: {0}", - "ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthoOf{0}": "This field must be a string or array type with a maximum length of '{0}'.", - "ThisFieldMustBeAStringOrArrayTypeWithAMinimumLengthOf{0}": "This field must be a string or array type with a minimum length of '{0}'.", - "ThisFieldIsNotAValidPhoneNumber.": "This field is not a valid phone number.", - "ThisFieldMustBeBetween{0}And{1}": "This field must be between {0} and {1}.", - "ThisFieldMustMatchTheRegularExpression{0}": "This field must match the regular expression '{0}'.", - "ThisFieldIsRequired.": "This field is required.", - "ThisFieldMustBeAStringWithAMaximumLengthOf{0}": "This field must be a string with a maximum length of {0}.", - "ThisFieldMustBeAStringWithAMinimumLengthOf{1}AndAMaximumLengthOf{0}": "This field must be a string with a minimum length of {1} and a maximum length of {0}.", - "ThisFieldIsNotAValidFullyQualifiedHttpHttpsOrFtpUrl": "This field is not a valid fully-qualified http, https, or ftp URL.", - "ThisFieldIsInvalid.": "This field is invalid." + "'{0}' and '{1}' do not match.": "'{0}' و '{1}' غير متطابقان", + "The {0} field is not a valid credit card number.": "الحقل {0} لا يمثل رقم بطاقة ائتمان صالح.", + "{0} is not valid.": "{0} غير صالح.", + "The {0} field is not a valid e-mail address.": "الحقل {0} لا يمثل عنوان بريد إلكتروني صالح.", + "The {0} field only accepts files with the following extensions: {1}": "الحقل {0} يقبل الملفات ذات الامتدادات التالية فقط: {1}", + "The field {0} must be a string or array type with a maximum length of '{1}'.": "يجب أن يكون الحقل {0} عبارة عن سلسلة أو صفيف طوله '{1}' كحد أقصى.", + "The field {0} must be a string or array type with a minimum length of '{1}'.": "يجب أن يكون الحقل {0} عبارة عن سلسلة أو صفيف طوله كحد أدنى '{1}'.", + "The {0} field is not a valid phone number.": "الحقل {0} لا يمثل رقم هاتف صالح.", + "The field {0} must be between {1} and {2}.": "يجب أن يكون الحقل {0} بين {1} و {2}.", + "The field {0} must match the regular expression '{1}'.": "يجب أن يتطابق الحقل {0} مع التعبير النمطي '{1}'.", + "The {0} field is required.": "الحقل {0} إجباري.", + "The field {0} must be a string with a maximum length of {1}.": "يجب أن يكون الحقل {0} سلسلة أحرف طولها كحد أقصى {1}.", + "The field {0} must be a string with a minimum length of {2} and a maximum length of {1}.": "يجب أن يكون الحقل {0} سلسلة أحرف طولها {1} كحد أدنى و {2} كحد أقصى.", + "The {0} field is not a valid fully-qualified http, https, or ftp URL.": "الحقل {0} ليس عنوانا URL صالحًا مؤهلاً بالكامل سواء كان عنوان http أو https أو ftp", + "The field {0} is invalid.": "الحقل {0} غير صالح.", + "ThisFieldIsNotAValidCreditCardNumber.": "هذا الحقل لا يمثل رقم بطاقة ائتمان صالح.", + "ThisFieldIsNotValid.": "هذا الحقل غير صالح.", + "ThisFieldIsNotAValidEmailAddress.": "هذا الحقل لا يمثل عنوان بريد إلكتروني صالح.", + "ThisFieldOnlyAcceptsFilesWithTheFollowingExtensions:{0}": "هذا الحقل يقبل الملفات ذات الامتدادات التالية فقط: {0}", + "ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthOf{0}": "هذا الحقل يجب أن يكون سلسلة أحرف أو صفيف لا يتعدى طوله '{0}'.", + "ThisFieldMustBeAStringOrArrayTypeWithAMinimumLengthOf{0}": " هذا الحقل يجب أن يكون عبارة عن سلسلة أو صفيف طوله كحد أدنى '{0}'.", + "ThisFieldIsNotAValidPhoneNumber.": "هذا الحقل لا يمثل رقم هاتف صالح.", + "ThisFieldMustBeBetween{0}And{1}": "هذا الحقل يجب أن يكون بين {0} و {1}.", + "ThisFieldMustMatchTheRegularExpression{0}": "هذا الحقل يجب أن يتطابق مع التعبير النمطي '{0}'.", + "ThisFieldIsRequired.": "هذا الحقل إجباري.", + "ThisFieldMustBeAStringWithAMaximumLengthOf{0}": "هذا الحقل يجب أن يكون سلسلة أحرف طولها كحد أقصى {0}.", + "ThisFieldMustBeAStringWithAMinimumLengthOf{1}AndAMaximumLengthOf{0}": "هذا الحقل يجب أن يكون سلسلة أحرف طولها {0} كحد أدنى و {1} كحد أقصى.", + "ThisFieldIsNotAValidFullyQualifiedHttpHttpsOrFtpUrl": "هذا الحقل ليس عنوانا URL صالحًا مؤهلاً بالكامل سواء كان عنوان http أو https أو ftp", + "ThisFieldIsInvalid.": "هذا الحقل غير صالح." } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.VirtualFileSystem/Volo.Abp.VirtualFileSystem.csproj b/framework/src/Volo.Abp.VirtualFileSystem/Volo.Abp.VirtualFileSystem.csproj index b4aa55b1c0..09af40a2c7 100644 --- a/framework/src/Volo.Abp.VirtualFileSystem/Volo.Abp.VirtualFileSystem.csproj +++ b/framework/src/Volo.Abp.VirtualFileSystem/Volo.Abp.VirtualFileSystem.csproj @@ -15,9 +15,9 @@ - - - + + + diff --git a/framework/test/SimpleConsoleDemo/SimpleConsoleDemo.csproj b/framework/test/SimpleConsoleDemo/SimpleConsoleDemo.csproj index 49c80c56db..4edbb748b9 100644 --- a/framework/test/SimpleConsoleDemo/SimpleConsoleDemo.csproj +++ b/framework/test/SimpleConsoleDemo/SimpleConsoleDemo.csproj @@ -18,7 +18,7 @@ - + diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/PeopleAppService_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/PeopleAppService_Tests.cs index 96a79d8971..aae7345908 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/PeopleAppService_Tests.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/PeopleAppService_Tests.cs @@ -83,7 +83,6 @@ namespace Volo.Abp.AspNetCore.Mvc (await _personRepository.FindAsync(resultDto.Id)).ShouldNotBeNull(); } - [Fact] public async Task Update_Test() { @@ -170,4 +169,4 @@ namespace Volo.Abp.AspNetCore.Mvc douglas.Phones.Any(p => p.Number == firstPhone.Number).ShouldBeFalse(); } } -} \ No newline at end of file +} diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/FormElements.cshtml b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/FormElements.cshtml index 889e798c8a..6fa9cabb37 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/FormElements.cshtml +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/FormElements.cshtml @@ -108,7 +108,7 @@ public class FormElementsModel : PageModel { public SampleModel MyModel { get; set; } - + public List<SelectListItem> CityList { get; set; } = new List<SelectListItem> { new SelectListItem { Value = "NY", Text = "New York"}, @@ -260,7 +260,7 @@ public class SampleModel { public string SampleInput0 { get; set; } - + public string SampleInput1 { get; set; } public string SampleInput2 { get; set; } @@ -354,7 +354,7 @@ public class FormElementsModel : PageModel { public SampleModel MyModel { get; set; } - + public List"SelectListItem" CityList { get; set; } = new List"SelectListItem" { new SelectListItem { Value = "NY", Text = "New York"}, @@ -458,6 +458,51 @@ <option value="3">Coupe</option> </select> </div> + + + + + + +

    Suppress Label Generation

    + +
    +
    + +
    +
    + + +
    
    + public class FormElementsModel : PageModel
    + {
    +     public SampleModel MyModel { get; set; }
    +
    +     public void OnGet()
    +     {
    +         MyModel = new SampleModel();
    +     }
    +
    +     public class SampleModel
    +     {
    +         [Required]
    +         public string Name { get; set; }
    +     }
    + }
    +
    +
    +
    + +
    
    +<abp-input asp-for="@@Model.MyModel.Name" suppress-label="true"/>
    +                
    +
    + +
    
    +<div class="form-group">
    +    <input type="text" id="MyModel_Name" name="MyModel.Name" value="" class="form-control ">
    +    <span class="text-danger field-validation-valid" data-valmsg-for="MyModel.Name" data-valmsg-replace="true"></span>
    +</div>
     
    diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/package.json b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/package.json index 16b087d9d9..9838b50651 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/package.json +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/package.json @@ -3,7 +3,7 @@ "name": "asp.net", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.shared": "^3.0.4", + "@abp/aspnetcore.mvc.ui.theme.shared": "^3.0.5", "highlight.js": "^9.13.1" }, "devDependencies": {} diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/yarn.lock b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/yarn.lock index 72471a0d99..3399733e18 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/yarn.lock +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/yarn.lock @@ -2,30 +2,30 @@ # yarn lockfile v1 -"@abp/aspnetcore.mvc.ui.theme.shared@^3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-3.0.4.tgz#50db612b6492d36c63dd9042000a9fb00fecec54" - integrity sha512-imd2T5XXkNvGQyXV/A9hA80qSeurKaUcW6HDkte8Y2fs4QfvkjgzWXi7neqF6F+HVbYYPg3rIBWC6QF1OXGn2w== - dependencies: - "@abp/aspnetcore.mvc.ui" "~3.0.4" - "@abp/bootstrap" "~3.0.4" - "@abp/bootstrap-datepicker" "~3.0.4" - "@abp/datatables.net-bs4" "~3.0.4" - "@abp/font-awesome" "~3.0.4" - "@abp/jquery-form" "~3.0.4" - "@abp/jquery-validation-unobtrusive" "~3.0.4" - "@abp/lodash" "~3.0.4" - "@abp/luxon" "~3.0.4" - "@abp/malihu-custom-scrollbar-plugin" "~3.0.4" - "@abp/select2" "~3.0.4" - "@abp/sweetalert" "~3.0.4" - "@abp/timeago" "~3.0.4" - "@abp/toastr" "~3.0.4" - -"@abp/aspnetcore.mvc.ui@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-3.0.4.tgz#4e4344d95a98a60c53037b351cb915d8ff7a7dcb" - integrity sha512-p7KI5CKkpD4EYzG4fCtygrtZhwMu7Z+IZFGbdqXelLbI5ACkbJyrn+N+9trPfS9papymeXY4p1E3v6xOH7rSHQ== +"@abp/aspnetcore.mvc.ui.theme.shared@^3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-3.0.5.tgz#5b370902cf229963f9aa10dd64482d57299dde76" + integrity sha512-iobAH39xrCdnqdaABaiYZg4lqNSOwPkMCLnyc+ytxOMbu/YnNKahXPmRhgd8dR/l+iQY1+6T4SWfLdGswi3gAg== + dependencies: + "@abp/aspnetcore.mvc.ui" "~3.0.5" + "@abp/bootstrap" "~3.0.5" + "@abp/bootstrap-datepicker" "~3.0.5" + "@abp/datatables.net-bs4" "~3.0.5" + "@abp/font-awesome" "~3.0.5" + "@abp/jquery-form" "~3.0.5" + "@abp/jquery-validation-unobtrusive" "~3.0.5" + "@abp/lodash" "~3.0.5" + "@abp/luxon" "~3.0.5" + "@abp/malihu-custom-scrollbar-plugin" "~3.0.5" + "@abp/select2" "~3.0.5" + "@abp/sweetalert" "~3.0.5" + "@abp/timeago" "~3.0.5" + "@abp/toastr" "~3.0.5" + +"@abp/aspnetcore.mvc.ui@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-3.0.5.tgz#a050eaee328c56a3730f5abbf25035adf612d6f0" + integrity sha512-/EOgjXTzR+I1OK9KA7L3yzQ2RGSDW97+n3cQrSdLAUF+tnRvMJS0nytDlSJSRK2hb8/clFMaofZHCC81aUkYUg== dependencies: ansi-colors "^4.1.1" extend-object "^1.0.0" @@ -34,144 +34,144 @@ path "^0.12.7" rimraf "^3.0.2" -"@abp/bootstrap-datepicker@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-3.0.4.tgz#d21963f158a4d348a0acdc46209e67378aae8e55" - integrity sha512-25e9qm39dj9AijIchQIdLlBiBM5/fhASkEuLKNt6M8S/e3qwKLwbRhX90DKMFvOyKdxxcK1gX7tDuGHyG9A8mA== +"@abp/bootstrap-datepicker@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-3.0.5.tgz#3a7fd86dbcadc31c11be5b2b31c8cf8ac3035540" + integrity sha512-vIY1wT4RHoJAPhVK11Q5MS71iZbwsmM/0VTBvvs04mjRSSi6xhdyRYkA+l+P49fnOwhZNaaK19XwQdVVScDuhA== dependencies: bootstrap-datepicker "^1.9.0" -"@abp/bootstrap@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-3.0.4.tgz#3b0e2b74dc174a04ce669de1b140df833eee6031" - integrity sha512-DwxL1fGYOwChPwmMpwUxCKiIdzTfRf/YDkOLUiwI8eRrpABBeuE2sSHKfks6Rqh9Wqo/nlWXvfccgDRqweqhsA== +"@abp/bootstrap@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-3.0.5.tgz#91f2c8fe6a57fc5789ae8fe9c206b2c159e55880" + integrity sha512-LdydeJIbw5k1k7FkWaI1lTectZeTXmEIoccJtj9Qx4N9yGt40wGsjz9SOvUdJ0IKAgntIWNQP8ohWiBPgzT7Xg== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" bootstrap "^4.5.0" bootstrap-v4-rtl "4.4.1-2" -"@abp/core@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/core/-/core-3.0.4.tgz#281940525e280e9e3a1aa4cb4e8f64548ea026a3" - integrity sha512-ZPEH1fa5bf/f1ek9ZLQHx7u78iF6kRZ8Z2NBI36cNL13RyQbx485qJF+KXyqNlJoJAh0CAs0NsAJADU3utcDNA== +"@abp/core@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/core/-/core-3.0.5.tgz#9a7bd990d02edc8128a5be3233b6fbb5669b15b8" + integrity sha512-LdkSkZQfN4hugcGNxBkje0+eQomivwNFbnAkMFQbvCwYLwKB2yvk7GCemEetW1piqvyrUGz4jqhtunjoXps5JA== dependencies: - "@abp/utils" "^3.0.4" + "@abp/utils" "^3.0.5" -"@abp/datatables.net-bs4@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs4/-/datatables.net-bs4-3.0.4.tgz#e488336bd2c199146ea60464e0ba75aeb134957e" - integrity sha512-ID1XWKPxG9pv6mxBkku7JWvgXjTxJ1uShxmYv5kFYVuwOGMSvj8QVZInTHFcMdECopV56yjGPNOIFLeQj4Mw8Q== +"@abp/datatables.net-bs4@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs4/-/datatables.net-bs4-3.0.5.tgz#9b79935970d2d4074994240b5deb01dff5987c0a" + integrity sha512-jIWRBEG/NlhaE43i6Xzr18XJ5aTWA+WWslf7WgNOArG5slGBbH4lLLja4HswLP3NGdSmzIyHJR1p9XQ82nBkbQ== dependencies: - "@abp/datatables.net" "~3.0.4" + "@abp/datatables.net" "~3.0.5" datatables.net-bs4 "^1.10.21" -"@abp/datatables.net@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-3.0.4.tgz#7ef143b83143ad8d769be25296411a812a17b255" - integrity sha512-gdQwRuxO5fQWhG/DiUaf40DQ8dWIVGhOCyt6SZ8LQzq4uSaQqa0qrtTTTW2/aiee4MgJu2vJWYF5YCszIR/HhA== +"@abp/datatables.net@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-3.0.5.tgz#0a0a39e8fa54522bf8a09ce76ff3927207a79491" + integrity sha512-HClQ2tcZuWBAChf9oOJOtMP1zHv8jqloPEcw3iqbhS/tWZBw5VQ4TO+BVfZsqHDXZNnWSmR6/2xiKsK32nliew== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" datatables.net "^1.10.21" -"@abp/font-awesome@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-3.0.4.tgz#98f83d0bf8b5a2a4d988cb07f5cceda2e1bc1a09" - integrity sha512-AKhKCUXx8vdHIgyXUfbSLitfYor5dJbo4qMRRGqIJVSj+cyMkpgYbGjqr24GGijgZxdmOGL+s1b9iCUbIPgLNQ== +"@abp/font-awesome@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-3.0.5.tgz#62d58c34f0abf268e30bdf788dfbd3f45b19b412" + integrity sha512-/+31Vtw1S9vvuNy/rN/Q6awkgUaYNNYXESgfvqI0pqIkvlPgigvS8E68mHqGzsxyRqdZisI6JGd94AF+vJD7sQ== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" "@fortawesome/fontawesome-free" "^5.13.0" -"@abp/jquery-form@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-3.0.4.tgz#022eebbfe58b55e328984c0f119884c9464bc767" - integrity sha512-xDkk4rY0AmXBJE4qztzr2hZz+OVucVNWeagOEgeQHXJSKU/T+0kj0a1RxEHpyI24xBvEXAbe8PbByTbu0QXYew== +"@abp/jquery-form@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-3.0.5.tgz#9d2ea9b2509448ee163a163298a7a09480865f78" + integrity sha512-RKgvQciaMml33NvzA28aYyfNI+WQHRfKLFWvOvqc8qO4C3BvyrnwepuzZcnI/9UsELKLSLoFrfk3rJHhxnTJhg== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" jquery-form "^4.3.0" -"@abp/jquery-validation-unobtrusive@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-3.0.4.tgz#6cc942aadcdde33893e7a45c84aa022e033e4e8b" - integrity sha512-N9sza91HCyPvJo+d0RAfq4skae3TgAYg7KXhlwzRE2hqUQS+EICVARURWyxJW7s7Zu6+gxDTkXhBZRTW56deZA== +"@abp/jquery-validation-unobtrusive@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-3.0.5.tgz#b8912e1c4cfdd4fdff6ad4aa7ee8693b55a3be1f" + integrity sha512-kPifDA+1ErbeQBwn+BzwtWuTHXzBke/EMCmITuj59plM/60gZ1ZfVm8EzzTmKS7iLaVVeOqTKf0vEQTobNYffw== dependencies: - "@abp/jquery-validation" "~3.0.4" + "@abp/jquery-validation" "~3.0.5" jquery-validation-unobtrusive "^3.2.11" -"@abp/jquery-validation@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-3.0.4.tgz#3f96d1f83c75a7fc47e5e5e12c51dbc8da7582b1" - integrity sha512-2ljqas4YKU0PcwB0wQW2qhXejDHjW+N1XCxZDyCZ2wF9UxGlKBR8rFt76TJByXSotKE7n2hJRZaOh9ICZUow2g== +"@abp/jquery-validation@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-3.0.5.tgz#55e36b3c832a35447fac69af312dedf1dee22279" + integrity sha512-fdqxc5CJ6TpUAF8+NEsKCmXvvZiOPR5bzczcfvfovxLOjvHvdEO74XPGjsjlBNTdaKMhmNGZRK4JRLUz7fvTng== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" jquery-validation "^1.19.2" -"@abp/jquery@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-3.0.4.tgz#5f236d95cfae7f65012cd8778fd74490b7542442" - integrity sha512-g8N8Te5vf+8djdksfZoZ3tXaqlBC4Ra39pAWU/XerlLl64u+XmAX1s6yKRfC2kS8lf292GgiFYuNOkb77J6k4A== +"@abp/jquery@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-3.0.5.tgz#2e43ac19a8df6e1e0220d6d4991d33a93d890754" + integrity sha512-2d2l+smWKAVP4/b6GSO+8rmJ7pFXiUewpBDwkHm5qgkK6n6UGqIcK9lIgiS2YQ6rmL6qdtuNhhqtC9GF2TF2kQ== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" jquery "~3.5.1" -"@abp/lodash@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-3.0.4.tgz#ee60bc99f98b59fd9fc5ff4bbcf914a13faf209d" - integrity sha512-jxvv74Sm/fUKDBMylK4Ne/8GKm7GuHAdcLFtkoJQptdO+RjZpKJ86ULT+X5vbKskZaTj2fEmOR6l4hmfnBULTA== +"@abp/lodash@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-3.0.5.tgz#d5d9f14095d76239bc0e6f9804c24d1f92ffcab4" + integrity sha512-+B2vxfPwxoIIb8Ac8eHi6OgAn0qbjmPZuun7/es0/ouYYTbEhvhTAD20X3PGZlHrIYeDyS2ansuXm8gJI+/BzA== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" lodash "^4.17.15" -"@abp/luxon@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-3.0.4.tgz#aa1bf6a93d6254433101e50a205b276759969414" - integrity sha512-gX9dQJt9C6M1wmCSUIBoNr8CAt0jkUZFQ7LKKO/BoADGM/XFvY5vDbhL7zcwbwitgCOzmucoCxs/6rIxSOVlrg== +"@abp/luxon@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-3.0.5.tgz#083042c939ea58bf719e35114a6c8efb07b68688" + integrity sha512-0UVHO2CO89t34O35OA3JzWGWLBpg+e1NQXSfVtK90nmg/QmzGkHVM9m7Su0jjLNBt3N5EA5OPOhriot3bCKItw== dependencies: luxon "^1.24.1" -"@abp/malihu-custom-scrollbar-plugin@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-3.0.4.tgz#c54824b98363f4190cc6a37fcd421c2bfdc1e201" - integrity sha512-6LdSGctOr4ACtdohdSlb19vrFNJ5C0hy1VdbBEafHOBspXckvRVx4uJl2KfHmAC+bMg9apoK/wcHjVNdBHThYQ== +"@abp/malihu-custom-scrollbar-plugin@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-3.0.5.tgz#f908289927a63fd76fb8f60b00a37747d9edbe39" + integrity sha512-3GoXA8VtGyZWKiqaPCipPXYLLM7h2Y5Oecq5c5YlMQyW2akv7uHiQQF2GZW/nVsFu3HBbGTOWpKRRmVk3PEO9Q== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" malihu-custom-scrollbar-plugin "^3.1.5" -"@abp/select2@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-3.0.4.tgz#49db0cfbc9b08f6ae4949b2cb8bdef92d7d308dd" - integrity sha512-zKEgkWBFdGU2KiAMUKhucHqBfzDy2KNSefyqxpOiUd1L3PEKu+9o23HZRWzDupYltop+SO1Jw33CNoVAkKm4+A== +"@abp/select2@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-3.0.5.tgz#79d7754713c64a6549da520dc6867ef3e3dd0350" + integrity sha512-VbeSHdAdDy8exrjplHMMhLVV4oG7Gq9zBb5M52f0sIkA4DkXFhksXHcHNoOCfhaKAXpenGA03Z9GJewZ3MTpMw== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" select2 "^4.0.13" -"@abp/sweetalert@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/sweetalert/-/sweetalert-3.0.4.tgz#f85d11c5aabc6ceb509af1b8fd8d75bc3c74bd27" - integrity sha512-hO0qXUWxeMy/lfy3sb+5HvOmIPst6UvYJJGy3ZQR6DKEdkOktBX9sEDb7m6ij6xAIHqWIx1xLveul0oGteOG0Q== +"@abp/sweetalert@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/sweetalert/-/sweetalert-3.0.5.tgz#e3b92d23cf3633983980b6255bf63c43d422ceab" + integrity sha512-OUt4ANgj14GESfRG7+UqdONS8daOu+8x2OZDWv98hriuX0uCVBjwB2J0jRI/n653C68Mi0yHixGONDv9OLy/lg== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" sweetalert "^2.1.2" -"@abp/timeago@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-3.0.4.tgz#82d90a04f703a7db9c0acedcd2f3cea684e7ae18" - integrity sha512-7SbbqScTKCix25xWy5oLT0slJqUQsW6YgJgI0N41C3AsByRBXZeh5Gcep033gprTPmpgNY8JOWcEcCXySnYvfA== +"@abp/timeago@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-3.0.5.tgz#b191a7c43f3dc77645fe27273f2f6619f7d4eb31" + integrity sha512-QhvAOU4C+Qxh/gFuCSGZtLU4KwVhdv/jtK8ovWyqbGvQMCCIimqa8BFKIz0H5XWIJhHwwWblkpjICWgjWh6uXw== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" timeago "^1.6.7" -"@abp/toastr@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/toastr/-/toastr-3.0.4.tgz#884ab2343ddd59981c291c632e3b0630532f7e9c" - integrity sha512-NG0pVD8bdIXGpjPW9cozCzmyuAdnSLE4vDYLUReP1pZFp9B8SMJqpgPk9eFg0Y06aaHTVMKYBKRoq9xXS4w9Xg== +"@abp/toastr@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/toastr/-/toastr-3.0.5.tgz#fc83b678f5be3a22040d0421ad9db5b2eac9d4fc" + integrity sha512-qgi2DEu6FYpN/DYcOZrmXsAr1HqcIKMhBWeXgjfoYUIl/K5+OuUgbQ69D/jdc0jz8HU9a4Cw7Q8hhe4CkgssEw== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" toastr "^2.1.4" -"@abp/utils@^3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-3.0.4.tgz#38068fa8f735e1889cf2f226128366825948814d" - integrity sha512-TnXRZIZcxv5+EctEPsZEi2cyRXbpl3A679wzQa6BACv+pM45cjGO1JR8P0VswKN4TVL0aQRMv26N6U/uie8sEQ== +"@abp/utils@^3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-3.0.5.tgz#66b3da953500e73ff1f23d1b41168cde8216ce49" + integrity sha512-X9V1xLIEazItaf93nFSA4p94WT6QK3yA4eIyvU7GyyEkkOQdaakNwPbRwy930sd+I+/oXnIqM1vDsJu9QDhgvg== dependencies: just-compare "^1.3.0" diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/package.json b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/package.json index 1cfcdc4382..8f9f972442 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/package.json +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/package.json @@ -3,7 +3,7 @@ "name": "asp.net", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.basic": "^3.0.4" + "@abp/aspnetcore.mvc.ui.theme.basic": "^3.0.5" }, "devDependencies": {} } \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/yarn.lock b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/yarn.lock index 2b6d5dd3e7..b30d4d2f76 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/yarn.lock +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/yarn.lock @@ -2,37 +2,37 @@ # yarn lockfile v1 -"@abp/aspnetcore.mvc.ui.theme.basic@^3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-3.0.4.tgz#c58e3e06ba78279875cf2b5f96d34b83ce09f6df" - integrity sha512-tKKqVRKpwN7P4Mn+he3bcz4Qbt8bkZ++wk6iOujd6HLTkBNa0uKyM4L6+iCn1Hi62S6HeK2Xytx+b+IYeIlsuw== - dependencies: - "@abp/aspnetcore.mvc.ui.theme.shared" "~3.0.4" - -"@abp/aspnetcore.mvc.ui.theme.shared@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-3.0.4.tgz#50db612b6492d36c63dd9042000a9fb00fecec54" - integrity sha512-imd2T5XXkNvGQyXV/A9hA80qSeurKaUcW6HDkte8Y2fs4QfvkjgzWXi7neqF6F+HVbYYPg3rIBWC6QF1OXGn2w== - dependencies: - "@abp/aspnetcore.mvc.ui" "~3.0.4" - "@abp/bootstrap" "~3.0.4" - "@abp/bootstrap-datepicker" "~3.0.4" - "@abp/datatables.net-bs4" "~3.0.4" - "@abp/font-awesome" "~3.0.4" - "@abp/jquery-form" "~3.0.4" - "@abp/jquery-validation-unobtrusive" "~3.0.4" - "@abp/lodash" "~3.0.4" - "@abp/luxon" "~3.0.4" - "@abp/malihu-custom-scrollbar-plugin" "~3.0.4" - "@abp/select2" "~3.0.4" - "@abp/sweetalert" "~3.0.4" - "@abp/timeago" "~3.0.4" - "@abp/toastr" "~3.0.4" - -"@abp/aspnetcore.mvc.ui@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-3.0.4.tgz#4e4344d95a98a60c53037b351cb915d8ff7a7dcb" - integrity sha512-p7KI5CKkpD4EYzG4fCtygrtZhwMu7Z+IZFGbdqXelLbI5ACkbJyrn+N+9trPfS9papymeXY4p1E3v6xOH7rSHQ== +"@abp/aspnetcore.mvc.ui.theme.basic@^3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-3.0.5.tgz#c146f20226522f0305cc0fa31cfcf9d39be78901" + integrity sha512-ptNhD4xV2wSLW7vQ5KUgaRiH5Ov1OscZW2ZEoivnt3kqgtIC3DGw+SiE9vjFCvyY83fjRkIlW2PIfQxSySbteQ== + dependencies: + "@abp/aspnetcore.mvc.ui.theme.shared" "~3.0.5" + +"@abp/aspnetcore.mvc.ui.theme.shared@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-3.0.5.tgz#5b370902cf229963f9aa10dd64482d57299dde76" + integrity sha512-iobAH39xrCdnqdaABaiYZg4lqNSOwPkMCLnyc+ytxOMbu/YnNKahXPmRhgd8dR/l+iQY1+6T4SWfLdGswi3gAg== + dependencies: + "@abp/aspnetcore.mvc.ui" "~3.0.5" + "@abp/bootstrap" "~3.0.5" + "@abp/bootstrap-datepicker" "~3.0.5" + "@abp/datatables.net-bs4" "~3.0.5" + "@abp/font-awesome" "~3.0.5" + "@abp/jquery-form" "~3.0.5" + "@abp/jquery-validation-unobtrusive" "~3.0.5" + "@abp/lodash" "~3.0.5" + "@abp/luxon" "~3.0.5" + "@abp/malihu-custom-scrollbar-plugin" "~3.0.5" + "@abp/select2" "~3.0.5" + "@abp/sweetalert" "~3.0.5" + "@abp/timeago" "~3.0.5" + "@abp/toastr" "~3.0.5" + +"@abp/aspnetcore.mvc.ui@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-3.0.5.tgz#a050eaee328c56a3730f5abbf25035adf612d6f0" + integrity sha512-/EOgjXTzR+I1OK9KA7L3yzQ2RGSDW97+n3cQrSdLAUF+tnRvMJS0nytDlSJSRK2hb8/clFMaofZHCC81aUkYUg== dependencies: ansi-colors "^4.1.1" extend-object "^1.0.0" @@ -41,144 +41,144 @@ path "^0.12.7" rimraf "^3.0.2" -"@abp/bootstrap-datepicker@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-3.0.4.tgz#d21963f158a4d348a0acdc46209e67378aae8e55" - integrity sha512-25e9qm39dj9AijIchQIdLlBiBM5/fhASkEuLKNt6M8S/e3qwKLwbRhX90DKMFvOyKdxxcK1gX7tDuGHyG9A8mA== +"@abp/bootstrap-datepicker@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-3.0.5.tgz#3a7fd86dbcadc31c11be5b2b31c8cf8ac3035540" + integrity sha512-vIY1wT4RHoJAPhVK11Q5MS71iZbwsmM/0VTBvvs04mjRSSi6xhdyRYkA+l+P49fnOwhZNaaK19XwQdVVScDuhA== dependencies: bootstrap-datepicker "^1.9.0" -"@abp/bootstrap@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-3.0.4.tgz#3b0e2b74dc174a04ce669de1b140df833eee6031" - integrity sha512-DwxL1fGYOwChPwmMpwUxCKiIdzTfRf/YDkOLUiwI8eRrpABBeuE2sSHKfks6Rqh9Wqo/nlWXvfccgDRqweqhsA== +"@abp/bootstrap@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-3.0.5.tgz#91f2c8fe6a57fc5789ae8fe9c206b2c159e55880" + integrity sha512-LdydeJIbw5k1k7FkWaI1lTectZeTXmEIoccJtj9Qx4N9yGt40wGsjz9SOvUdJ0IKAgntIWNQP8ohWiBPgzT7Xg== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" bootstrap "^4.5.0" bootstrap-v4-rtl "4.4.1-2" -"@abp/core@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/core/-/core-3.0.4.tgz#281940525e280e9e3a1aa4cb4e8f64548ea026a3" - integrity sha512-ZPEH1fa5bf/f1ek9ZLQHx7u78iF6kRZ8Z2NBI36cNL13RyQbx485qJF+KXyqNlJoJAh0CAs0NsAJADU3utcDNA== +"@abp/core@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/core/-/core-3.0.5.tgz#9a7bd990d02edc8128a5be3233b6fbb5669b15b8" + integrity sha512-LdkSkZQfN4hugcGNxBkje0+eQomivwNFbnAkMFQbvCwYLwKB2yvk7GCemEetW1piqvyrUGz4jqhtunjoXps5JA== dependencies: - "@abp/utils" "^3.0.4" + "@abp/utils" "^3.0.5" -"@abp/datatables.net-bs4@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs4/-/datatables.net-bs4-3.0.4.tgz#e488336bd2c199146ea60464e0ba75aeb134957e" - integrity sha512-ID1XWKPxG9pv6mxBkku7JWvgXjTxJ1uShxmYv5kFYVuwOGMSvj8QVZInTHFcMdECopV56yjGPNOIFLeQj4Mw8Q== +"@abp/datatables.net-bs4@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs4/-/datatables.net-bs4-3.0.5.tgz#9b79935970d2d4074994240b5deb01dff5987c0a" + integrity sha512-jIWRBEG/NlhaE43i6Xzr18XJ5aTWA+WWslf7WgNOArG5slGBbH4lLLja4HswLP3NGdSmzIyHJR1p9XQ82nBkbQ== dependencies: - "@abp/datatables.net" "~3.0.4" + "@abp/datatables.net" "~3.0.5" datatables.net-bs4 "^1.10.21" -"@abp/datatables.net@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-3.0.4.tgz#7ef143b83143ad8d769be25296411a812a17b255" - integrity sha512-gdQwRuxO5fQWhG/DiUaf40DQ8dWIVGhOCyt6SZ8LQzq4uSaQqa0qrtTTTW2/aiee4MgJu2vJWYF5YCszIR/HhA== +"@abp/datatables.net@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-3.0.5.tgz#0a0a39e8fa54522bf8a09ce76ff3927207a79491" + integrity sha512-HClQ2tcZuWBAChf9oOJOtMP1zHv8jqloPEcw3iqbhS/tWZBw5VQ4TO+BVfZsqHDXZNnWSmR6/2xiKsK32nliew== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" datatables.net "^1.10.21" -"@abp/font-awesome@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-3.0.4.tgz#98f83d0bf8b5a2a4d988cb07f5cceda2e1bc1a09" - integrity sha512-AKhKCUXx8vdHIgyXUfbSLitfYor5dJbo4qMRRGqIJVSj+cyMkpgYbGjqr24GGijgZxdmOGL+s1b9iCUbIPgLNQ== +"@abp/font-awesome@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-3.0.5.tgz#62d58c34f0abf268e30bdf788dfbd3f45b19b412" + integrity sha512-/+31Vtw1S9vvuNy/rN/Q6awkgUaYNNYXESgfvqI0pqIkvlPgigvS8E68mHqGzsxyRqdZisI6JGd94AF+vJD7sQ== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" "@fortawesome/fontawesome-free" "^5.13.0" -"@abp/jquery-form@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-3.0.4.tgz#022eebbfe58b55e328984c0f119884c9464bc767" - integrity sha512-xDkk4rY0AmXBJE4qztzr2hZz+OVucVNWeagOEgeQHXJSKU/T+0kj0a1RxEHpyI24xBvEXAbe8PbByTbu0QXYew== +"@abp/jquery-form@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-3.0.5.tgz#9d2ea9b2509448ee163a163298a7a09480865f78" + integrity sha512-RKgvQciaMml33NvzA28aYyfNI+WQHRfKLFWvOvqc8qO4C3BvyrnwepuzZcnI/9UsELKLSLoFrfk3rJHhxnTJhg== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" jquery-form "^4.3.0" -"@abp/jquery-validation-unobtrusive@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-3.0.4.tgz#6cc942aadcdde33893e7a45c84aa022e033e4e8b" - integrity sha512-N9sza91HCyPvJo+d0RAfq4skae3TgAYg7KXhlwzRE2hqUQS+EICVARURWyxJW7s7Zu6+gxDTkXhBZRTW56deZA== +"@abp/jquery-validation-unobtrusive@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-3.0.5.tgz#b8912e1c4cfdd4fdff6ad4aa7ee8693b55a3be1f" + integrity sha512-kPifDA+1ErbeQBwn+BzwtWuTHXzBke/EMCmITuj59plM/60gZ1ZfVm8EzzTmKS7iLaVVeOqTKf0vEQTobNYffw== dependencies: - "@abp/jquery-validation" "~3.0.4" + "@abp/jquery-validation" "~3.0.5" jquery-validation-unobtrusive "^3.2.11" -"@abp/jquery-validation@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-3.0.4.tgz#3f96d1f83c75a7fc47e5e5e12c51dbc8da7582b1" - integrity sha512-2ljqas4YKU0PcwB0wQW2qhXejDHjW+N1XCxZDyCZ2wF9UxGlKBR8rFt76TJByXSotKE7n2hJRZaOh9ICZUow2g== +"@abp/jquery-validation@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-3.0.5.tgz#55e36b3c832a35447fac69af312dedf1dee22279" + integrity sha512-fdqxc5CJ6TpUAF8+NEsKCmXvvZiOPR5bzczcfvfovxLOjvHvdEO74XPGjsjlBNTdaKMhmNGZRK4JRLUz7fvTng== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" jquery-validation "^1.19.2" -"@abp/jquery@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-3.0.4.tgz#5f236d95cfae7f65012cd8778fd74490b7542442" - integrity sha512-g8N8Te5vf+8djdksfZoZ3tXaqlBC4Ra39pAWU/XerlLl64u+XmAX1s6yKRfC2kS8lf292GgiFYuNOkb77J6k4A== +"@abp/jquery@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-3.0.5.tgz#2e43ac19a8df6e1e0220d6d4991d33a93d890754" + integrity sha512-2d2l+smWKAVP4/b6GSO+8rmJ7pFXiUewpBDwkHm5qgkK6n6UGqIcK9lIgiS2YQ6rmL6qdtuNhhqtC9GF2TF2kQ== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" jquery "~3.5.1" -"@abp/lodash@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-3.0.4.tgz#ee60bc99f98b59fd9fc5ff4bbcf914a13faf209d" - integrity sha512-jxvv74Sm/fUKDBMylK4Ne/8GKm7GuHAdcLFtkoJQptdO+RjZpKJ86ULT+X5vbKskZaTj2fEmOR6l4hmfnBULTA== +"@abp/lodash@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-3.0.5.tgz#d5d9f14095d76239bc0e6f9804c24d1f92ffcab4" + integrity sha512-+B2vxfPwxoIIb8Ac8eHi6OgAn0qbjmPZuun7/es0/ouYYTbEhvhTAD20X3PGZlHrIYeDyS2ansuXm8gJI+/BzA== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" lodash "^4.17.15" -"@abp/luxon@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-3.0.4.tgz#aa1bf6a93d6254433101e50a205b276759969414" - integrity sha512-gX9dQJt9C6M1wmCSUIBoNr8CAt0jkUZFQ7LKKO/BoADGM/XFvY5vDbhL7zcwbwitgCOzmucoCxs/6rIxSOVlrg== +"@abp/luxon@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-3.0.5.tgz#083042c939ea58bf719e35114a6c8efb07b68688" + integrity sha512-0UVHO2CO89t34O35OA3JzWGWLBpg+e1NQXSfVtK90nmg/QmzGkHVM9m7Su0jjLNBt3N5EA5OPOhriot3bCKItw== dependencies: luxon "^1.24.1" -"@abp/malihu-custom-scrollbar-plugin@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-3.0.4.tgz#c54824b98363f4190cc6a37fcd421c2bfdc1e201" - integrity sha512-6LdSGctOr4ACtdohdSlb19vrFNJ5C0hy1VdbBEafHOBspXckvRVx4uJl2KfHmAC+bMg9apoK/wcHjVNdBHThYQ== +"@abp/malihu-custom-scrollbar-plugin@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-3.0.5.tgz#f908289927a63fd76fb8f60b00a37747d9edbe39" + integrity sha512-3GoXA8VtGyZWKiqaPCipPXYLLM7h2Y5Oecq5c5YlMQyW2akv7uHiQQF2GZW/nVsFu3HBbGTOWpKRRmVk3PEO9Q== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" malihu-custom-scrollbar-plugin "^3.1.5" -"@abp/select2@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-3.0.4.tgz#49db0cfbc9b08f6ae4949b2cb8bdef92d7d308dd" - integrity sha512-zKEgkWBFdGU2KiAMUKhucHqBfzDy2KNSefyqxpOiUd1L3PEKu+9o23HZRWzDupYltop+SO1Jw33CNoVAkKm4+A== +"@abp/select2@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-3.0.5.tgz#79d7754713c64a6549da520dc6867ef3e3dd0350" + integrity sha512-VbeSHdAdDy8exrjplHMMhLVV4oG7Gq9zBb5M52f0sIkA4DkXFhksXHcHNoOCfhaKAXpenGA03Z9GJewZ3MTpMw== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" select2 "^4.0.13" -"@abp/sweetalert@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/sweetalert/-/sweetalert-3.0.4.tgz#f85d11c5aabc6ceb509af1b8fd8d75bc3c74bd27" - integrity sha512-hO0qXUWxeMy/lfy3sb+5HvOmIPst6UvYJJGy3ZQR6DKEdkOktBX9sEDb7m6ij6xAIHqWIx1xLveul0oGteOG0Q== +"@abp/sweetalert@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/sweetalert/-/sweetalert-3.0.5.tgz#e3b92d23cf3633983980b6255bf63c43d422ceab" + integrity sha512-OUt4ANgj14GESfRG7+UqdONS8daOu+8x2OZDWv98hriuX0uCVBjwB2J0jRI/n653C68Mi0yHixGONDv9OLy/lg== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" sweetalert "^2.1.2" -"@abp/timeago@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-3.0.4.tgz#82d90a04f703a7db9c0acedcd2f3cea684e7ae18" - integrity sha512-7SbbqScTKCix25xWy5oLT0slJqUQsW6YgJgI0N41C3AsByRBXZeh5Gcep033gprTPmpgNY8JOWcEcCXySnYvfA== +"@abp/timeago@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-3.0.5.tgz#b191a7c43f3dc77645fe27273f2f6619f7d4eb31" + integrity sha512-QhvAOU4C+Qxh/gFuCSGZtLU4KwVhdv/jtK8ovWyqbGvQMCCIimqa8BFKIz0H5XWIJhHwwWblkpjICWgjWh6uXw== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" timeago "^1.6.7" -"@abp/toastr@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/toastr/-/toastr-3.0.4.tgz#884ab2343ddd59981c291c632e3b0630532f7e9c" - integrity sha512-NG0pVD8bdIXGpjPW9cozCzmyuAdnSLE4vDYLUReP1pZFp9B8SMJqpgPk9eFg0Y06aaHTVMKYBKRoq9xXS4w9Xg== +"@abp/toastr@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/toastr/-/toastr-3.0.5.tgz#fc83b678f5be3a22040d0421ad9db5b2eac9d4fc" + integrity sha512-qgi2DEu6FYpN/DYcOZrmXsAr1HqcIKMhBWeXgjfoYUIl/K5+OuUgbQ69D/jdc0jz8HU9a4Cw7Q8hhe4CkgssEw== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" toastr "^2.1.4" -"@abp/utils@^3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-3.0.4.tgz#38068fa8f735e1889cf2f226128366825948814d" - integrity sha512-TnXRZIZcxv5+EctEPsZEi2cyRXbpl3A679wzQa6BACv+pM45cjGO1JR8P0VswKN4TVL0aQRMv26N6U/uie8sEQ== +"@abp/utils@^3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-3.0.5.tgz#66b3da953500e73ff1f23d1b41168cde8216ce49" + integrity sha512-X9V1xLIEazItaf93nFSA4p94WT6QK3yA4eIyvU7GyyEkkOQdaakNwPbRwy930sd+I+/oXnIqM1vDsJu9QDhgvg== dependencies: just-compare "^1.3.0" diff --git a/framework/test/Volo.Abp.BlobStoring.Aliyun.Tests/Volo.Abp.BlobStoring.Aliyun.Tests.csproj b/framework/test/Volo.Abp.BlobStoring.Aliyun.Tests/Volo.Abp.BlobStoring.Aliyun.Tests.csproj index 8c5097bd84..7d61dcecae 100644 --- a/framework/test/Volo.Abp.BlobStoring.Aliyun.Tests/Volo.Abp.BlobStoring.Aliyun.Tests.csproj +++ b/framework/test/Volo.Abp.BlobStoring.Aliyun.Tests/Volo.Abp.BlobStoring.Aliyun.Tests.csproj @@ -12,7 +12,7 @@ - + diff --git a/framework/test/Volo.Abp.BlobStoring.Aliyun.Tests/Volo/Abp/BlobStoring/Aliyun/AbpBlobStoringAliyunTestModule.cs b/framework/test/Volo.Abp.BlobStoring.Aliyun.Tests/Volo/Abp/BlobStoring/Aliyun/AbpBlobStoringAliyunTestModule.cs index 1635e3390f..b08737eba5 100644 --- a/framework/test/Volo.Abp.BlobStoring.Aliyun.Tests/Volo/Abp/BlobStoring/Aliyun/AbpBlobStoringAliyunTestModule.cs +++ b/framework/test/Volo.Abp.BlobStoring.Aliyun.Tests/Volo/Abp/BlobStoring/Aliyun/AbpBlobStoringAliyunTestModule.cs @@ -50,14 +50,16 @@ namespace Volo.Abp.BlobStoring.Aliyun aliyun.AccessKeySecret = accessKeySecret; aliyun.Endpoint = endpoint; //STS + aliyun.UseSecurityTokenService = true; aliyun.RegionId = regionId; aliyun.RoleArn = roleArn; aliyun.RoleSessionName = Guid.NewGuid().ToString("N"); - aliyun.DurationSeconds = 0; + aliyun.DurationSeconds = 900; aliyun.Policy = String.Empty; //Other aliyun.CreateContainerIfNotExists = true; aliyun.ContainerName = _randomContainerName; + aliyun.TemporaryCredentialsCacheKey = "297A96094D7048DBB2C28C3FDB20839A"; _configuration = aliyun; }); }); diff --git a/framework/test/Volo.Abp.BlobStoring.Aws.Tests/Volo.Abp.BlobStoring.Aws.Tests.csproj b/framework/test/Volo.Abp.BlobStoring.Aws.Tests/Volo.Abp.BlobStoring.Aws.Tests.csproj new file mode 100644 index 0000000000..2a4910e32c --- /dev/null +++ b/framework/test/Volo.Abp.BlobStoring.Aws.Tests/Volo.Abp.BlobStoring.Aws.Tests.csproj @@ -0,0 +1,19 @@ + + + + + + netcoreapp3.1 + + 9f0d2c00-80c1-435b-bfab-2c39c8249091 + + + + + + + + + + + diff --git a/framework/test/Volo.Abp.BlobStoring.Aws.Tests/Volo/Abp/BlobStoring/Aws/AbpBlobStoringAwsTestBase.cs b/framework/test/Volo.Abp.BlobStoring.Aws.Tests/Volo/Abp/BlobStoring/Aws/AbpBlobStoringAwsTestBase.cs new file mode 100644 index 0000000000..eef12cb0a1 --- /dev/null +++ b/framework/test/Volo.Abp.BlobStoring.Aws.Tests/Volo/Abp/BlobStoring/Aws/AbpBlobStoringAwsTestBase.cs @@ -0,0 +1,20 @@ +using Volo.Abp.Testing; + +namespace Volo.Abp.BlobStoring.Aws +{ + public class AbpBlobStoringAwsTestCommonBase : AbpIntegratedTest + { + protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) + { + options.UseAutofac(); + } + } + + public class AbpBlobStoringAwsTestBase : AbpIntegratedTest + { + protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) + { + options.UseAutofac(); + } + } +} diff --git a/framework/test/Volo.Abp.BlobStoring.Aws.Tests/Volo/Abp/BlobStoring/Aws/AbpBlobStoringAwsTestModule.cs b/framework/test/Volo.Abp.BlobStoring.Aws.Tests/Volo/Abp/BlobStoring/Aws/AbpBlobStoringAwsTestModule.cs new file mode 100644 index 0000000000..d176a864e7 --- /dev/null +++ b/framework/test/Volo.Abp.BlobStoring.Aws.Tests/Volo/Abp/BlobStoring/Aws/AbpBlobStoringAwsTestModule.cs @@ -0,0 +1,92 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Amazon.S3.Model; +using Amazon.S3.Util; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Modularity; +using Volo.Abp.Threading; + +namespace Volo.Abp.BlobStoring.Aws +{ + /// + /// This module will not try to connect to aws. + /// + [DependsOn( + typeof(AbpBlobStoringAwsModule), + typeof(AbpBlobStoringTestModule) + )] + public class AbpBlobStoringAwsTestCommonModule : AbpModule + { + } + + [DependsOn( + typeof(AbpBlobStoringAwsTestCommonModule) + )] + public class AbpBlobStoringAwsTestModule : AbpModule + { + private const string UserSecretsId = "9f0d2c00-80c1-435b-bfab-2c39c8249091"; + + private readonly string _randomContainerName = "abp-aws-test-container-" + Guid.NewGuid().ToString("N"); + + private AwsBlobProviderConfiguration _configuration; + + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.ReplaceConfiguration(ConfigurationHelper.BuildConfiguration(builderAction: builder => + { + builder.AddUserSecrets(UserSecretsId); + })); + + var configuration = context.Services.GetConfiguration(); + var accessKeyId = configuration["Aws:AccessKeyId"]; + var secretAccessKey = configuration["Aws:SecretAccessKey"]; + var region = configuration["Aws:Region"]; + + Configure(options => + { + options.Containers.ConfigureAll((containerName, containerConfiguration) => + { + containerConfiguration.UseAws(aws => + { + aws.AccessKeyId = accessKeyId; + aws.SecretAccessKey = secretAccessKey; + aws.Region = region; + aws.CreateContainerIfNotExists = true; + aws.ContainerName = _randomContainerName; + + _configuration = aws; + }); + }); + }); + } + + public override void OnApplicationShutdown(ApplicationShutdownContext context) + { + AsyncHelper.RunSync(() => DeleteBucketAsync(context)); + } + + private async Task DeleteBucketAsync(ApplicationShutdownContext context) + { + var amazonS3Client = await context.ServiceProvider.GetService() + .GetAmazonS3Client(_configuration); + + if (await AmazonS3Util.DoesS3BucketExistV2Async(amazonS3Client, _randomContainerName)) + { + var blobs = await amazonS3Client.ListObjectsAsync(_randomContainerName); + + if (blobs.S3Objects.Any()) + { + await amazonS3Client.DeleteObjectsAsync(new DeleteObjectsRequest + { + BucketName = _randomContainerName, + Objects = blobs.S3Objects.Select(o => new KeyVersion {Key = o.Key}).ToList() + }); + } + + await amazonS3Client.DeleteBucketAsync(_randomContainerName); + } + } + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.BlobStoring.Aws.Tests/Volo/Abp/BlobStoring/Aws/AwsBlobContainer_Tests.cs b/framework/test/Volo.Abp.BlobStoring.Aws.Tests/Volo/Abp/BlobStoring/Aws/AwsBlobContainer_Tests.cs new file mode 100644 index 0000000000..662949e7e3 --- /dev/null +++ b/framework/test/Volo.Abp.BlobStoring.Aws.Tests/Volo/Abp/BlobStoring/Aws/AwsBlobContainer_Tests.cs @@ -0,0 +1,16 @@ +using Xunit; + +namespace Volo.Abp.BlobStoring.Aws +{ + /* + //Please set the correct connection string in secrets.json and continue the test. + + public class AwsBlobContainer_Tests : BlobContainer_Tests + { + public AwsBlobContainer_Tests() + { + + } + } + */ +} diff --git a/framework/test/Volo.Abp.BlobStoring.Aws.Tests/Volo/Abp/BlobStoring/Aws/AwsBlobNameCalculator_Tests.cs b/framework/test/Volo.Abp.BlobStoring.Aws.Tests/Volo/Abp/BlobStoring/Aws/AwsBlobNameCalculator_Tests.cs new file mode 100644 index 0000000000..e4fbe714f8 --- /dev/null +++ b/framework/test/Volo.Abp.BlobStoring.Aws.Tests/Volo/Abp/BlobStoring/Aws/AwsBlobNameCalculator_Tests.cs @@ -0,0 +1,57 @@ +using System; +using Shouldly; +using Volo.Abp.MultiTenancy; +using Xunit; + +namespace Volo.Abp.BlobStoring.Aws +{ + public class AwsBlobNameCalculatorTests : AbpBlobStoringAwsTestCommonBase + { + private readonly IAwsBlobNameCalculator _calculator; + private readonly ICurrentTenant _currentTenant; + + private const string AwsContainerName = "/"; + private const string AwsSeparator = "/"; + + public AwsBlobNameCalculatorTests() + { + _calculator = GetRequiredService(); + _currentTenant = GetRequiredService(); + } + + [Fact] + public void Default_Settings() + { + _calculator.Calculate( + GetArgs("my-container", "my-blob") + ).ShouldBe($"host{AwsSeparator}my-blob"); + } + + [Fact] + public void Default_Settings_With_TenantId() + { + var tenantId = Guid.NewGuid(); + + using (_currentTenant.Change(tenantId)) + { + _calculator.Calculate( + GetArgs("my-container", "my-blob") + ).ShouldBe($"tenants{AwsSeparator}{tenantId:D}{AwsSeparator}my-blob"); + } + } + + private static BlobProviderArgs GetArgs( + string containerName, + string blobName) + { + return new BlobProviderGetArgs( + containerName, + new BlobContainerConfiguration().UseAws(x => + { + x.ContainerName = containerName; + }), + blobName + ); + } + } +} diff --git a/framework/test/Volo.Abp.BlobStoring.Aws.Tests/Volo/Abp/BlobStoring/Aws/DefaultAwsBlobNamingNormalizerProvider_Tests.cs b/framework/test/Volo.Abp.BlobStoring.Aws.Tests/Volo/Abp/BlobStoring/Aws/DefaultAwsBlobNamingNormalizerProvider_Tests.cs new file mode 100644 index 0000000000..7798c52419 --- /dev/null +++ b/framework/test/Volo.Abp.BlobStoring.Aws.Tests/Volo/Abp/BlobStoring/Aws/DefaultAwsBlobNamingNormalizerProvider_Tests.cs @@ -0,0 +1,57 @@ +using Shouldly; +using Xunit; + +namespace Volo.Abp.BlobStoring.Aws +{ + public class DefaultAwsBlobNamingNormalizerProviderTests : AbpBlobStoringAwsTestCommonBase + { + private readonly IBlobNamingNormalizer _blobNamingNormalizer; + + public DefaultAwsBlobNamingNormalizerProviderTests() + { + _blobNamingNormalizer = GetRequiredService(); + } + + [Fact] + public void NormalizeContainerName_Lowercase() + { + var filename = "ThisIsMyContainerName"; + filename = _blobNamingNormalizer.NormalizeContainerName(filename); + filename.ShouldBe("thisismycontainername"); + } + + [Fact] + public void NormalizeContainerName_Only_Letters_Numbers_Dash() + { + var filename = ",./this-i,./s-my-c,./ont,./ai+*/.=!@#$n^&*er-name.+/"; + filename = _blobNamingNormalizer.NormalizeContainerName(filename); + filename.ShouldBe("this-is-my-container-name"); + } + + [Fact] + public void NormalizeContainerName_Dash() + { + var filename = "-this--is----my-container----name-"; + filename = _blobNamingNormalizer.NormalizeContainerName(filename); + filename.ShouldBe("this-is-my-container-name"); + } + + + [Fact] + public void NormalizeContainerName_Min_Length() + { + var filename = "a"; + filename = _blobNamingNormalizer.NormalizeContainerName(filename); + filename.Length.ShouldBeGreaterThanOrEqualTo(3); + } + + + [Fact] + public void NormalizeContainerName_Max_Length() + { + var filename = "abpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabp"; + filename = _blobNamingNormalizer.NormalizeContainerName(filename); + filename.Length.ShouldBeLessThanOrEqualTo(63); + } + } +} diff --git a/framework/test/Volo.Abp.BlobStoring.Tests/Volo/Abp/BlobStoring/BlobContainer_Tests.cs b/framework/test/Volo.Abp.BlobStoring.Tests/Volo/Abp/BlobStoring/BlobContainer_Tests.cs index 091c1b7c95..a965f2852b 100644 --- a/framework/test/Volo.Abp.BlobStoring.Tests/Volo/Abp/BlobStoring/BlobContainer_Tests.cs +++ b/framework/test/Volo.Abp.BlobStoring.Tests/Volo/Abp/BlobStoring/BlobContainer_Tests.cs @@ -3,7 +3,9 @@ using System.Linq; using System.Threading.Tasks; using Shouldly; using Volo.Abp.BlobStoring.TestObjects; +using Volo.Abp.Clients; using Volo.Abp.Modularity; +using Volo.Abp.MultiTenancy; using Volo.Abp.Testing; using Xunit; @@ -14,11 +16,14 @@ namespace Volo.Abp.BlobStoring { protected IBlobContainer Container { get; } + protected ICurrentTenant CurrentTenant { get; } + protected BlobContainer_Tests() { Container = GetRequiredService>(); + CurrentTenant = GetRequiredService(); } - + protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) { options.UseAutofac(); @@ -37,35 +42,68 @@ namespace Volo.Abp.BlobStoring result.SequenceEqual(testContent).ShouldBeTrue(); } + [Fact] + public async Task Should_Save_And_Get_Blobs_In_Different_Tenant() + { + var blobName = "test-blob-1"; + var testContent = "test content".GetBytes(); + + using (CurrentTenant.Change(Guid.NewGuid())) + { + await Container.SaveAsync(blobName, testContent); + (await Container.GetAllBytesAsync(blobName)).SequenceEqual(testContent).ShouldBeTrue(); + } + + using (CurrentTenant.Change(Guid.NewGuid())) + { + await Container.SaveAsync(blobName, testContent); + (await Container.GetAllBytesAsync(blobName)).SequenceEqual(testContent).ShouldBeTrue(); + + using (CurrentTenant.Change(null)) + { + // Could not found the requested BLOB... + await Assert.ThrowsAsync(async () => + await Container.GetAllBytesAsync(blobName) + ); + } + } + + using (CurrentTenant.Change(null)) + { + await Container.SaveAsync(blobName, testContent); + (await Container.GetAllBytesAsync(blobName)).SequenceEqual(testContent).ShouldBeTrue(); + } + } + [Fact] public async Task Should_Overwrite_Pre_Saved_Blob_If_Requested() { var blobName = "test-blob-1"; - + var testContent = "test content".GetBytes(); await Container.SaveAsync(blobName, testContent); - + var testContentOverwritten = "test content overwritten".GetBytes(); await Container.SaveAsync(blobName, testContentOverwritten, true); - + var result = await Container.GetAllBytesAsync(blobName); result.SequenceEqual(testContentOverwritten).ShouldBeTrue(); } - + [Fact] public async Task Should_Not_Allow_To_Overwrite_Pre_Saved_Blob_By_Default() { var blobName = "test-blob-1"; - + var testContent = "test content".GetBytes(); await Container.SaveAsync(blobName, testContent); - + var testContentOverwritten = "test content overwritten".GetBytes(); await Assert.ThrowsAsync(() => Container.SaveAsync(blobName, testContentOverwritten) ); } - + [Theory] [InlineData("test-blob-1")] [InlineData("test-blob-1.txt")] @@ -91,7 +129,7 @@ namespace Volo.Abp.BlobStoring await Container.DeleteAsync(blobName); (await Container.ExistsAsync(blobName)).ShouldBeFalse(); } - + [Theory] [InlineData("test-blob-1")] [InlineData("test-blob-1.txt")] @@ -102,4 +140,4 @@ namespace Volo.Abp.BlobStoring (await Container.ExistsAsync(blobName)).ShouldBeFalse(); } } -} \ No newline at end of file +} diff --git a/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/AbpCachingTestModule.cs b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/AbpCachingTestModule.cs index be003431f2..9da6725fa5 100644 --- a/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/AbpCachingTestModule.cs +++ b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/AbpCachingTestModule.cs @@ -1,5 +1,7 @@ using Microsoft.Extensions.Caching.Distributed; using System; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using Volo.Abp.Modularity; namespace Volo.Abp.Caching @@ -26,6 +28,8 @@ namespace Volo.Abp.Caching option.GlobalCacheEntryOptions.SetSlidingExpiration(TimeSpan.FromMinutes(20)); }); + + context.Services.Replace(ServiceDescriptor.Singleton()); } } -} \ No newline at end of file +} diff --git a/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_Tests.cs b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_Tests.cs index 533362c851..32876c08fe 100644 --- a/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_Tests.cs +++ b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_Tests.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Shouldly; using Volo.Abp.Testing; +using Volo.Abp.Uow; using Xunit; namespace Volo.Abp.Caching @@ -30,7 +32,7 @@ namespace Volo.Abp.Caching cacheItem.ShouldNotBeNull(); cacheItem.Name.ShouldBe(personName); - //Remove + //Remove await personCache.RemoveAsync(cacheKey); //Get (not exists since removed) @@ -111,7 +113,7 @@ namespace Volo.Abp.Caching cacheItem1.ShouldNotBeNull(); cacheItem1.Name.ShouldBe(personName); - //Remove + //Remove await personCache.RemoveAsync(cacheKey); @@ -145,7 +147,7 @@ namespace Volo.Abp.Caching cacheItem.ShouldNotBeNull(); cacheItem.Name.ShouldBe(personName); - //Remove + //Remove await personCache.RemoveAsync(cacheKey); //Get (not exists since removed) @@ -180,10 +182,10 @@ namespace Volo.Abp.Caching factoryExecuted = false; cacheItem = await personCache.GetOrAddAsync(cacheKey, - async () => + () => { factoryExecuted = true; - return new PersonCacheItem(personName); + return Task.FromResult(new PersonCacheItem(personName)); }); factoryExecuted.ShouldBeFalse(); @@ -227,7 +229,7 @@ namespace Volo.Abp.Caching cacheItem1.ShouldNotBeNull(); cacheItem1.Name.ShouldBe(personName); - //Remove + //Remove await personCache.RemoveAsync(cacheKey); @@ -261,7 +263,7 @@ namespace Volo.Abp.Caching cacheItem.ShouldNotBeNull(); cacheItem.Name.ShouldBe(personName); - //Remove + //Remove await personCache.RemoveAsync(cacheKey); //Get (not exists since removed) @@ -299,7 +301,7 @@ namespace Volo.Abp.Caching cacheItem2.ShouldNotBeNull(); cacheItem2.Name.ShouldBe(personName); - //Remove + //Remove await personCache.RemoveAsync(cache1Key); await personCache.RemoveAsync(cache2Key); @@ -310,6 +312,377 @@ namespace Volo.Abp.Caching cacheItem2.ShouldBeNull(); } + [Fact] + public async Task Cache_Should_Only_Available_In_Uow_For_GetAsync() + { + const string key = "testkey"; + + using (var uow = GetRequiredService().Begin()) + { + var personCache = GetRequiredService>(); + + var cacheValue = await personCache.GetAsync(key, considerUow: true); + cacheValue.ShouldBeNull(); + + await personCache.SetAsync(key, new PersonCacheItem("john"), considerUow: true); + + cacheValue = await personCache.GetAsync(key, considerUow: true); + cacheValue.ShouldNotBeNull(); + cacheValue.Name.ShouldBe("john"); + + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + + uow.OnCompleted(async () => + { + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldNotBeNull(); + cacheValue.Name.ShouldBe("john"); + }); + + await uow.CompleteAsync(); + } + } + + [Fact] + public async Task Cache_Should_Rollback_With_Uow_For_GetAsync() + { + const string key = "testkey"; + var personCache = GetRequiredService>(); + + var cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + + using (var uow = GetRequiredService().Begin()) + { + cacheValue = await personCache.GetAsync(key, considerUow: true); + cacheValue.ShouldBeNull(); + + await personCache.SetAsync(key, new PersonCacheItem("john"), considerUow: true); + + cacheValue = await personCache.GetAsync(key, considerUow: true); + cacheValue.ShouldNotBeNull(); + cacheValue.Name.ShouldBe("john"); + + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + } + + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + } + + [Fact] + public async Task Cache_Should_Only_Available_In_Uow_For_GetOrAddAsync() + { + const string key = "testkey"; + + using (var uow = GetRequiredService().Begin()) + { + var personCache = GetRequiredService>(); + + var cacheValue = await personCache.GetOrAddAsync(key, () => Task.FromResult(new PersonCacheItem("john")), considerUow: true); + cacheValue.ShouldNotBeNull(); + cacheValue.Name.ShouldBe("john"); + + cacheValue = await personCache.GetAsync(key, considerUow: true); + cacheValue.ShouldNotBeNull(); + cacheValue.Name.ShouldBe("john"); + + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + + uow.OnCompleted(async () => + { + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldNotBeNull(); + cacheValue.Name.ShouldBe("john"); + }); + + await uow.CompleteAsync(); + } + } + + [Fact] + public async Task Cache_Should_Rollback_With_Uow_For_GetOrAddAsync() + { + const string key = "testkey"; + var personCache = GetRequiredService>(); + + var cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + + using (var uow = GetRequiredService().Begin()) + { + cacheValue = await personCache.GetOrAddAsync(key, () => Task.FromResult(new PersonCacheItem("john")), considerUow: true); + cacheValue.ShouldNotBeNull(); + cacheValue.Name.ShouldBe("john"); + + cacheValue = await personCache.GetAsync(key, considerUow: true); + cacheValue.ShouldNotBeNull(); + cacheValue.Name.ShouldBe("john"); + + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + } + + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + } + + [Fact] + public async Task Cache_Should_Only_Available_In_Uow_For_SetAsync() + { + const string key = "testkey"; + + using (var uow = GetRequiredService().Begin()) + { + var personCache = GetRequiredService>(); + + await personCache.SetAsync(key, new PersonCacheItem("john"), considerUow: true); + + var cacheValue = await personCache.GetAsync(key, considerUow: true); + cacheValue.ShouldNotBeNull(); + cacheValue.Name.ShouldBe("john"); + + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + + uow.OnCompleted(async () => + { + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldNotBeNull(); + cacheValue.Name.ShouldBe("john"); + }); + + await uow.CompleteAsync(); + } + } + + [Fact] + public async Task Cache_Should_Rollback_With_Uow_For_SetAsync() + { + const string key = "testkey"; + var personCache = GetRequiredService>(); + + var cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + + using (var uow = GetRequiredService().Begin()) + { + await personCache.SetAsync(key, new PersonCacheItem("john"), considerUow: true); + + cacheValue = await personCache.GetAsync(key, considerUow: true); + cacheValue.ShouldNotBeNull(); + cacheValue.Name.ShouldBe("john"); + + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + } + + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + } + + [Fact] + public async Task Cache_Should_Only_Available_In_Uow_For_RemoveAsync() + { + const string key = "testkey"; + + using (var uow = GetRequiredService().Begin()) + { + var personCache = GetRequiredService>(); + + await personCache.SetAsync(key, new PersonCacheItem("john"), considerUow: true); + + var cacheValue = await personCache.GetAsync(key, considerUow: true); + cacheValue.ShouldNotBeNull(); + cacheValue.Name.ShouldBe("john"); + + await personCache.RemoveAsync(key, considerUow: true); + + cacheValue = await personCache.GetAsync(key, considerUow: true); + cacheValue.ShouldBeNull(); + + uow.OnCompleted(async () => + { + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + }); + + await uow.CompleteAsync(); + } + } + + [Fact] + public async Task Cache_Should_Rollback_With_Uow_For_RemoveAsync() + { + const string key = "testkey"; + var personCache = GetRequiredService>(); + + var cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + + using (var uow = GetRequiredService().Begin()) + { + await personCache.SetAsync(key, new PersonCacheItem("john"), considerUow: true); + + cacheValue = await personCache.GetAsync(key, considerUow: true); + cacheValue.ShouldNotBeNull(); + cacheValue.Name.ShouldBe("john"); + + await personCache.RemoveAsync(key, considerUow: true); + + cacheValue = await personCache.GetAsync(key, considerUow: true); + cacheValue.ShouldBeNull(); + } + + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + } + + [Fact] + public async Task Cache_Should_Only_Available_In_Uow_For_GetManyAsync() + { + var testkey = "testkey"; + var testkey2 = "testkey2"; + var testKeys = new[] {testkey, testkey2}; + + using (var uow = GetRequiredService().Begin()) + { + var personCache = GetRequiredService>(); + + var cacheValue = await personCache.GetManyAsync(testKeys, considerUow: true); + cacheValue.Where(x => x.Value != null).ShouldBeEmpty(); + + await personCache.SetManyAsync(new List> + { + new KeyValuePair(testkey, new PersonCacheItem("john")), + new KeyValuePair(testkey2, new PersonCacheItem("jack")) + }, considerUow: true); + + cacheValue = await personCache.GetManyAsync(testKeys, considerUow: true); + cacheValue.Where(x => x.Value != null).ShouldNotBeEmpty(); + cacheValue.ShouldContain(x => x.Value.Name == "john" || x.Value.Name == "jack"); + + cacheValue = await personCache.GetManyAsync(testKeys, considerUow: false); + cacheValue.Where(x => x.Value != null).ShouldBeEmpty(); + + uow.OnCompleted(async () => + { + cacheValue = await personCache.GetManyAsync(testKeys, considerUow: false); + cacheValue.ShouldNotBeEmpty(); + cacheValue.ShouldContain(x => x.Value.Name == "john" || x.Value.Name == "jack"); + }); + + await uow.CompleteAsync(); + } + } + + [Fact] + public async Task Cache_Should_Rollback_With_Uow_For_GetManyAsync() + { + var testkey = "testkey"; + var testkey2 = "testkey2"; + var testKeys = new[] {testkey, testkey2}; + + var personCache = GetRequiredService>(); + + var cacheValue = await personCache.GetManyAsync(testKeys, considerUow: false); + cacheValue.Where(x => x.Value != null).ShouldBeEmpty(); + + using (var uow = GetRequiredService().Begin()) + { + cacheValue = await personCache.GetManyAsync(testKeys, considerUow: true); + cacheValue.Where(x => x.Value != null).ShouldBeEmpty(); + + await personCache.SetManyAsync(new List> + { + new KeyValuePair(testkey, new PersonCacheItem("john")), + new KeyValuePair(testkey2, new PersonCacheItem("jack")) + }, considerUow: true); + + cacheValue = await personCache.GetManyAsync(testKeys, considerUow: true); + cacheValue.Where(x => x.Value != null).ShouldNotBeEmpty(); + cacheValue.ShouldContain(x => x.Value.Name == "john" || x.Value.Name == "jack"); + + cacheValue = await personCache.GetManyAsync(testKeys, considerUow: false); + cacheValue.Where(x => x.Value != null).ShouldBeEmpty(); + } + + cacheValue = await personCache.GetManyAsync(testKeys, considerUow: false); + cacheValue.Where(x => x.Value != null).ShouldBeEmpty(); + } + + + [Fact] + public async Task Cache_Should_Only_Available_In_Uow_For_SetManyAsync() + { + var testkey = "testkey"; + var testkey2 = "testkey2"; + var testKeys = new[] {testkey, testkey2}; + + using (var uow = GetRequiredService().Begin()) + { + var personCache = GetRequiredService>(); + + await personCache.SetManyAsync(new List> + { + new KeyValuePair(testkey, new PersonCacheItem("john")), + new KeyValuePair(testkey, new PersonCacheItem("jack")) + }, considerUow: true); + + var cacheValue = await personCache.GetManyAsync(testKeys, considerUow: true); + cacheValue.Where(x => x.Value != null).ShouldNotBeEmpty(); + cacheValue.ShouldContain(x => x.Value.Name == "john" || x.Value.Name == "jack"); + + cacheValue = await personCache.GetManyAsync(testKeys, considerUow: false); + cacheValue.Where(x => x.Value != null).ShouldBeEmpty(); + + uow.OnCompleted(async () => + { + var cacheValue = await personCache.GetManyAsync(testKeys, considerUow: false); + cacheValue.Where(x => x.Value != null).ShouldNotBeEmpty(); + cacheValue.ShouldContain(x => x.Value.Name == "john" || x.Value.Name == "jack"); + }); + + await uow.CompleteAsync(); + } + } + + [Fact] + public async Task Cache_Should_Rollback_With_Uow_For_SetManyAsync() + { + var testkey = "testkey"; + var testkey2 = "testkey2"; + var testKeys = new[] {testkey, testkey2}; + + var personCache = GetRequiredService>(); + + var cacheValue = await personCache.GetManyAsync(testKeys, considerUow: false); + cacheValue.Where(x => x.Value != null).ShouldBeEmpty(); + + using (var uow = GetRequiredService().Begin()) + { + await personCache.SetManyAsync(new List> + { + new KeyValuePair(testkey, new PersonCacheItem("john")), + new KeyValuePair(testkey, new PersonCacheItem("jack")) + }, considerUow: true); + + cacheValue = await personCache.GetManyAsync(testKeys, considerUow: true); + cacheValue.Where(x => x.Value != null).ShouldNotBeEmpty(); + cacheValue.ShouldContain(x => x.Value.Name == "john" || x.Value.Name == "jack"); + + cacheValue = await personCache.GetManyAsync(testKeys, considerUow: false); + cacheValue.Where(x => x.Value != null).ShouldBeEmpty(); + } + + cacheValue = await personCache.GetManyAsync(testKeys, considerUow: false); + cacheValue.Where(x => x.Value != null).ShouldBeEmpty(); + } + + [Fact] public async Task Should_Set_And_Get_Multiple_Items_Async() { @@ -317,7 +690,7 @@ namespace Volo.Abp.Caching await personCache.SetManyAsync(new[] { - new KeyValuePair("john", new PersonCacheItem("John Nash")), + new KeyValuePair("john", new PersonCacheItem("John Nash")), new KeyValuePair("thomas", new PersonCacheItem("Thomas Moore")) }); @@ -327,7 +700,7 @@ namespace Volo.Abp.Caching "thomas", "baris" //doesn't exist }); - + cacheItems.Length.ShouldBe(3); cacheItems[0].Key.ShouldBe("john"); cacheItems[0].Value.Name.ShouldBe("John Nash"); @@ -335,9 +708,9 @@ namespace Volo.Abp.Caching cacheItems[1].Value.Name.ShouldBe("Thomas Moore"); cacheItems[2].Key.ShouldBe("baris"); cacheItems[2].Value.ShouldBeNull(); - + (await personCache.GetAsync("john")).Name.ShouldBe("John Nash"); (await personCache.GetAsync("baris")).ShouldBeNull(); } } -} \ No newline at end of file +} diff --git a/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/TestMemoryDistributedCache.cs b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/TestMemoryDistributedCache.cs new file mode 100644 index 0000000000..69589d97cd --- /dev/null +++ b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/TestMemoryDistributedCache.cs @@ -0,0 +1,61 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Caching +{ + [DisableConventionalRegistration] + public class TestMemoryDistributedCache : MemoryDistributedCache, ICacheSupportsMultipleItems + { + public TestMemoryDistributedCache(IOptions optionsAccessor) + : base(optionsAccessor) + { + } + + public TestMemoryDistributedCache(IOptions optionsAccessor, ILoggerFactory loggerFactory) + : base(optionsAccessor, loggerFactory) + { + } + + public byte[][] GetMany(IEnumerable keys) + { + var values = new List(); + foreach (var key in keys) + { + values.Add(Get(key)); + } + return values.ToArray(); + } + + public async Task GetManyAsync(IEnumerable keys, CancellationToken token = default) + { + var values = new List(); + foreach (var key in keys) + { + values.Add(await GetAsync(key, token)); + } + return values.ToArray(); + } + + public void SetMany(IEnumerable> items, DistributedCacheEntryOptions options) + { + foreach (var item in items) + { + Set(item.Key, item.Value, options); + } + } + + public async Task SetManyAsync(IEnumerable> items, DistributedCacheEntryOptions options, CancellationToken token = default) + { + foreach (var item in items) + { + await SetAsync(item.Key, item.Value, options, token); + } + } + } +} diff --git a/framework/test/Volo.Abp.Core.Tests/System/Collections/Generic/AbpListExtensions_Tests.cs b/framework/test/Volo.Abp.Core.Tests/System/Collections/Generic/AbpListExtensions_Tests.cs index 9c8926f38a..618b8769dc 100644 --- a/framework/test/Volo.Abp.Core.Tests/System/Collections/Generic/AbpListExtensions_Tests.cs +++ b/framework/test/Volo.Abp.Core.Tests/System/Collections/Generic/AbpListExtensions_Tests.cs @@ -7,6 +7,20 @@ namespace System.Collections.Generic { public class AbpListExtensions_Tests { + [Fact] + public void InsertRange() + { + var list = Enumerable.Range(1, 3).ToList(); + list.InsertRange(1, new[] {7, 8, 9}); + + list[0].ShouldBe(1); + list[1].ShouldBe(7); + list[2].ShouldBe(8); + list[3].ShouldBe(9); + list[4].ShouldBe(2); + list[5].ShouldBe(3); + } + [Fact] public void InsertAfter() { @@ -191,7 +205,7 @@ namespace System.Collections.Generic { var list = RandomHelper .GenerateRandomizedList(new char[] {'A', 'B', 'C', 'D', 'E', 'F', 'G'}); - + list = list.SortByDependencies(c => dependencies[c]); foreach (var dependency in dependencies) @@ -204,4 +218,4 @@ namespace System.Collections.Generic } } } -} \ No newline at end of file +} diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Domain/ExtraProperties_Tests.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Domain/ExtraProperties_Tests.cs index 557116d158..f8edd1f5f1 100644 --- a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Domain/ExtraProperties_Tests.cs +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Domain/ExtraProperties_Tests.cs @@ -1,6 +1,10 @@ -using System.Threading.Tasks; +using System; +using System.Linq; +using System.Threading.Tasks; using Shouldly; using Volo.Abp.Data; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.TestApp.Domain; using Volo.Abp.TestApp.Testing; using Xunit; @@ -22,11 +26,30 @@ namespace Volo.Abp.EntityFrameworkCore.Domain var london = await CityRepository.FindByNameAsync("London"); london.GetProperty("PhoneCode").ShouldBe("42"); - london.ExtraProperties["PhoneCode"] = "53"; + london.ExtraProperties["PhoneCode"] = 123456; + london.ExtraProperties["Rank"] = "88"; + london.ExtraProperties["ZipCode"] = null; await CityRepository.UpdateAsync(london); var london2 = await CityRepository.FindByNameAsync("London"); - london2.GetProperty("PhoneCode").ShouldBe("53"); + london2.GetProperty("PhoneCode").ShouldBe("123456"); + london2.GetProperty("Rank").ShouldBe(88); + london2.GetProperty("ZipCode").ShouldBe(null); + } + + + [Fact] + public async Task An_Extra_Property_Configured_As_Extension2() + { + await WithUnitOfWorkAsync(() => + { + var entityEntry = CityRepository.GetDbContext().Attach(new City(Guid.NewGuid(), "NewYork")); + var indexes = entityEntry.Metadata.GetIndexes().ToList(); + indexes.ShouldNotBeEmpty(); + indexes.ShouldContain(x => x.IsUnique); + return Task.CompletedTask; + }); + } } } diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Domain/TestEntityExtensionConfigurator.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Domain/TestEntityExtensionConfigurator.cs index 3d415d79e6..dd723ffb55 100644 --- a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Domain/TestEntityExtensionConfigurator.cs +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Domain/TestEntityExtensionConfigurator.cs @@ -15,7 +15,15 @@ namespace Volo.Abp.EntityFrameworkCore.Domain ObjectExtensionManager.Instance .MapEfCoreProperty( "PhoneCode", - p => p.HasMaxLength(8) + (e, p) => + { + e.HasIndex(p.Metadata.Name).IsUnique(); + p.HasMaxLength(8); + } + ).MapEfCoreProperty( + "ZipCode" + ).MapEfCoreProperty( + "Rank" ); }); } diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Repositories/Repository_Basic_Tests.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Repositories/Repository_Basic_Tests.cs index bfa6b8532d..3d5d7b656b 100644 --- a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Repositories/Repository_Basic_Tests.cs +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Repositories/Repository_Basic_Tests.cs @@ -1,9 +1,31 @@ -using Volo.Abp.TestApp.Testing; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Shouldly; +using Volo.Abp.TestApp.Testing; +using Xunit; namespace Volo.Abp.EntityFrameworkCore.Repositories { public class Repository_Basic_Tests : Repository_Basic_Tests { + [Fact] + public async Task EFCore_QueryableExtension_ToListAsync() + { + await WithUnitOfWorkAsync(async () => + { + var persons = await PersonRepository.ToListAsync(); + persons.Count.ShouldBeGreaterThan(0); + }); + } + [Fact] + public async Task EFCore_QueryableExtension_CountAsync() + { + await WithUnitOfWorkAsync(async () => + { + var count = await PersonRepository.CountAsync(); + count.ShouldBeGreaterThan(0); + }); + } } } diff --git a/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Distributed/LocalDistributedEventBus_Test.cs b/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Distributed/LocalDistributedEventBus_Test.cs index 47649ad3c3..d3371f383a 100644 --- a/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Distributed/LocalDistributedEventBus_Test.cs +++ b/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Distributed/LocalDistributedEventBus_Test.cs @@ -1,4 +1,7 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; +using Volo.Abp.Domain.Entities.Events.Distributed; +using Volo.Abp.MultiTenancy; using Xunit; namespace Volo.Abp.EventBus.Distributed @@ -17,5 +20,29 @@ namespace Volo.Abp.EventBus.Distributed Assert.Equal(3, MySimpleDistributedTransientEventHandler.HandleCount); Assert.Equal(3, MySimpleDistributedTransientEventHandler.DisposeCount); } + + [Fact] + public async Task Should_Change_TenantId_If_EventData_Is_MultiTenant() + { + var tenantId = Guid.NewGuid(); + + DistributedEventBus.Subscribe(GetRequiredService()); + + await DistributedEventBus.PublishAsync(new MySimpleEventData(3, tenantId)); + + Assert.Equal(tenantId, MySimpleDistributedSingleInstanceEventHandler.TenantId); + } + + [Fact] + public async Task Should_Change_TenantId_If_Generic_EventData_Is_MultiTenant() + { + var tenantId = Guid.NewGuid(); + + DistributedEventBus.Subscribe>(GetRequiredService()); + + await DistributedEventBus.PublishAsync(new MySimpleEventData(3, tenantId)); + + Assert.Equal(tenantId, MySimpleDistributedSingleInstanceEventHandler.TenantId); + } } } diff --git a/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Distributed/MySimpleDistributedSingleInstanceEventHandler.cs b/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Distributed/MySimpleDistributedSingleInstanceEventHandler.cs new file mode 100644 index 0000000000..53fe8e347b --- /dev/null +++ b/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Distributed/MySimpleDistributedSingleInstanceEventHandler.cs @@ -0,0 +1,32 @@ +using System; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Entities.Events.Distributed; +using Volo.Abp.MultiTenancy; + +namespace Volo.Abp.EventBus.Distributed +{ + public class MySimpleDistributedSingleInstanceEventHandler : IDistributedEventHandler, IDistributedEventHandler>, ITransientDependency + { + private readonly ICurrentTenant _currentTenant; + + public MySimpleDistributedSingleInstanceEventHandler(ICurrentTenant currentTenant) + { + _currentTenant = currentTenant; + } + + public static Guid? TenantId { get; set; } + + public Task HandleEventAsync(MySimpleEventData eventData) + { + TenantId = _currentTenant.Id; + return Task.CompletedTask; + } + + public Task HandleEventAsync(EntityCreatedEto eventData) + { + TenantId = _currentTenant.Id; + return Task.CompletedTask; + } + } +} diff --git a/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Local/EventBus_MultiTenancy_Test.cs b/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Local/EventBus_MultiTenancy_Test.cs new file mode 100644 index 0000000000..62dcb33b42 --- /dev/null +++ b/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Local/EventBus_MultiTenancy_Test.cs @@ -0,0 +1,64 @@ +using System; +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.Domain.Entities; +using Volo.Abp.Domain.Entities.Events; +using Volo.Abp.MultiTenancy; +using Xunit; + +namespace Volo.Abp.EventBus.Local +{ + public class EventBus_MultiTenancy_Test : EventBusTestBase + { + [Fact] + public async Task Should_Change_TenantId_If_EventData_Is_MultiTenant() + { + var tenantId = Guid.NewGuid(); + var handler = new MyEventHandler(GetRequiredService()); + + LocalEventBus.Subscribe>(handler); + + await LocalEventBus.PublishAsync(new EntityCreatedEventData(new MyEntity(tenantId))); + + handler.TenantId.ShouldBe(tenantId); + } + + public class MyEntity : Entity, IMultiTenant + { + public override object[] GetKeys() + { + return new object[0]; + } + + public MyEntity() + { + + } + + public MyEntity(Guid? tenantId) + { + TenantId = tenantId; + } + + public Guid? TenantId { get; } + } + + public class MyEventHandler : ILocalEventHandler> + { + private readonly ICurrentTenant _currentTenant; + + public MyEventHandler(ICurrentTenant currentTenant) + { + _currentTenant = currentTenant; + } + + public Guid? TenantId { get; set; } + + public Task HandleEventAsync(EntityChangedEventData eventData) + { + TenantId = _currentTenant.Id; + return Task.CompletedTask; + } + } + } +} diff --git a/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/MySimpleEventData.cs b/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/MySimpleEventData.cs index 08142b3355..45a3e7f237 100644 --- a/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/MySimpleEventData.cs +++ b/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/MySimpleEventData.cs @@ -1,12 +1,18 @@ +using System; +using Volo.Abp.MultiTenancy; + namespace Volo.Abp.EventBus { - public class MySimpleEventData + public class MySimpleEventData : IMultiTenant { public int Value { get; set; } - public MySimpleEventData(int value) + public Guid? TenantId { get; } + + public MySimpleEventData(int value, Guid? tenantId = null) { Value = value; + TenantId = tenantId; } } -} \ No newline at end of file +} diff --git a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/PersonAppServiceClientProxy_Tests.cs b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/PersonAppServiceClientProxy_Tests.cs index 5fa3d4ab66..e53e3cabbf 100644 --- a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/PersonAppServiceClientProxy_Tests.cs +++ b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/PersonAppServiceClientProxy_Tests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; @@ -40,12 +41,29 @@ namespace Volo.Abp.Http.DynamicProxying [Fact] public async Task GetList() { - var people = await _peopleAppService.GetListAsync(new PagedAndSortedResultRequestDto()) - ; + var people = await _peopleAppService.GetListAsync(new PagedAndSortedResultRequestDto()); people.TotalCount.ShouldBeGreaterThan(0); people.Items.Count.ShouldBe((int) people.TotalCount); } + [Fact] + public async Task GetParams() + { + var id1 = Guid.NewGuid(); + var id2 = Guid.NewGuid(); + + var @params = await _peopleAppService.GetParams(new List + { + id1, + id2 + }, new[] {"name1", "name2"}); + + @params.ShouldContain(id1.ToString("N")); + @params.ShouldContain(id2.ToString("N")); + @params.ShouldContain("name1"); + @params.ShouldContain("name2"); + } + [Fact] public async Task Delete() { @@ -151,4 +169,4 @@ namespace Volo.Abp.Http.DynamicProxying result.Inner1.Inner2.Value3.ShouldBe("value three"); } } -} \ No newline at end of file +} diff --git a/framework/test/Volo.Abp.Ldap.Tests/Volo.Abp.Ldap.Tests.csproj b/framework/test/Volo.Abp.Ldap.Tests/Volo.Abp.Ldap.Tests.csproj index 3c1f9c96db..d2db584347 100644 --- a/framework/test/Volo.Abp.Ldap.Tests/Volo.Abp.Ldap.Tests.csproj +++ b/framework/test/Volo.Abp.Ldap.Tests/Volo.Abp.Ldap.Tests.csproj @@ -9,10 +9,8 @@ - - - + diff --git a/framework/test/Volo.Abp.Ldap.Tests/Volo/Abp/Ldap/AbpLdapTestModule.cs b/framework/test/Volo.Abp.Ldap.Tests/Volo/Abp/Ldap/AbpLdapTestModule.cs new file mode 100644 index 0000000000..8297655891 --- /dev/null +++ b/framework/test/Volo.Abp.Ldap.Tests/Volo/Abp/Ldap/AbpLdapTestModule.cs @@ -0,0 +1,25 @@ +using Volo.Abp.Autofac; +using Volo.Abp.Modularity; + +namespace Volo.Abp.Ldap +{ + [DependsOn( + typeof(AbpAutofacModule), + typeof(AbpLdapModule), + typeof(AbpTestBaseModule) + )] + public class AbpLdapTestModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.ServerHost = "192.168.0.3"; + options.ServerPort = 389; + options.BaseDc = "dc=abp,dc=io"; + options.UserName = "admin"; + options.Password = "123qwe"; + }); + } + } +} diff --git a/framework/test/Volo.Abp.Ldap.Tests/Volo/Abp/Ldap/Authenticate_Tests.cs b/framework/test/Volo.Abp.Ldap.Tests/Volo/Abp/Ldap/Authenticate_Tests.cs deleted file mode 100644 index bf11262a99..0000000000 --- a/framework/test/Volo.Abp.Ldap.Tests/Volo/Abp/Ldap/Authenticate_Tests.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; -using Shouldly; -using Volo.Abp.Modularity; -using Volo.Abp.Testing; -using Xunit; - -namespace Volo.Abp.Ldap -{ - - public class Authenticate_Tests : AbpIntegratedTest - { - protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) - { - options.UseAutofac(); - } - - private readonly ILdapManager _ldapManager; - private readonly LdapTestData _testData; - - public Authenticate_Tests() - { - // ReSharper disable once VirtualMemberCallInConstructor - _testData = GetRequiredService(); - _ldapManager = GetRequiredService(); - } - - [Fact(Skip = "need environment AD ")] - public void Authenticate() - { - var result = _ldapManager.Authenticate(_testData.AdministratorDomainName, _testData.AdministratorPassword); - - result.ShouldBeTrue(); - } - - [Fact(Skip = "need environment AD ")] - public void Authenticate_With_Wrong_Password() - { - var result = _ldapManager.Authenticate("NonExistentNameA", "PasswordA"); - - result.ShouldBeFalse(); - } - - [DependsOn(typeof(AbpLdapModule))] - public class TestModule : AbpModule - { - public override void ConfigureServices(ServiceConfigurationContext context) - { - // not use ssl - // "LDAP": { - // "ServerHost": "192.168.101.54", - // "ServerPort": 389, - // "UseSSL": false - // } - - // use ssl - // "LDAP": { - // "ServerHost": "192.168.101.54", - // "ServerPort": 636, - // "UseSSL": true - // } - Configure(settings => - { - settings.ServerHost = "192.168.101.54"; - settings.ServerPort = 636; - settings.UseSsl = true; - }); - } - } - } - -} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Ldap.Tests/Volo/Abp/Ldap/LdapHelps_Tests.cs b/framework/test/Volo.Abp.Ldap.Tests/Volo/Abp/Ldap/LdapHelps_Tests.cs deleted file mode 100644 index 06d6834992..0000000000 --- a/framework/test/Volo.Abp.Ldap.Tests/Volo/Abp/Ldap/LdapHelps_Tests.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System.Collections.Generic; -using Shouldly; -using Xunit; - -namespace Volo.Abp.Ldap -{ - public class LdapHelps_Tests - { - - [Fact] - public void BuildCondition_With_Value() - { - // act - var res = LdapHelps.BuildCondition("objectClass", "testNameA"); - - // assert - res.ShouldBe("(objectClass=testNameA)"); - } - - [Fact] - public void BuildCondition_With_Null_Value() - { - // act - var res = LdapHelps.BuildCondition("objectClass", null); - - // assert - res.ShouldBeEmpty(); - } - - [Fact] - public void BuildCondition_With_Empty_Value() - { - // act - var res = LdapHelps.BuildCondition("objectClass", ""); - - // assert - res.ShouldBeEmpty(); - } - - [Fact] - public void BuildCondition_With_WhiteSpace_Value() - { - // act - var res = LdapHelps.BuildCondition("objectClass", " "); - - // assert - res.ShouldBeEmpty(); - } - - [Fact] - public void BuildFilter_With_Null_Condition() - { - // act - var res = LdapHelps.BuildFilter(null); - - // assert - res.ShouldBe("(&(objectClass=*))"); - } - - [Fact] - public void BuildFilter_With_Empty_Condition() - { - // act - var res = LdapHelps.BuildFilter(new Dictionary()); - - // assert - res.ShouldBe("(&(objectClass=*))"); - } - - [Fact] - public void BuildFilter_With_Condition() - { - // act - var conditions = new Dictionary - { - {"objectClass", "testClassA"}, {"objectCategory", "testCategoryA"}, {"name", null} - }; - var res = LdapHelps.BuildFilter(conditions); - - // assert - res.ShouldBe("(&(objectClass=testClassA)(objectCategory=testCategoryA))"); - } - } -} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Ldap.Tests/Volo/Abp/Ldap/LdapManager_Tests.cs b/framework/test/Volo.Abp.Ldap.Tests/Volo/Abp/Ldap/LdapManager_Tests.cs index 3e83d26543..8da9a9303d 100644 --- a/framework/test/Volo.Abp.Ldap.Tests/Volo/Abp/Ldap/LdapManager_Tests.cs +++ b/framework/test/Volo.Abp.Ldap.Tests/Volo/Abp/Ldap/LdapManager_Tests.cs @@ -1,246 +1,28 @@ -using System; -using Shouldly; -using Volo.Abp.Modularity; +using Shouldly; using Volo.Abp.Testing; using Xunit; namespace Volo.Abp.Ldap { - - public class LdapManager_Tests : AbpIntegratedTest + public class LdapManager_Tests : AbpIntegratedTest { - protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) - { - options.UseAutofac(); - } - private readonly ILdapManager _ldapManager; - private readonly LdapTestData _testData; public LdapManager_Tests() { - // ReSharper disable once VirtualMemberCallInConstructor - _testData = GetRequiredService(); _ldapManager = GetRequiredService(); } - [Fact(Skip = "need environment AD ")] - public void GetOrganizations_With_Empty_Condition() - { - var result = _ldapManager.GetOrganizations(); - - result.ShouldNotBeNull(); - result.ShouldContain(e => e.Name == _testData.DomainControllersName); - result.ShouldContain(e => e.DistinguishedName == _testData.DomainControllersDistinguishedName); - } - - [Fact(Skip = "need environment AD ")] - public void GetOrganizations_With_Name() - { - var result = _ldapManager.GetOrganizations(_testData.DomainControllersName); - - result.ShouldNotBeNull(); - result.ShouldHaveSingleItem(); - result.ShouldContain(e => e.Name == _testData.DomainControllersName); - result.ShouldContain(e => e.DistinguishedName == _testData.DomainControllersDistinguishedName); - } - - [Fact(Skip = "need environment AD ")] - public void GetOrganizations_With_Non_Existent_Name() - { - var result = _ldapManager.GetOrganizations("NonExistentNameA"); - - result.ShouldNotBeNull(); - result.ShouldBeEmpty(); - } - - [Fact(Skip = "need environment AD ")] - public void GetOrganization() - { - var result = _ldapManager.GetOrganization(_testData.DomainControllersDistinguishedName); - - result.ShouldNotBeNull(); - result.Name.ShouldBe(_testData.DomainControllersName); - } - - [Fact(Skip = "need environment AD ")] - public void GetOrganization_With_Non_Existent_DistinguishedName() - { - var result = _ldapManager.GetOrganization("NonExistentNameA"); - - result.ShouldBeNull(); - } - - [Fact(Skip = "need environment AD ")] - public void GetUsers_With_Empty_Condition() - { - var result = _ldapManager.GetUsers(); - - result.ShouldNotBeNull(); - result.ShouldContain(e => e.Name == _testData.AdministratorName); - } - - [Fact(Skip = "need environment AD ")] - public void GetUsers_With_Name() - { - var result = _ldapManager.GetUsers(name: _testData.AdministratorName); - - result.ShouldNotBeNull(); - result.ShouldContain(e => e.Name == _testData.AdministratorName); - } - - [Fact(Skip = "need environment AD ")] - public void GetUsers_With_Non_Existent_Name() - { - var result = _ldapManager.GetUsers(name: "NonExistentNameA"); - - result.ShouldNotBeNull(); - result.ShouldBeEmpty(); - } - - [Fact(Skip = "need environment AD ")] - public void GetUsers_With_CommonName() - { - var result = _ldapManager.GetUsers(commonName: _testData.AdministratorName); - - result.ShouldNotBeNull(); - result.ShouldContain(e => e.Name == _testData.AdministratorName); - } - - [Fact(Skip = "need environment AD ")] - public void GetUsers_With_Non_Existent_CommonName() - { - var result = _ldapManager.GetUsers(commonName: "NonExistentNameA"); - - result.ShouldNotBeNull(); - result.ShouldBeEmpty(); - } - - [Fact(Skip = "need environment AD ")] - public void GetUsers_With_DisplayName() - { - var result = _ldapManager.GetUsers(displayName: _testData.AdministratorName); - - result.ShouldNotBeNull(); - // the administrator in AD. not have display name by default. - result.ShouldBeEmpty(); - } - - [Fact(Skip = "need environment AD ")] - public void GetUser() - { - var result = _ldapManager.GetUser(_testData.AdministratorDistinguishedName); - - result.ShouldNotBeNull(); - result.Name.ShouldBe(_testData.AdministratorName); - } - - [Fact(Skip = "need environment AD ")] - public void GetUser_With_Non_Existent_DistinguishedName() + protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) { - var result = _ldapManager.GetOrganization("NonExistentNameA"); - - result.ShouldBeNull(); + options.UseAutofac(); } - [Fact(Skip = "need environment AD ")] + [Fact(Skip = "Required Ldap environment")] public void Authenticate() { - var result = _ldapManager.Authenticate(_testData.AdministratorDomainName, _testData.AdministratorPassword); - - result.ShouldBeTrue(); - } - - [Fact(Skip = "need environment AD ")] - public void Authenticate_With_Wrong_Password() - { - var result = _ldapManager.Authenticate("NonExistentNameA", "PasswordA"); - - result.ShouldBeFalse(); - } - - [Fact(Skip = "need environment AD ")] - public void AddSubOrganization() - { - var parentOrganization = _ldapManager.GetOrganization(_testData.DomainControllersDistinguishedName); - var randomName = $"Test_{DateTime.Now.Ticks}"; - - _ldapManager.AddSubOrganization(randomName, parentOrganization); - - var result = _ldapManager.GetOrganizations(randomName); - result.ShouldNotBeNull(); - result.ShouldContain(e => e.Name == randomName); - } - - [Fact(Skip = "need environment AD ")] - public void AddSubOrganization_With_DistinguishedName() - { - var randomName = $"Test_{DateTime.Now.Ticks}"; - - _ldapManager.AddSubOrganization(randomName, _testData.DomainControllersDistinguishedName); - - var result = _ldapManager.GetOrganizations(randomName); - result.ShouldNotBeNull(); - result.ShouldContain(e => e.Name == randomName); - } - - [Fact(Skip = "need environment AD ")] - public void AddOrganizationUser() - { - var parentOrganization = _ldapManager.GetOrganization(_testData.DomainControllersDistinguishedName); - var randomName = $"Test_{DateTime.Now:yyMMddHHmmss}"; - _ldapManager.AddUserToOrganization(randomName, _testData.AdministratorPassword, parentOrganization); - - var result = _ldapManager.GetUsers(randomName); - result.ShouldNotBeNull(); - result.ShouldContain(e=>e.Name == randomName); - } - - [DependsOn(typeof(AbpLdapModule))] - public class TestModule : AbpModule - { - public override void ConfigureServices(ServiceConfigurationContext context) - { - // not use ssl - // "LDAP": { - // "ServerHost": "192.168.101.54", - // "ServerPort": 389, - // "UseSSL": false, - // "Credentials": { - // "DomainUserName": "administrator@yourdomain.com.cn", - // "Password": "yH.20190528" - // }, - // "SearchBase": "CN=Users,DC=yourdomain,DC=com,DC=cn", - // "DomainName": "yourdomain.com.cn", - // "DomainDistinguishedName": "DC=yourdomain,DC=com,DC=cn" - // } - - // use ssl - // "LDAP": { - // "ServerHost": "192.168.101.54", - // "ServerPort": 636, - // "UseSSL": true, - // "Credentials": { - // "DomainUserName": "administrator@yourdomain.com.cn", - // "Password": "yH.20190528" - // }, - // "SearchBase": "CN=Users,DC=yourdomain,DC=com,DC=cn", - // "DomainName": "yourdomain.com.cn", - // "DomainDistinguishedName": "DC=yourdomain,DC=com,DC=cn" - // } - Configure(settings => - { - settings.ServerHost = "192.168.101.54"; - settings.ServerPort = 636; - settings.UseSsl = true; - settings.Credentials.DomainUserName = "administrator@yourdomain.com.cn"; - settings.Credentials.Password = "yH.20190528"; - settings.SearchBase = "DC=yourdomain,DC=com,DC=cn"; - settings.DomainName = "yourdomain.com.cn"; - settings.DomainDistinguishedName = "DC=yourdomain,DC=com,DC=cn"; - }); - } + _ldapManager.Authenticate("cn=abp,dc=abp,dc=io", "123qwe").ShouldBe(true); + _ldapManager.Authenticate("NoExists", "123qwe").ShouldBe(false); } } - -} \ No newline at end of file +} diff --git a/framework/test/Volo.Abp.Ldap.Tests/Volo/Abp/Ldap/LdapOptions_Tests.cs b/framework/test/Volo.Abp.Ldap.Tests/Volo/Abp/Ldap/LdapOptions_Tests.cs new file mode 100644 index 0000000000..8ec7ef94c6 --- /dev/null +++ b/framework/test/Volo.Abp.Ldap.Tests/Volo/Abp/Ldap/LdapOptions_Tests.cs @@ -0,0 +1,21 @@ +using Microsoft.Extensions.Options; +using Shouldly; +using Volo.Abp.Testing; +using Xunit; + +namespace Volo.Abp.Ldap +{ + public class LdapOptions_Tests : AbpIntegratedTest + { + protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) + { + options.UseAutofac(); + } + + [Fact] + public void Should_Resolve_AbpAbpLdapOptionsFactory() + { + GetRequiredService>().ShouldBeOfType(typeof(AbpAbpLdapOptionsFactory)); + } + } +} diff --git a/framework/test/Volo.Abp.Ldap.Tests/Volo/Abp/Ldap/LdapTestData.cs b/framework/test/Volo.Abp.Ldap.Tests/Volo/Abp/Ldap/LdapTestData.cs deleted file mode 100644 index 8224686daa..0000000000 --- a/framework/test/Volo.Abp.Ldap.Tests/Volo/Abp/Ldap/LdapTestData.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Volo.Abp.DependencyInjection; - -namespace Volo.Abp.Ldap -{ - public class LdapTestData : ISingletonDependency - { - public string AdministratorName { get; } = "Administrator"; - public string AdministratorPassword { get; } = "yH.20190528"; - public string AdministratorDistinguishedName { get; } = "CN=Administrator,CN=Users,DC=yourdomain,DC=com,DC=cn"; - public string AdministratorDomainName { get; } = "Administrator@yourdomain.com.cn"; - - public string DomainControllersName = "Domain Controllers"; - public string DomainControllersDistinguishedName = "OU=Domain Controllers,DC=yourdomain,DC=com,DC=cn"; - - public string RootDistinguishedName { get; } = "DC=yourdomain,DC=com,DC=cn"; - - public string Organization001Name { get; } = "Test_A"; - - public string Test001Name { get; } = "test001"; - public string Test001Password { get; } = "yH.20190528"; - public string Test001Email { get; } = "test001@yourdomain.com.cn"; - - public string Test002Name { get; } = "test002"; - public string Test002Password { get; } = "yH.20190528"; - public string Test002WrongPassword { get; } = "yH.20190529"; - } -} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/AbpSecurityTestModule.cs b/framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/AbpSecurityTestModule.cs index b71a0a214a..a1ce5c56aa 100644 --- a/framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/AbpSecurityTestModule.cs +++ b/framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/AbpSecurityTestModule.cs @@ -1,4 +1,5 @@ using Volo.Abp.Modularity; +using Volo.Abp.SecurityLog; namespace Volo.Abp.Security { @@ -8,6 +9,12 @@ namespace Volo.Abp.Security )] public class AbpSecurityTestModule : AbpModule { - + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(x => + { + x.ApplicationName = "AbpSecurityTest"; + }); + } } } diff --git a/framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/SecurityLog/SecurityLogManager_Tests.cs b/framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/SecurityLog/SecurityLogManager_Tests.cs new file mode 100644 index 0000000000..94fb6bccda --- /dev/null +++ b/framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/SecurityLog/SecurityLogManager_Tests.cs @@ -0,0 +1,45 @@ +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using NSubstitute; +using Volo.Abp.SecurityLog; +using Volo.Abp.Testing; +using Xunit; + +namespace Volo.Abp.Security.SecurityLog +{ + + public class SecurityLogManager_Tests : AbpIntegratedTest + { + private readonly ISecurityLogManager _securityLogManager; + + private ISecurityLogStore _auditingStore; + + public SecurityLogManager_Tests() + { + _securityLogManager = GetRequiredService(); + } + + protected override void AfterAddApplication(IServiceCollection services) + { + _auditingStore = Substitute.For(); + services.AddSingleton(_auditingStore); + } + + [Fact] + public async Task SaveAsync() + { + await _securityLogManager.SaveAsync(securityLog => + { + securityLog.Identity = "Test"; + securityLog.Action = "Test-Action"; + securityLog.UserName = "Test-User"; + }); + + await _auditingStore.Received().SaveAsync(Arg.Is(log => + log.ApplicationName == "AbpSecurityTest" && + log.Identity == "Test" && + log.Action == "Test-Action" && + log.UserName == "Test-User")); + } + } +} diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/IPeopleAppService.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/IPeopleAppService.cs index 38b0e87787..a34cd19ebb 100644 --- a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/IPeopleAppService.cs +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/IPeopleAppService.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Services; @@ -10,6 +11,8 @@ namespace Volo.Abp.TestApp.Application { Task> GetPhones(Guid id, GetPersonPhonesFilter filter); + Task> GetParams(IEnumerable ids, string[] names); + Task AddPhone(Guid id, PhoneDto phoneDto); Task RemovePhone(Guid id, string number); @@ -18,4 +21,4 @@ namespace Volo.Abp.TestApp.Application Task GetWithComplexType(GetWithComplexTypeInput input); } -} \ No newline at end of file +} diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/PeopleAppService.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/PeopleAppService.cs index 522f8d9794..cff72c7869 100644 --- a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/PeopleAppService.cs +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/PeopleAppService.cs @@ -18,18 +18,25 @@ namespace Volo.Abp.TestApp.Application { } - + public async Task> GetPhones(Guid id, GetPersonPhonesFilter filter) { var phones = (await GetEntityByIdAsync(id)).Phones .WhereIf(filter.Type.HasValue, p => p.Type == filter.Type) .ToList(); - + return new ListResultDto( ObjectMapper.Map, List>(phones) ); } + public Task> GetParams(IEnumerable ids, string[] names) + { + var @params = ids.Select(id => id.ToString("N")).ToList(); + @params.AddRange(names); + return Task.FromResult(@params.ToList()); + } + public async Task AddPhone(Guid id, PhoneDto phoneDto) { var person = await GetEntityByIdAsync(id); diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/TestDataBuilder.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/TestDataBuilder.cs index 312125d541..4b5f390d85 100644 --- a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/TestDataBuilder.cs +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/TestDataBuilder.cs @@ -46,7 +46,15 @@ namespace Volo.Abp.TestApp await _cityRepository.InsertAsync(new City(Guid.NewGuid(), "Tokyo")); await _cityRepository.InsertAsync(new City(Guid.NewGuid(), "Madrid")); - await _cityRepository.InsertAsync(new City(LondonCityId, "London") { ExtraProperties = { { "Population", 10_470_000 }, { "PhoneCode", "42" } } }); + await _cityRepository.InsertAsync(new City(LondonCityId, "London") + { + ExtraProperties = + { + { "Population", 10_470_000 }, + { "PhoneCode", "42" }, + { "ZipCode", "1000" } + } + }); await _cityRepository.InsertAsync(istanbul); await _cityRepository.InsertAsync(new City(Guid.NewGuid(), "Paris")); await _cityRepository.InsertAsync(new City(Guid.NewGuid(), "Washington")); @@ -79,4 +87,4 @@ namespace Volo.Abp.TestApp await _entityWithIntPksRepository.InsertAsync(new EntityWithIntPk("Entity1")); } } -} \ No newline at end of file +} diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/Repository_Basic_Tests.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/Repository_Basic_Tests.cs index 6e72bd1f3c..a93123c1ca 100644 --- a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/Repository_Basic_Tests.cs +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/Repository_Basic_Tests.cs @@ -8,7 +8,7 @@ using Xunit; namespace Volo.Abp.TestApp.Testing { - public abstract class Repository_Basic_Tests : TestAppTestBase + public abstract class Repository_Basic_Tests : TestAppTestBase where TStartupModule : IAbpModule { protected readonly IRepository PersonRepository; @@ -105,5 +105,15 @@ namespace Volo.Abp.TestApp.Testing var person = await PersonRepository.FindAsync(personId); person.ShouldNotBeNull(); } + + [Fact] + public async Task Insert_Should_Set_Guid_Id() + { + var person = new Person(Guid.Empty, "New Person", 35); + + await PersonRepository.InsertAsync(person); + + person.Id.ShouldNotBe(Guid.Empty); + } } } diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/Localization/ar.json b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/Localization/ar.json new file mode 100644 index 0000000000..31a75bad8b --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/Localization/ar.json @@ -0,0 +1,7 @@ +{ + "culture": "ar", + "texts": { + "HelloText": "مرحبا {0}", + "HowAreYou": "كيف حالكم ؟" + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.VirtualFileSystem.Tests/Volo.Abp.VirtualFileSystem.Tests.csproj b/framework/test/Volo.Abp.VirtualFileSystem.Tests/Volo.Abp.VirtualFileSystem.Tests.csproj index bcb9dcbf74..122f54f075 100644 --- a/framework/test/Volo.Abp.VirtualFileSystem.Tests/Volo.Abp.VirtualFileSystem.Tests.csproj +++ b/framework/test/Volo.Abp.VirtualFileSystem.Tests/Volo.Abp.VirtualFileSystem.Tests.csproj @@ -16,7 +16,7 @@ - + - + diff --git a/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo.Abp.Account.Application.Contracts.csproj b/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo.Abp.Account.Application.Contracts.csproj index 8b2ebba2f8..722a75a22c 100644 --- a/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo.Abp.Account.Application.Contracts.csproj +++ b/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo.Abp.Account.Application.Contracts.csproj @@ -21,7 +21,7 @@ - + - - \ No newline at end of file + + diff --git a/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/en.json b/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/en.json index f0dbdaadb6..2940fff63b 100644 --- a/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/en.json +++ b/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/en.json @@ -43,6 +43,7 @@ "Description:Abp.Account.EnableLocalLogin": "Indicates if the server will allow users to authenticate with a local account.", "LoggedOutTitle": "Signed Out", "LoggedOutText": "You have been signed out and you will be redirected soon.", - "ReturnToText": "Click here to redirect to {0}" + "ReturnToText": "Click here to redirect to {0}", + "OrLoginWith": "Or login with;" } } diff --git a/modules/account/src/Volo.Abp.Account.Application/Volo.Abp.Account.Application.csproj b/modules/account/src/Volo.Abp.Account.Application/Volo.Abp.Account.Application.csproj index c3a56c653d..bdcf0c9e8d 100644 --- a/modules/account/src/Volo.Abp.Account.Application/Volo.Abp.Account.Application.csproj +++ b/modules/account/src/Volo.Abp.Account.Application/Volo.Abp.Account.Application.csproj @@ -17,9 +17,9 @@ - + - + - + diff --git a/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLoginModel.cs b/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLoginModel.cs index 69986a45e9..297e54cd69 100644 --- a/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLoginModel.cs +++ b/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLoginModel.cs @@ -14,7 +14,10 @@ using System.Security.Principal; using System.Threading.Tasks; using Volo.Abp.Account.Settings; using Volo.Abp.DependencyInjection; +using Volo.Abp.Identity; +using Volo.Abp.Identity.AspNetCore; using Volo.Abp.MultiTenancy; +using Volo.Abp.SecurityLog; using Volo.Abp.Settings; using Volo.Abp.Uow; @@ -129,6 +132,13 @@ namespace Volo.Abp.Account.Web.Pages.Account true ); + await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext() + { + Identity = IdentitySecurityLogIdentityConsts.Identity, + Action = result.ToIdentitySecurityLogAction(), + UserName = LoginInput.UserNameOrEmailAddress + }); + if (result.RequiresTwoFactor) { return RedirectToPage("./SendSecurityCode", new diff --git a/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLogoutModel.cs b/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLogoutModel.cs index 43857cc111..bec2ede369 100644 --- a/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLogoutModel.cs +++ b/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLogoutModel.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Mvc; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Volo.Abp.DependencyInjection; +using Volo.Abp.Identity; namespace Volo.Abp.Account.Web.Pages.Account { @@ -19,6 +20,12 @@ namespace Volo.Abp.Account.Web.Pages.Account public override async Task OnGetAsync() { + await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext() + { + Identity = IdentitySecurityLogIdentityConsts.Identity, + Action = IdentitySecurityLogActionConsts.Logout + }); + await SignInManager.SignOutAsync(); var logoutId = Request.Query["logoutId"].ToString(); diff --git a/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Volo.Abp.Account.Web.IdentityServer.csproj b/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Volo.Abp.Account.Web.IdentityServer.csproj index 2d62917fcc..566d40b3f9 100644 --- a/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Volo.Abp.Account.Web.IdentityServer.csproj +++ b/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Volo.Abp.Account.Web.IdentityServer.csproj @@ -34,7 +34,7 @@ - + - + diff --git a/modules/account/src/Volo.Abp.Account.Web/Areas/Account/Controllers/AccountController.cs b/modules/account/src/Volo.Abp.Account.Web/Areas/Account/Controllers/AccountController.cs index 64fbdd40d1..575853f410 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Areas/Account/Controllers/AccountController.cs +++ b/modules/account/src/Volo.Abp.Account.Web/Areas/Account/Controllers/AccountController.cs @@ -7,6 +7,7 @@ using Volo.Abp.Account.Settings; using Volo.Abp.Account.Web.Areas.Account.Controllers.Models; using Volo.Abp.AspNetCore.Mvc; using Volo.Abp.Identity; +using Volo.Abp.Identity.AspNetCore; using Volo.Abp.Settings; using Volo.Abp.Validation; using SignInResult = Microsoft.AspNetCore.Identity.SignInResult; @@ -25,14 +26,20 @@ namespace Volo.Abp.Account.Web.Areas.Account.Controllers protected SignInManager SignInManager { get; } protected IdentityUserManager UserManager { get; } protected ISettingProvider SettingProvider { get; } + protected IdentitySecurityLogManager IdentitySecurityLogManager { get; } - public AccountController(SignInManager signInManager, IdentityUserManager userManager, ISettingProvider settingProvider) + public AccountController( + SignInManager signInManager, + IdentityUserManager userManager, + ISettingProvider settingProvider, + IdentitySecurityLogManager identitySecurityLogManager) { LocalizationResource = typeof(AccountResource); SignInManager = signInManager; UserManager = userManager; SettingProvider = settingProvider; + IdentitySecurityLogManager = identitySecurityLogManager; } [HttpPost] @@ -44,20 +51,34 @@ namespace Volo.Abp.Account.Web.Areas.Account.Controllers ValidateLoginInfo(login); await ReplaceEmailToUsernameOfInputIfNeeds(login); - - return GetAbpLoginResult(await SignInManager.PasswordSignInAsync( + var signInResult = await SignInManager.PasswordSignInAsync( login.UserNameOrEmailAddress, login.Password, login.RememberMe, true - )); + ); + + await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext() + { + Identity = IdentitySecurityLogIdentityConsts.Identity, + Action = signInResult.ToIdentitySecurityLogAction(), + UserName = login.UserNameOrEmailAddress + }); + + return GetAbpLoginResult(signInResult); } [HttpGet] [Route("logout")] public virtual async Task Logout() { - await SignInManager.SignOutAsync(); + await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext() + { + Identity = IdentitySecurityLogIdentityConsts.Identity, + Action = IdentitySecurityLogActionConsts.Logout + }); + + await SignInManager.SignOutAsync(); } [HttpPost] diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/AccountPageModel.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/AccountPageModel.cs index 41bdd93647..7e79442bd7 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/AccountPageModel.cs +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/AccountPageModel.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Volo.Abp.Account.Localization; @@ -15,6 +14,7 @@ namespace Volo.Abp.Account.Web.Pages.Account { public SignInManager SignInManager { get; set; } public IdentityUserManager UserManager { get; set; } + public IdentitySecurityLogManager IdentitySecurityLogManager { get; set; } protected AccountPageModel() { diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml index 656f40850f..41986c3856 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml @@ -6,69 +6,67 @@ @model Volo.Abp.Account.Web.Pages.Account.LoginModel @inject IHtmlLocalizer L @inject Volo.Abp.Settings.ISettingProvider SettingProvider -@if (Model.EnableLocalLogin) -{ -
    -
    -

    @L["Login"]

    - @if (await SettingProvider.IsTrueAsync(AccountSettingNames.IsSelfRegistrationEnabled)) - { - - @L["AreYouANewUser"] - @L["Register"] - - } +
    +
    +

    @L["Login"]

    + @if (await SettingProvider.IsTrueAsync(AccountSettingNames.IsSelfRegistrationEnabled)) + { + + @L["AreYouANewUser"] + @L["Register"] + + } + @if (Model.EnableLocalLogin) + {
    - - + +
    - +
    - +
    @L["Login"] + @if (Model.ShowCancelButton) + { + @L["Cancel"] + }
    -
    + } - @if (Model.ShowCancelButton) + @if (Model.VisibleExternalProviders.Any()) { - -} -@if (Model.VisibleExternalProviders.Any()) -{ -
    -

    @L["UseAnotherServiceToLogIn"]

    -
    - - - @foreach (var provider in Model.VisibleExternalProviders) - { - - } -
    -
    -} + @if (!Model.EnableLocalLogin && !Model.VisibleExternalProviders.Any()) + { +
    + @L["InvalidLoginRequest"] + @L["ThereAreNoLoginSchemesConfiguredForThisClient"] +
    + } -@if (!Model.EnableLocalLogin && !Model.VisibleExternalProviders.Any()) -{ -
    - @L["InvalidLoginRequest"] - @L["ThereAreNoLoginSchemesConfiguredForThisClient"]
    -} +
    diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs index 8052b23cd9..ccd6e07a0f 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs @@ -13,11 +13,12 @@ using System.Threading.Tasks; using Volo.Abp.Account.Settings; using Volo.Abp.Auditing; using Volo.Abp.Identity; +using Volo.Abp.Identity.AspNetCore; using Volo.Abp.Security.Claims; using Volo.Abp.Settings; -using Volo.Abp.Uow; using Volo.Abp.Validation; using IdentityUser = Volo.Abp.Identity.IdentityUser; +using SignInResult = Microsoft.AspNetCore.Identity.SignInResult; namespace Volo.Abp.Account.Web.Pages.Account { @@ -97,6 +98,13 @@ namespace Volo.Abp.Account.Web.Pages.Account true ); + await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext() + { + Identity = IdentitySecurityLogIdentityConsts.Identity, + Action = result.ToIdentitySecurityLogAction(), + UserName = LoginInput.UserNameOrEmailAddress + }); + if (result.RequiresTwoFactor) { return RedirectToPage("./SendSecurityCode", new @@ -184,6 +192,15 @@ namespace Volo.Abp.Account.Web.Pages.Account bypassTwoFactor: true ); + if (!result.Succeeded) + { + await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext() + { + Identity = IdentitySecurityLogIdentityConsts.IdentityExternal, + Action = "Login" + result + }); + } + if (result.IsLockedOut) { throw new UserFriendlyException("Cannot proceed because user is locked out!"); @@ -197,18 +214,41 @@ namespace Volo.Abp.Account.Web.Pages.Account //TODO: Handle other cases for result! // Get the information about the user from the external login provider - var info = await SignInManager.GetExternalLoginInfoAsync(); - if (info == null) + var externalLoginInfo = await SignInManager.GetExternalLoginInfoAsync(); + if (externalLoginInfo == null) { throw new ApplicationException("Error loading external login information during confirmation."); } - var user = await CreateExternalUserAsync(info); + if (!IsEmailRetrievedFromExternalLogin(externalLoginInfo)) + { + return RedirectToPage("./Register", new + { + IsExternalLogin = true, + ExternalLoginAuthSchema = externalLoginInfo.LoginProvider, + ReturnUrl = returnUrl + }); + } + + var user = await CreateExternalUserAsync(externalLoginInfo); await SignInManager.SignInAsync(user, false); + + await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext() + { + Identity = IdentitySecurityLogIdentityConsts.IdentityExternal, + Action = result.ToIdentitySecurityLogAction(), + UserName = user.Name + }); + return RedirectSafely(returnUrl, returnUrlHash); } + private static bool IsEmailRetrievedFromExternalLogin(ExternalLoginInfo externalLoginInfo) + { + return externalLoginInfo.Principal.FindFirstValue(AbpClaimTypes.Email) != null; + } + protected virtual async Task CreateExternalUserAsync(ExternalLoginInfo info) { var emailAddress = info.Principal.FindFirstValue(AbpClaimTypes.Email); diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Logout.cshtml.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Logout.cshtml.cs index 8cb2c2bc1e..5923827a24 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Logout.cshtml.cs +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Logout.cshtml.cs @@ -1,5 +1,6 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; +using Volo.Abp.Identity; namespace Volo.Abp.Account.Web.Pages.Account { @@ -15,6 +16,12 @@ namespace Volo.Abp.Account.Web.Pages.Account public virtual async Task OnGetAsync() { + await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext() + { + Identity = IdentitySecurityLogIdentityConsts.Identity, + Action = IdentitySecurityLogActionConsts.Logout + }); + await SignInManager.SignOutAsync(); if (ReturnUrl != null) { diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.cshtml b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.cshtml index 28aa876bba..23350442b2 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.cshtml +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.cshtml @@ -25,33 +25,41 @@ - -

    @L["ChangePassword"].Value


    - - - - -
    + @if (!Model.DisablePasswordChange) + { + +

    @L["ChangePassword"].Value


    +
    + @if (!Model.HideOldPasswordInput) + { + + } + + + + +
    + } -

    @L["PersonalSettings"].Value


    +

    @L["PersonalSettings"].Value


    - + - + - + - + - + - +
    diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.cshtml.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.cshtml.cs index 3c779dc870..db51f9890c 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.cshtml.cs +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.cshtml.cs @@ -12,6 +12,10 @@ namespace Volo.Abp.Account.Web.Pages.Account public PersonalSettingsInfoModel PersonalSettingsInfoModel { get; set; } + public bool DisablePasswordChange { get; set; } + + public bool HideOldPasswordInput { get; set; } + protected IProfileAppService ProfileAppService { get; } public ManageModel(IProfileAppService profileAppService) @@ -25,6 +29,9 @@ namespace Volo.Abp.Account.Web.Pages.Account PersonalSettingsInfoModel = ObjectMapper.Map(user); + DisablePasswordChange = user.IsExternal; + HideOldPasswordInput = !user.HasPassword; + return Page(); } @@ -54,7 +61,7 @@ namespace Volo.Abp.Account.Web.Pages.Account [DataType(DataType.Password)] public string NewPasswordConfirm { get; set; } } - + public class PersonalSettingsInfoModel { [Required] diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.js b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.js index 68109eb6da..3c7c4148e8 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.js +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.js @@ -15,13 +15,13 @@ if ( input.newPassword != input.newPasswordConfirm || - input.currentPassword == '' + input.newPassword == '' ) { abp.message.error(l('NewPasswordConfirmFailed')); return; } - if (input.currentPassword == '') { + if (input.currentPassword && input.currentPassword == ''){ return; } diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml index 204540e7e7..42779e864c 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml @@ -4,18 +4,27 @@ @model Volo.Abp.Account.Web.Pages.Account.RegisterModel @inject IHtmlLocalizer L -
    -
    -

    @L["Register"]

    - - @L["AlreadyRegistered"] - @L["Login"] - -
    - - - - @L["Register"] - -
    +
    +
    +

    @L["Register"]

    + + @L["AlreadyRegistered"] + @L["Login"] + +
    + @if (!Model.IsExternalLogin) + { + + } + + + + @if (!Model.IsExternalLogin) + { + + } + + @L["Register"] +
    +
    diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs index f6d626dd63..a8ec942084 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs @@ -1,14 +1,16 @@ +using System; using System.ComponentModel.DataAnnotations; using System.Linq; +using System.Security.Claims; +using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; using Volo.Abp.Account.Settings; using Volo.Abp.Auditing; -using Volo.Abp.Application.Dtos; using Volo.Abp.Identity; using Volo.Abp.Settings; -using Volo.Abp.Uow; using Volo.Abp.Validation; using IdentityUser = Volo.Abp.Identity.IdentityUser; @@ -27,6 +29,12 @@ namespace Volo.Abp.Account.Web.Pages.Account [BindProperty] public PostInput Input { get; set; } + [BindProperty(SupportsGet = true)] + public bool IsExternalLogin { get; set; } + + [BindProperty(SupportsGet = true)] + public string ExternalLoginAuthSchema { get; set; } + public RegisterModel(IAccountAppService accountAppService) { AccountAppService = accountAppService; @@ -35,32 +43,108 @@ namespace Volo.Abp.Account.Web.Pages.Account public virtual async Task OnGetAsync() { await CheckSelfRegistrationAsync(); - + await TrySetEmailAsync(); return Page(); } + private async Task TrySetEmailAsync() + { + if (IsExternalLogin) + { + var externalLoginInfo = await SignInManager.GetExternalLoginInfoAsync(); + if (externalLoginInfo == null) + { + return; + } + + if (!externalLoginInfo.Principal.Identities.Any()) + { + return; + } + + var identity = externalLoginInfo.Principal.Identities.First(); + var emailClaim = identity.FindFirst(ClaimTypes.Email); + + if (emailClaim == null) + { + return; + } + + Input = new PostInput {EmailAddress = emailClaim.Value}; + } + } + public virtual async Task OnPostAsync() { - ValidateModel(); + try + { + await CheckSelfRegistrationAsync(); + + if (IsExternalLogin) + { + var externalLoginInfo = await SignInManager.GetExternalLoginInfoAsync(); + if (externalLoginInfo == null) + { + Logger.LogWarning("External login info is not available"); + return RedirectToPage("./Login"); + } + + await RegisterExternalUserAsync(externalLoginInfo, Input.EmailAddress); + } + else + { + await RegisterLocalUserAsync(); + } + + return Redirect(ReturnUrl ?? "~/"); //TODO: How to ensure safety? IdentityServer requires it however it should be checked somehow! + } + catch (BusinessException e) + { + Alerts.Danger(e.Message); + return Page(); + } + } - await CheckSelfRegistrationAsync(); + protected virtual async Task RegisterLocalUserAsync() + { + ValidateModel(); - var registerDto = new RegisterDto - { - AppName = "MVC", - EmailAddress = Input.EmailAddress, - Password = Input.Password, - UserName = Input.UserName - }; + var userDto = await AccountAppService.RegisterAsync( + new RegisterDto + { + AppName = "MVC", + EmailAddress = Input.EmailAddress, + Password = Input.Password, + UserName = Input.UserName + } + ); - var userDto = await AccountAppService.RegisterAsync(registerDto); var user = await UserManager.GetByIdAsync(userDto.Id); + await SignInManager.SignInAsync(user, isPersistent: true); + } + + protected virtual async Task RegisterExternalUserAsync(ExternalLoginInfo externalLoginInfo, string emailAddress) + { + var user = new IdentityUser(GuidGenerator.Create(), emailAddress, emailAddress, CurrentTenant.Id); - await UserManager.SetEmailAsync(user, Input.EmailAddress); + (await UserManager.CreateAsync(user)).CheckErrors(); + (await UserManager.AddDefaultRolesAsync(user)).CheckErrors(); - await SignInManager.SignInAsync(user, isPersistent: false); + var userLoginAlreadyExists = user.Logins.Any(x => + x.TenantId == user.TenantId && + x.LoginProvider == externalLoginInfo.LoginProvider && + x.ProviderKey == externalLoginInfo.ProviderKey); + + if (!userLoginAlreadyExists) + { + (await UserManager.AddLoginAsync(user, new UserLoginInfo( + externalLoginInfo.LoginProvider, + externalLoginInfo.ProviderKey, + externalLoginInfo.ProviderDisplayName + ))).CheckErrors(); + } - return Redirect(ReturnUrl ?? "~/"); //TODO: How to ensure safety? IdentityServer requires it however it should be checked somehow! + await SignInManager.SignInAsync(user, isPersistent: true); } protected virtual async Task CheckSelfRegistrationAsync() diff --git a/modules/account/src/Volo.Abp.Account.Web/Volo.Abp.Account.Web.csproj b/modules/account/src/Volo.Abp.Account.Web/Volo.Abp.Account.Web.csproj index 469c17db62..e4bbaf00ba 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Volo.Abp.Account.Web.csproj +++ b/modules/account/src/Volo.Abp.Account.Web/Volo.Abp.Account.Web.csproj @@ -36,7 +36,7 @@ - + - - \ No newline at end of file + + diff --git a/modules/account/test/Volo.Abp.Account.Application.Tests/Volo.Abp.Account.Application.Tests.csproj b/modules/account/test/Volo.Abp.Account.Application.Tests/Volo.Abp.Account.Application.Tests.csproj index 779c2d2d25..84e7de1746 100644 --- a/modules/account/test/Volo.Abp.Account.Application.Tests/Volo.Abp.Account.Application.Tests.csproj +++ b/modules/account/test/Volo.Abp.Account.Application.Tests/Volo.Abp.Account.Application.Tests.csproj @@ -7,7 +7,7 @@ - + diff --git a/modules/audit-logging/test/Volo.Abp.AuditLogging.EntityFrameworkCore.Tests/Volo.Abp.AuditLogging.EntityFrameworkCore.Tests.csproj b/modules/audit-logging/test/Volo.Abp.AuditLogging.EntityFrameworkCore.Tests/Volo.Abp.AuditLogging.EntityFrameworkCore.Tests.csproj index 6287d732e2..0a85b56d87 100644 --- a/modules/audit-logging/test/Volo.Abp.AuditLogging.EntityFrameworkCore.Tests/Volo.Abp.AuditLogging.EntityFrameworkCore.Tests.csproj +++ b/modules/audit-logging/test/Volo.Abp.AuditLogging.EntityFrameworkCore.Tests/Volo.Abp.AuditLogging.EntityFrameworkCore.Tests.csproj @@ -15,7 +15,7 @@ - + diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Shared/Volo.Abp.BackgroundJobs.DemoApp.Shared.csproj b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Shared/Volo.Abp.BackgroundJobs.DemoApp.Shared.csproj index cf5ed0ce08..df4f574663 100644 --- a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Shared/Volo.Abp.BackgroundJobs.DemoApp.Shared.csproj +++ b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Shared/Volo.Abp.BackgroundJobs.DemoApp.Shared.csproj @@ -7,7 +7,7 @@ - + diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20200624023633_BackgroundJobs_Initial.Designer.cs b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20200810022513_Initial.Designer.cs similarity index 95% rename from modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20200624023633_BackgroundJobs_Initial.Designer.cs rename to modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20200810022513_Initial.Designer.cs index 40c0adcee7..cd91ca9fe9 100644 --- a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20200624023633_BackgroundJobs_Initial.Designer.cs +++ b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20200810022513_Initial.Designer.cs @@ -11,15 +11,15 @@ using Volo.Abp.EntityFrameworkCore; namespace Volo.Abp.BackgroundJobs.DemoApp.Migrations { [DbContext(typeof(DemoAppDbContext))] - [Migration("20200624023633_BackgroundJobs_Initial")] - partial class BackgroundJobs_Initial + [Migration("20200810022513_Initial")] + partial class Initial { protected override void BuildTargetModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) - .HasAnnotation("ProductVersion", "3.1.5") + .HasAnnotation("ProductVersion", "3.1.6") .HasAnnotation("Relational:MaxIdentifierLength", 128) .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20200624023633_BackgroundJobs_Initial.cs b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20200810022513_Initial.cs similarity index 96% rename from modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20200624023633_BackgroundJobs_Initial.cs rename to modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20200810022513_Initial.cs index eebffa859a..99c4a11d89 100644 --- a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20200624023633_BackgroundJobs_Initial.cs +++ b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20200810022513_Initial.cs @@ -3,7 +3,7 @@ using Microsoft.EntityFrameworkCore.Migrations; namespace Volo.Abp.BackgroundJobs.DemoApp.Migrations { - public partial class BackgroundJobs_Initial : Migration + public partial class Initial : Migration { protected override void Up(MigrationBuilder migrationBuilder) { diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/DemoAppDbContextModelSnapshot.cs b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/DemoAppDbContextModelSnapshot.cs index fa77fd75ca..4a5df70449 100644 --- a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/DemoAppDbContextModelSnapshot.cs +++ b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/DemoAppDbContextModelSnapshot.cs @@ -17,7 +17,7 @@ namespace Volo.Abp.BackgroundJobs.DemoApp.Migrations #pragma warning disable 612, 618 modelBuilder .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) - .HasAnnotation("ProductVersion", "3.1.5") + .HasAnnotation("ProductVersion", "3.1.6") .HasAnnotation("Relational:MaxIdentifierLength", 128) .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Volo.Abp.BackgroundJobs.DemoApp.csproj b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Volo.Abp.BackgroundJobs.DemoApp.csproj index 0d8388c610..0faa1370a8 100644 --- a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Volo.Abp.BackgroundJobs.DemoApp.csproj +++ b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Volo.Abp.BackgroundJobs.DemoApp.csproj @@ -6,7 +6,7 @@ - + all runtime; build; native; contentfiles; analyzers diff --git a/modules/background-jobs/test/Volo.Abp.BackgroundJobs.EntityFrameworkCore.Tests/Volo.Abp.BackgroundJobs.EntityFrameworkCore.Tests.csproj b/modules/background-jobs/test/Volo.Abp.BackgroundJobs.EntityFrameworkCore.Tests/Volo.Abp.BackgroundJobs.EntityFrameworkCore.Tests.csproj index 3246aefa4f..50c8e74edb 100644 --- a/modules/background-jobs/test/Volo.Abp.BackgroundJobs.EntityFrameworkCore.Tests/Volo.Abp.BackgroundJobs.EntityFrameworkCore.Tests.csproj +++ b/modules/background-jobs/test/Volo.Abp.BackgroundJobs.EntityFrameworkCore.Tests/Volo.Abp.BackgroundJobs.EntityFrameworkCore.Tests.csproj @@ -15,7 +15,7 @@ - + diff --git a/modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/BlobStoring.Database.Host.ConsoleApp.ConsoleApp.csproj b/modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/BlobStoring.Database.Host.ConsoleApp.ConsoleApp.csproj index 43455e655a..7e8d79ec68 100644 --- a/modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/BlobStoring.Database.Host.ConsoleApp.ConsoleApp.csproj +++ b/modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/BlobStoring.Database.Host.ConsoleApp.ConsoleApp.csproj @@ -20,13 +20,13 @@ - + - - + + diff --git a/modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/20200624023744_Initial.Designer.cs b/modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/20200810022447_Initial.Designer.cs similarity index 97% rename from modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/20200624023744_Initial.Designer.cs rename to modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/20200810022447_Initial.Designer.cs index 5aa34b1eda..6d2051908a 100644 --- a/modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/20200624023744_Initial.Designer.cs +++ b/modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/20200810022447_Initial.Designer.cs @@ -11,7 +11,7 @@ using Volo.Abp.EntityFrameworkCore; namespace BlobStoring.Database.Host.ConsoleApp.ConsoleApp.Migrations { [DbContext(typeof(BlobStoringHostDbContext))] - [Migration("20200624023744_Initial")] + [Migration("20200810022447_Initial")] partial class Initial { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -19,7 +19,7 @@ namespace BlobStoring.Database.Host.ConsoleApp.ConsoleApp.Migrations #pragma warning disable 612, 618 modelBuilder .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) - .HasAnnotation("ProductVersion", "3.1.5") + .HasAnnotation("ProductVersion", "3.1.6") .HasAnnotation("Relational:MaxIdentifierLength", 128) .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); diff --git a/modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/20200624023744_Initial.cs b/modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/20200810022447_Initial.cs similarity index 100% rename from modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/20200624023744_Initial.cs rename to modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/20200810022447_Initial.cs diff --git a/modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/BlobStoringHostDbContextModelSnapshot.cs b/modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/BlobStoringHostDbContextModelSnapshot.cs index 796c68b0ad..a32d6221d7 100644 --- a/modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/BlobStoringHostDbContextModelSnapshot.cs +++ b/modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/BlobStoringHostDbContextModelSnapshot.cs @@ -17,7 +17,7 @@ namespace BlobStoring.Database.Host.ConsoleApp.ConsoleApp.Migrations #pragma warning disable 612, 618 modelBuilder .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) - .HasAnnotation("ProductVersion", "3.1.5") + .HasAnnotation("ProductVersion", "3.1.6") .HasAnnotation("Relational:MaxIdentifierLength", 128) .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); diff --git a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain/Volo/Abp/BlobStoring/Database/DatabaseBlobProvider.cs b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain/Volo/Abp/BlobStoring/Database/DatabaseBlobProvider.cs index fbccdb067e..7fec4592c6 100644 --- a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain/Volo/Abp/BlobStoring/Database/DatabaseBlobProvider.cs +++ b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain/Volo/Abp/BlobStoring/Database/DatabaseBlobProvider.cs @@ -3,6 +3,7 @@ using System.Threading; using System.Threading.Tasks; using Volo.Abp.DependencyInjection; using Volo.Abp.Guids; +using Volo.Abp.MultiTenancy; namespace Volo.Abp.BlobStoring.Database { @@ -11,15 +12,18 @@ namespace Volo.Abp.BlobStoring.Database protected IDatabaseBlobRepository DatabaseBlobRepository { get; } protected IDatabaseBlobContainerRepository DatabaseBlobContainerRepository { get; } protected IGuidGenerator GuidGenerator { get; } + protected ICurrentTenant CurrentTenant { get; } public DatabaseBlobProvider( IDatabaseBlobRepository databaseBlobRepository, IDatabaseBlobContainerRepository databaseBlobContainerRepository, - IGuidGenerator guidGenerator) + IGuidGenerator guidGenerator, + ICurrentTenant currentTenant) { DatabaseBlobRepository = databaseBlobRepository; DatabaseBlobContainerRepository = databaseBlobContainerRepository; GuidGenerator = guidGenerator; + CurrentTenant = currentTenant; } public override async Task SaveAsync(BlobProviderSaveArgs args) @@ -48,7 +52,7 @@ namespace Volo.Abp.BlobStoring.Database } else { - blob = new DatabaseBlob(GuidGenerator.Create(), container.Id, args.BlobName, content); + blob = new DatabaseBlob(GuidGenerator.Create(), container.Id, args.BlobName, content, CurrentTenant.Id); await DatabaseBlobRepository.InsertAsync(blob, autoSave: true); } } @@ -128,10 +132,10 @@ namespace Volo.Abp.BlobStoring.Database return container; } - container = new DatabaseBlobContainer(GuidGenerator.Create(), name); + container = new DatabaseBlobContainer(GuidGenerator.Create(), name, CurrentTenant.Id); await DatabaseBlobContainerRepository.InsertAsync(container, cancellationToken: cancellationToken); return container; } } -} \ No newline at end of file +} diff --git a/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.EntityFrameworkCore.Tests/EntityFrameworkCore/EfCoreDatabaseBlobContainer_Tests.cs b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.EntityFrameworkCore.Tests/EntityFrameworkCore/EfCoreDatabaseBlobContainer_Tests.cs new file mode 100644 index 0000000000..094217f9e6 --- /dev/null +++ b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.EntityFrameworkCore.Tests/EntityFrameworkCore/EfCoreDatabaseBlobContainer_Tests.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.BlobStoring.Database.EntityFrameworkCore +{ + public class EfCoreDatabaseBlobContainer_Tests : DatabaseBlobContainer_Tests + { + + } +} diff --git a/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.EntityFrameworkCore.Tests/Volo.Abp.BlobStoring.Database.EntityFrameworkCore.Tests.csproj b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.EntityFrameworkCore.Tests/Volo.Abp.BlobStoring.Database.EntityFrameworkCore.Tests.csproj index 04bdd5071a..fdaeecf17e 100644 --- a/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.EntityFrameworkCore.Tests/Volo.Abp.BlobStoring.Database.EntityFrameworkCore.Tests.csproj +++ b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.EntityFrameworkCore.Tests/Volo.Abp.BlobStoring.Database.EntityFrameworkCore.Tests.csproj @@ -7,9 +7,9 @@ - - - + + + diff --git a/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.MongoDB.Tests/MongoDB/MongoDbDatabaseBlobContainer_Tests.cs b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.MongoDB.Tests/MongoDB/MongoDbDatabaseBlobContainer_Tests.cs new file mode 100644 index 0000000000..4d476d5e75 --- /dev/null +++ b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.MongoDB.Tests/MongoDB/MongoDbDatabaseBlobContainer_Tests.cs @@ -0,0 +1,10 @@ +using Xunit; + +namespace Volo.Abp.BlobStoring.Database.MongoDB +{ + [Collection(MongoTestCollection.Name)] + public class MongoDbDatabaseBlobContainer_Tests : DatabaseBlobContainer_Tests + { + + } +} diff --git a/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.TestBase/DatabaseBlobContainer_Tests.cs b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.TestBase/DatabaseBlobContainer_Tests.cs new file mode 100644 index 0000000000..542bb82113 --- /dev/null +++ b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.TestBase/DatabaseBlobContainer_Tests.cs @@ -0,0 +1,10 @@ +using Volo.Abp.Modularity; + +namespace Volo.Abp.BlobStoring.Database +{ + public abstract class DatabaseBlobContainer_Tests : BlobContainer_Tests + where TStartupModule : IAbpModule + { + + } +} diff --git a/modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/20200722033428_Added_SecurityLogs.Designer.cs b/modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/20200722033428_Added_SecurityLogs.Designer.cs new file mode 100644 index 0000000000..9faa725ce3 --- /dev/null +++ b/modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/20200722033428_Added_SecurityLogs.Designer.cs @@ -0,0 +1,1172 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Volo.Abp.EntityFrameworkCore; +using Volo.BloggingTestApp.EntityFrameworkCore; + +namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations +{ + [DbContext(typeof(BloggingTestAppDbContext))] + [Migration("20200722033428_Added_SecurityLogs")] + partial class Added_SecurityLogs + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) + .HasAnnotation("ProductVersion", "3.1.5") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityClaimType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("Description") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsStatic") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("Regex") + .HasColumnType("nvarchar(512)") + .HasMaxLength(512); + + b.Property("RegexDescription") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("Required") + .HasColumnType("bit"); + + b.Property("ValueType") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("AbpClaimTypes"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDefault") + .HasColumnName("IsDefault") + .HasColumnType("bit"); + + b.Property("IsPublic") + .HasColumnName("IsPublic") + .HasColumnType("bit"); + + b.Property("IsStatic") + .HasColumnName("IsStatic") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("NormalizedName") + .IsRequired() + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName"); + + b.ToTable("AbpRoles"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("ClaimType") + .IsRequired() + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(1024)") + .HasMaxLength(1024); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AbpRoleClaims"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentitySecurityLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Action") + .HasColumnType("nvarchar(96)") + .HasMaxLength(96); + + b.Property("ApplicationName") + .HasColumnType("nvarchar(96)") + .HasMaxLength(96); + + b.Property("BrowserInfo") + .HasColumnType("nvarchar(512)") + .HasMaxLength(512); + + b.Property("ClientId") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ClientIpAddress") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CorrelationId") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("CreationTime") + .HasColumnType("datetime2"); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("Identity") + .HasColumnType("nvarchar(96)") + .HasMaxLength(96); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantName") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Action"); + + b.HasIndex("TenantId", "ApplicationName"); + + b.HasIndex("TenantId", "Identity"); + + b.HasIndex("TenantId", "UserId"); + + b.ToTable("AbpSecurityLogs"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccessFailedCount") + .ValueGeneratedOnAdd() + .HasColumnName("AccessFailedCount") + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeleterId") + .HasColumnName("DeleterId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletionTime") + .HasColumnName("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("Email") + .IsRequired() + .HasColumnName("Email") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("EmailConfirmed") + .ValueGeneratedOnAdd() + .HasColumnName("EmailConfirmed") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnName("IsDeleted") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModificationTime") + .HasColumnName("LastModificationTime") + .HasColumnType("datetime2"); + + b.Property("LastModifierId") + .HasColumnName("LastModifierId") + .HasColumnType("uniqueidentifier"); + + b.Property("LockoutEnabled") + .ValueGeneratedOnAdd() + .HasColumnName("LockoutEnabled") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("Name") + .HasColumnName("Name") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("NormalizedEmail") + .IsRequired() + .HasColumnName("NormalizedEmail") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .IsRequired() + .HasColumnName("NormalizedUserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("PasswordHash") + .HasColumnName("PasswordHash") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("PhoneNumber") + .HasColumnName("PhoneNumber") + .HasColumnType("nvarchar(16)") + .HasMaxLength(16); + + b.Property("PhoneNumberConfirmed") + .ValueGeneratedOnAdd() + .HasColumnName("PhoneNumberConfirmed") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("SecurityStamp") + .IsRequired() + .HasColumnName("SecurityStamp") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("Surname") + .HasColumnName("Surname") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("TwoFactorEnabled") + .ValueGeneratedOnAdd() + .HasColumnName("TwoFactorEnabled") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("UserName") + .IsRequired() + .HasColumnName("UserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("Email"); + + b.HasIndex("NormalizedEmail"); + + b.HasIndex("NormalizedUserName"); + + b.HasIndex("UserName"); + + b.ToTable("AbpUsers"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("ClaimType") + .IsRequired() + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(1024)") + .HasMaxLength(1024); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AbpUserClaims"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserLogin", b => + { + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("ProviderKey") + .IsRequired() + .HasColumnType("nvarchar(196)") + .HasMaxLength(196); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("UserId", "LoginProvider"); + + b.HasIndex("LoginProvider", "ProviderKey"); + + b.ToTable("AbpUserLogins"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserOrganizationUnit", b => + { + b.Property("OrganizationUnitId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("OrganizationUnitId", "UserId"); + + b.HasIndex("UserId", "OrganizationUnitId"); + + b.ToTable("AbpUserOrganizationUnits"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId", "UserId"); + + b.ToTable("AbpUserRoles"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("Name") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AbpUserTokens"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.OrganizationUnit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Code") + .IsRequired() + .HasColumnName("Code") + .HasColumnType("nvarchar(95)") + .HasMaxLength(95); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeleterId") + .HasColumnName("DeleterId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletionTime") + .HasColumnName("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .IsRequired() + .HasColumnName("DisplayName") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnName("IsDeleted") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModificationTime") + .HasColumnName("LastModificationTime") + .HasColumnType("datetime2"); + + b.Property("LastModifierId") + .HasColumnName("LastModifierId") + .HasColumnType("uniqueidentifier"); + + b.Property("ParentId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("Code"); + + b.HasIndex("ParentId"); + + b.ToTable("AbpOrganizationUnits"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.OrganizationUnitRole", b => + { + b.Property("OrganizationUnitId") + .HasColumnType("uniqueidentifier"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("OrganizationUnitId", "RoleId"); + + b.HasIndex("RoleId", "OrganizationUnitId"); + + b.ToTable("AbpOrganizationUnitRoles"); + }); + + modelBuilder.Entity("Volo.Abp.PermissionManagement.PermissionGrant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("ProviderKey") + .IsRequired() + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ProviderName") + .IsRequired() + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("Name", "ProviderName", "ProviderKey"); + + b.ToTable("AbpPermissionGrants"); + }); + + modelBuilder.Entity("Volo.Abp.SettingManagement.Setting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ProviderName") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("Value") + .IsRequired() + .HasColumnType("nvarchar(2048)") + .HasMaxLength(2048); + + b.HasKey("Id"); + + b.HasIndex("Name", "ProviderName", "ProviderKey"); + + b.ToTable("AbpSettings"); + }); + + modelBuilder.Entity("Volo.Blogging.Blogs.Blog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeleterId") + .HasColumnName("DeleterId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletionTime") + .HasColumnName("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnName("Description") + .HasColumnType("nvarchar(1024)") + .HasMaxLength(1024); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnName("IsDeleted") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModificationTime") + .HasColumnName("LastModificationTime") + .HasColumnType("datetime2"); + + b.Property("LastModifierId") + .HasColumnName("LastModifierId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnName("Name") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("ShortName") + .IsRequired() + .HasColumnName("ShortName") + .HasColumnType("nvarchar(32)") + .HasMaxLength(32); + + b.HasKey("Id"); + + b.ToTable("BlgBlogs"); + }); + + modelBuilder.Entity("Volo.Blogging.Comments.Comment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeleterId") + .HasColumnName("DeleterId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletionTime") + .HasColumnName("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnName("IsDeleted") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModificationTime") + .HasColumnName("LastModificationTime") + .HasColumnType("datetime2"); + + b.Property("LastModifierId") + .HasColumnName("LastModifierId") + .HasColumnType("uniqueidentifier"); + + b.Property("PostId") + .HasColumnName("PostId") + .HasColumnType("uniqueidentifier"); + + b.Property("RepliedCommentId") + .HasColumnName("RepliedCommentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Text") + .IsRequired() + .HasColumnName("Text") + .HasColumnType("nvarchar(1024)") + .HasMaxLength(1024); + + b.HasKey("Id"); + + b.HasIndex("PostId"); + + b.HasIndex("RepliedCommentId"); + + b.ToTable("BlgComments"); + }); + + modelBuilder.Entity("Volo.Blogging.Posts.Post", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BlogId") + .HasColumnName("BlogId") + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("Content") + .HasColumnName("Content") + .HasColumnType("nvarchar(max)") + .HasMaxLength(1048576); + + b.Property("CoverImage") + .IsRequired() + .HasColumnName("CoverImage") + .HasColumnType("nvarchar(max)"); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeleterId") + .HasColumnName("DeleterId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletionTime") + .HasColumnName("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnName("Description") + .HasColumnType("nvarchar(1000)") + .HasMaxLength(1000); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnName("IsDeleted") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModificationTime") + .HasColumnName("LastModificationTime") + .HasColumnType("datetime2"); + + b.Property("LastModifierId") + .HasColumnName("LastModifierId") + .HasColumnType("uniqueidentifier"); + + b.Property("ReadCount") + .HasColumnType("int"); + + b.Property("Title") + .IsRequired() + .HasColumnName("Title") + .HasColumnType("nvarchar(512)") + .HasMaxLength(512); + + b.Property("Url") + .IsRequired() + .HasColumnName("Url") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.HasKey("Id"); + + b.HasIndex("BlogId"); + + b.ToTable("BlgPosts"); + }); + + modelBuilder.Entity("Volo.Blogging.Posts.PostTag", b => + { + b.Property("PostId") + .HasColumnName("PostId") + .HasColumnType("uniqueidentifier"); + + b.Property("TagId") + .HasColumnName("TagId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("PostId", "TagId"); + + b.HasIndex("TagId"); + + b.ToTable("BlgPostTags"); + }); + + modelBuilder.Entity("Volo.Blogging.Tagging.Tag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BlogId") + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeleterId") + .HasColumnName("DeleterId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletionTime") + .HasColumnName("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnName("Description") + .HasColumnType("nvarchar(512)") + .HasMaxLength(512); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnName("IsDeleted") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModificationTime") + .HasColumnName("LastModificationTime") + .HasColumnType("datetime2"); + + b.Property("LastModifierId") + .HasColumnName("LastModifierId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnName("Name") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("UsageCount") + .HasColumnName("UsageCount") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("BlgTags"); + }); + + modelBuilder.Entity("Volo.Blogging.Users.BlogUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("Email") + .IsRequired() + .HasColumnName("Email") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("EmailConfirmed") + .ValueGeneratedOnAdd() + .HasColumnName("EmailConfirmed") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasColumnName("Name") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("PhoneNumber") + .HasColumnName("PhoneNumber") + .HasColumnType("nvarchar(16)") + .HasMaxLength(16); + + b.Property("PhoneNumberConfirmed") + .ValueGeneratedOnAdd() + .HasColumnName("PhoneNumberConfirmed") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("Surname") + .HasColumnName("Surname") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserName") + .IsRequired() + .HasColumnName("UserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.ToTable("BlgUsers"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityRoleClaim", b => + { + b.HasOne("Volo.Abp.Identity.IdentityRole", null) + .WithMany("Claims") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserClaim", b => + { + b.HasOne("Volo.Abp.Identity.IdentityUser", null) + .WithMany("Claims") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserLogin", b => + { + b.HasOne("Volo.Abp.Identity.IdentityUser", null) + .WithMany("Logins") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserOrganizationUnit", b => + { + b.HasOne("Volo.Abp.Identity.OrganizationUnit", null) + .WithMany() + .HasForeignKey("OrganizationUnitId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Volo.Abp.Identity.IdentityUser", null) + .WithMany("OrganizationUnits") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserRole", b => + { + b.HasOne("Volo.Abp.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Volo.Abp.Identity.IdentityUser", null) + .WithMany("Roles") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserToken", b => + { + b.HasOne("Volo.Abp.Identity.IdentityUser", null) + .WithMany("Tokens") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.OrganizationUnit", b => + { + b.HasOne("Volo.Abp.Identity.OrganizationUnit", null) + .WithMany() + .HasForeignKey("ParentId"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.OrganizationUnitRole", b => + { + b.HasOne("Volo.Abp.Identity.OrganizationUnit", null) + .WithMany("Roles") + .HasForeignKey("OrganizationUnitId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Volo.Abp.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Blogging.Comments.Comment", b => + { + b.HasOne("Volo.Blogging.Posts.Post", null) + .WithMany() + .HasForeignKey("PostId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Volo.Blogging.Comments.Comment", null) + .WithMany() + .HasForeignKey("RepliedCommentId"); + }); + + modelBuilder.Entity("Volo.Blogging.Posts.Post", b => + { + b.HasOne("Volo.Blogging.Blogs.Blog", null) + .WithMany() + .HasForeignKey("BlogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Blogging.Posts.PostTag", b => + { + b.HasOne("Volo.Blogging.Posts.Post", null) + .WithMany("Tags") + .HasForeignKey("PostId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Volo.Blogging.Tagging.Tag", null) + .WithMany() + .HasForeignKey("TagId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/20200722033428_Added_SecurityLogs.cs b/modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/20200722033428_Added_SecurityLogs.cs new file mode 100644 index 0000000000..7ac1cd66d2 --- /dev/null +++ b/modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/20200722033428_Added_SecurityLogs.cs @@ -0,0 +1,226 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations +{ + public partial class Added_SecurityLogs : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "ConcurrencyStamp", + table: "BlgUsers", + maxLength: 40, + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(max)", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "ConcurrencyStamp", + table: "BlgTags", + maxLength: 40, + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(max)", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "ConcurrencyStamp", + table: "BlgPosts", + maxLength: 40, + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(max)", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "ConcurrencyStamp", + table: "BlgComments", + maxLength: 40, + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(max)", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "ConcurrencyStamp", + table: "BlgBlogs", + maxLength: 40, + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(max)", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "ConcurrencyStamp", + table: "AbpUsers", + maxLength: 40, + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(max)", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "ConcurrencyStamp", + table: "AbpRoles", + maxLength: 40, + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(256)", + oldMaxLength: 256); + + migrationBuilder.AlterColumn( + name: "ConcurrencyStamp", + table: "AbpOrganizationUnits", + maxLength: 40, + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(max)", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "ConcurrencyStamp", + table: "AbpClaimTypes", + maxLength: 40, + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(256)", + oldMaxLength: 256); + + migrationBuilder.CreateTable( + name: "AbpSecurityLogs", + columns: table => new + { + Id = table.Column(nullable: false), + ExtraProperties = table.Column(nullable: true), + ConcurrencyStamp = table.Column(maxLength: 40, nullable: true), + TenantId = table.Column(nullable: true), + ApplicationName = table.Column(maxLength: 96, nullable: true), + Identity = table.Column(maxLength: 96, nullable: true), + Action = table.Column(maxLength: 96, nullable: true), + UserId = table.Column(nullable: true), + UserName = table.Column(maxLength: 256, nullable: true), + TenantName = table.Column(maxLength: 64, nullable: true), + ClientId = table.Column(maxLength: 64, nullable: true), + CorrelationId = table.Column(maxLength: 64, nullable: true), + ClientIpAddress = table.Column(maxLength: 64, nullable: true), + BrowserInfo = table.Column(maxLength: 512, nullable: true), + CreationTime = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpSecurityLogs", x => x.Id); + }); + + migrationBuilder.CreateIndex( + name: "IX_AbpSecurityLogs_TenantId_Action", + table: "AbpSecurityLogs", + columns: new[] { "TenantId", "Action" }); + + migrationBuilder.CreateIndex( + name: "IX_AbpSecurityLogs_TenantId_ApplicationName", + table: "AbpSecurityLogs", + columns: new[] { "TenantId", "ApplicationName" }); + + migrationBuilder.CreateIndex( + name: "IX_AbpSecurityLogs_TenantId_Identity", + table: "AbpSecurityLogs", + columns: new[] { "TenantId", "Identity" }); + + migrationBuilder.CreateIndex( + name: "IX_AbpSecurityLogs_TenantId_UserId", + table: "AbpSecurityLogs", + columns: new[] { "TenantId", "UserId" }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AbpSecurityLogs"); + + migrationBuilder.AlterColumn( + name: "ConcurrencyStamp", + table: "BlgUsers", + type: "nvarchar(max)", + nullable: true, + oldClrType: typeof(string), + oldMaxLength: 40, + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "ConcurrencyStamp", + table: "BlgTags", + type: "nvarchar(max)", + nullable: true, + oldClrType: typeof(string), + oldMaxLength: 40, + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "ConcurrencyStamp", + table: "BlgPosts", + type: "nvarchar(max)", + nullable: true, + oldClrType: typeof(string), + oldMaxLength: 40, + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "ConcurrencyStamp", + table: "BlgComments", + type: "nvarchar(max)", + nullable: true, + oldClrType: typeof(string), + oldMaxLength: 40, + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "ConcurrencyStamp", + table: "BlgBlogs", + type: "nvarchar(max)", + nullable: true, + oldClrType: typeof(string), + oldMaxLength: 40, + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "ConcurrencyStamp", + table: "AbpUsers", + type: "nvarchar(max)", + nullable: true, + oldClrType: typeof(string), + oldMaxLength: 40, + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "ConcurrencyStamp", + table: "AbpRoles", + type: "nvarchar(256)", + maxLength: 256, + nullable: false, + oldClrType: typeof(string), + oldMaxLength: 40, + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "ConcurrencyStamp", + table: "AbpOrganizationUnits", + type: "nvarchar(max)", + nullable: true, + oldClrType: typeof(string), + oldMaxLength: 40, + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "ConcurrencyStamp", + table: "AbpClaimTypes", + type: "nvarchar(256)", + maxLength: 256, + nullable: false, + oldClrType: typeof(string), + oldMaxLength: 40, + oldNullable: true); + } + } +} diff --git a/modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/20200810022522_IdentityUserIsExternal.Designer.cs b/modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/20200810022522_IdentityUserIsExternal.Designer.cs new file mode 100644 index 0000000000..10ddf6bdf3 --- /dev/null +++ b/modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/20200810022522_IdentityUserIsExternal.Designer.cs @@ -0,0 +1,1178 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Volo.Abp.EntityFrameworkCore; +using Volo.BloggingTestApp.EntityFrameworkCore; + +namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations +{ + [DbContext(typeof(BloggingTestAppDbContext))] + [Migration("20200810022522_IdentityUserIsExternal")] + partial class IdentityUserIsExternal + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) + .HasAnnotation("ProductVersion", "3.1.6") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityClaimType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("Description") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsStatic") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("Regex") + .HasColumnType("nvarchar(512)") + .HasMaxLength(512); + + b.Property("RegexDescription") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("Required") + .HasColumnType("bit"); + + b.Property("ValueType") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("AbpClaimTypes"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDefault") + .HasColumnName("IsDefault") + .HasColumnType("bit"); + + b.Property("IsPublic") + .HasColumnName("IsPublic") + .HasColumnType("bit"); + + b.Property("IsStatic") + .HasColumnName("IsStatic") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("NormalizedName") + .IsRequired() + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName"); + + b.ToTable("AbpRoles"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("ClaimType") + .IsRequired() + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(1024)") + .HasMaxLength(1024); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AbpRoleClaims"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentitySecurityLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Action") + .HasColumnType("nvarchar(96)") + .HasMaxLength(96); + + b.Property("ApplicationName") + .HasColumnType("nvarchar(96)") + .HasMaxLength(96); + + b.Property("BrowserInfo") + .HasColumnType("nvarchar(512)") + .HasMaxLength(512); + + b.Property("ClientId") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ClientIpAddress") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CorrelationId") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("CreationTime") + .HasColumnType("datetime2"); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("Identity") + .HasColumnType("nvarchar(96)") + .HasMaxLength(96); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantName") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Action"); + + b.HasIndex("TenantId", "ApplicationName"); + + b.HasIndex("TenantId", "Identity"); + + b.HasIndex("TenantId", "UserId"); + + b.ToTable("AbpSecurityLogs"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccessFailedCount") + .ValueGeneratedOnAdd() + .HasColumnName("AccessFailedCount") + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeleterId") + .HasColumnName("DeleterId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletionTime") + .HasColumnName("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("Email") + .IsRequired() + .HasColumnName("Email") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("EmailConfirmed") + .ValueGeneratedOnAdd() + .HasColumnName("EmailConfirmed") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnName("IsDeleted") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("IsExternal") + .ValueGeneratedOnAdd() + .HasColumnName("IsExternal") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModificationTime") + .HasColumnName("LastModificationTime") + .HasColumnType("datetime2"); + + b.Property("LastModifierId") + .HasColumnName("LastModifierId") + .HasColumnType("uniqueidentifier"); + + b.Property("LockoutEnabled") + .ValueGeneratedOnAdd() + .HasColumnName("LockoutEnabled") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("Name") + .HasColumnName("Name") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("NormalizedEmail") + .IsRequired() + .HasColumnName("NormalizedEmail") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .IsRequired() + .HasColumnName("NormalizedUserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("PasswordHash") + .HasColumnName("PasswordHash") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("PhoneNumber") + .HasColumnName("PhoneNumber") + .HasColumnType("nvarchar(16)") + .HasMaxLength(16); + + b.Property("PhoneNumberConfirmed") + .ValueGeneratedOnAdd() + .HasColumnName("PhoneNumberConfirmed") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("SecurityStamp") + .IsRequired() + .HasColumnName("SecurityStamp") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("Surname") + .HasColumnName("Surname") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("TwoFactorEnabled") + .ValueGeneratedOnAdd() + .HasColumnName("TwoFactorEnabled") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("UserName") + .IsRequired() + .HasColumnName("UserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("Email"); + + b.HasIndex("NormalizedEmail"); + + b.HasIndex("NormalizedUserName"); + + b.HasIndex("UserName"); + + b.ToTable("AbpUsers"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("ClaimType") + .IsRequired() + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(1024)") + .HasMaxLength(1024); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AbpUserClaims"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserLogin", b => + { + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("ProviderKey") + .IsRequired() + .HasColumnType("nvarchar(196)") + .HasMaxLength(196); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("UserId", "LoginProvider"); + + b.HasIndex("LoginProvider", "ProviderKey"); + + b.ToTable("AbpUserLogins"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserOrganizationUnit", b => + { + b.Property("OrganizationUnitId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("OrganizationUnitId", "UserId"); + + b.HasIndex("UserId", "OrganizationUnitId"); + + b.ToTable("AbpUserOrganizationUnits"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId", "UserId"); + + b.ToTable("AbpUserRoles"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("Name") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AbpUserTokens"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.OrganizationUnit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Code") + .IsRequired() + .HasColumnName("Code") + .HasColumnType("nvarchar(95)") + .HasMaxLength(95); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeleterId") + .HasColumnName("DeleterId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletionTime") + .HasColumnName("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .IsRequired() + .HasColumnName("DisplayName") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnName("IsDeleted") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModificationTime") + .HasColumnName("LastModificationTime") + .HasColumnType("datetime2"); + + b.Property("LastModifierId") + .HasColumnName("LastModifierId") + .HasColumnType("uniqueidentifier"); + + b.Property("ParentId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("Code"); + + b.HasIndex("ParentId"); + + b.ToTable("AbpOrganizationUnits"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.OrganizationUnitRole", b => + { + b.Property("OrganizationUnitId") + .HasColumnType("uniqueidentifier"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("OrganizationUnitId", "RoleId"); + + b.HasIndex("RoleId", "OrganizationUnitId"); + + b.ToTable("AbpOrganizationUnitRoles"); + }); + + modelBuilder.Entity("Volo.Abp.PermissionManagement.PermissionGrant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("ProviderKey") + .IsRequired() + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ProviderName") + .IsRequired() + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("Name", "ProviderName", "ProviderKey"); + + b.ToTable("AbpPermissionGrants"); + }); + + modelBuilder.Entity("Volo.Abp.SettingManagement.Setting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ProviderName") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("Value") + .IsRequired() + .HasColumnType("nvarchar(2048)") + .HasMaxLength(2048); + + b.HasKey("Id"); + + b.HasIndex("Name", "ProviderName", "ProviderKey"); + + b.ToTable("AbpSettings"); + }); + + modelBuilder.Entity("Volo.Blogging.Blogs.Blog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeleterId") + .HasColumnName("DeleterId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletionTime") + .HasColumnName("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnName("Description") + .HasColumnType("nvarchar(1024)") + .HasMaxLength(1024); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnName("IsDeleted") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModificationTime") + .HasColumnName("LastModificationTime") + .HasColumnType("datetime2"); + + b.Property("LastModifierId") + .HasColumnName("LastModifierId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnName("Name") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("ShortName") + .IsRequired() + .HasColumnName("ShortName") + .HasColumnType("nvarchar(32)") + .HasMaxLength(32); + + b.HasKey("Id"); + + b.ToTable("BlgBlogs"); + }); + + modelBuilder.Entity("Volo.Blogging.Comments.Comment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeleterId") + .HasColumnName("DeleterId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletionTime") + .HasColumnName("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnName("IsDeleted") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModificationTime") + .HasColumnName("LastModificationTime") + .HasColumnType("datetime2"); + + b.Property("LastModifierId") + .HasColumnName("LastModifierId") + .HasColumnType("uniqueidentifier"); + + b.Property("PostId") + .HasColumnName("PostId") + .HasColumnType("uniqueidentifier"); + + b.Property("RepliedCommentId") + .HasColumnName("RepliedCommentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Text") + .IsRequired() + .HasColumnName("Text") + .HasColumnType("nvarchar(1024)") + .HasMaxLength(1024); + + b.HasKey("Id"); + + b.HasIndex("PostId"); + + b.HasIndex("RepliedCommentId"); + + b.ToTable("BlgComments"); + }); + + modelBuilder.Entity("Volo.Blogging.Posts.Post", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BlogId") + .HasColumnName("BlogId") + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("Content") + .HasColumnName("Content") + .HasColumnType("nvarchar(max)") + .HasMaxLength(1048576); + + b.Property("CoverImage") + .IsRequired() + .HasColumnName("CoverImage") + .HasColumnType("nvarchar(max)"); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeleterId") + .HasColumnName("DeleterId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletionTime") + .HasColumnName("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnName("Description") + .HasColumnType("nvarchar(1000)") + .HasMaxLength(1000); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnName("IsDeleted") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModificationTime") + .HasColumnName("LastModificationTime") + .HasColumnType("datetime2"); + + b.Property("LastModifierId") + .HasColumnName("LastModifierId") + .HasColumnType("uniqueidentifier"); + + b.Property("ReadCount") + .HasColumnType("int"); + + b.Property("Title") + .IsRequired() + .HasColumnName("Title") + .HasColumnType("nvarchar(512)") + .HasMaxLength(512); + + b.Property("Url") + .IsRequired() + .HasColumnName("Url") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.HasKey("Id"); + + b.HasIndex("BlogId"); + + b.ToTable("BlgPosts"); + }); + + modelBuilder.Entity("Volo.Blogging.Posts.PostTag", b => + { + b.Property("PostId") + .HasColumnName("PostId") + .HasColumnType("uniqueidentifier"); + + b.Property("TagId") + .HasColumnName("TagId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("PostId", "TagId"); + + b.HasIndex("TagId"); + + b.ToTable("BlgPostTags"); + }); + + modelBuilder.Entity("Volo.Blogging.Tagging.Tag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BlogId") + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeleterId") + .HasColumnName("DeleterId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletionTime") + .HasColumnName("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnName("Description") + .HasColumnType("nvarchar(512)") + .HasMaxLength(512); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnName("IsDeleted") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModificationTime") + .HasColumnName("LastModificationTime") + .HasColumnType("datetime2"); + + b.Property("LastModifierId") + .HasColumnName("LastModifierId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnName("Name") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("UsageCount") + .HasColumnName("UsageCount") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("BlgTags"); + }); + + modelBuilder.Entity("Volo.Blogging.Users.BlogUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("Email") + .IsRequired() + .HasColumnName("Email") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("EmailConfirmed") + .ValueGeneratedOnAdd() + .HasColumnName("EmailConfirmed") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasColumnName("Name") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("PhoneNumber") + .HasColumnName("PhoneNumber") + .HasColumnType("nvarchar(16)") + .HasMaxLength(16); + + b.Property("PhoneNumberConfirmed") + .ValueGeneratedOnAdd() + .HasColumnName("PhoneNumberConfirmed") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("Surname") + .HasColumnName("Surname") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserName") + .IsRequired() + .HasColumnName("UserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.ToTable("BlgUsers"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityRoleClaim", b => + { + b.HasOne("Volo.Abp.Identity.IdentityRole", null) + .WithMany("Claims") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserClaim", b => + { + b.HasOne("Volo.Abp.Identity.IdentityUser", null) + .WithMany("Claims") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserLogin", b => + { + b.HasOne("Volo.Abp.Identity.IdentityUser", null) + .WithMany("Logins") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserOrganizationUnit", b => + { + b.HasOne("Volo.Abp.Identity.OrganizationUnit", null) + .WithMany() + .HasForeignKey("OrganizationUnitId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Volo.Abp.Identity.IdentityUser", null) + .WithMany("OrganizationUnits") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserRole", b => + { + b.HasOne("Volo.Abp.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Volo.Abp.Identity.IdentityUser", null) + .WithMany("Roles") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserToken", b => + { + b.HasOne("Volo.Abp.Identity.IdentityUser", null) + .WithMany("Tokens") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.OrganizationUnit", b => + { + b.HasOne("Volo.Abp.Identity.OrganizationUnit", null) + .WithMany() + .HasForeignKey("ParentId"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.OrganizationUnitRole", b => + { + b.HasOne("Volo.Abp.Identity.OrganizationUnit", null) + .WithMany("Roles") + .HasForeignKey("OrganizationUnitId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Volo.Abp.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Blogging.Comments.Comment", b => + { + b.HasOne("Volo.Blogging.Posts.Post", null) + .WithMany() + .HasForeignKey("PostId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Volo.Blogging.Comments.Comment", null) + .WithMany() + .HasForeignKey("RepliedCommentId"); + }); + + modelBuilder.Entity("Volo.Blogging.Posts.Post", b => + { + b.HasOne("Volo.Blogging.Blogs.Blog", null) + .WithMany() + .HasForeignKey("BlogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Blogging.Posts.PostTag", b => + { + b.HasOne("Volo.Blogging.Posts.Post", null) + .WithMany("Tags") + .HasForeignKey("PostId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Volo.Blogging.Tagging.Tag", null) + .WithMany() + .HasForeignKey("TagId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/20200810022522_IdentityUserIsExternal.cs b/modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/20200810022522_IdentityUserIsExternal.cs new file mode 100644 index 0000000000..ee2e259b8a --- /dev/null +++ b/modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/20200810022522_IdentityUserIsExternal.cs @@ -0,0 +1,23 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations +{ + public partial class IdentityUserIsExternal : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "IsExternal", + table: "AbpUsers", + nullable: false, + defaultValue: false); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "IsExternal", + table: "AbpUsers"); + } + } +} diff --git a/modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/BloggingTestAppDbContextModelSnapshot.cs b/modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/BloggingTestAppDbContextModelSnapshot.cs index 00e9389e8f..47647bb25e 100644 --- a/modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/BloggingTestAppDbContextModelSnapshot.cs +++ b/modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/BloggingTestAppDbContextModelSnapshot.cs @@ -4,6 +4,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Volo.Abp.EntityFrameworkCore; using Volo.BloggingTestApp.EntityFrameworkCore; namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations @@ -15,7 +16,8 @@ namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "3.1.2") + .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) + .HasAnnotation("ProductVersion", "3.1.6") .HasAnnotation("Relational:MaxIdentifierLength", 128) .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); @@ -27,10 +29,9 @@ namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations b.Property("ConcurrencyStamp") .IsConcurrencyToken() - .IsRequired() .HasColumnName("ConcurrencyStamp") - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); b.Property("Description") .HasColumnType("nvarchar(256)") @@ -75,10 +76,9 @@ namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations b.Property("ConcurrencyStamp") .IsConcurrencyToken() - .IsRequired() .HasColumnName("ConcurrencyStamp") - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); b.Property("ExtraProperties") .HasColumnName("ExtraProperties") @@ -145,6 +145,81 @@ namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations b.ToTable("AbpRoleClaims"); }); + modelBuilder.Entity("Volo.Abp.Identity.IdentitySecurityLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Action") + .HasColumnType("nvarchar(96)") + .HasMaxLength(96); + + b.Property("ApplicationName") + .HasColumnType("nvarchar(96)") + .HasMaxLength(96); + + b.Property("BrowserInfo") + .HasColumnType("nvarchar(512)") + .HasMaxLength(512); + + b.Property("ClientId") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ClientIpAddress") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CorrelationId") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("CreationTime") + .HasColumnType("datetime2"); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("Identity") + .HasColumnType("nvarchar(96)") + .HasMaxLength(96); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantName") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Action"); + + b.HasIndex("TenantId", "ApplicationName"); + + b.HasIndex("TenantId", "Identity"); + + b.HasIndex("TenantId", "UserId"); + + b.ToTable("AbpSecurityLogs"); + }); + modelBuilder.Entity("Volo.Abp.Identity.IdentityUser", b => { b.Property("Id") @@ -160,7 +235,8 @@ namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations b.Property("ConcurrencyStamp") .IsConcurrencyToken() .HasColumnName("ConcurrencyStamp") - .HasColumnType("nvarchar(max)"); + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); b.Property("CreationTime") .HasColumnName("CreationTime") @@ -200,6 +276,12 @@ namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations .HasColumnType("bit") .HasDefaultValue(false); + b.Property("IsExternal") + .ValueGeneratedOnAdd() + .HasColumnName("IsExternal") + .HasColumnType("bit") + .HasDefaultValue(false); + b.Property("LastModificationTime") .HasColumnName("LastModificationTime") .HasColumnType("datetime2"); @@ -433,7 +515,8 @@ namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations b.Property("ConcurrencyStamp") .IsConcurrencyToken() .HasColumnName("ConcurrencyStamp") - .HasColumnType("nvarchar(max)"); + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); b.Property("CreationTime") .HasColumnName("CreationTime") @@ -590,7 +673,8 @@ namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations b.Property("ConcurrencyStamp") .IsConcurrencyToken() .HasColumnName("ConcurrencyStamp") - .HasColumnType("nvarchar(max)"); + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); b.Property("CreationTime") .HasColumnName("CreationTime") @@ -657,7 +741,8 @@ namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations b.Property("ConcurrencyStamp") .IsConcurrencyToken() .HasColumnName("ConcurrencyStamp") - .HasColumnType("nvarchar(max)"); + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); b.Property("CreationTime") .HasColumnName("CreationTime") @@ -729,7 +814,8 @@ namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations b.Property("ConcurrencyStamp") .IsConcurrencyToken() .HasColumnName("ConcurrencyStamp") - .HasColumnType("nvarchar(max)"); + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); b.Property("Content") .HasColumnName("Content") @@ -839,7 +925,8 @@ namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations b.Property("ConcurrencyStamp") .IsConcurrencyToken() .HasColumnName("ConcurrencyStamp") - .HasColumnType("nvarchar(max)"); + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); b.Property("CreationTime") .HasColumnName("CreationTime") @@ -904,7 +991,8 @@ namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations b.Property("ConcurrencyStamp") .IsConcurrencyToken() .HasColumnName("ConcurrencyStamp") - .HasColumnType("nvarchar(max)"); + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); b.Property("Email") .IsRequired() diff --git a/modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Volo.BloggingTestApp.EntityFrameworkCore.csproj b/modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Volo.BloggingTestApp.EntityFrameworkCore.csproj index ad5f9321ff..5d3175a12a 100644 --- a/modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Volo.BloggingTestApp.EntityFrameworkCore.csproj +++ b/modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Volo.BloggingTestApp.EntityFrameworkCore.csproj @@ -15,8 +15,8 @@ - - + + diff --git a/modules/blogging/app/Volo.BloggingTestApp/BloggingTestAppModule.cs b/modules/blogging/app/Volo.BloggingTestApp/BloggingTestAppModule.cs index 3d37192145..0cbdeaf6fc 100644 --- a/modules/blogging/app/Volo.BloggingTestApp/BloggingTestAppModule.cs +++ b/modules/blogging/app/Volo.BloggingTestApp/BloggingTestAppModule.cs @@ -1,4 +1,4 @@ -//#define MONGODB +//#define MONGODB using System.Collections.Generic; using System.Globalization; @@ -14,6 +14,7 @@ using Microsoft.Extensions.Options; using Microsoft.OpenApi.Models; using Swashbuckle.AspNetCore.Swagger; using Volo.Abp; +using Volo.Abp.Account; using Volo.Abp.Account.Web; using Volo.Abp.AspNetCore.Mvc.UI; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap; @@ -51,6 +52,7 @@ namespace Volo.BloggingTestApp typeof(BloggingTestAppEntityFrameworkCoreModule), #endif typeof(AbpAccountWebModule), + typeof(AbpAccountApplicationModule), typeof(AbpIdentityWebModule), typeof(AbpIdentityApplicationModule), typeof(AbpPermissionManagementDomainIdentityModule), diff --git a/modules/blogging/app/Volo.BloggingTestApp/Volo.BloggingTestApp.csproj b/modules/blogging/app/Volo.BloggingTestApp/Volo.BloggingTestApp.csproj index 70497b3a37..4fb4e461af 100644 --- a/modules/blogging/app/Volo.BloggingTestApp/Volo.BloggingTestApp.csproj +++ b/modules/blogging/app/Volo.BloggingTestApp/Volo.BloggingTestApp.csproj @@ -33,6 +33,7 @@ + diff --git a/modules/blogging/app/Volo.BloggingTestApp/package.json b/modules/blogging/app/Volo.BloggingTestApp/package.json index 9d3c620dd3..63cfac4070 100644 --- a/modules/blogging/app/Volo.BloggingTestApp/package.json +++ b/modules/blogging/app/Volo.BloggingTestApp/package.json @@ -3,7 +3,7 @@ "name": "volo.blogtestapp", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.basic": "^3.0.4", - "@abp/blogging": "^3.0.4" + "@abp/aspnetcore.mvc.ui.theme.basic": "^3.0.5", + "@abp/blogging": "^3.0.5" } } \ No newline at end of file diff --git a/modules/blogging/app/Volo.BloggingTestApp/yarn.lock b/modules/blogging/app/Volo.BloggingTestApp/yarn.lock index 3ddac913ca..fcd9dbcd00 100644 --- a/modules/blogging/app/Volo.BloggingTestApp/yarn.lock +++ b/modules/blogging/app/Volo.BloggingTestApp/yarn.lock @@ -2,37 +2,37 @@ # yarn lockfile v1 -"@abp/aspnetcore.mvc.ui.theme.basic@^3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-3.0.4.tgz#c58e3e06ba78279875cf2b5f96d34b83ce09f6df" - integrity sha512-tKKqVRKpwN7P4Mn+he3bcz4Qbt8bkZ++wk6iOujd6HLTkBNa0uKyM4L6+iCn1Hi62S6HeK2Xytx+b+IYeIlsuw== - dependencies: - "@abp/aspnetcore.mvc.ui.theme.shared" "~3.0.4" - -"@abp/aspnetcore.mvc.ui.theme.shared@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-3.0.4.tgz#50db612b6492d36c63dd9042000a9fb00fecec54" - integrity sha512-imd2T5XXkNvGQyXV/A9hA80qSeurKaUcW6HDkte8Y2fs4QfvkjgzWXi7neqF6F+HVbYYPg3rIBWC6QF1OXGn2w== - dependencies: - "@abp/aspnetcore.mvc.ui" "~3.0.4" - "@abp/bootstrap" "~3.0.4" - "@abp/bootstrap-datepicker" "~3.0.4" - "@abp/datatables.net-bs4" "~3.0.4" - "@abp/font-awesome" "~3.0.4" - "@abp/jquery-form" "~3.0.4" - "@abp/jquery-validation-unobtrusive" "~3.0.4" - "@abp/lodash" "~3.0.4" - "@abp/luxon" "~3.0.4" - "@abp/malihu-custom-scrollbar-plugin" "~3.0.4" - "@abp/select2" "~3.0.4" - "@abp/sweetalert" "~3.0.4" - "@abp/timeago" "~3.0.4" - "@abp/toastr" "~3.0.4" - -"@abp/aspnetcore.mvc.ui@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-3.0.4.tgz#4e4344d95a98a60c53037b351cb915d8ff7a7dcb" - integrity sha512-p7KI5CKkpD4EYzG4fCtygrtZhwMu7Z+IZFGbdqXelLbI5ACkbJyrn+N+9trPfS9papymeXY4p1E3v6xOH7rSHQ== +"@abp/aspnetcore.mvc.ui.theme.basic@^3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-3.0.5.tgz#c146f20226522f0305cc0fa31cfcf9d39be78901" + integrity sha512-ptNhD4xV2wSLW7vQ5KUgaRiH5Ov1OscZW2ZEoivnt3kqgtIC3DGw+SiE9vjFCvyY83fjRkIlW2PIfQxSySbteQ== + dependencies: + "@abp/aspnetcore.mvc.ui.theme.shared" "~3.0.5" + +"@abp/aspnetcore.mvc.ui.theme.shared@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-3.0.5.tgz#5b370902cf229963f9aa10dd64482d57299dde76" + integrity sha512-iobAH39xrCdnqdaABaiYZg4lqNSOwPkMCLnyc+ytxOMbu/YnNKahXPmRhgd8dR/l+iQY1+6T4SWfLdGswi3gAg== + dependencies: + "@abp/aspnetcore.mvc.ui" "~3.0.5" + "@abp/bootstrap" "~3.0.5" + "@abp/bootstrap-datepicker" "~3.0.5" + "@abp/datatables.net-bs4" "~3.0.5" + "@abp/font-awesome" "~3.0.5" + "@abp/jquery-form" "~3.0.5" + "@abp/jquery-validation-unobtrusive" "~3.0.5" + "@abp/lodash" "~3.0.5" + "@abp/luxon" "~3.0.5" + "@abp/malihu-custom-scrollbar-plugin" "~3.0.5" + "@abp/select2" "~3.0.5" + "@abp/sweetalert" "~3.0.5" + "@abp/timeago" "~3.0.5" + "@abp/toastr" "~3.0.5" + +"@abp/aspnetcore.mvc.ui@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-3.0.5.tgz#a050eaee328c56a3730f5abbf25035adf612d6f0" + integrity sha512-/EOgjXTzR+I1OK9KA7L3yzQ2RGSDW97+n3cQrSdLAUF+tnRvMJS0nytDlSJSRK2hb8/clFMaofZHCC81aUkYUg== dependencies: ansi-colors "^4.1.1" extend-object "^1.0.0" @@ -41,213 +41,213 @@ path "^0.12.7" rimraf "^3.0.2" -"@abp/blogging@^3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/blogging/-/blogging-3.0.4.tgz#d309b18a410d047346c39ab3063cd95429ca693a" - integrity sha512-HjPmNgFuAI1PPB4Rn8q+CYZq77kTqdRByIbYHvM9NsLyjb4CKruJxwHIFQUEs2oaB1V0/1CtfQWOcJ8r6ciGfA== +"@abp/blogging@^3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/blogging/-/blogging-3.0.5.tgz#07d476d9d7bb20b52ccd122c8cf7c7c2bd3c1aa4" + integrity sha512-3qBS3EfBRrcGzgo8xaJXIVNu/qkuhBLYixPur69nUa8sDAsYI725mVIOfznyYyTAxPXB2EDOf4WA+NKiEINrqQ== dependencies: - "@abp/aspnetcore.mvc.ui.theme.shared" "~3.0.4" - "@abp/owl.carousel" "~3.0.4" - "@abp/prismjs" "~3.0.4" - "@abp/tui-editor" "~3.0.4" + "@abp/aspnetcore.mvc.ui.theme.shared" "~3.0.5" + "@abp/owl.carousel" "~3.0.5" + "@abp/prismjs" "~3.0.5" + "@abp/tui-editor" "~3.0.5" -"@abp/bootstrap-datepicker@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-3.0.4.tgz#d21963f158a4d348a0acdc46209e67378aae8e55" - integrity sha512-25e9qm39dj9AijIchQIdLlBiBM5/fhASkEuLKNt6M8S/e3qwKLwbRhX90DKMFvOyKdxxcK1gX7tDuGHyG9A8mA== +"@abp/bootstrap-datepicker@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-3.0.5.tgz#3a7fd86dbcadc31c11be5b2b31c8cf8ac3035540" + integrity sha512-vIY1wT4RHoJAPhVK11Q5MS71iZbwsmM/0VTBvvs04mjRSSi6xhdyRYkA+l+P49fnOwhZNaaK19XwQdVVScDuhA== dependencies: bootstrap-datepicker "^1.9.0" -"@abp/bootstrap@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-3.0.4.tgz#3b0e2b74dc174a04ce669de1b140df833eee6031" - integrity sha512-DwxL1fGYOwChPwmMpwUxCKiIdzTfRf/YDkOLUiwI8eRrpABBeuE2sSHKfks6Rqh9Wqo/nlWXvfccgDRqweqhsA== +"@abp/bootstrap@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-3.0.5.tgz#91f2c8fe6a57fc5789ae8fe9c206b2c159e55880" + integrity sha512-LdydeJIbw5k1k7FkWaI1lTectZeTXmEIoccJtj9Qx4N9yGt40wGsjz9SOvUdJ0IKAgntIWNQP8ohWiBPgzT7Xg== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" bootstrap "^4.5.0" bootstrap-v4-rtl "4.4.1-2" -"@abp/clipboard@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/clipboard/-/clipboard-3.0.4.tgz#17ff40e398de3c0ab5214e564d443ed52ac10630" - integrity sha512-vjjupRau7fgBxvQwaDrBHApP4Ce1x9XM7L64/c+A+oBny0SzuiSBGBYsjlFrJGmTHT/PhjoB1XQiBHrYYPXQfQ== +"@abp/clipboard@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/clipboard/-/clipboard-3.0.5.tgz#7ceadd21360db11c91582a532029bfe65d10a930" + integrity sha512-iq455C096L5D+b6IrWkMxA9mbpggBNZ1kWfCQ3rhIjo0qbOGrvdGB3uQ+BqRS9Ni9ku7vBb2COldouIC9KOp5A== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" clipboard "^2.0.6" -"@abp/codemirror@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/codemirror/-/codemirror-3.0.4.tgz#d223b9e44719844948025f1c47bd404923d9e40d" - integrity sha512-shv8bqnATrEg9K9P0m14eqlJCCSfSO57SCxYmF0DUH0wuykkWjFJ8gok2GijVZtA9FQDJbNbNXh2oSm4MCgRsA== +"@abp/codemirror@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/codemirror/-/codemirror-3.0.5.tgz#2d60ad50f7b50833b0032f3c2e8992a89f257aa9" + integrity sha512-RkbmRGmSCB2962rJdhp0NgJ+qEP4gG05wuSuCMsYJTU3U9IkhkqiJbzupUzAkHQTaCQwCOUeXoBLUkwCHUP2DA== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" codemirror "^5.54.0" -"@abp/core@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/core/-/core-3.0.4.tgz#281940525e280e9e3a1aa4cb4e8f64548ea026a3" - integrity sha512-ZPEH1fa5bf/f1ek9ZLQHx7u78iF6kRZ8Z2NBI36cNL13RyQbx485qJF+KXyqNlJoJAh0CAs0NsAJADU3utcDNA== +"@abp/core@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/core/-/core-3.0.5.tgz#9a7bd990d02edc8128a5be3233b6fbb5669b15b8" + integrity sha512-LdkSkZQfN4hugcGNxBkje0+eQomivwNFbnAkMFQbvCwYLwKB2yvk7GCemEetW1piqvyrUGz4jqhtunjoXps5JA== dependencies: - "@abp/utils" "^3.0.4" + "@abp/utils" "^3.0.5" -"@abp/datatables.net-bs4@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs4/-/datatables.net-bs4-3.0.4.tgz#e488336bd2c199146ea60464e0ba75aeb134957e" - integrity sha512-ID1XWKPxG9pv6mxBkku7JWvgXjTxJ1uShxmYv5kFYVuwOGMSvj8QVZInTHFcMdECopV56yjGPNOIFLeQj4Mw8Q== +"@abp/datatables.net-bs4@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs4/-/datatables.net-bs4-3.0.5.tgz#9b79935970d2d4074994240b5deb01dff5987c0a" + integrity sha512-jIWRBEG/NlhaE43i6Xzr18XJ5aTWA+WWslf7WgNOArG5slGBbH4lLLja4HswLP3NGdSmzIyHJR1p9XQ82nBkbQ== dependencies: - "@abp/datatables.net" "~3.0.4" + "@abp/datatables.net" "~3.0.5" datatables.net-bs4 "^1.10.21" -"@abp/datatables.net@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-3.0.4.tgz#7ef143b83143ad8d769be25296411a812a17b255" - integrity sha512-gdQwRuxO5fQWhG/DiUaf40DQ8dWIVGhOCyt6SZ8LQzq4uSaQqa0qrtTTTW2/aiee4MgJu2vJWYF5YCszIR/HhA== +"@abp/datatables.net@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-3.0.5.tgz#0a0a39e8fa54522bf8a09ce76ff3927207a79491" + integrity sha512-HClQ2tcZuWBAChf9oOJOtMP1zHv8jqloPEcw3iqbhS/tWZBw5VQ4TO+BVfZsqHDXZNnWSmR6/2xiKsK32nliew== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" datatables.net "^1.10.21" -"@abp/font-awesome@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-3.0.4.tgz#98f83d0bf8b5a2a4d988cb07f5cceda2e1bc1a09" - integrity sha512-AKhKCUXx8vdHIgyXUfbSLitfYor5dJbo4qMRRGqIJVSj+cyMkpgYbGjqr24GGijgZxdmOGL+s1b9iCUbIPgLNQ== +"@abp/font-awesome@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-3.0.5.tgz#62d58c34f0abf268e30bdf788dfbd3f45b19b412" + integrity sha512-/+31Vtw1S9vvuNy/rN/Q6awkgUaYNNYXESgfvqI0pqIkvlPgigvS8E68mHqGzsxyRqdZisI6JGd94AF+vJD7sQ== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" "@fortawesome/fontawesome-free" "^5.13.0" -"@abp/highlight.js@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/highlight.js/-/highlight.js-3.0.4.tgz#2f4cb60b7b621fd73f3eb92f70c3d8611ce07c2b" - integrity sha512-7iTDFgBBHn3w5MlhRylkV5to0+8cl0akVspe358zvIy9bdBv+t8Xgc8IomxKqA5EfjBFhR+Q/g0+DfoWLpL7xw== +"@abp/highlight.js@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/highlight.js/-/highlight.js-3.0.5.tgz#f222b54958bd842840db5ad8b32d82f1cd209302" + integrity sha512-VT9UfukzbhMsWhUZNc8b792fY04lqkuJL98QprU59No1Rb7+XUa1k9FMbt2kl12Je3jltY3+UrDl+vrdasFxKg== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" -"@abp/jquery-form@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-3.0.4.tgz#022eebbfe58b55e328984c0f119884c9464bc767" - integrity sha512-xDkk4rY0AmXBJE4qztzr2hZz+OVucVNWeagOEgeQHXJSKU/T+0kj0a1RxEHpyI24xBvEXAbe8PbByTbu0QXYew== +"@abp/jquery-form@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-3.0.5.tgz#9d2ea9b2509448ee163a163298a7a09480865f78" + integrity sha512-RKgvQciaMml33NvzA28aYyfNI+WQHRfKLFWvOvqc8qO4C3BvyrnwepuzZcnI/9UsELKLSLoFrfk3rJHhxnTJhg== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" jquery-form "^4.3.0" -"@abp/jquery-validation-unobtrusive@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-3.0.4.tgz#6cc942aadcdde33893e7a45c84aa022e033e4e8b" - integrity sha512-N9sza91HCyPvJo+d0RAfq4skae3TgAYg7KXhlwzRE2hqUQS+EICVARURWyxJW7s7Zu6+gxDTkXhBZRTW56deZA== +"@abp/jquery-validation-unobtrusive@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-3.0.5.tgz#b8912e1c4cfdd4fdff6ad4aa7ee8693b55a3be1f" + integrity sha512-kPifDA+1ErbeQBwn+BzwtWuTHXzBke/EMCmITuj59plM/60gZ1ZfVm8EzzTmKS7iLaVVeOqTKf0vEQTobNYffw== dependencies: - "@abp/jquery-validation" "~3.0.4" + "@abp/jquery-validation" "~3.0.5" jquery-validation-unobtrusive "^3.2.11" -"@abp/jquery-validation@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-3.0.4.tgz#3f96d1f83c75a7fc47e5e5e12c51dbc8da7582b1" - integrity sha512-2ljqas4YKU0PcwB0wQW2qhXejDHjW+N1XCxZDyCZ2wF9UxGlKBR8rFt76TJByXSotKE7n2hJRZaOh9ICZUow2g== +"@abp/jquery-validation@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-3.0.5.tgz#55e36b3c832a35447fac69af312dedf1dee22279" + integrity sha512-fdqxc5CJ6TpUAF8+NEsKCmXvvZiOPR5bzczcfvfovxLOjvHvdEO74XPGjsjlBNTdaKMhmNGZRK4JRLUz7fvTng== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" jquery-validation "^1.19.2" -"@abp/jquery@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-3.0.4.tgz#5f236d95cfae7f65012cd8778fd74490b7542442" - integrity sha512-g8N8Te5vf+8djdksfZoZ3tXaqlBC4Ra39pAWU/XerlLl64u+XmAX1s6yKRfC2kS8lf292GgiFYuNOkb77J6k4A== +"@abp/jquery@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-3.0.5.tgz#2e43ac19a8df6e1e0220d6d4991d33a93d890754" + integrity sha512-2d2l+smWKAVP4/b6GSO+8rmJ7pFXiUewpBDwkHm5qgkK6n6UGqIcK9lIgiS2YQ6rmL6qdtuNhhqtC9GF2TF2kQ== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" jquery "~3.5.1" -"@abp/lodash@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-3.0.4.tgz#ee60bc99f98b59fd9fc5ff4bbcf914a13faf209d" - integrity sha512-jxvv74Sm/fUKDBMylK4Ne/8GKm7GuHAdcLFtkoJQptdO+RjZpKJ86ULT+X5vbKskZaTj2fEmOR6l4hmfnBULTA== +"@abp/lodash@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-3.0.5.tgz#d5d9f14095d76239bc0e6f9804c24d1f92ffcab4" + integrity sha512-+B2vxfPwxoIIb8Ac8eHi6OgAn0qbjmPZuun7/es0/ouYYTbEhvhTAD20X3PGZlHrIYeDyS2ansuXm8gJI+/BzA== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" lodash "^4.17.15" -"@abp/luxon@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-3.0.4.tgz#aa1bf6a93d6254433101e50a205b276759969414" - integrity sha512-gX9dQJt9C6M1wmCSUIBoNr8CAt0jkUZFQ7LKKO/BoADGM/XFvY5vDbhL7zcwbwitgCOzmucoCxs/6rIxSOVlrg== +"@abp/luxon@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-3.0.5.tgz#083042c939ea58bf719e35114a6c8efb07b68688" + integrity sha512-0UVHO2CO89t34O35OA3JzWGWLBpg+e1NQXSfVtK90nmg/QmzGkHVM9m7Su0jjLNBt3N5EA5OPOhriot3bCKItw== dependencies: luxon "^1.24.1" -"@abp/malihu-custom-scrollbar-plugin@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-3.0.4.tgz#c54824b98363f4190cc6a37fcd421c2bfdc1e201" - integrity sha512-6LdSGctOr4ACtdohdSlb19vrFNJ5C0hy1VdbBEafHOBspXckvRVx4uJl2KfHmAC+bMg9apoK/wcHjVNdBHThYQ== +"@abp/malihu-custom-scrollbar-plugin@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-3.0.5.tgz#f908289927a63fd76fb8f60b00a37747d9edbe39" + integrity sha512-3GoXA8VtGyZWKiqaPCipPXYLLM7h2Y5Oecq5c5YlMQyW2akv7uHiQQF2GZW/nVsFu3HBbGTOWpKRRmVk3PEO9Q== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" malihu-custom-scrollbar-plugin "^3.1.5" -"@abp/markdown-it@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/markdown-it/-/markdown-it-3.0.4.tgz#8be97cc1a48add764c76049c6303291252c56b12" - integrity sha512-94HjUbRQ+/RZoG0/h0nYA8Kk6IqJUtVzwM8/rUWGi45kwkSk6N/mCv537QD4hivHS0sLDx2KgEve9Ka8WnIKbQ== +"@abp/markdown-it@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/markdown-it/-/markdown-it-3.0.5.tgz#43c0daf0dd30de3d0109980aa5493db6f72108c1" + integrity sha512-HMRv5B29fQ5nPz4wFlfTsJ3dACotNLHZUuQbOt8SifRkt9Va0t20j+IwIGhSYVc93ttYHjB0IuMk6G10mdBA1A== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" markdown-it "^11.0.0" -"@abp/owl.carousel@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/owl.carousel/-/owl.carousel-3.0.4.tgz#18b4f93d06ad3b0f22f9fa4073b757f54e559c93" - integrity sha512-yZjlZ28JCuYSJPtnfoXssn1PdinbLbB4/Rixi43b2RbE0pI3U6ZnUOd0TAOwZyS7sO0OLdlGp2F/MKbln8u53A== +"@abp/owl.carousel@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/owl.carousel/-/owl.carousel-3.0.5.tgz#d575d201a9c4853aa8578e6d8da396de60d6c961" + integrity sha512-fBzNaJPRnFc1VV5MBpRAGr3yZtkcbRHOOKlq74bkg9kvnqNa8dLISPRxHsBCtX+bxndWI3OfmN9Uliiq39TgSA== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" owl.carousel "^2.3.4" -"@abp/prismjs@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/prismjs/-/prismjs-3.0.4.tgz#9cd8113b33dfd1b349b12740a2f7462f88bf3841" - integrity sha512-D+RNAhUhOCXnBNZVOq9EQg883eRbPKlIpFsxeMW065XOm5g7iO0eJACvyqGC8mt5reHmtxApnfiGNlhT1dK+Eg== +"@abp/prismjs@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/prismjs/-/prismjs-3.0.5.tgz#81a6ef656f3d7a76f365eb0bcbf2d0d3013d6e72" + integrity sha512-vdSSa1+tXS5+HWIgQyjDy4yQG7Q1YyuHQnFU6zlDJCQlE+20PL6lYgPnqR7DQ+Cd4lhRb3oxvloImYjwiwSQvA== dependencies: - "@abp/clipboard" "~3.0.4" - "@abp/core" "~3.0.4" + "@abp/clipboard" "~3.0.5" + "@abp/core" "~3.0.5" prismjs "^1.20.0" -"@abp/select2@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-3.0.4.tgz#49db0cfbc9b08f6ae4949b2cb8bdef92d7d308dd" - integrity sha512-zKEgkWBFdGU2KiAMUKhucHqBfzDy2KNSefyqxpOiUd1L3PEKu+9o23HZRWzDupYltop+SO1Jw33CNoVAkKm4+A== +"@abp/select2@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-3.0.5.tgz#79d7754713c64a6549da520dc6867ef3e3dd0350" + integrity sha512-VbeSHdAdDy8exrjplHMMhLVV4oG7Gq9zBb5M52f0sIkA4DkXFhksXHcHNoOCfhaKAXpenGA03Z9GJewZ3MTpMw== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" select2 "^4.0.13" -"@abp/sweetalert@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/sweetalert/-/sweetalert-3.0.4.tgz#f85d11c5aabc6ceb509af1b8fd8d75bc3c74bd27" - integrity sha512-hO0qXUWxeMy/lfy3sb+5HvOmIPst6UvYJJGy3ZQR6DKEdkOktBX9sEDb7m6ij6xAIHqWIx1xLveul0oGteOG0Q== +"@abp/sweetalert@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/sweetalert/-/sweetalert-3.0.5.tgz#e3b92d23cf3633983980b6255bf63c43d422ceab" + integrity sha512-OUt4ANgj14GESfRG7+UqdONS8daOu+8x2OZDWv98hriuX0uCVBjwB2J0jRI/n653C68Mi0yHixGONDv9OLy/lg== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" sweetalert "^2.1.2" -"@abp/timeago@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-3.0.4.tgz#82d90a04f703a7db9c0acedcd2f3cea684e7ae18" - integrity sha512-7SbbqScTKCix25xWy5oLT0slJqUQsW6YgJgI0N41C3AsByRBXZeh5Gcep033gprTPmpgNY8JOWcEcCXySnYvfA== +"@abp/timeago@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-3.0.5.tgz#b191a7c43f3dc77645fe27273f2f6619f7d4eb31" + integrity sha512-QhvAOU4C+Qxh/gFuCSGZtLU4KwVhdv/jtK8ovWyqbGvQMCCIimqa8BFKIz0H5XWIJhHwwWblkpjICWgjWh6uXw== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" timeago "^1.6.7" -"@abp/toastr@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/toastr/-/toastr-3.0.4.tgz#884ab2343ddd59981c291c632e3b0630532f7e9c" - integrity sha512-NG0pVD8bdIXGpjPW9cozCzmyuAdnSLE4vDYLUReP1pZFp9B8SMJqpgPk9eFg0Y06aaHTVMKYBKRoq9xXS4w9Xg== +"@abp/toastr@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/toastr/-/toastr-3.0.5.tgz#fc83b678f5be3a22040d0421ad9db5b2eac9d4fc" + integrity sha512-qgi2DEu6FYpN/DYcOZrmXsAr1HqcIKMhBWeXgjfoYUIl/K5+OuUgbQ69D/jdc0jz8HU9a4Cw7Q8hhe4CkgssEw== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" toastr "^2.1.4" -"@abp/tui-editor@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/tui-editor/-/tui-editor-3.0.4.tgz#fe44e7e28bd9d3902fe2e77b4e78eb2aa2959846" - integrity sha512-+H03yYEtPbX+THnzdsW1j+XXSYMrKl8oLPIrrl71QkUW7mif+c2s6YHauJ2mqJ1Uc/7fEL/i1/nBkqU3XjQXPw== +"@abp/tui-editor@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/tui-editor/-/tui-editor-3.0.5.tgz#9eee7cb555793a9c01a6e0ce10e77bb46577857c" + integrity sha512-xwaBhwnyvoIbEKWMSrTs2JHIOf3JK6tsABfcxhTRHXQfr2VONOEjbaLaAruNOuLJy3m/7rOsGWTeT8lMWwf3Fw== dependencies: - "@abp/codemirror" "~3.0.4" - "@abp/highlight.js" "~3.0.4" - "@abp/jquery" "~3.0.4" - "@abp/markdown-it" "~3.0.4" + "@abp/codemirror" "~3.0.5" + "@abp/highlight.js" "~3.0.5" + "@abp/jquery" "~3.0.5" + "@abp/markdown-it" "~3.0.5" tui-editor "^1.4.10" -"@abp/utils@^3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-3.0.4.tgz#38068fa8f735e1889cf2f226128366825948814d" - integrity sha512-TnXRZIZcxv5+EctEPsZEi2cyRXbpl3A679wzQa6BACv+pM45cjGO1JR8P0VswKN4TVL0aQRMv26N6U/uie8sEQ== +"@abp/utils@^3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-3.0.5.tgz#66b3da953500e73ff1f23d1b41168cde8216ce49" + integrity sha512-X9V1xLIEazItaf93nFSA4p94WT6QK3yA4eIyvU7GyyEkkOQdaakNwPbRwy930sd+I+/oXnIqM1vDsJu9QDhgvg== dependencies: just-compare "^1.3.0" diff --git a/modules/blogging/src/Volo.Blogging.Admin.Web/Volo.Blogging.Admin.Web.csproj b/modules/blogging/src/Volo.Blogging.Admin.Web/Volo.Blogging.Admin.Web.csproj index 611e62b1ec..16520deaf6 100644 --- a/modules/blogging/src/Volo.Blogging.Admin.Web/Volo.Blogging.Admin.Web.csproj +++ b/modules/blogging/src/Volo.Blogging.Admin.Web/Volo.Blogging.Admin.Web.csproj @@ -24,9 +24,9 @@ - + - + diff --git a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo.Blogging.Domain.Shared.csproj b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo.Blogging.Domain.Shared.csproj index cf9c05e3b2..ca7dcca8a2 100644 --- a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo.Blogging.Domain.Shared.csproj +++ b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo.Blogging.Domain.Shared.csproj @@ -19,9 +19,9 @@ - + - + - + diff --git a/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Edit.cshtml b/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Edit.cshtml index ad00452e26..1aa239efd7 100644 --- a/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Edit.cshtml +++ b/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Edit.cshtml @@ -45,6 +45,7 @@
    + *
    diff --git a/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/New.cshtml b/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/New.cshtml index bcb35c9b54..d57fd1b772 100644 --- a/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/New.cshtml +++ b/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/New.cshtml @@ -45,6 +45,7 @@
    + *
    diff --git a/modules/blogging/src/Volo.Blogging.Web/Volo.Blogging.Web.csproj b/modules/blogging/src/Volo.Blogging.Web/Volo.Blogging.Web.csproj index 1c2ddac81f..511b2b5d39 100644 --- a/modules/blogging/src/Volo.Blogging.Web/Volo.Blogging.Web.csproj +++ b/modules/blogging/src/Volo.Blogging.Web/Volo.Blogging.Web.csproj @@ -24,9 +24,9 @@
    - + - + diff --git a/modules/client-simulation/demo/Volo.ClientSimulation.Demo/package.json b/modules/client-simulation/demo/Volo.ClientSimulation.Demo/package.json index 8c5602c2c7..c4410d5c13 100644 --- a/modules/client-simulation/demo/Volo.ClientSimulation.Demo/package.json +++ b/modules/client-simulation/demo/Volo.ClientSimulation.Demo/package.json @@ -3,6 +3,6 @@ "name": "client-simulation-web", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.basic": "^3.0.4" + "@abp/aspnetcore.mvc.ui.theme.basic": "^3.0.5" } } \ No newline at end of file diff --git a/modules/client-simulation/demo/Volo.ClientSimulation.Demo/yarn.lock b/modules/client-simulation/demo/Volo.ClientSimulation.Demo/yarn.lock index 72373bfe9d..2233b4f0cb 100644 --- a/modules/client-simulation/demo/Volo.ClientSimulation.Demo/yarn.lock +++ b/modules/client-simulation/demo/Volo.ClientSimulation.Demo/yarn.lock @@ -2,37 +2,37 @@ # yarn lockfile v1 -"@abp/aspnetcore.mvc.ui.theme.basic@^3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-3.0.4.tgz#c58e3e06ba78279875cf2b5f96d34b83ce09f6df" - integrity sha512-tKKqVRKpwN7P4Mn+he3bcz4Qbt8bkZ++wk6iOujd6HLTkBNa0uKyM4L6+iCn1Hi62S6HeK2Xytx+b+IYeIlsuw== - dependencies: - "@abp/aspnetcore.mvc.ui.theme.shared" "~3.0.4" - -"@abp/aspnetcore.mvc.ui.theme.shared@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-3.0.4.tgz#50db612b6492d36c63dd9042000a9fb00fecec54" - integrity sha512-imd2T5XXkNvGQyXV/A9hA80qSeurKaUcW6HDkte8Y2fs4QfvkjgzWXi7neqF6F+HVbYYPg3rIBWC6QF1OXGn2w== - dependencies: - "@abp/aspnetcore.mvc.ui" "~3.0.4" - "@abp/bootstrap" "~3.0.4" - "@abp/bootstrap-datepicker" "~3.0.4" - "@abp/datatables.net-bs4" "~3.0.4" - "@abp/font-awesome" "~3.0.4" - "@abp/jquery-form" "~3.0.4" - "@abp/jquery-validation-unobtrusive" "~3.0.4" - "@abp/lodash" "~3.0.4" - "@abp/luxon" "~3.0.4" - "@abp/malihu-custom-scrollbar-plugin" "~3.0.4" - "@abp/select2" "~3.0.4" - "@abp/sweetalert" "~3.0.4" - "@abp/timeago" "~3.0.4" - "@abp/toastr" "~3.0.4" - -"@abp/aspnetcore.mvc.ui@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-3.0.4.tgz#4e4344d95a98a60c53037b351cb915d8ff7a7dcb" - integrity sha512-p7KI5CKkpD4EYzG4fCtygrtZhwMu7Z+IZFGbdqXelLbI5ACkbJyrn+N+9trPfS9papymeXY4p1E3v6xOH7rSHQ== +"@abp/aspnetcore.mvc.ui.theme.basic@^3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-3.0.5.tgz#c146f20226522f0305cc0fa31cfcf9d39be78901" + integrity sha512-ptNhD4xV2wSLW7vQ5KUgaRiH5Ov1OscZW2ZEoivnt3kqgtIC3DGw+SiE9vjFCvyY83fjRkIlW2PIfQxSySbteQ== + dependencies: + "@abp/aspnetcore.mvc.ui.theme.shared" "~3.0.5" + +"@abp/aspnetcore.mvc.ui.theme.shared@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-3.0.5.tgz#5b370902cf229963f9aa10dd64482d57299dde76" + integrity sha512-iobAH39xrCdnqdaABaiYZg4lqNSOwPkMCLnyc+ytxOMbu/YnNKahXPmRhgd8dR/l+iQY1+6T4SWfLdGswi3gAg== + dependencies: + "@abp/aspnetcore.mvc.ui" "~3.0.5" + "@abp/bootstrap" "~3.0.5" + "@abp/bootstrap-datepicker" "~3.0.5" + "@abp/datatables.net-bs4" "~3.0.5" + "@abp/font-awesome" "~3.0.5" + "@abp/jquery-form" "~3.0.5" + "@abp/jquery-validation-unobtrusive" "~3.0.5" + "@abp/lodash" "~3.0.5" + "@abp/luxon" "~3.0.5" + "@abp/malihu-custom-scrollbar-plugin" "~3.0.5" + "@abp/select2" "~3.0.5" + "@abp/sweetalert" "~3.0.5" + "@abp/timeago" "~3.0.5" + "@abp/toastr" "~3.0.5" + +"@abp/aspnetcore.mvc.ui@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-3.0.5.tgz#a050eaee328c56a3730f5abbf25035adf612d6f0" + integrity sha512-/EOgjXTzR+I1OK9KA7L3yzQ2RGSDW97+n3cQrSdLAUF+tnRvMJS0nytDlSJSRK2hb8/clFMaofZHCC81aUkYUg== dependencies: ansi-colors "^4.1.1" extend-object "^1.0.0" @@ -41,144 +41,144 @@ path "^0.12.7" rimraf "^3.0.2" -"@abp/bootstrap-datepicker@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-3.0.4.tgz#d21963f158a4d348a0acdc46209e67378aae8e55" - integrity sha512-25e9qm39dj9AijIchQIdLlBiBM5/fhASkEuLKNt6M8S/e3qwKLwbRhX90DKMFvOyKdxxcK1gX7tDuGHyG9A8mA== +"@abp/bootstrap-datepicker@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-3.0.5.tgz#3a7fd86dbcadc31c11be5b2b31c8cf8ac3035540" + integrity sha512-vIY1wT4RHoJAPhVK11Q5MS71iZbwsmM/0VTBvvs04mjRSSi6xhdyRYkA+l+P49fnOwhZNaaK19XwQdVVScDuhA== dependencies: bootstrap-datepicker "^1.9.0" -"@abp/bootstrap@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-3.0.4.tgz#3b0e2b74dc174a04ce669de1b140df833eee6031" - integrity sha512-DwxL1fGYOwChPwmMpwUxCKiIdzTfRf/YDkOLUiwI8eRrpABBeuE2sSHKfks6Rqh9Wqo/nlWXvfccgDRqweqhsA== +"@abp/bootstrap@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-3.0.5.tgz#91f2c8fe6a57fc5789ae8fe9c206b2c159e55880" + integrity sha512-LdydeJIbw5k1k7FkWaI1lTectZeTXmEIoccJtj9Qx4N9yGt40wGsjz9SOvUdJ0IKAgntIWNQP8ohWiBPgzT7Xg== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" bootstrap "^4.5.0" bootstrap-v4-rtl "4.4.1-2" -"@abp/core@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/core/-/core-3.0.4.tgz#281940525e280e9e3a1aa4cb4e8f64548ea026a3" - integrity sha512-ZPEH1fa5bf/f1ek9ZLQHx7u78iF6kRZ8Z2NBI36cNL13RyQbx485qJF+KXyqNlJoJAh0CAs0NsAJADU3utcDNA== +"@abp/core@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/core/-/core-3.0.5.tgz#9a7bd990d02edc8128a5be3233b6fbb5669b15b8" + integrity sha512-LdkSkZQfN4hugcGNxBkje0+eQomivwNFbnAkMFQbvCwYLwKB2yvk7GCemEetW1piqvyrUGz4jqhtunjoXps5JA== dependencies: - "@abp/utils" "^3.0.4" + "@abp/utils" "^3.0.5" -"@abp/datatables.net-bs4@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs4/-/datatables.net-bs4-3.0.4.tgz#e488336bd2c199146ea60464e0ba75aeb134957e" - integrity sha512-ID1XWKPxG9pv6mxBkku7JWvgXjTxJ1uShxmYv5kFYVuwOGMSvj8QVZInTHFcMdECopV56yjGPNOIFLeQj4Mw8Q== +"@abp/datatables.net-bs4@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs4/-/datatables.net-bs4-3.0.5.tgz#9b79935970d2d4074994240b5deb01dff5987c0a" + integrity sha512-jIWRBEG/NlhaE43i6Xzr18XJ5aTWA+WWslf7WgNOArG5slGBbH4lLLja4HswLP3NGdSmzIyHJR1p9XQ82nBkbQ== dependencies: - "@abp/datatables.net" "~3.0.4" + "@abp/datatables.net" "~3.0.5" datatables.net-bs4 "^1.10.21" -"@abp/datatables.net@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-3.0.4.tgz#7ef143b83143ad8d769be25296411a812a17b255" - integrity sha512-gdQwRuxO5fQWhG/DiUaf40DQ8dWIVGhOCyt6SZ8LQzq4uSaQqa0qrtTTTW2/aiee4MgJu2vJWYF5YCszIR/HhA== +"@abp/datatables.net@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-3.0.5.tgz#0a0a39e8fa54522bf8a09ce76ff3927207a79491" + integrity sha512-HClQ2tcZuWBAChf9oOJOtMP1zHv8jqloPEcw3iqbhS/tWZBw5VQ4TO+BVfZsqHDXZNnWSmR6/2xiKsK32nliew== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" datatables.net "^1.10.21" -"@abp/font-awesome@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-3.0.4.tgz#98f83d0bf8b5a2a4d988cb07f5cceda2e1bc1a09" - integrity sha512-AKhKCUXx8vdHIgyXUfbSLitfYor5dJbo4qMRRGqIJVSj+cyMkpgYbGjqr24GGijgZxdmOGL+s1b9iCUbIPgLNQ== +"@abp/font-awesome@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-3.0.5.tgz#62d58c34f0abf268e30bdf788dfbd3f45b19b412" + integrity sha512-/+31Vtw1S9vvuNy/rN/Q6awkgUaYNNYXESgfvqI0pqIkvlPgigvS8E68mHqGzsxyRqdZisI6JGd94AF+vJD7sQ== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" "@fortawesome/fontawesome-free" "^5.13.0" -"@abp/jquery-form@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-3.0.4.tgz#022eebbfe58b55e328984c0f119884c9464bc767" - integrity sha512-xDkk4rY0AmXBJE4qztzr2hZz+OVucVNWeagOEgeQHXJSKU/T+0kj0a1RxEHpyI24xBvEXAbe8PbByTbu0QXYew== +"@abp/jquery-form@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-3.0.5.tgz#9d2ea9b2509448ee163a163298a7a09480865f78" + integrity sha512-RKgvQciaMml33NvzA28aYyfNI+WQHRfKLFWvOvqc8qO4C3BvyrnwepuzZcnI/9UsELKLSLoFrfk3rJHhxnTJhg== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" jquery-form "^4.3.0" -"@abp/jquery-validation-unobtrusive@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-3.0.4.tgz#6cc942aadcdde33893e7a45c84aa022e033e4e8b" - integrity sha512-N9sza91HCyPvJo+d0RAfq4skae3TgAYg7KXhlwzRE2hqUQS+EICVARURWyxJW7s7Zu6+gxDTkXhBZRTW56deZA== +"@abp/jquery-validation-unobtrusive@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-3.0.5.tgz#b8912e1c4cfdd4fdff6ad4aa7ee8693b55a3be1f" + integrity sha512-kPifDA+1ErbeQBwn+BzwtWuTHXzBke/EMCmITuj59plM/60gZ1ZfVm8EzzTmKS7iLaVVeOqTKf0vEQTobNYffw== dependencies: - "@abp/jquery-validation" "~3.0.4" + "@abp/jquery-validation" "~3.0.5" jquery-validation-unobtrusive "^3.2.11" -"@abp/jquery-validation@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-3.0.4.tgz#3f96d1f83c75a7fc47e5e5e12c51dbc8da7582b1" - integrity sha512-2ljqas4YKU0PcwB0wQW2qhXejDHjW+N1XCxZDyCZ2wF9UxGlKBR8rFt76TJByXSotKE7n2hJRZaOh9ICZUow2g== +"@abp/jquery-validation@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-3.0.5.tgz#55e36b3c832a35447fac69af312dedf1dee22279" + integrity sha512-fdqxc5CJ6TpUAF8+NEsKCmXvvZiOPR5bzczcfvfovxLOjvHvdEO74XPGjsjlBNTdaKMhmNGZRK4JRLUz7fvTng== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" jquery-validation "^1.19.2" -"@abp/jquery@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-3.0.4.tgz#5f236d95cfae7f65012cd8778fd74490b7542442" - integrity sha512-g8N8Te5vf+8djdksfZoZ3tXaqlBC4Ra39pAWU/XerlLl64u+XmAX1s6yKRfC2kS8lf292GgiFYuNOkb77J6k4A== +"@abp/jquery@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-3.0.5.tgz#2e43ac19a8df6e1e0220d6d4991d33a93d890754" + integrity sha512-2d2l+smWKAVP4/b6GSO+8rmJ7pFXiUewpBDwkHm5qgkK6n6UGqIcK9lIgiS2YQ6rmL6qdtuNhhqtC9GF2TF2kQ== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" jquery "~3.5.1" -"@abp/lodash@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-3.0.4.tgz#ee60bc99f98b59fd9fc5ff4bbcf914a13faf209d" - integrity sha512-jxvv74Sm/fUKDBMylK4Ne/8GKm7GuHAdcLFtkoJQptdO+RjZpKJ86ULT+X5vbKskZaTj2fEmOR6l4hmfnBULTA== +"@abp/lodash@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-3.0.5.tgz#d5d9f14095d76239bc0e6f9804c24d1f92ffcab4" + integrity sha512-+B2vxfPwxoIIb8Ac8eHi6OgAn0qbjmPZuun7/es0/ouYYTbEhvhTAD20X3PGZlHrIYeDyS2ansuXm8gJI+/BzA== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" lodash "^4.17.15" -"@abp/luxon@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-3.0.4.tgz#aa1bf6a93d6254433101e50a205b276759969414" - integrity sha512-gX9dQJt9C6M1wmCSUIBoNr8CAt0jkUZFQ7LKKO/BoADGM/XFvY5vDbhL7zcwbwitgCOzmucoCxs/6rIxSOVlrg== +"@abp/luxon@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-3.0.5.tgz#083042c939ea58bf719e35114a6c8efb07b68688" + integrity sha512-0UVHO2CO89t34O35OA3JzWGWLBpg+e1NQXSfVtK90nmg/QmzGkHVM9m7Su0jjLNBt3N5EA5OPOhriot3bCKItw== dependencies: luxon "^1.24.1" -"@abp/malihu-custom-scrollbar-plugin@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-3.0.4.tgz#c54824b98363f4190cc6a37fcd421c2bfdc1e201" - integrity sha512-6LdSGctOr4ACtdohdSlb19vrFNJ5C0hy1VdbBEafHOBspXckvRVx4uJl2KfHmAC+bMg9apoK/wcHjVNdBHThYQ== +"@abp/malihu-custom-scrollbar-plugin@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-3.0.5.tgz#f908289927a63fd76fb8f60b00a37747d9edbe39" + integrity sha512-3GoXA8VtGyZWKiqaPCipPXYLLM7h2Y5Oecq5c5YlMQyW2akv7uHiQQF2GZW/nVsFu3HBbGTOWpKRRmVk3PEO9Q== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" malihu-custom-scrollbar-plugin "^3.1.5" -"@abp/select2@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-3.0.4.tgz#49db0cfbc9b08f6ae4949b2cb8bdef92d7d308dd" - integrity sha512-zKEgkWBFdGU2KiAMUKhucHqBfzDy2KNSefyqxpOiUd1L3PEKu+9o23HZRWzDupYltop+SO1Jw33CNoVAkKm4+A== +"@abp/select2@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-3.0.5.tgz#79d7754713c64a6549da520dc6867ef3e3dd0350" + integrity sha512-VbeSHdAdDy8exrjplHMMhLVV4oG7Gq9zBb5M52f0sIkA4DkXFhksXHcHNoOCfhaKAXpenGA03Z9GJewZ3MTpMw== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" select2 "^4.0.13" -"@abp/sweetalert@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/sweetalert/-/sweetalert-3.0.4.tgz#f85d11c5aabc6ceb509af1b8fd8d75bc3c74bd27" - integrity sha512-hO0qXUWxeMy/lfy3sb+5HvOmIPst6UvYJJGy3ZQR6DKEdkOktBX9sEDb7m6ij6xAIHqWIx1xLveul0oGteOG0Q== +"@abp/sweetalert@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/sweetalert/-/sweetalert-3.0.5.tgz#e3b92d23cf3633983980b6255bf63c43d422ceab" + integrity sha512-OUt4ANgj14GESfRG7+UqdONS8daOu+8x2OZDWv98hriuX0uCVBjwB2J0jRI/n653C68Mi0yHixGONDv9OLy/lg== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" sweetalert "^2.1.2" -"@abp/timeago@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-3.0.4.tgz#82d90a04f703a7db9c0acedcd2f3cea684e7ae18" - integrity sha512-7SbbqScTKCix25xWy5oLT0slJqUQsW6YgJgI0N41C3AsByRBXZeh5Gcep033gprTPmpgNY8JOWcEcCXySnYvfA== +"@abp/timeago@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-3.0.5.tgz#b191a7c43f3dc77645fe27273f2f6619f7d4eb31" + integrity sha512-QhvAOU4C+Qxh/gFuCSGZtLU4KwVhdv/jtK8ovWyqbGvQMCCIimqa8BFKIz0H5XWIJhHwwWblkpjICWgjWh6uXw== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" timeago "^1.6.7" -"@abp/toastr@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/toastr/-/toastr-3.0.4.tgz#884ab2343ddd59981c291c632e3b0630532f7e9c" - integrity sha512-NG0pVD8bdIXGpjPW9cozCzmyuAdnSLE4vDYLUReP1pZFp9B8SMJqpgPk9eFg0Y06aaHTVMKYBKRoq9xXS4w9Xg== +"@abp/toastr@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/toastr/-/toastr-3.0.5.tgz#fc83b678f5be3a22040d0421ad9db5b2eac9d4fc" + integrity sha512-qgi2DEu6FYpN/DYcOZrmXsAr1HqcIKMhBWeXgjfoYUIl/K5+OuUgbQ69D/jdc0jz8HU9a4Cw7Q8hhe4CkgssEw== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" toastr "^2.1.4" -"@abp/utils@^3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-3.0.4.tgz#38068fa8f735e1889cf2f226128366825948814d" - integrity sha512-TnXRZIZcxv5+EctEPsZEi2cyRXbpl3A679wzQa6BACv+pM45cjGO1JR8P0VswKN4TVL0aQRMv26N6U/uie8sEQ== +"@abp/utils@^3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-3.0.5.tgz#66b3da953500e73ff1f23d1b41168cde8216ce49" + integrity sha512-X9V1xLIEazItaf93nFSA4p94WT6QK3yA4eIyvU7GyyEkkOQdaakNwPbRwy930sd+I+/oXnIqM1vDsJu9QDhgvg== dependencies: just-compare "^1.3.0" diff --git a/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/Volo.CmsKit.HttpApi.Host.csproj b/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/Volo.CmsKit.HttpApi.Host.csproj index 3fb091137d..2a0c5e57c1 100644 --- a/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/Volo.CmsKit.HttpApi.Host.csproj +++ b/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/Volo.CmsKit.HttpApi.Host.csproj @@ -14,8 +14,8 @@ - - + + diff --git a/modules/cms-kit/host/Volo.CmsKit.IdentityServer/Migrations/20200624023331_Initial.Designer.cs b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/Migrations/20200810022553_Initial.Designer.cs similarity index 99% rename from modules/cms-kit/host/Volo.CmsKit.IdentityServer/Migrations/20200624023331_Initial.Designer.cs rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/Migrations/20200810022553_Initial.Designer.cs index 406a3d3f68..aac1944355 100644 --- a/modules/cms-kit/host/Volo.CmsKit.IdentityServer/Migrations/20200624023331_Initial.Designer.cs +++ b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/Migrations/20200810022553_Initial.Designer.cs @@ -5,13 +5,13 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Volo.CmsKit.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore; +using Volo.CmsKit.EntityFrameworkCore; namespace Volo.CmsKit.Migrations { [DbContext(typeof(IdentityServerHostMigrationsDbContext))] - [Migration("20200624023331_Initial")] + [Migration("20200810022553_Initial")] partial class Initial { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -19,7 +19,7 @@ namespace Volo.CmsKit.Migrations #pragma warning disable 612, 618 modelBuilder .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) - .HasAnnotation("ProductVersion", "3.1.5") + .HasAnnotation("ProductVersion", "3.1.6") .HasAnnotation("Relational:MaxIdentifierLength", 128) .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); diff --git a/modules/cms-kit/host/Volo.CmsKit.IdentityServer/Migrations/20200624023331_Initial.cs b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/Migrations/20200810022553_Initial.cs similarity index 100% rename from modules/cms-kit/host/Volo.CmsKit.IdentityServer/Migrations/20200624023331_Initial.cs rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/Migrations/20200810022553_Initial.cs diff --git a/modules/cms-kit/host/Volo.CmsKit.IdentityServer/Migrations/IdentityServerHostMigrationsDbContextModelSnapshot.cs b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/Migrations/IdentityServerHostMigrationsDbContextModelSnapshot.cs index 1218617479..b4249161b0 100644 --- a/modules/cms-kit/host/Volo.CmsKit.IdentityServer/Migrations/IdentityServerHostMigrationsDbContextModelSnapshot.cs +++ b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/Migrations/IdentityServerHostMigrationsDbContextModelSnapshot.cs @@ -4,8 +4,8 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Volo.CmsKit.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore; +using Volo.CmsKit.EntityFrameworkCore; namespace Volo.CmsKit.Migrations { @@ -17,7 +17,7 @@ namespace Volo.CmsKit.Migrations #pragma warning disable 612, 618 modelBuilder .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) - .HasAnnotation("ProductVersion", "3.1.5") + .HasAnnotation("ProductVersion", "3.1.6") .HasAnnotation("Relational:MaxIdentifierLength", 128) .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); diff --git a/modules/cms-kit/host/Volo.CmsKit.IdentityServer/Volo.CmsKit.IdentityServer.csproj b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/Volo.CmsKit.IdentityServer.csproj index cc4b05cf3d..da90915bf2 100644 --- a/modules/cms-kit/host/Volo.CmsKit.IdentityServer/Volo.CmsKit.IdentityServer.csproj +++ b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/Volo.CmsKit.IdentityServer.csproj @@ -12,8 +12,8 @@ - - + + diff --git a/modules/cms-kit/host/Volo.CmsKit.Web.Host/Volo.CmsKit.Web.Host.csproj b/modules/cms-kit/host/Volo.CmsKit.Web.Host/Volo.CmsKit.Web.Host.csproj index a137c30124..46b83d5180 100644 --- a/modules/cms-kit/host/Volo.CmsKit.Web.Host/Volo.CmsKit.Web.Host.csproj +++ b/modules/cms-kit/host/Volo.CmsKit.Web.Host/Volo.CmsKit.Web.Host.csproj @@ -13,8 +13,8 @@ - - + + @@ -28,7 +28,7 @@ - + @@ -52,5 +52,5 @@ Always - + diff --git a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/CmsKitWebUnifiedModule.cs b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/CmsKitWebUnifiedModule.cs index 183469ce48..3579043302 100644 --- a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/CmsKitWebUnifiedModule.cs +++ b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/CmsKitWebUnifiedModule.cs @@ -34,6 +34,7 @@ using Volo.Abp.TenantManagement.Web; using Volo.Abp.Threading; using Volo.Abp.VirtualFileSystem; using Volo.CmsKit.Admin.Web; +using Volo.CmsKit.Public.Web; namespace Volo.CmsKit { @@ -112,6 +113,11 @@ namespace Volo.CmsKit { options.IsEnabled = MultiTenancyConsts.IsEnabled; }); + + Configure(options => + { + options.PublicCommentEntities.Add("publicQuote"); + }); } public override void OnApplicationInitialization(ApplicationInitializationContext context) diff --git a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200715191733_Added_UserReactions.cs b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200715191733_Added_UserReactions.cs deleted file mode 100644 index 6e2e270763..0000000000 --- a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200715191733_Added_UserReactions.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Volo.CmsKit.Migrations -{ - public partial class Added_UserReactions : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "CmsUserReactions", - columns: table => new - { - Id = table.Column(nullable: false), - EntityType = table.Column(maxLength: 64, nullable: false), - EntityId = table.Column(maxLength: 64, nullable: false), - ReactionName = table.Column(maxLength: 32, nullable: false), - CreationTime = table.Column(nullable: false), - UserId = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_CmsUserReactions", x => x.Id); - }); - - migrationBuilder.CreateIndex( - name: "IX_CmsUserReactions_EntityType_EntityId", - table: "CmsUserReactions", - columns: new[] { "EntityType", "EntityId" }); - - migrationBuilder.CreateIndex( - name: "IX_CmsUserReactions_UserId_EntityType_EntityId_ReactionName", - table: "CmsUserReactions", - columns: new[] { "UserId", "EntityType", "EntityId", "ReactionName" }); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "CmsUserReactions"); - } - } -} diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/20200624023340_Initial.Designer.cs b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200807070949_Reaction_Comment_Multitenancy.Designer.cs similarity index 87% rename from templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/20200624023340_Initial.Designer.cs rename to modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200807070949_Reaction_Comment_Multitenancy.Designer.cs index d93e2804a6..addf1e46c6 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/20200624023340_Initial.Designer.cs +++ b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200807070949_Reaction_Comment_Multitenancy.Designer.cs @@ -5,14 +5,14 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using MyCompanyName.MyProjectName.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore; +using Volo.CmsKit.EntityFrameworkCore; -namespace MyCompanyName.MyProjectName.Migrations +namespace Volo.CmsKit.Migrations { [DbContext(typeof(UnifiedDbContext))] - [Migration("20200624023340_Initial")] - partial class Initial + [Migration("20200807070949_Reaction_Comment_Multitenancy")] + partial class Reaction_Comment_Multitenancy { protected override void BuildTargetModel(ModelBuilder modelBuilder) { @@ -914,6 +914,157 @@ namespace MyCompanyName.MyProjectName.Migrations b.ToTable("AbpTenantConnectionStrings"); }); + modelBuilder.Entity("Volo.CmsKit.Comments.Comment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("EntityId") + .IsRequired() + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("EntityType") + .IsRequired() + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("RepliedCommentId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("Text") + .IsRequired() + .HasColumnType("nvarchar(512)") + .HasMaxLength(512); + + b.HasKey("Id"); + + b.HasIndex("RepliedCommentId"); + + b.HasIndex("EntityType", "EntityId"); + + b.ToTable("CmsComments"); + }); + + modelBuilder.Entity("Volo.CmsKit.Reactions.UserReaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("EntityId") + .IsRequired() + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("EntityType") + .IsRequired() + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ReactionName") + .IsRequired() + .HasColumnType("nvarchar(32)") + .HasMaxLength(32); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("EntityType", "EntityId"); + + b.HasIndex("CreatorId", "EntityType", "EntityId", "ReactionName"); + + b.ToTable("CmsUserReactions"); + }); + + modelBuilder.Entity("Volo.CmsKit.Users.CmsUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("Email") + .IsRequired() + .HasColumnName("Email") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("EmailConfirmed") + .ValueGeneratedOnAdd() + .HasColumnName("EmailConfirmed") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasColumnName("Name") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("PhoneNumber") + .HasColumnName("PhoneNumber") + .HasColumnType("nvarchar(16)") + .HasMaxLength(16); + + b.Property("PhoneNumberConfirmed") + .ValueGeneratedOnAdd() + .HasColumnName("PhoneNumberConfirmed") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("Surname") + .HasColumnName("Surname") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserName") + .IsRequired() + .HasColumnName("UserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.ToTable("CmsUsers"); + }); + modelBuilder.Entity("Volo.Abp.AuditLogging.AuditLogAction", b => { b.HasOne("Volo.Abp.AuditLogging.AuditLog", null) diff --git a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200807070949_Reaction_Comment_Multitenancy.cs b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200807070949_Reaction_Comment_Multitenancy.cs new file mode 100644 index 0000000000..42f880e3b4 --- /dev/null +++ b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200807070949_Reaction_Comment_Multitenancy.cs @@ -0,0 +1,32 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Volo.CmsKit.Migrations +{ + public partial class Reaction_Comment_Multitenancy : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "TenantId", + table: "CmsUserReactions", + nullable: true); + + migrationBuilder.AddColumn( + name: "TenantId", + table: "CmsComments", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "TenantId", + table: "CmsUserReactions"); + + migrationBuilder.DropColumn( + name: "TenantId", + table: "CmsComments"); + } + } +} diff --git a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200624023340_Initial.Designer.cs b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200810022644_Initial.Designer.cs similarity index 87% rename from modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200624023340_Initial.Designer.cs rename to modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200810022644_Initial.Designer.cs index d9a7210cad..a29b41671a 100644 --- a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200624023340_Initial.Designer.cs +++ b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200810022644_Initial.Designer.cs @@ -5,13 +5,13 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Volo.CmsKit.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore; +using Volo.CmsKit.EntityFrameworkCore; namespace Volo.CmsKit.Migrations { [DbContext(typeof(UnifiedDbContext))] - [Migration("20200624023340_Initial")] + [Migration("20200810022644_Initial")] partial class Initial { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -19,7 +19,7 @@ namespace Volo.CmsKit.Migrations #pragma warning disable 612, 618 modelBuilder .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) - .HasAnnotation("ProductVersion", "3.1.5") + .HasAnnotation("ProductVersion", "3.1.6") .HasAnnotation("Relational:MaxIdentifierLength", 128) .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); @@ -914,6 +914,149 @@ namespace Volo.CmsKit.Migrations b.ToTable("AbpTenantConnectionStrings"); }); + modelBuilder.Entity("Volo.CmsKit.Comments.Comment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("EntityId") + .IsRequired() + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("EntityType") + .IsRequired() + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("RepliedCommentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Text") + .IsRequired() + .HasColumnType("nvarchar(512)") + .HasMaxLength(512); + + b.HasKey("Id"); + + b.HasIndex("RepliedCommentId"); + + b.HasIndex("EntityType", "EntityId"); + + b.ToTable("CmsComments"); + }); + + modelBuilder.Entity("Volo.CmsKit.Reactions.UserReaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("EntityId") + .IsRequired() + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("EntityType") + .IsRequired() + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ReactionName") + .IsRequired() + .HasColumnType("nvarchar(32)") + .HasMaxLength(32); + + b.HasKey("Id"); + + b.HasIndex("EntityType", "EntityId"); + + b.HasIndex("CreatorId", "EntityType", "EntityId", "ReactionName"); + + b.ToTable("CmsUserReactions"); + }); + + modelBuilder.Entity("Volo.CmsKit.Users.CmsUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("Email") + .IsRequired() + .HasColumnName("Email") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("EmailConfirmed") + .ValueGeneratedOnAdd() + .HasColumnName("EmailConfirmed") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasColumnName("Name") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("PhoneNumber") + .HasColumnName("PhoneNumber") + .HasColumnType("nvarchar(16)") + .HasMaxLength(16); + + b.Property("PhoneNumberConfirmed") + .ValueGeneratedOnAdd() + .HasColumnName("PhoneNumberConfirmed") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("Surname") + .HasColumnName("Surname") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserName") + .IsRequired() + .HasColumnName("UserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.ToTable("CmsUsers"); + }); + modelBuilder.Entity("Volo.Abp.AuditLogging.AuditLogAction", b => { b.HasOne("Volo.Abp.AuditLogging.AuditLog", null) diff --git a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200624023340_Initial.cs b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200810022644_Initial.cs similarity index 88% rename from modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200624023340_Initial.cs rename to modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200810022644_Initial.cs index d749beb7b8..6f8b2a069c 100644 --- a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200624023340_Initial.cs +++ b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200810022644_Initial.cs @@ -195,6 +195,60 @@ namespace Volo.CmsKit.Migrations table.PrimaryKey("PK_AbpUsers", x => x.Id); }); + migrationBuilder.CreateTable( + name: "CmsComments", + columns: table => new + { + Id = table.Column(nullable: false), + EntityType = table.Column(maxLength: 64, nullable: false), + EntityId = table.Column(maxLength: 64, nullable: false), + Text = table.Column(maxLength: 512, nullable: false), + RepliedCommentId = table.Column(nullable: true), + CreatorId = table.Column(nullable: false), + CreationTime = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CmsComments", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CmsUserReactions", + columns: table => new + { + Id = table.Column(nullable: false), + EntityType = table.Column(maxLength: 64, nullable: false), + EntityId = table.Column(maxLength: 64, nullable: false), + ReactionName = table.Column(maxLength: 32, nullable: false), + CreatorId = table.Column(nullable: false), + CreationTime = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CmsUserReactions", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CmsUsers", + columns: table => new + { + Id = table.Column(nullable: false), + ExtraProperties = table.Column(nullable: true), + ConcurrencyStamp = table.Column(maxLength: 40, nullable: true), + TenantId = table.Column(nullable: true), + UserName = table.Column(maxLength: 256, nullable: false), + Email = table.Column(maxLength: 256, nullable: false), + Name = table.Column(maxLength: 64, nullable: true), + Surname = table.Column(maxLength: 64, nullable: true), + EmailConfirmed = table.Column(nullable: false, defaultValue: false), + PhoneNumber = table.Column(maxLength: 16, nullable: true), + PhoneNumberConfirmed = table.Column(nullable: false, defaultValue: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CmsUsers", x => x.Id); + }); + migrationBuilder.CreateTable( name: "AbpAuditLogActions", columns: table => new @@ -564,6 +618,26 @@ namespace Volo.CmsKit.Migrations name: "IX_AbpUsers_UserName", table: "AbpUsers", column: "UserName"); + + migrationBuilder.CreateIndex( + name: "IX_CmsComments_RepliedCommentId", + table: "CmsComments", + column: "RepliedCommentId"); + + migrationBuilder.CreateIndex( + name: "IX_CmsComments_EntityType_EntityId", + table: "CmsComments", + columns: new[] { "EntityType", "EntityId" }); + + migrationBuilder.CreateIndex( + name: "IX_CmsUserReactions_EntityType_EntityId", + table: "CmsUserReactions", + columns: new[] { "EntityType", "EntityId" }); + + migrationBuilder.CreateIndex( + name: "IX_CmsUserReactions_CreatorId_EntityType_EntityId_ReactionName", + table: "CmsUserReactions", + columns: new[] { "CreatorId", "EntityType", "EntityId", "ReactionName" }); } protected override void Down(MigrationBuilder migrationBuilder) @@ -607,6 +681,15 @@ namespace Volo.CmsKit.Migrations migrationBuilder.DropTable( name: "AbpUserTokens"); + migrationBuilder.DropTable( + name: "CmsComments"); + + migrationBuilder.DropTable( + name: "CmsUserReactions"); + + migrationBuilder.DropTable( + name: "CmsUsers"); + migrationBuilder.DropTable( name: "AbpEntityChanges"); diff --git a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/UnifiedDbContextModelSnapshot.cs b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/UnifiedDbContextModelSnapshot.cs index e905ac9e66..a0684b4eb6 100644 --- a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/UnifiedDbContextModelSnapshot.cs +++ b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/UnifiedDbContextModelSnapshot.cs @@ -17,7 +17,7 @@ namespace Volo.CmsKit.Migrations #pragma warning disable 612, 618 modelBuilder .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) - .HasAnnotation("ProductVersion", "3.1.5") + .HasAnnotation("ProductVersion", "3.1.6") .HasAnnotation("Relational:MaxIdentifierLength", 128) .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); @@ -912,6 +912,51 @@ namespace Volo.CmsKit.Migrations b.ToTable("AbpTenantConnectionStrings"); }); + modelBuilder.Entity("Volo.CmsKit.Comments.Comment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("EntityId") + .IsRequired() + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("EntityType") + .IsRequired() + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("RepliedCommentId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("Text") + .IsRequired() + .HasColumnType("nvarchar(512)") + .HasMaxLength(512); + + b.HasKey("Id"); + + b.HasIndex("RepliedCommentId"); + + b.HasIndex("EntityType", "EntityId"); + + b.ToTable("CmsComments"); + }); + modelBuilder.Entity("Volo.CmsKit.Reactions.UserReaction", b => { b.Property("Id") @@ -919,8 +964,13 @@ namespace Volo.CmsKit.Migrations .HasColumnType("uniqueidentifier"); b.Property("CreationTime") + .HasColumnName("CreationTime") .HasColumnType("datetime2"); + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + b.Property("EntityId") .IsRequired() .HasColumnType("nvarchar(64)") @@ -936,18 +986,83 @@ namespace Volo.CmsKit.Migrations .HasColumnType("nvarchar(32)") .HasMaxLength(32); - b.Property("UserId") + b.Property("TenantId") + .HasColumnName("TenantId") .HasColumnType("uniqueidentifier"); b.HasKey("Id"); b.HasIndex("EntityType", "EntityId"); - b.HasIndex("UserId", "EntityType", "EntityId", "ReactionName"); + b.HasIndex("CreatorId", "EntityType", "EntityId", "ReactionName"); b.ToTable("CmsUserReactions"); }); + modelBuilder.Entity("Volo.CmsKit.Users.CmsUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("Email") + .IsRequired() + .HasColumnName("Email") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("EmailConfirmed") + .ValueGeneratedOnAdd() + .HasColumnName("EmailConfirmed") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasColumnName("Name") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("PhoneNumber") + .HasColumnName("PhoneNumber") + .HasColumnType("nvarchar(16)") + .HasMaxLength(16); + + b.Property("PhoneNumberConfirmed") + .ValueGeneratedOnAdd() + .HasColumnName("PhoneNumberConfirmed") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("Surname") + .HasColumnName("Surname") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserName") + .IsRequired() + .HasColumnName("UserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.ToTable("CmsUsers"); + }); + modelBuilder.Entity("Volo.Abp.AuditLogging.AuditLogAction", b => { b.HasOne("Volo.Abp.AuditLogging.AuditLog", null) diff --git a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Pages/Index.cshtml b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Pages/Index.cshtml index 62ef15d4b7..5c6dabfd19 100644 --- a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Pages/Index.cshtml +++ b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Pages/Index.cshtml @@ -2,13 +2,12 @@ @using Localization.Resources.AbpUi @using Microsoft.Extensions.Localization @using Volo.CmsKit.Pages -@using Volo.CmsKit.Web.Pages.CmsKit.Shared.Components.ReactionSelection +@using Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Commenting +@using Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.ReactionSelection @model IndexModel @inject IStringLocalizer Localizer

    CMS Kit DEMO

    -

    Reactions

    - @@ -22,6 +21,8 @@ @await Component.InvokeAsync(typeof(ReactionSelectionViewComponent), new { entityType = "quote", entityId = "1" }) +
    + @await Component.InvokeAsync(typeof(CommentingViewComponent), new { entityType = "quote", entityId = "1" })
    @@ -38,5 +39,7 @@ @await Component.InvokeAsync(typeof(ReactionSelectionViewComponent), new { entityType = "quote", entityId = "2" }) +
    + @await Component.InvokeAsync(typeof(CommentingViewComponent), new { entityType = "quote", entityId = "2" })
    diff --git a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Pages/Public.cshtml b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Pages/Public.cshtml new file mode 100644 index 0000000000..1846666943 --- /dev/null +++ b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Pages/Public.cshtml @@ -0,0 +1,39 @@ +@page +@using Localization.Resources.AbpUi +@using Microsoft.Extensions.Localization +@using Volo.CmsKit.Pages +@using Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Commenting +@using Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.ReactionSelection +@model PublicModel +@inject IStringLocalizer Localizer +

    CMS Kit DEMO - Public

    + + + + +

    + Any fool can write code that a computer can understand. Good programmers write code that humans can understand. +

    +
    + Martin Fowler +
    +
    +
    + @await Component.InvokeAsync(typeof(ReactionSelectionViewComponent), new { entityType = "publicQuote", entityId = "1" }) +
    + @await Component.InvokeAsync(typeof(CommentingViewComponent), new { entityType = "publicQuote", entityId = "1", loginUrl = "Account/Login?ReturnUrl=%2Fpublic" }) +
    + + + + +

    + Writing code is very simple, but writing simple code is the hardest thing there is! +

    +
    + Halil ibrahim Kalkan (inspired from Johan Cruyff) +
    +
    +
    + @await Component.InvokeAsync(typeof(CommentingViewComponent), new { entityType = "publicQuote", entityId = "2", loginUrl="Account/Login?ReturnUrl=%2Fpublic" }) +
    diff --git a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Pages/Public.cshtml.cs b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Pages/Public.cshtml.cs new file mode 100644 index 0000000000..3ec2b2020a --- /dev/null +++ b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Pages/Public.cshtml.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace Volo.CmsKit.Pages +{ + public class PublicModel : PageModel + { + public void OnGet() + { + } + } +} diff --git a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Volo.CmsKit.Web.Unified.csproj b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Volo.CmsKit.Web.Unified.csproj index d0237cd54c..d500cbce2d 100644 --- a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Volo.CmsKit.Web.Unified.csproj +++ b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Volo.CmsKit.Web.Unified.csproj @@ -13,7 +13,7 @@ - + @@ -47,6 +47,16 @@ + + true + PreserveNewest + + + public.cshtml + + + Public.cshtml + diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/CmsKitAdminApplicationContractsModule.cs b/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/CmsKitAdminApplicationContractsModule.cs similarity index 87% rename from modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/CmsKitAdminApplicationContractsModule.cs rename to modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/CmsKitAdminApplicationContractsModule.cs index 3347e66f18..13c053c756 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/CmsKitAdminApplicationContractsModule.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/CmsKitAdminApplicationContractsModule.cs @@ -1,6 +1,6 @@ using Volo.Abp.Modularity; -namespace Volo.CmsKit +namespace Volo.CmsKit.Admin { [DependsOn( typeof(CmsKitCommonApplicationContractsModule) diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/CmsKitPublicRemoteServiceConsts.cs b/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/CmsKitPublicRemoteServiceConsts.cs similarity index 80% rename from modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/CmsKitPublicRemoteServiceConsts.cs rename to modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/CmsKitPublicRemoteServiceConsts.cs index 372430fb8c..967b52bf8b 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/CmsKitPublicRemoteServiceConsts.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/CmsKitPublicRemoteServiceConsts.cs @@ -1,4 +1,4 @@ -namespace Volo.CmsKit +namespace Volo.CmsKit.Admin { public class CmsKitAdminRemoteServiceConsts { diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Permissions/CmsKitAdminPermissionDefinitionProvider.cs b/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Permissions/CmsKitAdminPermissionDefinitionProvider.cs similarity index 93% rename from modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Permissions/CmsKitAdminPermissionDefinitionProvider.cs rename to modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Permissions/CmsKitAdminPermissionDefinitionProvider.cs index 6c997d4860..61c9f990d4 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Permissions/CmsKitAdminPermissionDefinitionProvider.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Permissions/CmsKitAdminPermissionDefinitionProvider.cs @@ -2,7 +2,7 @@ using Volo.Abp.Localization; using Volo.CmsKit.Localization; -namespace Volo.CmsKit.Permissions +namespace Volo.CmsKit.Admin.Permissions { public class CmsKitAdminPermissionDefinitionProvider : PermissionDefinitionProvider { diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Permissions/CmsKitAdminPermissions.cs b/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Permissions/CmsKitAdminPermissions.cs similarity index 88% rename from modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Permissions/CmsKitAdminPermissions.cs rename to modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Permissions/CmsKitAdminPermissions.cs index c97829fecb..10e3a4e1d8 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Permissions/CmsKitAdminPermissions.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Permissions/CmsKitAdminPermissions.cs @@ -1,6 +1,6 @@ using Volo.Abp.Reflection; -namespace Volo.CmsKit.Permissions +namespace Volo.CmsKit.Admin.Permissions { public class CmsKitAdminPermissions { diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/CmsKitAdminAppService.cs b/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/CmsKitAdminAppService.cs similarity index 87% rename from modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/CmsKitAdminAppService.cs rename to modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/CmsKitAdminAppService.cs index 15ac49da44..8ef58a46a6 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/CmsKitAdminAppService.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/CmsKitAdminAppService.cs @@ -1,4 +1,4 @@ -namespace Volo.CmsKit +namespace Volo.CmsKit.Admin { public abstract class CmsKitAdminAppService : CmsKitAppService { diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/CmsKitAdminApplicationAutoMapperProfile.cs b/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/CmsKitAdminApplicationAutoMapperProfile.cs similarity index 93% rename from modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/CmsKitAdminApplicationAutoMapperProfile.cs rename to modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/CmsKitAdminApplicationAutoMapperProfile.cs index 24aa11ef5a..59c8058590 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/CmsKitAdminApplicationAutoMapperProfile.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/CmsKitAdminApplicationAutoMapperProfile.cs @@ -1,6 +1,6 @@ using AutoMapper; -namespace Volo.CmsKit +namespace Volo.CmsKit.Admin { public class CmsKitAdminApplicationAutoMapperProfile : Profile { diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/CmsKitAdminApplicationModule.cs b/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/CmsKitAdminApplicationModule.cs similarity index 93% rename from modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/CmsKitAdminApplicationModule.cs rename to modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/CmsKitAdminApplicationModule.cs index e995d733fd..ef0f583836 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/CmsKitAdminApplicationModule.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/CmsKitAdminApplicationModule.cs @@ -1,9 +1,8 @@ using Microsoft.Extensions.DependencyInjection; -using Volo.Abp.Application; using Volo.Abp.AutoMapper; using Volo.Abp.Modularity; -namespace Volo.CmsKit +namespace Volo.CmsKit.Admin { [DependsOn( typeof(CmsKitAdminApplicationContractsModule), diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi.Client/Volo/CmsKit/CmsKitAdminHttpApiClientModule.cs b/modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi.Client/Volo/CmsKit/Admin/CmsKitAdminHttpApiClientModule.cs similarity index 95% rename from modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi.Client/Volo/CmsKit/CmsKitAdminHttpApiClientModule.cs rename to modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi.Client/Volo/CmsKit/Admin/CmsKitAdminHttpApiClientModule.cs index c0772a8b18..586dede103 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi.Client/Volo/CmsKit/CmsKitAdminHttpApiClientModule.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi.Client/Volo/CmsKit/Admin/CmsKitAdminHttpApiClientModule.cs @@ -2,7 +2,7 @@ using Volo.Abp.Http.Client; using Volo.Abp.Modularity; -namespace Volo.CmsKit +namespace Volo.CmsKit.Admin { [DependsOn( typeof(CmsKitAdminApplicationContractsModule), diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi/Volo/CmsKit/CmsKitAdminController.cs b/modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi/Volo/CmsKit/Admin/CmsKitAdminController.cs similarity index 90% rename from modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi/Volo/CmsKit/CmsKitAdminController.cs rename to modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi/Volo/CmsKit/Admin/CmsKitAdminController.cs index cf0ca68a86..f4303c0245 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi/Volo/CmsKit/CmsKitAdminController.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi/Volo/CmsKit/Admin/CmsKitAdminController.cs @@ -1,7 +1,7 @@ using Volo.Abp.AspNetCore.Mvc; using Volo.CmsKit.Localization; -namespace Volo.CmsKit +namespace Volo.CmsKit.Admin { public abstract class CmsKitAdminController : AbpController { diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi/Volo/CmsKit/CmsKitAdminHttpApiModule.cs b/modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi/Volo/CmsKit/Admin/CmsKitAdminHttpApiModule.cs similarity index 95% rename from modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi/Volo/CmsKit/CmsKitAdminHttpApiModule.cs rename to modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi/Volo/CmsKit/Admin/CmsKitAdminHttpApiModule.cs index 3f570321e1..5d84242bcd 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi/Volo/CmsKit/CmsKitAdminHttpApiModule.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi/Volo/CmsKit/Admin/CmsKitAdminHttpApiModule.cs @@ -1,7 +1,7 @@ using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Modularity; -namespace Volo.CmsKit +namespace Volo.CmsKit.Admin { [DependsOn( typeof(CmsKitAdminApplicationContractsModule), diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Volo.CmsKit.Admin.Web.csproj b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Volo.CmsKit.Admin.Web.csproj index 6034ef0eed..f10f38c18c 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Volo.CmsKit.Admin.Web.csproj +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Volo.CmsKit.Admin.Web.csproj @@ -19,7 +19,7 @@ - + diff --git a/modules/cms-kit/src/Volo.CmsKit.Application.Contracts/Volo/CmsKit/CmsKitApplicationContractsModule.cs b/modules/cms-kit/src/Volo.CmsKit.Application.Contracts/Volo/CmsKit/CmsKitApplicationContractsModule.cs index 3f8744542c..e29c725b05 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Application.Contracts/Volo/CmsKit/CmsKitApplicationContractsModule.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Application.Contracts/Volo/CmsKit/CmsKitApplicationContractsModule.cs @@ -1,4 +1,6 @@ using Volo.Abp.Modularity; +using Volo.CmsKit.Admin; +using Volo.CmsKit.Public; namespace Volo.CmsKit { diff --git a/modules/cms-kit/src/Volo.CmsKit.Application/Volo/CmsKit/CmsKitApplicationModule.cs b/modules/cms-kit/src/Volo.CmsKit.Application/Volo/CmsKit/CmsKitApplicationModule.cs index ab649bbfb4..2b48e56716 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Application/Volo/CmsKit/CmsKitApplicationModule.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Application/Volo/CmsKit/CmsKitApplicationModule.cs @@ -1,4 +1,6 @@ using Volo.Abp.Modularity; +using Volo.CmsKit.Admin; +using Volo.CmsKit.Public; namespace Volo.CmsKit { diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.HttpApi/Volo/CmsKit/CmsKitPublicController.cs b/modules/cms-kit/src/Volo.CmsKit.Common.HttpApi/Volo/CmsKit/Controllers/CmsKitControllerBase.cs similarity index 55% rename from modules/cms-kit/src/Volo.CmsKit.Public.HttpApi/Volo/CmsKit/CmsKitPublicController.cs rename to modules/cms-kit/src/Volo.CmsKit.Common.HttpApi/Volo/CmsKit/Controllers/CmsKitControllerBase.cs index 75f38932f1..8938109b3b 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.HttpApi/Volo/CmsKit/CmsKitPublicController.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Common.HttpApi/Volo/CmsKit/Controllers/CmsKitControllerBase.cs @@ -1,11 +1,11 @@ using Volo.Abp.AspNetCore.Mvc; using Volo.CmsKit.Localization; -namespace Volo.CmsKit +namespace Volo.CmsKit.Controllers { - public abstract class CmsKitPublicController : AbpController + public class CmsKitControllerBase : AbpController { - protected CmsKitPublicController() + public CmsKitControllerBase() { LocalizationResource = typeof(CmsKitResource); } diff --git a/modules/cms-kit/src/Volo.CmsKit.Common.Web/CmsKitCommonWebModule.cs b/modules/cms-kit/src/Volo.CmsKit.Common.Web/CmsKitCommonWebModule.cs index dd991aa114..54743b948f 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Common.Web/CmsKitCommonWebModule.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Common.Web/CmsKitCommonWebModule.cs @@ -20,6 +20,12 @@ namespace Volo.CmsKit.Web { options.ReactionIcons[StandardReactions.Smile] = new LocalizableIconDictionary("/cms-kit/icons/smile.png"); options.ReactionIcons[StandardReactions.ThumbsUp] = new LocalizableIconDictionary("/cms-kit/icons/thumbsup.png"); + options.ReactionIcons[StandardReactions.Confused] = new LocalizableIconDictionary("/cms-kit/icons/confused.png"); + options.ReactionIcons[StandardReactions.Eyes] = new LocalizableIconDictionary("/cms-kit/icons/eyes.png"); + options.ReactionIcons[StandardReactions.Heart] = new LocalizableIconDictionary("/cms-kit/icons/heart.png"); + options.ReactionIcons[StandardReactions.Hooray] = new LocalizableIconDictionary("/cms-kit/icons/hooray.png"); + options.ReactionIcons[StandardReactions.Rocket] = new LocalizableIconDictionary("/cms-kit/icons/rocket.png"); + options.ReactionIcons[StandardReactions.ThumbsDown] = new LocalizableIconDictionary("/cms-kit/icons/thumbsdown.png"); }); Configure(options => diff --git a/modules/cms-kit/src/Volo.CmsKit.Common.Web/Volo.CmsKit.Common.Web.csproj b/modules/cms-kit/src/Volo.CmsKit.Common.Web/Volo.CmsKit.Common.Web.csproj index 7a849b69b3..b1a67ff399 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Common.Web/Volo.CmsKit.Common.Web.csproj +++ b/modules/cms-kit/src/Volo.CmsKit.Common.Web/Volo.CmsKit.Common.Web.csproj @@ -19,7 +19,7 @@ - + diff --git a/modules/cms-kit/src/Volo.CmsKit.Common.Web/wwwroot/cms-kit/icons/confused.png b/modules/cms-kit/src/Volo.CmsKit.Common.Web/wwwroot/cms-kit/icons/confused.png new file mode 100644 index 0000000000..4ecf0c98c1 Binary files /dev/null and b/modules/cms-kit/src/Volo.CmsKit.Common.Web/wwwroot/cms-kit/icons/confused.png differ diff --git a/modules/cms-kit/src/Volo.CmsKit.Common.Web/wwwroot/cms-kit/icons/eyes.png b/modules/cms-kit/src/Volo.CmsKit.Common.Web/wwwroot/cms-kit/icons/eyes.png new file mode 100644 index 0000000000..0b11401c9e Binary files /dev/null and b/modules/cms-kit/src/Volo.CmsKit.Common.Web/wwwroot/cms-kit/icons/eyes.png differ diff --git a/modules/cms-kit/src/Volo.CmsKit.Common.Web/wwwroot/cms-kit/icons/heart.png b/modules/cms-kit/src/Volo.CmsKit.Common.Web/wwwroot/cms-kit/icons/heart.png new file mode 100644 index 0000000000..578091d6d2 Binary files /dev/null and b/modules/cms-kit/src/Volo.CmsKit.Common.Web/wwwroot/cms-kit/icons/heart.png differ diff --git a/modules/cms-kit/src/Volo.CmsKit.Common.Web/wwwroot/cms-kit/icons/hooray.png b/modules/cms-kit/src/Volo.CmsKit.Common.Web/wwwroot/cms-kit/icons/hooray.png new file mode 100644 index 0000000000..5a6d5af253 Binary files /dev/null and b/modules/cms-kit/src/Volo.CmsKit.Common.Web/wwwroot/cms-kit/icons/hooray.png differ diff --git a/modules/cms-kit/src/Volo.CmsKit.Common.Web/wwwroot/cms-kit/icons/rocket.png b/modules/cms-kit/src/Volo.CmsKit.Common.Web/wwwroot/cms-kit/icons/rocket.png new file mode 100644 index 0000000000..e72bb81025 Binary files /dev/null and b/modules/cms-kit/src/Volo.CmsKit.Common.Web/wwwroot/cms-kit/icons/rocket.png differ diff --git a/modules/cms-kit/src/Volo.CmsKit.Common.Web/wwwroot/cms-kit/icons/thumbsdown.png b/modules/cms-kit/src/Volo.CmsKit.Common.Web/wwwroot/cms-kit/icons/thumbsdown.png new file mode 100644 index 0000000000..aef5b5c317 Binary files /dev/null and b/modules/cms-kit/src/Volo.CmsKit.Common.Web/wwwroot/cms-kit/icons/thumbsdown.png differ diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo.CmsKit.Domain.Shared.csproj b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo.CmsKit.Domain.Shared.csproj index f489caf6e4..03bc3bce5d 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo.CmsKit.Domain.Shared.csproj +++ b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo.CmsKit.Domain.Shared.csproj @@ -11,7 +11,7 @@ - + diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Comments/CommentConsts.cs b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Comments/CommentConsts.cs new file mode 100644 index 0000000000..9fed054cae --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Comments/CommentConsts.cs @@ -0,0 +1,11 @@ +namespace Volo.CmsKit.Comments +{ + public static class CommentConsts + { + public static int EntityTypeLength { get; set; } = 64; + + public static int EntityIdLength { get; set; } = 64; + + public static int MaxTextLength { get; set; } = 512; + } +} diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/en.json b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/en.json index 2dd71201b7..3e16a17a47 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/en.json +++ b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/en.json @@ -1,6 +1,18 @@ { "culture": "en", "texts": { - + "PickYourReaction": "Pick your reaction", + "YourComment": "Your comment", + "YourReply": "Your reply", + "Comments": "Comments", + "Send": "Send", + "Delete": "Delete", + "Reply": "Reply", + "Update": "Update", + "Edit": "Edit", + "LoginToAddComment": "Login to add comment", + "LoginToReply": "Login to reply", + "MessageDeletionConfirmationMessage": "This comment will be deleted completely.", + "CommentAuthorizationExceptionMessage": "Those comments are not allowed for public display." } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/tr.json b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/tr.json index d7fb14a7c7..ce0d27fd3c 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/tr.json +++ b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/tr.json @@ -1,5 +1,6 @@ { "culture": "tr", "texts": { + "PickYourReaction": "Tepkinizi seçin" } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Reactions/StandardReactions.cs b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Reactions/StandardReactions.cs index db1a4ebaea..69a2d4c73a 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Reactions/StandardReactions.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Reactions/StandardReactions.cs @@ -2,7 +2,13 @@ { public static class StandardReactions { - public const string Smile = "SM"; - public const string ThumbsUp = "TU"; + public const string Smile = "_SM"; + public const string ThumbsUp = "_TU"; + public const string ThumbsDown = "_TD"; + public const string Confused = "_CF"; + public const string Eyes = "_EY"; + public const string Heart = "_HE"; + public const string Hooray = "_HO"; + public const string Rocket = "_RO"; } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Reactions/UserReactionConsts.cs b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Reactions/UserReactionConsts.cs index 5e9f6d95eb..be9b10af1b 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Reactions/UserReactionConsts.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Reactions/UserReactionConsts.cs @@ -5,6 +5,7 @@ public static int EntityTypeLength { get; set; } = 64; public static int EntityIdLength { get; set; } = 64; + public static int ReactionNameLength { get; set; } = 32; } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo.CmsKit.Domain.csproj b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo.CmsKit.Domain.csproj index f016bed623..ee064cbedc 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo.CmsKit.Domain.csproj +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo.CmsKit.Domain.csproj @@ -11,6 +11,7 @@ + diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/CmsKitDomainModule.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/CmsKitDomainModule.cs index 4fbdf07204..7659c40126 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/CmsKitDomainModule.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/CmsKitDomainModule.cs @@ -1,11 +1,13 @@ using Volo.Abp.Domain; using Volo.Abp.Modularity; +using Volo.Abp.Users; using Volo.CmsKit.Reactions; namespace Volo.CmsKit { [DependsOn( typeof(CmsKitDomainSharedModule), + typeof(AbpUsersDomainModule), typeof(AbpDddDomainModule) )] public class CmsKitDomainModule : AbpModule @@ -16,6 +18,12 @@ namespace Volo.CmsKit { options.Reactions.AddOrReplace(StandardReactions.Smile); options.Reactions.AddOrReplace(StandardReactions.ThumbsUp); + options.Reactions.AddOrReplace(StandardReactions.ThumbsDown); + options.Reactions.AddOrReplace(StandardReactions.Confused); + options.Reactions.AddOrReplace(StandardReactions.Eyes); + options.Reactions.AddOrReplace(StandardReactions.Heart); + options.Reactions.AddOrReplace(StandardReactions.Hooray); + options.Reactions.AddOrReplace(StandardReactions.Rocket); }); } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/CmsKitOptions.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/CmsKitOptions.cs index cc69f715ce..8e13acca5c 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/CmsKitOptions.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/CmsKitOptions.cs @@ -1,4 +1,5 @@ -using JetBrains.Annotations; +using System.Collections.Generic; +using JetBrains.Annotations; using Volo.CmsKit.Reactions; namespace Volo.CmsKit @@ -8,9 +9,13 @@ namespace Volo.CmsKit [NotNull] public ReactionDefinitionDictionary Reactions { get; } + [NotNull] + public List PublicCommentEntities { get; } + public CmsKitOptions() { Reactions = new ReactionDefinitionDictionary(); + PublicCommentEntities = new List(); } } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Comments/Comment.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Comments/Comment.cs new file mode 100644 index 0000000000..a0ee37ccb7 --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Comments/Comment.cs @@ -0,0 +1,55 @@ +using System; +using JetBrains.Annotations; +using Volo.Abp; +using Volo.Abp.Auditing; +using Volo.Abp.Domain.Entities; +using Volo.Abp.MultiTenancy; + +namespace Volo.CmsKit.Comments +{ + public class Comment: Entity, IAggregateRoot, IHasCreationTime, IMustHaveCreator, IMultiTenant + { + public virtual Guid? TenantId { get; protected set; } + + public virtual string EntityType { get; protected set; } + + public virtual string EntityId { get; protected set; } + + public virtual string Text { get; protected set; } + + public virtual Guid? RepliedCommentId { get; protected set; } + + public virtual Guid CreatorId { get; set; } + + public virtual DateTime CreationTime { get; set; } + + protected Comment() + { + + } + + public Comment( + Guid id, + [NotNull] string entityType, + [NotNull] string entityId, + [NotNull] string text, + Guid? repliedCommentId, + Guid creatorId, + Guid? tenantId = null) + : base(id) + { + EntityType = Check.NotNullOrWhiteSpace(entityType, nameof(entityType), CommentConsts.EntityTypeLength); + EntityId = Check.NotNullOrWhiteSpace(entityId, nameof(entityId), CommentConsts.EntityIdLength); + RepliedCommentId = repliedCommentId; + CreatorId = creatorId; + TenantId = tenantId; + + SetText(text); + } + + public virtual void SetText(string text) + { + Text = Check.NotNullOrWhiteSpace(text, nameof(text), CommentConsts.MaxTextLength); + } + } +} diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Comments/CommentWithAuthor.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Comments/CommentWithAuthor.cs new file mode 100644 index 0000000000..3a04a31ee8 --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Comments/CommentWithAuthor.cs @@ -0,0 +1,11 @@ +using Volo.CmsKit.Users; + +namespace Volo.CmsKit.Comments +{ + public class CommentWithAuthor + { + public Comment Comment { get; set; } + + public CmsUser Author { get; set; } + } +} diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Comments/ICommentRepository.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Comments/ICommentRepository.cs new file mode 100644 index 0000000000..8e5bba3427 --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Comments/ICommentRepository.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Volo.Abp.Domain.Repositories; + +namespace Volo.CmsKit.Comments +{ + public interface ICommentRepository : IBasicRepository + { + Task> GetListWithAuthorsAsync( + [NotNull] string entityType, + [NotNull] string entityId); + } +} diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Reactions/DefaultReactionDefinitionStore.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Reactions/DefaultReactionDefinitionStore.cs index cc0daa3b65..4c7d4e095c 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Reactions/DefaultReactionDefinitionStore.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Reactions/DefaultReactionDefinitionStore.cs @@ -15,12 +15,12 @@ namespace Volo.CmsKit.Reactions Options = options.Value; } - public virtual Task> GetAvailableReactionsAsync(string entityType) + public virtual Task> GetReactionsAsync(string entityType = null) { return Task.FromResult(Options.Reactions.Values.ToList()); } - public Task GetReactionOrNullAsync(string reactionName, string entityType) + public Task GetReactionOrNullAsync(string reactionName, string entityType = null) { return Task.FromResult(Options.Reactions.GetOrDefault(reactionName)); } diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Reactions/IReactionDefinitionStore.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Reactions/IReactionDefinitionStore.cs index fa3cad8076..3c2dee7008 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Reactions/IReactionDefinitionStore.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Reactions/IReactionDefinitionStore.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Threading.Tasks; using JetBrains.Annotations; @@ -7,8 +6,8 @@ namespace Volo.CmsKit.Reactions { public interface IReactionDefinitionStore { - Task> GetAvailableReactionsAsync([CanBeNull] string entityType); + Task> GetReactionsAsync([CanBeNull] string entityType = null); - Task GetReactionOrNullAsync([NotNull] string reactionName, [CanBeNull] string entityType); + Task GetReactionOrNullAsync([NotNull] string reactionName, [CanBeNull] string entityType = null); } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Reactions/IUserReactionRepository.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Reactions/IUserReactionRepository.cs index 549982d404..2c964c70f5 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Reactions/IUserReactionRepository.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Reactions/IUserReactionRepository.cs @@ -14,8 +14,13 @@ namespace Volo.CmsKit.Reactions [NotNull] string entityId, [NotNull] string reactionName); - Task> GetListForUserAsync(Guid userId, string entityType, string entityId); + Task> GetListForUserAsync( + Guid userId, + [NotNull] string entityType, + [NotNull] string entityId); - Task> GetSummariesAsync(string entityType, string entityId); + Task> GetSummariesAsync( + [NotNull] string entityType, + [NotNull] string entityId); } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Reactions/ReactionDefinitionDictionary.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Reactions/ReactionDefinitionDictionary.cs index 1159abd38b..94ff90fa45 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Reactions/ReactionDefinitionDictionary.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Reactions/ReactionDefinitionDictionary.cs @@ -1,11 +1,12 @@ using System.Collections.Generic; +using JetBrains.Annotations; using Volo.Abp.Localization; namespace Volo.CmsKit.Reactions { public class ReactionDefinitionDictionary : Dictionary { - public void AddOrReplace(string name, ILocalizableString displayName = null) + public void AddOrReplace([NotNull] string name, ILocalizableString displayName = null) { this[name] = new ReactionDefinition(name, displayName); } diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Reactions/ReactionManager.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Reactions/ReactionManager.cs index 7d36d79c21..894ed10a47 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Reactions/ReactionManager.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Reactions/ReactionManager.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using JetBrains.Annotations; using Volo.Abp; +using Volo.Abp.MultiTenancy; namespace Volo.CmsKit.Reactions { @@ -19,84 +21,38 @@ namespace Volo.CmsKit.Reactions ReactionDefinitionStore = reactionDefinitionStore; } - public virtual async Task> GetAvailableReactionsAsync( + public virtual async Task> GetReactionsAsync( [CanBeNull] string entityType = null) { - return await ReactionDefinitionStore.GetAvailableReactionsAsync(entityType); + return await ReactionDefinitionStore.GetReactionsAsync(entityType); } public virtual async Task> GetSummariesAsync( [NotNull] string entityType, [NotNull] string entityId) { - var summaries = await UserReactionRepository.GetSummariesAsync(entityType, entityId); - - var summaryDtos = new List(); - - foreach (var summary in summaries) - { - var summaryDto = new ReactionSummary - { - Count = summary.Count - }; - - //TODO: Get all definitions then filter here? - var reactionDefinition = await ReactionDefinitionStore - .GetReactionOrNullAsync( - summary.ReactionName, - entityType - ); - - if (reactionDefinition == null) - { - continue; - } - - summaryDto.Reaction = reactionDefinition; + Check.NotNullOrWhiteSpace(entityType, nameof(entityType)); + Check.NotNullOrWhiteSpace(entityId, nameof(entityId)); - summaryDtos.Add(summaryDto); - } + var userReactionCounts = (await UserReactionRepository.GetSummariesAsync(entityType, entityId)) + .ToDictionary(x => x.ReactionName, x => x.Count); - return summaryDtos; - } - - public virtual async Task> GetUserReactionsAsync( - Guid userId, - [NotNull] string entityType, - [NotNull] string entityId) - { - var userReactions = await UserReactionRepository - .GetListForUserAsync( - userId, - entityType, - entityId + var reactions = await ReactionDefinitionStore + .GetReactionsAsync( + entityType ); - var reactionDtos = new List(); - - foreach (var userReaction in userReactions) - { - //TODO: Get all definitions then filter here? - var reactionDefinition = await ReactionDefinitionStore - .GetReactionOrNullAsync( - userReaction.ReactionName, - userReaction.EntityType - ); - - if (reactionDefinition == null) + return reactions + .Select(reaction => new ReactionSummary { - await UserReactionRepository.DeleteAsync(userReaction); - continue; - } - - reactionDtos.Add(reactionDefinition); - } - - return reactionDtos; + Reaction = reaction, + Count = userReactionCounts.GetOrDefault(reaction.Name) + }) + .ToList(); } public virtual async Task CreateAsync( - Guid userId, + Guid creatorId, [NotNull] string entityType, [NotNull] string entityId, [NotNull] string reactionName) @@ -105,7 +61,7 @@ namespace Volo.CmsKit.Reactions Check.NotNullOrWhiteSpace(entityId, nameof(entityId)); Check.NotNullOrWhiteSpace(reactionName, nameof(reactionName)); - var existingReaction = await UserReactionRepository.FindAsync(userId, entityType, entityId, reactionName); + var existingReaction = await UserReactionRepository.FindAsync(creatorId, entityType, entityId, reactionName); if (existingReaction != null) { return existingReaction; @@ -114,11 +70,11 @@ namespace Volo.CmsKit.Reactions return await UserReactionRepository.InsertAsync( new UserReaction( GuidGenerator.Create(), - userId, entityType, entityId, reactionName, - Clock.Now + creatorId, + CurrentTenant.Id ) ); } @@ -129,6 +85,10 @@ namespace Volo.CmsKit.Reactions [NotNull] string entityId, [NotNull] string reactionName) { + Check.NotNullOrWhiteSpace(entityType, nameof(entityType)); + Check.NotNullOrWhiteSpace(entityId, nameof(entityId)); + Check.NotNullOrWhiteSpace(reactionName, nameof(reactionName)); + var existingReaction = await UserReactionRepository.FindAsync(userId, entityType, entityId, reactionName); if (existingReaction == null) { diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Reactions/UserReaction.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Reactions/UserReaction.cs index 34c3fa9bcd..fc525e4f65 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Reactions/UserReaction.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Reactions/UserReaction.cs @@ -1,21 +1,25 @@ using System; using JetBrains.Annotations; using Volo.Abp; +using Volo.Abp.Auditing; using Volo.Abp.Domain.Entities; +using Volo.Abp.MultiTenancy; namespace Volo.CmsKit.Reactions { - public class UserReaction : Entity, IAggregateRoot + public class UserReaction : Entity, IAggregateRoot, IHasCreationTime, IMustHaveCreator, IMultiTenant { + public virtual Guid? TenantId { get; protected set; } + public virtual string EntityType { get; protected set; } public virtual string EntityId { get; protected set; } public virtual string ReactionName { get; protected set; } - public virtual DateTime CreationTime { get; protected set; } + public virtual Guid CreatorId { get; set; } - public virtual Guid UserId { get; protected set; } + public virtual DateTime CreationTime { get; set; } protected UserReaction() { @@ -24,18 +28,18 @@ namespace Volo.CmsKit.Reactions internal UserReaction( Guid id, - Guid userId, [NotNull] string entityType, [NotNull] string entityId, [NotNull] string reactionName, - DateTime creationTime) + Guid creatorId, + Guid? tenantId = null) : base(id) { EntityType = Check.NotNullOrWhiteSpace(entityType, nameof(entityType)); EntityId = Check.NotNullOrWhiteSpace(entityId, nameof(entityId)); ReactionName = Check.NotNullOrWhiteSpace(reactionName, nameof(reactionName)); - UserId = userId; - CreationTime = creationTime; + CreatorId = creatorId; + TenantId = tenantId; } } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Users/CmsUser.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Users/CmsUser.cs new file mode 100644 index 0000000000..7e66aae6a1 --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Users/CmsUser.cs @@ -0,0 +1,82 @@ +using System; +using Volo.Abp.Domain.Entities; +using Volo.Abp.Users; + +namespace Volo.CmsKit.Users +{ + public class CmsUser : AggregateRoot, IUser, IUpdateUserData + { + public virtual Guid? TenantId { get; protected set; } + + public virtual string UserName { get; protected set; } + + public virtual string Email { get; protected set; } + + public virtual string Name { get; set; } + + public virtual string Surname { get; set; } + + public virtual bool EmailConfirmed { get; protected set; } + + public virtual string PhoneNumber { get; protected set; } + + public virtual bool PhoneNumberConfirmed { get; protected set; } + + protected CmsUser() + { + + } + + public CmsUser(IUserData user) + : base(user.Id) + { + TenantId = user.TenantId; + UpdateInternal(user); + } + + public virtual bool Update(IUserData user) + { + if (Id != user.Id) + { + throw new ArgumentException($"Given User's Id '{user.Id}' does not match to this User's Id '{Id}'"); + } + + if (TenantId != user.TenantId) + { + throw new ArgumentException($"Given User's TenantId '{user.TenantId}' does not match to this User's TenantId '{TenantId}'"); + } + + if (Equals(user)) + { + return false; + } + + UpdateInternal(user); + return true; + } + + protected virtual bool Equals(IUserData user) + { + return Id == user.Id && + TenantId == user.TenantId && + UserName == user.UserName && + Name == user.Name && + Surname == user.Surname && + Email == user.Email && + EmailConfirmed == user.EmailConfirmed && + PhoneNumber == user.PhoneNumber && + PhoneNumberConfirmed == user.PhoneNumberConfirmed; + } + + protected virtual void UpdateInternal(IUserData user) + { + Email = user.Email; + Name = user.Name; + Surname = user.Surname; + EmailConfirmed = user.EmailConfirmed; + PhoneNumber = user.PhoneNumber; + PhoneNumberConfirmed = user.PhoneNumberConfirmed; + UserName = user.UserName; + } + } +} diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Users/CmsUserLookupService.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Users/CmsUserLookupService.cs new file mode 100644 index 0000000000..0771140a10 --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Users/CmsUserLookupService.cs @@ -0,0 +1,24 @@ +using Volo.Abp.Uow; +using Volo.Abp.Users; + +namespace Volo.CmsKit.Users +{ + public class CmsUserLookupService: UserLookupService, ICmsUserLookupService + { + public CmsUserLookupService( + ICmsUserRepository userRepository, + IUnitOfWorkManager unitOfWorkManager) + : base( + userRepository, + unitOfWorkManager) + { + + } + + protected override CmsUser CreateUser(IUserData externalUser) + { + return new CmsUser(externalUser); + } + + } +} diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Users/CmsUserSynchronizer.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Users/CmsUserSynchronizer.cs new file mode 100644 index 0000000000..6fc785ad4a --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Users/CmsUserSynchronizer.cs @@ -0,0 +1,43 @@ +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Entities.Events.Distributed; +using Volo.Abp.EventBus.Distributed; +using Volo.Abp.Users; + +namespace Volo.CmsKit.Users +{ + public class CmsUserSynchronizer : + IDistributedEventHandler>, + ITransientDependency + { + protected ICmsUserRepository UserRepository { get; } + + protected ICmsUserLookupService UserLookupService { get; } + + public CmsUserSynchronizer( + ICmsUserRepository userRepository, + ICmsUserLookupService userLookupService) + { + UserRepository = userRepository; + UserLookupService = userLookupService; + } + + public virtual async Task HandleEventAsync(EntityUpdatedEto eventData) + { + var user = await UserRepository.FindAsync(eventData.Entity.Id); + if (user == null) + { + user = await UserLookupService.FindByIdAsync(eventData.Entity.Id); + if (user == null) + { + return; + } + } + + if (user.Update(eventData.Entity)) + { + await UserRepository.UpdateAsync(user); + } + } + } +} diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Users/ICmsUserLookupService.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Users/ICmsUserLookupService.cs new file mode 100644 index 0000000000..8731a187fc --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Users/ICmsUserLookupService.cs @@ -0,0 +1,9 @@ +using Volo.Abp.Users; + +namespace Volo.CmsKit.Users +{ + public interface ICmsUserLookupService : IUserLookupService + { + + } +} diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Users/ICmsUserRepository.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Users/ICmsUserRepository.cs new file mode 100644 index 0000000000..95971911af --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Users/ICmsUserRepository.cs @@ -0,0 +1,9 @@ +using Volo.Abp.Users; + +namespace Volo.CmsKit.Users +{ + public interface ICmsUserRepository: IUserRepository + { + + } +} diff --git a/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo.CmsKit.EntityFrameworkCore.csproj b/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo.CmsKit.EntityFrameworkCore.csproj index de174d0afd..ace2e010bd 100644 --- a/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo.CmsKit.EntityFrameworkCore.csproj +++ b/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo.CmsKit.EntityFrameworkCore.csproj @@ -10,7 +10,8 @@ - + + diff --git a/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Comments/EfCoreCommentRepository.cs b/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Comments/EfCoreCommentRepository.cs new file mode 100644 index 0000000000..3c39386f03 --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Comments/EfCoreCommentRepository.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Volo.Abp; +using Volo.Abp.Domain.Repositories.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore; +using Volo.CmsKit.EntityFrameworkCore; + +namespace Volo.CmsKit.Comments +{ + public class EfCoreCommentRepository : EfCoreRepository, + ICommentRepository + { + public EfCoreCommentRepository(IDbContextProvider dbContextProvider) + : base(dbContextProvider) + { + } + + public async Task> GetListWithAuthorsAsync( + string entityType, + string entityId) + { + Check.NotNullOrWhiteSpace(entityType, nameof(entityType)); + Check.NotNullOrWhiteSpace(entityId, nameof(entityId)); + + var query = from comment in DbSet + join user in DbContext.CmsUsers on comment.CreatorId equals user.Id + where entityType == comment.EntityType && entityId == comment.EntityId + orderby comment.CreationTime + select new CommentWithAuthor + { + Comment = comment, + Author = user + }; + + return await query.ToListAsync(); + } + + public override async Task DeleteAsync(Guid id, bool autoSave = false, CancellationToken cancellationToken = default) + { + var replies = await DbSet + .Where(x => x.RepliedCommentId == id) + .ToListAsync(GetCancellationToken(cancellationToken)); + + foreach (var reply in replies) + { + //TODO: Discuss if it is better to mark it as deleted and show in the ui as "This is deleted" instead of deleting it and replies completely + await base.DeleteAsync(reply.Id, autoSave, GetCancellationToken(cancellationToken)); + } + + await base.DeleteAsync(id, autoSave, GetCancellationToken(cancellationToken)); + } + } +} diff --git a/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/EntityFrameworkCore/CmsKitDbContext.cs b/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/EntityFrameworkCore/CmsKitDbContext.cs index e90e133d7f..8033522f70 100644 --- a/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/EntityFrameworkCore/CmsKitDbContext.cs +++ b/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/EntityFrameworkCore/CmsKitDbContext.cs @@ -1,7 +1,9 @@ using Microsoft.EntityFrameworkCore; using Volo.Abp.Data; using Volo.Abp.EntityFrameworkCore; +using Volo.CmsKit.Comments; using Volo.CmsKit.Reactions; +using Volo.CmsKit.Users; namespace Volo.CmsKit.EntityFrameworkCore { @@ -10,6 +12,10 @@ namespace Volo.CmsKit.EntityFrameworkCore { public DbSet UserReactions { get; set; } + public DbSet Comments { get; set; } + + public DbSet CmsUsers { get; set; } + public CmsKitDbContext(DbContextOptions options) : base(options) { diff --git a/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/EntityFrameworkCore/CmsKitDbContextModelCreatingExtensions.cs b/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/EntityFrameworkCore/CmsKitDbContextModelCreatingExtensions.cs index 7a12382e80..cb7a7b78d6 100644 --- a/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/EntityFrameworkCore/CmsKitDbContextModelCreatingExtensions.cs +++ b/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/EntityFrameworkCore/CmsKitDbContextModelCreatingExtensions.cs @@ -2,7 +2,10 @@ using Microsoft.EntityFrameworkCore; using Volo.Abp; using Volo.Abp.EntityFrameworkCore.Modeling; +using Volo.CmsKit.Comments; using Volo.CmsKit.Reactions; +using Volo.CmsKit.Users; +using Volo.Abp.Users.EntityFrameworkCore; namespace Volo.CmsKit.EntityFrameworkCore { @@ -21,6 +24,14 @@ namespace Volo.CmsKit.EntityFrameworkCore optionsAction?.Invoke(options); + builder.Entity(b => + { + b.ToTable(options.TablePrefix + "Users", options.Schema); + + b.ConfigureByConvention(); + b.ConfigureAbpUser(); + }); + builder.Entity(b => { b.ToTable(options.TablePrefix + "UserReactions", options.Schema); @@ -32,7 +43,22 @@ namespace Volo.CmsKit.EntityFrameworkCore b.Property(x => x.CreationTime); b.HasIndex(x => new { x.EntityType, x.EntityId }); - b.HasIndex(x => new { x.UserId, x.EntityType, x.EntityId, x.ReactionName }); + b.HasIndex(x => new { x.CreatorId, x.EntityType, x.EntityId, x.ReactionName }); + }); + + builder.Entity(b => + { + b.ToTable(options.TablePrefix + "Comments", options.Schema); + b.ConfigureByConvention(); + + b.Property(x => x.EntityType).IsRequired().HasMaxLength(CommentConsts.EntityTypeLength); + b.Property(x => x.EntityId).IsRequired().HasMaxLength(CommentConsts.EntityIdLength); + b.Property(x => x.Text).IsRequired().HasMaxLength(CommentConsts.MaxTextLength); + b.Property(x => x.RepliedCommentId); + b.Property(x => x.CreationTime); + + b.HasIndex(x => new { x.EntityType, x.EntityId }); + b.HasIndex(x => new { x.RepliedCommentId }); }); } } diff --git a/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/EntityFrameworkCore/CmsKitEntityFrameworkCoreModule.cs b/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/EntityFrameworkCore/CmsKitEntityFrameworkCoreModule.cs index afad9b56e8..028bc466b2 100644 --- a/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/EntityFrameworkCore/CmsKitEntityFrameworkCoreModule.cs +++ b/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/EntityFrameworkCore/CmsKitEntityFrameworkCoreModule.cs @@ -1,11 +1,16 @@ using Microsoft.Extensions.DependencyInjection; using Volo.Abp.EntityFrameworkCore; using Volo.Abp.Modularity; +using Volo.Abp.Users.EntityFrameworkCore; +using Volo.CmsKit.Comments; +using Volo.CmsKit.Reactions; +using Volo.CmsKit.Users; namespace Volo.CmsKit.EntityFrameworkCore { [DependsOn( typeof(CmsKitDomainModule), + typeof(AbpUsersEntityFrameworkCoreModule), typeof(AbpEntityFrameworkCoreModule) )] public class CmsKitEntityFrameworkCoreModule : AbpModule @@ -14,10 +19,10 @@ namespace Volo.CmsKit.EntityFrameworkCore { context.Services.AddAbpDbContext(options => { - /* Add custom repositories here. Example: - * options.AddRepository(); - */ + options.AddRepository(); + options.AddRepository(); + options.AddRepository(); }); } } -} \ No newline at end of file +} diff --git a/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/EntityFrameworkCore/ICmsKitDbContext.cs b/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/EntityFrameworkCore/ICmsKitDbContext.cs index 9441986b23..e63366d5ae 100644 --- a/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/EntityFrameworkCore/ICmsKitDbContext.cs +++ b/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/EntityFrameworkCore/ICmsKitDbContext.cs @@ -1,7 +1,9 @@ using Microsoft.EntityFrameworkCore; using Volo.Abp.Data; using Volo.Abp.EntityFrameworkCore; +using Volo.CmsKit.Comments; using Volo.CmsKit.Reactions; +using Volo.CmsKit.Users; namespace Volo.CmsKit.EntityFrameworkCore { @@ -9,5 +11,9 @@ namespace Volo.CmsKit.EntityFrameworkCore public interface ICmsKitDbContext : IEfCoreDbContext { DbSet UserReactions { get; } + + DbSet Comments { get; } + + DbSet CmsUsers { get; set; } } } diff --git a/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Reactions/EfCoreUserReactionRepository.cs b/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Reactions/EfCoreUserReactionRepository.cs index 2ddd72bdd3..2d0b8ba989 100644 --- a/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Reactions/EfCoreUserReactionRepository.cs +++ b/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Reactions/EfCoreUserReactionRepository.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; +using Volo.Abp; using Volo.Abp.Domain.Repositories.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore; using Volo.CmsKit.EntityFrameworkCore; @@ -23,27 +24,42 @@ namespace Volo.CmsKit.Reactions string entityId, string reactionName) { + Check.NotNullOrWhiteSpace(entityType, nameof(entityType)); + Check.NotNullOrWhiteSpace(entityId, nameof(entityId)); + Check.NotNullOrWhiteSpace(reactionName, nameof(reactionName)); + return await DbSet .Where(x => - x.UserId == userId && + x.CreatorId == userId && x.EntityType == entityType && x.EntityId == entityId && x.ReactionName == reactionName) .FirstOrDefaultAsync(); } - public async Task> GetListForUserAsync(Guid userId, string entityType, string entityId) + public async Task> GetListForUserAsync( + Guid userId, + string entityType, + string entityId) { + Check.NotNullOrWhiteSpace(entityType, nameof(entityType)); + Check.NotNullOrWhiteSpace(entityId, nameof(entityId)); + return await DbSet .Where(x => - x.UserId == userId && + x.CreatorId == userId && x.EntityType == entityType && x.EntityId == entityId) .ToListAsync(); } - public async Task> GetSummariesAsync(string entityType, string entityId) + public async Task> GetSummariesAsync( + string entityType, + string entityId) { + Check.NotNullOrWhiteSpace(entityType, nameof(entityType)); + Check.NotNullOrWhiteSpace(entityId, nameof(entityId)); + return await DbSet .Where(x => x.EntityType == entityType && diff --git a/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Users/EfCoreCmsUserRepository.cs b/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Users/EfCoreCmsUserRepository.cs new file mode 100644 index 0000000000..0b6e804be0 --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Users/EfCoreCmsUserRepository.cs @@ -0,0 +1,14 @@ +using Volo.Abp.EntityFrameworkCore; +using Volo.Abp.Users.EntityFrameworkCore; +using Volo.CmsKit.EntityFrameworkCore; + +namespace Volo.CmsKit.Users +{ + public class EfCoreCmsUserRepository: EfCoreUserRepositoryBase, ICmsUserRepository + { + public EfCoreCmsUserRepository(IDbContextProvider dbContextProvider) + : base(dbContextProvider) + { + } + } +} diff --git a/modules/cms-kit/src/Volo.CmsKit.HttpApi.Client/Volo/CmsKit/CmsKitHttpApiClientModule.cs b/modules/cms-kit/src/Volo.CmsKit.HttpApi.Client/Volo/CmsKit/CmsKitHttpApiClientModule.cs index ca2d21c38f..25513a59bf 100644 --- a/modules/cms-kit/src/Volo.CmsKit.HttpApi.Client/Volo/CmsKit/CmsKitHttpApiClientModule.cs +++ b/modules/cms-kit/src/Volo.CmsKit.HttpApi.Client/Volo/CmsKit/CmsKitHttpApiClientModule.cs @@ -1,4 +1,6 @@ using Volo.Abp.Modularity; +using Volo.CmsKit.Admin; +using Volo.CmsKit.Public; namespace Volo.CmsKit { diff --git a/modules/cms-kit/src/Volo.CmsKit.HttpApi/Volo/CmsKit/CmsKitHttpApiModule.cs b/modules/cms-kit/src/Volo.CmsKit.HttpApi/Volo/CmsKit/CmsKitHttpApiModule.cs index 8756d6a48d..c4a45bb8cf 100644 --- a/modules/cms-kit/src/Volo.CmsKit.HttpApi/Volo/CmsKit/CmsKitHttpApiModule.cs +++ b/modules/cms-kit/src/Volo.CmsKit.HttpApi/Volo/CmsKit/CmsKitHttpApiModule.cs @@ -1,4 +1,6 @@ using Volo.Abp.Modularity; +using Volo.CmsKit.Admin; +using Volo.CmsKit.Public; namespace Volo.CmsKit { diff --git a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo.CmsKit.MongoDB.csproj b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo.CmsKit.MongoDB.csproj index 079112e5b2..7a25b6424b 100644 --- a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo.CmsKit.MongoDB.csproj +++ b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo.CmsKit.MongoDB.csproj @@ -4,13 +4,14 @@ - netcoreapp3.1 + netstandard2.0 + diff --git a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/CmsKitMongoDbContext.cs b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/CmsKitMongoDbContext.cs index 5587203290..63bf48a14e 100644 --- a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/CmsKitMongoDbContext.cs +++ b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/CmsKitMongoDbContext.cs @@ -1,14 +1,20 @@ -using Volo.Abp.Data; +using MongoDB.Driver; +using Volo.Abp.Data; using Volo.Abp.MongoDB; +using Volo.CmsKit.Comments; +using Volo.CmsKit.Reactions; +using Volo.CmsKit.Users; namespace Volo.CmsKit.MongoDB { [ConnectionStringName(CmsKitDbProperties.ConnectionStringName)] public class CmsKitMongoDbContext : AbpMongoDbContext, ICmsKitMongoDbContext { - /* Add mongo collections here. Example: - * public IMongoCollection Questions => Collection(); - */ + public IMongoCollection Comments => Collection(); + + public IMongoCollection UserReactions => Collection(); + + public IMongoCollection CmsUsers => Collection(); protected override void CreateModel(IMongoModelBuilder modelBuilder) { @@ -17,4 +23,4 @@ namespace Volo.CmsKit.MongoDB modelBuilder.ConfigureCmsKit(); } } -} \ No newline at end of file +} diff --git a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/CmsKitMongoDbModule.cs b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/CmsKitMongoDbModule.cs index b9ce6b7862..c54a1456aa 100644 --- a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/CmsKitMongoDbModule.cs +++ b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/CmsKitMongoDbModule.cs @@ -1,11 +1,19 @@ using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Modularity; using Volo.Abp.MongoDB; +using Volo.Abp.Users.MongoDB; +using Volo.CmsKit.Comments; +using Volo.CmsKit.MongoDB.Comments; +using Volo.CmsKit.MongoDB.Reactions; +using Volo.CmsKit.MongoDB.Users; +using Volo.CmsKit.Reactions; +using Volo.CmsKit.Users; namespace Volo.CmsKit.MongoDB { [DependsOn( typeof(CmsKitDomainModule), + typeof(AbpUsersMongoDbModule), typeof(AbpMongoDbModule) )] public class CmsKitMongoDbModule : AbpModule @@ -14,6 +22,9 @@ namespace Volo.CmsKit.MongoDB { context.Services.AddMongoDbContext(options => { + options.AddRepository(); + options.AddRepository(); + options.AddRepository(); /* Add custom repositories here. Example: * options.AddRepository(); */ diff --git a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Comments/MongoCommentRepository.cs b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Comments/MongoCommentRepository.cs new file mode 100644 index 0000000000..26a7c09a8c --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Comments/MongoCommentRepository.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MongoDB.Driver.Linq; +using MongoDB.Driver; +using Volo.Abp; +using Volo.Abp.Domain.Repositories.MongoDB; +using Volo.Abp.MongoDB; +using Volo.CmsKit.Comments; + +namespace Volo.CmsKit.MongoDB.Comments +{ + public class MongoCommentRepository : MongoDbRepository, ICommentRepository + { + public MongoCommentRepository(IMongoDbContextProvider dbContextProvider) : base(dbContextProvider) + { + } + + public async Task> GetListWithAuthorsAsync( + string entityType, + string entityId) + { + Check.NotNullOrWhiteSpace(entityType, nameof(entityType)); + Check.NotNullOrWhiteSpace(entityId, nameof(entityId)); + + var authorsQuery = from comment in GetMongoQueryable() + join user in DbContext.CmsUsers on comment.CreatorId equals user.Id + where entityType == comment.EntityType && entityId == comment.EntityId + orderby comment.CreationTime + select user; + + var authors = await authorsQuery.ToListAsync(); + + var comments = await GetMongoQueryable() + .Where(c => c.EntityId == entityId && c.EntityType == entityType) + .OrderBy(c => c.CreationTime).ToListAsync(); + + return comments + .Select( + comment => + new CommentWithAuthor + { + Comment = comment, + Author = authors.FirstOrDefault(a => a.Id == comment.CreatorId) + }).ToList(); + } + + public override async Task DeleteAsync(Guid id, bool autoSave = false, CancellationToken cancellationToken = default) + { + var replies = await GetMongoQueryable() + .Where(x => x.RepliedCommentId == id) + .ToListAsync(GetCancellationToken(cancellationToken)); + + foreach (var reply in replies) + { + //TODO: Discuss if it is better to mark it as deleted and show in the ui as "This is deleted" instead of deleting it and replies completely + await base.DeleteAsync(reply.Id, autoSave, GetCancellationToken(cancellationToken)); + } + + await base.DeleteAsync(id, autoSave, GetCancellationToken(cancellationToken)); + } + } +} diff --git a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/ICmsKitMongoDbContext.cs b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/ICmsKitMongoDbContext.cs index 101efedaa4..16b72c8902 100644 --- a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/ICmsKitMongoDbContext.cs +++ b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/ICmsKitMongoDbContext.cs @@ -1,13 +1,19 @@ -using Volo.Abp.Data; +using MongoDB.Driver; +using Volo.Abp.Data; using Volo.Abp.MongoDB; +using Volo.CmsKit.Comments; +using Volo.CmsKit.Reactions; +using Volo.CmsKit.Users; namespace Volo.CmsKit.MongoDB { [ConnectionStringName(CmsKitDbProperties.ConnectionStringName)] public interface ICmsKitMongoDbContext : IAbpMongoDbContext { - /* Define mongo collections here. Example: - * IMongoCollection Questions { get; } - */ + IMongoCollection UserReactions { get; } + + IMongoCollection Comments { get; } + + IMongoCollection CmsUsers { get; } } } diff --git a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Reactions/MongoUserReactionRepository.cs b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Reactions/MongoUserReactionRepository.cs new file mode 100644 index 0000000000..ae3b28766a --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Reactions/MongoUserReactionRepository.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using MongoDB.Driver; +using MongoDB.Driver.Linq; +using Volo.Abp; +using Volo.Abp.Domain.Repositories.MongoDB; +using Volo.Abp.MongoDB; +using Volo.CmsKit.Reactions; + +namespace Volo.CmsKit.MongoDB.Reactions +{ + public class MongoUserReactionRepository : MongoDbRepository, IUserReactionRepository + { + public MongoUserReactionRepository(IMongoDbContextProvider dbContextProvider) : base(dbContextProvider) + { + } + + public async Task FindAsync( + Guid userId, + string entityType, + string entityId, + string reactionName) + { + Check.NotNullOrWhiteSpace(entityType, nameof(entityType)); + Check.NotNullOrWhiteSpace(entityId, nameof(entityId)); + Check.NotNullOrWhiteSpace(reactionName, nameof(reactionName)); + + return await GetMongoQueryable() + .Where(x => + x.CreatorId == userId && + x.EntityType == entityType && + x.EntityId == entityId && + x.ReactionName == reactionName) + .FirstOrDefaultAsync(); + } + + public async Task> GetListForUserAsync( + Guid userId, + string entityType, + string entityId) + { + Check.NotNullOrWhiteSpace(entityType, nameof(entityType)); + Check.NotNullOrWhiteSpace(entityId, nameof(entityId)); + + return await GetMongoQueryable() + .Where(x => + x.CreatorId == userId && + x.EntityType == entityType && + x.EntityId == entityId) + .ToListAsync(); + } + + public async Task> GetSummariesAsync( + string entityType, + string entityId) + { + Check.NotNullOrWhiteSpace(entityType, nameof(entityType)); + Check.NotNullOrWhiteSpace(entityId, nameof(entityId)); + + return await GetMongoQueryable() + .Where(x => + x.EntityType == entityType && + x.EntityId == entityId) + .GroupBy(x => x.ReactionName) + .Select(g => new ReactionSummaryQueryResultItem + { + ReactionName = g.Key, + Count = g.Count() + }) + .ToListAsync(); + } + } +} diff --git a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Users/MongoCmsUserRepository.cs b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Users/MongoCmsUserRepository.cs new file mode 100644 index 0000000000..ed2691a5a8 --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Users/MongoCmsUserRepository.cs @@ -0,0 +1,15 @@ +using Volo.Abp.MongoDB; +using Volo.Abp.Users.MongoDB; +using Volo.CmsKit.Users; + +namespace Volo.CmsKit.MongoDB.Users +{ + public class MongoCmsUserRepository : MongoUserRepositoryBase, ICmsUserRepository + { + public MongoCmsUserRepository(IMongoDbContextProvider dbContextProvider) + : base(dbContextProvider) + { + } + + } +} diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/CmsKitPublicApplicationContractsModule.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/CmsKitPublicApplicationContractsModule.cs similarity index 87% rename from modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/CmsKitPublicApplicationContractsModule.cs rename to modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/CmsKitPublicApplicationContractsModule.cs index 4c48f4034d..92ab969bf6 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/CmsKitPublicApplicationContractsModule.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/CmsKitPublicApplicationContractsModule.cs @@ -1,6 +1,6 @@ using Volo.Abp.Modularity; -namespace Volo.CmsKit +namespace Volo.CmsKit.Public { [DependsOn( typeof(CmsKitCommonApplicationContractsModule) diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/CmsKitPublicRemoteServiceConsts.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/CmsKitPublicRemoteServiceConsts.cs similarity index 80% rename from modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/CmsKitPublicRemoteServiceConsts.cs rename to modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/CmsKitPublicRemoteServiceConsts.cs index d19348495f..ee33e7769f 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/CmsKitPublicRemoteServiceConsts.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/CmsKitPublicRemoteServiceConsts.cs @@ -1,4 +1,4 @@ -namespace Volo.CmsKit +namespace Volo.CmsKit.Public { public class CmsKitPublicRemoteServiceConsts { diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Comments/CmsUserDto.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Comments/CmsUserDto.cs new file mode 100644 index 0000000000..b246cbc915 --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Comments/CmsUserDto.cs @@ -0,0 +1,17 @@ +using System; + +namespace Volo.CmsKit.Public.Comments +{ + public class CmsUserDto + { + public Guid Id { get; set; } + + public string UserName { get; set; } + + public string Email { get; set; } + + public string Name { get; set; } + + public string Surname { get; set; } + } +} diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Comments/CommentDto.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Comments/CommentDto.cs new file mode 100644 index 0000000000..f7bdf27d13 --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Comments/CommentDto.cs @@ -0,0 +1,23 @@ +using System; + +namespace Volo.CmsKit.Public.Comments +{ + public class CommentDto + { + public Guid Id { get; set; } + + public string EntityType { get; set; } + + public string EntityId { get; set; } + + public string Text { get; set; } + + public Guid? RepliedCommentId { get; set; } + + public Guid CreatorId { get; set; } + + public DateTime CreationTime { get; set; } + + public CmsUserDto Author { get; set; } + } +} diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Comments/CommentWithDetailsDto.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Comments/CommentWithDetailsDto.cs new file mode 100644 index 0000000000..5c0c3454d9 --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Comments/CommentWithDetailsDto.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; + +namespace Volo.CmsKit.Public.Comments +{ + public class CommentWithDetailsDto + { + public Guid Id { get; set; } + + public string EntityType { get; set; } + + public string EntityId { get; set; } + + public string Text { get; set; } + + public Guid CreatorId { get; set; } + + public DateTime CreationTime { get; set; } + + public List Replies { get; set; } + + public CmsUserDto Author { get; set; } + } +} diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Comments/CreateCommentInput.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Comments/CreateCommentInput.cs new file mode 100644 index 0000000000..ebbc41898e --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Comments/CreateCommentInput.cs @@ -0,0 +1,24 @@ +using System; +using System.ComponentModel.DataAnnotations; +using Volo.Abp.Validation; +using Volo.CmsKit.Comments; + +namespace Volo.CmsKit.Public.Comments +{ + public class CreateCommentInput + { + [Required] + [DynamicStringLength(typeof(CommentConsts), nameof(CommentConsts.EntityTypeLength))] + public string EntityType { get; set; } + + [Required] + [DynamicStringLength(typeof(CommentConsts), nameof(CommentConsts.EntityIdLength))] + public string EntityId { get; set; } + + [Required] + [DynamicStringLength(typeof(CommentConsts), nameof(CommentConsts.MaxTextLength))] + public string Text { get; set; } + + public Guid? RepliedCommentId { get; set; } + } +} diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Comments/ICommentPublicAppService.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Comments/ICommentPublicAppService.cs new file mode 100644 index 0000000000..8f13c7fb05 --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Comments/ICommentPublicAppService.cs @@ -0,0 +1,18 @@ +using System; +using System.Threading.Tasks; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Application.Services; + +namespace Volo.CmsKit.Public.Comments +{ + public interface ICommentPublicAppService : IApplicationService + { + Task> GetAllForEntityAsync(string entityType, string entityId); + + Task CreateAsync(CreateCommentInput input); + + Task UpdateAsync(Guid id, UpdateCommentInput input); + + Task DeleteAsync(Guid id); + } +} diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Comments/UpdateCommentInput.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Comments/UpdateCommentInput.cs new file mode 100644 index 0000000000..af8cb1bfef --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Comments/UpdateCommentInput.cs @@ -0,0 +1,13 @@ +using System.ComponentModel.DataAnnotations; +using Volo.Abp.Validation; +using Volo.CmsKit.Comments; + +namespace Volo.CmsKit.Public.Comments +{ + public class UpdateCommentInput + { + [Required] + [DynamicStringLength(typeof(CommentConsts), nameof(CommentConsts.MaxTextLength))] + public string Text { get; set; } + } +} diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Permissions/CmsKitPublicPermissionDefinitionProvider.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Permissions/CmsKitPublicPermissionDefinitionProvider.cs similarity index 93% rename from modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Permissions/CmsKitPublicPermissionDefinitionProvider.cs rename to modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Permissions/CmsKitPublicPermissionDefinitionProvider.cs index f52d398766..2baf544a69 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Permissions/CmsKitPublicPermissionDefinitionProvider.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Permissions/CmsKitPublicPermissionDefinitionProvider.cs @@ -2,7 +2,7 @@ using Volo.Abp.Localization; using Volo.CmsKit.Localization; -namespace Volo.CmsKit.Permissions +namespace Volo.CmsKit.Public.Permissions { public class CmsKitPublicPermissionDefinitionProvider : PermissionDefinitionProvider { diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Permissions/CmsKitPublicPermissions.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Permissions/CmsKitPublicPermissions.cs similarity index 88% rename from modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Permissions/CmsKitPublicPermissions.cs rename to modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Permissions/CmsKitPublicPermissions.cs index 4a2991c71e..6b348ee430 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Permissions/CmsKitPublicPermissions.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Permissions/CmsKitPublicPermissions.cs @@ -1,6 +1,6 @@ using Volo.Abp.Reflection; -namespace Volo.CmsKit.Permissions +namespace Volo.CmsKit.Public.Permissions { public class CmsKitPublicPermissions { diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Reactions/CreateReactionDto.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Reactions/CreateReactionDto.cs similarity index 82% rename from modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Reactions/CreateReactionDto.cs rename to modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Reactions/CreateReactionDto.cs index c91b6f51ed..8612ba6762 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Reactions/CreateReactionDto.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Reactions/CreateReactionDto.cs @@ -1,4 +1,4 @@ -namespace Volo.CmsKit.Reactions +namespace Volo.CmsKit.Public.Reactions { public class CreateReactionDto { diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Reactions/DeleteReactionDto.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Reactions/DeleteReactionDto.cs similarity index 82% rename from modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Reactions/DeleteReactionDto.cs rename to modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Reactions/DeleteReactionDto.cs index f7c2e5a933..8ce4dd6808 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Reactions/DeleteReactionDto.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Reactions/DeleteReactionDto.cs @@ -1,4 +1,4 @@ -namespace Volo.CmsKit.Reactions +namespace Volo.CmsKit.Public.Reactions { public class DeleteReactionDto { diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Reactions/IReactionPublicAppService.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Reactions/IReactionPublicAppService.cs new file mode 100644 index 0000000000..3058148afd --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Reactions/IReactionPublicAppService.cs @@ -0,0 +1,15 @@ +using System.Threading.Tasks; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Application.Services; + +namespace Volo.CmsKit.Public.Reactions +{ + public interface IReactionPublicAppService : IApplicationService + { + Task> GetForSelectionAsync(string entityType, string entityId); + + Task CreateAsync(CreateReactionDto input); + + Task DeleteAsync(DeleteReactionDto input); + } +} diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Reactions/ReactionDto.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Reactions/ReactionDto.cs similarity index 84% rename from modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Reactions/ReactionDto.cs rename to modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Reactions/ReactionDto.cs index b66c7ce6f1..e770bb4017 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Reactions/ReactionDto.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Reactions/ReactionDto.cs @@ -1,6 +1,6 @@ using JetBrains.Annotations; -namespace Volo.CmsKit.Reactions +namespace Volo.CmsKit.Public.Reactions { public class ReactionDto { diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Reactions/ReactionWithSelectionDto.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Reactions/ReactionWithSelectionDto.cs similarity index 82% rename from modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Reactions/ReactionWithSelectionDto.cs rename to modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Reactions/ReactionWithSelectionDto.cs index 643b5b5099..71adc4756c 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Reactions/ReactionWithSelectionDto.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Reactions/ReactionWithSelectionDto.cs @@ -1,4 +1,4 @@ -namespace Volo.CmsKit.Reactions +namespace Volo.CmsKit.Public.Reactions { public class ReactionWithSelectionDto { diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Reactions/GetAvailableReactionsDto.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Reactions/GetAvailableReactionsDto.cs deleted file mode 100644 index 0b6ee20836..0000000000 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Reactions/GetAvailableReactionsDto.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Volo.CmsKit.Reactions -{ - public class GetAvailableReactionsDto - { - public string EntityType { get; set; } - } -} diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Reactions/GetForSelectionDto.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Reactions/GetForSelectionDto.cs deleted file mode 100644 index 51a4d101ad..0000000000 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Reactions/GetForSelectionDto.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Volo.CmsKit.Reactions -{ - public class GetForSelectionDto - { - public string EntityType { get; set; } - - public string EntityId { get; set; } - } -} \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Reactions/GetMyReactionsDto.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Reactions/GetMyReactionsDto.cs deleted file mode 100644 index f5f2362eb2..0000000000 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Reactions/GetMyReactionsDto.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Volo.CmsKit.Reactions -{ - public class GetMyReactionsDto - { - public string EntityType { get; set; } - - public string EntityId { get; set; } - } -} \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Reactions/GetReactionSummariesDto.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Reactions/GetReactionSummariesDto.cs deleted file mode 100644 index 354edbf0e9..0000000000 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Reactions/GetReactionSummariesDto.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Volo.CmsKit.Reactions -{ - public class GetReactionSummariesDto - { - public string EntityType { get; set; } - - public string EntityId { get; set; } - } -} \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Reactions/IReactionPublicAppService.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Reactions/IReactionPublicAppService.cs deleted file mode 100644 index bf184c3c4f..0000000000 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Reactions/IReactionPublicAppService.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Threading.Tasks; -using Volo.Abp.Application.Dtos; -using Volo.Abp.Application.Services; - -namespace Volo.CmsKit.Reactions -{ - public interface IReactionPublicAppService : IApplicationService - { - Task> GetAvailableReactions( - GetAvailableReactionsDto input - ); - - Task> GetReactionSummariesAsync( - GetReactionSummariesDto input - ); - - Task> GetMyReactions( - GetMyReactionsDto input - ); - - Task> GetForSelectionAsync(GetForSelectionDto input); - - Task CreateAsync(CreateReactionDto input); - - Task DeleteAsync(DeleteReactionDto input); - } -} diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Reactions/ReactionSummaryDto.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Reactions/ReactionSummaryDto.cs deleted file mode 100644 index 72d3007aaf..0000000000 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Reactions/ReactionSummaryDto.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Volo.CmsKit.Reactions -{ - public class ReactionSummaryDto - { - public ReactionDto Reaction { get; set; } - - public int Count { get; set; } - } -} diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/CmsKitPublicAppService.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/CmsKitPublicAppService.cs similarity index 87% rename from modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/CmsKitPublicAppService.cs rename to modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/CmsKitPublicAppService.cs index 7a9c87c954..afdc42fd1a 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/CmsKitPublicAppService.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/CmsKitPublicAppService.cs @@ -1,4 +1,4 @@ -namespace Volo.CmsKit +namespace Volo.CmsKit.Public { public abstract class CmsKitPublicAppService : CmsKitAppService { diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/CmsKitPublicApplicationModule.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/CmsKitPublicApplicationModule.cs similarity index 93% rename from modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/CmsKitPublicApplicationModule.cs rename to modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/CmsKitPublicApplicationModule.cs index 45849fd825..c8cda19e68 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/CmsKitPublicApplicationModule.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/CmsKitPublicApplicationModule.cs @@ -1,9 +1,8 @@ using Microsoft.Extensions.DependencyInjection; -using Volo.Abp.Application; using Volo.Abp.AutoMapper; using Volo.Abp.Modularity; -namespace Volo.CmsKit +namespace Volo.CmsKit.Public { [DependsOn( typeof(CmsKitCommonApplicationModule), diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Comments/CommentPublicAppService.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Comments/CommentPublicAppService.cs new file mode 100644 index 0000000000..895d8c5948 --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Comments/CommentPublicAppService.cs @@ -0,0 +1,146 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Options; +using Volo.Abp; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Application.Services; +using Volo.Abp.Authorization; +using Volo.Abp.MultiTenancy; +using Volo.Abp.Users; +using Volo.CmsKit.Comments; +using Volo.CmsKit.Users; + +namespace Volo.CmsKit.Public.Comments +{ + public class CommentPublicAppService : ApplicationService, ICommentPublicAppService + { + protected CmsKitOptions CmsKitOptions { get; } + protected ICommentRepository CommentRepository { get; } + protected ICmsUserLookupService CmsUserLookupService { get; } + + public CommentPublicAppService( + ICommentRepository commentRepository, + ICmsUserLookupService cmsUserLookupService, + IOptions cmsKitOptions) + { + CmsKitOptions = cmsKitOptions.Value; + CommentRepository = commentRepository; + CmsUserLookupService = cmsUserLookupService; + } + + public virtual async Task> GetAllForEntityAsync(string entityType, string entityId) + { + CheckAuthorizationAsync(entityType); + + var commentsWithAuthor = await CommentRepository.GetListWithAuthorsAsync(entityType, entityId); + + return new ListResultDto( + ConvertCommentsToNestedStructure(commentsWithAuthor) + ); + } + + [Authorize] + public virtual async Task CreateAsync(CreateCommentInput input) + { + var user = await CmsUserLookupService.FindByIdAsync(CurrentUser.GetId()); + + if (user == null) + { + throw new BusinessException(message: "User Not found!"); + } + + var comment = await CommentRepository.InsertAsync(new Comment( + GuidGenerator.Create(), + input.EntityType, + input.EntityId, + input.Text, + input.RepliedCommentId, + user.Id, + CurrentTenant.Id + )); + + return ObjectMapper.Map(comment); + } + + [Authorize] + public virtual async Task UpdateAsync(Guid id, UpdateCommentInput input) + { + var comment = await CommentRepository.GetAsync(id); + + if (comment.CreatorId != CurrentUser.GetId()) + { + throw new BusinessException(); + } + + comment.SetText(input.Text); + + var updatedComment = await CommentRepository.UpdateAsync(comment); + + return ObjectMapper.Map(updatedComment); + } + + [Authorize] + public virtual async Task DeleteAsync(Guid id) + { + var comment = await CommentRepository.GetAsync(id); + + if (comment.CreatorId != CurrentUser.GetId()) + { + throw new BusinessException(); + } + + await CommentRepository.DeleteAsync(id); + } + + private List ConvertCommentsToNestedStructure(List comments) + { + var parentComments = comments + .Where(c=> c.Comment.RepliedCommentId == null) + .Select(c=> ObjectMapper.Map(c.Comment)) + .ToList(); + + foreach (var parentComment in parentComments) + { + parentComment.Author = GetAuthorAsDtoFromCommentList(comments, parentComment.Id); + + parentComment.Replies = comments + .Where(c => c.Comment.RepliedCommentId == parentComment.Id) + .Select(c => ObjectMapper.Map(c.Comment)) + .ToList(); + + foreach (var reply in parentComment.Replies) + { + reply.Author = GetAuthorAsDtoFromCommentList(comments, reply.Id); + } + } + + return parentComments; + } + + private async Task CheckAuthorizationAsync(string entityType) + { + if (await IsPublicEntity(entityType)) + { + return; + } + + if (!CurrentUser.IsAuthenticated) + { + throw new AbpAuthorizationException(L["CommentAuthorizationExceptionMessage"]); + } + } + + private async Task IsPublicEntity(string entityType) + { + return CmsKitOptions.PublicCommentEntities.Contains(entityType); + } + + private CmsUserDto GetAuthorAsDtoFromCommentList(List comments, Guid commentId) + { + return ObjectMapper.Map(comments.Single(c => c.Comment.Id == commentId).Author); + } + } +} diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/PublicApplicationAutoMapperProfile.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/PublicApplicationAutoMapperProfile.cs similarity index 52% rename from modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/PublicApplicationAutoMapperProfile.cs rename to modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/PublicApplicationAutoMapperProfile.cs index e88dd5c4b3..92b6cc0895 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/PublicApplicationAutoMapperProfile.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/PublicApplicationAutoMapperProfile.cs @@ -1,6 +1,10 @@ using AutoMapper; +using Volo.Abp.AutoMapper; +using Volo.CmsKit.Comments; +using Volo.CmsKit.Public.Comments; +using Volo.CmsKit.Users; -namespace Volo.CmsKit +namespace Volo.CmsKit.Public { public class PublicApplicationAutoMapperProfile : Profile { @@ -9,6 +13,10 @@ namespace Volo.CmsKit /* You can configure your AutoMapper mapping configuration here. * Alternatively, you can split your mapping configurations * into multiple profile classes for a better organization. */ + + CreateMap(); + CreateMap().Ignore(x=> x.Author); + CreateMap().Ignore(x=> x.Replies).Ignore(x=> x.Author); } } -} \ No newline at end of file +} diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Reactions/ReactionPublicAppService.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Reactions/ReactionPublicAppService.cs new file mode 100644 index 0000000000..6a1431f065 --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Reactions/ReactionPublicAppService.cs @@ -0,0 +1,90 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Users; +using Volo.CmsKit.Reactions; + +namespace Volo.CmsKit.Public.Reactions +{ + //TODO: Authorization + public class ReactionPublicAppService : CmsKitPublicAppService, IReactionPublicAppService + { + protected IReactionDefinitionStore ReactionDefinitionStore { get; } + + protected IUserReactionRepository UserReactionRepository { get; } + + protected ReactionManager ReactionManager { get; } + + public ReactionPublicAppService( + IReactionDefinitionStore reactionDefinitionStore, + IUserReactionRepository userReactionRepository, + ReactionManager reactionManager) + { + ReactionDefinitionStore = reactionDefinitionStore; + UserReactionRepository = userReactionRepository; + ReactionManager = reactionManager; + } + + public virtual async Task> GetForSelectionAsync(string entityType, string entityId) + { + var summaries = await ReactionManager.GetSummariesAsync(entityType, entityId); + + var userReactions = CurrentUser.IsAuthenticated ? + (await UserReactionRepository + .GetListForUserAsync( + CurrentUser.GetId(), + entityType, + entityId + )).ToDictionary(x => x.ReactionName, x => x) : null; + + var reactionWithSelectionDtos = new List(); + + foreach (var summary in summaries) + { + reactionWithSelectionDtos.Add( + new ReactionWithSelectionDto + { + Reaction = ConvertToReactionDto(summary.Reaction), + Count = summary.Count, + IsSelectedByCurrentUser = userReactions?.ContainsKey(summary.Reaction.Name) ?? false + } + ); + } + + return new ListResultDto(reactionWithSelectionDtos); + } + + [Authorize] + public virtual async Task CreateAsync(CreateReactionDto input) + { + await ReactionManager.CreateAsync( + CurrentUser.GetId(), + input.EntityType, + input.EntityId, + input.ReactionName + ); + } + + [Authorize] + public virtual async Task DeleteAsync(DeleteReactionDto input) + { + await ReactionManager.DeleteAsync( + CurrentUser.GetId(), + input.EntityType, + input.EntityId, + input.ReactionName + ); + } + + private ReactionDto ConvertToReactionDto(ReactionDefinition reactionDefinition) + { + return new ReactionDto + { + Name = reactionDefinition.Name, + DisplayName = reactionDefinition.DisplayName?.Localize(StringLocalizerFactory) + }; + } + } +} diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Reactions/ReactionPublicAppService.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Reactions/ReactionPublicAppService.cs deleted file mode 100644 index f5cdae199d..0000000000 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Reactions/ReactionPublicAppService.cs +++ /dev/null @@ -1,135 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Volo.Abp.Application.Dtos; -using Volo.Abp.Users; - -namespace Volo.CmsKit.Reactions -{ - //TODO: Authorization - public class ReactionPublicAppService : CmsKitPublicAppService, IReactionPublicAppService - { - protected IReactionDefinitionStore ReactionDefinitionStore { get; } - - protected IUserReactionRepository UserReactionRepository { get; } - - protected ReactionManager ReactionManager { get; } - - public ReactionPublicAppService( - IReactionDefinitionStore reactionDefinitionStore, - IUserReactionRepository userReactionRepository, - ReactionManager reactionManager) - { - ReactionDefinitionStore = reactionDefinitionStore; - UserReactionRepository = userReactionRepository; - ReactionManager = reactionManager; - } - - public virtual async Task> GetAvailableReactions( - GetAvailableReactionsDto input) - { - var reactionDefinitions = await ReactionManager - .GetAvailableReactionsAsync( - input.EntityType - ); - - var reactionDtos = reactionDefinitions - .Select(ConvertToReactionDto) - .ToList(); - - return new ListResultDto(reactionDtos); - } - - public virtual async Task> GetReactionSummariesAsync(GetReactionSummariesDto input) - { - var summaries = await ReactionManager.GetSummariesAsync(input.EntityType, input.EntityId); - - var summaryDtos = summaries - .Select(summary => new ReactionSummaryDto - { - Count = summary.Count, - Reaction = ConvertToReactionDto(summary.Reaction) - }) - .ToList(); - - return new ListResultDto(summaryDtos); - } - - public virtual async Task> GetMyReactions(GetMyReactionsDto input) - { - var userReactions = await ReactionManager.GetUserReactionsAsync( - CurrentUser.GetId(), - input.EntityType, - input.EntityId - ); - - var reactionDtos = userReactions - .Select(ConvertToReactionDto) - .ToList(); - - return new ListResultDto(reactionDtos); - } - - public virtual async Task> GetForSelectionAsync(GetForSelectionDto input) - { - var reactionDefinitions = await ReactionManager - .GetAvailableReactionsAsync( - input.EntityType - ); - var summaries = - (await ReactionManager.GetSummariesAsync(input.EntityType, input.EntityId)) - .ToDictionary(x => x.Reaction.Name, x => x.Count); - - var userReactions = await ReactionManager.GetUserReactionsAsync( - CurrentUser.GetId(), - input.EntityType, - input.EntityId - ); - - var reactionDtos = new List(); - - foreach (var reactionDefinition in reactionDefinitions) - { - reactionDtos.Add( - new ReactionWithSelectionDto - { - Reaction = ConvertToReactionDto(reactionDefinition), - Count = summaries.GetOrDefault(reactionDefinition.Name), - IsSelectedByCurrentUser = userReactions.Any(x => x.Name == reactionDefinition.Name) - } - ); - } - - return new ListResultDto(reactionDtos); - } - - public virtual async Task CreateAsync(CreateReactionDto input) - { - await ReactionManager.CreateAsync( - CurrentUser.GetId(), - input.EntityType, - input.EntityId, - input.ReactionName - ); - } - - public virtual async Task DeleteAsync(DeleteReactionDto input) - { - await ReactionManager.DeleteAsync( - CurrentUser.GetId(), - input.EntityType, - input.EntityId, - input.ReactionName - ); - } - - private ReactionDto ConvertToReactionDto(ReactionDefinition reactionDefinition) - { - return new ReactionDto - { - Name = reactionDefinition.Name, - DisplayName = reactionDefinition.DisplayName?.Localize(StringLocalizerFactory) - }; - } - } -} diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.HttpApi.Client/Volo/CmsKit/CmsKitPublicHttpApiClientModule.cs b/modules/cms-kit/src/Volo.CmsKit.Public.HttpApi.Client/Volo/CmsKit/Public/CmsKitPublicHttpApiClientModule.cs similarity index 91% rename from modules/cms-kit/src/Volo.CmsKit.Public.HttpApi.Client/Volo/CmsKit/CmsKitPublicHttpApiClientModule.cs rename to modules/cms-kit/src/Volo.CmsKit.Public.HttpApi.Client/Volo/CmsKit/Public/CmsKitPublicHttpApiClientModule.cs index 57cc7f6972..f8a8ee0fe3 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.HttpApi.Client/Volo/CmsKit/CmsKitPublicHttpApiClientModule.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.HttpApi.Client/Volo/CmsKit/Public/CmsKitPublicHttpApiClientModule.cs @@ -1,7 +1,8 @@ using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Http.Client; using Volo.Abp.Modularity; -namespace Volo.CmsKit +namespace Volo.CmsKit.Public { [DependsOn( typeof(CmsKitPublicApplicationContractsModule), diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.HttpApi/Volo/CmsKit/Public/CmsKitPublicControllerBase.cs b/modules/cms-kit/src/Volo.CmsKit.Public.HttpApi/Volo/CmsKit/Public/CmsKitPublicControllerBase.cs new file mode 100644 index 0000000000..79bb1b63ba --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Public.HttpApi/Volo/CmsKit/Public/CmsKitPublicControllerBase.cs @@ -0,0 +1,8 @@ +using Volo.CmsKit.Controllers; + +namespace Volo.CmsKit.Public +{ + public abstract class CmsKitPublicControllerBase : CmsKitControllerBase + { + } +} diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.HttpApi/Volo/CmsKit/CmsKitPublicHttpApiModule.cs b/modules/cms-kit/src/Volo.CmsKit.Public.HttpApi/Volo/CmsKit/Public/CmsKitPublicHttpApiModule.cs similarity index 95% rename from modules/cms-kit/src/Volo.CmsKit.Public.HttpApi/Volo/CmsKit/CmsKitPublicHttpApiModule.cs rename to modules/cms-kit/src/Volo.CmsKit.Public.HttpApi/Volo/CmsKit/Public/CmsKitPublicHttpApiModule.cs index 00ab062a07..7e667ace38 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.HttpApi/Volo/CmsKit/CmsKitPublicHttpApiModule.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.HttpApi/Volo/CmsKit/Public/CmsKitPublicHttpApiModule.cs @@ -1,7 +1,7 @@ using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Modularity; -namespace Volo.CmsKit +namespace Volo.CmsKit.Public { [DependsOn( typeof(CmsKitPublicApplicationContractsModule), diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.HttpApi/Volo/CmsKit/Public/Comments/CommentPublicController.cs b/modules/cms-kit/src/Volo.CmsKit.Public.HttpApi/Volo/CmsKit/Public/Comments/CommentPublicController.cs new file mode 100644 index 0000000000..22c121ce58 --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Public.HttpApi/Volo/CmsKit/Public/Comments/CommentPublicController.cs @@ -0,0 +1,48 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp; +using Volo.Abp.Application.Dtos; + +namespace Volo.CmsKit.Public.Comments +{ + [RemoteService(Name = CmsKitPublicRemoteServiceConsts.RemoteServiceName)] + [Area("cms-kit")] + [Route("api/cms-kit-public/comments")] + public class CommentPublicController : CmsKitPublicControllerBase, ICommentPublicAppService + { + public ICommentPublicAppService CommentPublicAppService { get; } + + public CommentPublicController(ICommentPublicAppService commentPublicAppService) + { + CommentPublicAppService = commentPublicAppService; + } + + [HttpGet] + [Route("{entityType}/{entityId}")] + public Task> GetAllForEntityAsync(string entityType, string entityId) + { + return CommentPublicAppService.GetAllForEntityAsync(entityType, entityId); + } + + [HttpPost] + public Task CreateAsync(CreateCommentInput input) + { + return CommentPublicAppService.CreateAsync(input); + } + + [HttpPost] + [Route("{id}")] + public Task UpdateAsync(Guid id, UpdateCommentInput input) + { + return CommentPublicAppService.UpdateAsync(id, input); + } + + [HttpDelete] + [Route("update")] + public Task DeleteAsync(Guid id) + { + return CommentPublicAppService.DeleteAsync(id); + } + } +} diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.HttpApi/Volo/CmsKit/Reactions/ReactionPublicController.cs b/modules/cms-kit/src/Volo.CmsKit.Public.HttpApi/Volo/CmsKit/Public/Reactions/ReactionPublicController.cs similarity index 54% rename from modules/cms-kit/src/Volo.CmsKit.Public.HttpApi/Volo/CmsKit/Reactions/ReactionPublicController.cs rename to modules/cms-kit/src/Volo.CmsKit.Public.HttpApi/Volo/CmsKit/Public/Reactions/ReactionPublicController.cs index 4deb3796a3..cbab4a649f 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.HttpApi/Volo/CmsKit/Reactions/ReactionPublicController.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.HttpApi/Volo/CmsKit/Public/Reactions/ReactionPublicController.cs @@ -3,12 +3,12 @@ using Microsoft.AspNetCore.Mvc; using Volo.Abp; using Volo.Abp.Application.Dtos; -namespace Volo.CmsKit.Reactions +namespace Volo.CmsKit.Public.Reactions { [RemoteService(Name = CmsKitPublicRemoteServiceConsts.RemoteServiceName)] [Area("cms-kit")] [Route("api/cms-kit-public/reactions")] - public class ReactionPublicController : CmsKitPublicController, IReactionPublicAppService + public class ReactionPublicController : CmsKitPublicControllerBase, IReactionPublicAppService { protected IReactionPublicAppService ReactionPublicAppService { get; } @@ -18,31 +18,10 @@ namespace Volo.CmsKit.Reactions } [HttpGet] - [Route("available")] - public virtual Task> GetAvailableReactions(GetAvailableReactionsDto input) + [Route("{entityType}/{entityId}")] + public virtual Task> GetForSelectionAsync(string entityType, string entityId) { - return ReactionPublicAppService.GetAvailableReactions(input); - } - - [HttpGet] - [Route("summaries")] - public virtual Task> GetReactionSummariesAsync(GetReactionSummariesDto input) - { - return ReactionPublicAppService.GetReactionSummariesAsync(input); - } - - [HttpGet] - [Route("my")] - public virtual Task> GetMyReactions(GetMyReactionsDto input) - { - return ReactionPublicAppService.GetMyReactions(input); - } - - [HttpGet] - [Route("selection")] //TODO: Consider to rename! - public virtual Task> GetForSelectionAsync(GetForSelectionDto input) - { - return ReactionPublicAppService.GetForSelectionAsync(input); + return ReactionPublicAppService.GetForSelectionAsync(entityType, entityId); } [HttpPut] diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/CmsKitPublicWebAutoMapperProfile.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Web/CmsKitPublicWebAutoMapperProfile.cs index 338adbdb0f..da60048298 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/CmsKitPublicWebAutoMapperProfile.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/CmsKitPublicWebAutoMapperProfile.cs @@ -1,6 +1,6 @@ using AutoMapper; -namespace Volo.CmsKit.Web +namespace Volo.CmsKit.Public.Web { public class CmsKitPublicWebAutoMapperProfile : Profile { diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/CmsKitPublicWebModule.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Web/CmsKitPublicWebModule.cs index 19b819d42c..2b4a2e8fce 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/CmsKitPublicWebModule.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/CmsKitPublicWebModule.cs @@ -6,9 +6,10 @@ using Volo.Abp.Modularity; using Volo.Abp.UI.Navigation; using Volo.Abp.VirtualFileSystem; using Volo.CmsKit.Localization; -using Volo.CmsKit.Web.Menus; +using Volo.CmsKit.Public.Web.Menus; +using Volo.CmsKit.Web; -namespace Volo.CmsKit.Web +namespace Volo.CmsKit.Public.Web { [DependsOn( typeof(CmsKitPublicHttpApiModule), diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Controllers/CmsKitPublicWidgetsController.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Controllers/CmsKitPublicWidgetsController.cs new file mode 100644 index 0000000000..29d6e27d1f --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Controllers/CmsKitPublicWidgetsController.cs @@ -0,0 +1,21 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Commenting; +using Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.ReactionSelection; + +namespace Volo.CmsKit.Public.Web.Controllers +{ + //TODO: Consider to move to an area, but also consider to not have the same prefix with API Controllers which can be problem in case of a tiered architecture + public class CmsKitPublicWidgetsController : CmsKitPublicControllerBase + { + public async Task ReactionSelection(string entityType, string entityId) + { + return ViewComponent(typeof(ReactionSelectionViewComponent), new {entityType, entityId}); + } + + public async Task Commenting(string entityType, string entityId) + { + return ViewComponent(typeof(CommentingViewComponent), new {entityType, entityId}); + } + } +} diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Menus/CmsKitPublicMenuContributor.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Menus/CmsKitPublicMenuContributor.cs index 7bda87f1cc..34093ed39c 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Menus/CmsKitPublicMenuContributor.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Menus/CmsKitPublicMenuContributor.cs @@ -1,7 +1,7 @@ using System.Threading.Tasks; using Volo.Abp.UI.Navigation; -namespace Volo.CmsKit.Web.Menus +namespace Volo.CmsKit.Public.Web.Menus { public class CmsKitPublicMenuContributor : IMenuContributor { diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Menus/CmsKitPublicMenus.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Menus/CmsKitPublicMenus.cs index 00aab7da14..5b8e026122 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Menus/CmsKitPublicMenus.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Menus/CmsKitPublicMenus.cs @@ -1,4 +1,4 @@ -namespace Volo.CmsKit.Web.Menus +namespace Volo.CmsKit.Public.Web.Menus { public class CmsKitPublicMenus { diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/CommentingScriptBundleContributor.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/CommentingScriptBundleContributor.cs new file mode 100644 index 0000000000..3e27c22b41 --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/CommentingScriptBundleContributor.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using Volo.Abp.AspNetCore.Mvc.UI.Bundling; + +namespace Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Commenting +{ + public class CommentingScriptBundleContributor : BundleContributor + { + public override void ConfigureBundle(BundleConfigurationContext context) + { + context.Files.AddIfNotContains("/Pages/CmsKit/Shared/Components/Commenting/default.js"); + } + } +} diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/CommentingStyleBundleContributor.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/CommentingStyleBundleContributor.cs new file mode 100644 index 0000000000..7e4445bbb2 --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/CommentingStyleBundleContributor.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using Volo.Abp.AspNetCore.Mvc.UI.Bundling; + +namespace Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Commenting +{ + public class CommentingStyleBundleContributor : BundleContributor + { + public override void ConfigureBundle(BundleConfigurationContext context) + { + context.Files.AddIfNotContains("/Pages/CmsKit/Shared/Components/Commenting/default.css"); + } + } +} diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/CommentingViewComponent.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/CommentingViewComponent.cs new file mode 100644 index 0000000000..ceafd07134 --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/CommentingViewComponent.cs @@ -0,0 +1,53 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.UI.Widgets; +using Volo.CmsKit.Public.Comments; + +namespace Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Commenting +{ + [ViewComponent(Name = "CmsCommenting")] + [Widget( + ScriptTypes = new[] {typeof(CommentingScriptBundleContributor)}, + StyleTypes = new[] {typeof(CommentingStyleBundleContributor)}, + RefreshUrl = "/CmsKitPublicWidgets/Commenting" + )] + public class CommentingViewComponent : AbpViewComponent + { + public ICommentPublicAppService CommentPublicAppService { get; } + + public CommentingViewComponent( + ICommentPublicAppService commentPublicAppService) + { + CommentPublicAppService = commentPublicAppService; + } + + public virtual async Task InvokeAsync(string entityType, string entityId, string loginUrl = null) + { + var result = await CommentPublicAppService.GetAllForEntityAsync(entityType, entityId); + + var viewModel = new CommentingViewModel + { + EntityId = entityId, + EntityType = entityType, + LoginUrl = loginUrl, + Comments = result.Items.ToList() + }; + + return View("~/Pages/CmsKit/Shared/Components/Commenting/Default.cshtml", viewModel); + } + + public class CommentingViewModel + { + public string EntityType { get; set; } + + public string EntityId { get; set; } + + public string LoginUrl { get; set; } + + public List Comments { get; set; } + } + } +} diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/Default.cshtml b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/Default.cshtml new file mode 100644 index 0000000000..743f4f7d9c --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/Default.cshtml @@ -0,0 +1,147 @@ +@using Microsoft.AspNetCore.Html +@using Microsoft.AspNetCore.Mvc.Localization +@using Volo.Abp.Users +@using Volo.CmsKit.Comments +@using Volo.CmsKit.Localization +@using Volo.CmsKit.Public.Comments +@inject ICurrentUser CurrentUser +@inject IHtmlLocalizer L +@model Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Commenting.CommentingViewComponent.CommentingViewModel + +@{ + Func GetCommentTitle(CmsUserDto author, DateTime creationTime) => + @ + @((string.IsNullOrWhiteSpace(author.Name) + ? author.UserName + : author.Name + " " + author.Surname).Trim()) + @creationTime.ToString() + ; +} +@{ + Func GetCommentArea(Guid? repliedCommentId, bool cancelButton = false) => + @
    +
    + +
    + + +
    + @if (cancelButton) + { + @L["Cancel"] + } + @L["Send"] +
    +
    ; +} +@{ + Func GetCommentContentArea(Guid id, Guid authorId, bool isReply, string text) => + @
    +
    +

    + @text +

    +
    + + @if (!isReply) + { + @if (CurrentUser.IsAuthenticated) + { + + @L["Reply"] + + } + else + { + @L["LoginToReply"] + } + } + @if (authorId == CurrentUser.Id) + { + @if (!isReply) + { + | + } + + + @L["Delete"] + + + | + + + @L["Edit"] + + } + +
    ; +} + +
    + +
    +

    + + @L["Comments"] +

    + @foreach (var comment in Model.Comments) + { +
    +
    +
    + @GetCommentTitle(comment.Author, comment.CreationTime).Invoke(null) +
    + + @GetCommentContentArea(comment.Id, comment.Author.Id, false, comment.Text).Invoke(null) + + @if (CurrentUser.IsAuthenticated) + { + @GetCommentArea(comment.Id, true).Invoke(null) + } + + @if (comment.Replies.Any()) + { +
    + @foreach (var reply in comment.Replies) + { +
    +
    + +
    + @GetCommentTitle(reply.Author, reply.CreationTime).Invoke(null) +
    + + @GetCommentContentArea(reply.Id, reply.Author.Id, true, reply.Text).Invoke(null) +
    +
    + } +
    + } +
    +
    + } +
    + @if (CurrentUser.IsAuthenticated) + { + @GetCommentArea(null).Invoke(null) + } + else if (!string.IsNullOrWhiteSpace(Model.LoginUrl)) + { + + } +
    +
    +
    diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/default.css b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/default.css new file mode 100644 index 0000000000..5f282702bb --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/default.css @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/default.js b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/default.js new file mode 100644 index 0000000000..4fb29abb51 --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/default.js @@ -0,0 +1,161 @@ +(function ($) { + + var l = abp.localization.getResource('CmsKit'); + + $(document).ready(function () { + + abp.widgets.CmsCommenting = function ($widget) { + var widgetManager = $widget.data('abp-widget-manager'); + var $commentArea = $widget.find('.cms-comment-area'); + + function getFilters() { + return { + entityType: $commentArea.attr('data-entity-type'), + entityId: $commentArea.attr('data-entity-id') + }; + } + + function registerEditLinks($container) { + $container.find('.comment-edit-link').each(function () { + var $link = $(this); + $link.on('click', function (e) { + e.preventDefault(); + + var commentId = $link.data('id'); + + var $relatedCommentContentArea = $container.find('.cms-comment-content-area[data-id='+ commentId +']'); + var $relatedCommentEditFormArea = $container.find('.cms-comment-edit-area[data-id='+ commentId +']'); + + $relatedCommentContentArea.hide(); + $relatedCommentEditFormArea.show(); + $link.removeAttr('href'); + }); + }); + $container.find('.comment-edit-cancel-button').each(function () { + var $button = $(this); + $button.on('click', function (e) { + e.preventDefault(); + + var commentId = $button.data('id'); + + var $relatedCommentContentArea = $container.find('.cms-comment-content-area[data-id='+ commentId +']'); + var $relatedCommentEditFormArea = $container.find('.cms-comment-edit-area[data-id='+ commentId +']'); + var $link = $container.find('.comment-edit-link[data-id='+ commentId +']'); + + $relatedCommentContentArea.show(); + $relatedCommentEditFormArea.hide(); + $link.attr('href','#'); + }); + }); + } + + function registerReplyLinks($container) { + $container.find('.comment-reply-link').each(function () { + var $link = $(this); + $link.on('click', function (e) { + e.preventDefault(); + + var replyCommentId = $link.data('reply-id'); + + var $relatedCommentArea = $container.find('.cms-comment-form-area[data-reply-id='+ replyCommentId +']'); + + $relatedCommentArea.show(); + $link.removeAttr('href'); + }); + }); + $container.find('.reply-cancel-button').each(function () { + var $button = $(this); + $button.on('click', function (e) { + e.preventDefault(); + + var replyCommentId = $button.data('reply-id'); + + var $relatedCommentArea = $container.find('.cms-comment-form-area[data-reply-id='+ replyCommentId +']'); + var $replyLink = $container.find('.comment-reply-link[data-reply-id='+ replyCommentId +']'); + + $relatedCommentArea.hide(); + $replyLink.attr('href','#'); + }); + }); + } + + function registerDeleteLinks($container) { + $container.find('.comment-delete-link').each(function () { + var $link = $(this); + $link.on('click', '', function (e) { + e.preventDefault(); + + abp.message.confirm(l("MessageDeletionConfirmationMessage"), function (ok) { + if (ok){ + volo.cmsKit.public.comments.commentPublic.delete($link.data('id') + ).then(function () { + widgetManager.refresh($widget); + }); + } + }); + }); + }); + } + + function registerUpdateOfNewComment($container) { + $container.find('.cms-comment-update-form').each(function () { + var $form = $(this); + $form.submit(function (e) { + e.preventDefault(); + var formAsObject = $form.serializeFormToObject(); + volo.cmsKit.public.comments.commentPublic.update( + formAsObject.id, + { + text: formAsObject.commentText + } + ).then(function () { + widgetManager.refresh($widget); + }); + }); + }); + } + + function registerSubmissionOfNewComment($container) { + $container.find('.cms-comment-form').each(function () { + var $form = $(this); + $form.submit(function (e) { + e.preventDefault(); + var formAsObject = $form.serializeFormToObject(); + volo.cmsKit.public.comments.commentPublic.create( + $.extend(getFilters(), { + repliedCommentId: formAsObject.repliedCommentId, + text: formAsObject.commentText + }) + ).then(function () { + widgetManager.refresh($widget); + }); + }); + }); + } + + function init() { + registerReplyLinks($widget); + registerEditLinks($widget); + registerDeleteLinks($widget); + + registerUpdateOfNewComment($widget); + registerSubmissionOfNewComment($widget); + } + + return { + init: init, + getFilters: getFilters + }; + }; + + $('.abp-widget-wrapper[data-widget-name="CmsCommenting"]') + .each(function () { + var widgetManager = new abp.WidgetManager({ + wrapper: $(this), + }); + + widgetManager.init(); + }); + }); + +})(jQuery); diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/ReactionSelection/Default.cshtml b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/ReactionSelection/Default.cshtml index 1d1c4e7306..9096f8f154 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/ReactionSelection/Default.cshtml +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/ReactionSelection/Default.cshtml @@ -1,22 +1,25 @@ -@model Volo.CmsKit.Web.Pages.CmsKit.Shared.Components.ReactionSelection.ReactionSelectionViewModel - -
    - Pick a reaction: - @foreach (var reaction in Model.Reactions) - { - - - - } -
    -
    - Current reactions: - @foreach (var reaction in Model.Reactions.Where(r => r.Count > 0)) - { - - - @reaction.Count - - } -
    +@inject ICurrentUser CurrentUser +@using Volo.Abp.Users +@model Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.ReactionSelection.ReactionSelectionViewComponent.ReactionSelectionViewModel + + + @if (CurrentUser.IsAuthenticated) + { + + + } + @foreach (var reaction in Model.Reactions.Where(r => r.Count > 0)) + { + + + @(reaction.Count) + + } diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/ReactionSelection/ReactionSelectionScriptBundleContributor.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/ReactionSelection/ReactionSelectionScriptBundleContributor.cs index f6eb2ebe25..d776725dc3 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/ReactionSelection/ReactionSelectionScriptBundleContributor.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/ReactionSelection/ReactionSelectionScriptBundleContributor.cs @@ -1,9 +1,7 @@ using System.Collections.Generic; using Volo.Abp.AspNetCore.Mvc.UI.Bundling; -using Volo.Abp.AspNetCore.Mvc.UI.Packages.Bootstrap; -using Volo.Abp.Modularity; -namespace Volo.CmsKit.Web.Pages.CmsKit.Shared.Components.ReactionSelection +namespace Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.ReactionSelection { public class ReactionSelectionScriptBundleContributor : BundleContributor { diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/ReactionSelection/ReactionSelectionStyleBundleContributor.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/ReactionSelection/ReactionSelectionStyleBundleContributor.cs index e58f210455..11d6453236 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/ReactionSelection/ReactionSelectionStyleBundleContributor.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/ReactionSelection/ReactionSelectionStyleBundleContributor.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using Volo.Abp.AspNetCore.Mvc.UI.Bundling; -namespace Volo.CmsKit.Web.Pages.CmsKit.Shared.Components.ReactionSelection +namespace Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.ReactionSelection { public class ReactionSelectionStyleBundleContributor : BundleContributor { diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/ReactionSelection/ReactionSelectionViewComponent.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/ReactionSelection/ReactionSelectionViewComponent.cs index 45ae31c3b1..0af928e0f0 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/ReactionSelection/ReactionSelectionViewComponent.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/ReactionSelection/ReactionSelectionViewComponent.cs @@ -1,16 +1,20 @@ using System.Collections.Generic; using System.Threading.Tasks; +using JetBrains.Annotations; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using Volo.Abp.AspNetCore.Mvc; using Volo.Abp.AspNetCore.Mvc.UI.Widgets; -using Volo.CmsKit.Reactions; +using Volo.CmsKit.Public.Reactions; +using Volo.CmsKit.Web; -namespace Volo.CmsKit.Web.Pages.CmsKit.Shared.Components.ReactionSelection +namespace Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.ReactionSelection { + [ViewComponent(Name = "CmsReactionSelection")] [Widget( ScriptTypes = new[] {typeof(ReactionSelectionScriptBundleContributor)}, - StyleTypes = new[] {typeof(ReactionSelectionStyleBundleContributor)} + StyleTypes = new[] {typeof(ReactionSelectionStyleBundleContributor)}, + RefreshUrl = "/CmsKitPublicWidgets/ReactionSelection" )] public class ReactionSelectionViewComponent : AbpViewComponent { @@ -30,12 +34,7 @@ namespace Volo.CmsKit.Web.Pages.CmsKit.Shared.Components.ReactionSelection string entityType, string entityId) { - var result = await ReactionPublicAppService.GetForSelectionAsync( - new GetForSelectionDto - { - EntityType = entityType, - EntityId = entityId - }); + var result = await ReactionPublicAppService.GetForSelectionAsync(entityType, entityId); var viewModel = new ReactionSelectionViewModel { @@ -59,5 +58,30 @@ namespace Volo.CmsKit.Web.Pages.CmsKit.Shared.Components.ReactionSelection return View("~/Pages/CmsKit/Shared/Components/ReactionSelection/Default.cshtml", viewModel); } + + public class ReactionSelectionViewModel + { + public string EntityType { get; set; } + + public string EntityId { get; set; } + + public List Reactions { get; set; } + } + + public class ReactionViewModel + { + [NotNull] + public string Name { get; set; } + + [CanBeNull] + public string DisplayName { get; set; } + + [NotNull] + public string Icon { get; set; } + + public int Count { get; set; } + + public bool IsSelectedByCurrentUser { get; set; } + } } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/ReactionSelection/ReactionSelectionViewModel.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/ReactionSelection/ReactionSelectionViewModel.cs deleted file mode 100644 index bd22fd4229..0000000000 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/ReactionSelection/ReactionSelectionViewModel.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Collections.Generic; - -namespace Volo.CmsKit.Web.Pages.CmsKit.Shared.Components.ReactionSelection -{ - public class ReactionSelectionViewModel - { - public string EntityType { get; set; } - - public string EntityId { get; set; } - - public List Reactions { get; set; } - } -} diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/ReactionSelection/ReactionViewModel.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/ReactionSelection/ReactionViewModel.cs deleted file mode 100644 index 706ec1306f..0000000000 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/ReactionSelection/ReactionViewModel.cs +++ /dev/null @@ -1,21 +0,0 @@ -using JetBrains.Annotations; - -namespace Volo.CmsKit.Web.Pages.CmsKit.Shared.Components.ReactionSelection -{ - public class ReactionViewModel - { - [NotNull] - public string Name { get; set; } - - [CanBeNull] - public string DisplayName { get; set; } - - [NotNull] - public string Icon { get; set; } - - public int Count { get; set; } - - public bool IsSelectedByCurrentUser { get; set; } - - } -} diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/ReactionSelection/default.css b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/ReactionSelection/default.css index 844fefaafa..8e27846c5c 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/ReactionSelection/default.css +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/ReactionSelection/default.css @@ -1,8 +1,13 @@ -.cms-reaction-icon +.cms-reaction-select-icon { cursor: pointer; } +.cms-reaction-icon +{ + cursor: pointer; + padding: 3px 5px 5px; +} .cms-reaction-icon-selected { - border: 1px solid gray; + background-color: #eef; } diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/ReactionSelection/default.js b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/ReactionSelection/default.js index 50daa6f162..38e5da93e5 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/ReactionSelection/default.js +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/ReactionSelection/default.js @@ -1,25 +1,78 @@ -(function () { +(function ($) { + + var l = abp.localization.getResource('CmsKit'); + + var myDefaultWhiteList = $.fn.tooltip.Constructor.Default.whiteList; + + if (myDefaultWhiteList.span.indexOf('data-reaction-name') < 0) { + myDefaultWhiteList.span.push('data-reaction-name'); + } + $(document).ready(function () { - function initReactionSelection() { - var $wrapper = $(this); - var $availableReactions = $wrapper.find('.cms-reaction-selection-available-reactions'); - - $wrapper.find('.cms-reaction-icon').each(function () { - var $icon = $(this); - $icon.click(function () { - var methodName = $icon.hasClass('cms-reaction-icon-selected') ? 'delete' : 'create'; - volo.cmsKit.reactions.reactionPublic[methodName]({ - entityType: $wrapper.attr('data-entity-type'), - entityId: $wrapper.attr('data-entity-id'), - reactionName: $icon.attr('data-name') - }).then(function () { - location.reload(); //TODO: JUST TESTING !!!!!!!! + abp.widgets.CmsReactionSelection = function ($widget) { + var widgetManager = $widget.data('abp-widget-manager'); + var $reactionArea = $widget.find('.cms-reaction-area'); + var $selectIcon = $widget.find('.cms-reaction-select-icon'); + var $popoverContent = $widget.find('.cms-reaction-selection-popover-content'); + + function getFilters() { + return { + entityType: $reactionArea.attr('data-entity-type'), + entityId: $reactionArea.attr('data-entity-id') + }; + } + + function registerClickOfReactionIcons($container) { + $container.find('.cms-reaction-icon').each(function () { + var $icon = $(this); + var reactionName = $icon.attr('data-reaction-name'); + if ($icon.attr('data-click-action') === 'false'){ + return; + } + $icon.click(function () { + var methodName = $icon.hasClass('cms-reaction-icon-selected') ? 'delete' : 'create'; + volo.cmsKit.public.reactions.reactionPublic[methodName]( + $.extend(getFilters(), { + reactionName: reactionName + }) + ).then(function () { + $selectIcon.popover('hide'); + widgetManager.refresh($widget); + }); }); }); - }); - } + } + + function init() { - $('.cms-reaction-selection').each(initReactionSelection); + $selectIcon.popover({ + placement: 'right', + html: true, + trigger: 'focus', + title: l('PickYourReaction'), + content: $popoverContent.html() + }).on('shown.bs.popover', function () { + var $popover = $('#' + $selectIcon.attr('aria-describedby')); + registerClickOfReactionIcons($popover); + }); + + registerClickOfReactionIcons($widget); + } + + return { + init: init, + getFilters: getFilters + }; + }; + + $('.abp-widget-wrapper[data-widget-name="CmsReactionSelection"]') + .each(function () { + var widgetManager = new abp.WidgetManager({ + wrapper: $(this), + }); + + widgetManager.init(); + }); }); -})(); +})(jQuery); diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/PublicPageModel.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/PublicPageModel.cs index af143d5a8f..0ea29457e9 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/PublicPageModel.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/PublicPageModel.cs @@ -1,7 +1,7 @@ using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; using Volo.CmsKit.Localization; -namespace Volo.CmsKit.Web.Pages +namespace Volo.CmsKit.Public.Web.Pages { /* Inherit your PageModel classes from this class. */ diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Volo.CmsKit.Public.Web.csproj b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Volo.CmsKit.Public.Web.csproj index c3a45b7838..102e2038d1 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Volo.CmsKit.Public.Web.csproj +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Volo.CmsKit.Public.Web.csproj @@ -8,14 +8,14 @@ $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; true Library - Volo.CmsKit.Web + Volo.CmsKit.Public.Web true - + diff --git a/modules/cms-kit/src/Volo.CmsKit.Web/CmsKitWebModule.cs b/modules/cms-kit/src/Volo.CmsKit.Web/CmsKitWebModule.cs index 6ec041b690..c54ec27feb 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Web/CmsKitWebModule.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Web/CmsKitWebModule.cs @@ -1,5 +1,6 @@ using Volo.Abp.Modularity; using Volo.CmsKit.Admin.Web; +using Volo.CmsKit.Public.Web; namespace Volo.CmsKit.Web { diff --git a/modules/cms-kit/src/Volo.CmsKit.Web/Volo.CmsKit.Web.csproj b/modules/cms-kit/src/Volo.CmsKit.Web/Volo.CmsKit.Web.csproj index 84a31c94db..74d3bbe84f 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Web/Volo.CmsKit.Web.csproj +++ b/modules/cms-kit/src/Volo.CmsKit.Web/Volo.CmsKit.Web.csproj @@ -19,7 +19,7 @@ - + diff --git a/modules/cms-kit/test/Volo.CmsKit.Application.Tests/CmsKitApplicationTestBase.cs b/modules/cms-kit/test/Volo.CmsKit.Application.Tests/CmsKitApplicationTestBase.cs index ada0bb99c2..d07fe0d1d1 100644 --- a/modules/cms-kit/test/Volo.CmsKit.Application.Tests/CmsKitApplicationTestBase.cs +++ b/modules/cms-kit/test/Volo.CmsKit.Application.Tests/CmsKitApplicationTestBase.cs @@ -1,10 +1,27 @@ -namespace Volo.CmsKit +using System; +using Volo.CmsKit.EntityFrameworkCore; + +namespace Volo.CmsKit { /* Inherit from this class for your application layer tests. * See SampleAppService_Tests for example. */ public abstract class CmsKitApplicationTestBase : CmsKitTestBase { + protected virtual void UsingDbContext(Action action) + { + using (var dbContext = GetRequiredService()) + { + action.Invoke(dbContext); + } + } + protected virtual T UsingDbContext(Func action) + { + using (var dbContext = GetRequiredService()) + { + return action.Invoke(dbContext); + } + } } -} \ No newline at end of file +} diff --git a/modules/cms-kit/test/Volo.CmsKit.Application.Tests/Comments/CommentPublicAppService_Tests.cs b/modules/cms-kit/test/Volo.CmsKit.Application.Tests/Comments/CommentPublicAppService_Tests.cs new file mode 100644 index 0000000000..9568f79c6f --- /dev/null +++ b/modules/cms-kit/test/Volo.CmsKit.Application.Tests/Comments/CommentPublicAppService_Tests.cs @@ -0,0 +1,101 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using NSubstitute; +using Shouldly; +using Volo.Abp.MultiTenancy; +using Volo.Abp.Users; +using Volo.CmsKit.Public.Comments; +using Xunit; + +namespace Volo.CmsKit.Comments +{ + public class CommentPublicAppService_Tests : CmsKitApplicationTestBase + { + private readonly ICommentPublicAppService _commentAppService; + private ICurrentUser _currentUser; + private readonly CmsKitTestData _cmsKitTestData; + + public CommentPublicAppService_Tests() + { + _commentAppService = GetRequiredService(); + _cmsKitTestData = GetRequiredService(); + } + + protected override void AfterAddApplication(IServiceCollection services) + { + _currentUser = Substitute.For(); + services.AddSingleton(_currentUser); + } + + [Fact] + public async Task GetAllForEntityAsync() + { + var list = await _commentAppService.GetAllForEntityAsync(_cmsKitTestData.EntityType1, _cmsKitTestData.EntityId1); + + list.Items.Count.ShouldBe(2); + list.Items.First().Replies.Count.ShouldBe(2); + } + + [Fact] + public async Task CreateAsync() + { + _currentUser.Id.Returns(_cmsKitTestData.User2Id); + + var newComment = await _commentAppService.CreateAsync(new CreateCommentInput + { + EntityId = _cmsKitTestData.EntityId1, + EntityType = _cmsKitTestData.EntityType1, + RepliedCommentId = null, + Text = "newComment" + }); + + UsingDbContext(context => + { + var comments = context.Comments.Where(x => + x.EntityId == _cmsKitTestData.EntityId1 && x.EntityType == _cmsKitTestData.EntityType1).ToList(); + + comments + .Any(c=>c.Id == newComment.Id && c.CreatorId == newComment.CreatorId && c.Text == "newComment") + .ShouldBeTrue(); + }); + } + + [Fact] + public async Task UpdateAsync() + { + _currentUser.Id.Returns(_cmsKitTestData.User1Id); + + await _commentAppService.UpdateAsync(_cmsKitTestData.CommentWithChildId, new UpdateCommentInput + { + Text = "I'm Updated" + }); + + UsingDbContext(context => + { + var comment = context.Comments.Single(x => + x.Id == _cmsKitTestData.CommentWithChildId); + + comment.Text.ShouldBe("I'm Updated"); + }); + } + + [Fact] + public async Task DeleteAsync() + { + _currentUser.Id.Returns(_cmsKitTestData.User1Id); + + await _commentAppService.DeleteAsync(_cmsKitTestData.CommentWithChildId); + + UsingDbContext(context => + { + var comment = context.Comments.FirstOrDefault(x => + x.Id == _cmsKitTestData.CommentWithChildId); + + comment.ShouldBeNull(); + }); + } + } +} diff --git a/modules/cms-kit/test/Volo.CmsKit.Application.Tests/Reactions/ReactionPublicAppService_Tests.cs b/modules/cms-kit/test/Volo.CmsKit.Application.Tests/Reactions/ReactionPublicAppService_Tests.cs new file mode 100644 index 0000000000..d03712ea07 --- /dev/null +++ b/modules/cms-kit/test/Volo.CmsKit.Application.Tests/Reactions/ReactionPublicAppService_Tests.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using NSubstitute; +using Shouldly; +using Volo.Abp.Users; +using Volo.CmsKit.Public.Reactions; +using Xunit; + +namespace Volo.CmsKit.Reactions +{ + public class ReactionPublicAppService_Tests : CmsKitApplicationTestBase + { + + private readonly CmsKitTestData _cmsKitTestData; + private readonly ReactionPublicAppService _reactionPublicAppService; + private ICurrentUser _currentUser; + + public ReactionPublicAppService_Tests() + { + _cmsKitTestData = GetRequiredService(); + _reactionPublicAppService = GetRequiredService(); + } + + protected override void AfterAddApplication(IServiceCollection services) + { + _currentUser = Substitute.For(); + services.AddSingleton(_currentUser); + } + + [Fact] + public async Task GetForSelectionAsync() + { + _currentUser.Id.Returns(_cmsKitTestData.User1Id); + + var reactions = await _reactionPublicAppService.GetForSelectionAsync( + _cmsKitTestData.EntityType2, + _cmsKitTestData.EntityId1 + ); + + reactions.Items. + First(r=>r.Reaction.Name == StandardReactions.Rocket).IsSelectedByCurrentUser.ShouldBeTrue(); + reactions.Items. + First(r=>r.Reaction.Name == StandardReactions.Rocket).Count.ShouldBe(1); + + reactions.Items. + Where(r=>r.Reaction.Name != StandardReactions.Rocket).All(r=>!r.IsSelectedByCurrentUser) + .ShouldBeTrue(); + reactions.Items. + Where(r=>r.Reaction.Name != StandardReactions.Rocket).All(r=> r.Count == 0) + .ShouldBeTrue(); + } + + [Fact] + public async Task CreateAsync() + { + _currentUser.Id.Returns(_cmsKitTestData.User1Id); + + await _reactionPublicAppService.CreateAsync(new CreateReactionDto + { + EntityType = _cmsKitTestData.EntityType2, + EntityId = _cmsKitTestData.EntityId2, + ReactionName = StandardReactions.Hooray + }); + + UsingDbContext(context => + { + var reaction = context.UserReactions.FirstOrDefault(x => + x.CreatorId == _cmsKitTestData.User1Id && + x.ReactionName == StandardReactions.Hooray && + x.EntityId == _cmsKitTestData.EntityId2 && + x.EntityType == _cmsKitTestData.EntityType2); + + reaction.ShouldNotBeNull(); + }); + } + + [Fact] + public async Task DeleteAsync() + { + _currentUser.Id.Returns(_cmsKitTestData.User1Id); + + await _reactionPublicAppService.DeleteAsync(new DeleteReactionDto + { + EntityType = _cmsKitTestData.EntityType1, + EntityId = _cmsKitTestData.EntityId1, + ReactionName = StandardReactions.Confused + }); + + UsingDbContext(context => + { + var reaction = context.UserReactions.FirstOrDefault(x => + x.CreatorId == _cmsKitTestData.User1Id && + x.ReactionName == StandardReactions.Confused && + x.EntityId == _cmsKitTestData.EntityId1 && + x.EntityType == _cmsKitTestData.EntityType1); + + reaction.ShouldBeNull(); + }); + } + } +} diff --git a/modules/cms-kit/test/Volo.CmsKit.Domain.Tests/CmsKitDomainTestBase.cs b/modules/cms-kit/test/Volo.CmsKit.Domain.Tests/CmsKitDomainTestBase.cs index d2054cd623..763942e5ac 100644 --- a/modules/cms-kit/test/Volo.CmsKit.Domain.Tests/CmsKitDomainTestBase.cs +++ b/modules/cms-kit/test/Volo.CmsKit.Domain.Tests/CmsKitDomainTestBase.cs @@ -1,10 +1,27 @@ -namespace Volo.CmsKit +using System; +using Volo.CmsKit.EntityFrameworkCore; + +namespace Volo.CmsKit { /* Inherit from this class for your domain layer tests. * See SampleManager_Tests for example. */ public abstract class CmsKitDomainTestBase : CmsKitTestBase { + protected virtual void UsingDbContext(Action action) + { + using (var dbContext = GetRequiredService()) + { + action.Invoke(dbContext); + } + } + protected virtual T UsingDbContext(Func action) + { + using (var dbContext = GetRequiredService()) + { + return action.Invoke(dbContext); + } + } } } \ No newline at end of file diff --git a/modules/cms-kit/test/Volo.CmsKit.Domain.Tests/Reactions/ReactionManager_Tests.cs b/modules/cms-kit/test/Volo.CmsKit.Domain.Tests/Reactions/ReactionManager_Tests.cs new file mode 100644 index 0000000000..6f4fe2425d --- /dev/null +++ b/modules/cms-kit/test/Volo.CmsKit.Domain.Tests/Reactions/ReactionManager_Tests.cs @@ -0,0 +1,82 @@ +using System.Linq; +using System.Threading.Tasks; +using Shouldly; +using Xunit; + +namespace Volo.CmsKit.Reactions +{ + public class ReactionManager_Tests : CmsKitDomainTestBase + { + private readonly CmsKitTestData _cmsKitTestData; + private readonly ReactionManager _reactionManager; + + public ReactionManager_Tests() + { + _cmsKitTestData = GetRequiredService(); + _reactionManager = GetRequiredService(); + } + + [Fact] + public async Task GetReactionsAsync() + { + var reactions = await _reactionManager.GetReactionsAsync(); + + reactions.Count.ShouldBe(8); + + var reactionsByEntityType = await _reactionManager.GetReactionsAsync(_cmsKitTestData.EntityType1); + + reactionsByEntityType.Count.ShouldBe(8); + } + + [Fact] + public async Task GetSummariesAsync() + { + var summary = await _reactionManager.GetSummariesAsync( + _cmsKitTestData.EntityType1, + _cmsKitTestData.EntityId1 + ); + + summary.Single(s=>s.Reaction.Name == StandardReactions.ThumbsUp).Count.ShouldBe(2); + summary.Single(s=>s.Reaction.Name == StandardReactions.Confused).Count.ShouldBe(1); + summary.Single(s=>s.Reaction.Name == StandardReactions.Rocket).Count.ShouldBe(0); + } + + [Fact] + public async Task CreateAsync() + { + var reaction = await _reactionManager.CreateAsync( + _cmsKitTestData.User2Id, + _cmsKitTestData.EntityType1, + _cmsKitTestData.EntityId2, + StandardReactions.Eyes + ); + + reaction.CreatorId.ShouldBe(_cmsKitTestData.User2Id); + reaction.EntityType.ShouldBe(_cmsKitTestData.EntityType1); + reaction.EntityId.ShouldBe(_cmsKitTestData.EntityId2); + reaction.ReactionName.ShouldBe(StandardReactions.Eyes); + } + + [Fact] + public async Task DeleteAsync() + { + await _reactionManager.DeleteAsync( + _cmsKitTestData.User1Id, + _cmsKitTestData.EntityType1, + _cmsKitTestData.EntityId1, + StandardReactions.Confused + ); + + UsingDbContext(context => + { + var reaction = context.UserReactions.FirstOrDefault(x => + x.CreatorId == _cmsKitTestData.User1Id && + x.ReactionName == StandardReactions.Confused && + x.EntityId == _cmsKitTestData.EntityId1 && + x.EntityType == _cmsKitTestData.EntityType1); + + reaction.ShouldBeNull(); + }); + } + } +} diff --git a/modules/cms-kit/test/Volo.CmsKit.Domain.Tests/Samples/SampleManager_Tests.cs b/modules/cms-kit/test/Volo.CmsKit.Domain.Tests/Samples/SampleManager_Tests.cs deleted file mode 100644 index 2fe5d2ca29..0000000000 --- a/modules/cms-kit/test/Volo.CmsKit.Domain.Tests/Samples/SampleManager_Tests.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Threading.Tasks; -using Xunit; - -namespace Volo.CmsKit.Samples -{ - public class SampleManager_Tests : CmsKitDomainTestBase - { - //private readonly SampleManager _sampleManager; - - public SampleManager_Tests() - { - //_sampleManager = GetRequiredService(); - } - - [Fact] - public async Task Method1Async() - { - - } - } -} diff --git a/modules/cms-kit/test/Volo.CmsKit.EntityFrameworkCore.Tests/EntityFrameworkCore/Comments/CommentRepository_Tests.cs b/modules/cms-kit/test/Volo.CmsKit.EntityFrameworkCore.Tests/EntityFrameworkCore/Comments/CommentRepository_Tests.cs new file mode 100644 index 0000000000..69b3da3d9e --- /dev/null +++ b/modules/cms-kit/test/Volo.CmsKit.EntityFrameworkCore.Tests/EntityFrameworkCore/Comments/CommentRepository_Tests.cs @@ -0,0 +1,9 @@ +using Volo.CmsKit.Comments; + +namespace Volo.CmsKit.EntityFrameworkCore.Comments +{ + public class CommentRepository_Tests : CommentRepository_Tests + { + + } +} diff --git a/modules/cms-kit/test/Volo.CmsKit.EntityFrameworkCore.Tests/EntityFrameworkCore/Reactions/UserReactionRepository_Tests.cs b/modules/cms-kit/test/Volo.CmsKit.EntityFrameworkCore.Tests/EntityFrameworkCore/Reactions/UserReactionRepository_Tests.cs new file mode 100644 index 0000000000..8bb4c68c8e --- /dev/null +++ b/modules/cms-kit/test/Volo.CmsKit.EntityFrameworkCore.Tests/EntityFrameworkCore/Reactions/UserReactionRepository_Tests.cs @@ -0,0 +1,8 @@ +using Volo.CmsKit.Reactions; + +namespace Volo.CmsKit.EntityFrameworkCore.Reactions +{ + public class UserReactionRepository_Tests : UserReactionRepository_Tests + { + } +} diff --git a/modules/cms-kit/test/Volo.CmsKit.EntityFrameworkCore.Tests/EntityFrameworkCore/Samples/SampleRepository_Tests.cs b/modules/cms-kit/test/Volo.CmsKit.EntityFrameworkCore.Tests/EntityFrameworkCore/Samples/SampleRepository_Tests.cs deleted file mode 100644 index 5e5cb3652f..0000000000 --- a/modules/cms-kit/test/Volo.CmsKit.EntityFrameworkCore.Tests/EntityFrameworkCore/Samples/SampleRepository_Tests.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Volo.CmsKit.Samples; - -namespace Volo.CmsKit.EntityFrameworkCore.Samples -{ - public class SampleRepository_Tests : SampleRepository_Tests - { - /* Don't write custom repository tests here, instead write to - * the base class. - * One exception can be some specific tests related to EF core. - */ - } -} diff --git a/modules/cms-kit/test/Volo.CmsKit.EntityFrameworkCore.Tests/Volo.CmsKit.EntityFrameworkCore.Tests.csproj b/modules/cms-kit/test/Volo.CmsKit.EntityFrameworkCore.Tests/Volo.CmsKit.EntityFrameworkCore.Tests.csproj index fbcdaa6c17..ab7406a97d 100644 --- a/modules/cms-kit/test/Volo.CmsKit.EntityFrameworkCore.Tests/Volo.CmsKit.EntityFrameworkCore.Tests.csproj +++ b/modules/cms-kit/test/Volo.CmsKit.EntityFrameworkCore.Tests/Volo.CmsKit.EntityFrameworkCore.Tests.csproj @@ -9,7 +9,7 @@ - + diff --git a/modules/cms-kit/test/Volo.CmsKit.HttpApi.Client.ConsoleTestApp/Volo.CmsKit.HttpApi.Client.ConsoleTestApp.csproj b/modules/cms-kit/test/Volo.CmsKit.HttpApi.Client.ConsoleTestApp/Volo.CmsKit.HttpApi.Client.ConsoleTestApp.csproj index 0168088fbc..65b381f4f4 100644 --- a/modules/cms-kit/test/Volo.CmsKit.HttpApi.Client.ConsoleTestApp/Volo.CmsKit.HttpApi.Client.ConsoleTestApp.csproj +++ b/modules/cms-kit/test/Volo.CmsKit.HttpApi.Client.ConsoleTestApp/Volo.CmsKit.HttpApi.Client.ConsoleTestApp.csproj @@ -20,7 +20,7 @@ - + diff --git a/modules/cms-kit/test/Volo.CmsKit.MongoDB.Tests/MongoDB/Comments/CommentRepository_Tests.cs b/modules/cms-kit/test/Volo.CmsKit.MongoDB.Tests/MongoDB/Comments/CommentRepository_Tests.cs new file mode 100644 index 0000000000..cf2e936ca8 --- /dev/null +++ b/modules/cms-kit/test/Volo.CmsKit.MongoDB.Tests/MongoDB/Comments/CommentRepository_Tests.cs @@ -0,0 +1,9 @@ +using Volo.CmsKit.Comments; + +namespace Volo.CmsKit.MongoDB.Comments +{ + public class CommentRepository_Tests : CommentRepository_Tests + { + + } +} diff --git a/modules/cms-kit/test/Volo.CmsKit.MongoDB.Tests/MongoDB/Reactions/UserReactionRepository_Tests.cs b/modules/cms-kit/test/Volo.CmsKit.MongoDB.Tests/MongoDB/Reactions/UserReactionRepository_Tests.cs new file mode 100644 index 0000000000..452ee211c5 --- /dev/null +++ b/modules/cms-kit/test/Volo.CmsKit.MongoDB.Tests/MongoDB/Reactions/UserReactionRepository_Tests.cs @@ -0,0 +1,8 @@ +using Volo.CmsKit.Reactions; + +namespace Volo.CmsKit.MongoDB.Reactions +{ + public class UserReactionRepository_Tests : UserReactionRepository_Tests + { + } +} diff --git a/modules/cms-kit/test/Volo.CmsKit.MongoDB.Tests/MongoDB/Samples/SampleRepository_Tests.cs b/modules/cms-kit/test/Volo.CmsKit.MongoDB.Tests/MongoDB/Samples/SampleRepository_Tests.cs deleted file mode 100644 index a93724c803..0000000000 --- a/modules/cms-kit/test/Volo.CmsKit.MongoDB.Tests/MongoDB/Samples/SampleRepository_Tests.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Volo.CmsKit.Samples; -using Xunit; - -namespace Volo.CmsKit.MongoDB.Samples -{ - [Collection(MongoTestCollection.Name)] - public class SampleRepository_Tests : SampleRepository_Tests - { - /* Don't write custom repository tests here, instead write to - * the base class. - * One exception can be some specific tests related to MongoDB. - */ - } -} diff --git a/modules/cms-kit/test/Volo.CmsKit.TestBase/CmsKitDataSeedContributor.cs b/modules/cms-kit/test/Volo.CmsKit.TestBase/CmsKitDataSeedContributor.cs index 8b33f127c2..12c6255623 100644 --- a/modules/cms-kit/test/Volo.CmsKit.TestBase/CmsKitDataSeedContributor.cs +++ b/modules/cms-kit/test/Volo.CmsKit.TestBase/CmsKitDataSeedContributor.cs @@ -2,26 +2,134 @@ using Volo.Abp.Data; using Volo.Abp.DependencyInjection; using Volo.Abp.Guids; +using Volo.Abp.Users; +using Volo.CmsKit.Comments; +using Volo.CmsKit.Reactions; +using Volo.CmsKit.Users; namespace Volo.CmsKit { public class CmsKitDataSeedContributor : IDataSeedContributor, ITransientDependency { private readonly IGuidGenerator _guidGenerator; + private readonly ICmsUserRepository _cmsUserRepository; + private readonly CmsKitTestData _cmsKitTestData; + private readonly ICommentRepository _commentRepository; + private readonly ReactionManager _reactionManager; public CmsKitDataSeedContributor( - IGuidGenerator guidGenerator) + IGuidGenerator guidGenerator, + ICmsUserRepository cmsUserRepository, + CmsKitTestData cmsKitTestData, + ICommentRepository commentRepository, + ReactionManager reactionManager) { _guidGenerator = guidGenerator; + _cmsUserRepository = cmsUserRepository; + _cmsKitTestData = cmsKitTestData; + _commentRepository = commentRepository; + _reactionManager = reactionManager; } - - public Task SeedAsync(DataSeedContext context) + + public async Task SeedAsync(DataSeedContext context) + { + await SeedUsersAsync(); + + await SeedCommentsAsync(); + + await SeedReactionsAsync(); + } + + private async Task SeedUsersAsync() + { + await _cmsUserRepository.InsertAsync(new CmsUser(new UserData(_cmsKitTestData.User1Id, "user1", "user1@volo.com", + "user", "1"))); + await _cmsUserRepository.InsertAsync(new CmsUser(new UserData(_cmsKitTestData.User2Id, "user2", "user2@volo.com", + "user", "2"))); + } + + private async Task SeedCommentsAsync() + { + var comment1 = await _commentRepository.InsertAsync(new Comment(_cmsKitTestData.CommentWithChildId, + _cmsKitTestData.EntityType1, + _cmsKitTestData.EntityId1, + "comment", + null, + _cmsKitTestData.User1Id + )); + + await _commentRepository.InsertAsync(new Comment(_guidGenerator.Create(), + _cmsKitTestData.EntityType1, + _cmsKitTestData.EntityId1, + "reply", + comment1.Id, + _cmsKitTestData.User2Id + )); + + await _commentRepository.InsertAsync(new Comment(_guidGenerator.Create(), + _cmsKitTestData.EntityType1, + _cmsKitTestData.EntityId1, + "reply", + comment1.Id, + _cmsKitTestData.User1Id + )); + + await _commentRepository.InsertAsync(new Comment(_guidGenerator.Create(), + _cmsKitTestData.EntityType1, + _cmsKitTestData.EntityId1, + "comment", + null, + _cmsKitTestData.User2Id + )); + + await _commentRepository.InsertAsync(new Comment(_guidGenerator.Create(), + _cmsKitTestData.EntityType1, + _cmsKitTestData.EntityId2, + "comment", + null, + _cmsKitTestData.User2Id + )); + + await _commentRepository.InsertAsync(new Comment(_guidGenerator.Create(), + _cmsKitTestData.EntityType2, + _cmsKitTestData.EntityId1, + "comment", + null, + _cmsKitTestData.User2Id + )); + } + + private async Task SeedReactionsAsync() { - /* Instead of returning the Task.CompletedTask, you can insert your test data - * at this point! - */ + await _reactionManager.CreateAsync( + _cmsKitTestData.User1Id, + _cmsKitTestData.EntityType1, + _cmsKitTestData.EntityId1, + StandardReactions.Confused); + + await _reactionManager.CreateAsync( + _cmsKitTestData.User1Id, + _cmsKitTestData.EntityType1, + _cmsKitTestData.EntityId1, + StandardReactions.ThumbsUp); + + await _reactionManager.CreateAsync( + _cmsKitTestData.User1Id, + _cmsKitTestData.EntityType1, + _cmsKitTestData.EntityId2, + StandardReactions.Heart); + + await _reactionManager.CreateAsync( + _cmsKitTestData.User1Id, + _cmsKitTestData.EntityType2, + _cmsKitTestData.EntityId1, + StandardReactions.Rocket); - return Task.CompletedTask; + await _reactionManager.CreateAsync( + _cmsKitTestData.User2Id, + _cmsKitTestData.EntityType1, + _cmsKitTestData.EntityId1, + StandardReactions.ThumbsUp); } } -} \ No newline at end of file +} diff --git a/modules/cms-kit/test/Volo.CmsKit.TestBase/CmsKitTestData.cs b/modules/cms-kit/test/Volo.CmsKit.TestBase/CmsKitTestData.cs new file mode 100644 index 0000000000..4e1472a3d8 --- /dev/null +++ b/modules/cms-kit/test/Volo.CmsKit.TestBase/CmsKitTestData.cs @@ -0,0 +1,22 @@ +using System; +using Volo.Abp.DependencyInjection; + +namespace Volo.CmsKit +{ + public class CmsKitTestData : ISingletonDependency + { + public Guid User1Id { get; } = Guid.NewGuid(); + + public Guid User2Id { get; } = Guid.NewGuid(); + + public Guid CommentWithChildId { get; } = Guid.NewGuid(); + + public string EntityType1 { get; } = "EntityName1"; + + public string EntityType2 { get; } = "EntityName2"; + + public string EntityId1 { get; } = "1"; + + public string EntityId2 { get; } = "2"; + } +} diff --git a/modules/cms-kit/test/Volo.CmsKit.TestBase/Comments/CommentRepository_Tests.cs b/modules/cms-kit/test/Volo.CmsKit.TestBase/Comments/CommentRepository_Tests.cs new file mode 100644 index 0000000000..afb7652edf --- /dev/null +++ b/modules/cms-kit/test/Volo.CmsKit.TestBase/Comments/CommentRepository_Tests.cs @@ -0,0 +1,44 @@ +using System.Linq; +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.Modularity; +using Xunit; + +namespace Volo.CmsKit.Comments +{ + public abstract class CommentRepository_Tests : CmsKitTestBase + where TStartupModule : IAbpModule + { + private readonly CmsKitTestData _cmsKitTestData; + private readonly ICommentRepository _commentRepository; + + public CommentRepository_Tests() + { + _cmsKitTestData = GetRequiredService(); + _commentRepository = GetRequiredService(); + } + + [Fact] + public async Task GetListWithAuthorsAsync() + { + var list = await _commentRepository.GetListWithAuthorsAsync(_cmsKitTestData.EntityType1, + _cmsKitTestData.EntityId1); + + list.Count.ShouldBe(4); + list.Any(x=>x.Comment == null).ShouldBeFalse(); + list.Any(x=>x.Author == null).ShouldBeFalse(); + } + + [Fact] + public async Task DeleteAsync() + { + await _commentRepository.DeleteAsync(_cmsKitTestData.CommentWithChildId); + + var list = await _commentRepository.GetListAsync(); + + list.Any(x=> + x.Id == _cmsKitTestData.CommentWithChildId || x.RepliedCommentId == _cmsKitTestData.CommentWithChildId) + .ShouldBeFalse(); + } + } +} diff --git a/modules/cms-kit/test/Volo.CmsKit.TestBase/Reactions/UserReactionRepository_Tests.cs b/modules/cms-kit/test/Volo.CmsKit.TestBase/Reactions/UserReactionRepository_Tests.cs new file mode 100644 index 0000000000..0499bc264d --- /dev/null +++ b/modules/cms-kit/test/Volo.CmsKit.TestBase/Reactions/UserReactionRepository_Tests.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.Modularity; +using Volo.CmsKit.Comments; +using Xunit; + +namespace Volo.CmsKit.Reactions +{ + public abstract class UserReactionRepository_Tests : CmsKitTestBase + where TStartupModule : IAbpModule + { + private readonly CmsKitTestData _cmsKitTestData; + private readonly IUserReactionRepository __userReactionRepository; + + public UserReactionRepository_Tests() + { + _cmsKitTestData = GetRequiredService(); + __userReactionRepository = GetRequiredService(); + } + + [Fact] + public async Task FindAsync() + { + var reaction = await __userReactionRepository.FindAsync( + _cmsKitTestData.User1Id, + _cmsKitTestData.EntityType1, + _cmsKitTestData.EntityId1, + StandardReactions.Confused + ); + + reaction.ShouldNotBeNull(); + reaction.CreatorId.ShouldBe(_cmsKitTestData.User1Id); + reaction.EntityId.ShouldBe(_cmsKitTestData.EntityId1); + reaction.EntityType.ShouldBe(_cmsKitTestData.EntityType1); + reaction.ReactionName.ShouldBe(StandardReactions.Confused); + } + + [Fact] + public async Task GetListForUserAsync() + { + var reactions = await __userReactionRepository.GetListForUserAsync( + _cmsKitTestData.User1Id, + _cmsKitTestData.EntityType1, + _cmsKitTestData.EntityId1 + ); + + reactions.Count.ShouldBe(2); + reactions.Count(x=>x.ReactionName == StandardReactions.Confused).ShouldBe(1); + reactions.Count(x=>x.ReactionName == StandardReactions.ThumbsUp).ShouldBe(1); + } + + [Fact] + public async Task GetSummariesAsync() + { + var summaries = await __userReactionRepository.GetSummariesAsync( + _cmsKitTestData.EntityType1, + _cmsKitTestData.EntityId1 + ); + + summaries.Count.ShouldBe(2); + summaries.First(x => x.ReactionName == StandardReactions.Confused).Count.ShouldBe(1); + summaries.First(x => x.ReactionName == StandardReactions.ThumbsUp).Count.ShouldBe(2); + + } + } +} diff --git a/modules/cms-kit/test/Volo.CmsKit.TestBase/Samples/SampleRepository_Tests.cs b/modules/cms-kit/test/Volo.CmsKit.TestBase/Samples/SampleRepository_Tests.cs deleted file mode 100644 index 2568c68810..0000000000 --- a/modules/cms-kit/test/Volo.CmsKit.TestBase/Samples/SampleRepository_Tests.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Threading.Tasks; -using Volo.Abp.Modularity; -using Xunit; - -namespace Volo.CmsKit.Samples -{ - /* Write your custom repository tests like that, in this project, as abstract classes. - * Then inherit these abstract classes from EF Core & MongoDB test projects. - * In this way, both database providers are tests with the same set tests. - */ - public abstract class SampleRepository_Tests : CmsKitTestBase - where TStartupModule : IAbpModule - { - //private readonly ISampleRepository _sampleRepository; - - protected SampleRepository_Tests() - { - //_sampleRepository = GetRequiredService(); - } - - [Fact] - public async Task Method1Async() - { - - } - } -} diff --git a/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20200624023838_Initial.Designer.cs b/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20200810022424_Initial.Designer.cs similarity index 91% rename from modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20200624023838_Initial.Designer.cs rename to modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20200810022424_Initial.Designer.cs index 2fb2a6782a..2cdcd59ea1 100644 --- a/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20200624023838_Initial.Designer.cs +++ b/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20200810022424_Initial.Designer.cs @@ -11,7 +11,7 @@ using VoloDocs.EntityFrameworkCore; namespace VoloDocs.EntityFrameworkCore.Migrations { [DbContext(typeof(VoloDocsDbContext))] - [Migration("20200624023838_Initial")] + [Migration("20200810022424_Initial")] partial class Initial { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -19,7 +19,7 @@ namespace VoloDocs.EntityFrameworkCore.Migrations #pragma warning disable 612, 618 modelBuilder .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) - .HasAnnotation("ProductVersion", "3.1.5") + .HasAnnotation("ProductVersion", "3.1.6") .HasAnnotation("Relational:MaxIdentifierLength", 128) .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); @@ -147,6 +147,81 @@ namespace VoloDocs.EntityFrameworkCore.Migrations b.ToTable("AbpRoleClaims"); }); + modelBuilder.Entity("Volo.Abp.Identity.IdentitySecurityLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Action") + .HasColumnType("nvarchar(96)") + .HasMaxLength(96); + + b.Property("ApplicationName") + .HasColumnType("nvarchar(96)") + .HasMaxLength(96); + + b.Property("BrowserInfo") + .HasColumnType("nvarchar(512)") + .HasMaxLength(512); + + b.Property("ClientId") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ClientIpAddress") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CorrelationId") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("CreationTime") + .HasColumnType("datetime2"); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("Identity") + .HasColumnType("nvarchar(96)") + .HasMaxLength(96); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantName") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Action"); + + b.HasIndex("TenantId", "ApplicationName"); + + b.HasIndex("TenantId", "Identity"); + + b.HasIndex("TenantId", "UserId"); + + b.ToTable("AbpSecurityLogs"); + }); + modelBuilder.Entity("Volo.Abp.Identity.IdentityUser", b => { b.Property("Id") @@ -203,6 +278,12 @@ namespace VoloDocs.EntityFrameworkCore.Migrations .HasColumnType("bit") .HasDefaultValue(false); + b.Property("IsExternal") + .ValueGeneratedOnAdd() + .HasColumnName("IsExternal") + .HasColumnType("bit") + .HasDefaultValue(false); + b.Property("LastModificationTime") .HasColumnName("LastModificationTime") .HasColumnType("datetime2"); diff --git a/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20200624023838_Initial.cs b/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20200810022424_Initial.cs similarity index 90% rename from modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20200624023838_Initial.cs rename to modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20200810022424_Initial.cs index 66b5265a33..ce7877ad70 100644 --- a/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20200624023838_Initial.cs +++ b/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20200810022424_Initial.cs @@ -91,6 +91,31 @@ namespace VoloDocs.EntityFrameworkCore.Migrations table.PrimaryKey("PK_AbpRoles", x => x.Id); }); + migrationBuilder.CreateTable( + name: "AbpSecurityLogs", + columns: table => new + { + Id = table.Column(nullable: false), + ExtraProperties = table.Column(nullable: true), + ConcurrencyStamp = table.Column(maxLength: 40, nullable: true), + TenantId = table.Column(nullable: true), + ApplicationName = table.Column(maxLength: 96, nullable: true), + Identity = table.Column(maxLength: 96, nullable: true), + Action = table.Column(maxLength: 96, nullable: true), + UserId = table.Column(nullable: true), + UserName = table.Column(maxLength: 256, nullable: true), + TenantName = table.Column(maxLength: 64, nullable: true), + ClientId = table.Column(maxLength: 64, nullable: true), + CorrelationId = table.Column(maxLength: 64, nullable: true), + ClientIpAddress = table.Column(maxLength: 64, nullable: true), + BrowserInfo = table.Column(maxLength: 512, nullable: true), + CreationTime = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpSecurityLogs", x => x.Id); + }); + migrationBuilder.CreateTable( name: "AbpSettings", columns: table => new @@ -130,6 +155,7 @@ namespace VoloDocs.EntityFrameworkCore.Migrations EmailConfirmed = table.Column(nullable: false, defaultValue: false), PasswordHash = table.Column(maxLength: 256, nullable: true), SecurityStamp = table.Column(maxLength: 256, nullable: false), + IsExternal = table.Column(nullable: false, defaultValue: false), PhoneNumber = table.Column(maxLength: 16, nullable: true), PhoneNumberConfirmed = table.Column(nullable: false, defaultValue: false), TwoFactorEnabled = table.Column(nullable: false, defaultValue: false), @@ -406,6 +432,26 @@ namespace VoloDocs.EntityFrameworkCore.Migrations table: "AbpRoles", column: "NormalizedName"); + migrationBuilder.CreateIndex( + name: "IX_AbpSecurityLogs_TenantId_Action", + table: "AbpSecurityLogs", + columns: new[] { "TenantId", "Action" }); + + migrationBuilder.CreateIndex( + name: "IX_AbpSecurityLogs_TenantId_ApplicationName", + table: "AbpSecurityLogs", + columns: new[] { "TenantId", "ApplicationName" }); + + migrationBuilder.CreateIndex( + name: "IX_AbpSecurityLogs_TenantId_Identity", + table: "AbpSecurityLogs", + columns: new[] { "TenantId", "Identity" }); + + migrationBuilder.CreateIndex( + name: "IX_AbpSecurityLogs_TenantId_UserId", + table: "AbpSecurityLogs", + columns: new[] { "TenantId", "UserId" }); + migrationBuilder.CreateIndex( name: "IX_AbpSettings_Name_ProviderName_ProviderKey", table: "AbpSettings", @@ -466,6 +512,9 @@ namespace VoloDocs.EntityFrameworkCore.Migrations migrationBuilder.DropTable( name: "AbpRoleClaims"); + migrationBuilder.DropTable( + name: "AbpSecurityLogs"); + migrationBuilder.DropTable( name: "AbpSettings"); diff --git a/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/VoloDocsDbContextModelSnapshot.cs b/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/VoloDocsDbContextModelSnapshot.cs index 73b261e454..614e4eb25a 100644 --- a/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/VoloDocsDbContextModelSnapshot.cs +++ b/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/VoloDocsDbContextModelSnapshot.cs @@ -17,7 +17,7 @@ namespace VoloDocs.EntityFrameworkCore.Migrations #pragma warning disable 612, 618 modelBuilder .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) - .HasAnnotation("ProductVersion", "3.1.5") + .HasAnnotation("ProductVersion", "3.1.6") .HasAnnotation("Relational:MaxIdentifierLength", 128) .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); @@ -145,6 +145,81 @@ namespace VoloDocs.EntityFrameworkCore.Migrations b.ToTable("AbpRoleClaims"); }); + modelBuilder.Entity("Volo.Abp.Identity.IdentitySecurityLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Action") + .HasColumnType("nvarchar(96)") + .HasMaxLength(96); + + b.Property("ApplicationName") + .HasColumnType("nvarchar(96)") + .HasMaxLength(96); + + b.Property("BrowserInfo") + .HasColumnType("nvarchar(512)") + .HasMaxLength(512); + + b.Property("ClientId") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ClientIpAddress") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CorrelationId") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("CreationTime") + .HasColumnType("datetime2"); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("Identity") + .HasColumnType("nvarchar(96)") + .HasMaxLength(96); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantName") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Action"); + + b.HasIndex("TenantId", "ApplicationName"); + + b.HasIndex("TenantId", "Identity"); + + b.HasIndex("TenantId", "UserId"); + + b.ToTable("AbpSecurityLogs"); + }); + modelBuilder.Entity("Volo.Abp.Identity.IdentityUser", b => { b.Property("Id") @@ -201,6 +276,12 @@ namespace VoloDocs.EntityFrameworkCore.Migrations .HasColumnType("bit") .HasDefaultValue(false); + b.Property("IsExternal") + .ValueGeneratedOnAdd() + .HasColumnName("IsExternal") + .HasColumnType("bit") + .HasDefaultValue(false); + b.Property("LastModificationTime") .HasColumnName("LastModificationTime") .HasColumnType("datetime2"); diff --git a/modules/docs/app/VoloDocs.EntityFrameworkCore/VoloDocs.EntityFrameworkCore.csproj b/modules/docs/app/VoloDocs.EntityFrameworkCore/VoloDocs.EntityFrameworkCore.csproj index 4789667e2c..b8d9bcd623 100644 --- a/modules/docs/app/VoloDocs.EntityFrameworkCore/VoloDocs.EntityFrameworkCore.csproj +++ b/modules/docs/app/VoloDocs.EntityFrameworkCore/VoloDocs.EntityFrameworkCore.csproj @@ -16,8 +16,8 @@
    - - + + diff --git a/modules/docs/app/VoloDocs.Web/package.json b/modules/docs/app/VoloDocs.Web/package.json index 2117f0b2f2..c6b5ba0dba 100644 --- a/modules/docs/app/VoloDocs.Web/package.json +++ b/modules/docs/app/VoloDocs.Web/package.json @@ -3,7 +3,7 @@ "name": "volo.docstestapp", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.basic": "^3.0.4", - "@abp/docs": "^3.0.4" + "@abp/aspnetcore.mvc.ui.theme.basic": "^3.0.5", + "@abp/docs": "^3.0.5" } } diff --git a/modules/docs/app/VoloDocs.Web/yarn.lock b/modules/docs/app/VoloDocs.Web/yarn.lock index 8c4d6fe3d6..4c93967952 100644 --- a/modules/docs/app/VoloDocs.Web/yarn.lock +++ b/modules/docs/app/VoloDocs.Web/yarn.lock @@ -2,45 +2,45 @@ # yarn lockfile v1 -"@abp/anchor-js@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/anchor-js/-/anchor-js-3.0.4.tgz#a8a0702c9204ba953e899890338512545af63b4d" - integrity sha512-J8/QIbUK8Cy+7EABw1gUC2CWuJVF44SjdJOIvJSno6k5xbAkyx4oOvPH6x7ngT68rlopt0zez14OpeM6Oy/0Bg== +"@abp/anchor-js@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/anchor-js/-/anchor-js-3.0.5.tgz#e1affea2e21370562b244f81ce8911d3d36f8d55" + integrity sha512-068PVUu/Zu1zQNgWsJXhUmSIm/HRA4Mx6VXxlNL+qiCLffg8AzRuj4rZc+UaYJieDmJDJc0dlWgiyUFpuvzZ8A== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" anchor-js "^4.2.2" -"@abp/aspnetcore.mvc.ui.theme.basic@^3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-3.0.4.tgz#c58e3e06ba78279875cf2b5f96d34b83ce09f6df" - integrity sha512-tKKqVRKpwN7P4Mn+he3bcz4Qbt8bkZ++wk6iOujd6HLTkBNa0uKyM4L6+iCn1Hi62S6HeK2Xytx+b+IYeIlsuw== - dependencies: - "@abp/aspnetcore.mvc.ui.theme.shared" "~3.0.4" - -"@abp/aspnetcore.mvc.ui.theme.shared@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-3.0.4.tgz#50db612b6492d36c63dd9042000a9fb00fecec54" - integrity sha512-imd2T5XXkNvGQyXV/A9hA80qSeurKaUcW6HDkte8Y2fs4QfvkjgzWXi7neqF6F+HVbYYPg3rIBWC6QF1OXGn2w== - dependencies: - "@abp/aspnetcore.mvc.ui" "~3.0.4" - "@abp/bootstrap" "~3.0.4" - "@abp/bootstrap-datepicker" "~3.0.4" - "@abp/datatables.net-bs4" "~3.0.4" - "@abp/font-awesome" "~3.0.4" - "@abp/jquery-form" "~3.0.4" - "@abp/jquery-validation-unobtrusive" "~3.0.4" - "@abp/lodash" "~3.0.4" - "@abp/luxon" "~3.0.4" - "@abp/malihu-custom-scrollbar-plugin" "~3.0.4" - "@abp/select2" "~3.0.4" - "@abp/sweetalert" "~3.0.4" - "@abp/timeago" "~3.0.4" - "@abp/toastr" "~3.0.4" - -"@abp/aspnetcore.mvc.ui@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-3.0.4.tgz#4e4344d95a98a60c53037b351cb915d8ff7a7dcb" - integrity sha512-p7KI5CKkpD4EYzG4fCtygrtZhwMu7Z+IZFGbdqXelLbI5ACkbJyrn+N+9trPfS9papymeXY4p1E3v6xOH7rSHQ== +"@abp/aspnetcore.mvc.ui.theme.basic@^3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-3.0.5.tgz#c146f20226522f0305cc0fa31cfcf9d39be78901" + integrity sha512-ptNhD4xV2wSLW7vQ5KUgaRiH5Ov1OscZW2ZEoivnt3kqgtIC3DGw+SiE9vjFCvyY83fjRkIlW2PIfQxSySbteQ== + dependencies: + "@abp/aspnetcore.mvc.ui.theme.shared" "~3.0.5" + +"@abp/aspnetcore.mvc.ui.theme.shared@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-3.0.5.tgz#5b370902cf229963f9aa10dd64482d57299dde76" + integrity sha512-iobAH39xrCdnqdaABaiYZg4lqNSOwPkMCLnyc+ytxOMbu/YnNKahXPmRhgd8dR/l+iQY1+6T4SWfLdGswi3gAg== + dependencies: + "@abp/aspnetcore.mvc.ui" "~3.0.5" + "@abp/bootstrap" "~3.0.5" + "@abp/bootstrap-datepicker" "~3.0.5" + "@abp/datatables.net-bs4" "~3.0.5" + "@abp/font-awesome" "~3.0.5" + "@abp/jquery-form" "~3.0.5" + "@abp/jquery-validation-unobtrusive" "~3.0.5" + "@abp/lodash" "~3.0.5" + "@abp/luxon" "~3.0.5" + "@abp/malihu-custom-scrollbar-plugin" "~3.0.5" + "@abp/select2" "~3.0.5" + "@abp/sweetalert" "~3.0.5" + "@abp/timeago" "~3.0.5" + "@abp/toastr" "~3.0.5" + +"@abp/aspnetcore.mvc.ui@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-3.0.5.tgz#a050eaee328c56a3730f5abbf25035adf612d6f0" + integrity sha512-/EOgjXTzR+I1OK9KA7L3yzQ2RGSDW97+n3cQrSdLAUF+tnRvMJS0nytDlSJSRK2hb8/clFMaofZHCC81aUkYUg== dependencies: ansi-colors "^4.1.1" extend-object "^1.0.0" @@ -49,180 +49,180 @@ path "^0.12.7" rimraf "^3.0.2" -"@abp/bootstrap-datepicker@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-3.0.4.tgz#d21963f158a4d348a0acdc46209e67378aae8e55" - integrity sha512-25e9qm39dj9AijIchQIdLlBiBM5/fhASkEuLKNt6M8S/e3qwKLwbRhX90DKMFvOyKdxxcK1gX7tDuGHyG9A8mA== +"@abp/bootstrap-datepicker@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-3.0.5.tgz#3a7fd86dbcadc31c11be5b2b31c8cf8ac3035540" + integrity sha512-vIY1wT4RHoJAPhVK11Q5MS71iZbwsmM/0VTBvvs04mjRSSi6xhdyRYkA+l+P49fnOwhZNaaK19XwQdVVScDuhA== dependencies: bootstrap-datepicker "^1.9.0" -"@abp/bootstrap@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-3.0.4.tgz#3b0e2b74dc174a04ce669de1b140df833eee6031" - integrity sha512-DwxL1fGYOwChPwmMpwUxCKiIdzTfRf/YDkOLUiwI8eRrpABBeuE2sSHKfks6Rqh9Wqo/nlWXvfccgDRqweqhsA== +"@abp/bootstrap@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-3.0.5.tgz#91f2c8fe6a57fc5789ae8fe9c206b2c159e55880" + integrity sha512-LdydeJIbw5k1k7FkWaI1lTectZeTXmEIoccJtj9Qx4N9yGt40wGsjz9SOvUdJ0IKAgntIWNQP8ohWiBPgzT7Xg== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" bootstrap "^4.5.0" bootstrap-v4-rtl "4.4.1-2" -"@abp/clipboard@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/clipboard/-/clipboard-3.0.4.tgz#17ff40e398de3c0ab5214e564d443ed52ac10630" - integrity sha512-vjjupRau7fgBxvQwaDrBHApP4Ce1x9XM7L64/c+A+oBny0SzuiSBGBYsjlFrJGmTHT/PhjoB1XQiBHrYYPXQfQ== +"@abp/clipboard@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/clipboard/-/clipboard-3.0.5.tgz#7ceadd21360db11c91582a532029bfe65d10a930" + integrity sha512-iq455C096L5D+b6IrWkMxA9mbpggBNZ1kWfCQ3rhIjo0qbOGrvdGB3uQ+BqRS9Ni9ku7vBb2COldouIC9KOp5A== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" clipboard "^2.0.6" -"@abp/core@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/core/-/core-3.0.4.tgz#281940525e280e9e3a1aa4cb4e8f64548ea026a3" - integrity sha512-ZPEH1fa5bf/f1ek9ZLQHx7u78iF6kRZ8Z2NBI36cNL13RyQbx485qJF+KXyqNlJoJAh0CAs0NsAJADU3utcDNA== +"@abp/core@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/core/-/core-3.0.5.tgz#9a7bd990d02edc8128a5be3233b6fbb5669b15b8" + integrity sha512-LdkSkZQfN4hugcGNxBkje0+eQomivwNFbnAkMFQbvCwYLwKB2yvk7GCemEetW1piqvyrUGz4jqhtunjoXps5JA== dependencies: - "@abp/utils" "^3.0.4" + "@abp/utils" "^3.0.5" -"@abp/datatables.net-bs4@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs4/-/datatables.net-bs4-3.0.4.tgz#e488336bd2c199146ea60464e0ba75aeb134957e" - integrity sha512-ID1XWKPxG9pv6mxBkku7JWvgXjTxJ1uShxmYv5kFYVuwOGMSvj8QVZInTHFcMdECopV56yjGPNOIFLeQj4Mw8Q== +"@abp/datatables.net-bs4@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs4/-/datatables.net-bs4-3.0.5.tgz#9b79935970d2d4074994240b5deb01dff5987c0a" + integrity sha512-jIWRBEG/NlhaE43i6Xzr18XJ5aTWA+WWslf7WgNOArG5slGBbH4lLLja4HswLP3NGdSmzIyHJR1p9XQ82nBkbQ== dependencies: - "@abp/datatables.net" "~3.0.4" + "@abp/datatables.net" "~3.0.5" datatables.net-bs4 "^1.10.21" -"@abp/datatables.net@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-3.0.4.tgz#7ef143b83143ad8d769be25296411a812a17b255" - integrity sha512-gdQwRuxO5fQWhG/DiUaf40DQ8dWIVGhOCyt6SZ8LQzq4uSaQqa0qrtTTTW2/aiee4MgJu2vJWYF5YCszIR/HhA== +"@abp/datatables.net@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-3.0.5.tgz#0a0a39e8fa54522bf8a09ce76ff3927207a79491" + integrity sha512-HClQ2tcZuWBAChf9oOJOtMP1zHv8jqloPEcw3iqbhS/tWZBw5VQ4TO+BVfZsqHDXZNnWSmR6/2xiKsK32nliew== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" datatables.net "^1.10.21" -"@abp/docs@^3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/docs/-/docs-3.0.4.tgz#366d550247189981d5989652d3d2cf663ae518e5" - integrity sha512-NdqukX/oPkkmG31xZflc8oeVm0Up/CUZCBb9ahKCHleK7C0os8eQBQ52wO27dJFa5FaeUhW2ejLYT3aS2XkrTg== +"@abp/docs@^3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/docs/-/docs-3.0.5.tgz#733211ea72f3064ea5c23ed36a16c7f8b0ef5d8b" + integrity sha512-LQDHz6mE3ptP1AmpK+IEoY6edQB/t7Hu5ueLdtNHqTJWkwW7ngXOaECbkC2KVPywLqbXw0vzHBw2OnCVSKxtyw== dependencies: - "@abp/anchor-js" "~3.0.4" - "@abp/clipboard" "~3.0.4" - "@abp/malihu-custom-scrollbar-plugin" "~3.0.4" - "@abp/popper.js" "~3.0.4" - "@abp/prismjs" "~3.0.4" + "@abp/anchor-js" "~3.0.5" + "@abp/clipboard" "~3.0.5" + "@abp/malihu-custom-scrollbar-plugin" "~3.0.5" + "@abp/popper.js" "~3.0.5" + "@abp/prismjs" "~3.0.5" -"@abp/font-awesome@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-3.0.4.tgz#98f83d0bf8b5a2a4d988cb07f5cceda2e1bc1a09" - integrity sha512-AKhKCUXx8vdHIgyXUfbSLitfYor5dJbo4qMRRGqIJVSj+cyMkpgYbGjqr24GGijgZxdmOGL+s1b9iCUbIPgLNQ== +"@abp/font-awesome@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-3.0.5.tgz#62d58c34f0abf268e30bdf788dfbd3f45b19b412" + integrity sha512-/+31Vtw1S9vvuNy/rN/Q6awkgUaYNNYXESgfvqI0pqIkvlPgigvS8E68mHqGzsxyRqdZisI6JGd94AF+vJD7sQ== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" "@fortawesome/fontawesome-free" "^5.13.0" -"@abp/jquery-form@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-3.0.4.tgz#022eebbfe58b55e328984c0f119884c9464bc767" - integrity sha512-xDkk4rY0AmXBJE4qztzr2hZz+OVucVNWeagOEgeQHXJSKU/T+0kj0a1RxEHpyI24xBvEXAbe8PbByTbu0QXYew== +"@abp/jquery-form@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-3.0.5.tgz#9d2ea9b2509448ee163a163298a7a09480865f78" + integrity sha512-RKgvQciaMml33NvzA28aYyfNI+WQHRfKLFWvOvqc8qO4C3BvyrnwepuzZcnI/9UsELKLSLoFrfk3rJHhxnTJhg== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" jquery-form "^4.3.0" -"@abp/jquery-validation-unobtrusive@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-3.0.4.tgz#6cc942aadcdde33893e7a45c84aa022e033e4e8b" - integrity sha512-N9sza91HCyPvJo+d0RAfq4skae3TgAYg7KXhlwzRE2hqUQS+EICVARURWyxJW7s7Zu6+gxDTkXhBZRTW56deZA== +"@abp/jquery-validation-unobtrusive@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-3.0.5.tgz#b8912e1c4cfdd4fdff6ad4aa7ee8693b55a3be1f" + integrity sha512-kPifDA+1ErbeQBwn+BzwtWuTHXzBke/EMCmITuj59plM/60gZ1ZfVm8EzzTmKS7iLaVVeOqTKf0vEQTobNYffw== dependencies: - "@abp/jquery-validation" "~3.0.4" + "@abp/jquery-validation" "~3.0.5" jquery-validation-unobtrusive "^3.2.11" -"@abp/jquery-validation@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-3.0.4.tgz#3f96d1f83c75a7fc47e5e5e12c51dbc8da7582b1" - integrity sha512-2ljqas4YKU0PcwB0wQW2qhXejDHjW+N1XCxZDyCZ2wF9UxGlKBR8rFt76TJByXSotKE7n2hJRZaOh9ICZUow2g== +"@abp/jquery-validation@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-3.0.5.tgz#55e36b3c832a35447fac69af312dedf1dee22279" + integrity sha512-fdqxc5CJ6TpUAF8+NEsKCmXvvZiOPR5bzczcfvfovxLOjvHvdEO74XPGjsjlBNTdaKMhmNGZRK4JRLUz7fvTng== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" jquery-validation "^1.19.2" -"@abp/jquery@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-3.0.4.tgz#5f236d95cfae7f65012cd8778fd74490b7542442" - integrity sha512-g8N8Te5vf+8djdksfZoZ3tXaqlBC4Ra39pAWU/XerlLl64u+XmAX1s6yKRfC2kS8lf292GgiFYuNOkb77J6k4A== +"@abp/jquery@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-3.0.5.tgz#2e43ac19a8df6e1e0220d6d4991d33a93d890754" + integrity sha512-2d2l+smWKAVP4/b6GSO+8rmJ7pFXiUewpBDwkHm5qgkK6n6UGqIcK9lIgiS2YQ6rmL6qdtuNhhqtC9GF2TF2kQ== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" jquery "~3.5.1" -"@abp/lodash@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-3.0.4.tgz#ee60bc99f98b59fd9fc5ff4bbcf914a13faf209d" - integrity sha512-jxvv74Sm/fUKDBMylK4Ne/8GKm7GuHAdcLFtkoJQptdO+RjZpKJ86ULT+X5vbKskZaTj2fEmOR6l4hmfnBULTA== +"@abp/lodash@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-3.0.5.tgz#d5d9f14095d76239bc0e6f9804c24d1f92ffcab4" + integrity sha512-+B2vxfPwxoIIb8Ac8eHi6OgAn0qbjmPZuun7/es0/ouYYTbEhvhTAD20X3PGZlHrIYeDyS2ansuXm8gJI+/BzA== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" lodash "^4.17.15" -"@abp/luxon@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-3.0.4.tgz#aa1bf6a93d6254433101e50a205b276759969414" - integrity sha512-gX9dQJt9C6M1wmCSUIBoNr8CAt0jkUZFQ7LKKO/BoADGM/XFvY5vDbhL7zcwbwitgCOzmucoCxs/6rIxSOVlrg== +"@abp/luxon@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-3.0.5.tgz#083042c939ea58bf719e35114a6c8efb07b68688" + integrity sha512-0UVHO2CO89t34O35OA3JzWGWLBpg+e1NQXSfVtK90nmg/QmzGkHVM9m7Su0jjLNBt3N5EA5OPOhriot3bCKItw== dependencies: luxon "^1.24.1" -"@abp/malihu-custom-scrollbar-plugin@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-3.0.4.tgz#c54824b98363f4190cc6a37fcd421c2bfdc1e201" - integrity sha512-6LdSGctOr4ACtdohdSlb19vrFNJ5C0hy1VdbBEafHOBspXckvRVx4uJl2KfHmAC+bMg9apoK/wcHjVNdBHThYQ== +"@abp/malihu-custom-scrollbar-plugin@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-3.0.5.tgz#f908289927a63fd76fb8f60b00a37747d9edbe39" + integrity sha512-3GoXA8VtGyZWKiqaPCipPXYLLM7h2Y5Oecq5c5YlMQyW2akv7uHiQQF2GZW/nVsFu3HBbGTOWpKRRmVk3PEO9Q== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" malihu-custom-scrollbar-plugin "^3.1.5" -"@abp/popper.js@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/popper.js/-/popper.js-3.0.4.tgz#f68f2c3392dd9f86ce0f53cc12b33b0258bbbf9f" - integrity sha512-DTxqTMyLFLGjpf7sbKLJMsPbuZ/+8OdgqlO/JjNjyBVWCCM6b50yhYAcYKQq1f2kcqIegJv0B6+zDHAFMtnibg== +"@abp/popper.js@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/popper.js/-/popper.js-3.0.5.tgz#ef46770e3a63cf10c91d4bda983198cd84b8e063" + integrity sha512-5MzLBMRWh2hHLVJJIrvJa6c4fwEdUk9HmnqpaqXnSX52B4uy9jcBexMdi/H7+cmao9NHm+Ld9n9G0YA8vMNeKA== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" popper.js "^1.16.0" -"@abp/prismjs@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/prismjs/-/prismjs-3.0.4.tgz#9cd8113b33dfd1b349b12740a2f7462f88bf3841" - integrity sha512-D+RNAhUhOCXnBNZVOq9EQg883eRbPKlIpFsxeMW065XOm5g7iO0eJACvyqGC8mt5reHmtxApnfiGNlhT1dK+Eg== +"@abp/prismjs@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/prismjs/-/prismjs-3.0.5.tgz#81a6ef656f3d7a76f365eb0bcbf2d0d3013d6e72" + integrity sha512-vdSSa1+tXS5+HWIgQyjDy4yQG7Q1YyuHQnFU6zlDJCQlE+20PL6lYgPnqR7DQ+Cd4lhRb3oxvloImYjwiwSQvA== dependencies: - "@abp/clipboard" "~3.0.4" - "@abp/core" "~3.0.4" + "@abp/clipboard" "~3.0.5" + "@abp/core" "~3.0.5" prismjs "^1.20.0" -"@abp/select2@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-3.0.4.tgz#49db0cfbc9b08f6ae4949b2cb8bdef92d7d308dd" - integrity sha512-zKEgkWBFdGU2KiAMUKhucHqBfzDy2KNSefyqxpOiUd1L3PEKu+9o23HZRWzDupYltop+SO1Jw33CNoVAkKm4+A== +"@abp/select2@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-3.0.5.tgz#79d7754713c64a6549da520dc6867ef3e3dd0350" + integrity sha512-VbeSHdAdDy8exrjplHMMhLVV4oG7Gq9zBb5M52f0sIkA4DkXFhksXHcHNoOCfhaKAXpenGA03Z9GJewZ3MTpMw== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" select2 "^4.0.13" -"@abp/sweetalert@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/sweetalert/-/sweetalert-3.0.4.tgz#f85d11c5aabc6ceb509af1b8fd8d75bc3c74bd27" - integrity sha512-hO0qXUWxeMy/lfy3sb+5HvOmIPst6UvYJJGy3ZQR6DKEdkOktBX9sEDb7m6ij6xAIHqWIx1xLveul0oGteOG0Q== +"@abp/sweetalert@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/sweetalert/-/sweetalert-3.0.5.tgz#e3b92d23cf3633983980b6255bf63c43d422ceab" + integrity sha512-OUt4ANgj14GESfRG7+UqdONS8daOu+8x2OZDWv98hriuX0uCVBjwB2J0jRI/n653C68Mi0yHixGONDv9OLy/lg== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" sweetalert "^2.1.2" -"@abp/timeago@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-3.0.4.tgz#82d90a04f703a7db9c0acedcd2f3cea684e7ae18" - integrity sha512-7SbbqScTKCix25xWy5oLT0slJqUQsW6YgJgI0N41C3AsByRBXZeh5Gcep033gprTPmpgNY8JOWcEcCXySnYvfA== +"@abp/timeago@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-3.0.5.tgz#b191a7c43f3dc77645fe27273f2f6619f7d4eb31" + integrity sha512-QhvAOU4C+Qxh/gFuCSGZtLU4KwVhdv/jtK8ovWyqbGvQMCCIimqa8BFKIz0H5XWIJhHwwWblkpjICWgjWh6uXw== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" timeago "^1.6.7" -"@abp/toastr@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/toastr/-/toastr-3.0.4.tgz#884ab2343ddd59981c291c632e3b0630532f7e9c" - integrity sha512-NG0pVD8bdIXGpjPW9cozCzmyuAdnSLE4vDYLUReP1pZFp9B8SMJqpgPk9eFg0Y06aaHTVMKYBKRoq9xXS4w9Xg== +"@abp/toastr@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/toastr/-/toastr-3.0.5.tgz#fc83b678f5be3a22040d0421ad9db5b2eac9d4fc" + integrity sha512-qgi2DEu6FYpN/DYcOZrmXsAr1HqcIKMhBWeXgjfoYUIl/K5+OuUgbQ69D/jdc0jz8HU9a4Cw7Q8hhe4CkgssEw== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" toastr "^2.1.4" -"@abp/utils@^3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-3.0.4.tgz#38068fa8f735e1889cf2f226128366825948814d" - integrity sha512-TnXRZIZcxv5+EctEPsZEi2cyRXbpl3A679wzQa6BACv+pM45cjGO1JR8P0VswKN4TVL0aQRMv26N6U/uie8sEQ== +"@abp/utils@^3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-3.0.5.tgz#66b3da953500e73ff1f23d1b41168cde8216ce49" + integrity sha512-X9V1xLIEazItaf93nFSA4p94WT6QK3yA4eIyvU7GyyEkkOQdaakNwPbRwy930sd+I+/oXnIqM1vDsJu9QDhgvg== dependencies: just-compare "^1.3.0" diff --git a/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo.Docs.Admin.Application.Contracts.csproj b/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo.Docs.Admin.Application.Contracts.csproj index 72fd333456..d482c4d954 100644 --- a/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo.Docs.Admin.Application.Contracts.csproj +++ b/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo.Docs.Admin.Application.Contracts.csproj @@ -22,7 +22,7 @@
    - + - + diff --git a/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/cs.json b/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/cs.json index 081a5f2cd4..f02fa96f50 100644 --- a/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/cs.json +++ b/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/cs.json @@ -31,6 +31,8 @@ "DisplayName:GitHubRootUrl": "GitHub kořenové URL", "DisplayName:GitHubAccessToken": "GitHub přístupový token", "DisplayName:GitHubUserAgent": "GitHub user agent", + "DisplayName:GithubVersionProviderSource": "GitHub version provider source", + "DisplayName:VersionBranchPrefix": "Version branch prefix", "DisplayName:All": "Pull všeho", "DisplayName:LanguageCode": "Kód jazyka", "DisplayName:Version": "Verze", diff --git a/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/en.json b/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/en.json index b73f43317c..8b940df86d 100644 --- a/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/en.json +++ b/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/en.json @@ -31,6 +31,8 @@ "DisplayName:GitHubRootUrl": "GitHub root URL", "DisplayName:GitHubAccessToken": "GitHub access token", "DisplayName:GitHubUserAgent": "GitHub user agent", + "DisplayName:GithubVersionProviderSource": "GitHub version provider source", + "DisplayName:VersionBranchPrefix": "Version branch prefix", "DisplayName:All": "Pull all", "DisplayName:LanguageCode": "Language code", "DisplayName:Version": "Version", @@ -55,4 +57,4 @@ "FileName": "File name", "LastCachedTime": "Cache time" } -} \ No newline at end of file +} diff --git a/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Projects/CreateProjectDto.cs b/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Projects/CreateProjectDto.cs index eb603b334c..0918e19d8e 100644 --- a/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Projects/CreateProjectDto.cs +++ b/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Projects/CreateProjectDto.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Volo.Docs.Projects; namespace Volo.Docs.Admin.Projects { @@ -26,4 +27,4 @@ namespace Volo.Docs.Admin.Projects public Dictionary ExtraProperties { get; set; } } -} \ No newline at end of file +} diff --git a/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Projects/ProjectDto.cs b/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Projects/ProjectDto.cs index c71f34e357..b624a1f187 100644 --- a/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Projects/ProjectDto.cs +++ b/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Projects/ProjectDto.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using Volo.Abp.Application.Dtos; +using Volo.Docs.Projects; namespace Volo.Docs.Admin.Projects { @@ -29,4 +30,4 @@ namespace Volo.Docs.Admin.Projects public Dictionary ExtraProperties { get; set; } } -} \ No newline at end of file +} diff --git a/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Projects/UpdateProjectDto.cs b/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Projects/UpdateProjectDto.cs index 9e94cec66e..f2d3c218c8 100644 --- a/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Projects/UpdateProjectDto.cs +++ b/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Projects/UpdateProjectDto.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Volo.Docs.Projects; namespace Volo.Docs.Admin.Projects { @@ -22,4 +23,4 @@ namespace Volo.Docs.Admin.Projects public Dictionary ExtraProperties { get; set; } } -} \ No newline at end of file +} diff --git a/modules/docs/src/Volo.Docs.Admin.Web/DocsAdminWebAutoMapperProfile.cs b/modules/docs/src/Volo.Docs.Admin.Web/DocsAdminWebAutoMapperProfile.cs index ca430cdaad..83f993d1e8 100644 --- a/modules/docs/src/Volo.Docs.Admin.Web/DocsAdminWebAutoMapperProfile.cs +++ b/modules/docs/src/Volo.Docs.Admin.Web/DocsAdminWebAutoMapperProfile.cs @@ -15,7 +15,11 @@ namespace Volo.Docs.Admin CreateMap().Ignore(x => x.ExtraProperties); CreateMap () - .Ignore(x => x.GitHubAccessToken).Ignore(x => x.GitHubRootUrl).Ignore(x => x.GitHubUserAgent); + .Ignore(x => x.GitHubAccessToken) + .Ignore(x => x.GitHubRootUrl) + .Ignore(x => x.GitHubUserAgent) + .Ignore(x => x.GithubVersionProviderSource) + .Ignore(x => x.VersionBranchPrefix); CreateMap(); CreateMap(); diff --git a/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Create.cshtml.cs b/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Create.cshtml.cs index 506b674ba5..3f5b847538 100644 --- a/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Create.cshtml.cs +++ b/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Create.cshtml.cs @@ -62,7 +62,9 @@ namespace Volo.Docs.Admin.Pages.Docs.Admin.Projects { {nameof(GithubProject.GitHubRootUrl), GithubProject.GitHubRootUrl}, {nameof(GithubProject.GitHubUserAgent), GithubProject.GitHubUserAgent}, - {nameof(GithubProject.GitHubAccessToken), GithubProject.GitHubAccessToken} + {nameof(GithubProject.GitHubAccessToken), GithubProject.GitHubAccessToken}, + {nameof(GithubProject.GithubVersionProviderSource), GithubProject.GithubVersionProviderSource}, + {nameof(GithubProject.VersionBranchPrefix), GithubProject.VersionBranchPrefix} }; return dto; @@ -118,6 +120,13 @@ namespace Volo.Docs.Admin.Pages.Docs.Admin.Projects [DisplayOrder(10002)] [StringLength(64)] public string GitHubUserAgent { get; set; } + + [DisplayOrder(10003)] + public GithubVersionProviderSource GithubVersionProviderSource { get; set; } = GithubVersionProviderSource.Releases; + + [DisplayOrder(10004)] + [StringLength(64)] + public string VersionBranchPrefix { get; set; } } } -} \ No newline at end of file +} diff --git a/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Edit.cshtml.cs b/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Edit.cshtml.cs index ef1762c460..ee27a2dc15 100644 --- a/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Edit.cshtml.cs +++ b/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Edit.cshtml.cs @@ -62,7 +62,9 @@ namespace Volo.Docs.Admin.Pages.Docs.Admin.Projects { {nameof(GithubProject.GitHubRootUrl), GithubProject.GitHubRootUrl}, {nameof(GithubProject.GitHubUserAgent), GithubProject.GitHubUserAgent}, - {nameof(GithubProject.GitHubAccessToken), GithubProject.GitHubAccessToken} + {nameof(GithubProject.GitHubAccessToken), GithubProject.GitHubAccessToken}, + {nameof(GithubProject.GithubVersionProviderSource), GithubProject.GithubVersionProviderSource}, + {nameof(GithubProject.VersionBranchPrefix), GithubProject.VersionBranchPrefix} }; return dto; @@ -75,6 +77,16 @@ namespace Volo.Docs.Admin.Pages.Docs.Admin.Projects GithubProject.GitHubAccessToken = (string) dto.ExtraProperties[nameof(GithubProject.GitHubAccessToken)]; GithubProject.GitHubRootUrl = (string) dto.ExtraProperties[nameof(GithubProject.GitHubRootUrl)]; GithubProject.GitHubUserAgent = (string) dto.ExtraProperties[nameof(GithubProject.GitHubUserAgent)]; + + if (dto.ExtraProperties.ContainsKey(nameof(GithubProject.GithubVersionProviderSource))) + { + GithubProject.GithubVersionProviderSource = (GithubVersionProviderSource) (long) dto.ExtraProperties[nameof(GithubProject.GithubVersionProviderSource)]; + } + + if (dto.ExtraProperties.ContainsKey(nameof(GithubProject.VersionBranchPrefix))) + { + GithubProject.VersionBranchPrefix = (string) dto.ExtraProperties[nameof(GithubProject.VersionBranchPrefix)]; + } } public abstract class EditProjectViewModelBase @@ -125,6 +137,13 @@ namespace Volo.Docs.Admin.Pages.Docs.Admin.Projects [DisplayOrder(10002)] [StringLength(64)] public string GitHubUserAgent { get; set; } + + [DisplayOrder(10003)] + public GithubVersionProviderSource GithubVersionProviderSource { get; set; } = GithubVersionProviderSource.Releases; + + [DisplayOrder(10004)] + [StringLength(64)] + public string VersionBranchPrefix { get; set; } } } -} \ No newline at end of file +} diff --git a/modules/docs/src/Volo.Docs.Admin.Web/Volo.Docs.Admin.Web.csproj b/modules/docs/src/Volo.Docs.Admin.Web/Volo.Docs.Admin.Web.csproj index 48c28054ef..8e6805cf0a 100644 --- a/modules/docs/src/Volo.Docs.Admin.Web/Volo.Docs.Admin.Web.csproj +++ b/modules/docs/src/Volo.Docs.Admin.Web/Volo.Docs.Admin.Web.csproj @@ -17,7 +17,7 @@ - + @@ -27,16 +27,16 @@ - + - + - + diff --git a/modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs b/modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs index 6cacf9d3e3..6df4c6179e 100644 --- a/modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs +++ b/modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs @@ -64,6 +64,8 @@ namespace Volo.Docs.Documents { var project = await _projectRepository.GetAsync(input.ProjectId); + input.Version = GetProjectVersionPrefixIfExist(project) + input.Version; + return await GetDocumentWithDetailsDtoAsync( project, input.Name, @@ -76,6 +78,8 @@ namespace Volo.Docs.Documents { var project = await _projectRepository.GetAsync(input.ProjectId); + input.Version = GetProjectVersionPrefixIfExist(project) + input.Version; + return await GetDocumentWithDetailsDtoAsync( project, project.DefaultDocumentName + "." + project.Format, @@ -88,6 +92,8 @@ namespace Volo.Docs.Documents { var project = await _projectRepository.GetAsync(input.ProjectId); + input.Version = GetProjectVersionPrefixIfExist(project) + input.Version; + var navigationDocument = await GetDocumentWithDetailsDtoAsync( project, project.NavigationDocumentName, @@ -133,10 +139,11 @@ namespace Volo.Docs.Documents public async Task GetResourceAsync(GetDocumentResourceInput input) { var project = await _projectRepository.GetAsync(input.ProjectId); - var cacheKey = - CacheKeyGenerator.GenerateDocumentResourceCacheKey(project, input.Name, input.LanguageCode, - input.Version); + input.Version = string.IsNullOrWhiteSpace(input.Version) ? project.LatestVersionBranchName : input.Version; + input.Version = GetProjectVersionPrefixIfExist(project) + input.Version; + + var cacheKey = CacheKeyGenerator.GenerateDocumentResourceCacheKey(project, input.Name, input.LanguageCode,input.Version); async Task GetResourceAsync() { @@ -166,6 +173,8 @@ namespace Volo.Docs.Documents { var project = await _projectRepository.GetAsync(input.ProjectId); + input.Version = GetProjectVersionPrefixIfExist(project) + input.Version; + var esDocs = await _documentFullSearch.SearchAsync(input.Context, project.Id, input.LanguageCode, input.Version); @@ -188,6 +197,8 @@ namespace Volo.Docs.Documents { var project = await _projectRepository.GetAsync(input.ProjectId); + input.Version = GetProjectVersionPrefixIfExist(project) + input.Version; + try { if (string.IsNullOrWhiteSpace(project.ParametersDocumentName)) @@ -337,5 +348,22 @@ namespace Volo.Docs.Documents return TimeSpan.Parse(value); } + + private string GetProjectVersionPrefixIfExist(Project project) + { + if (GetGithubVersionProviderSource(project) == GithubVersionProviderSource.Branches) + { + return project.ExtraProperties["VersionBranchPrefix"].ToString(); + } + + return ""; + } + + private GithubVersionProviderSource GetGithubVersionProviderSource(Project project) + { + return project.ExtraProperties.ContainsKey("GithubVersionProviderSource") + ? (GithubVersionProviderSource) (long) project.ExtraProperties["GithubVersionProviderSource"] + : GithubVersionProviderSource.Releases; + } } } diff --git a/modules/docs/src/Volo.Docs.Application/Volo/Docs/Projects/ProjectAppService.cs b/modules/docs/src/Volo.Docs.Application/Volo/Docs/Projects/ProjectAppService.cs index 04b96f9e6f..728840135e 100644 --- a/modules/docs/src/Volo.Docs.Application/Volo/Docs/Projects/ProjectAppService.cs +++ b/modules/docs/src/Volo.Docs.Application/Volo/Docs/Projects/ProjectAppService.cs @@ -86,11 +86,6 @@ namespace Volo.Docs.Projects } } - if (versions.Any() && !string.IsNullOrEmpty(project.LatestVersionBranchName)) - { - versions.First().Name = project.LatestVersionBranchName; - } - return versions; } @@ -111,6 +106,8 @@ namespace Volo.Docs.Projects var project = await _projectRepository.GetByShortNameAsync(shortName); var store = _documentSource.Create(project.DocumentStoreType); + version = GetProjectVersionPrefixIfExist(project) + version; + async Task GetLanguagesAsync() { return await store.GetLanguageListAsync(project, version); @@ -125,5 +122,22 @@ namespace Volo.Docs.Projects } ); } + + private string GetProjectVersionPrefixIfExist(Project project) + { + if (GetGithubVersionProviderSource(project) == GithubVersionProviderSource.Branches) + { + return project.ExtraProperties["VersionBranchPrefix"].ToString(); + } + + return ""; + } + + private GithubVersionProviderSource GetGithubVersionProviderSource(Project project) + { + return project.ExtraProperties.ContainsKey("GithubVersionProviderSource") + ? (GithubVersionProviderSource) (long) project.ExtraProperties["GithubVersionProviderSource"] + : GithubVersionProviderSource.Releases; + } } -} \ No newline at end of file +} diff --git a/modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Projects/GithubVersionProviderSource.cs b/modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Projects/GithubVersionProviderSource.cs new file mode 100644 index 0000000000..4773ecf74d --- /dev/null +++ b/modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Projects/GithubVersionProviderSource.cs @@ -0,0 +1,8 @@ +namespace Volo.Docs.Projects +{ + public enum GithubVersionProviderSource + { + Releases, + Branches + } +} diff --git a/modules/docs/src/Volo.Docs.Domain/Volo.Docs.Domain.csproj b/modules/docs/src/Volo.Docs.Domain/Volo.Docs.Domain.csproj index 1042aeec0e..0f0b393787 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo.Docs.Domain.csproj +++ b/modules/docs/src/Volo.Docs.Domain/Volo.Docs.Domain.csproj @@ -17,12 +17,13 @@ - + - + + - + diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/FullSearch/Elastic/ElasticDocumentFullSearch.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/FullSearch/Elastic/ElasticDocumentFullSearch.cs index 05a3f1c4c8..b525aa65d9 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/FullSearch/Elastic/ElasticDocumentFullSearch.cs +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/FullSearch/Elastic/ElasticDocumentFullSearch.cs @@ -73,7 +73,7 @@ namespace Volo.Docs.Documents.FullSearch.Elastic FileName = document.FileName, Content = document.Content, LanguageCode = ConvertLanguageCode(document.LanguageCode), - Version = document.Version + Version = ConvertVersion(document.Version) }; if (!existsResponse.Exists) @@ -173,7 +173,7 @@ namespace Volo.Docs.Documents.FullSearch.Elastic new TermQuery { Field = "version", - Value = version + Value = ConvertVersion(version) }, new TermQuery { @@ -237,5 +237,10 @@ namespace Volo.Docs.Documents.FullSearch.Elastic { return languageCode.Replace("-", "").ToLower(); } + + protected string ConvertVersion(string version) + { + return version.Replace("-", "").ToLower(); + } } } diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubDocumentSource.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubDocumentSource.cs index 1da1ab1f9d..3a79227934 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubDocumentSource.cs +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubDocumentSource.cs @@ -9,6 +9,7 @@ using Volo.Docs.GitHub.Projects; using Volo.Docs.Projects; using Octokit; using Volo.Abp; +using Volo.Docs.GitHub.Documents.Version; using Volo.Extensions; using Project = Volo.Docs.Projects.Project; @@ -22,11 +23,13 @@ namespace Volo.Docs.GitHub.Documents private readonly IGithubRepositoryManager _githubRepositoryManager; private readonly IGithubPatchAnalyzer _githubPatchAnalyzer; + private readonly IVersionHelper _versionHelper; - public GithubDocumentSource(IGithubRepositoryManager githubRepositoryManager, IGithubPatchAnalyzer githubPatchAnalyzer) + public GithubDocumentSource(IGithubRepositoryManager githubRepositoryManager, IGithubPatchAnalyzer githubPatchAnalyzer, IVersionHelper versionHelper) { _githubRepositoryManager = githubRepositoryManager; _githubPatchAnalyzer = githubPatchAnalyzer; + _versionHelper = versionHelper; } public virtual async Task GetDocumentAsync(Project project, string documentName, string languageCode, string version, DateTime? lastKnownSignificantUpdateTime = null) @@ -172,7 +175,7 @@ namespace Volo.Docs.GitHub.Documents * Getting file commits usually throws "Resource temporarily unavailable" or "Network is unreachable" * This is a trival information and running this inside try-catch is safer. */ - + try { return await GetFileCommitsAsync(project, version, project.GetGitHubInnerUrl(languageCode, documentName)); @@ -221,15 +224,19 @@ namespace Volo.Docs.GitHub.Documents public async Task> GetVersionsAsync(Project project) { + var url = project.GetGitHubUrl(); + var ownerName = GetOwnerNameFromUrl(url); + var repositoryName = GetRepositoryNameFromUrl(url); + var githubVersionProviderSource = GetGithubVersionProviderSource(project); + List versions; try { - versions = (await GetReleasesAsync(project)) - .OrderByDescending(r => r.PublishedAt) + versions = (await _githubRepositoryManager.GetVersionsAsync(ownerName, repositoryName, project.GetGitHubAccessTokenOrNull(), githubVersionProviderSource)) .Select(r => new VersionInfo { - Name = r.TagName, - DisplayName = r.TagName + Name = r.Name, + DisplayName = r.Name }).ToList(); } catch (Exception ex) @@ -239,14 +246,45 @@ namespace Volo.Docs.GitHub.Documents versions = new List(); } - if (!versions.Any() && !string.IsNullOrEmpty(project.LatestVersionBranchName)) + if (githubVersionProviderSource == GithubVersionProviderSource.Branches && project.ExtraProperties.ContainsKey("VersionBranchPrefix")) + { + var prefix = (string) project.ExtraProperties["VersionBranchPrefix"]; + + if (!string.IsNullOrEmpty(prefix)) + { + versions = versions.Where(v => v.Name.StartsWith(prefix)).ToList(); + foreach (var v in versions) + { + v.Name = v.Name.Substring(prefix.Length); + v.DisplayName = v.DisplayName.Substring(prefix.Length); + } + } + + versions = _versionHelper.OrderByDescending(versions); + } + + if(githubVersionProviderSource == GithubVersionProviderSource.Releases) { - versions.Add(new VersionInfo { DisplayName = "1.0.0", Name = project.LatestVersionBranchName }); + if (!versions.Any() && !string.IsNullOrEmpty(project.LatestVersionBranchName)) + { + versions.Add(new VersionInfo { DisplayName = "1.0.0", Name = project.LatestVersionBranchName }); + } + else + { + versions = _versionHelper.OrderByDescending(versions); + } } return versions; } + private GithubVersionProviderSource GetGithubVersionProviderSource(Project project) + { + return project.ExtraProperties.ContainsKey("GithubVersionProviderSource") + ? (GithubVersionProviderSource) (long) project.ExtraProperties["GithubVersionProviderSource"] + : GithubVersionProviderSource.Releases; + } + public async Task GetResource(Project project, string resourceName, string languageCode, string version) { var rawRootUrl = CalculateRawRootUrlWithLanguageCode(project.GetGitHubUrl(version), languageCode); @@ -277,14 +315,6 @@ namespace Volo.Docs.GitHub.Documents return languageConfig; } - private async Task> GetReleasesAsync(Project project) - { - var url = project.GetGitHubUrl(); - var ownerName = GetOwnerNameFromUrl(url); - var repositoryName = GetRepositoryNameFromUrl(url); - return await _githubRepositoryManager.GetReleasesAsync(ownerName, repositoryName, project.GetGitHubAccessTokenOrNull()); - } - private async Task> GetFileCommitsAsync(Project project, string version, string filename) { var url = project.GetGitHubUrl(); diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubRepositoryManager.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubRepositoryManager.cs index ef21353236..8d99e360ef 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubRepositoryManager.cs +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubRepositoryManager.cs @@ -6,6 +6,8 @@ using System.Net.Http.Headers; using System.Threading.Tasks; using Octokit; using Octokit.Internal; +using Volo.Docs.GitHub.Documents.Version; +using Volo.Docs.Projects; using ProductHeaderValue = Octokit.ProductHeaderValue; namespace Volo.Docs.GitHub.Documents @@ -15,10 +17,12 @@ namespace Volo.Docs.GitHub.Documents public const string HttpClientName = "GithubRepositoryManagerHttpClientName"; private readonly IHttpClientFactory _clientFactory; + private readonly IGithubVersionProviderFactory _githubVersionProviderFactory; - public GithubRepositoryManager(IHttpClientFactory clientFactory) + public GithubRepositoryManager(IHttpClientFactory clientFactory, IGithubVersionProviderFactory githubVersionProviderFactory) { _clientFactory = clientFactory; + _githubVersionProviderFactory = githubVersionProviderFactory; } public async Task GetFileRawStringContentAsync(string rawUrl, string token, string userAgent) @@ -32,11 +36,12 @@ namespace Volo.Docs.GitHub.Documents using var httpClient = CreateHttpClient(token, userAgent); return await httpClient.GetByteArrayAsync(new Uri(rawUrl)); } - - public async Task> GetReleasesAsync(string name, string repositoryName, string token) + + public async Task> GetVersionsAsync(string name, string repositoryName, string token, GithubVersionProviderSource githubVersionProviderSource) { - var client = GetGitHubClient(name, token); - return await client.Repository.Release.GetAll(name, repositoryName); + var _provider = _githubVersionProviderFactory.Create(githubVersionProviderSource); + + return await _provider.GetVersions(name, repositoryName, token); } public async Task> GetFileCommitsAsync(string name, string repositoryName, string version, string filename, string token) diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/IGithubRepositoryManager.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/IGithubRepositoryManager.cs index 67536fc559..12f3bc4ef2 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/IGithubRepositoryManager.cs +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/IGithubRepositoryManager.cs @@ -4,6 +4,8 @@ using System.Text; using System.Threading.Tasks; using Octokit; using Volo.Abp.DependencyInjection; +using Volo.Docs.GitHub.Documents.Version; +using Volo.Docs.Projects; namespace Volo.Docs.GitHub.Documents { @@ -13,7 +15,7 @@ namespace Volo.Docs.GitHub.Documents Task GetFileRawByteArrayContentAsync(string rawUrl, string token, string userAgent); - Task> GetReleasesAsync(string name, string repositoryName, string token); + Task> GetVersionsAsync(string name, string repositoryName, string token, GithubVersionProviderSource githubVersionProviderSource); Task> GetFileCommitsAsync(string name, string repositoryName, string version, string filename, string token); diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/Version/BranchGithubVersionProvider.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/Version/BranchGithubVersionProvider.cs new file mode 100644 index 0000000000..524eecb33c --- /dev/null +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/Version/BranchGithubVersionProvider.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Octokit; +using Octokit.Internal; +using Volo.Abp.DependencyInjection; + +namespace Volo.Docs.GitHub.Documents.Version +{ + public class BranchGithubVersionProvider : IGithubVersionProvider, ITransientDependency + { + public async Task> GetVersions(string name, string repositoryName, string token) + { + var client = GetGitHubClient(name, token); + var branches = await client.Repository.Branch.GetAll(name, repositoryName); + + return branches.Select(b => new GithubVersion {Name = b.Name}).ToList(); + } + + private static GitHubClient GetGitHubClient(string name, string token) + { + return token.IsNullOrWhiteSpace() + ? new GitHubClient(new ProductHeaderValue(name)) + : new GitHubClient(new ProductHeaderValue(name), new InMemoryCredentialStore(new Credentials(token))); + } + } +} diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/Version/GithubVersion.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/Version/GithubVersion.cs new file mode 100644 index 0000000000..5c3e98aac0 --- /dev/null +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/Version/GithubVersion.cs @@ -0,0 +1,7 @@ +namespace Volo.Docs.GitHub.Documents.Version +{ + public class GithubVersion + { + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/Version/GithubVersionProviderFactory.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/Version/GithubVersionProviderFactory.cs new file mode 100644 index 0000000000..d3c7ba9a72 --- /dev/null +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/Version/GithubVersionProviderFactory.cs @@ -0,0 +1,34 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.DependencyInjection; +using Volo.Docs.Projects; + +namespace Volo.Docs.GitHub.Documents.Version +{ + public class GithubVersionProviderFactory : IGithubVersionProviderFactory, ITransientDependency + { + public IServiceProvider ServiceProvider { get; } + + public GithubVersionProviderFactory(IServiceProvider serviceProvider) + { + ServiceProvider = serviceProvider; + } + public IGithubVersionProvider Create(GithubVersionProviderSource source) + { + Type serviceType; + + switch (source) + { + case GithubVersionProviderSource.Branches: + serviceType = typeof(BranchGithubVersionProvider); + break; + case GithubVersionProviderSource.Releases: + default: + serviceType = typeof(ReleaseGithubVersionProvider); + break; + } + + return (IGithubVersionProvider)ServiceProvider.GetRequiredService(serviceType); + } + } +} diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/Version/IGithubVersionProvider.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/Version/IGithubVersionProvider.cs new file mode 100644 index 0000000000..429e2ec34b --- /dev/null +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/Version/IGithubVersionProvider.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Volo.Docs.GitHub.Documents.Version +{ + public interface IGithubVersionProvider + { + Task> GetVersions(string name, string repositoryName, string token); + } +} diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/Version/IGithubVersionProviderFactory.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/Version/IGithubVersionProviderFactory.cs new file mode 100644 index 0000000000..5236e52fbd --- /dev/null +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/Version/IGithubVersionProviderFactory.cs @@ -0,0 +1,9 @@ +using Volo.Docs.Projects; + +namespace Volo.Docs.GitHub.Documents.Version +{ + public interface IGithubVersionProviderFactory + { + IGithubVersionProvider Create(GithubVersionProviderSource source); + } +} diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/Version/IVersionHelper.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/Version/IVersionHelper.cs new file mode 100644 index 0000000000..67bed57093 --- /dev/null +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/Version/IVersionHelper.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using Volo.Docs.Projects; + +namespace Volo.Docs.GitHub.Documents.Version +{ + public interface IVersionHelper + { + List OrderByDescending(List versions); + + List OrderByDescending(List versions); + + bool IsPreRelease(string version); + } +} diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/Version/ReleaseGithubVersionProvider.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/Version/ReleaseGithubVersionProvider.cs new file mode 100644 index 0000000000..f4b9fee2b2 --- /dev/null +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/Version/ReleaseGithubVersionProvider.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Octokit; +using Octokit.Internal; +using Volo.Abp.DependencyInjection; + +namespace Volo.Docs.GitHub.Documents.Version +{ + public class ReleaseGithubVersionProvider : IGithubVersionProvider, ITransientDependency + { + public async Task> GetVersions(string name, string repositoryName, string token) + { + var client = GetGitHubClient(name, token); + var releases = await client.Repository.Release.GetAll(name, repositoryName); + + return releases.Select(r => new GithubVersion {Name = r.TagName}).ToList(); + } + + private static GitHubClient GetGitHubClient(string name, string token) + { + return token.IsNullOrWhiteSpace() + ? new GitHubClient(new ProductHeaderValue(name)) + : new GitHubClient(new ProductHeaderValue(name), new InMemoryCredentialStore(new Credentials(token))); + } + } +} diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/Version/SemanticVersionHelper.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/Version/SemanticVersionHelper.cs new file mode 100644 index 0000000000..fbd315f834 --- /dev/null +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/Version/SemanticVersionHelper.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NuGet.Versioning; +using Volo.Abp.DependencyInjection; +using Volo.Docs.Projects; + +namespace Volo.Docs.GitHub.Documents.Version +{ + public class SemanticVersionHelper : IVersionHelper, ITransientDependency + { + public List OrderByDescending(List versions) + { + return versions.OrderByDescending(v=> SemanticVersion.Parse(NormalizeVersion(v)), new VersionComparer()).ToList(); + } + + public List OrderByDescending(List versions) + { + return versions.OrderByDescending(v => SemanticVersion.Parse(NormalizeVersion(v.Name)), new VersionComparer()).ToList(); + } + + public bool IsPreRelease(string version) + { + return SemanticVersion.Parse(NormalizeVersion(version)).IsPrerelease; + } + + private string NormalizeVersion(string version) + { + version = version.RemovePreFix("v"); + + var normalizedVersion = ""; + + var versionParts = version.Split("-"); + + var firstVersionPartSplitted = versionParts[0].Split("."); + + if (firstVersionPartSplitted.Length > 3) + { + normalizedVersion = string.Join(".",firstVersionPartSplitted.Take(3)); + } + else if (firstVersionPartSplitted.Length < 3) + { + normalizedVersion = versionParts[0]; + for (int i = firstVersionPartSplitted.Length; i < 3; i++) + { + normalizedVersion += ".0"; + } + } + else + { + normalizedVersion = versionParts[0]; + } + + if (versionParts.Length > 1) + { + return normalizedVersion + "-" + string.Join("-", versionParts.Skip(1)); + } + + return normalizedVersion; + } + } +} diff --git a/modules/docs/src/Volo.Docs.Web/Areas/Documents/TagHelpers/TreeTagHelper.cs b/modules/docs/src/Volo.Docs.Web/Areas/Documents/TagHelpers/TreeTagHelper.cs index 5f7ae8e7a7..726121ab5d 100644 --- a/modules/docs/src/Volo.Docs.Web/Areas/Documents/TagHelpers/TreeTagHelper.cs +++ b/modules/docs/src/Volo.Docs.Web/Areas/Documents/TagHelpers/TreeTagHelper.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.Text; using Microsoft.AspNetCore.Razor.TagHelpers; @@ -132,8 +132,7 @@ namespace Volo.Docs.Areas.Documents.TagHelpers var newBadge = "" + _localizer["New"] + ""; badge += newBadge; } - - if (node.LastSignificantUpdateTime != null && node.LastSignificantUpdateTime + TimeSpan.FromDays(14) > DateTime.Now) + else if (node.LastSignificantUpdateTime != null && node.LastSignificantUpdateTime + TimeSpan.FromDays(14) > DateTime.Now) { var updBadge = "" + _localizer["Upd"] + ""; badge += updBadge; diff --git a/modules/docs/src/Volo.Docs.Web/DocsUiOptions.cs b/modules/docs/src/Volo.Docs.Web/DocsUiOptions.cs index 4b4ab6a3b4..b4f3dfbf21 100644 --- a/modules/docs/src/Volo.Docs.Web/DocsUiOptions.cs +++ b/modules/docs/src/Volo.Docs.Web/DocsUiOptions.cs @@ -21,6 +21,12 @@ namespace Volo.Docs ///
    public bool ShowProjectsCombobox = true; + /// + /// If true, allows to create sections in document and show/hide sections according to user preferences. + /// Default value: True; + /// + public bool SectionRendering = true; + private string GetFormattedRoutePrefix() { if (string.IsNullOrWhiteSpace(_routePrefix)) diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml index 90a754b1a9..bd76814461 100644 --- a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml @@ -242,8 +242,8 @@ } - - + + diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs index a86d895c90..4568e574a5 100644 --- a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs @@ -16,6 +16,7 @@ using Volo.Docs.Documents; using Volo.Docs.HtmlConverting; using Volo.Docs.Models; using Volo.Docs.Projects; +using Volo.Docs.GitHub.Documents.Version; namespace Volo.Docs.Pages.Documents.Project { @@ -76,6 +77,7 @@ namespace Volo.Docs.Pages.Documents.Project private readonly IDocumentToHtmlConverterFactory _documentToHtmlConverterFactory; private readonly IProjectAppService _projectAppService; private readonly IDocumentSectionRenderer _documentSectionRenderer; + private readonly IVersionHelper _versionHelper; private readonly DocsUiOptions _uiOptions; public IndexModel( @@ -83,7 +85,8 @@ namespace Volo.Docs.Pages.Documents.Project IDocumentToHtmlConverterFactory documentToHtmlConverterFactory, IProjectAppService projectAppService, IOptions options, - IDocumentSectionRenderer documentSectionRenderer) + IDocumentSectionRenderer documentSectionRenderer, + IVersionHelper versionHelper) { ObjectMapperContext = typeof(DocsWebModule); @@ -91,6 +94,7 @@ namespace Volo.Docs.Pages.Documents.Project _documentToHtmlConverterFactory = documentToHtmlConverterFactory; _projectAppService = projectAppService; _documentSectionRenderer = documentSectionRenderer; + _versionHelper = versionHelper; _uiOptions = options.Value; } @@ -250,13 +254,16 @@ namespace Volo.Docs.Pages.Documents.Project } var output = await _projectAppService.GetVersionsAsync(Project.ShortName); - var versions = output.Items + var versions = output.Items.ToList() .Select(v => new VersionInfoViewModel(v.DisplayName, v.Name)) .ToList(); if (versions.Any()) { - LatestVersionInfo = versions.First(); + LatestVersionInfo = GetLatestVersionInfo(versions); + + SetLatestVersionBranchName(versions); + LatestVersionInfo.DisplayText = $"{LatestVersionInfo.DisplayText} ({DocsAppConsts.Latest})"; if (string.Equals(Version, DocsAppConsts.Latest, StringComparison.OrdinalIgnoreCase)) @@ -274,13 +281,15 @@ namespace Volo.Docs.Pages.Documents.Project } else { - versions.First().IsSelected = true; - Version = versions.First().Version; + LatestVersionInfo.IsSelected = true; + Version = LatestVersionInfo.Version; } } } else { + SetLatestVersionBranchName(versions); + LatestVersionInfo = new VersionInfoViewModel( $"{DocsAppConsts.Latest}", DocsAppConsts.Latest, @@ -295,6 +304,50 @@ namespace Volo.Docs.Pages.Documents.Project }).ToList(); } + private VersionInfoViewModel GetLatestVersionInfo(List versions) + { + if (Project.ExtraProperties.ContainsKey("GithubVersionProviderSource") + && (GithubVersionProviderSource) (long) Project.ExtraProperties["GithubVersionProviderSource"] == GithubVersionProviderSource.Branches) + { + var LatestVersionBranchNameWithoutPrefix = RemoveVersionPrefix(Project.LatestVersionBranchName); + + var latest = versions.FirstOrDefault(v=> v.Version == LatestVersionBranchNameWithoutPrefix); + + if (latest != null) + { + return latest; + } + } + + return versions.FirstOrDefault(v => !_versionHelper.IsPreRelease(v.Version)) ?? versions.First(); + } + + private string RemoveVersionPrefix(string version) + { + if (!Project.ExtraProperties.ContainsKey("VersionBranchPrefix")) + { + return version; + } + + var prefix = Project.ExtraProperties["VersionBranchPrefix"].ToString(); + + if (string.IsNullOrWhiteSpace(version) || !version.StartsWith(prefix) || version.Length <= prefix.Length) + { + return version; + } + + return version.Substring(prefix.Length); + } + + private void SetLatestVersionBranchName(List versions) + { + if (!Project.ExtraProperties.ContainsKey("GithubVersionProviderSource") + || (GithubVersionProviderSource) (long) Project.ExtraProperties["GithubVersionProviderSource"] == GithubVersionProviderSource.Releases) + { + versions.First(v=> !_versionHelper.IsPreRelease(v.Version)).Version = Project.LatestVersionBranchName; + } + } + private async Task SetNavigationAsync() { try @@ -338,7 +391,7 @@ namespace Volo.Docs.Pages.Documents.Project return DocsAppConsts.Latest; } - return Document.Version == LatestVersionInfo.Version ? + return RemoveVersionPrefix(Document.Version) == LatestVersionInfo.Version ? DocsAppConsts.Latest : Document.Version; } @@ -386,12 +439,15 @@ namespace Volo.Docs.Pages.Documents.Project private async Task ConvertDocumentContentToHtmlAsync() { - await SetDocumentPreferencesAsync(); - SetUserPreferences(); + if (_uiOptions.SectionRendering) + { + await SetDocumentPreferencesAsync(); + SetUserPreferences(); - var partialTemplates = await GetDocumentPartialTemplatesAsync(); + var partialTemplates = await GetDocumentPartialTemplatesAsync(); - Document.Content = await _documentSectionRenderer.RenderAsync(Document.Content, UserPreferences, partialTemplates); + Document.Content = await _documentSectionRenderer.RenderAsync(Document.Content, UserPreferences, partialTemplates); + } var converter = _documentToHtmlConverterFactory.Create(Document.Format ?? Project.Format); var content = converter.Convert(Project, Document, GetSpecificVersionOrLatest(), LanguageCode); @@ -546,7 +602,7 @@ namespace Volo.Docs.Pages.Documents.Project } } - public async Task SetDocumentPreferencesAsync() + private async Task SetDocumentPreferencesAsync() { var projectParameters = await _documentAppService.GetParametersAsync( new GetParametersDocumentInput diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Search.cshtml.cs b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Search.cshtml.cs index 1e4e797e73..fbd58275ff 100644 --- a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Search.cshtml.cs +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Search.cshtml.cs @@ -7,39 +7,40 @@ using System.Web; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Volo.Docs.Documents; +using Volo.Docs.GitHub.Documents.Version; using Volo.Docs.HtmlConverting; using Volo.Docs.Models; using Volo.Docs.Projects; +using Volo.Docs.Utils; namespace Volo.Docs.Pages.Documents { public class SearchModel : PageModel { - [BindProperty(SupportsGet = true)] - public string ProjectName { get; set; } + [BindProperty(SupportsGet = true)] public string ProjectName { get; set; } - [BindProperty(SupportsGet = true)] - public string Version { get; set; } + [BindProperty(SupportsGet = true)] public string Version { get; set; } - [BindProperty(SupportsGet = true)] - public string LanguageCode { get; set; } + [BindProperty(SupportsGet = true)] public string LanguageCode { get; set; } - [BindProperty(SupportsGet = true)] - public string KeyWord { get; set; } + [BindProperty(SupportsGet = true)] public string KeyWord { get; set; } public ProjectDto Project { get; set; } private readonly IProjectAppService _projectAppService; private readonly IDocumentAppService _documentAppService; private readonly HtmlEncoder _encoder; + private readonly IVersionHelper _versionHelper; public SearchModel(IProjectAppService projectAppService, IDocumentAppService documentAppService, - HtmlEncoder encoder) + HtmlEncoder encoder, + IVersionHelper versionHelper) { _projectAppService = projectAppService; _documentAppService = documentAppService; _encoder = encoder; + _versionHelper = versionHelper; } public List SearchOutputs { get; set; } = new List(); @@ -59,9 +60,19 @@ namespace Volo.Docs.Pages.Documents var versions = output.Items.ToList(); - if (versions.Any() && string.Equals(Version, DocsAppConsts.Latest, StringComparison.OrdinalIgnoreCase)) + if (versions.Any() && + string.Equals(Version, DocsAppConsts.Latest, StringComparison.OrdinalIgnoreCase)) { - Version = versions.First().Name; + if ((!Project.ExtraProperties.ContainsKey("GithubVersionProviderSource") || + (GithubVersionProviderSource) (long) Project.ExtraProperties["GithubVersionProviderSource"] ==GithubVersionProviderSource.Releases) && + !string.IsNullOrEmpty(Project.LatestVersionBranchName)) + { + Version = Project.LatestVersionBranchName; + } + else + { + Version = (versions.FirstOrDefault(v => !_versionHelper.IsPreRelease(v.Name)) ?? versions.First()).Name; + } } SearchOutputs = await _documentAppService.SearchAsync(new DocumentSearchInput @@ -79,7 +90,8 @@ namespace Volo.Docs.Pages.Documents for (var i = 0; i < searchOutput.Highlight.Count; i++) { searchOutput.Highlight[i] = _encoder - .Encode(searchOutput.Highlight[i].Replace("", highlightTag1).Replace("", highlightTag2)) + .Encode(searchOutput.Highlight[i].Replace("", highlightTag1) + .Replace("", highlightTag2)) .Replace(highlightTag1, "").Replace(highlightTag2, ""); } } diff --git a/modules/docs/src/Volo.Docs.Web/Volo.Docs.Web.csproj b/modules/docs/src/Volo.Docs.Web/Volo.Docs.Web.csproj index a82e806e24..cde0af6a76 100644 --- a/modules/docs/src/Volo.Docs.Web/Volo.Docs.Web.csproj +++ b/modules/docs/src/Volo.Docs.Web/Volo.Docs.Web.csproj @@ -24,7 +24,7 @@ - +
    diff --git a/modules/docs/test/Volo.Docs.EntityFrameworkCore.Tests/Volo.Docs.EntityFrameworkCore.Tests.csproj b/modules/docs/test/Volo.Docs.EntityFrameworkCore.Tests/Volo.Docs.EntityFrameworkCore.Tests.csproj index 7774d2b76a..f24d08ea52 100644 --- a/modules/docs/test/Volo.Docs.EntityFrameworkCore.Tests/Volo.Docs.EntityFrameworkCore.Tests.csproj +++ b/modules/docs/test/Volo.Docs.EntityFrameworkCore.Tests/Volo.Docs.EntityFrameworkCore.Tests.csproj @@ -7,7 +7,7 @@ - + diff --git a/modules/docs/test/Volo.Docs.TestBase/Volo/Docs/DocsTestBase.cs b/modules/docs/test/Volo.Docs.TestBase/Volo/Docs/DocsTestBase.cs index 56a8ea0203..16a8e5538d 100644 --- a/modules/docs/test/Volo.Docs.TestBase/Volo/Docs/DocsTestBase.cs +++ b/modules/docs/test/Volo.Docs.TestBase/Volo/Docs/DocsTestBase.cs @@ -7,10 +7,12 @@ using Volo.Abp; using Volo.Abp.Modularity; using Volo.Docs.GitHub.Documents; using Volo.Abp.Testing; +using Volo.Docs.GitHub.Documents.Version; +using Volo.Docs.Projects; namespace Volo.Docs { - public abstract class DocsTestBase : AbpIntegratedTest + public abstract class DocsTestBase : AbpIntegratedTest where TStartupModule : IAbpModule { protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) @@ -30,27 +32,13 @@ namespace Volo.Docs repositoryManager.GetFileRawByteArrayContentAsync(Arg.Any(), Arg.Any(), Arg.Any()) .Returns(new byte[] { 0x01, 0x02, 0x03 }); - repositoryManager.GetReleasesAsync(Arg.Any(), Arg.Any(), Arg.Any()) - .Returns(new List + repositoryManager.GetVersionsAsync(Arg.Any(), Arg.Any(), Arg.Any(), GithubVersionProviderSource.Releases) + .Returns(new List { - new Release("https://api.github.com/repos/abpframework/abp/releases/16293679", - "https://github.com/abpframework/abp/releases/tag/0.15.0", - "https://api.github.com/repos/abpframework/abp/releases/16293679/assets", - "https://uploads.github.com/repos/abpframework/abp/releases/16293679/assets{?name,label}", - 16293679, - "", - "0.15.0", - "master", - "0.15.0", - "0.15.0 already release", - false, - false, - DateTimeOffset.Parse("2019-03-22T18:43:58Z"), - DateTimeOffset.Parse("2019-03-22T19:44:25Z"), - null, - "https://api.github.com/repos/abpframework/abp/tarball/0.15.0", - "https://api.github.com/repos/abpframework/abp/zipball/0.15.0", - null) + new GithubVersion() + { + Name = "0.15.0" + } }); repositoryManager.GetFileCommitsAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/AbpFeatureManagementApplicationContractsModule.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/AbpFeatureManagementApplicationContractsModule.cs index 8b71e3f85b..37b49f2d8a 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/AbpFeatureManagementApplicationContractsModule.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/AbpFeatureManagementApplicationContractsModule.cs @@ -1,4 +1,5 @@ using Volo.Abp.Application; +using Volo.Abp.Json.Newtonsoft; using Volo.Abp.Modularity; using Volo.Abp.VirtualFileSystem; @@ -16,6 +17,11 @@ namespace Volo.Abp.FeatureManagement { options.FileSets.AddEmbedded(); }); + + Configure(options => + { + options.Converters.Add(); + }); } } } diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/StringValueTypeJsonConverter.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/StringValueTypeJsonConverter.cs new file mode 100644 index 0000000000..0ee276cce1 --- /dev/null +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/StringValueTypeJsonConverter.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Validation.StringValues; + +namespace Volo.Abp.FeatureManagement +{ + public class StringValueTypeJsonConverter : JsonConverter, ITransientDependency + { + public override bool CanWrite => false; + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(IStringValueType); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + throw new NotImplementedException("This method should not be called to write (since CanWrite is false)."); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType != JsonToken.StartObject) + { + return null; + } + + var jsonObject = JObject.Load(reader); + + var stringValue = CreateStringValueTypeByName(jsonObject, jsonObject["name"].ToString()); + foreach (var o in serializer.Deserialize>( + new JsonTextReader(new StringReader(jsonObject["properties"].ToString())))) + { + stringValue[o.Key] = o.Value; + } + + stringValue.Validator = CreateValueValidatorByName(jsonObject["validator"], jsonObject["validator"]["name"].ToString()); + foreach (var o in serializer.Deserialize>( + new JsonTextReader(new StringReader(jsonObject["validator"]["properties"].ToString())))) + { + stringValue.Validator[o.Key] = o.Value; + } + + return stringValue; + } + + protected virtual IStringValueType CreateStringValueTypeByName(JObject jObject, string name) + { + if (name == "SelectionStringValueType") + { + var selectionStringValueType = new SelectionStringValueType(); + if (jObject["itemSource"].HasValues) + { + selectionStringValueType.ItemSource = new StaticSelectionStringValueItemSource(jObject["itemSource"]["items"] + .Select(item => new LocalizableSelectionStringValueItem() + { + Value = item["value"].ToString(), + DisplayText = new LocalizableStringInfo(item["displayText"]["resourceName"].ToString(), item["displayText"]["name"].ToString()) + }).ToArray()); + } + + return selectionStringValueType; + } + + return name switch + { + "FreeTextStringValueType" => new FreeTextStringValueType(), + "ToggleStringValueType" => new ToggleStringValueType(), + _ => throw new ArgumentException($"{nameof(IStringValueType)} named {name} was not found!") + }; + } + + protected virtual IValueValidator CreateValueValidatorByName(JToken jObject, string name) + { + return name switch + { + "NULL" => new AlwaysValidValueValidator(), + "BOOLEAN" => new BooleanValueValidator(), + "NUMERIC" => new NumericValueValidator(), + "STRING" => new StringValueValidator(), + _ => throw new ArgumentException($"{nameof(IValueValidator)} named {name} was not found!") + }; + } + } +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/UpdateFeatureDto.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/UpdateFeatureDto.cs index 26b351ee9e..7fc367b0d2 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/UpdateFeatureDto.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/UpdateFeatureDto.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Volo.Abp.FeatureManagement +namespace Volo.Abp.FeatureManagement { public class UpdateFeatureDto { diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application/Volo/Abp/FeatureManagement/FeatureAppService.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application/Volo/Abp/FeatureManagement/FeatureAppService.cs index 6c9db9bade..ab097a54a6 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application/Volo/Abp/FeatureManagement/FeatureAppService.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application/Volo/Abp/FeatureManagement/FeatureAppService.cs @@ -1,13 +1,9 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.AspNetCore.Authorization; -using Microsoft.Extensions.Localization; using Microsoft.Extensions.Options; -using Volo.Abp.Application.Services; using Volo.Abp.Features; namespace Volo.Abp.FeatureManagement diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo.Abp.FeatureManagement.Domain.Shared.csproj b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo.Abp.FeatureManagement.Domain.Shared.csproj index b43e2351af..865c652680 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo.Abp.FeatureManagement.Domain.Shared.csproj +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo.Abp.FeatureManagement.Domain.Shared.csproj @@ -13,11 +13,11 @@ - + - + - + diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureManagementStore.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureManagementStore.cs index 26388fdaf9..5b892a91bb 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureManagementStore.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureManagementStore.cs @@ -5,6 +5,7 @@ using Volo.Abp.Caching; using Volo.Abp.DependencyInjection; using Volo.Abp.Features; using Volo.Abp.Guids; +using Volo.Abp.Uow; namespace Volo.Abp.FeatureManagement { @@ -27,12 +28,14 @@ namespace Volo.Abp.FeatureManagement FeatureDefinitionManager = featureDefinitionManager; } + [UnitOfWork] public virtual async Task GetOrNullAsync(string name, string providerName, string providerKey) { var cacheItem = await GetCacheItemAsync(name, providerName, providerKey); return cacheItem.Value; } + [UnitOfWork] public virtual async Task SetAsync(string name, string value, string providerName, string providerKey) { var featureValue = await FeatureValueRepository.FindAsync(name, providerName, providerKey); @@ -46,21 +49,25 @@ namespace Volo.Abp.FeatureManagement featureValue.Value = value; await FeatureValueRepository.UpdateAsync(featureValue); } + + await Cache.SetAsync(CalculateCacheKey(name, providerName, providerKey), new FeatureValueCacheItem(featureValue?.Value), considerUow: true); } + [UnitOfWork] public virtual async Task DeleteAsync(string name, string providerName, string providerKey) { var featureValues = await FeatureValueRepository.FindAllAsync(name, providerName, providerKey); foreach (var featureValue in featureValues) { await FeatureValueRepository.DeleteAsync(featureValue); + await Cache.RemoveAsync(CalculateCacheKey(name, providerName, providerKey), considerUow: true); } } protected virtual async Task GetCacheItemAsync(string name, string providerName, string providerKey) { var cacheKey = CalculateCacheKey(name, providerName, providerKey); - var cacheItem = await Cache.GetAsync(cacheKey); + var cacheItem = await Cache.GetAsync(cacheKey, considerUow: true); if (cacheItem != null) { @@ -68,28 +75,28 @@ namespace Volo.Abp.FeatureManagement } cacheItem = new FeatureValueCacheItem(null); - + await SetCacheItemsAsync(providerName, providerKey, name, cacheItem); return cacheItem; } - + private async Task SetCacheItemsAsync( - string providerName, - string providerKey, - string currentName, + string providerName, + string providerKey, + string currentName, FeatureValueCacheItem currentCacheItem) { var featureDefinitions = FeatureDefinitionManager.GetAll(); var featuresDictionary = (await FeatureValueRepository.GetListAsync(providerName, providerKey)) .ToDictionary(s => s.Name, s => s.Value); - - var cacheItems = new List>(); - + + var cacheItems = new List>(); + foreach (var featureDefinition in featureDefinitions) { var featureValue = featuresDictionary.GetOrDefault(featureDefinition.Name); - + cacheItems.Add( new KeyValuePair( CalculateCacheKey(featureDefinition.Name, providerName, providerKey), @@ -103,7 +110,7 @@ namespace Volo.Abp.FeatureManagement } } - await Cache.SetManyAsync(cacheItems); + await Cache.SetManyAsync(cacheItems, considerUow: true); } protected virtual string CalculateCacheKey(string name, string providerName, string providerKey) diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Volo.Abp.FeatureManagement.Web.csproj b/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Volo.Abp.FeatureManagement.Web.csproj index 666c6dccae..735678e332 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Volo.Abp.FeatureManagement.Web.csproj +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Volo.Abp.FeatureManagement.Web.csproj @@ -20,9 +20,9 @@ - + - + diff --git a/modules/feature-management/test/Volo.Abp.FeatureManagement.Application.Tests/Volo/Abp/FeatureManagement/FeatureAppService_Tests.cs b/modules/feature-management/test/Volo.Abp.FeatureManagement.Application.Tests/Volo/Abp/FeatureManagement/FeatureAppService_Tests.cs index b86e6be013..b257f873cd 100644 --- a/modules/feature-management/test/Volo.Abp.FeatureManagement.Application.Tests/Volo/Abp/FeatureManagement/FeatureAppService_Tests.cs +++ b/modules/feature-management/test/Volo.Abp.FeatureManagement.Application.Tests/Volo/Abp/FeatureManagement/FeatureAppService_Tests.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using NSubstitute; diff --git a/modules/feature-management/test/Volo.Abp.FeatureManagement.Application.Tests/Volo/Abp/FeatureManagement/StringValueJsonConverter_Tests.cs b/modules/feature-management/test/Volo.Abp.FeatureManagement.Application.Tests/Volo/Abp/FeatureManagement/StringValueJsonConverter_Tests.cs new file mode 100644 index 0000000000..528e0de5e8 --- /dev/null +++ b/modules/feature-management/test/Volo.Abp.FeatureManagement.Application.Tests/Volo/Abp/FeatureManagement/StringValueJsonConverter_Tests.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json; +using Shouldly; +using Volo.Abp.Json; +using Volo.Abp.Validation.StringValues; +using Xunit; + +namespace Volo.Abp.FeatureManagement +{ + public class StringValueJsonConverter_Tests : FeatureManagementApplicationTestBase + { + private readonly IJsonSerializer _jsonSerializer; + + public StringValueJsonConverter_Tests() + { + _jsonSerializer = GetRequiredService(); + } + + [Fact] + public void Should_Serialize_And_Deserialize() + { + var featureListDto = new FeatureListDto + { + Features = new List + { + new FeatureDto + { + ValueType = new FreeTextStringValueType + { + Validator = new BooleanValueValidator() + } + }, + new FeatureDto + { + ValueType = new SelectionStringValueType + { + ItemSource = new StaticSelectionStringValueItemSource( + new LocalizableSelectionStringValueItem + { + Value = "TestValue", + DisplayText = new LocalizableStringInfo("TestResourceName", "TestName") + }), + Validator = new AlwaysValidValueValidator() + } + }, + new FeatureDto + { + ValueType = new ToggleStringValueType + { + Validator = new NumericValueValidator + { + MaxValue = 1000, + MinValue = 10 + } + } + } + } + }; + + var serialized = _jsonSerializer.Serialize(featureListDto, indented: true); + var featureListDto2 = _jsonSerializer.Deserialize(serialized); + + featureListDto2.Features[0].ValueType.ShouldBeOfType(); + featureListDto2.Features[0].ValueType.Validator.ShouldBeOfType(); + + featureListDto2.Features[1].ValueType.ShouldBeOfType(); + featureListDto2.Features[1].ValueType.Validator.ShouldBeOfType(); + featureListDto2.Features[1].ValueType.As().ItemSource.Items.ShouldBeOfType(); + featureListDto2.Features[1].ValueType.As().ItemSource.Items.ShouldContain(x => + x.Value == "TestValue" && x.DisplayText.ResourceName == "TestResourceName" && + x.DisplayText.Name == "TestName"); + + featureListDto2.Features[2].ValueType.ShouldBeOfType(); + featureListDto2.Features[2].ValueType.Validator.ShouldBeOfType(); + featureListDto2.Features[2].ValueType.Validator.As().MaxValue.ShouldBe(1000); + featureListDto2.Features[2].ValueType.Validator.As().MinValue.ShouldBe(10); + } + } +} diff --git a/modules/feature-management/test/Volo.Abp.FeatureManagement.EntityFrameworkCore.Tests/Volo.Abp.FeatureManagement.EntityFrameworkCore.Tests.csproj b/modules/feature-management/test/Volo.Abp.FeatureManagement.EntityFrameworkCore.Tests/Volo.Abp.FeatureManagement.EntityFrameworkCore.Tests.csproj index 4feee0b39d..dae0b0db40 100644 --- a/modules/feature-management/test/Volo.Abp.FeatureManagement.EntityFrameworkCore.Tests/Volo.Abp.FeatureManagement.EntityFrameworkCore.Tests.csproj +++ b/modules/feature-management/test/Volo.Abp.FeatureManagement.EntityFrameworkCore.Tests/Volo.Abp.FeatureManagement.EntityFrameworkCore.Tests.csproj @@ -15,7 +15,7 @@ - + diff --git a/modules/feature-management/test/Volo.Abp.FeatureManagement.TestBase/Volo/Abp/FeatureManagement/FeatureManagementStore_Tests.cs b/modules/feature-management/test/Volo.Abp.FeatureManagement.TestBase/Volo/Abp/FeatureManagement/FeatureManagementStore_Tests.cs index a6f040dde3..a5250f1804 100644 --- a/modules/feature-management/test/Volo.Abp.FeatureManagement.TestBase/Volo/Abp/FeatureManagement/FeatureManagementStore_Tests.cs +++ b/modules/feature-management/test/Volo.Abp.FeatureManagement.TestBase/Volo/Abp/FeatureManagement/FeatureManagementStore_Tests.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Shouldly; using Volo.Abp.Features; using Volo.Abp.Modularity; +using Volo.Abp.Uow; using Xunit; namespace Volo.Abp.FeatureManagement @@ -14,11 +15,13 @@ namespace Volo.Abp.FeatureManagement { private IFeatureManagementStore FeatureManagementStore { get; set; } private IFeatureValueRepository FeatureValueRepository { get; set; } + private IUnitOfWorkManager UnitOfWorkManager { get; set; } protected FeatureManagementStore_Tests() { FeatureManagementStore = GetRequiredService(); FeatureValueRepository = GetRequiredService(); + UnitOfWorkManager = GetRequiredService(); } [Fact] @@ -73,6 +76,30 @@ namespace Volo.Abp.FeatureManagement TestEditionIds.Regular.ToString())).Value.ShouldBe(false.ToString().ToUpperInvariant()); } + [Fact] + public async Task Set_In_UnitOfWork_Should_Be_Consistent() + { + using (UnitOfWorkManager.Begin()) + { + // Arrange + (await FeatureManagementStore.GetOrNullAsync(TestFeatureDefinitionProvider.SocialLogins, + EditionFeatureValueProvider.ProviderName, + TestEditionIds.Regular.ToString())).ShouldNotBeNull(); + + + // Act + await FeatureManagementStore.SetAsync(TestFeatureDefinitionProvider.SocialLogins, + false.ToString().ToUpperInvariant(), + EditionFeatureValueProvider.ProviderName, + TestEditionIds.Regular.ToString()); + + // Assert + (await FeatureManagementStore.GetOrNullAsync(TestFeatureDefinitionProvider.SocialLogins, + EditionFeatureValueProvider.ProviderName, + TestEditionIds.Regular.ToString())).ShouldBe(false.ToString().ToUpperInvariant()); + } + } + [Fact] public async Task DeleteAsync() { @@ -94,5 +121,29 @@ namespace Volo.Abp.FeatureManagement } + + [Fact] + public async Task Delete_In_UnitOfWork_Should_Be_Consistent() + { + using (var uow = UnitOfWorkManager.Begin()) + { + // Arrange + (await FeatureManagementStore.GetOrNullAsync(TestFeatureDefinitionProvider.SocialLogins, + EditionFeatureValueProvider.ProviderName, + TestEditionIds.Regular.ToString())).ShouldNotBeNull(); + + // Act + await FeatureManagementStore.DeleteAsync(TestFeatureDefinitionProvider.SocialLogins, + EditionFeatureValueProvider.ProviderName, + TestEditionIds.Regular.ToString()); + + await uow.SaveChangesAsync(); + + // Assert + (await FeatureManagementStore.GetOrNullAsync(TestFeatureDefinitionProvider.SocialLogins, + EditionFeatureValueProvider.ProviderName, + TestEditionIds.Regular.ToString())).ShouldBeNull(); + } + } } } diff --git a/modules/identity/Volo.Abp.Identity.sln b/modules/identity/Volo.Abp.Identity.sln index 0d993d8bf7..388cd0c555 100644 --- a/modules/identity/Volo.Abp.Identity.sln +++ b/modules/identity/Volo.Abp.Identity.sln @@ -39,6 +39,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Identity.AspNetCor EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.PermissionManagement.Domain.Identity", "src\Volo.Abp.PermissionManagement.Domain.Identity\Volo.Abp.PermissionManagement.Domain.Identity.csproj", "{736F91E7-8A70-441B-89DE-0E29A348E718}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Identity.AspNetCore.Tests", "test\Volo.Abp.Identity.AspNetCore.Tests\Volo.Abp.Identity.AspNetCore.Tests.csproj", "{89C094EB-D80A-4976-9C10-7CE3EBEEE877}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -109,6 +111,10 @@ Global {736F91E7-8A70-441B-89DE-0E29A348E718}.Debug|Any CPU.Build.0 = Debug|Any CPU {736F91E7-8A70-441B-89DE-0E29A348E718}.Release|Any CPU.ActiveCfg = Release|Any CPU {736F91E7-8A70-441B-89DE-0E29A348E718}.Release|Any CPU.Build.0 = Release|Any CPU + {89C094EB-D80A-4976-9C10-7CE3EBEEE877}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {89C094EB-D80A-4976-9C10-7CE3EBEEE877}.Debug|Any CPU.Build.0 = Debug|Any CPU + {89C094EB-D80A-4976-9C10-7CE3EBEEE877}.Release|Any CPU.ActiveCfg = Release|Any CPU + {89C094EB-D80A-4976-9C10-7CE3EBEEE877}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -130,6 +136,7 @@ Global {588B6E38-323B-4251-AC21-5F67C815A44E} = {9FACAF96-A681-4B36-A938-A37DCA0B7EC1} {D5EFC912-75A0-4856-9B8D-DFDD4CD66BAB} = {AADC5A0A-F100-4511-87DE-B74E55F5B69B} {736F91E7-8A70-441B-89DE-0E29A348E718} = {AADC5A0A-F100-4511-87DE-B74E55F5B69B} + {89C094EB-D80A-4976-9C10-7CE3EBEEE877} = {9FACAF96-A681-4B36-A938-A37DCA0B7EC1} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {05740D37-83CF-4041-9C2A-D89F1B3DB5A4} diff --git a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/AbpIdentityApplicationContractsModule.cs b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/AbpIdentityApplicationContractsModule.cs index 107acba9d1..43481ff30a 100644 --- a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/AbpIdentityApplicationContractsModule.cs +++ b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/AbpIdentityApplicationContractsModule.cs @@ -1,6 +1,8 @@ using Volo.Abp.Application; using Volo.Abp.Authorization; using Volo.Abp.Modularity; +using Volo.Abp.ObjectExtending; +using Volo.Abp.ObjectExtending.Modularity; using Volo.Abp.PermissionManagement; using Volo.Abp.Users; @@ -19,5 +21,26 @@ namespace Volo.Abp.Identity { } + + public override void PostConfigureServices(ServiceConfigurationContext context) + { + ModuleExtensionConfigurationHelper + .ApplyEntityConfigurationToApi( + IdentityModuleExtensionConsts.ModuleName, + IdentityModuleExtensionConsts.EntityNames.Role, + getApiTypes: new[] { typeof(IdentityRoleDto) }, + createApiTypes: new[] { typeof(IdentityRoleCreateDto) }, + updateApiTypes: new[] { typeof(IdentityRoleUpdateDto) } + ); + + ModuleExtensionConfigurationHelper + .ApplyEntityConfigurationToApi( + IdentityModuleExtensionConsts.ModuleName, + IdentityModuleExtensionConsts.EntityNames.User, + getApiTypes: new[] { typeof(IdentityUserDto) }, + createApiTypes: new[] { typeof(IdentityUserCreateDto) }, + updateApiTypes: new[] { typeof(IdentityUserUpdateDto) } + ); + } } } \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/ChangePasswordInput.cs b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/ChangePasswordInput.cs index a5bd73908a..04f7879f15 100644 --- a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/ChangePasswordInput.cs +++ b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/ChangePasswordInput.cs @@ -1,9 +1,18 @@ -namespace Volo.Abp.Identity +using System.ComponentModel.DataAnnotations; +using Volo.Abp.Auditing; +using Volo.Abp.Validation; + +namespace Volo.Abp.Identity { public class ChangePasswordInput { + [DisableAuditing] + [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPasswordLength))] public string CurrentPassword { get; set; } + [Required] + [DisableAuditing] + [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPasswordLength))] public string NewPassword { get; set; } } } diff --git a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityRoleCreateOrUpdateDtoBase.cs b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityRoleCreateOrUpdateDtoBase.cs index 491e82d766..6b68c43568 100644 --- a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityRoleCreateOrUpdateDtoBase.cs +++ b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityRoleCreateOrUpdateDtoBase.cs @@ -13,5 +13,10 @@ namespace Volo.Abp.Identity public bool IsDefault { get; set; } public bool IsPublic { get; set; } + + protected IdentityRoleCreateOrUpdateDtoBase() : base(false) + { + + } } } \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserCreateOrUpdateDtoBase.cs b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserCreateOrUpdateDtoBase.cs index e06ea8b987..41981b2ca8 100644 --- a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserCreateOrUpdateDtoBase.cs +++ b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserCreateOrUpdateDtoBase.cs @@ -32,5 +32,10 @@ namespace Volo.Abp.Identity [CanBeNull] public string[] RoleNames { get; set; } + + protected IdentityUserCreateOrUpdateDtoBase() : base(false) + { + + } } } \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/ProfileDto.cs b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/ProfileDto.cs index 4777c0ad23..98469700a9 100644 --- a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/ProfileDto.cs +++ b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/ProfileDto.cs @@ -13,5 +13,9 @@ namespace Volo.Abp.Identity public string Surname { get; set; } public string PhoneNumber { get; set; } + + public bool IsExternal { get; set; } + + public bool HasPassword { get; set; } } -} \ No newline at end of file +} diff --git a/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/AbpIdentityApplicationModuleAutoMapperProfile.cs b/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/AbpIdentityApplicationModuleAutoMapperProfile.cs index 01a68dc677..4c28e37f01 100644 --- a/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/AbpIdentityApplicationModuleAutoMapperProfile.cs +++ b/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/AbpIdentityApplicationModuleAutoMapperProfile.cs @@ -1,4 +1,5 @@ using AutoMapper; +using Volo.Abp.AutoMapper; namespace Volo.Abp.Identity { @@ -11,9 +12,11 @@ namespace Volo.Abp.Identity CreateMap() .MapExtraProperties(); - + CreateMap() + .ForMember(dest => dest.HasPassword, + op => op.MapFrom(src => src.PasswordHash != null)) .MapExtraProperties(); } } -} \ No newline at end of file +} diff --git a/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/ProfileAppService.cs b/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/ProfileAppService.cs index 45bcdf3e35..c08eb7ce32 100644 --- a/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/ProfileAppService.cs +++ b/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/ProfileAppService.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System.Linq; +using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Volo.Abp.Identity.Settings; @@ -20,9 +21,9 @@ namespace Volo.Abp.Identity public virtual async Task GetAsync() { - return ObjectMapper.Map( - await UserManager.GetByIdAsync(CurrentUser.GetId()) - ); + var currentUser = await UserManager.GetByIdAsync(CurrentUser.GetId()); + + return ObjectMapper.Map(currentUser); } public virtual async Task UpdateAsync(UpdateProfileDto input) @@ -56,6 +57,19 @@ namespace Volo.Abp.Identity public virtual async Task ChangePasswordAsync(ChangePasswordInput input) { var currentUser = await UserManager.GetByIdAsync(CurrentUser.GetId()); + + if (currentUser.IsExternal) + { + throw new BusinessException(code: IdentityErrorCodes.ExternalUserPasswordChange); + } + + if (currentUser.PasswordHash == null) + { + (await UserManager.AddPasswordAsync(currentUser, input.NewPassword)).CheckErrors(); + + return; + } + (await UserManager.ChangePasswordAsync(currentUser, input.CurrentPassword, input.NewPassword)).CheckErrors(); } } diff --git a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreModule.cs b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreModule.cs index c0e3031568..019ef0469e 100644 --- a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreModule.cs +++ b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreModule.cs @@ -13,8 +13,9 @@ namespace Volo.Abp.Identity.AspNetCore { PreConfigure(builder => { - builder.AddDefaultTokenProviders(); - builder.AddSignInManager(); + builder + .AddDefaultTokenProviders() + .AddSignInManager(); }); } diff --git a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreOptions.cs b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreOptions.cs index ad42a23f9a..590d691eaa 100644 --- a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreOptions.cs +++ b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreOptions.cs @@ -7,4 +7,4 @@ /// public bool ConfigureAuthentication { get; set; } = true; } -} \ No newline at end of file +} diff --git a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpSignInManager.cs b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpSignInManager.cs new file mode 100644 index 0000000000..8e4b435b48 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpSignInManager.cs @@ -0,0 +1,66 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Volo.Abp.Identity.AspNetCore +{ + public class AbpSignInManager : SignInManager + { + protected AbpIdentityOptions AbpOptions { get; } + + public AbpSignInManager( + IdentityUserManager userManager, + IHttpContextAccessor contextAccessor, + IUserClaimsPrincipalFactory claimsFactory, + IOptions optionsAccessor, + ILogger> logger, + IAuthenticationSchemeProvider schemes, + IUserConfirmation confirmation, + IOptions options + ) : base( + userManager, + contextAccessor, + claimsFactory, + optionsAccessor, + logger, + schemes, + confirmation) + { + AbpOptions = options.Value; + } + + public override async Task PasswordSignInAsync( + string userName, + string password, + bool isPersistent, + bool lockoutOnFailure) + { + foreach (var externalLoginProviderInfo in AbpOptions.ExternalLoginProviders.Values) + { + var externalLoginProvider = (IExternalLoginProvider) Context.RequestServices + .GetRequiredService(externalLoginProviderInfo.Type); + + if (await externalLoginProvider.TryAuthenticateAsync(userName, password)) + { + var user = await UserManager.FindByNameAsync(userName); + if (user == null) + { + user = await externalLoginProvider.CreateUserAsync(userName, externalLoginProviderInfo.Name); + } + else + { + await externalLoginProvider.UpdateUserAsync(user, externalLoginProviderInfo.Name); + } + + return await SignInOrTwoFactorAsync(user, isPersistent); + } + } + + return await base.PasswordSignInAsync(userName, password, isPersistent, lockoutOnFailure); + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/SignInResultExtensions.cs b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/SignInResultExtensions.cs new file mode 100644 index 0000000000..39e3ae92fc --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/SignInResultExtensions.cs @@ -0,0 +1,37 @@ +using Microsoft.AspNetCore.Identity; + +namespace Volo.Abp.Identity.AspNetCore +{ + public static class SignInResultExtensions + { + public static string ToIdentitySecurityLogAction(this SignInResult result) + { + if (result.Succeeded) + { + return IdentitySecurityLogActionConsts.LoginSucceeded; + } + + if (result.IsLockedOut) + { + return IdentitySecurityLogActionConsts.LoginLockedout; + } + + if (result.RequiresTwoFactor) + { + return IdentitySecurityLogActionConsts.LoginRequiresTwoFactor; + } + + if (result.IsNotAllowed) + { + return IdentitySecurityLogActionConsts.LoginNotAllowed; + } + + if (!result.Succeeded) + { + return IdentitySecurityLogActionConsts.LoginFailed; + } + + return IdentitySecurityLogActionConsts.LoginFailed; + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo.Abp.Identity.Domain.Shared.csproj b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo.Abp.Identity.Domain.Shared.csproj index 45b89b023e..06cef29fc3 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo.Abp.Identity.Domain.Shared.csproj +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo.Abp.Identity.Domain.Shared.csproj @@ -26,7 +26,7 @@ - + - + diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityErrorCodes.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityErrorCodes.cs index d77ee9f5a9..d5e57a6953 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityErrorCodes.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityErrorCodes.cs @@ -4,5 +4,6 @@ { public const string UserSelfDeletion = "Volo.Abp.Identity:010001"; public const string MaxAllowedOuMembership = "Volo.Abp.Identity:010002"; + public const string ExternalUserPasswordChange = "Volo.Abp.Identity:010003"; } -} \ No newline at end of file +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentitySecurityLogActionConsts.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentitySecurityLogActionConsts.cs new file mode 100644 index 0000000000..ea5276e673 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentitySecurityLogActionConsts.cs @@ -0,0 +1,33 @@ +namespace Volo.Abp.Identity +{ + public class IdentitySecurityLogActionConsts + { + public static string LoginSucceeded { get; set; } = "LoginSucceeded"; + + public static string LoginLockedout { get; set; } = "LoginLockedout"; + + public static string LoginNotAllowed { get; set; } = "LoginNotAllowed"; + + public static string LoginRequiresTwoFactor { get; set; } = "LoginRequiresTwoFactor"; + + public static string LoginFailed { get; set; } = "LoginFailed"; + + public static string LoginInvalidUserName { get; set; } = "LoginInvalidUserName"; + + public static string LoginInvalidUserNameOrPassword { get; set; } = "LoginInvalidUserNameOrPassword"; + + public static string Logout { get; set; } = "Logout"; + + public static string ChangeUserName { get; set; } = "ChangeUserName"; + + public static string ChangeEmail { get; set; } = "ChangeEmail"; + + public static string ChangePhoneNumber { get; set; } = "ChangePhoneNumber"; + + public static string ChangePassword { get; set; } = "ChangePassword"; + + public static string TwoFactorEnabled { get; set; } = "TwoFactorEnabled"; + + public static string TwoFactorDisabled { get; set; } = "TwoFactorDisabled"; + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentitySecurityLogConsts.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentitySecurityLogConsts.cs new file mode 100644 index 0000000000..43b2dd25fb --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentitySecurityLogConsts.cs @@ -0,0 +1,52 @@ +namespace Volo.Abp.Identity +{ + public class IdentitySecurityLogConsts + { + /// + /// Default value: 96 + /// + public static int MaxApplicationNameLength { get; set; } = 96; + + /// + /// Default value: 96 + /// + public static int MaxIdentityLength { get; set; } = 96; + + /// + /// Default value: 96 + /// + public static int MaxActionLength { get; set; } = 96; + + + /// + /// Default value: 256 + /// + public static int MaxUserNameLength { get; set; } = 256; + + /// + /// Default value: 64 + /// + public static int MaxTenantNameLength { get; set; } = 64; + + /// + /// Default value: 64 + /// + public static int MaxClientIpAddressLength { get; set; } = 64; + + /// + /// Default value: 64 + /// + public static int MaxClientIdLength { get; set; } = 64; + + /// + /// Default value: 64 + /// + public static int MaxCorrelationIdLength { get; set; } = 64; + + /// + /// Default value: 512 + /// + public static int MaxBrowserInfoLength { get; set; } = 512; + + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentitySecurityLogIdentityConsts.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentitySecurityLogIdentityConsts.cs new file mode 100644 index 0000000000..5616dfb4e6 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentitySecurityLogIdentityConsts.cs @@ -0,0 +1,11 @@ +namespace Volo.Abp.Identity +{ + public static class IdentitySecurityLogIdentityConsts + { + public static string Identity { get; set; } = "Identity"; + + public static string IdentityExternal { get; set; } = "IdentityExternal"; + + public static string IdentityTwoFactor { get; set; } = "IdentityTwoFactor"; + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityUserConsts.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityUserConsts.cs index c374cbc678..01c6e285a9 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityUserConsts.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityUserConsts.cs @@ -32,5 +32,10 @@ namespace Volo.Abp.Identity /// Default value: 256 /// public static int MaxSecurityStampLength { get; set; } = 256; + + /// + /// Default value: 16 + /// + public static int MaxLoginProviderLength { get; set; } = 16; } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/en.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/en.json index 8305ca9589..bcc2108fe5 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/en.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/en.json @@ -25,7 +25,7 @@ "DisplayName:Email": "Email address", "DisplayName:PhoneNumber": "Phone number", "DisplayName:TwoFactorEnabled": "Two factor verification", - "DisplayName:LockoutEnabled": "Locking account after failed login attempts", + "DisplayName:LockoutEnabled": "Lock account after failed login attempts", "NewRole": "New role", "RoleName": "Role name", "CreationTime": "Creation time", @@ -102,6 +102,7 @@ "Description:Abp.Identity.SignIn.RequireConfirmedPhoneNumber": "Whether a confirmed telephone number is required to sign in.", "Description:Abp.Identity.User.IsUserNameUpdateEnabled": "Whether the username can be updated by the user.", "Description:Abp.Identity.User.IsEmailUpdateEnabled": "Whether the email can be updated by the user.", - "Volo.Abp.Identity:010002": "Can not set more than {MaxUserMembershipCount} organization unit for a user!" + "Volo.Abp.Identity:010002": "Can not set more than {MaxUserMembershipCount} organization unit for a user!", + "Volo.Abp.Identity:010003": "Can not change password of an externally logged in user!" } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Microsoft/AspNetCore/Identity/AbpIdentityResultExtensions.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Microsoft/AspNetCore/Identity/AbpIdentityResultExtensions.cs index 58349e8b69..0ef3582af1 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Microsoft/AspNetCore/Identity/AbpIdentityResultExtensions.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Microsoft/AspNetCore/Identity/AbpIdentityResultExtensions.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Globalization; using Microsoft.Extensions.Localization; using Volo.Abp.Identity; +using Volo.Abp.Localization; using Volo.Abp.Text.Formatting; namespace Microsoft.AspNetCore.Identity @@ -48,12 +49,15 @@ namespace Microsoft.AspNetCore.Identity if (!localizedString.ResourceNotFound) { - var englishLocalizedString = localizer.WithCulture(CultureInfo.GetCultureInfo("en"))[key]; - if (!englishLocalizedString.ResourceNotFound) + using (CultureHelper.Use(CultureInfo.GetCultureInfo("en"))) { - if (FormattedStringValueExtracter.IsMatch(error.Description, englishLocalizedString.Value, out var values)) + var englishLocalizedString = localizer[key]; + if (!englishLocalizedString.ResourceNotFound) { - return string.Format(localizedString.Value, values.Cast().ToArray()); + if (FormattedStringValueExtracter.IsMatch(error.Description, englishLocalizedString.Value, out var values)) + { + return string.Format(localizedString.Value, values.Cast().ToArray()); + } } } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo.Abp.Identity.Domain.csproj b/modules/identity/src/Volo.Abp.Identity.Domain/Volo.Abp.Identity.Domain.csproj index 7712adc0a9..9cd721a4b8 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo.Abp.Identity.Domain.csproj +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo.Abp.Identity.Domain.csproj @@ -30,7 +30,7 @@ - + diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityOptions.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityOptions.cs new file mode 100644 index 0000000000..6cfaf75c0c --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityOptions.cs @@ -0,0 +1,12 @@ +namespace Volo.Abp.Identity +{ + public class AbpIdentityOptions + { + public ExternalLoginProviderDictionary ExternalLoginProviders { get; } + + public AbpIdentityOptions() + { + ExternalLoginProviders = new ExternalLoginProviderDictionary(); + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/ExternalLoginProviderBase.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/ExternalLoginProviderBase.cs new file mode 100644 index 0000000000..0898577e3d --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/ExternalLoginProviderBase.cs @@ -0,0 +1,156 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.Guids; +using Volo.Abp.MultiTenancy; + +namespace Volo.Abp.Identity +{ + public abstract class ExternalLoginProviderBase : IExternalLoginProvider + { + protected IGuidGenerator GuidGenerator { get; } + protected ICurrentTenant CurrentTenant { get; } + protected IdentityUserManager UserManager { get; } + protected IIdentityUserRepository IdentityUserRepository { get; } + + protected ExternalLoginProviderBase( + IGuidGenerator guidGenerator, + ICurrentTenant currentTenant, + IdentityUserManager userManager, + IIdentityUserRepository identityUserRepository) + { + GuidGenerator = guidGenerator; + CurrentTenant = currentTenant; + UserManager = userManager; + IdentityUserRepository = identityUserRepository; + } + + public abstract Task TryAuthenticateAsync(string userName, string plainPassword); + + public virtual async Task CreateUserAsync(string userName, string providerName) + { + var externalUser = await GetUserInfoAsync(userName); + NormalizeExternalLoginUserInfo(externalUser, userName); + + var user = new IdentityUser( + GuidGenerator.Create(), + userName, + externalUser.Email, + tenantId: CurrentTenant.Id + ); + + user.Name = externalUser.Name; + user.Surname = externalUser.Surname; + + user.IsExternal = true; + + user.SetEmailConfirmed(externalUser.EmailConfirmed ?? false); + user.SetPhoneNumber(externalUser.PhoneNumber, externalUser.PhoneNumberConfirmed ?? false); + + (await UserManager.CreateAsync(user)).CheckErrors(); + + if (externalUser.TwoFactorEnabled != null) + { + (await UserManager.SetTwoFactorEnabledAsync(user, externalUser.TwoFactorEnabled.Value)).CheckErrors(); + } + + (await UserManager.AddDefaultRolesAsync(user)).CheckErrors(); + (await UserManager.AddLoginAsync( + user, + new UserLoginInfo( + providerName, + externalUser.ProviderKey , + providerName + ) + ) + ).CheckErrors(); + + return user; + } + + public virtual async Task UpdateUserAsync(IdentityUser user, string providerName) + { + var externalUser = await GetUserInfoAsync(user); + NormalizeExternalLoginUserInfo(externalUser, user.UserName); + + if (!externalUser.Name.IsNullOrWhiteSpace()) + { + user.Name = externalUser.Name; + } + + if (!externalUser.Surname.IsNullOrWhiteSpace()) + { + user.Surname = externalUser.Surname; + } + + if (user.PhoneNumber != externalUser.PhoneNumber) + { + if (!externalUser.PhoneNumber.IsNullOrWhiteSpace()) + { + await UserManager.SetPhoneNumberAsync(user, externalUser.PhoneNumber); + user.SetPhoneNumberConfirmed(externalUser.PhoneNumberConfirmed == true); + } + } + else + { + if (!user.PhoneNumber.IsNullOrWhiteSpace() && + user.PhoneNumberConfirmed == false && + externalUser.PhoneNumberConfirmed == true) + { + user.SetPhoneNumberConfirmed(true); + } + } + + if (!string.Equals(user.Email, externalUser.Email, StringComparison.OrdinalIgnoreCase)) + { + (await UserManager.SetEmailAsync(user, externalUser.Email)).CheckErrors(); + user.SetEmailConfirmed(externalUser.EmailConfirmed ?? false); + } + + if (externalUser.TwoFactorEnabled != null) + { + (await UserManager.SetTwoFactorEnabledAsync(user, externalUser.TwoFactorEnabled.Value)).CheckErrors(); + } + + await IdentityUserRepository.EnsureCollectionLoadedAsync(user, u => u.Logins); + + var userLogin = user.Logins.FirstOrDefault(l => l.LoginProvider == providerName); + if (userLogin != null) + { + if (userLogin.ProviderKey != externalUser.ProviderKey) + { + (await UserManager.RemoveLoginAsync(user, providerName, userLogin.ProviderKey)).CheckErrors(); + (await UserManager.AddLoginAsync(user, new UserLoginInfo(providerName, externalUser.ProviderKey, providerName))).CheckErrors(); + } + } + else + { + (await UserManager.AddLoginAsync(user, new UserLoginInfo(providerName, externalUser.ProviderKey, providerName))).CheckErrors(); + } + + user.IsExternal = true; + + (await UserManager.UpdateAsync(user)).CheckErrors(); + } + + protected abstract Task GetUserInfoAsync(string userName); + + protected virtual Task GetUserInfoAsync(IdentityUser user) + { + return GetUserInfoAsync(user.UserName); + } + + private static void NormalizeExternalLoginUserInfo( + ExternalLoginUserInfo externalUser, + string userName + ) + { + if (externalUser.ProviderKey.IsNullOrWhiteSpace()) + { + externalUser.ProviderKey = userName; + } + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/ExternalLoginProviderDictionary.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/ExternalLoginProviderDictionary.cs new file mode 100644 index 0000000000..822971efb0 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/ExternalLoginProviderDictionary.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using JetBrains.Annotations; + +namespace Volo.Abp.Identity +{ + public class ExternalLoginProviderDictionary : Dictionary + { + /// + /// Adds or replaces a provider. + /// + public void Add([NotNull] string name) + where TProvider : IExternalLoginProvider + { + this[name] = new ExternalLoginProviderInfo(name, typeof(TProvider)); + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/ExternalLoginProviderInfo.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/ExternalLoginProviderInfo.cs new file mode 100644 index 0000000000..4d1989d5dc --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/ExternalLoginProviderInfo.cs @@ -0,0 +1,25 @@ +using System; +using JetBrains.Annotations; + +namespace Volo.Abp.Identity +{ + public class ExternalLoginProviderInfo + { + public string Name { get; } + + public Type Type + { + get => _type; + set => _type = Check.NotNull(value, nameof(value)); + } + private Type _type; + + public ExternalLoginProviderInfo( + [NotNull] string name, + [NotNull] Type type) + { + Name = Check.NotNullOrWhiteSpace(name, nameof(name)); + Type = Check.AssignableTo(type, nameof(type)); + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/ExternalLoginUserInfo.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/ExternalLoginUserInfo.cs new file mode 100644 index 0000000000..65e4a2c776 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/ExternalLoginUserInfo.cs @@ -0,0 +1,36 @@ +using JetBrains.Annotations; + +namespace Volo.Abp.Identity +{ + public class ExternalLoginUserInfo + { + [CanBeNull] + public string Name { get; set; } + + [CanBeNull] + public string Surname { get; set; } + + [CanBeNull] + public string PhoneNumber { get; set; } + + [NotNull] + public string Email { get; private set; } + + [CanBeNull] + public bool? PhoneNumberConfirmed { get; set; } + + [CanBeNull] + public bool? EmailConfirmed { get; set; } + + [CanBeNull] + public bool? TwoFactorEnabled { get; set; } + + [CanBeNull] + public string ProviderKey { get; set; } + + public ExternalLoginUserInfo([NotNull] string email) + { + Email = Check.NotNullOrWhiteSpace(email, nameof(email)); + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IExternalLoginProvider.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IExternalLoginProvider.cs new file mode 100644 index 0000000000..79e730d901 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IExternalLoginProvider.cs @@ -0,0 +1,32 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.Identity +{ + public interface IExternalLoginProvider + { + /// + /// Used to try authenticate a user by this source. + /// + /// User name or email address + /// Plain password of the user + /// True, indicates that this used has authenticated by this source + Task TryAuthenticateAsync(string userName, string plainPassword); + + /// + /// This method is called when a user is authenticated by this source but the user does not exists yet. + /// So, the source should create the user and fill the properties. + /// + /// User name + /// The name of this provider + /// Newly created user + Task CreateUserAsync(string userName, string providerName); + + /// + /// This method is called after an existing user is authenticated by this source. + /// It can be used to update some properties of the user by the source. + /// + /// The name of this provider + /// The user that can be updated + Task UpdateUserAsync(IdentityUser user, string providerName); + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIdentitySecurityLogRepository.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIdentitySecurityLogRepository.cs new file mode 100644 index 0000000000..f68f289b4f --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIdentitySecurityLogRepository.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.Domain.Repositories; + +namespace Volo.Abp.Identity +{ + public interface IIdentitySecurityLogRepository : IBasicRepository + { + Task> GetListAsync( + string sorting = null, + int maxResultCount = 50, + int skipCount = 0, + DateTime? startTime = null, + DateTime? endTime = null, + string applicationName = null, + string identity = null, + string action = null, + Guid? userId = null, + string userName = null, + string clientId = null, + string correlationId = null, + bool includeDetails = false, + CancellationToken cancellationToken = default); + + Task GetCountAsync( + DateTime? startTime = null, + DateTime? endTime = null, + string applicationName = null, + string identity = null, + string action = null, + Guid? userId = null, + string userName = null, + string clientId = null, + string correlationId = null, + CancellationToken cancellationToken = default); + + Task GetByUserIdAsync( + Guid id, + Guid userId, + bool includeDetails = false, + CancellationToken cancellationToken = default); + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentitySecurityLog.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentitySecurityLog.cs new file mode 100644 index 0000000000..b720f667f0 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentitySecurityLog.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using Volo.Abp.Domain.Entities; +using Volo.Abp.Guids; +using Volo.Abp.MultiTenancy; +using Volo.Abp.SecurityLog; + +namespace Volo.Abp.Identity +{ + public class IdentitySecurityLog : AggregateRoot, IMultiTenant + { + public Guid? TenantId { get; protected set; } + + public string ApplicationName { get; protected set; } + + public string Identity { get; protected set; } + + public string Action { get; protected set; } + + public Guid? UserId { get; protected set; } + + public string UserName { get; protected set; } + + public string TenantName { get; protected set; } + + public string ClientId { get; protected set; } + + public string CorrelationId { get; protected set; } + + public string ClientIpAddress { get; protected set; } + + public string BrowserInfo { get; protected set; } + + public DateTime CreationTime { get; protected set; } + + protected IdentitySecurityLog() + { + ExtraProperties = new Dictionary(); + } + + public IdentitySecurityLog(IGuidGenerator guidGenerator, SecurityLogInfo securityLogInfo) + { + Id = guidGenerator.Create(); + TenantId = securityLogInfo.TenantId; + TenantName = securityLogInfo.TenantName.Truncate(IdentitySecurityLogConsts.MaxTenantNameLength); + + ApplicationName = securityLogInfo.ApplicationName.Truncate(IdentitySecurityLogConsts.MaxApplicationNameLength); + Identity = securityLogInfo.Identity.Truncate(IdentitySecurityLogConsts.MaxIdentityLength); + Action = securityLogInfo.Action.Truncate(IdentitySecurityLogConsts.MaxActionLength); + + UserId = securityLogInfo.UserId; + UserName = securityLogInfo.UserName.Truncate(IdentitySecurityLogConsts.MaxUserNameLength); + + CreationTime = securityLogInfo.CreationTime; + + ClientIpAddress = securityLogInfo.ClientIpAddress.Truncate(IdentitySecurityLogConsts.MaxClientIpAddressLength); + ClientId = securityLogInfo.ClientId.Truncate(IdentitySecurityLogConsts.MaxClientIdLength); + CorrelationId = securityLogInfo.CorrelationId.Truncate(IdentitySecurityLogConsts.MaxCorrelationIdLength); + BrowserInfo = securityLogInfo.BrowserInfo.Truncate(IdentitySecurityLogConsts.MaxBrowserInfoLength); + + ExtraProperties = securityLogInfo.ExtraProperties; + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentitySecurityLogContext.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentitySecurityLogContext.cs new file mode 100644 index 0000000000..740688ff84 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentitySecurityLogContext.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; + +namespace Volo.Abp.Identity +{ + public class IdentitySecurityLogContext + { + public string Identity { get; set; } + + public string Action { get; set; } + + public string UserName { get; set; } + + public string ClientId { get; set; } + + public Dictionary ExtraProperties { get; } + + public IdentitySecurityLogContext() + { + ExtraProperties = new Dictionary(); + } + + public virtual IdentitySecurityLogContext WithProperty(string key, object value) + { + ExtraProperties[key] = value; + return this; + } + + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentitySecurityLogManager.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentitySecurityLogManager.cs new file mode 100644 index 0000000000..bdad21df13 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentitySecurityLogManager.cs @@ -0,0 +1,84 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Security.Claims; +using Volo.Abp.SecurityLog; +using Volo.Abp.Users; + +namespace Volo.Abp.Identity +{ + public class IdentitySecurityLogManager : ITransientDependency + { + protected ISecurityLogManager SecurityLogManager { get; } + protected IdentityUserManager UserManager { get; } + protected ICurrentPrincipalAccessor CurrentPrincipalAccessor { get; } + protected IUserClaimsPrincipalFactory UserClaimsPrincipalFactory { get; } + protected ICurrentUser CurrentUser { get; } + + public IdentitySecurityLogManager( + ISecurityLogManager securityLogManager, + IdentityUserManager userManager, + ICurrentPrincipalAccessor currentPrincipalAccessor, + IUserClaimsPrincipalFactory userClaimsPrincipalFactory, + ICurrentUser currentUser) + { + SecurityLogManager = securityLogManager; + UserManager = userManager; + CurrentPrincipalAccessor = currentPrincipalAccessor; + UserClaimsPrincipalFactory = userClaimsPrincipalFactory; + CurrentUser = currentUser; + } + + public async Task SaveAsync(IdentitySecurityLogContext context) + { + Action securityLogAction = securityLog => + { + securityLog.Identity = context.Identity; + securityLog.Action = context.Action; + + if (!context.UserName.IsNullOrWhiteSpace()) + { + securityLog.UserName = context.UserName; + } + + if (!context.ClientId.IsNullOrWhiteSpace()) + { + securityLog.ClientId = context.ClientId; + } + + foreach (var property in context.ExtraProperties) + { + securityLog.ExtraProperties[property.Key] = property.Value; + } + }; + + if (CurrentUser.IsAuthenticated) + { + await SecurityLogManager.SaveAsync(securityLogAction); + } + else + { + if (context.UserName.IsNullOrWhiteSpace()) + { + await SecurityLogManager.SaveAsync(securityLogAction); + } + else + { + var user = await UserManager.FindByNameAsync(context.UserName); + if (user != null) + { + using (CurrentPrincipalAccessor.Change(await UserClaimsPrincipalFactory.CreateAsync(user))) + { + await SecurityLogManager.SaveAsync(securityLogAction); + } + } + else + { + await SecurityLogManager.SaveAsync(securityLogAction); + } + } + } + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentitySecurityLogStore.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentitySecurityLogStore.cs new file mode 100644 index 0000000000..17e3c20794 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentitySecurityLogStore.cs @@ -0,0 +1,49 @@ +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Guids; +using Volo.Abp.SecurityLog; +using Volo.Abp.Uow; + +namespace Volo.Abp.Identity +{ + [Dependency(ReplaceServices = true)] + public class IdentitySecurityLogStore : ISecurityLogStore, ITransientDependency + { + public ILogger Logger { get; set; } + + protected AbpSecurityLogOptions SecurityLogOptions { get; } + protected IIdentitySecurityLogRepository IdentitySecurityLogRepository { get; } + protected IGuidGenerator GuidGenerator { get; } + protected IUnitOfWorkManager UnitOfWorkManager { get; } + + public IdentitySecurityLogStore( + ILogger logger, + IOptions securityLogOptions, + IIdentitySecurityLogRepository identitySecurityLogRepository, + IGuidGenerator guidGenerator, + IUnitOfWorkManager unitOfWorkManager) + { + Logger = logger; + SecurityLogOptions = securityLogOptions.Value; + IdentitySecurityLogRepository = identitySecurityLogRepository; + GuidGenerator = guidGenerator; + UnitOfWorkManager = unitOfWorkManager; + } + + public async Task SaveAsync(SecurityLogInfo securityLogInfo) + { + if (!SecurityLogOptions.IsEnabled) + { + return; + } + + using (var uow = UnitOfWorkManager.Begin(requiresNew: true)) + { + await IdentitySecurityLogRepository.InsertAsync(new IdentitySecurityLog(GuidGenerator, securityLogInfo)); + await uow.CompleteAsync(); + } + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUser.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUser.cs index ea1f5ccf72..287328573d 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUser.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUser.cs @@ -30,11 +30,13 @@ namespace Volo.Abp.Identity /// /// Gets or sets the Name for the user. /// + [CanBeNull] public virtual string Name { get; set; } /// /// Gets or sets the Surname for the user. /// + [CanBeNull] public virtual string Surname { get; set; } /// @@ -66,9 +68,12 @@ namespace Volo.Abp.Identity [DisableAuditing] public virtual string SecurityStamp { get; protected internal set; } + public virtual bool IsExternal { get; set; } + /// /// Gets or sets a telephone number for the user. /// + [CanBeNull] public virtual string PhoneNumber { get; protected internal set; } /// @@ -133,7 +138,11 @@ namespace Volo.Abp.Identity { } - public IdentityUser(Guid id, [NotNull] string userName, [NotNull] string email, Guid? tenantId = null) + public IdentityUser( + Guid id, + [NotNull] string userName, + [NotNull] string email, + Guid? tenantId = null) { Check.NotNull(userName, nameof(userName)); Check.NotNull(email, nameof(email)); @@ -254,7 +263,8 @@ namespace Volo.Abp.Identity Check.NotNull(loginProvider, nameof(loginProvider)); Check.NotNull(providerKey, nameof(providerKey)); - Logins.RemoveAll(userLogin => userLogin.LoginProvider == loginProvider && userLogin.ProviderKey == providerKey); + Logins.RemoveAll(userLogin => + userLogin.LoginProvider == loginProvider && userLogin.ProviderKey == providerKey); } [CanBeNull] @@ -316,9 +326,37 @@ namespace Volo.Abp.Identity ); } + /// + /// Use for regular email confirmation. + /// Using this skips the confirmation process and directly sets the . + /// + public virtual void SetEmailConfirmed(bool confirmed) + { + EmailConfirmed = confirmed; + } + + public virtual void SetPhoneNumberConfirmed(bool confirmed) + { + PhoneNumberConfirmed = confirmed; + } + public override string ToString() { return $"{base.ToString()}, UserName = {UserName}"; } + + /// + /// Normally use to change the phone number + /// in the application code. + /// This method is to directly set it with a confirmation information. + /// + /// + /// + /// + public void SetPhoneNumber(string phoneNumber, bool confirmed) + { + PhoneNumber = phoneNumber; + PhoneNumberConfirmed = !phoneNumber.IsNullOrWhiteSpace() && confirmed; + } } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserStore.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserStore.cs index 81269adf5a..17a0fbd39d 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserStore.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserStore.cs @@ -291,7 +291,7 @@ namespace Volo.Abp.Identity /// /// The user to retrieve the password hash for. /// The used to propagate notifications that the operation should be canceled. - /// A containing a flag indicating if the specified user has a password. If the + /// A containing a flag indicating if the specified user has a password. If the /// user has a password the returned value with be true, otherwise it will be false. public virtual Task HasPasswordAsync([NotNull] IdentityUser user, CancellationToken cancellationToken = default) { @@ -326,7 +326,7 @@ namespace Volo.Abp.Identity { throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "Role {0} does not exist!", normalizedRoleName)); } - + await UserRepository.EnsureCollectionLoadedAsync(user, u => u.Roles, cancellationToken); user.AddRole(role.Id); @@ -353,7 +353,7 @@ namespace Volo.Abp.Identity } await UserRepository.EnsureCollectionLoadedAsync(user, u => u.Roles, cancellationToken); - + user.RemoveRole(role.Id); } @@ -384,15 +384,15 @@ namespace Volo.Abp.Identity /// The user whose role membership should be checked. /// The role to check membership of /// The used to propagate notifications that the operation should be canceled. - /// A containing a flag indicating if the specified user is a member of the given group. If the + /// A containing a flag indicating if the specified user is a member of the given group. If the /// user is a member of the group the returned value with be true, otherwise it will be false. public virtual async Task IsInRoleAsync( - [NotNull] IdentityUser user, + [NotNull] IdentityUser user, [NotNull] string normalizedRoleName, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); - + Check.NotNull(user, nameof(user)); Check.NotNullOrWhiteSpace(normalizedRoleName, nameof(normalizedRoleName)); @@ -589,7 +589,7 @@ namespace Volo.Abp.Identity Check.NotNull(user, nameof(user)); - user.EmailConfirmed = confirmed; + user.SetEmailConfirmed(confirmed); return Task.CompletedTask; } @@ -864,7 +864,7 @@ namespace Volo.Abp.Identity Check.NotNull(user, nameof(user)); - user.PhoneNumberConfirmed = confirmed; + user.SetPhoneNumberConfirmed(confirmed); return Task.CompletedTask; } @@ -928,7 +928,7 @@ namespace Volo.Abp.Identity /// The user whose two factor authentication enabled status should be set. /// The used to propagate notifications that the operation should be canceled. /// - /// The that represents the asynchronous operation, containing a flag indicating whether the specified + /// The that represents the asynchronous operation, containing a flag indicating whether the specified /// has two factor authentication enabled or not. /// public virtual Task GetTwoFactorEnabledAsync([NotNull] IdentityUser user, CancellationToken cancellationToken = default) @@ -946,7 +946,7 @@ namespace Volo.Abp.Identity /// The claim whose users should be retrieved. /// The used to propagate notifications that the operation should be canceled. /// - /// The contains a list of users, if any, that contain the specified claim. + /// The contains a list of users, if any, that contain the specified claim. /// public virtual async Task> GetUsersForClaimAsync([NotNull] Claim claim, CancellationToken cancellationToken = default) { @@ -963,7 +963,7 @@ namespace Volo.Abp.Identity /// The role whose users should be retrieved. /// The used to propagate notifications that the operation should be canceled. /// - /// The contains a list of users, if any, that are in the specified role. + /// The contains a list of users, if any, that are in the specified role. /// public virtual async Task> GetUsersInRoleAsync([NotNull] string normalizedRoleName, CancellationToken cancellationToken = default) { diff --git a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EFCoreIdentitySecurityLogRepository.cs b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EFCoreIdentitySecurityLogRepository.cs new file mode 100644 index 0000000000..0b1dbe9e3b --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EFCoreIdentitySecurityLogRepository.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Dynamic.Core; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Volo.Abp.Domain.Repositories.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore; + +namespace Volo.Abp.Identity.EntityFrameworkCore +{ + public class EFCoreIdentitySecurityLogRepository : EfCoreRepository, IIdentitySecurityLogRepository + { + public EFCoreIdentitySecurityLogRepository(IDbContextProvider dbContextProvider) + : base(dbContextProvider) + { + + } + + public async Task> GetListAsync( + string sorting = null, + int maxResultCount = 50, + int skipCount = 0, + DateTime? startTime = null, + DateTime? endTime = null, + string applicationName = null, + string identity = null, + string action = null, + Guid? userId = null, + string userName = null, + string clientId = null, + string correlationId = null, + bool includeDetails = false, + CancellationToken cancellationToken = default) + { + var query = GetListQuery( + startTime, + endTime, + applicationName, + identity, + action, + userId, + userName, + clientId, + correlationId + ); + + return await query.OrderBy(sorting ?? nameof(IdentitySecurityLog.CreationTime) + " desc") + .PageBy(skipCount, maxResultCount) + .ToListAsync(GetCancellationToken(cancellationToken)); + } + + public async Task GetCountAsync( + DateTime? startTime = null, + DateTime? endTime = null, + string applicationName = null, + string identity = null, + string action = null, + Guid? userId = null, + string userName = null, + string clientId = null, + string correlationId = null, + CancellationToken cancellationToken = default) + { + var query = GetListQuery( + startTime, + endTime, + applicationName, + identity, + action, + userId, + userName, + clientId, + correlationId + ); + + return await query.LongCountAsync(GetCancellationToken(cancellationToken)); + } + + public async Task GetByUserIdAsync(Guid id, Guid userId, bool includeDetails = false, CancellationToken cancellationToken = default) + { + return await DbSet.FirstOrDefaultAsync(x => x.Id == id && x.UserId == userId, GetCancellationToken(cancellationToken)); + } + + protected virtual IQueryable GetListQuery( + DateTime? startTime = null, + DateTime? endTime = null, + string applicationName = null, + string identity = null, + string action = null, + Guid? userId = null, + string userName = null, + string clientId = null, + string correlationId = null) + { + return DbSet.AsNoTracking() + .WhereIf(startTime.HasValue, securityLog => securityLog.CreationTime >= startTime.Value) + .WhereIf(endTime.HasValue, securityLog => securityLog.CreationTime < endTime.Value.AddDays(1).Date) + .WhereIf(!applicationName.IsNullOrWhiteSpace(), securityLog => securityLog.ApplicationName == applicationName) + .WhereIf(!identity.IsNullOrWhiteSpace(), securityLog => securityLog.Identity == identity) + .WhereIf(!action.IsNullOrWhiteSpace(), securityLog => securityLog.Action == action) + .WhereIf(userId.HasValue, securityLog => securityLog.UserId == userId) + .WhereIf(!userName.IsNullOrWhiteSpace(), securityLog => securityLog.UserName == userName) + .WhereIf(!clientId.IsNullOrWhiteSpace(), securityLog => securityLog.ClientId == clientId) + .WhereIf(!correlationId.IsNullOrWhiteSpace(), securityLog => securityLog.CorrelationId == correlationId); + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IIdentityDbContext.cs b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IIdentityDbContext.cs index f27c9b534a..48da4daf87 100644 --- a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IIdentityDbContext.cs +++ b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IIdentityDbContext.cs @@ -1,6 +1,7 @@ using Microsoft.EntityFrameworkCore; using Volo.Abp.Data; using Volo.Abp.EntityFrameworkCore; +using Volo.Abp.SecurityLog; namespace Volo.Abp.Identity.EntityFrameworkCore { @@ -14,5 +15,7 @@ namespace Volo.Abp.Identity.EntityFrameworkCore DbSet ClaimTypes { get; set; } DbSet OrganizationUnits { get; set; } + + DbSet IdentitySecurityLogs { get; set; } } } diff --git a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContext.cs b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContext.cs index 811280de7b..16679429b8 100644 --- a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContext.cs +++ b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContext.cs @@ -18,6 +18,8 @@ namespace Volo.Abp.Identity.EntityFrameworkCore public DbSet OrganizationUnits { get; set; } + public DbSet IdentitySecurityLogs { get; set; } + public IdentityDbContext(DbContextOptions options) : base(options) { @@ -31,4 +33,4 @@ namespace Volo.Abp.Identity.EntityFrameworkCore builder.ConfigureIdentity(); } } -} \ No newline at end of file +} diff --git a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContextModelBuilderExtensions.cs b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContextModelBuilderExtensions.cs index 000a2a8f00..8efe82c5ce 100644 --- a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContextModelBuilderExtensions.cs +++ b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContextModelBuilderExtensions.cs @@ -43,6 +43,9 @@ namespace Volo.Abp.Identity.EntityFrameworkCore b.Property(u => u.LockoutEnabled).HasDefaultValue(false) .HasColumnName(nameof(IdentityUser.LockoutEnabled)); + b.Property(u => u.IsExternal).IsRequired().HasDefaultValue(false) + .HasColumnName(nameof(IdentityUser.IsExternal)); + b.Property(u => u.AccessFailedCount) .If(!builder.IsUsingOracle(), p => p.HasDefaultValue(0)) .HasColumnName(nameof(IdentityUser.AccessFailedCount)); @@ -204,6 +207,32 @@ namespace Volo.Abp.Identity.EntityFrameworkCore b.HasIndex(ou => new {ou.UserId, ou.OrganizationUnitId}); }); + + builder.Entity(b => + { + b.ToTable(options.TablePrefix + "SecurityLogs", options.Schema); + + b.ConfigureByConvention(); + + b.Property(x => x.TenantName).HasMaxLength(IdentitySecurityLogConsts.MaxTenantNameLength); + + b.Property(x => x.ApplicationName).HasMaxLength(IdentitySecurityLogConsts.MaxApplicationNameLength); + b.Property(x => x.Identity).HasMaxLength(IdentitySecurityLogConsts.MaxIdentityLength); + b.Property(x => x.Action).HasMaxLength(IdentitySecurityLogConsts.MaxActionLength); + + b.Property(x => x.UserName).HasMaxLength(IdentitySecurityLogConsts.MaxUserNameLength); + + b.Property(x => x.ClientIpAddress).HasMaxLength(IdentitySecurityLogConsts.MaxClientIpAddressLength); + b.Property(x => x.ClientId).HasMaxLength(IdentitySecurityLogConsts.MaxClientIdLength); + b.Property(x => x.CorrelationId).HasMaxLength(IdentitySecurityLogConsts.MaxCorrelationIdLength); + b.Property(x => x.BrowserInfo).HasMaxLength(IdentitySecurityLogConsts.MaxBrowserInfoLength); + + b.HasIndex(x => new { x.TenantId, x.ApplicationName }); + b.HasIndex(x => new { x.TenantId, x.Identity }); + b.HasIndex(x => new { x.TenantId, x.Action }); + b.HasIndex(x => new { x.TenantId, x.UserId }); + }); + } } -} \ No newline at end of file +} diff --git a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbContext.cs b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbContext.cs index 3240ce1454..19c3316b88 100644 --- a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbContext.cs +++ b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbContext.cs @@ -15,6 +15,8 @@ namespace Volo.Abp.Identity.MongoDB public IMongoCollection OrganizationUnits => Collection(); + public IMongoCollection IdentitySecurityLogs => Collection(); + protected override void CreateModel(IMongoModelBuilder modelBuilder) { base.CreateModel(modelBuilder); @@ -22,4 +24,4 @@ namespace Volo.Abp.Identity.MongoDB modelBuilder.ConfigureIdentity(); } } -} \ No newline at end of file +} diff --git a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbContextExtensions.cs b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbContextExtensions.cs index bc303e0eb5..89aeddeb92 100644 --- a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbContextExtensions.cs +++ b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbContextExtensions.cs @@ -36,6 +36,11 @@ namespace Volo.Abp.Identity.MongoDB { b.CollectionName = options.CollectionPrefix + "OrganizationUnits"; }); + + builder.Entity(b => + { + b.CollectionName = options.CollectionPrefix + "SecurityLogs"; + }); } } -} \ No newline at end of file +} diff --git a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbModule.cs b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbModule.cs index 268c718b12..3ceab94e23 100644 --- a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbModule.cs +++ b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbModule.cs @@ -18,6 +18,7 @@ namespace Volo.Abp.Identity.MongoDB options.AddRepository(); options.AddRepository(); options.AddRepository(); + options.AddRepository(); }); } } diff --git a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/IAbpIdentityMongoDbContext.cs b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/IAbpIdentityMongoDbContext.cs index c903c5d96d..54819ffa19 100644 --- a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/IAbpIdentityMongoDbContext.cs +++ b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/IAbpIdentityMongoDbContext.cs @@ -14,5 +14,7 @@ namespace Volo.Abp.Identity.MongoDB IMongoCollection ClaimTypes { get; } IMongoCollection OrganizationUnits { get; } + + IMongoCollection IdentitySecurityLogs { get; } } -} \ No newline at end of file +} diff --git a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentitySecurityLogRepository.cs b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentitySecurityLogRepository.cs new file mode 100644 index 0000000000..cbf20e0393 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentitySecurityLogRepository.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Dynamic.Core; +using System.Threading; +using System.Threading.Tasks; +using MongoDB.Driver; +using MongoDB.Driver.Linq; +using Volo.Abp.Domain.Repositories.MongoDB; +using Volo.Abp.MongoDB; + +namespace Volo.Abp.Identity.MongoDB +{ + public class MongoIdentitySecurityLogRepository : + MongoDbRepository, IIdentitySecurityLogRepository + { + public MongoIdentitySecurityLogRepository(IMongoDbContextProvider dbContextProvider) + : base(dbContextProvider) + { + } + + public async Task> GetListAsync( + string sorting = null, + int maxResultCount = 50, + int skipCount = 0, + DateTime? startTime = null, + DateTime? endTime = null, + string applicationName = null, + string identity = null, + string action = null, + Guid? userId = null, + string userName = null, + string clientId = null, + string correlationId = null, + bool includeDetails = false, + CancellationToken cancellationToken = default) + { + var query = GetListQuery( + startTime, + endTime, + applicationName, + identity, + action, + userId, + userName, + clientId, + correlationId + ); + + return await query.OrderBy(sorting ?? nameof(IdentitySecurityLog.CreationTime) + " desc") + .As>() + .PageBy>(skipCount, maxResultCount) + .ToListAsync(GetCancellationToken(cancellationToken)); + } + + public async Task GetCountAsync( + DateTime? startTime = null, + DateTime? endTime = null, + string applicationName = null, + string identity = null, + string action = null, + Guid? userId = null, + string userName = null, + string clientId = null, + string correlationId = null, + CancellationToken cancellationToken = default) + { + var query = GetListQuery( + startTime, + endTime, + applicationName, + identity, + action, + userId, + userName, + clientId, + correlationId + ); + + return await query.As>() + .LongCountAsync(GetCancellationToken(cancellationToken)); + } + + + public async Task GetByUserIdAsync(Guid id, Guid userId, bool includeDetails = false, + CancellationToken cancellationToken = default) + { + return await GetMongoQueryable().FirstOrDefaultAsync(x => x.Id == id && x.UserId == userId, + GetCancellationToken(cancellationToken)); + } + + protected virtual IQueryable GetListQuery( + DateTime? startTime = null, + DateTime? endTime = null, + string applicationName = null, + string identity = null, + string action = null, + Guid? userId = null, + string userName = null, + string clientId = null, + string correlationId = null) + { + return GetMongoQueryable() + .WhereIf(startTime.HasValue, securityLog => securityLog.CreationTime >= startTime.Value) + .WhereIf(endTime.HasValue, securityLog => securityLog.CreationTime < endTime.Value.AddDays(1).Date) + .WhereIf(!applicationName.IsNullOrWhiteSpace(), + securityLog => securityLog.ApplicationName == applicationName) + .WhereIf(!identity.IsNullOrWhiteSpace(), securityLog => securityLog.Identity == identity) + .WhereIf(!action.IsNullOrWhiteSpace(), securityLog => securityLog.Action == action) + .WhereIf(userId.HasValue, securityLog => securityLog.UserId == userId) + .WhereIf(!userName.IsNullOrWhiteSpace(), securityLog => securityLog.UserName == userName) + .WhereIf(!clientId.IsNullOrWhiteSpace(), securityLog => securityLog.ClientId == clientId) + .WhereIf(!correlationId.IsNullOrWhiteSpace(), + securityLog => securityLog.CorrelationId == correlationId); + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Web/AbpIdentityWebAutoMapperProfile.cs b/modules/identity/src/Volo.Abp.Identity.Web/AbpIdentityWebAutoMapperProfile.cs index b0154f543f..f7dfc69099 100644 --- a/modules/identity/src/Volo.Abp.Identity.Web/AbpIdentityWebAutoMapperProfile.cs +++ b/modules/identity/src/Volo.Abp.Identity.Web/AbpIdentityWebAutoMapperProfile.cs @@ -22,7 +22,7 @@ namespace Volo.Abp.Identity.Web //CreateModal CreateMap() - .Ignore(x => x.ExtraProperties) + .MapExtraProperties() .ForMember(dest => dest.RoleNames, opt => opt.Ignore()); CreateMap() @@ -30,7 +30,7 @@ namespace Volo.Abp.Identity.Web //EditModal CreateMap() - .Ignore(x => x.ExtraProperties) + .MapExtraProperties() .ForMember(dest => dest.RoleNames, opt => opt.Ignore()); CreateMap() @@ -44,11 +44,11 @@ namespace Volo.Abp.Identity.Web //CreateModal CreateMap() - .Ignore(x => x.ExtraProperties); + .MapExtraProperties(); //EditModal CreateMap() - .Ignore(x => x.ExtraProperties); + .MapExtraProperties(); } } } diff --git a/modules/identity/src/Volo.Abp.Identity.Web/AbpIdentityWebModule.cs b/modules/identity/src/Volo.Abp.Identity.Web/AbpIdentityWebModule.cs index 79b636418c..23a4529502 100644 --- a/modules/identity/src/Volo.Abp.Identity.Web/AbpIdentityWebModule.cs +++ b/modules/identity/src/Volo.Abp.Identity.Web/AbpIdentityWebModule.cs @@ -7,6 +7,8 @@ using Volo.Abp.AutoMapper; using Volo.Abp.Identity.Localization; using Volo.Abp.Identity.Web.Navigation; using Volo.Abp.Modularity; +using Volo.Abp.ObjectExtending; +using Volo.Abp.ObjectExtending.Modularity; using Volo.Abp.PermissionManagement.Web; using Volo.Abp.UI.Navigation; using Volo.Abp.VirtualFileSystem; @@ -62,5 +64,24 @@ namespace Volo.Abp.Identity.Web options.Conventions.AuthorizePage("/Identity/Roles/EditModal", IdentityPermissions.Roles.Update); }); } + + public override void PostConfigureServices(ServiceConfigurationContext context) + { + ModuleExtensionConfigurationHelper + .ApplyEntityConfigurationToUi( + IdentityModuleExtensionConsts.ModuleName, + IdentityModuleExtensionConsts.EntityNames.Role, + createFormTypes: new[] { typeof(Volo.Abp.Identity.Web.Pages.Identity.Roles.CreateModalModel.RoleInfoModel) }, + editFormTypes: new[] { typeof(Volo.Abp.Identity.Web.Pages.Identity.Roles.EditModalModel.RoleInfoModel) } + ); + + ModuleExtensionConfigurationHelper + .ApplyEntityConfigurationToUi( + IdentityModuleExtensionConsts.ModuleName, + IdentityModuleExtensionConsts.EntityNames.User, + createFormTypes: new[] { typeof(Volo.Abp.Identity.Web.Pages.Identity.Users.CreateModalModel.UserInfoViewModel) }, + editFormTypes: new[] { typeof(Volo.Abp.Identity.Web.Pages.Identity.Users.EditModalModel.UserInfoViewModel) } + ); + } } } diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/CreateModal.cshtml b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/CreateModal.cshtml index ce075d0b58..72ea9da317 100644 --- a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/CreateModal.cshtml +++ b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/CreateModal.cshtml @@ -1,9 +1,14 @@ @page @using Microsoft.AspNetCore.Mvc.Localization +@using Microsoft.Extensions.Localization @using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal @using Volo.Abp.Identity.Localization -@model Volo.Abp.Identity.Web.Pages.Identity.Roles.CreateModalModel +@using Volo.Abp.Identity.Web.Pages.Identity.Roles +@using Volo.Abp.Localization +@using Volo.Abp.ObjectExtending +@model CreateModalModel @inject IHtmlLocalizer L +@inject IStringLocalizerFactory StringLocalizerFactory @{ Layout = null; } @@ -12,8 +17,27 @@ + + + + @foreach (var propertyInfo in ObjectExtensionManager.Instance.GetProperties()) + { + if (propertyInfo.Type.IsEnum) + { + + } + else + { + + } + } diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/CreateModal.cshtml.cs b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/CreateModal.cshtml.cs index df86acd94d..79787da08b 100644 --- a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/CreateModal.cshtml.cs +++ b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/CreateModal.cshtml.cs @@ -1,6 +1,7 @@ using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; +using Volo.Abp.ObjectExtending; using Volo.Abp.Validation; namespace Volo.Abp.Identity.Web.Pages.Identity.Roles @@ -19,6 +20,8 @@ namespace Volo.Abp.Identity.Web.Pages.Identity.Roles public virtual Task OnGetAsync() { + Role = new RoleInfoModel(); + return Task.FromResult(Page()); } @@ -32,7 +35,7 @@ namespace Volo.Abp.Identity.Web.Pages.Identity.Roles return NoContent(); } - public class RoleInfoModel + public class RoleInfoModel : ExtensibleObject { [Required] [DynamicStringLength(typeof(IdentityRoleConsts), nameof(IdentityRoleConsts.MaxNameLength))] diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/EditModal.cshtml b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/EditModal.cshtml index 0a3cd3668d..8ef33b32cc 100644 --- a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/EditModal.cshtml +++ b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/EditModal.cshtml @@ -1,9 +1,14 @@ @page @using Microsoft.AspNetCore.Mvc.Localization +@using Microsoft.Extensions.Localization @using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal @using Volo.Abp.Identity.Localization -@model Volo.Abp.Identity.Web.Pages.Identity.Roles.EditModalModel +@using Volo.Abp.Identity.Web.Pages.Identity.Roles +@using Volo.Abp.Localization +@using Volo.Abp.ObjectExtending +@model EditModalModel @inject IHtmlLocalizer L +@inject IStringLocalizerFactory StringLocalizerFactory @{ Layout = null; } @@ -12,7 +17,9 @@ + + @if (Model.Role.IsStatic) { @@ -21,8 +28,27 @@ { } + + + + @foreach (var propertyInfo in ObjectExtensionManager.Instance.GetProperties()) + { + if (propertyInfo.Type.IsEnum) + { + + } + else + { + + } + } diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/EditModal.cshtml.cs b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/EditModal.cshtml.cs index 8a004e95a5..14236c1466 100644 --- a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/EditModal.cshtml.cs +++ b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/EditModal.cshtml.cs @@ -3,6 +3,7 @@ using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Volo.Abp.Domain.Entities; +using Volo.Abp.ObjectExtending; using Volo.Abp.Validation; namespace Volo.Abp.Identity.Web.Pages.Identity.Roles @@ -36,7 +37,7 @@ namespace Volo.Abp.Identity.Web.Pages.Identity.Roles return NoContent(); } - public class RoleInfoModel : IHasConcurrencyStamp + public class RoleInfoModel : ExtensibleObject, IHasConcurrencyStamp { [HiddenInput] public Guid Id { get; set; } diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/Index.cshtml b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/Index.cshtml index 4658f44013..734fc40fe5 100644 --- a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/Index.cshtml +++ b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/Index.cshtml @@ -41,13 +41,6 @@ - - - - @L["Actions"] - @L["RoleName"] - - - + \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/index.js b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/index.js index 4331ab4229..d66d7240e3 100644 --- a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/index.js +++ b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/index.js @@ -12,76 +12,76 @@ abp.appPath + 'Identity/Roles/CreateModal' ); - $(function () { - var _$wrapper = $('#IdentityRolesWrapper'); - var _$table = _$wrapper.find('table'); + var _dataTable = null; - var _dataTable = _$table.DataTable( - abp.libs.datatables.normalizeConfiguration({ - order: [[1, 'asc']], - searching: false, - processing: true, - serverSide: true, - scrollX: true, - paging: true, - ajax: abp.libs.datatables.createAjax( - _identityRoleAppService.getList - ), - columnDefs: [ + abp.ui.extensions.entityActions.get('identity.role').addContributor( + function(actionList) { + return actionList.addManyTail( + [ { - rowAction: { - items: [ - { - text: l('Edit'), - visible: abp.auth.isGranted( - 'AbpIdentity.Roles.Update' - ), - action: function (data) { - _editModal.open({ - id: data.record.id, - }); - }, - }, - { - text: l('Permissions'), - visible: abp.auth.isGranted( - 'AbpIdentity.Roles.ManagePermissions' - ), - action: function (data) { - _permissionsModal.open({ - providerName: 'R', - providerKey: data.record.name, - }); - }, - }, - { - text: l('Delete'), - visible: function (data) { - return ( - !data.isStatic && - abp.auth.isGranted( - 'AbpIdentity.Roles.Delete' - ) - ); //TODO: Check permission - }, - confirmMessage: function (data) { - return l( - 'RoleDeletionConfirmationMessage', - data.record.name - ); - }, - action: function (data) { - _identityRoleAppService - .delete(data.record.id) - .then(function () { - _dataTable.ajax.reload(); - }); - }, - }, - ], + text: l('Edit'), + visible: abp.auth.isGranted( + 'AbpIdentity.Roles.Update' + ), + action: function (data) { + _editModal.open({ + id: data.record.id, + }); + }, + }, + { + text: l('Permissions'), + visible: abp.auth.isGranted( + 'AbpIdentity.Roles.ManagePermissions' + ), + action: function (data) { + _permissionsModal.open({ + providerName: 'R', + providerKey: data.record.name, + }); + }, + }, + { + text: l('Delete'), + visible: function (data) { + return ( + !data.isStatic && + abp.auth.isGranted( + 'AbpIdentity.Roles.Delete' + ) + ); //TODO: Check permission + }, + confirmMessage: function (data) { + return l( + 'RoleDeletionConfirmationMessage', + data.record.name + ); }, + action: function (data) { + _identityRoleAppService + .delete(data.record.id) + .then(function () { + _dataTable.ajax.reload(); + }); + }, + } + ] + ); + } + ); + + abp.ui.extensions.tableColumns.get('identity.role').addContributor( + function (columnList) { + columnList.addManyTail( + [ + { + title: l("Actions"), + rowAction: { + items: abp.ui.extensions.entityActions.get('identity.role').actions.toArray() + } }, { + title: l('RoleName'), data: 'name', render: function (data, type, row) { var name = '' + data + ''; @@ -99,8 +99,29 @@ } return name; }, - }, - ], + } + ] + ); + }, + 0 //adds as the first contributor + ); + + $(function () { + var _$wrapper = $('#IdentityRolesWrapper'); + var _$table = _$wrapper.find('table'); + + _dataTable = _$table.DataTable( + abp.libs.datatables.normalizeConfiguration({ + order: [[1, 'asc']], + searching: false, + processing: true, + serverSide: true, + scrollX: true, + paging: true, + ajax: abp.libs.datatables.createAjax( + _identityRoleAppService.getList + ), + columnDefs: abp.ui.extensions.tableColumns.get('identity.role').columns.toArray() }) ); diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/CreateModal.cshtml b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/CreateModal.cshtml index d20d0a0026..b171b785a2 100644 --- a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/CreateModal.cshtml +++ b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/CreateModal.cshtml @@ -1,9 +1,14 @@ @page @using Microsoft.AspNetCore.Mvc.Localization +@using Microsoft.Extensions.Localization @using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal @using Volo.Abp.Identity.Localization -@model Volo.Abp.Identity.Web.Pages.Identity.Users.CreateModalModel +@using Volo.Abp.Identity.Web.Pages.Identity.Users +@using Volo.Abp.Localization +@using Volo.Abp.ObjectExtending +@model CreateModalModel @inject IHtmlLocalizer L +@inject IStringLocalizerFactory StringLocalizerFactory @{ Layout = null; } @@ -22,6 +27,23 @@ + + @foreach (var propertyInfo in ObjectExtensionManager.Instance.GetProperties()) + { + if (propertyInfo.Type.IsEnum) + { + + } + else + { + + } + } @for (var i = 0; i < Model.Roles.Length; i++) diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/CreateModal.cshtml.cs b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/CreateModal.cshtml.cs index 6149c676b8..a868890632 100644 --- a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/CreateModal.cshtml.cs +++ b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/CreateModal.cshtml.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Volo.Abp.Auditing; using Volo.Abp.Application.Dtos; +using Volo.Abp.ObjectExtending; using Volo.Abp.Validation; namespace Volo.Abp.Identity.Web.Pages.Identity.Users @@ -52,7 +53,7 @@ namespace Volo.Abp.Identity.Web.Pages.Identity.Users return NoContent(); } - public class UserInfoViewModel + public class UserInfoViewModel : ExtensibleObject { [Required] [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxUserNameLength))] diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/EditModal.cshtml b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/EditModal.cshtml index 26962350e0..56ac3929f1 100644 --- a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/EditModal.cshtml +++ b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/EditModal.cshtml @@ -1,9 +1,14 @@ @page @using Microsoft.AspNetCore.Mvc.Localization +@using Microsoft.Extensions.Localization @using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal @using Volo.Abp.Identity.Localization -@model Volo.Abp.Identity.Web.Pages.Identity.Users.EditModalModel +@using Volo.Abp.Identity.Web.Pages.Identity.Users +@using Volo.Abp.Localization +@using Volo.Abp.ObjectExtending +@model EditModalModel @inject IHtmlLocalizer L +@inject IStringLocalizerFactory StringLocalizerFactory @{ Layout = null; } @@ -24,6 +29,24 @@ + + @foreach (var propertyInfo in ObjectExtensionManager.Instance.GetProperties()) + { + if (propertyInfo.Type.IsEnum) + { + + } + else + { + + } + } + @for (var i = 0; i < Model.Roles.Length; i++) diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/EditModal.cshtml.cs b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/EditModal.cshtml.cs index 08c5ae0dd9..a2336c2b8e 100644 --- a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/EditModal.cshtml.cs +++ b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/EditModal.cshtml.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Mvc; using Volo.Abp.Auditing; using Volo.Abp.Application.Dtos; using Volo.Abp.Domain.Entities; +using Volo.Abp.ObjectExtending; using Volo.Abp.Validation; namespace Volo.Abp.Identity.Web.Pages.Identity.Users @@ -55,7 +56,7 @@ namespace Volo.Abp.Identity.Web.Pages.Identity.Users return NoContent(); } - public class UserInfoViewModel : IHasConcurrencyStamp + public class UserInfoViewModel : ExtensibleObject, IHasConcurrencyStamp { [HiddenInput] public Guid Id { get; set; } diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/Index.cshtml b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/Index.cshtml index 23f7747ac8..ee9192d58f 100644 --- a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/Index.cshtml +++ b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/Index.cshtml @@ -42,15 +42,6 @@ - - - - @L["Actions"] - @L["UserName"] - @L["EmailAddress"] - @L["PhoneNumber"] - - - + \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/index.js b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/index.js index 53f66ddb5e..71675d8b8e 100644 --- a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/index.js +++ b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/index.js @@ -12,10 +12,91 @@ abp.appPath + 'AbpPermissionManagement/PermissionManagementModal' ); + var _dataTable = null; + + abp.ui.extensions.entityActions.get('identity.user').addContributor( + function(actionList) { + return actionList.addManyTail( + [ + { + text: l('Edit'), + visible: abp.auth.isGranted( + 'AbpIdentity.Users.Update' + ), + action: function (data) { + _editModal.open({ + id: data.record.id, + }); + }, + }, + { + text: l('Permissions'), + visible: abp.auth.isGranted( + 'AbpIdentity.Users.ManagePermissions' + ), + action: function (data) { + _permissionsModal.open({ + providerName: 'U', + providerKey: data.record.id, + }); + }, + }, + { + text: l('Delete'), + visible: abp.auth.isGranted( + 'AbpIdentity.Users.Delete' + ), + confirmMessage: function (data) { + return l( + 'UserDeletionConfirmationMessage', + data.record.userName + ); + }, + action: function (data) { + _identityUserAppService + .delete(data.record.id) + .then(function () { + _dataTable.ajax.reload(); + }); + }, + } + ] + ); + } + ); + + abp.ui.extensions.tableColumns.get('identity.user').addContributor( + function (columnList) { + columnList.addManyTail( + [ + { + title: l("Actions"), + rowAction: { + items: abp.ui.extensions.entityActions.get('identity.user').actions.toArray() + } + }, + { + title: l('UserName'), + data: 'userName', + }, + { + title: l('EmailAddress'), + data: 'email', + }, + { + title: l('PhoneNumber'), + data: 'phoneNumber', + } + ] + ); + }, + 0 //adds as the first contributor + ); + $(function () { var _$wrapper = $('#IdentityUsersWrapper'); var _$table = _$wrapper.find('table'); - var _dataTable = _$table.DataTable( + _dataTable = _$table.DataTable( abp.libs.datatables.normalizeConfiguration({ order: [[1, 'asc']], processing: true, @@ -25,65 +106,7 @@ ajax: abp.libs.datatables.createAjax( _identityUserAppService.getList ), - columnDefs: [ - { - rowAction: { - items: [ - { - text: l('Edit'), - visible: abp.auth.isGranted( - 'AbpIdentity.Users.Update' - ), - action: function (data) { - _editModal.open({ - id: data.record.id, - }); - }, - }, - { - text: l('Permissions'), - visible: abp.auth.isGranted( - 'AbpIdentity.Users.ManagePermissions' - ), - action: function (data) { - _permissionsModal.open({ - providerName: 'U', - providerKey: data.record.id, - }); - }, - }, - { - text: l('Delete'), - visible: abp.auth.isGranted( - 'AbpIdentity.Users.Delete' - ), - confirmMessage: function (data) { - return l( - 'UserDeletionConfirmationMessage', - data.record.userName - ); - }, - action: function (data) { - _identityUserAppService - .delete(data.record.id) - .then(function () { - _dataTable.ajax.reload(); - }); - }, - }, - ], - }, - }, - { - data: 'userName', - }, - { - data: 'email', - }, - { - data: 'phoneNumber', - }, - ], + columnDefs: abp.ui.extensions.tableColumns.get('identity.user').columns.toArray() }) ); diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Volo.Abp.Identity.Web.csproj b/modules/identity/src/Volo.Abp.Identity.Web/Volo.Abp.Identity.Web.csproj index ae1d1b6ce7..69546e1493 100644 --- a/modules/identity/src/Volo.Abp.Identity.Web/Volo.Abp.Identity.Web.csproj +++ b/modules/identity/src/Volo.Abp.Identity.Web/Volo.Abp.Identity.Web.csproj @@ -40,7 +40,7 @@ - + - + diff --git a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo.Abp.Identity.AspNetCore.Tests.csproj b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo.Abp.Identity.AspNetCore.Tests.csproj new file mode 100644 index 0000000000..594080f283 --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo.Abp.Identity.AspNetCore.Tests.csproj @@ -0,0 +1,25 @@ + + + + netcoreapp3.1 + Volo.Abp.Identity.AspNetCore.Tests + Volo.Abp.Identity.AspNetCore.Tests + true + false + false + false + + + + + + + + + + + + + + + diff --git a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreTestBase.cs b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreTestBase.cs new file mode 100644 index 0000000000..c9e11f49b3 --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreTestBase.cs @@ -0,0 +1,24 @@ +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.AspNetCore.TestBase; + +namespace Volo.Abp.Identity.AspNetCore +{ + public abstract class AbpIdentityAspNetCoreTestBase : AbpAspNetCoreIntegratedTestBase + { + protected virtual async Task GetResponseAsStringAsync(string url, HttpStatusCode expectedStatusCode = HttpStatusCode.OK) + { + var response = await GetResponseAsync(url, expectedStatusCode); + return await response.Content.ReadAsStringAsync(); + } + + protected virtual async Task GetResponseAsync(string url, HttpStatusCode expectedStatusCode = HttpStatusCode.OK) + { + var response = await Client.GetAsync(url); + response.StatusCode.ShouldBe(expectedStatusCode); + return response; + } + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreTestModule.cs b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreTestModule.cs new file mode 100644 index 0000000000..c3c035a208 --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreTestModule.cs @@ -0,0 +1,45 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Mvc.ApplicationParts; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.TestBase; +using Volo.Abp.Modularity; + +namespace Volo.Abp.Identity.AspNetCore +{ + [DependsOn( + typeof(AbpIdentityAspNetCoreModule), + typeof(AbpIdentityDomainTestModule), + typeof(AbpAspNetCoreTestBaseModule), + typeof(AbpAspNetCoreMvcModule) + )] + public class AbpIdentityAspNetCoreTestModule : AbpModule + { + public override void PreConfigureServices(ServiceConfigurationContext context) + { + context.Services.PreConfigure(builder => + { + builder.PartManager.ApplicationParts.Add(new AssemblyPart(typeof(AbpIdentityAspNetCoreTestModule).Assembly)); + }); + } + + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.ExternalLoginProviders.Add(FakeExternalLoginProvider.Name); + }); + } + + public override void OnApplicationInitialization(ApplicationInitializationContext context) + { + var app = context.GetApplicationBuilder(); + var env = context.GetEnvironment(); + + app.UseRouting(); + app.UseAuthentication(); + app.UseAuthorization(); + app.UseConfiguredEndpoints(); + } + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreTestStartup.cs b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreTestStartup.cs new file mode 100644 index 0000000000..d495323c98 --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreTestStartup.cs @@ -0,0 +1,19 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Volo.Abp.Identity.AspNetCore +{ + public class AbpIdentityAspNetCoreTestStartup + { + public void ConfigureServices(IServiceCollection services) + { + services.AddApplication(); + } + + public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) + { + app.InitializeApplication(); + } + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpSignInManager_Tests.cs b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpSignInManager_Tests.cs new file mode 100644 index 0000000000..dc5d961f9a --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpSignInManager_Tests.cs @@ -0,0 +1,39 @@ +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Shouldly; +using Xunit; + +namespace Volo.Abp.Identity.AspNetCore +{ + public class AbpSignInManager_Tests : AbpIdentityAspNetCoreTestBase + { + [Fact] + public void Should_Resolve_AbpSignInManager() + { + var signInManager = GetRequiredService>(); + signInManager.ShouldBeOfType(); + } + + [Fact] + public async Task Should_SignIn_With_Correct_Credentials() + { + var result = await GetResponseAsStringAsync( + "api/signin-test/password?userName=admin&password=1q2w3E*" + ); + + result.ShouldBe("Succeeded"); + } + + [Fact] + public async Task Should_Not_SignIn_With_Wrong_Credentials() + { + var result = await GetResponseAsStringAsync( + "api/signin-test/password?userName=admin&password=WRONG_PASSWORD" + ); + + result.ShouldBe("Failed"); + } + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/ExternalLoginProvider_Tests.cs b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/ExternalLoginProvider_Tests.cs new file mode 100644 index 0000000000..ee9a56432e --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/ExternalLoginProvider_Tests.cs @@ -0,0 +1,57 @@ +using System.Linq; +using System.Threading.Tasks; +using Shouldly; +using Xunit; + +namespace Volo.Abp.Identity.AspNetCore +{ + public class ExternalLoginProvider_Tests : AbpIdentityAspNetCoreTestBase + { + [Fact] + public async Task Should_SignIn_With_ExternalLoginProvider() + { + // User does not exists yet + (await GetRequiredService().FindByNameAsync("ext_user")).ShouldBeNull(); + + // Try to login + + var result = await GetResponseAsStringAsync( + "api/signin-test/password?userName=ext_user&password=abc" + ); + + result.ShouldBe("Succeeded"); + + // User should be created now + + await CheckUserAsync(); + + // Re-login + + result = await GetResponseAsStringAsync( + "api/signin-test/password?userName=ext_user&password=abc" + ); + + result.ShouldBe("Succeeded"); + + await CheckUserAsync(); + } + + private async Task CheckUserAsync() + { + var userRepository = GetRequiredService(); + + var user = await userRepository.FindByNormalizedUserNameAsync("EXT_USER"); + user.Name.ShouldBe("Test Name"); + user.Surname.ShouldBe("Test Surname"); + user.EmailConfirmed.ShouldBeTrue(); + user.TwoFactorEnabled.ShouldBeFalse(); + user.PhoneNumber.ShouldBe("123"); + user.PhoneNumberConfirmed.ShouldBeFalse(); + user.IsExternal.ShouldBeTrue(); + + var logins = user.Logins.Where(l => l.LoginProvider == "Fake").ToList(); + logins.Count.ShouldBe(1); + logins[0].ProviderKey.ShouldBe("123"); + } + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/FakeExternalLoginProvider.cs b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/FakeExternalLoginProvider.cs new file mode 100644 index 0000000000..6a54ccf7e3 --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/FakeExternalLoginProvider.cs @@ -0,0 +1,55 @@ +using System; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Guids; +using Volo.Abp.MultiTenancy; + +namespace Volo.Abp.Identity.AspNetCore +{ + public class FakeExternalLoginProvider : ExternalLoginProviderBase, ITransientDependency + { + public const string Name = "Fake"; + + public FakeExternalLoginProvider( + IGuidGenerator guidGenerator, + ICurrentTenant currentTenant, + IdentityUserManager userManager, + IIdentityUserRepository identityUserRepository) + : base( + guidGenerator, + currentTenant, + userManager, + identityUserRepository) + { + + } + + public override Task TryAuthenticateAsync(string userName, string plainPassword) + { + return Task.FromResult( + userName == "ext_user" && plainPassword == "abc" + ); + } + + protected override Task GetUserInfoAsync(string userName) + { + if (userName != "ext_user") + { + throw new ArgumentException(); + } + + return Task.FromResult( + new ExternalLoginUserInfo("ext_user@test.com") + { + Name = "Test Name", //optional, if the provider knows it + Surname = "Test Surname", //optional, if the provider knows it + EmailConfirmed = true, //optional, if the provider knows it + TwoFactorEnabled = false, //optional, if the provider knows it + PhoneNumber = "123", //optional, if the provider knows it + PhoneNumberConfirmed = false, //optional, if the provider knows it + ProviderKey = "123" //The id of the user on the provider side + } + ); + } + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/SignInTestController.cs b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/SignInTestController.cs new file mode 100644 index 0000000000..77fb7918e2 --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/SignInTestController.cs @@ -0,0 +1,31 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc; + +namespace Volo.Abp.Identity.AspNetCore +{ + [Route("api/signin-test")] + public class SignInTestController : AbpController + { + private readonly SignInManager _signInManager; + + public SignInTestController(SignInManager signInManager) + { + _signInManager = signInManager; + } + + [Route("password")] + public async Task PasswordLogin(string userName, string password) + { + var result = await _signInManager.PasswordSignInAsync( + userName, + password, + false, + false + ); + + return Content(result.ToString()); + } + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/AbpIdentityExtendedTestBase.cs b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/AbpIdentityExtendedTestBase.cs index 4a89feaa8c..5385aaec49 100644 --- a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/AbpIdentityExtendedTestBase.cs +++ b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/AbpIdentityExtendedTestBase.cs @@ -1,12 +1,14 @@ using System; using System.Linq; +using System.Threading.Tasks; using Volo.Abp.Domain.Entities; using Volo.Abp.Identity.EntityFrameworkCore; using Volo.Abp.Modularity; +using Volo.Abp.Uow; namespace Volo.Abp.Identity { - public abstract class AbpIdentityExtendedTestBase : AbpIdentityTestBase + public abstract class AbpIdentityExtendedTestBase : AbpIdentityTestBase where TStartupModule : IAbpModule { protected virtual IdentityUser GetUser(string userName) @@ -40,5 +42,14 @@ namespace Volo.Abp.Identity return action.Invoke(dbContext); } } + + protected virtual async Task UsingUowAsync(Func action) + { + using (var uow = GetRequiredService().Begin()) + { + await action(); + await uow.CompleteAsync(); + } + } } -} \ No newline at end of file +} diff --git a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityClaimTypeManager_Tests.cs b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityClaimTypeManager_Tests.cs index c5c68d40c6..1ef603e6b3 100644 --- a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityClaimTypeManager_Tests.cs +++ b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityClaimTypeManager_Tests.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Text; using System.Threading.Tasks; using Shouldly; using Xunit; diff --git a/modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo.Abp.Identity.EntityFrameworkCore.Tests.csproj b/modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo.Abp.Identity.EntityFrameworkCore.Tests.csproj index 9e31625c1b..d28ec1a50b 100644 --- a/modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo.Abp.Identity.EntityFrameworkCore.Tests.csproj +++ b/modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo.Abp.Identity.EntityFrameworkCore.Tests.csproj @@ -20,7 +20,7 @@ - + diff --git a/modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo/Abp/Identity/EntityFrameworkCore/IdentitySecurityLogRepository_Tests.cs b/modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo/Abp/Identity/EntityFrameworkCore/IdentitySecurityLogRepository_Tests.cs new file mode 100644 index 0000000000..434f74706d --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo/Abp/Identity/EntityFrameworkCore/IdentitySecurityLogRepository_Tests.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.Identity.EntityFrameworkCore +{ + public class IdentitySecurityLogRepository_Tests : IdentitySecurityLogRepository_Tests + { + + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/IdentitySecurityLogRepository_Tests.cs b/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/IdentitySecurityLogRepository_Tests.cs new file mode 100644 index 0000000000..85d496fdb2 --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/IdentitySecurityLogRepository_Tests.cs @@ -0,0 +1,10 @@ +using Xunit; + +namespace Volo.Abp.Identity.MongoDB +{ + [Collection(MongoTestCollection.Name)] + public class IdentitySecurityLogRepository_Tests : IdentitySecurityLogRepository_Tests + { + + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/AbpIdentityTestDataBuilder.cs b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/AbpIdentityTestDataBuilder.cs index 9258be16af..79dd858c20 100644 --- a/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/AbpIdentityTestDataBuilder.cs +++ b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/AbpIdentityTestDataBuilder.cs @@ -1,9 +1,10 @@ -using System; +using System; using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Volo.Abp.DependencyInjection; using Volo.Abp.Guids; +using Volo.Abp.SecurityLog; namespace Volo.Abp.Identity { @@ -14,6 +15,7 @@ namespace Volo.Abp.Identity private readonly IIdentityClaimTypeRepository _identityClaimTypeRepository; private readonly IIdentityRoleRepository _roleRepository; private readonly IOrganizationUnitRepository _organizationUnitRepository; + private readonly IIdentitySecurityLogRepository _identitySecurityLogRepository; private readonly ILookupNormalizer _lookupNormalizer; private readonly IdentityTestData _testData; private readonly OrganizationUnitManager _organizationUnitManager; @@ -31,6 +33,7 @@ namespace Volo.Abp.Identity IIdentityClaimTypeRepository identityClaimTypeRepository, IIdentityRoleRepository roleRepository, IOrganizationUnitRepository organizationUnitRepository, + IIdentitySecurityLogRepository identitySecurityLogRepository, ILookupNormalizer lookupNormalizer, IdentityTestData testData, OrganizationUnitManager organizationUnitManager) @@ -43,6 +46,7 @@ namespace Volo.Abp.Identity _testData = testData; _organizationUnitRepository = organizationUnitRepository; _organizationUnitManager = organizationUnitManager; + _identitySecurityLogRepository = identitySecurityLogRepository; } public async Task Build() @@ -51,6 +55,7 @@ namespace Volo.Abp.Identity await AddOrganizationUnits(); await AddUsers(); await AddClaimTypes(); + await AddSecurityLogs(); } private async Task AddRoles() @@ -69,7 +74,7 @@ namespace Volo.Abp.Identity } /* Creates OU tree as shown below: - * + * * - OU1 * - OU11 * - OU111 @@ -138,5 +143,30 @@ namespace Volo.Abp.Identity var ou = await _organizationUnitRepository.InsertAsync(new OrganizationUnit(_guidGenerator.Create(), displayName, parentId) { Code = code }); return ou; } + + private async Task AddSecurityLogs() + { + await _identitySecurityLogRepository.InsertAsync(new IdentitySecurityLog(_guidGenerator, new SecurityLogInfo + { + ApplicationName = "Test-ApplicationName", + Identity = "Test-Identity", + Action = "Test-Action", + UserId = _testData.UserJohnId, + UserName = "john.nash", + + CreationTime = new DateTime(2020, 01, 01, 10, 0, 0) + })); + + await _identitySecurityLogRepository.InsertAsync(new IdentitySecurityLog(_guidGenerator, new SecurityLogInfo + { + ApplicationName = "Test-ApplicationName", + Identity = "Test-Identity", + Action = "Test-Action", + UserId = _testData.UserDavidId, + UserName = "david", + + CreationTime = new DateTime(2020, 01, 02, 10, 0, 0) + })); + } } -} \ No newline at end of file +} diff --git a/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentitySecurityLogRepository_Tests.cs b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentitySecurityLogRepository_Tests.cs new file mode 100644 index 0000000000..13ff8a06a3 --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentitySecurityLogRepository_Tests.cs @@ -0,0 +1,36 @@ +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.Modularity; +using Xunit; + +namespace Volo.Abp.Identity +{ + public abstract class IdentitySecurityLogRepository_Tests : AbpIdentityTestBase + where TStartupModule : IAbpModule + { + protected IIdentitySecurityLogRepository RoleRepository { get; } + protected IdentityTestData TestData { get; } + + protected IdentitySecurityLogRepository_Tests() + { + RoleRepository = GetRequiredService(); + TestData = GetRequiredService(); + } + + [Fact] + public async Task GetListAsync() + { + var logs = await RoleRepository.GetListAsync(); + logs.ShouldNotBeEmpty(); + logs.ShouldContain(x => x.ApplicationName == "Test-ApplicationName" && x.UserId == TestData.UserJohnId); + logs.ShouldContain(x => x.ApplicationName == "Test-ApplicationName" && x.UserId == TestData.UserDavidId); + } + + [Fact] + public async Task GetCountAsync() + { + var count = await RoleRepository.GetCountAsync(); + count.ShouldBe(2); + } + } +} diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/IdentityServerSecurityLogActionConsts.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/IdentityServerSecurityLogActionConsts.cs new file mode 100644 index 0000000000..47ad6b21d5 --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/IdentityServerSecurityLogActionConsts.cs @@ -0,0 +1,19 @@ +namespace Volo.Abp.IdentityServer +{ + public class IdentityServerSecurityLogActionConsts + { + public static string LoginSucceeded { get; set; } = "LoginSucceeded"; + + public static string LoginLockedout { get; set; } = "LoginLockedout"; + + public static string LoginNotAllowed { get; set; } = "LoginNotAllowed"; + + public static string LoginRequiresTwoFactor { get; set; } = "LoginRequiresTwoFactor"; + + public static string LoginFailed { get; set; } = "LoginFailed"; + + public static string LoginInvalidUserName { get; set; } = "LoginInvalidUserName"; + + public static string LoginInvalidUserNameOrPassword { get; set; } = "LoginInvalidUserNameOrPassword"; + } +} diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/IdentityServerSecurityLogIdentityConsts.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/IdentityServerSecurityLogIdentityConsts.cs new file mode 100644 index 0000000000..4a5f96e0e2 --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/IdentityServerSecurityLogIdentityConsts.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.IdentityServer +{ + public class IdentityServerSecurityLogIdentityConsts + { + public static string IdentityServer { get; set; } = "IdentityServer"; + } +} diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo.Abp.IdentityServer.Domain.csproj.DotSettings b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo.Abp.IdentityServer.Domain.csproj.DotSettings deleted file mode 100644 index 58ad6c8854..0000000000 --- a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo.Abp.IdentityServer.Domain.csproj.DotSettings +++ /dev/null @@ -1,2 +0,0 @@ - - CSharp71 \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/AbpResourceOwnerPasswordValidator.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/AbpResourceOwnerPasswordValidator.cs index e45df9b3f7..5aeddd8b86 100644 --- a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/AbpResourceOwnerPasswordValidator.cs +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/AbpResourceOwnerPasswordValidator.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Linq; using System.Security.Claims; using System.Threading.Tasks; using IdentityModel; @@ -8,8 +10,12 @@ using IdentityServer4.Models; using IdentityServer4.Services; using IdentityServer4.Validation; using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Localization; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Identity; using Volo.Abp.IdentityServer.Localization; using Volo.Abp.Security.Claims; using Volo.Abp.Uow; @@ -23,21 +29,30 @@ namespace Volo.Abp.IdentityServer.AspNetIdentity protected SignInManager SignInManager { get; } protected IEventService Events { get; } protected UserManager UserManager { get; } + protected IdentitySecurityLogManager IdentitySecurityLogManager { get; } protected ILogger> Logger { get; } protected IStringLocalizer Localizer { get; } + protected IHybridServiceScopeFactory ServiceScopeFactory { get; } + protected AbpIdentityOptions AbpIdentityOptions { get; } public AbpResourceOwnerPasswordValidator( UserManager userManager, SignInManager signInManager, + IdentitySecurityLogManager identitySecurityLogManager, IEventService events, - ILogger> logger, - IStringLocalizer localizer) + ILogger> logger, + IStringLocalizer localizer, + IOptions abpIdentityOptions, + IHybridServiceScopeFactory serviceScopeFactory) { UserManager = userManager; SignInManager = signInManager; + IdentitySecurityLogManager = identitySecurityLogManager; Events = events; Logger = logger; Localizer = localizer; + ServiceScopeFactory = serviceScopeFactory; + AbpIdentityOptions = abpIdentityOptions.Value; } /// @@ -48,29 +63,71 @@ namespace Volo.Abp.IdentityServer.AspNetIdentity [UnitOfWork] public virtual async Task ValidateAsync(ResourceOwnerPasswordValidationContext context) { + using var scope = ServiceScopeFactory.CreateScope(); + await ReplaceEmailToUsernameOfInputIfNeeds(context); - var user = await UserManager.FindByNameAsync(context.UserName); - string errorDescription; - if (user != null) + + IdentityUser user = null; + + async Task SetSuccessResultAsync() { - var result = await SignInManager.CheckPasswordSignInAsync(user, context.Password, true); - if (result.Succeeded) - { - var sub = await UserManager.GetUserIdAsync(user); + var sub = await UserManager.GetUserIdAsync(user); + + Logger.LogInformation("Credentials validated for username: {username}", context.UserName); + await Events.RaiseAsync(new UserLoginSuccessEvent(context.UserName, sub, context.UserName, interactive: false)); - Logger.LogInformation("Credentials validated for username: {username}", context.UserName); - await Events.RaiseAsync(new UserLoginSuccessEvent(context.UserName, sub, context.UserName, interactive: false)); + var additionalClaims = new List(); - var additionalClaims = new List(); + await AddCustomClaimsAsync(additionalClaims, user, context); - await AddCustomClaimsAsync(additionalClaims, user, context); + context.Result = new GrantValidationResult( + sub, + OidcConstants.AuthenticationMethods.Password, + additionalClaims.ToArray() + ); + + await IdentitySecurityLogManager.SaveAsync( + new IdentitySecurityLogContext + { + Identity = IdentityServerSecurityLogIdentityConsts.IdentityServer, + Action = IdentityServerSecurityLogActionConsts.LoginSucceeded + } + ); + } + + if (AbpIdentityOptions.ExternalLoginProviders.Any()) + { + foreach (var externalLoginProviderInfo in AbpIdentityOptions.ExternalLoginProviders.Values) + { + var externalLoginProvider = (IExternalLoginProvider) scope.ServiceProvider + .GetRequiredService(externalLoginProviderInfo.Type); - context.Result = new GrantValidationResult( - sub, - OidcConstants.AuthenticationMethods.Password, - additionalClaims.ToArray() - ); + if (await externalLoginProvider.TryAuthenticateAsync(context.UserName, context.Password)) + { + user = await UserManager.FindByNameAsync(context.UserName); + if (user == null) + { + user = await externalLoginProvider.CreateUserAsync(context.UserName, externalLoginProviderInfo.Name); + } + else + { + await externalLoginProvider.UpdateUserAsync(user, externalLoginProviderInfo.Name); + } + await SetSuccessResultAsync(); + return; + } + } + } + + user = await UserManager.FindByNameAsync(context.UserName); + string errorDescription; + if (user != null) + { + var result = await SignInManager.CheckPasswordSignInAsync(user, context.Password, true); + if (result.Succeeded) + { + await SetSuccessResultAsync(); return; } else if (result.IsLockedOut) @@ -91,12 +148,25 @@ namespace Volo.Abp.IdentityServer.AspNetIdentity await Events.RaiseAsync(new UserLoginFailureEvent(context.UserName, "invalid credentials", interactive: false)); errorDescription = Localizer["InvalidUserNameOrPassword"]; } + + await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext + { + Identity = IdentityServerSecurityLogIdentityConsts.IdentityServer, + Action = result.ToIdentitySecurityLogAction(), + UserName = context.UserName + }); } else { Logger.LogInformation("No user found matching username: {username}", context.UserName); await Events.RaiseAsync(new UserLoginFailureEvent(context.UserName, "invalid username", interactive: false)); errorDescription = Localizer["InvalidUsername"]; + + await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext() + { + Identity = IdentityServerSecurityLogIdentityConsts.IdentityServer, + Action = IdentityServerSecurityLogActionConsts.LoginInvalidUserName + }); } context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, errorDescription); diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/SignInResultExtensions.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/SignInResultExtensions.cs new file mode 100644 index 0000000000..6f167912c3 --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/SignInResultExtensions.cs @@ -0,0 +1,37 @@ +using Microsoft.AspNetCore.Identity; + +namespace Volo.Abp.IdentityServer.AspNetIdentity +{ + public static class SignInResultExtensions + { + public static string ToIdentitySecurityLogAction(this SignInResult result) + { + if (result.Succeeded) + { + return IdentityServerSecurityLogActionConsts.LoginSucceeded; + } + + if (result.IsLockedOut) + { + return IdentityServerSecurityLogActionConsts.LoginLockedout; + } + + if (result.RequiresTwoFactor) + { + return IdentityServerSecurityLogActionConsts.LoginRequiresTwoFactor; + } + + if (result.IsNotAllowed) + { + return IdentityServerSecurityLogActionConsts.LoginNotAllowed; + } + + if (!result.Succeeded) + { + return IdentityServerSecurityLogActionConsts.LoginFailed; + } + + return IdentityServerSecurityLogActionConsts.LoginFailed; + } + } +} diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Devices/DeviceFlowStore.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Devices/DeviceFlowStore.cs index ba0ef757fe..25432e2d58 100644 --- a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Devices/DeviceFlowStore.cs +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Devices/DeviceFlowStore.cs @@ -5,20 +5,19 @@ using IdentityServer4.Models; using IdentityServer4.Stores; using IdentityServer4.Stores.Serialization; using JetBrains.Annotations; -using Volo.Abp.DependencyInjection; using Volo.Abp.Guids; namespace Volo.Abp.IdentityServer.Devices { - public class DeviceFlowStore : IDeviceFlowStore, ITransientDependency + public class DeviceFlowStore : IDeviceFlowStore { protected IDeviceFlowCodesRepository DeviceFlowCodesRepository { get; } protected IGuidGenerator GuidGenerator { get; } protected IPersistentGrantSerializer PersistentGrantSerializer { get; } public DeviceFlowStore( - IDeviceFlowCodesRepository deviceFlowCodesRepository, - IGuidGenerator guidGenerator, + IDeviceFlowCodesRepository deviceFlowCodesRepository, + IGuidGenerator guidGenerator, IPersistentGrantSerializer persistentGrantSerializer) { DeviceFlowCodesRepository = deviceFlowCodesRepository; @@ -46,7 +45,7 @@ namespace Volo.Abp.IdentityServer.Devices } ); } - + public virtual async Task FindByUserCodeAsync(string userCode) { Check.NotNull(userCode, nameof(userCode)); @@ -70,7 +69,7 @@ namespace Volo.Abp.IdentityServer.Devices var deviceCodes = await DeviceFlowCodesRepository .FindByDeviceCodeAsync(deviceCode) ; - + if (deviceCodes == null) { return null; diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/Clients/ClientRepository.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/Clients/ClientRepository.cs index 5b350d0365..1c7a0ccccb 100644 --- a/modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/Clients/ClientRepository.cs +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/Clients/ClientRepository.cs @@ -53,6 +53,56 @@ namespace Volo.Abp.IdentityServer.Clients return await DbSet.AnyAsync(c => c.Id != expectedId && c.ClientId == clientId, cancellationToken: cancellationToken); } + public override async Task DeleteAsync(Guid id, bool autoSave = false, CancellationToken cancellationToken = default) + { + foreach (var clientGrantType in DbContext.Set().Where(x => x.ClientId == id)) + { + DbContext.Set().Remove(clientGrantType); + } + + foreach (var clientRedirectUri in DbContext.Set().Where(x => x.ClientId == id)) + { + DbContext.Set().Remove(clientRedirectUri); + } + + foreach (var clientPostLogoutRedirectUri in DbContext.Set().Where(x => x.ClientId == id)) + { + DbContext.Set().Remove(clientPostLogoutRedirectUri); + } + + foreach (var clientScope in DbContext.Set().Where(x => x.ClientId == id)) + { + DbContext.Set().Remove(clientScope); + } + + foreach (var clientSecret in DbContext.Set().Where(x => x.ClientId == id)) + { + DbContext.Set().Remove(clientSecret); + } + + foreach (var clientClaim in DbContext.Set().Where(x => x.ClientId == id)) + { + DbContext.Set().Remove(clientClaim); + } + + foreach (var clientIdPRestriction in DbContext.Set().Where(x => x.ClientId == id)) + { + DbContext.Set().Remove(clientIdPRestriction); + } + + foreach (var clientCorsOrigin in DbContext.Set().Where(x => x.ClientId == id)) + { + DbContext.Set().Remove(clientCorsOrigin); + } + + foreach (var clientProperty in DbContext.Set().Where(x => x.ClientId == id)) + { + DbContext.Set().Remove(clientProperty); + } + + await base.DeleteAsync(id, autoSave, cancellationToken); + } + public override IQueryable WithDetails() { return GetQueryable().IncludeDetails(); diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo.Abp.PermissionManagement.Domain.Shared.csproj b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo.Abp.PermissionManagement.Domain.Shared.csproj index 53009938cf..5b510b9e8b 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo.Abp.PermissionManagement.Domain.Shared.csproj +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo.Abp.PermissionManagement.Domain.Shared.csproj @@ -25,7 +25,7 @@ - + - + diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionDataSeedContributor.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionDataSeedContributor.cs index 906192fd4f..ac8274d445 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionDataSeedContributor.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionDataSeedContributor.cs @@ -30,6 +30,7 @@ namespace Volo.Abp.PermissionManagement var permissionNames = PermissionDefinitionManager .GetPermissions() .Where(p => p.MultiTenancySide.HasFlag(multiTenancySide)) + .Where(p => !p.Providers.Any() || p.Providers.Contains(RolePermissionValueProvider.ProviderName)) .Select(p => p.Name) .ToArray(); diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Volo.Abp.PermissionManagement.Web.csproj b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Volo.Abp.PermissionManagement.Web.csproj index 0732dd121a..5181fec0b4 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Volo.Abp.PermissionManagement.Web.csproj +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Volo.Abp.PermissionManagement.Web.csproj @@ -28,7 +28,7 @@ - + - + diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo.Abp.PermissionManagement.Domain.Tests.csproj b/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo.Abp.PermissionManagement.Domain.Tests.csproj index c22762d006..c08b0f7c6c 100644 --- a/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo.Abp.PermissionManagement.Domain.Tests.csproj +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo.Abp.PermissionManagement.Domain.Tests.csproj @@ -17,7 +17,7 @@ - + diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/PermissionDataSeedContributor_Tests.cs b/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/PermissionDataSeedContributor_Tests.cs new file mode 100644 index 0000000000..a888beb5f9 --- /dev/null +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/PermissionDataSeedContributor_Tests.cs @@ -0,0 +1,32 @@ +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.Data; +using Xunit; + +namespace Volo.Abp.PermissionManagement +{ + public class PermissionDataSeedContributor_Tests : PermissionTestBase + { + private readonly PermissionDataSeedContributor _permissionDataSeedContributor; + private readonly IPermissionGrantRepository _grantpermissionGrantRepository; + + public PermissionDataSeedContributor_Tests() + { + _permissionDataSeedContributor = GetRequiredService(); + _grantpermissionGrantRepository = GetRequiredService(); + } + + [Fact] + public async Task SeedAsync() + { + (await _grantpermissionGrantRepository.FindAsync("MyPermission1", RolePermissionValueProvider.ProviderName, "admin")).ShouldBeNull(); + (await _grantpermissionGrantRepository.FindAsync("MyPermission4", RolePermissionValueProvider.ProviderName, "admin")).ShouldBeNull(); + + await _permissionDataSeedContributor.SeedAsync(new DataSeedContext(null)); + + (await _grantpermissionGrantRepository.FindAsync("MyPermission1", RolePermissionValueProvider.ProviderName, "admin")).ShouldNotBeNull(); + (await _grantpermissionGrantRepository.FindAsync("MyPermission4", RolePermissionValueProvider.ProviderName, "admin")).ShouldBeNull(); + } + } +} diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.EntityFrameworkCore.Tests/Volo.Abp.PermissionManagement.EntityFrameworkCore.Tests.csproj b/modules/permission-management/test/Volo.Abp.PermissionManagement.EntityFrameworkCore.Tests/Volo.Abp.PermissionManagement.EntityFrameworkCore.Tests.csproj index 5fe2086c6c..e0f4bf29d6 100644 --- a/modules/permission-management/test/Volo.Abp.PermissionManagement.EntityFrameworkCore.Tests/Volo.Abp.PermissionManagement.EntityFrameworkCore.Tests.csproj +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.EntityFrameworkCore.Tests/Volo.Abp.PermissionManagement.EntityFrameworkCore.Tests.csproj @@ -17,7 +17,7 @@ - + diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/TestPermissionDefinitionProvider.cs b/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/TestPermissionDefinitionProvider.cs index 584c4dd2be..c6de42ac3b 100644 --- a/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/TestPermissionDefinitionProvider.cs +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/TestPermissionDefinitionProvider.cs @@ -16,6 +16,8 @@ namespace Volo.Abp.PermissionManagement myPermission2.AddChild("MyPermission2.ChildPermission1"); testGroup.AddPermission("MyPermission3", multiTenancySide: MultiTenancySides.Host); + + testGroup.AddPermission("MyPermission4", multiTenancySide: MultiTenancySides.Host).WithProviders(UserPermissionValueProvider.ProviderName); } } -} \ No newline at end of file +} diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo.Abp.SettingManagement.Domain.Shared.csproj b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo.Abp.SettingManagement.Domain.Shared.csproj index 64a60969bb..40ec418ff7 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo.Abp.SettingManagement.Domain.Shared.csproj +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo.Abp.SettingManagement.Domain.Shared.csproj @@ -21,9 +21,9 @@ - + - + diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/SettingManagementStore.cs b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/SettingManagementStore.cs index 617d360898..a568184f0e 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/SettingManagementStore.cs +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/SettingManagementStore.cs @@ -5,6 +5,7 @@ using Volo.Abp.Caching; using Volo.Abp.DependencyInjection; using Volo.Abp.Guids; using Volo.Abp.Settings; +using Volo.Abp.Uow; namespace Volo.Abp.SettingManagement { @@ -16,8 +17,8 @@ namespace Volo.Abp.SettingManagement protected IGuidGenerator GuidGenerator { get; } public SettingManagementStore( - ISettingRepository settingRepository, - IGuidGenerator guidGenerator, + ISettingRepository settingRepository, + IGuidGenerator guidGenerator, IDistributedCache cache, ISettingDefinitionManager settingDefinitionManager) { @@ -27,11 +28,13 @@ namespace Volo.Abp.SettingManagement SettingDefinitionManager = settingDefinitionManager; } + [UnitOfWork] public virtual async Task GetOrNullAsync(string name, string providerName, string providerKey) { return (await GetCacheItemAsync(name, providerName, providerKey)).Value; } + [UnitOfWork] public virtual async Task SetAsync(string name, string value, string providerName, string providerKey) { var setting = await SettingRepository.FindAsync(name, providerName, providerKey); @@ -45,6 +48,8 @@ namespace Volo.Abp.SettingManagement setting.Value = value; await SettingRepository.UpdateAsync(setting); } + + await Cache.SetAsync(CalculateCacheKey(name, providerName, providerKey), new SettingCacheItem(setting?.Value), considerUow: true); } public virtual async Task> GetListAsync(string providerName, string providerKey) @@ -53,19 +58,21 @@ namespace Volo.Abp.SettingManagement return settings.Select(s => new SettingValue(s.Name, s.Value)).ToList(); } + [UnitOfWork] public virtual async Task DeleteAsync(string name, string providerName, string providerKey) { var setting = await SettingRepository.FindAsync(name, providerName, providerKey); if (setting != null) { await SettingRepository.DeleteAsync(setting); + await Cache.RemoveAsync(CalculateCacheKey(name, providerName, providerKey), considerUow: true); } } protected virtual async Task GetCacheItemAsync(string name, string providerName, string providerKey) { var cacheKey = CalculateCacheKey(name, providerName, providerKey); - var cacheItem = await Cache.GetAsync(cacheKey); + var cacheItem = await Cache.GetAsync(cacheKey, considerUow: true); if (cacheItem != null) { @@ -75,26 +82,26 @@ namespace Volo.Abp.SettingManagement cacheItem = new SettingCacheItem(null); await SetCacheItemsAsync(providerName, providerKey, name, cacheItem); - + return cacheItem; } private async Task SetCacheItemsAsync( - string providerName, - string providerKey, - string currentName, + string providerName, + string providerKey, + string currentName, SettingCacheItem currentCacheItem) { var settingDefinitions = SettingDefinitionManager.GetAll(); var settingsDictionary = (await SettingRepository.GetListAsync(providerName, providerKey)) .ToDictionary(s => s.Name, s => s.Value); - - var cacheItems = new List>(); - + + var cacheItems = new List>(); + foreach (var settingDefinition in settingDefinitions) { var settingValue = settingsDictionary.GetOrDefault(settingDefinition.Name); - + cacheItems.Add( new KeyValuePair( CalculateCacheKey(settingDefinition.Name, providerName, providerKey), @@ -108,7 +115,7 @@ namespace Volo.Abp.SettingManagement } } - await Cache.SetManyAsync(cacheItems); + await Cache.SetManyAsync(cacheItems, considerUow: true); } protected virtual string CalculateCacheKey(string name, string providerName, string providerKey) diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Volo.Abp.SettingManagement.Web.csproj b/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Volo.Abp.SettingManagement.Web.csproj index f88aac942a..8777b66256 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Volo.Abp.SettingManagement.Web.csproj +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Volo.Abp.SettingManagement.Web.csproj @@ -21,7 +21,7 @@ - + diff --git a/modules/setting-management/test/Volo.Abp.SettingManagement.Tests/Volo/Abp/SettingManagement/SettingManagementStore_Tests.cs b/modules/setting-management/test/Volo.Abp.SettingManagement.Tests/Volo/Abp/SettingManagement/SettingManagementStore_Tests.cs index 753ba15054..7a405faf2d 100644 --- a/modules/setting-management/test/Volo.Abp.SettingManagement.Tests/Volo/Abp/SettingManagement/SettingManagementStore_Tests.cs +++ b/modules/setting-management/test/Volo.Abp.SettingManagement.Tests/Volo/Abp/SettingManagement/SettingManagementStore_Tests.cs @@ -4,6 +4,7 @@ using System.Text; using System.Threading.Tasks; using Shouldly; using Volo.Abp.Settings; +using Volo.Abp.Uow; using Xunit; namespace Volo.Abp.SettingManagement @@ -13,12 +14,14 @@ namespace Volo.Abp.SettingManagement private readonly ISettingManagementStore _settingManagementStore; private readonly ISettingRepository _settingRepository; private readonly SettingTestData _testData; + private readonly IUnitOfWorkManager _unitOfWorkManager; public SettingManagementStore_Tests() { _settingManagementStore = GetRequiredService(); _settingRepository = GetRequiredService(); _testData = GetRequiredService(); + _unitOfWorkManager= GetRequiredService(); } [Fact] @@ -50,6 +53,21 @@ namespace Volo.Abp.SettingManagement (await _settingRepository.FindAsync(_testData.SettingId)).Value.ShouldBe("43"); } + [Fact] + public async Task Set_In_UnitOfWork_Should_Be_Consistent() + { + using (_unitOfWorkManager.Begin()) + { + var value = await _settingManagementStore.GetOrNullAsync("MySetting1", GlobalSettingValueProvider.ProviderName, null); + value.ShouldBe("42"); + + await _settingManagementStore.SetAsync("MySetting1", "43", GlobalSettingValueProvider.ProviderName, null); + + var valueAfterSet = await _settingManagementStore.GetOrNullAsync("MySetting1", GlobalSettingValueProvider.ProviderName, null); + valueAfterSet.ShouldBe("43"); + } + } + [Fact] public async Task DeleteAsync() { @@ -60,5 +78,21 @@ namespace Volo.Abp.SettingManagement (await _settingRepository.FindAsync(_testData.SettingId)).ShouldBeNull(); } + [Fact] + public async Task Delete_In_UnitOfWork_Should_Be_Consistent() + { + using (var uow = _unitOfWorkManager.Begin()) + { + (await _settingManagementStore.GetOrNullAsync("MySetting1", GlobalSettingValueProvider.ProviderName, null)).ShouldNotBeNull(); + + await _settingManagementStore.DeleteAsync("MySetting1", GlobalSettingValueProvider.ProviderName, null); + + await uow.SaveChangesAsync(); + + var value = await _settingManagementStore.GetOrNullAsync("MySetting1", GlobalSettingValueProvider.ProviderName, null); + value.ShouldBeNull(); + } + } + } } diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/AbpTenantManagementApplicationContractsModule.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/AbpTenantManagementApplicationContractsModule.cs index deef6f17cd..ea9bc778dd 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/AbpTenantManagementApplicationContractsModule.cs +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/AbpTenantManagementApplicationContractsModule.cs @@ -1,5 +1,7 @@ using Volo.Abp.Application; using Volo.Abp.Modularity; +using Volo.Abp.ObjectExtending; +using Volo.Abp.ObjectExtending.Modularity; namespace Volo.Abp.TenantManagement { @@ -8,6 +10,16 @@ namespace Volo.Abp.TenantManagement typeof(AbpTenantManagementDomainSharedModule))] public class AbpTenantManagementApplicationContractsModule : AbpModule { - + public override void PostConfigureServices(ServiceConfigurationContext context) + { + ModuleExtensionConfigurationHelper + .ApplyEntityConfigurationToApi( + TenantManagementModuleExtensionConsts.ModuleName, + TenantManagementModuleExtensionConsts.EntityNames.Tenant, + getApiTypes: new[] { typeof(TenantDto) }, + createApiTypes: new[] { typeof(TenantCreateDto) }, + updateApiTypes: new[] { typeof(TenantUpdateDto) } + ); + } } } \ No newline at end of file diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/TenantCreateOrUpdateDtoBase.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/TenantCreateOrUpdateDtoBase.cs index 7dc805ea79..9f047af2fd 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/TenantCreateOrUpdateDtoBase.cs +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/TenantCreateOrUpdateDtoBase.cs @@ -9,5 +9,10 @@ namespace Volo.Abp.TenantManagement [Required] [DynamicStringLength(typeof(TenantConsts), nameof(TenantConsts.MaxNameLength))] public string Name { get; set; } + + public TenantCreateOrUpdateDtoBase() : base(false) + { + + } } } \ No newline at end of file diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo.Abp.TenantManagement.Domain.Shared.csproj b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo.Abp.TenantManagement.Domain.Shared.csproj index 9db53f84e6..57f073b9ad 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo.Abp.TenantManagement.Domain.Shared.csproj +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo.Abp.TenantManagement.Domain.Shared.csproj @@ -19,13 +19,13 @@ - + - + - + diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/ObjectExtending/TenantManagementModuleExtensionConfiguration.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/ObjectExtending/TenantManagementModuleExtensionConfiguration.cs new file mode 100644 index 0000000000..709141a3fb --- /dev/null +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/ObjectExtending/TenantManagementModuleExtensionConfiguration.cs @@ -0,0 +1,17 @@ +using System; +using Volo.Abp.ObjectExtending.Modularity; + +namespace Volo.Abp.ObjectExtending +{ + public class TenantManagementModuleExtensionConfiguration : ModuleExtensionConfiguration + { + public TenantManagementModuleExtensionConfiguration ConfigureTenant( + Action configureAction) + { + return this.ConfigureEntity( + TenantManagementModuleExtensionConsts.EntityNames.Tenant, + configureAction + ); + } + } +} \ No newline at end of file diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/ObjectExtending/TenantManagementModuleExtensionConfigurationDictionaryExtensions.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/ObjectExtending/TenantManagementModuleExtensionConfigurationDictionaryExtensions.cs new file mode 100644 index 0000000000..b04991a250 --- /dev/null +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/ObjectExtending/TenantManagementModuleExtensionConfigurationDictionaryExtensions.cs @@ -0,0 +1,18 @@ +using System; +using Volo.Abp.ObjectExtending.Modularity; + +namespace Volo.Abp.ObjectExtending +{ + public static class TenantManagementModuleExtensionConfigurationDictionaryExtensions + { + public static ModuleExtensionConfigurationDictionary ConfigureTenantManagement( + this ModuleExtensionConfigurationDictionary modules, + Action configureAction) + { + return modules.ConfigureModule( + TenantManagementModuleExtensionConsts.ModuleName, + configureAction + ); + } + } +} \ No newline at end of file diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/ObjectExtending/TenantManagementModuleExtensionConsts.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/ObjectExtending/TenantManagementModuleExtensionConsts.cs new file mode 100644 index 0000000000..e057bdc89d --- /dev/null +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/ObjectExtending/TenantManagementModuleExtensionConsts.cs @@ -0,0 +1,12 @@ +namespace Volo.Abp.ObjectExtending +{ + public class TenantManagementModuleExtensionConsts + { + public const string ModuleName = "TenantManagement"; + + public static class EntityNames + { + public const string Tenant = "Tenant"; + } + } +} \ No newline at end of file diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/AbpTenantManagementDomainModule.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/AbpTenantManagementDomainModule.cs index 1f512c8807..686ca76378 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/AbpTenantManagementDomainModule.cs +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/AbpTenantManagementDomainModule.cs @@ -5,6 +5,8 @@ using Volo.Abp.Domain; using Volo.Abp.Domain.Entities.Events.Distributed; using Volo.Abp.Modularity; using Volo.Abp.MultiTenancy; +using Volo.Abp.ObjectExtending; +using Volo.Abp.ObjectExtending.Modularity; namespace Volo.Abp.TenantManagement { @@ -29,5 +31,14 @@ namespace Volo.Abp.TenantManagement options.EtoMappings.Add(); }); } + + public override void PostConfigureServices(ServiceConfigurationContext context) + { + ModuleExtensionConfigurationHelper.ApplyEntityConfigurationToEntity( + TenantManagementModuleExtensionConsts.ModuleName, + TenantManagementModuleExtensionConsts.EntityNames.Tenant, + typeof(Tenant) + ); + } } } diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/AbpTenantManagementWebAutoMapperProfile.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/AbpTenantManagementWebAutoMapperProfile.cs index d226e9629c..e04a6bdc03 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/AbpTenantManagementWebAutoMapperProfile.cs +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/AbpTenantManagementWebAutoMapperProfile.cs @@ -13,11 +13,11 @@ namespace Volo.Abp.TenantManagement.Web //CreateModal CreateMap() - .Ignore(x => x.ExtraProperties); + .MapExtraProperties(); //EditModal CreateMap() - .Ignore(x => x.ExtraProperties); + .MapExtraProperties(); } } } diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/AbpTenantManagementWebModule.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/AbpTenantManagementWebModule.cs index af6412b465..c186359b1c 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/AbpTenantManagementWebModule.cs +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/AbpTenantManagementWebModule.cs @@ -5,6 +5,8 @@ using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap; using Volo.Abp.AutoMapper; using Volo.Abp.FeatureManagement; using Volo.Abp.Modularity; +using Volo.Abp.ObjectExtending; +using Volo.Abp.ObjectExtending.Modularity; using Volo.Abp.TenantManagement.Localization; using Volo.Abp.TenantManagement.Web.Navigation; using Volo.Abp.UI.Navigation; @@ -57,5 +59,16 @@ namespace Volo.Abp.TenantManagement.Web options.Conventions.AuthorizePage("/TenantManagement/Tenants/ConnectionStrings", TenantManagementPermissions.Tenants.ManageConnectionStrings); }); } + + public override void PostConfigureServices(ServiceConfigurationContext context) + { + ModuleExtensionConfigurationHelper + .ApplyEntityConfigurationToUi( + TenantManagementModuleExtensionConsts.ModuleName, + TenantManagementModuleExtensionConsts.EntityNames.Tenant, + createFormTypes: new[] { typeof(Volo.Abp.TenantManagement.Web.Pages.TenantManagement.Tenants.CreateModalModel.TenantInfoModel) }, + editFormTypes: new[] { typeof(Volo.Abp.TenantManagement.Web.Pages.TenantManagement.Tenants.EditModalModel.TenantInfoModel) } + ); + } } } \ No newline at end of file diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/CreateModal.cshtml b/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/CreateModal.cshtml index 8593679a7c..1728f0ea82 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/CreateModal.cshtml +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/CreateModal.cshtml @@ -1,22 +1,43 @@ @page +@using Microsoft.AspNetCore.Mvc.Localization @using Microsoft.Extensions.Localization @using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal +@using Volo.Abp.Localization +@using Volo.Abp.ObjectExtending @using Volo.Abp.TenantManagement.Localization @using Volo.Abp.TenantManagement.Web.Pages.TenantManagement.Tenants @model CreateModalModel -@inject IStringLocalizer L +@inject IHtmlLocalizer L +@inject IStringLocalizerFactory StringLocalizerFactory @{ Layout = null; }
    - + + + @foreach (var propertyInfo in ObjectExtensionManager.Instance.GetProperties()) + { + if (propertyInfo.Type.IsEnum) + { + + } + else + { + + } + } diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/CreateModal.cshtml.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/CreateModal.cshtml.cs index 0c1e6e0bec..82050430a0 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/CreateModal.cshtml.cs +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/CreateModal.cshtml.cs @@ -1,6 +1,7 @@ using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; +using Volo.Abp.ObjectExtending; using Volo.Abp.Validation; namespace Volo.Abp.TenantManagement.Web.Pages.TenantManagement.Tenants @@ -19,6 +20,7 @@ namespace Volo.Abp.TenantManagement.Web.Pages.TenantManagement.Tenants public virtual Task OnGetAsync() { + Tenant = new TenantInfoModel(); return Task.FromResult(Page()); } @@ -32,7 +34,7 @@ namespace Volo.Abp.TenantManagement.Web.Pages.TenantManagement.Tenants return NoContent(); } - public class TenantInfoModel + public class TenantInfoModel: ExtensibleObject { [Required] [DynamicStringLength(typeof(TenantConsts), nameof(TenantConsts.MaxNameLength))] diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/EditModal.cshtml b/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/EditModal.cshtml index 3f1c9a9d0e..71367676d9 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/EditModal.cshtml +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/EditModal.cshtml @@ -1,10 +1,14 @@ @page @using Microsoft.AspNetCore.Mvc.Localization +@using Microsoft.Extensions.Localization @using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal +@using Volo.Abp.Localization +@using Volo.Abp.ObjectExtending @using Volo.Abp.TenantManagement.Localization @using Volo.Abp.TenantManagement.Web.Pages.TenantManagement.Tenants @model EditModalModel @inject IHtmlLocalizer L +@inject IStringLocalizerFactory StringLocalizerFactory @{ Layout = null; } @@ -14,6 +18,22 @@ + @foreach (var propertyInfo in ObjectExtensionManager.Instance.GetProperties()) + { + if (propertyInfo.Type.IsEnum) + { + + } + else + { + + } + } diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/EditModal.cshtml.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/EditModal.cshtml.cs index 736d0174cf..f605f77190 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/EditModal.cshtml.cs +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/EditModal.cshtml.cs @@ -2,6 +2,7 @@ using System; using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; +using Volo.Abp.ObjectExtending; using Volo.Abp.Validation; namespace Volo.Abp.TenantManagement.Web.Pages.TenantManagement.Tenants @@ -37,7 +38,7 @@ namespace Volo.Abp.TenantManagement.Web.Pages.TenantManagement.Tenants return NoContent(); } - public class TenantInfoModel + public class TenantInfoModel : ExtensibleObject { [HiddenInput] public Guid Id { get; set; } diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/Index.cshtml b/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/Index.cshtml index 22e17f7c86..eabc75c786 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/Index.cshtml +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/Index.cshtml @@ -37,13 +37,6 @@ - - - - @L["Actions"] - @L["TenantName"] - - - + \ No newline at end of file diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/Index.js b/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/Index.js index 65fedcad23..4b19d0bbb7 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/Index.js +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/Index.js @@ -17,10 +17,94 @@ modalClass: 'TenantConnectionStringManagement', }); + var _dataTable = null; + + abp.ui.extensions.entityActions.get('tenantManagement.tenant').addContributor( + function(actionList) { + return actionList.addManyTail( + [ + { + text: l('Edit'), + visible: abp.auth.isGranted( + 'AbpTenantManagement.Tenants.Update' + ), + action: function (data) { + _editModal.open({ + id: data.record.id, + }); + }, + }, + { + text: l('ConnectionStrings'), + visible: abp.auth.isGranted( + 'AbpTenantManagement.Tenants.ManageConnectionStrings' + ), + action: function (data) { + _connectionStringsModal.open({ + id: data.record.id, + }); + }, + }, + { + text: l('Features'), + visible: abp.auth.isGranted( + 'AbpTenantManagement.Tenants.ManageFeatures' + ), + action: function (data) { + _featuresModal.open({ + providerName: 'T', + providerKey: data.record.id, + }); + }, + }, + { + text: l('Delete'), + visible: abp.auth.isGranted( + 'AbpTenantManagement.Tenants.Delete' + ), + confirmMessage: function (data) { + return l( + 'TenantDeletionConfirmationMessage', + data.record.name + ); + }, + action: function (data) { + _tenantAppService + .delete(data.record.id) + .then(function () { + _dataTable.ajax.reload(); + }); + }, + } + ] + ); + } + ); + + abp.ui.extensions.tableColumns.get('tenantManagement.tenant').addContributor( + function (columnList) { + columnList.addManyTail( + [ + { + title: l("Actions"), + rowAction: { + items: abp.ui.extensions.entityActions.get('tenantManagement.tenant').actions.toArray() + } + }, + { + title: l("TenantName"), + data: 'name', + } + ] + ); + }, + 0 //adds as the first contributor + ); + $(function () { var _$wrapper = $('#TenantsWrapper'); - var _dataTable = _$wrapper.find('table').DataTable( + _dataTable = _$wrapper.find('table').DataTable( abp.libs.datatables.normalizeConfiguration({ order: [[1, 'asc']], processing: true, @@ -28,70 +112,7 @@ scrollX: true, serverSide: true, ajax: abp.libs.datatables.createAjax(_tenantAppService.getList), - columnDefs: [ - { - rowAction: { - items: [ - { - text: l('Edit'), - visible: abp.auth.isGranted( - 'AbpTenantManagement.Tenants.Update' - ), - action: function (data) { - _editModal.open({ - id: data.record.id, - }); - }, - }, - { - text: l('ConnectionStrings'), - visible: abp.auth.isGranted( - 'AbpTenantManagement.Tenants.ManageConnectionStrings' - ), - action: function (data) { - _connectionStringsModal.open({ - id: data.record.id, - }); - }, - }, - { - text: l('Features'), - visible: abp.auth.isGranted( - 'AbpTenantManagement.Tenants.ManageFeatures' - ), - action: function (data) { - _featuresModal.open({ - providerName: 'T', - providerKey: data.record.id, - }); - }, - }, - { - text: l('Delete'), - visible: abp.auth.isGranted( - 'AbpTenantManagement.Tenants.Delete' - ), - confirmMessage: function (data) { - return l( - 'TenantDeletionConfirmationMessage', - data.record.name - ); - }, - action: function (data) { - _tenantAppService - .delete(data.record.id) - .then(function () { - _dataTable.ajax.reload(); - }); - }, - }, - ], - }, - }, - { - data: 'name', - }, - ], + columnDefs: abp.ui.extensions.tableColumns.get('tenantManagement.tenant').columns.toArray(), }) ); diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Volo.Abp.TenantManagement.Web.csproj b/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Volo.Abp.TenantManagement.Web.csproj index 4862fba7c2..caf72888dd 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Volo.Abp.TenantManagement.Web.csproj +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Volo.Abp.TenantManagement.Web.csproj @@ -29,9 +29,9 @@ - + - + - + diff --git a/modules/tenant-management/test/Volo.Abp.TenantManagement.EntityFrameworkCore.Tests/Volo.Abp.TenantManagement.EntityFrameworkCore.Tests.csproj b/modules/tenant-management/test/Volo.Abp.TenantManagement.EntityFrameworkCore.Tests/Volo.Abp.TenantManagement.EntityFrameworkCore.Tests.csproj index 385b09f899..445ee992fa 100644 --- a/modules/tenant-management/test/Volo.Abp.TenantManagement.EntityFrameworkCore.Tests/Volo.Abp.TenantManagement.EntityFrameworkCore.Tests.csproj +++ b/modules/tenant-management/test/Volo.Abp.TenantManagement.EntityFrameworkCore.Tests/Volo.Abp.TenantManagement.EntityFrameworkCore.Tests.csproj @@ -19,7 +19,7 @@ - + diff --git a/modules/users/test/Volo.Abp.Users.EntityFrameworkCore.Tests/Volo.Abp.Users.EntityFrameworkCore.Tests.csproj b/modules/users/test/Volo.Abp.Users.EntityFrameworkCore.Tests/Volo.Abp.Users.EntityFrameworkCore.Tests.csproj deleted file mode 100644 index 912daad74d..0000000000 --- a/modules/users/test/Volo.Abp.Users.EntityFrameworkCore.Tests/Volo.Abp.Users.EntityFrameworkCore.Tests.csproj +++ /dev/null @@ -1,25 +0,0 @@ - - - - netcoreapp2.2 - Volo.Abp.Users.EntityFrameworkCore.Tests - Volo.Abp.Users.EntityFrameworkCore.Tests - latest - true - false - false - false - - - - - - - - - - - - - - diff --git a/modules/users/test/Volo.Abp.Users.EntityFrameworkCore.Tests/Volo.Abp.Users.EntityFrameworkCore.Tests.csproj.DotSettings b/modules/users/test/Volo.Abp.Users.EntityFrameworkCore.Tests/Volo.Abp.Users.EntityFrameworkCore.Tests.csproj.DotSettings deleted file mode 100644 index 58ad6c8854..0000000000 --- a/modules/users/test/Volo.Abp.Users.EntityFrameworkCore.Tests/Volo.Abp.Users.EntityFrameworkCore.Tests.csproj.DotSettings +++ /dev/null @@ -1,2 +0,0 @@ - - CSharp71 \ No newline at end of file diff --git a/modules/users/test/Volo.Abp.Users.EntityFrameworkCore.Tests/Volo/Abp/Users/EntityFrameworkCore/AbpUserRepository_Tests.cs b/modules/users/test/Volo.Abp.Users.EntityFrameworkCore.Tests/Volo/Abp/Users/EntityFrameworkCore/AbpUserRepository_Tests.cs deleted file mode 100644 index 7f92ae7483..0000000000 --- a/modules/users/test/Volo.Abp.Users.EntityFrameworkCore.Tests/Volo/Abp/Users/EntityFrameworkCore/AbpUserRepository_Tests.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Volo.Abp.Users.EntityFrameworkCore -{ - public class AbpUserRepository_Tests : AbpUserRepository_Tests - { - - } -} diff --git a/modules/users/test/Volo.Abp.Users.EntityFrameworkCore.Tests/Volo/Abp/Users/EntityFrameworkCore/AbpUsersEntityFrameworkCoreTestModule.cs b/modules/users/test/Volo.Abp.Users.EntityFrameworkCore.Tests/Volo/Abp/Users/EntityFrameworkCore/AbpUsersEntityFrameworkCoreTestModule.cs deleted file mode 100644 index 7a19077afe..0000000000 --- a/modules/users/test/Volo.Abp.Users.EntityFrameworkCore.Tests/Volo/Abp/Users/EntityFrameworkCore/AbpUsersEntityFrameworkCoreTestModule.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; -using Volo.Abp.EntityFrameworkCore; -using Volo.Abp.Modularity; -using Volo.Abp.Uow; - -namespace Volo.Abp.Users.EntityFrameworkCore -{ - [DependsOn( - typeof(AbpUsersTestsSharedModule), - typeof(AbpUsersEntityFrameworkCoreModule) - )] - public class AbpUsersEntityFrameworkCoreTestModule : AbpModule - { - public override void ConfigureServices(IServiceCollection services) - { - services.AddEntityFrameworkInMemoryDatabase(); - - var databaseName = Guid.NewGuid().ToString(); - - services.Configure(options => - { - options.Configure(context => - { - context.DbContextOptions.UseInMemoryDatabase(databaseName); - }); - }); - - services.Configure(options => - { - options.TransactionBehavior = UnitOfWorkTransactionBehavior.Disabled; //EF in-memory database does not support transactions - }); - - services.AddAssemblyOf(); - } - } -} diff --git a/modules/users/test/Volo.Abp.Users.EntityFrameworkCore.Tests/Volo/Abp/Users/EntityFrameworkCore/ExternalUserLookupService_Tests.cs b/modules/users/test/Volo.Abp.Users.EntityFrameworkCore.Tests/Volo/Abp/Users/EntityFrameworkCore/ExternalUserLookupService_Tests.cs deleted file mode 100644 index 26fd633808..0000000000 --- a/modules/users/test/Volo.Abp.Users.EntityFrameworkCore.Tests/Volo/Abp/Users/EntityFrameworkCore/ExternalUserLookupService_Tests.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Volo.Abp.Users.EntityFrameworkCore -{ - public class ExternalUserLookupService_Tests : ExternalUserLookupService_Tests - { - - } -} \ No newline at end of file diff --git a/modules/users/test/Volo.Abp.Users.EntityFrameworkCore.Tests/Volo/Abp/Users/EntityFrameworkCore/LocalUserLookupService_Tests.cs b/modules/users/test/Volo.Abp.Users.EntityFrameworkCore.Tests/Volo/Abp/Users/EntityFrameworkCore/LocalUserLookupService_Tests.cs deleted file mode 100644 index 089fd35dbe..0000000000 --- a/modules/users/test/Volo.Abp.Users.EntityFrameworkCore.Tests/Volo/Abp/Users/EntityFrameworkCore/LocalUserLookupService_Tests.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Volo.Abp.Users.EntityFrameworkCore -{ - public class LocalUserLookupService_Tests : LocalUserLookupService_Tests - { - - } -} \ No newline at end of file diff --git a/modules/users/test/Volo.Abp.Users.MongoDB.Tests/Volo.Abp.Users.MongoDB.Tests.csproj b/modules/users/test/Volo.Abp.Users.MongoDB.Tests/Volo.Abp.Users.MongoDB.Tests.csproj deleted file mode 100644 index 06b92ffd34..0000000000 --- a/modules/users/test/Volo.Abp.Users.MongoDB.Tests/Volo.Abp.Users.MongoDB.Tests.csproj +++ /dev/null @@ -1,25 +0,0 @@ - - - - netcoreapp2.2 - Volo.Abp.Users.MongoDB.Tests - Volo.Abp.Users.MongoDB.Tests - latest - true - false - false - false - - - - - - - - - - - - - - diff --git a/modules/users/test/Volo.Abp.Users.MongoDB.Tests/Volo/Abp/Users/MongoDB/AbpUserRepository_Tests.cs b/modules/users/test/Volo.Abp.Users.MongoDB.Tests/Volo/Abp/Users/MongoDB/AbpUserRepository_Tests.cs deleted file mode 100644 index a54790410e..0000000000 --- a/modules/users/test/Volo.Abp.Users.MongoDB.Tests/Volo/Abp/Users/MongoDB/AbpUserRepository_Tests.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Volo.Abp.Users.MongoDB -{ - [Collection(MongoTestCollection.Name)] - public class AbpUserRepository_Tests : AbpUserRepository_Tests - { - - } -} diff --git a/modules/users/test/Volo.Abp.Users.MongoDB.Tests/Volo/Abp/Users/MongoDB/AbpUsersMongoDbTestModule.cs b/modules/users/test/Volo.Abp.Users.MongoDB.Tests/Volo/Abp/Users/MongoDB/AbpUsersMongoDbTestModule.cs deleted file mode 100644 index 560d6287d7..0000000000 --- a/modules/users/test/Volo.Abp.Users.MongoDB.Tests/Volo/Abp/Users/MongoDB/AbpUsersMongoDbTestModule.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using Microsoft.Extensions.DependencyInjection; -using Mongo2Go; -using Volo.Abp.Data; -using Volo.Abp.Modularity; - -namespace Volo.Abp.Users.MongoDB -{ - [DependsOn( - typeof(AbpUsersMongoDbModule), - typeof(AbpUsersTestsSharedModule) - )] - public class AbpUsersMongoDbTestModule : AbpModule - { - private static readonly MongoDbRunner MongoDbRunner = MongoDbRunner.Start(); - - public override void ConfigureServices(IServiceCollection services) - { - var connectionString = MongoDbFixture.ConnectionString.EnsureEndsWith('/') + - "Db_" + - Guid.NewGuid().ToString("N"); - - Configure(options => - { - options.ConnectionStrings.Default = connectionString; - }); - - services.AddAssemblyOf(); - } - } -} diff --git a/modules/users/test/Volo.Abp.Users.MongoDB.Tests/Volo/Abp/Users/MongoDB/ExternalUserLookupService_Tests.cs b/modules/users/test/Volo.Abp.Users.MongoDB.Tests/Volo/Abp/Users/MongoDB/ExternalUserLookupService_Tests.cs deleted file mode 100644 index 764cadedf4..0000000000 --- a/modules/users/test/Volo.Abp.Users.MongoDB.Tests/Volo/Abp/Users/MongoDB/ExternalUserLookupService_Tests.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Volo.Abp.Users.MongoDB -{ - [Collection(MongoTestCollection.Name)] - public class ExternalUserLookupService_Tests : ExternalUserLookupService_Tests - { - - } -} diff --git a/modules/users/test/Volo.Abp.Users.MongoDB.Tests/Volo/Abp/Users/MongoDB/LocalUserLookupService_Tests.cs b/modules/users/test/Volo.Abp.Users.MongoDB.Tests/Volo/Abp/Users/MongoDB/LocalUserLookupService_Tests.cs deleted file mode 100644 index b52af21d3e..0000000000 --- a/modules/users/test/Volo.Abp.Users.MongoDB.Tests/Volo/Abp/Users/MongoDB/LocalUserLookupService_Tests.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Volo.Abp.Users.MongoDB -{ - [Collection(MongoTestCollection.Name)] - public class LocalUserLookupService_Tests : LocalUserLookupService_Tests - { - - } -} diff --git a/modules/users/test/Volo.Abp.Users.MongoDB.Tests/Volo/Abp/Users/MongoDB/MongoDbFixture.cs b/modules/users/test/Volo.Abp.Users.MongoDB.Tests/Volo/Abp/Users/MongoDB/MongoDbFixture.cs deleted file mode 100644 index 4f8c6eea2f..0000000000 --- a/modules/users/test/Volo.Abp.Users.MongoDB.Tests/Volo/Abp/Users/MongoDB/MongoDbFixture.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using Mongo2Go; - -namespace Volo.Abp.Users.MongoDB -{ - public class MongoDbFixture : IDisposable - { - private static readonly MongoDbRunner MongoDbRunner; - public static readonly string ConnectionString; - - static MongoDbFixture() - { - MongoDbRunner = MongoDbRunner.Start(); - ConnectionString = MongoDbRunner.ConnectionString; - } - - public void Dispose() - { - MongoDbRunner?.Dispose(); - } - } -} diff --git a/modules/users/test/Volo.Abp.Users.MongoDB.Tests/Volo/Abp/Users/MongoDB/MongoTestCollection.cs b/modules/users/test/Volo.Abp.Users.MongoDB.Tests/Volo/Abp/Users/MongoDB/MongoTestCollection.cs deleted file mode 100644 index d8b6a718d8..0000000000 --- a/modules/users/test/Volo.Abp.Users.MongoDB.Tests/Volo/Abp/Users/MongoDB/MongoTestCollection.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Xunit; - -namespace Volo.Abp.Users.MongoDB -{ - [CollectionDefinition(Name)] - public class MongoTestCollection : ICollectionFixture - { - public const string Name = "MongoDB Collection"; - } -} diff --git a/modules/users/test/Volo.Abp.Users.Tests.Shared/Volo.Abp.Users.Tests.Shared.csproj b/modules/users/test/Volo.Abp.Users.Tests.Shared/Volo.Abp.Users.Tests.Shared.csproj deleted file mode 100644 index ececb4eddd..0000000000 --- a/modules/users/test/Volo.Abp.Users.Tests.Shared/Volo.Abp.Users.Tests.Shared.csproj +++ /dev/null @@ -1,31 +0,0 @@ - - - - netcoreapp2.2 - Volo.Abp.Users.Tests.Shared - Volo.Abp.Users.Tests.Shared - latest - true - false - false - false - - - - - - - - - - - - - - - - - - - - diff --git a/modules/users/test/Volo.Abp.Users.Tests.Shared/Volo.Abp.Users.Tests.Shared.csproj.DotSettings b/modules/users/test/Volo.Abp.Users.Tests.Shared/Volo.Abp.Users.Tests.Shared.csproj.DotSettings deleted file mode 100644 index 58ad6c8854..0000000000 --- a/modules/users/test/Volo.Abp.Users.Tests.Shared/Volo.Abp.Users.Tests.Shared.csproj.DotSettings +++ /dev/null @@ -1,2 +0,0 @@ - - CSharp71 \ No newline at end of file diff --git a/modules/users/test/Volo.Abp.Users.Tests.Shared/Volo/Abp/Users/AbpUserRepository_Tests.cs b/modules/users/test/Volo.Abp.Users.Tests.Shared/Volo/Abp/Users/AbpUserRepository_Tests.cs deleted file mode 100644 index 40d8de9572..0000000000 --- a/modules/users/test/Volo.Abp.Users.Tests.Shared/Volo/Abp/Users/AbpUserRepository_Tests.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using Shouldly; -using Volo.Abp.Modularity; -using Xunit; - -namespace Volo.Abp.Users -{ - public abstract class AbpUserRepository_Tests : AbpUsersTestBase - where TStartupModule : IAbpModule - { - private readonly AbpUsersLocalTestData _localTestData; - private readonly IAbpUserRepository _userRepository; - - protected AbpUserRepository_Tests() - { - _userRepository = GetRequiredService(); - _localTestData = GetRequiredService(); - } - - [Fact] - public async Task FindAsync() - { - var john = await _userRepository.FindAsync(_localTestData.John.Id); - john.ShouldNotBeNull(); - john.UserName.ShouldBe(_localTestData.John.UserName); - - //Undefined user - (await _userRepository.FindAsync(Guid.NewGuid())).ShouldBeNull(); - } - - [Fact] - public async Task FindByUserNameAsync() - { - var john = await _userRepository.FindByUserNameAsync(_localTestData.John.UserName); - john.ShouldNotBeNull(); - john.Id.ShouldBe(_localTestData.John.Id); - - //Undefined user - (await _userRepository.FindByUserNameAsync("undefined-user")).ShouldBeNull(); - } - - [Fact] - public async Task GetListAsync() - { - (await _userRepository.GetListAsync(new Guid[0])).Any().ShouldBeFalse(); - (await _userRepository.GetListAsync(new[] { _localTestData.John.Id })).Count.ShouldBe(1); - (await _userRepository.GetListAsync(new[] { _localTestData.John.Id, _localTestData.David.Id })).Count.ShouldBe(2); - (await _userRepository.GetListAsync(new[] { _localTestData.John.Id, _localTestData.David.Id, Guid.NewGuid() })).Count.ShouldBe(2); - } - } -} diff --git a/modules/users/test/Volo.Abp.Users.Tests.Shared/Volo/Abp/Users/AbpUsersExternalTestData.cs b/modules/users/test/Volo.Abp.Users.Tests.Shared/Volo/Abp/Users/AbpUsersExternalTestData.cs deleted file mode 100644 index dba3c573de..0000000000 --- a/modules/users/test/Volo.Abp.Users.Tests.Shared/Volo/Abp/Users/AbpUsersExternalTestData.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Collections.Generic; -using Volo.Abp.DependencyInjection; - -namespace Volo.Abp.Users -{ - public class AbpUsersExternalTestData : ISingletonDependency - { - public IAbpUserData David { get; } - public IAbpUserData Neo { get; } - - public AbpUsersExternalTestData(AbpUsersLocalTestData localTestData) - { - Neo = new AbpUserData(Guid.NewGuid(), "neo"); - David = localTestData.David.ToAbpUserData(); - } - - public List GetAllUsers() - { - return new List - { - David, - Neo - }; - } - } -} \ No newline at end of file diff --git a/modules/users/test/Volo.Abp.Users.Tests.Shared/Volo/Abp/Users/AbpUsersLocalTestData.cs b/modules/users/test/Volo.Abp.Users.Tests.Shared/Volo/Abp/Users/AbpUsersLocalTestData.cs deleted file mode 100644 index 2382a3f93e..0000000000 --- a/modules/users/test/Volo.Abp.Users.Tests.Shared/Volo/Abp/Users/AbpUsersLocalTestData.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using Volo.Abp.DependencyInjection; - -namespace Volo.Abp.Users -{ - public class AbpUsersLocalTestData : ISingletonDependency - { - public AbpUser John { get; } - public AbpUser David { get; } - - public AbpUsersLocalTestData() - { - John = new AbpUser(Guid.NewGuid(), "john"); - David = new AbpUser(Guid.NewGuid(), "david", "david@abp.io"); - } - } -} \ No newline at end of file diff --git a/modules/users/test/Volo.Abp.Users.Tests.Shared/Volo/Abp/Users/AbpUsersTestBase.cs b/modules/users/test/Volo.Abp.Users.Tests.Shared/Volo/Abp/Users/AbpUsersTestBase.cs deleted file mode 100644 index e36f485a70..0000000000 --- a/modules/users/test/Volo.Abp.Users.Tests.Shared/Volo/Abp/Users/AbpUsersTestBase.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Volo.Abp.Modularity; -using Volo.Abp.Testing; - -namespace Volo.Abp.Users -{ - public abstract class AbpUsersTestBase : AbpIntegratedTest - where TStartupModule : IAbpModule - { - protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) - { - options.UseAutofac(); - } - } -} diff --git a/modules/users/test/Volo.Abp.Users.Tests.Shared/Volo/Abp/Users/AbpUsersTestDataBuilder.cs b/modules/users/test/Volo.Abp.Users.Tests.Shared/Volo/Abp/Users/AbpUsersTestDataBuilder.cs deleted file mode 100644 index a3602491be..0000000000 --- a/modules/users/test/Volo.Abp.Users.Tests.Shared/Volo/Abp/Users/AbpUsersTestDataBuilder.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Volo.Abp.DependencyInjection; - -namespace Volo.Abp.Users -{ - public class AbpUsersTestDataBuilder : ITransientDependency - { - private readonly IAbpUserRepository _userRepository; - private readonly AbpUsersLocalTestData _localTestData; - - public AbpUsersTestDataBuilder( - IAbpUserRepository userRepository, - AbpUsersLocalTestData localTestData) - { - _userRepository = userRepository; - _localTestData = localTestData; - } - - public void Build() - { - AddUsers(); - } - - private void AddUsers() - { - _userRepository.Insert(_localTestData.John); - _userRepository.Insert(_localTestData.David); - } - } -} \ No newline at end of file diff --git a/modules/users/test/Volo.Abp.Users.Tests.Shared/Volo/Abp/Users/AbpUsersTestsSharedModule.cs b/modules/users/test/Volo.Abp.Users.Tests.Shared/Volo/Abp/Users/AbpUsersTestsSharedModule.cs deleted file mode 100644 index 50da79183d..0000000000 --- a/modules/users/test/Volo.Abp.Users.Tests.Shared/Volo/Abp/Users/AbpUsersTestsSharedModule.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Volo.Abp.Autofac; -using Volo.Abp.Modularity; - -namespace Volo.Abp.Users -{ - [DependsOn( - typeof(AbpUsersDomainModule), - typeof(AbpTestBaseModule), - typeof(AbpAutofacModule) - )] - public class AbpUsersTestsSharedModule : AbpModule - { - public override void ConfigureServices(IServiceCollection services) - { - services.AddAssemblyOf(); - } - - public override void OnApplicationInitialization(ApplicationInitializationContext context) - { - SeedTestData(context); - } - - private static void SeedTestData(ApplicationInitializationContext context) - { - using (var scope = context.ServiceProvider.CreateScope()) - { - scope.ServiceProvider - .GetRequiredService() - .Build(); - } - } - } -} diff --git a/modules/users/test/Volo.Abp.Users.Tests.Shared/Volo/Abp/Users/ExternalUserLookupService_Tests.cs b/modules/users/test/Volo.Abp.Users.Tests.Shared/Volo/Abp/Users/ExternalUserLookupService_Tests.cs deleted file mode 100644 index 8916607ea7..0000000000 --- a/modules/users/test/Volo.Abp.Users.Tests.Shared/Volo/Abp/Users/ExternalUserLookupService_Tests.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; -using Shouldly; -using Volo.Abp.Modularity; -using Xunit; - -namespace Volo.Abp.Users -{ - public abstract class ExternalUserLookupService_Tests : AbpUsersTestBase - where TStartupModule : IAbpModule - { - private readonly IAbpUserLookupService _lookupService; - private readonly AbpUsersLocalTestData _localTestData; - private readonly AbpUsersExternalTestData _externalTestData; - - protected ExternalUserLookupService_Tests() - { - _lookupService = GetRequiredService(); - _localTestData = GetRequiredService(); - _externalTestData = GetRequiredService(); - } - - protected override void AfterAddApplication(IServiceCollection services) - { - services.AddTransient(); - } - - [Fact] - public async Task FindByUserNameAsync() - { - (await GetRequiredService().FindByUserNameAsync(_localTestData.John.UserName)).ShouldNotBeNull(); - - (await _lookupService.FindByUserNameAsync("undefined-user")).ShouldBeNull(); - (await _lookupService.FindByUserNameAsync(_localTestData.John.UserName)).ShouldBeNull(); //Because it's not available in the external provider. And this will delete the user! - (await _lookupService.FindByUserNameAsync(_localTestData.David.UserName)).ShouldNotBeNull(); - (await _lookupService.FindByUserNameAsync(_externalTestData.Neo.UserName)).ShouldNotBeNull(); - - (await GetRequiredService().FindByUserNameAsync(_localTestData.John.UserName)).ShouldBeNull(); - } - - [Fact] - public async Task FindByIdAsync() - { - (await GetRequiredService().FindAsync(_localTestData.John.Id)).ShouldNotBeNull(); - - (await _lookupService.FindByIdAsync(Guid.NewGuid())).ShouldBeNull(); - (await _lookupService.FindByIdAsync(_localTestData.John.Id)).ShouldBeNull(); //Because it's not available in the external provider. And this will delete the user! - (await _lookupService.FindByIdAsync(_localTestData.David.Id)).ShouldNotBeNull(); - (await _lookupService.FindByIdAsync(_externalTestData.Neo.Id)).ShouldNotBeNull(); - - (await GetRequiredService().FindAsync(_localTestData.John.Id)).ShouldBeNull(); - } - - public class TestExternalAbpUserLookupServiceProvider : IExternalAbpUserLookupServiceProvider - { - private readonly AbpUsersExternalTestData _externalTestData; - - public TestExternalAbpUserLookupServiceProvider(AbpUsersExternalTestData externalTestData) - { - _externalTestData = externalTestData; - } - - public Task FindByIdAsync(Guid id, CancellationToken cancellationToken = default) - { - return Task.FromResult(_externalTestData.GetAllUsers().FirstOrDefault(u => u.Id == id)); - } - - public Task FindByUserNameAsync(string userName, CancellationToken cancellationToken = default) - { - return Task.FromResult(_externalTestData.GetAllUsers().FirstOrDefault(u => u.UserName == userName)); - } - } - } -} \ No newline at end of file diff --git a/modules/users/test/Volo.Abp.Users.Tests.Shared/Volo/Abp/Users/LocalUserLookupService_Tests.cs b/modules/users/test/Volo.Abp.Users.Tests.Shared/Volo/Abp/Users/LocalUserLookupService_Tests.cs deleted file mode 100644 index 9e128f14cc..0000000000 --- a/modules/users/test/Volo.Abp.Users.Tests.Shared/Volo/Abp/Users/LocalUserLookupService_Tests.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Threading.Tasks; -using Shouldly; -using Volo.Abp.Modularity; -using Xunit; - -namespace Volo.Abp.Users -{ - public abstract class LocalUserLookupService_Tests : AbpUsersTestBase - where TStartupModule : IAbpModule - { - private readonly IAbpUserLookupService _lookupService; - private readonly AbpUsersLocalTestData _localTestData; - - protected LocalUserLookupService_Tests() - { - _lookupService = GetRequiredService(); - _localTestData = GetRequiredService(); - } - - [Fact] - public async Task FindByUserNameAsync() - { - (await _lookupService.FindByUserNameAsync(_localTestData.John.UserName)).ShouldNotBeNull(); - (await _lookupService.FindByUserNameAsync(_localTestData.David.UserName)).ShouldNotBeNull(); - (await _lookupService.FindByUserNameAsync("undefined-user")).ShouldBeNull(); - } - } -} diff --git a/npm/lerna.json b/npm/lerna.json index d50ae1cbff..d579e21dcb 100644 --- a/npm/lerna.json +++ b/npm/lerna.json @@ -1,5 +1,5 @@ { - "version": "3.0.4", + "version": "3.0.5", "packages": [ "packs/*" ], diff --git a/npm/ng-packs/apps/dev-app/src/environments/environment.prod.ts b/npm/ng-packs/apps/dev-app/src/environments/environment.prod.ts index ae0e6aba2c..21e1302773 100644 --- a/npm/ng-packs/apps/dev-app/src/environments/environment.prod.ts +++ b/npm/ng-packs/apps/dev-app/src/environments/environment.prod.ts @@ -1,7 +1,10 @@ +import { Config } from '@abp/ng.core'; + export const environment = { production: true, hmr: false, application: { + baseUrl: 'http://localhost:4200/', name: 'MyProjectName', logoUrl: '', }, @@ -10,7 +13,6 @@ export const environment = { clientId: 'MyProjectName_App', dummyClientSecret: '1q2w3e*', scope: 'MyProjectName', - showDebugInformation: true, oidc: false, requireHttps: true, }, @@ -19,4 +21,4 @@ export const environment = { url: 'https://localhost:44305', }, }, -}; +} as Config.Environment; diff --git a/npm/ng-packs/apps/dev-app/src/environments/environment.ts b/npm/ng-packs/apps/dev-app/src/environments/environment.ts index e678b55a74..8cae243d46 100644 --- a/npm/ng-packs/apps/dev-app/src/environments/environment.ts +++ b/npm/ng-packs/apps/dev-app/src/environments/environment.ts @@ -1,7 +1,10 @@ +import { Config } from '@abp/ng.core'; + export const environment = { production: false, hmr: false, application: { + baseUrl: 'http://localhost:4200/', name: 'MyProjectName', logoUrl: '', }, @@ -10,7 +13,6 @@ export const environment = { clientId: 'MyProjectName_App', dummyClientSecret: '1q2w3e*', scope: 'MyProjectName', - showDebugInformation: true, oidc: false, requireHttps: true, }, @@ -19,4 +21,4 @@ export const environment = { url: 'https://localhost:44305', }, }, -}; +} as Config.Environment; diff --git a/npm/ng-packs/lerna.version.json b/npm/ng-packs/lerna.version.json index fdb5f39cbf..20edebc30f 100644 --- a/npm/ng-packs/lerna.version.json +++ b/npm/ng-packs/lerna.version.json @@ -1,5 +1,5 @@ { - "version": "3.0.4", + "version": "3.0.5", "packages": [ "packages/*" ], diff --git a/npm/ng-packs/package.json b/npm/ng-packs/package.json index 0a159562c0..fa58409270 100644 --- a/npm/ng-packs/package.json +++ b/npm/ng-packs/package.json @@ -16,31 +16,31 @@ "scripts:build": "cd scripts && yarn && yarn build", "prepare:workspace": "yarn scripts:build --noInstall", "ci": "yarn ng lint && yarn prepare:workspace && yarn ci:test && yarn ci:build", - "ci:test": "ng test --coverage=false", + "ci:test": "ng test --coverage=false --silent", "ci:build": "cd scripts && yarn build:prod", "lerna": "lerna", "compile:ivy": "yarn ngcc --properties es2015 browser module main --first-only --create-ivy-entry-points --tsconfig './tsconfig.prod.json' --source node_modules --async false", "postinstall": "npm run compile:ivy" }, "devDependencies": { - "@abp/ng.account": "~3.0.4", - "@abp/ng.core": "~3.0.4", - "@abp/ng.feature-management": "~3.0.4", - "@abp/ng.identity": "~3.0.4", - "@abp/ng.permission-management": "~3.0.4", - "@abp/ng.setting-management": "~3.0.4", - "@abp/ng.tenant-management": "~3.0.4", - "@abp/ng.theme.basic": "~3.0.4", - "@abp/ng.theme.shared": "~3.0.4", - "@abp/utils": "^3.0.4", + "@abp/ng.account": "~3.0.5", + "@abp/ng.core": "~3.0.5", + "@abp/ng.feature-management": "~3.0.5", + "@abp/ng.identity": "~3.0.5", + "@abp/ng.permission-management": "~3.0.5", + "@abp/ng.setting-management": "~3.0.5", + "@abp/ng.tenant-management": "~3.0.5", + "@abp/ng.theme.basic": "~3.0.5", + "@abp/ng.theme.shared": "~3.0.5", + "@abp/utils": "^3.0.5", "@angular-builders/jest": "^9.0.1", "@angular-devkit/build-angular": "~0.1000.0", "@angular-devkit/build-ng-packagr": "~0.1000.0", "@angular/animations": "~10.0.0", "@angular/cli": "~10.0.0", "@angular/common": "~10.0.0", - "@angular/compiler": "~10.0.0", - "@angular/compiler-cli": "~10.0.0", + "@angular/compiler": "10.0.0", + "@angular/compiler-cli": "10.0.0", "@angular/core": "~10.0.0", "@angular/forms": "~10.0.0", "@angular/language-service": "~10.0.0", @@ -51,7 +51,7 @@ "@fortawesome/fontawesome-free": "^5.13.0", "@ng-bootstrap/ng-bootstrap": "^6.1.0", "@ngneat/spectator": "^5.11.0", - "@ngx-validate/core": "^0.0.9", + "@ngx-validate/core": "^0.0.10", "@ngxs/devtools-plugin": "^3.6.2", "@ngxs/logger-plugin": "^3.6.2", "@ngxs/router-plugin": "^3.6.2", @@ -92,12 +92,13 @@ "dependencies": { "tslib": "^2.0.0" }, + "resolutions": { + "@angular/compiler": "10.0.0", + "@angular/compiler-cli": "10.0.0" + }, "config": { "commitizen": { "path": "cz-conventional-changelog" } - }, - "resolutions": { - "@ngx-validate/core": "^0.0.8" } } diff --git a/npm/ng-packs/packages/account/package.json b/npm/ng-packs/packages/account/package.json index 7cde8b409a..3d257a3a89 100644 --- a/npm/ng-packs/packages/account/package.json +++ b/npm/ng-packs/packages/account/package.json @@ -1,13 +1,13 @@ { "name": "@abp/ng.account", - "version": "3.0.4", + "version": "3.0.5", "homepage": "https://abp.io", "repository": { "type": "git", "url": "https://github.com/abpframework/abp.git" }, "dependencies": { - "@abp/ng.theme.shared": "~3.0.4", + "@abp/ng.theme.shared": "~3.0.5", "tslib": "^2.0.0" }, "publishConfig": { diff --git a/npm/ng-packs/packages/account/src/lib/components/auth-wrapper/auth-wrapper.component.html b/npm/ng-packs/packages/account/src/lib/components/auth-wrapper/auth-wrapper.component.html index 3377b74a88..f57643f7b2 100644 --- a/npm/ng-packs/packages/account/src/lib/components/auth-wrapper/auth-wrapper.component.html +++ b/npm/ng-packs/packages/account/src/lib/components/auth-wrapper/auth-wrapper.component.html @@ -1,6 +1,6 @@
    - + diff --git a/npm/ng-packs/packages/account/src/lib/components/auth-wrapper/auth-wrapper.component.ts b/npm/ng-packs/packages/account/src/lib/components/auth-wrapper/auth-wrapper.component.ts index 23130318b8..e7b398cc3d 100644 --- a/npm/ng-packs/packages/account/src/lib/components/auth-wrapper/auth-wrapper.component.ts +++ b/npm/ng-packs/packages/account/src/lib/components/auth-wrapper/auth-wrapper.component.ts @@ -1,21 +1,18 @@ -import { ConfigState, takeUntilDestroy } from '@abp/ng.core'; -import { Component, Input, OnDestroy, OnInit, TemplateRef } from '@angular/core'; +import { ConfigState, SubscriptionService, MultiTenancyService } from '@abp/ng.core'; +import { Component, Input, OnInit, TemplateRef } from '@angular/core'; import { Select, Store } from '@ngxs/store'; import { Observable } from 'rxjs'; -import { Account } from '../../models/account'; import { eAccountComponents } from '../../enums/components'; +import { Account } from '../../models/account'; @Component({ selector: 'abp-auth-wrapper', templateUrl: './auth-wrapper.component.html', exportAs: 'abpAuthWrapper', + providers: [SubscriptionService], }) export class AuthWrapperComponent - implements - Account.AuthWrapperComponentInputs, - Account.AuthWrapperComponentOutputs, - OnInit, - OnDestroy { + implements Account.AuthWrapperComponentInputs, Account.AuthWrapperComponentOutputs, OnInit { @Input() readonly mainContentRef: TemplateRef; @@ -29,18 +26,20 @@ export class AuthWrapperComponent tenantBoxKey = eAccountComponents.TenantBox; - constructor(private store: Store) {} + constructor( + public readonly multiTenancy: MultiTenancyService, + private store: Store, + private subscription: SubscriptionService, + ) {} ngOnInit() { - this.store - .select(ConfigState.getSetting('Abp.Account.EnableLocalLogin')) - .pipe(takeUntilDestroy(this)) - .subscribe(value => { + this.subscription.addOne( + this.store.select(ConfigState.getSetting('Abp.Account.EnableLocalLogin')), + value => { if (value) { this.enableLocalLogin = value.toLowerCase() !== 'false'; } - }); + }, + ); } - - ngOnDestroy() {} } diff --git a/npm/ng-packs/packages/account/src/lib/tests/account.service.spec.ts b/npm/ng-packs/packages/account/src/lib/tests/account.service.spec.ts index aa7d7f90a4..b538713bb7 100644 --- a/npm/ng-packs/packages/account/src/lib/tests/account.service.spec.ts +++ b/npm/ng-packs/packages/account/src/lib/tests/account.service.spec.ts @@ -15,7 +15,7 @@ describe('AccountService', () => { beforeEach(() => (spectator = createHttp())); it('should send a GET to find tenant', () => { - spectator.get(Store).selectSnapshot.andReturn('https://abp.io'); + spectator.inject(Store).selectSnapshot.andReturn('https://abp.io'); spectator.service.findTenant('test').subscribe(); spectator.expectOne( 'https://abp.io/api/abp/multi-tenancy/tenants/by-name/test', @@ -30,7 +30,7 @@ describe('AccountService', () => { password: 'test1234', appName: 'Angular', } as RegisterRequest; - spectator.get(Store).selectSnapshot.andReturn('https://abp.io'); + spectator.inject(Store).selectSnapshot.andReturn('https://abp.io'); spectator.service.register(mock).subscribe(); const req = spectator.expectOne('https://abp.io/api/account/register', HttpMethod.POST); expect(req.request.body).toEqual(mock); diff --git a/npm/ng-packs/packages/components/tree/src/lib/components/tree.component.html b/npm/ng-packs/packages/components/tree/src/lib/components/tree.component.html index 47c1355077..b7c777a449 100644 --- a/npm/ng-packs/packages/components/tree/src/lib/components/tree.component.html +++ b/npm/ng-packs/packages/components/tree/src/lib/components/tree.component.html @@ -1,4 +1,6 @@
    ; @Output() readonly checkedKeysChange = new EventEmitter(); @Output() readonly expandedKeysChange = new EventEmitter(); @Output() readonly selectedNodeChange = new EventEmitter(); + @Output() readonly dropOver = new EventEmitter(); + @Input() draggable: boolean; @Input() checkable: boolean; @Input() checkStrictly: boolean; @Input() checkedKeys = []; @@ -29,6 +37,10 @@ export class TreeComponent { @Input() expandedKeys: string[] = []; @Input() selectedNode: any; @Input() isNodeSelected = node => this.selectedNode?.id === node.key; + @Input() beforeDrop = (event: NzFormatBeforeDropEvent) => { + this.dropPosition = event.pos; + return of(false); + }; onSelectedNodeChange(node) { this.selectedNode = node.origin.entity; @@ -44,4 +56,12 @@ export class TreeComponent { this.expandedKeys = [...event.keys]; this.expandedKeysChange.emit(event.keys); } + + onDrop(event: DropEvent) { + event.event.stopPropagation(); + event.event.preventDefault(); + event.pos = this.dropPosition; + + this.dropOver.emit(event); + } } diff --git a/npm/ng-packs/packages/core/package.json b/npm/ng-packs/packages/core/package.json index 131f3031e0..4053f0fcc4 100644 --- a/npm/ng-packs/packages/core/package.json +++ b/npm/ng-packs/packages/core/package.json @@ -1,13 +1,13 @@ { "name": "@abp/ng.core", - "version": "3.0.4", + "version": "3.0.5", "homepage": "https://abp.io", "repository": { "type": "git", "url": "https://github.com/abpframework/abp.git" }, "dependencies": { - "@abp/utils": "^3.0.4", + "@abp/utils": "^3.0.5", "@angular/localize": "~9.1.11", "@ngxs/router-plugin": "^3.6.2", "@ngxs/storage-plugin": "^3.6.2", diff --git a/npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts b/npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts index 7b1395223c..c47b3c02fd 100644 --- a/npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts +++ b/npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts @@ -1,4 +1,4 @@ -import { Component, Injector, OnDestroy, Optional, SkipSelf, Type } from '@angular/core'; +import { Component, Injector, Optional, SkipSelf, Type } from '@angular/core'; import { ActivatedRoute, NavigationEnd, Router } from '@angular/router'; import { Store } from '@ngxs/store'; import { eLayoutType } from '../enums/common'; @@ -6,9 +6,9 @@ import { ABP } from '../models'; import { ReplaceableComponents } from '../models/replaceable-components'; import { LocalizationService } from '../services/localization.service'; import { RoutesService } from '../services/routes.service'; +import { SubscriptionService } from '../services/subscription.service'; import { ReplaceableComponentsState } from '../states/replaceable-components.state'; import { findRoute, getRoutePath } from '../utils/route-utils'; -import { takeUntilDestroy } from '../utils/rxjs-utils'; import { TreeNode } from '../utils/tree-utils'; @Component({ @@ -20,8 +20,9 @@ import { TreeNode } from '../utils/tree-utils'; > `, + providers: [SubscriptionService], }) -export class DynamicLayoutComponent implements OnDestroy { +export class DynamicLayoutComponent { layout: Type; // TODO: Consider a shared enum (eThemeSharedComponents) for known layouts @@ -37,6 +38,7 @@ export class DynamicLayoutComponent implements OnDestroy { injector: Injector, private localizationService: LocalizationService, private store: Store, + private subscription: SubscriptionService, @Optional() @SkipSelf() dynamicLayoutComponent: DynamicLayoutComponent, ) { if (dynamicLayoutComponent) return; @@ -44,7 +46,7 @@ export class DynamicLayoutComponent implements OnDestroy { const router = injector.get(Router); const routes = injector.get(RoutesService); - router.events.pipe(takeUntilDestroy(this)).subscribe(event => { + this.subscription.addOne(router.events, event => { if (event instanceof NavigationEnd) { let expectedLayout = (route.snapshot.data || {}).layout; @@ -73,7 +75,7 @@ export class DynamicLayoutComponent implements OnDestroy { } private listenToLanguageChange() { - this.localizationService.languageChange.pipe(takeUntilDestroy(this)).subscribe(() => { + this.subscription.addOne(this.localizationService.languageChange, () => { this.isLayoutVisible = false; setTimeout(() => (this.isLayoutVisible = true), 0); }); @@ -82,6 +84,4 @@ export class DynamicLayoutComponent implements OnDestroy { private getComponent(key: string): ReplaceableComponents.ReplaceableComponent { return this.store.selectSnapshot(ReplaceableComponentsState.getComponent(key)); } - - ngOnDestroy() {} } diff --git a/npm/ng-packs/packages/core/src/lib/components/replaceable-route-container.component.ts b/npm/ng-packs/packages/core/src/lib/components/replaceable-route-container.component.ts index 32d2dadb76..04c596d398 100644 --- a/npm/ng-packs/packages/core/src/lib/components/replaceable-route-container.component.ts +++ b/npm/ng-packs/packages/core/src/lib/components/replaceable-route-container.component.ts @@ -1,39 +1,45 @@ -import { Component, OnDestroy, OnInit, Type } from '@angular/core'; +import { Component, OnInit, Type } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { Store } from '@ngxs/store'; import { distinctUntilChanged } from 'rxjs/operators'; -import { ABP } from '../models/common'; import { ReplaceableComponents } from '../models/replaceable-components'; +import { SubscriptionService } from '../services/subscription.service'; import { ReplaceableComponentsState } from '../states/replaceable-components.state'; -import { takeUntilDestroy } from '../utils/rxjs-utils'; @Component({ selector: 'abp-replaceable-route-container', template: ` `, + providers: [SubscriptionService], }) -export class ReplaceableRouteContainerComponent implements OnInit, OnDestroy { +export class ReplaceableRouteContainerComponent implements OnInit { defaultComponent: Type; componentKey: string; externalComponent: Type; - constructor(private route: ActivatedRoute, private store: Store) {} + constructor( + private route: ActivatedRoute, + private store: Store, + private subscription: SubscriptionService, + ) {} ngOnInit() { this.defaultComponent = this.route.snapshot.data.replaceableComponent.defaultComponent; this.componentKey = (this.route.snapshot.data .replaceableComponent as ReplaceableComponents.RouteData).key; - this.store + const component$ = this.store .select(ReplaceableComponentsState.getComponent(this.componentKey)) - .pipe(takeUntilDestroy(this), distinctUntilChanged()) - .subscribe((res = {} as ReplaceableComponents.ReplaceableComponent) => { + .pipe(distinctUntilChanged()); + + this.subscription.addOne( + component$, + (res = {} as ReplaceableComponents.ReplaceableComponent) => { this.externalComponent = res.component; - }); + }, + ); } - - ngOnDestroy() {} } diff --git a/npm/ng-packs/packages/core/src/lib/core.module.ts b/npm/ng-packs/packages/core/src/lib/core.module.ts index f118ab2836..85cf87297f 100644 --- a/npm/ng-packs/packages/core/src/lib/core.module.ts +++ b/npm/ng-packs/packages/core/src/lib/core.module.ts @@ -21,6 +21,7 @@ import { PermissionDirective } from './directives/permission.directive'; import { ReplaceableTemplateDirective } from './directives/replaceable-template.directive'; import { StopPropagationDirective } from './directives/stop-propagation.directive'; import { VisibilityDirective } from './directives/visibility.directive'; +import { OAuthConfigurationHandler } from './handlers/oauth-configuration.handler'; import { RoutesHandler } from './handlers/routes.handler'; import { ApiInterceptor } from './interceptors/api.interceptor'; import { LocalizationModule } from './localization.module'; @@ -34,10 +35,10 @@ import { ConfigState } from './states/config.state'; import { ProfileState } from './states/profile.state'; import { ReplaceableComponentsState } from './states/replaceable-components.state'; import { SessionState } from './states/session.state'; -import { CORE_OPTIONS } from './tokens/options.token'; +import { coreOptionsFactory, CORE_OPTIONS } from './tokens/options.token'; import { noop } from './utils/common-utils'; import './utils/date-extensions'; -import { getInitialData, localeInitializer, configureOAuth } from './utils/initial-utils'; +import { getInitialData, localeInitializer } from './utils/initial-utils'; export function storageFactory(): OAuthStorage { return localStorage; @@ -171,9 +172,14 @@ export class CoreModule { useValue: { environment: options.environment }, }, { - provide: CORE_OPTIONS, + provide: 'CORE_OPTIONS', useValue: options, }, + { + provide: CORE_OPTIONS, + useFactory: coreOptionsFactory, + deps: ['CORE_OPTIONS'], + }, { provide: HTTP_INTERCEPTORS, useClass: ApiInterceptor, @@ -182,8 +188,8 @@ export class CoreModule { { provide: APP_INITIALIZER, multi: true, - deps: [Injector, NGXS_CONFIG_PLUGIN_OPTIONS], - useFactory: configureOAuth, + deps: [OAuthConfigurationHandler], + useFactory: noop, }, { provide: APP_INITIALIZER, diff --git a/npm/ng-packs/packages/core/src/lib/directives/debounce.directive.ts b/npm/ng-packs/packages/core/src/lib/directives/debounce.directive.ts index add5545d06..f35b0c6768 100644 --- a/npm/ng-packs/packages/core/src/lib/directives/debounce.directive.ts +++ b/npm/ng-packs/packages/core/src/lib/directives/debounce.directive.ts @@ -1,34 +1,25 @@ -import { - Directive, - ElementRef, - EventEmitter, - Input, - OnDestroy, - OnInit, - Output, -} from '@angular/core'; -import { takeUntilDestroy } from '../utils/rxjs-utils'; +import { Directive, ElementRef, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { fromEvent } from 'rxjs'; import { debounceTime } from 'rxjs/operators'; +import { SubscriptionService } from '../services/subscription.service'; @Directive({ // tslint:disable-next-line: directive-selector selector: '[input.debounce]', + providers: [SubscriptionService], }) -export class InputEventDebounceDirective implements OnInit, OnDestroy { +export class InputEventDebounceDirective implements OnInit { @Input() debounce = 300; @Output('input.debounce') readonly debounceEvent = new EventEmitter(); - constructor(private el: ElementRef) {} + constructor(private el: ElementRef, private subscription: SubscriptionService) {} ngOnInit(): void { - fromEvent(this.el.nativeElement, 'input') - .pipe(debounceTime(this.debounce), takeUntilDestroy(this)) - .subscribe((event: Event) => { - this.debounceEvent.emit(event); - }); - } + const input$ = fromEvent(this.el.nativeElement, 'input').pipe(debounceTime(this.debounce)); - ngOnDestroy(): void {} + this.subscription.addOne(input$, (event: Event) => { + this.debounceEvent.emit(event); + }); + } } diff --git a/npm/ng-packs/packages/core/src/lib/directives/form-submit.directive.ts b/npm/ng-packs/packages/core/src/lib/directives/form-submit.directive.ts index 4d6c1014db..1d5955dca2 100644 --- a/npm/ng-packs/packages/core/src/lib/directives/form-submit.directive.ts +++ b/npm/ng-packs/packages/core/src/lib/directives/form-submit.directive.ts @@ -4,7 +4,6 @@ import { ElementRef, EventEmitter, Input, - OnDestroy, OnInit, Output, Self, @@ -12,15 +11,16 @@ import { import { FormControl, FormGroup, FormGroupDirective } from '@angular/forms'; import { fromEvent } from 'rxjs'; import { debounceTime, filter } from 'rxjs/operators'; -import { takeUntilDestroy } from '../utils'; +import { SubscriptionService } from '../services/subscription.service'; type Controls = { [key: string]: FormControl } | FormGroup[]; @Directive({ // tslint:disable-next-line: directive-selector selector: 'form[ngSubmit][formGroup]', + providers: [SubscriptionService], }) -export class FormSubmitDirective implements OnInit, OnDestroy { +export class FormSubmitDirective implements OnInit { @Input() debounce = 200; @@ -35,30 +35,30 @@ export class FormSubmitDirective implements OnInit, OnDestroy { @Self() private formGroupDirective: FormGroupDirective, private host: ElementRef, private cdRef: ChangeDetectorRef, + private subscription: SubscriptionService, ) {} ngOnInit() { - this.formGroupDirective.ngSubmit.pipe(takeUntilDestroy(this)).subscribe(() => { + this.subscription.addOne(this.formGroupDirective.ngSubmit, () => { this.markAsDirty(); this.executedNgSubmit = true; }); - fromEvent(this.host.nativeElement as HTMLElement, 'keyup') - .pipe( - debounceTime(this.debounce), - filter((key: KeyboardEvent) => key && key.key === 'Enter'), - takeUntilDestroy(this), - ) - .subscribe(() => { - if (!this.executedNgSubmit) { - this.host.nativeElement.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true })); - } + const keyup$ = fromEvent(this.host.nativeElement as HTMLElement, 'keyup').pipe( + debounceTime(this.debounce), + filter((key: KeyboardEvent) => key && key.key === 'Enter'), + ); - this.executedNgSubmit = false; - }); - } + this.subscription.addOne(keyup$, () => { + if (!this.executedNgSubmit) { + this.host.nativeElement.dispatchEvent( + new Event('submit', { bubbles: true, cancelable: true }), + ); + } - ngOnDestroy(): void {} + this.executedNgSubmit = false; + }); + } markAsDirty() { const { form } = this.formGroupDirective; diff --git a/npm/ng-packs/packages/core/src/lib/directives/permission.directive.ts b/npm/ng-packs/packages/core/src/lib/directives/permission.directive.ts index 870e4ec144..fda8b7d315 100644 --- a/npm/ng-packs/packages/core/src/lib/directives/permission.directive.ts +++ b/npm/ng-packs/packages/core/src/lib/directives/permission.directive.ts @@ -2,19 +2,18 @@ import { Directive, ElementRef, Input, + OnChanges, OnDestroy, OnInit, - Renderer2, - ViewContainerRef, - TemplateRef, Optional, + Renderer2, SimpleChanges, - OnChanges, + TemplateRef, + ViewContainerRef, } from '@angular/core'; import { Store } from '@ngxs/store'; -import { ConfigState } from '../states'; -import { takeUntilDestroy } from '../utils'; import { Subscription } from 'rxjs'; +import { ConfigState } from '../states'; @Directive({ selector: '[abpPermission]', @@ -39,7 +38,6 @@ export class PermissionDirective implements OnInit, OnDestroy, OnChanges { this.subscription = this.store .select(ConfigState.getGrantedPolicy(this.condition)) - .pipe(takeUntilDestroy(this)) .subscribe(isGranted => { if (this.templateRef && isGranted) { this.vcRef.clear(); @@ -47,7 +45,10 @@ export class PermissionDirective implements OnInit, OnDestroy, OnChanges { } else if (this.templateRef && !isGranted) { this.vcRef.clear(); } else if (!isGranted && !this.templateRef) { - this.renderer.removeChild((this.elRef.nativeElement as HTMLElement).parentElement, this.elRef.nativeElement); + this.renderer.removeChild( + (this.elRef.nativeElement as HTMLElement).parentElement, + this.elRef.nativeElement, + ); } }); } @@ -58,7 +59,9 @@ export class PermissionDirective implements OnInit, OnDestroy, OnChanges { } } - ngOnDestroy(): void {} + ngOnDestroy(): void { + if (this.subscription) this.subscription.unsubscribe(); + } ngOnChanges({ condition }: SimpleChanges) { if ((condition || { currentValue: null }).currentValue) { diff --git a/npm/ng-packs/packages/core/src/lib/directives/replaceable-template.directive.ts b/npm/ng-packs/packages/core/src/lib/directives/replaceable-template.directive.ts index 91f666137f..fd9051c64e 100644 --- a/npm/ng-packs/packages/core/src/lib/directives/replaceable-template.directive.ts +++ b/npm/ng-packs/packages/core/src/lib/directives/replaceable-template.directive.ts @@ -4,7 +4,6 @@ import { Injector, Input, OnChanges, - OnDestroy, OnInit, SimpleChanges, TemplateRef, @@ -12,17 +11,17 @@ import { ViewContainerRef, } from '@angular/core'; import { Store } from '@ngxs/store'; +import compare from 'just-compare'; import { Subscription } from 'rxjs'; import { filter } from 'rxjs/operators'; +import snq from 'snq'; import { ABP } from '../models/common'; import { ReplaceableComponents } from '../models/replaceable-components'; +import { SubscriptionService } from '../services/subscription.service'; import { ReplaceableComponentsState } from '../states/replaceable-components.state'; -import { takeUntilDestroy } from '../utils/rxjs-utils'; -import compare from 'just-compare'; -import snq from 'snq'; -@Directive({ selector: '[abpReplaceableTemplate]' }) -export class ReplaceableTemplateDirective implements OnInit, OnDestroy, OnChanges { +@Directive({ selector: '[abpReplaceableTemplate]', providers: [SubscriptionService] }) +export class ReplaceableTemplateDirective implements OnInit, OnChanges { @Input('abpReplaceableTemplate') data: ReplaceableComponents.ReplaceableTemplateDirectiveInput; @@ -47,6 +46,7 @@ export class ReplaceableTemplateDirective implements OnInit, OnDestroy, OnChange private cfRes: ComponentFactoryResolver, private vcRef: ViewContainerRef, private store: Store, + private subscription: SubscriptionService, ) { this.context = { initTemplate: ref => { @@ -58,16 +58,18 @@ export class ReplaceableTemplateDirective implements OnInit, OnDestroy, OnChange } ngOnInit() { - this.store + const component$ = this.store .select(ReplaceableComponentsState.getComponent(this.data.componentKey)) .pipe( filter( (res = {} as ReplaceableComponents.ReplaceableComponent) => !this.initialized || !compare(res.component, this.externalComponent), ), - takeUntilDestroy(this), - ) - .subscribe((res = {} as ReplaceableComponents.ReplaceableComponent) => { + ); + + this.subscription.addOne( + component$, + (res = {} as ReplaceableComponents.ReplaceableComponent) => { this.vcRef.clear(); this.externalComponent = res.component; if (this.defaultComponentRef) { @@ -90,7 +92,8 @@ export class ReplaceableTemplateDirective implements OnInit, OnDestroy, OnChange } this.initialized = true; - }); + }, + ); } ngOnChanges(changes: SimpleChanges) { @@ -99,8 +102,6 @@ export class ReplaceableTemplateDirective implements OnInit, OnDestroy, OnChange } } - ngOnDestroy() {} - setDefaultComponentInputs() { if (!this.defaultComponentRef || (!this.data.inputs && !this.data.outputs)) return; diff --git a/npm/ng-packs/packages/core/src/lib/directives/stop-propagation.directive.ts b/npm/ng-packs/packages/core/src/lib/directives/stop-propagation.directive.ts index ced0ae4159..12c035cc19 100644 --- a/npm/ng-packs/packages/core/src/lib/directives/stop-propagation.directive.ts +++ b/npm/ng-packs/packages/core/src/lib/directives/stop-propagation.directive.ts @@ -1,24 +1,21 @@ -import { Directive, ElementRef, EventEmitter, OnInit, Output, OnDestroy } from '@angular/core'; +import { Directive, ElementRef, EventEmitter, OnInit, Output } from '@angular/core'; import { fromEvent } from 'rxjs'; -import { takeUntilDestroy } from '../utils/rxjs-utils'; +import { SubscriptionService } from '../services/subscription.service'; @Directive({ // tslint:disable-next-line: directive-selector selector: '[click.stop]', + providers: [SubscriptionService], }) -export class StopPropagationDirective implements OnInit, OnDestroy { +export class StopPropagationDirective implements OnInit { @Output('click.stop') readonly stopPropEvent = new EventEmitter(); - constructor(private el: ElementRef) {} + constructor(private el: ElementRef, private subscription: SubscriptionService) {} ngOnInit(): void { - fromEvent(this.el.nativeElement, 'click') - .pipe(takeUntilDestroy(this)) - .subscribe((event: MouseEvent) => { - event.stopPropagation(); - this.stopPropEvent.emit(event); - }); + this.subscription.addOne(fromEvent(this.el.nativeElement, 'click'), (event: MouseEvent) => { + event.stopPropagation(); + this.stopPropEvent.emit(event); + }); } - - ngOnDestroy(): void {} } diff --git a/npm/ng-packs/packages/core/src/lib/handlers/index.ts b/npm/ng-packs/packages/core/src/lib/handlers/index.ts index b666218d93..e11b317f45 100644 --- a/npm/ng-packs/packages/core/src/lib/handlers/index.ts +++ b/npm/ng-packs/packages/core/src/lib/handlers/index.ts @@ -1 +1,2 @@ +export * from './oauth-configuration.handler'; export * from './routes.handler'; diff --git a/npm/ng-packs/packages/core/src/lib/handlers/oauth-configuration.handler.ts b/npm/ng-packs/packages/core/src/lib/handlers/oauth-configuration.handler.ts new file mode 100644 index 0000000000..d9c6806900 --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/handlers/oauth-configuration.handler.ts @@ -0,0 +1,35 @@ +import { Inject, Injectable } from '@angular/core'; +import { Actions, ofActionSuccessful } from '@ngxs/store'; +import { OAuthService } from 'angular-oauth2-oidc'; +import compare from 'just-compare'; +import { filter, map } from 'rxjs/operators'; +import { SetEnvironment } from '../actions/config.actions'; +import { ABP } from '../models/common'; +import { CORE_OPTIONS } from '../tokens/options.token'; + +@Injectable({ + providedIn: 'root', +}) +export class OAuthConfigurationHandler { + constructor( + private actions: Actions, + private oAuthService: OAuthService, + @Inject(CORE_OPTIONS) private options: ABP.Root, + ) { + this.oAuthService.configure(this.options.environment.oAuthConfig); + + this.listenToSetEnvironment(); + } + + private listenToSetEnvironment() { + this.actions + .pipe(ofActionSuccessful(SetEnvironment)) + .pipe( + map(({ environment }: SetEnvironment) => environment.oAuthConfig), + filter(config => !compare(config, this.options.environment.oAuthConfig)), + ) + .subscribe(config => { + this.oAuthService.configure(config); + }); + } +} diff --git a/npm/ng-packs/packages/core/src/lib/models/common.ts b/npm/ng-packs/packages/core/src/lib/models/common.ts index 930265020c..85b1138002 100644 --- a/npm/ng-packs/packages/core/src/lib/models/common.ts +++ b/npm/ng-packs/packages/core/src/lib/models/common.ts @@ -9,6 +9,7 @@ export namespace ABP { environment: Partial; skipGetAppConfiguration?: boolean; sendNullsAsQueryParam?: boolean; + cultureNameLocaleFileMap?: Dictionary; } export interface Test { @@ -30,6 +31,11 @@ export namespace ABP { maxResultCount?: number; } + export interface Lookup { + id: string; + displayName: string; + } + export interface Nav { name: string; parentName?: string; diff --git a/npm/ng-packs/packages/core/src/lib/models/config.ts b/npm/ng-packs/packages/core/src/lib/models/config.ts index d328cfe5b5..e3d6f6ab70 100644 --- a/npm/ng-packs/packages/core/src/lib/models/config.ts +++ b/npm/ng-packs/packages/core/src/lib/models/config.ts @@ -7,16 +7,18 @@ export namespace Config { export type State = ApplicationConfiguration.Response & ABP.Root & { environment: Environment }; export interface Environment { + apis: Apis; application: Application; - production: boolean; hmr?: boolean; - oAuthConfig: AuthConfig; - apis: Apis; localization?: { defaultResourceName?: string }; + oAuthConfig: AuthConfig; + production: boolean; + remoteEnv?: RemoteEnv; } export interface Application { name: string; + baseUrl?: string; logoUrl?: string; } @@ -40,4 +42,10 @@ export namespace Config { } export type LocalizationParam = string | LocalizationWithDefault; + + export interface RemoteEnv { + url: string; + method?: string; + headers?: ABP.Dictionary; + } } diff --git a/npm/ng-packs/packages/core/src/lib/models/find-tenant-result-dto.ts b/npm/ng-packs/packages/core/src/lib/models/find-tenant-result-dto.ts new file mode 100644 index 0000000000..9b1b0cc4b6 --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/models/find-tenant-result-dto.ts @@ -0,0 +1,14 @@ + +export class FindTenantResultDto { + success: boolean; + tenantId?: string; + name: string; + + constructor(initialValues: Partial = {}) { + for (const key in initialValues) { + if (initialValues.hasOwnProperty(key)) { + this[key] = initialValues[key]; + } + } + } +} diff --git a/npm/ng-packs/packages/core/src/lib/models/index.ts b/npm/ng-packs/packages/core/src/lib/models/index.ts index c0950c3dee..e5776a682e 100644 --- a/npm/ng-packs/packages/core/src/lib/models/index.ts +++ b/npm/ng-packs/packages/core/src/lib/models/index.ts @@ -2,6 +2,7 @@ export * from './application-configuration'; export * from './common'; export * from './config'; export * from './dtos'; +export * from './find-tenant-result-dto'; export * from './profile'; export * from './replaceable-components'; export * from './rest'; diff --git a/npm/ng-packs/packages/core/src/lib/services/config-state.service.ts b/npm/ng-packs/packages/core/src/lib/services/config-state.service.ts index fe10f9ddf0..027c284f52 100644 --- a/npm/ng-packs/packages/core/src/lib/services/config-state.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/config-state.service.ts @@ -29,6 +29,10 @@ export class ConfigStateService { return this.store.selectSnapshot(ConfigState.getApiUrl(...args)); } + getFeature(...args: Parameters) { + return this.store.selectSnapshot(ConfigState.getFeature(...args)); + } + getSetting(...args: Parameters) { return this.store.selectSnapshot(ConfigState.getSetting(...args)); } diff --git a/npm/ng-packs/packages/core/src/lib/services/index.ts b/npm/ng-packs/packages/core/src/lib/services/index.ts index 064dcbd35e..c084fd4808 100644 --- a/npm/ng-packs/packages/core/src/lib/services/index.ts +++ b/npm/ng-packs/packages/core/src/lib/services/index.ts @@ -6,9 +6,11 @@ export * from './dom-insertion.service'; export * from './lazy-load.service'; export * from './list.service'; export * from './localization.service'; +export * from './multi-tenancy.service'; export * from './profile-state.service'; export * from './profile.service'; export * from './rest.service'; export * from './routes.service'; export * from './session-state.service'; +export * from './subscription.service'; export * from './track-by.service'; diff --git a/npm/ng-packs/packages/core/src/lib/services/list.service.ts b/npm/ng-packs/packages/core/src/lib/services/list.service.ts index d3da082eeb..3ebb6dc193 100644 --- a/npm/ng-packs/packages/core/src/lib/services/list.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/list.service.ts @@ -1,10 +1,17 @@ import { Inject, Injectable, OnDestroy, Optional } from '@angular/core'; -import { BehaviorSubject, Observable, of, ReplaySubject } from 'rxjs'; -import { catchError, debounceTime, filter, shareReplay, switchMap, tap } from 'rxjs/operators'; +import { BehaviorSubject, Observable, of, ReplaySubject, Subject } from 'rxjs'; +import { + catchError, + debounceTime, + filter, + shareReplay, + switchMap, + takeUntil, + tap, +} from 'rxjs/operators'; import { ABP } from '../models/common'; import { PagedResultDto } from '../models/dtos'; import { LIST_QUERY_DEBOUNCE_TIME } from '../tokens/list.token'; -import { takeUntilDestroy } from '../utils/rxjs-utils'; @Injectable() export class ListService implements OnDestroy { @@ -65,6 +72,8 @@ export class ListService implements OnDes private _isLoading$ = new BehaviorSubject(false); + private destroy$ = new Subject(); + get isLoading$(): Observable { return this._isLoading$.asObservable(); } @@ -92,11 +101,13 @@ export class ListService implements OnDes filter(Boolean), tap(() => this._isLoading$.next(false)), shareReplay({ bufferSize: 1, refCount: true }), - takeUntilDestroy(this), + takeUntil(this.destroy$), ); } - ngOnDestroy() {} + ngOnDestroy() { + this.destroy$.next(); + } } export type QueryStreamCreatorCallback = ( diff --git a/npm/ng-packs/packages/core/src/lib/services/localization.service.ts b/npm/ng-packs/packages/core/src/lib/services/localization.service.ts index 3f89642fd9..5b98afe0c3 100644 --- a/npm/ng-packs/packages/core/src/lib/services/localization.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/localization.service.ts @@ -8,6 +8,7 @@ import { Config } from '../models/config'; import { ConfigState } from '../states/config.state'; import { registerLocale } from '../utils/initial-utils'; import { createLocalizer, createLocalizerWithFallback } from '../utils/localization-utils'; +import { CORE_OPTIONS } from '../tokens/options.token'; type ShouldReuseRoute = (future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot) => boolean; @@ -44,11 +45,12 @@ export class LocalizationService { registerLocale(locale: string) { const router = this.injector.get(Router); + const { cultureNameLocaleFileMap } = this.injector.get(CORE_OPTIONS); const { shouldReuseRoute } = router.routeReuseStrategy; router.routeReuseStrategy.shouldReuseRoute = () => false; router.navigated = false; - return registerLocale(locale).then(() => { + return registerLocale(locale, cultureNameLocaleFileMap).then(() => { this.ngZone.run(async () => { await router.navigateByUrl(router.url).catch(noop); router.routeReuseStrategy.shouldReuseRoute = shouldReuseRoute; diff --git a/npm/ng-packs/packages/core/src/lib/services/multi-tenancy.service.ts b/npm/ng-packs/packages/core/src/lib/services/multi-tenancy.service.ts new file mode 100644 index 0000000000..3a66a99311 --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/services/multi-tenancy.service.ts @@ -0,0 +1,45 @@ +import { Injectable } from '@angular/core'; +import { Store } from '@ngxs/store'; +import { Observable } from 'rxjs'; +import { SetTenant } from '../actions/session.actions'; +import { ABP } from '../models/common'; +import { FindTenantResultDto } from '../models/find-tenant-result-dto'; +import { RestService } from './rest.service'; + +@Injectable({ providedIn: 'root' }) +export class MultiTenancyService { + private _domainTenant: ABP.BasicItem = null; + + set domainTenant(value: ABP.BasicItem) { + this._domainTenant = value; + this.store.dispatch(new SetTenant(value)); + } + + get domainTenant() { + return this._domainTenant; + } + + isTenantBoxVisible = true; + + apiName = 'abp'; + + constructor(private restService: RestService, private store: Store) {} + + findTenantByName(name: string, headers: ABP.Dictionary): Observable { + return this.restService.request( + { + url: `/api/abp/multi-tenancy/tenants/by-name/${name}`, + method: 'GET', + headers, + }, + { apiName: this.apiName }, + ); + } + + findTenantById(id: string, headers: ABP.Dictionary): Observable { + return this.restService.request( + { url: `/api/abp/multi-tenancy/tenants/by-id/${id}`, method: 'GET', headers }, + { apiName: this.apiName }, + ); + } +} diff --git a/npm/ng-packs/packages/core/src/lib/services/routes.service.ts b/npm/ng-packs/packages/core/src/lib/services/routes.service.ts index 2f80d6b44d..c2b90a91c7 100644 --- a/npm/ng-packs/packages/core/src/lib/services/routes.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/routes.service.ts @@ -1,10 +1,9 @@ import { Injectable, OnDestroy } from '@angular/core'; import { Actions, ofActionSuccessful, Store } from '@ngxs/store'; -import { BehaviorSubject, Observable } from 'rxjs'; +import { BehaviorSubject, Observable, Subscription } from 'rxjs'; import { GetAppConfiguration } from '../actions/config.actions'; import { ABP } from '../models/common'; import { ConfigState } from '../states/config.state'; -import { takeUntilDestroy } from '../utils/rxjs-utils'; import { pushValueTo } from '../utils/array-utils'; import { BaseTreeNode, createTreeFromList, TreeNode } from '../utils/tree-utils'; @@ -129,6 +128,7 @@ export abstract class AbstractTreeService { @Injectable() export abstract class AbstractNavTreeService extends AbstractTreeService implements OnDestroy { + private subscription: Subscription; readonly id = 'name'; readonly parentId = 'parentName'; readonly hide = (item: T) => item.invisible || !this.isGranted(item); @@ -142,8 +142,8 @@ export abstract class AbstractNavTreeService extends Abstract constructor(protected actions: Actions, protected store: Store) { super(); - this.actions - .pipe(takeUntilDestroy(this), ofActionSuccessful(GetAppConfiguration)) + this.subscription = this.actions + .pipe(ofActionSuccessful(GetAppConfiguration)) .subscribe(() => this.refresh()); } @@ -162,7 +162,9 @@ export abstract class AbstractNavTreeService extends Abstract } /* istanbul ignore next */ - ngOnDestroy() {} + ngOnDestroy() { + this.subscription.unsubscribe(); + } } @Injectable({ providedIn: 'root' }) diff --git a/npm/ng-packs/packages/core/src/lib/services/subscription.service.ts b/npm/ng-packs/packages/core/src/lib/services/subscription.service.ts new file mode 100644 index 0000000000..9296585d2a --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/services/subscription.service.ts @@ -0,0 +1,54 @@ +import { Injectable } from '@angular/core'; +import type { OnDestroy } from '@angular/core'; +import { Subscription } from 'rxjs'; +import type { Observable, PartialObserver } from 'rxjs'; + +@Injectable() +export class SubscriptionService implements OnDestroy { + private subscription = new Subscription(); + + get isClosed() { + return this.subscription.closed; + } + + addOne( + source$: Observable, + next?: (value: T) => void, + error?: (error: any) => void, + ): Subscription; + addOne(source$: Observable, observer?: PartialObserver): Subscription; + addOne( + source$: Observable, + nextOrObserver?: PartialObserver | Next, + error?: (error: any) => void, + ): Subscription { + const subscription = source$.subscribe(nextOrObserver as Next, error); + this.subscription.add(subscription); + return subscription; + } + + closeAll() { + this.subscription.unsubscribe(); + } + + closeOne(subscription: Subscription | undefined | null) { + this.removeOne(subscription); + subscription.unsubscribe(); + } + + ngOnDestroy(): void { + this.subscription.unsubscribe(); + } + + removeOne(subscription: Subscription | undefined | null) { + if (!subscription) return; + this.subscription.remove(subscription); + } + + reset() { + this.subscription.unsubscribe(); + this.subscription = new Subscription(); + } +} + +type Next = (value: T) => void; diff --git a/npm/ng-packs/packages/core/src/lib/states/config.state.ts b/npm/ng-packs/packages/core/src/lib/states/config.state.ts index d9793529aa..2aaf7a3bc2 100644 --- a/npm/ng-packs/packages/core/src/lib/states/config.state.ts +++ b/npm/ng-packs/packages/core/src/lib/states/config.state.ts @@ -65,6 +65,14 @@ export class ConfigState { return selector; } + static getFeature(key: string) { + const selector = createSelector([ConfigState], (state: Config.State) => { + return snq(() => state.features.values[key]); + }); + + return selector; + } + static getSetting(key: string) { const selector = createSelector([ConfigState], (state: Config.State) => { return snq(() => state.setting.values[key]); @@ -225,8 +233,6 @@ export class ConfigState { @Action(SetEnvironment) setEnvironment({ patchState }: StateContext, { environment }: SetEnvironment) { - return patchState({ - environment, - }); + return patchState({ environment }); } } diff --git a/npm/ng-packs/packages/core/src/lib/tests/api.interceptor.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/api.interceptor.spec.ts index b4f1dcd408..8285a6a9f4 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/api.interceptor.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/api.interceptor.spec.ts @@ -21,8 +21,8 @@ describe('ApiInterceptor', () => { beforeEach(() => { spectator = createService(); interceptor = spectator.service; - store = spectator.get(Store); - oauthService = spectator.get(OAuthService); + store = spectator.inject(Store); + oauthService = spectator.inject(OAuthService); }); it('should add headers to http request', done => { diff --git a/npm/ng-packs/packages/core/src/lib/tests/application-configuration.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/application-configuration.service.spec.ts index 1fccc14c8f..8bbdabda80 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/application-configuration.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/application-configuration.service.spec.ts @@ -14,7 +14,7 @@ describe('ApplicationConfigurationService', () => { beforeEach(() => (spectator = createHttp())); it('should send a GET to application-configuration API', () => { - spectator.get(Store).selectSnapshot.andReturn('https://abp.io'); + spectator.inject(Store).selectSnapshot.andReturn('https://abp.io'); spectator.service.getConfiguration().subscribe(); spectator.expectOne('https://abp.io/api/abp/application-configuration', HttpMethod.GET); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/auth.guard.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/auth.guard.spec.ts index 1e7f5bf585..5e20d7f568 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/auth.guard.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/auth.guard.spec.ts @@ -22,13 +22,13 @@ describe('AuthGuard', () => { }); it('should return true when user logged in', () => { - spectator.get(OAuthService).hasValidAccessToken.andReturn(true); + spectator.inject(OAuthService).hasValidAccessToken.andReturn(true); expect(guard.canActivate(null, null)).toBe(true); }); it('should return navigate to login page with redirectUrl state', () => { - const router = spectator.get(Router); - spectator.get(OAuthService).hasValidAccessToken.andReturn(false); + const router = spectator.inject(Router); + spectator.inject(OAuthService).hasValidAccessToken.andReturn(false); expect(guard.canActivate(null, { url: '/' } as any)).toBe(true); expect(router.navigate).toHaveBeenCalledWith(['/account/login'], { diff --git a/npm/ng-packs/packages/core/src/lib/tests/config-state.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/config-state.service.spec.ts index 4b0c9aa682..9a8f9b651e 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/config-state.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/config-state.service.spec.ts @@ -102,7 +102,7 @@ describe('ConfigStateService', () => { beforeEach(() => { spectator = createService(); service = spectator.service; - store = spectator.get(Store); + store = spectator.inject(Store); }); test('should have the all ConfigState static methods', () => { const reg = /(?<=static )(.*)(?=\()/gm; diff --git a/npm/ng-packs/packages/core/src/lib/tests/config.state.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/config.state.spec.ts index 0f9aa08c1a..e30a55635b 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/config.state.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/config.state.spec.ts @@ -93,7 +93,9 @@ export const CONFIG_STATE_DATA = { roles: [], }, features: { - values: {}, + values: { + 'Chat.Enable': 'True', + }, }, } as Config.State; @@ -110,9 +112,9 @@ describe('ConfigState', () => { beforeEach(() => { spectator = createService(); - store = spectator.get(Store); + store = spectator.inject(Store); service = spectator.service; - state = new ConfigState(spectator.get(HttpClient), store); + state = new ConfigState(spectator.inject(HttpClient), store); }); describe('#getAll', () => { @@ -163,6 +165,14 @@ describe('ConfigState', () => { }); }); + describe('#getFeature', () => { + it('should return a setting', () => { + expect(ConfigState.getFeature('Chat.Enable')(CONFIG_STATE_DATA)).toEqual( + CONFIG_STATE_DATA.features.values['Chat.Enable'], + ); + }); + }); + describe('#getSetting', () => { it('should return a setting', () => { expect(ConfigState.getSetting('Abp.Localization.DefaultLanguage')(CONFIG_STATE_DATA)).toEqual( @@ -250,7 +260,7 @@ describe('ConfigState', () => { dispatchArg = a; return of(a); }); - const httpClient = spectator.get(HttpClient); + const httpClient = spectator.inject(HttpClient); httpClient.get.andReturn(res$); state.addData({ patchState, dispatch } as any).subscribe(); diff --git a/npm/ng-packs/packages/core/src/lib/tests/dynamic-layout.component.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/dynamic-layout.component.spec.ts index 216df7b681..4d5f53a9ba 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/dynamic-layout.component.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/dynamic-layout.component.spec.ts @@ -167,8 +167,8 @@ describe('DynamicLayoutComponent', () => { beforeEach(async () => { spectator = createComponent(); - store = spectator.get(Store); - const routesService = spectator.get(RoutesService); + store = spectator.inject(Store); + const routesService = spectator.inject(RoutesService); routesService.add(routes); store.reset(storeData); diff --git a/npm/ng-packs/packages/core/src/lib/tests/environment-utils.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/environment-utils.spec.ts new file mode 100644 index 0000000000..ab7cc0df0c --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/tests/environment-utils.spec.ts @@ -0,0 +1,71 @@ +import { HttpClient } from '@angular/common/http'; +import { Component, Injector } from '@angular/core'; +import { createComponentFactory, Spectator } from '@ngneat/spectator/jest'; +import { Store } from '@ngxs/store'; +import { BehaviorSubject, of } from 'rxjs'; +import { getRemoteEnv } from '../utils/environment-utils'; +import { SetEnvironment } from '../actions/config.actions'; + +const environment = { + production: false, + hmr: false, + application: { + baseUrl: 'https://volosoft.com', + name: 'MyProjectName', + logoUrl: '', + }, + oAuthConfig: { + issuer: 'https://api.volosoft.com', + clientId: 'MyProjectName_App', + dummyClientSecret: '1q2w3e*', + scope: 'MyProjectName', + oidc: false, + requireHttps: true, + }, + apis: { + default: { + url: 'https://api.volosoft.com', + }, + }, +}; + +@Component({ + selector: 'abp-dummy', + template: '', +}) +export class DummyComponent {} + +describe('EnvironmentUtils', () => { + let spectator: Spectator; + const createComponent = createComponentFactory({ + component: DummyComponent, + mocks: [Store, HttpClient], + }); + + beforeEach(() => (spectator = createComponent())); + + describe('#getRemoteEnv', async () => { + test('should call the remoteEnv URL and dispatch the SetEnvironment action ', async () => { + const injector = spectator.inject(Injector); + const injectorSpy = jest.spyOn(injector, 'get'); + const store = spectator.inject(Store); + const dispatchSpy = jest.spyOn(store, 'dispatch'); + const http = spectator.inject(HttpClient); + const requestSpy = jest.spyOn(http, 'request'); + + injectorSpy.mockReturnValueOnce(http); + injectorSpy.mockReturnValueOnce(store); + + requestSpy.mockReturnValue(new BehaviorSubject(environment)); + dispatchSpy.mockReturnValue(of(true)); + + const partialEnv = { remoteEnv: { url: '/assets/appsettings.json' } }; + getRemoteEnv(injector, partialEnv); + + expect(requestSpy).toHaveBeenCalledWith('GET', '/assets/appsettings.json', { headers: {} }); + expect(dispatchSpy).toHaveBeenCalledWith( + new SetEnvironment({ ...environment, ...partialEnv }), + ); + }); + }); +}); diff --git a/npm/ng-packs/packages/core/src/lib/tests/initial-utils.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/initial-utils.spec.ts index 553fcc6d7e..c81ce081b2 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/initial-utils.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/initial-utils.spec.ts @@ -1,10 +1,13 @@ import { Component, Injector } from '@angular/core'; import { createComponentFactory, Spectator } from '@ngneat/spectator/jest'; import { Store } from '@ngxs/store'; +import { OAuthService } from 'angular-oauth2-oidc'; import { of } from 'rxjs'; import { GetAppConfiguration } from '../actions'; -import { getInitialData, localeInitializer, configureOAuth } from '../utils'; -import { OAuthService } from 'angular-oauth2-oidc'; +import { CORE_OPTIONS } from '../tokens/options.token'; +import { checkAccessToken, getInitialData, localeInitializer } from '../utils'; +import * as multiTenancyUtils from '../utils/multi-tenancy-utils'; +import * as environmentUtils from '../utils/environment-utils'; @Component({ selector: 'abp-dummy', @@ -17,30 +20,23 @@ describe('InitialUtils', () => { const createComponent = createComponentFactory({ component: DummyComponent, mocks: [Store, OAuthService], + providers: [ + { provide: CORE_OPTIONS, useValue: { environment: { oAuthConfig: { issuer: 'test' } } } }, + ], }); beforeEach(() => (spectator = createComponent())); - describe('#configureOAuth', () => { - test('should be called the the configure method of OAuthService', async () => { - const injector = spectator.inject(Injector); - const injectorSpy = jest.spyOn(injector, 'get'); - const oAuth = spectator.inject(OAuthService); - const configureSpy = jest.spyOn(oAuth, 'configure'); - - injectorSpy.mockReturnValueOnce(oAuth); - - await configureOAuth(injector, { environment: { oAuthConfig: { issuer: 'test' } } })(); - expect(configureSpy).toHaveBeenCalledWith({ issuer: 'test' }); - }); - }); - describe('#getInitialData', () => { test('should dispatch GetAppConfiguration and return', async () => { const injector = spectator.inject(Injector); const injectorSpy = jest.spyOn(injector, 'get'); const store = spectator.inject(Store); const dispatchSpy = jest.spyOn(store, 'dispatch'); + const parseTenantFromUrlSpy = jest.spyOn(multiTenancyUtils, 'parseTenantFromUrl'); + const getRemoteEnvSpy = jest.spyOn(environmentUtils, 'getRemoteEnv'); + parseTenantFromUrlSpy.mockReturnValue(Promise.resolve()); + getRemoteEnvSpy.mockReturnValue(Promise.resolve()); injectorSpy.mockReturnValueOnce(store); injectorSpy.mockReturnValueOnce({ skipGetAppConfiguration: false }); @@ -57,16 +53,16 @@ describe('InitialUtils', () => { test('should call logOut fn of OAuthService when token is valid and current user not found', async () => { const injector = spectator.inject(Injector); const injectorSpy = jest.spyOn(injector, 'get'); - const store = spectator.inject(Store); - const dispatchSpy = jest.spyOn(store, 'dispatch'); const logOutFn = jest.fn(); - injectorSpy.mockReturnValueOnce(store); - injectorSpy.mockReturnValueOnce({ skipGetAppConfiguration: false }); - injectorSpy.mockReturnValueOnce({ hasValidAccessToken: () => true, logOut: logOutFn }); - dispatchSpy.mockReturnValue(of({ currentUser: { id: null } })); + injectorSpy.mockReturnValue({ hasValidAccessToken: () => true, logOut: logOutFn }); - getInitialData(injector)(); + checkAccessToken( + { + selectSnapshot: () => false, + } as any, + injector, + ); expect(logOutFn).toHaveBeenCalled(); }); }); @@ -77,7 +73,8 @@ describe('InitialUtils', () => { const injectorSpy = jest.spyOn(injector, 'get'); const store = spectator.inject(Store); store.selectSnapshot.andCallFake(selector => selector({ SessionState: { language: 'tr' } })); - injectorSpy.mockReturnValue(store); + injectorSpy.mockReturnValueOnce(store); + injectorSpy.mockReturnValueOnce({ cultureNameLocaleFileMap: {} }); expect(typeof localeInitializer(injector)).toBe('function'); expect(await localeInitializer(injector)()).toBe('resolved'); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/locale.provider.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/locale.provider.spec.ts index b5c788635d..b07006a04d 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/locale.provider.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/locale.provider.spec.ts @@ -26,12 +26,12 @@ describe('LocaleProvider', () => { describe('#LOCALE_ID', () => { test('should equal to currentLang', async () => { spectator = createComponent(); - const localizationService = spectator.get(LocalizationService); + const localizationService = spectator.inject(LocalizationService); - expect(spectator.get(LOCALE_ID).valueOf()).toBe(localesMapping['en-US'] || 'en-US'); + expect(spectator.inject(LOCALE_ID).valueOf()).toBe(localesMapping['en-US'] || 'en-US'); (localizationService as any).currentLang = 'tr'; - expect(spectator.get(LOCALE_ID).valueOf()).toBe(localesMapping['tr'] || 'tr'); + expect(spectator.inject(LOCALE_ID).valueOf()).toBe(localesMapping['tr'] || 'tr'); }); }); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/localization.pipe.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/localization.pipe.spec.ts index 7444265e35..a062d2c18a 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/localization.pipe.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/localization.pipe.spec.ts @@ -12,8 +12,8 @@ describe('LocalizationPipe', () => { beforeEach(() => { spectator = createService(); - pipe = spectator.get(LocalizationPipe); - store = spectator.get(Store); + pipe = spectator.inject(LocalizationPipe); + store = spectator.inject(Store); }); it('should call getLocalization selector', () => { diff --git a/npm/ng-packs/packages/core/src/lib/tests/localization.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/localization.service.spec.ts index 9d6f6ba3bf..0d30fa87d3 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/localization.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/localization.service.spec.ts @@ -1,3 +1,4 @@ +import { CORE_OPTIONS } from '../tokens/options.token'; import { Router } from '@angular/router'; import { createServiceFactory, SpectatorService, SpyObject } from '@ngneat/spectator/jest'; import { Actions, Store } from '@ngxs/store'; @@ -13,12 +14,15 @@ describe('LocalizationService', () => { service: LocalizationService, entryComponents: [], mocks: [Store, Router], - providers: [{ provide: Actions, useValue: new Subject() }], + providers: [ + { provide: Actions, useValue: new Subject() }, + { provide: CORE_OPTIONS, useValue: { cultureNameLocaleFileMap: {} } }, + ], }); beforeEach(() => { spectator = createService(); - store = spectator.get(Store); + store = spectator.inject(Store); service = spectator.service; }); @@ -52,7 +56,7 @@ describe('LocalizationService', () => { describe('#registerLocale', () => { it('should return registerLocale and then call setRouteReuse', () => { - const router = spectator.get(Router); + const router = spectator.inject(Router); const shouldReuseRoute = () => true; router.routeReuseStrategy = { shouldReuseRoute } as any; diff --git a/npm/ng-packs/packages/core/src/lib/tests/multi-tenancy-utils.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/multi-tenancy-utils.spec.ts new file mode 100644 index 0000000000..f802e22371 --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/tests/multi-tenancy-utils.spec.ts @@ -0,0 +1,103 @@ +import { Component, Injector } from '@angular/core'; +import { createComponentFactory, Spectator } from '@ngneat/spectator/jest'; +import { Store } from '@ngxs/store'; +import clone from 'just-clone'; +import { BehaviorSubject } from 'rxjs'; +import { FindTenantResultDto } from '../models/find-tenant-result-dto'; +import { MultiTenancyService } from '../services/multi-tenancy.service'; +import { parseTenantFromUrl } from '../utils'; + +const environment = { + production: false, + hmr: false, + application: { + baseUrl: 'https://{0}.volosoft.com', + name: 'MyProjectName', + logoUrl: '', + }, + oAuthConfig: { + issuer: 'https://{0}.api.volosoft.com', + clientId: 'MyProjectName_App', + dummyClientSecret: '1q2w3e*', + scope: 'MyProjectName', + oidc: false, + requireHttps: true, + }, + apis: { + default: { + url: 'https://{0}.api.volosoft.com', + }, + abp: { + url: 'https://api.volosoft.com/{0}', + }, + }, +}; + +const setHref = url => { + global.window = Object.create(window); + delete window.location; + Object.defineProperty(window, 'location', { + value: { + href: url, + }, + }); +}; + +@Component({ + selector: 'abp-dummy', + template: '', +}) +export class DummyComponent {} + +describe('MultiTenancyUtils', () => { + let spectator: Spectator; + const createComponent = createComponentFactory({ + component: DummyComponent, + mocks: [Store, MultiTenancyService], + }); + + beforeEach(() => (spectator = createComponent())); + + describe('#parseTenantFromUrl', () => { + test('should get the tenancyName, set replaced environment and call the findTenantByName method of MultiTenancyService', async () => { + const injector = spectator.inject(Injector); + const injectorSpy = jest.spyOn(injector, 'get'); + const store = spectator.inject(Store); + const selectSnapshotSpy = jest.spyOn(store, 'selectSnapshot'); + const dispatchSpy = jest.spyOn(store, 'dispatch'); + const multiTenancyService = spectator.inject(MultiTenancyService); + const findTenantByNameSpy = jest.spyOn(multiTenancyService, 'findTenantByName'); + + injectorSpy.mockReturnValueOnce(spectator.inject(Store)); + injectorSpy.mockReturnValueOnce(multiTenancyService); + selectSnapshotSpy.mockReturnValue(clone(environment)); + + setHref('https://abp.volosoft.com/'); + + dispatchSpy.mockReturnValue(new BehaviorSubject(true)); + findTenantByNameSpy.mockReturnValue( + new BehaviorSubject({ name: 'abp', tenantId: '1', success: true } as FindTenantResultDto), + ); + + parseTenantFromUrl(injector); + + const replacedEnv = { + ...environment, + application: { ...environment.application, baseUrl: 'https://abp.volosoft.com' }, + oAuthConfig: { ...environment.oAuthConfig, issuer: 'https://abp.api.volosoft.com' }, + apis: { + default: { + url: 'https://abp.api.volosoft.com', + }, + abp: { + url: 'https://api.volosoft.com/abp', + }, + }, + }; + + expect(dispatchSpy).toHaveBeenCalledWith({ environment: replacedEnv }); + expect(findTenantByNameSpy).toHaveBeenCalledWith('abp', { __tenant: '' }); + expect(multiTenancyService.domainTenant).toEqual({ id: '1', name: 'abp' }); + }); + }); +}); diff --git a/npm/ng-packs/packages/core/src/lib/tests/permission.directive.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/permission.directive.spec.ts index 1b6dabf05b..4c6356634b 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/permission.directive.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/permission.directive.spec.ts @@ -42,7 +42,7 @@ describe('PermissionDirective', () => { }); it('should do nothing when condition is undefined', () => { - const spy = jest.spyOn(spectator.get(Store), 'select'); + const spy = jest.spyOn(spectator.inject(Store), 'select'); grantedPolicy$.next(false); expect(spy.mock.calls).toHaveLength(0); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/permission.guard.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/permission.guard.spec.ts index dcfd27ab69..cd9108ec87 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/permission.guard.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/permission.guard.spec.ts @@ -52,7 +52,7 @@ describe('PermissionGuard', () => { spectator = createService(); guard = spectator.service; routes = spectator.inject(RoutesService); - store = spectator.get(Store); + store = spectator.inject(Store); }); it('should return true when the grantedPolicy is true', done => { diff --git a/npm/ng-packs/packages/core/src/lib/tests/profile-state.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/profile-state.service.spec.ts index 10db71f6c6..967957b537 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/profile-state.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/profile-state.service.spec.ts @@ -13,7 +13,7 @@ describe('ProfileStateService', () => { beforeEach(() => { spectator = createService(); service = spectator.service; - store = spectator.get(Store); + store = spectator.inject(Store); }); test('should have the all ProfileState static methods', () => { const reg = /(?<=static )(.*)(?=\()/gm; diff --git a/npm/ng-packs/packages/core/src/lib/tests/profile.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/profile.service.spec.ts index 6f4d7f3041..4c7b7ba3c1 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/profile.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/profile.service.spec.ts @@ -14,14 +14,14 @@ describe('ProfileService', () => { beforeEach(() => (spectator = createHttp())); it('should send a GET to my-profile API', () => { - spectator.get(Store).selectSnapshot.andReturn('https://abp.io'); + spectator.inject(Store).selectSnapshot.andReturn('https://abp.io'); spectator.service.get().subscribe(); spectator.expectOne('https://abp.io/api/identity/my-profile', HttpMethod.GET); }); it('should send a POST to change-password API', () => { const mock = { currentPassword: 'test', newPassword: 'test' }; - spectator.get(Store).selectSnapshot.andReturn('https://abp.io'); + spectator.inject(Store).selectSnapshot.andReturn('https://abp.io'); spectator.service.changePassword(mock).subscribe(); const req = spectator.expectOne( 'https://abp.io/api/identity/my-profile/change-password', @@ -38,7 +38,7 @@ describe('ProfileService', () => { surname: 'Doe', phoneNumber: '+123456', }; - spectator.get(Store).selectSnapshot.andReturn('https://abp.io'); + spectator.inject(Store).selectSnapshot.andReturn('https://abp.io'); spectator.service.update(mock).subscribe(); const req = spectator.expectOne('https://abp.io/api/identity/my-profile', HttpMethod.PUT); expect(req.request.body).toEqual(mock); diff --git a/npm/ng-packs/packages/core/src/lib/tests/profile.state.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/profile.state.spec.ts index 380ea53b1c..2ab5f1dff2 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/profile.state.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/profile.state.spec.ts @@ -26,7 +26,7 @@ describe('ProfileState', () => { beforeEach(() => { spectator = createService(); - profileService = spectator.get(ProfileService); + profileService = spectator.inject(ProfileService); state = new ProfileState(profileService); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/replaceable-components.state.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/replaceable-components.state.spec.ts index dc113c433c..be063c4844 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/replaceable-components.state.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/replaceable-components.state.spec.ts @@ -25,7 +25,7 @@ describe('ReplaceableComponentsState', () => { }); it('should add a component to the state', () => { - const store = spectator.get(Store); + const store = spectator.inject(Store); expect(store.selectSnapshot(ReplaceableComponentsState.getAll)).toEqual([]); store.dispatch(new AddReplaceableComponent({ component: DummyComponent, key: 'Dummy' })); expect(store.selectSnapshot(ReplaceableComponentsState.getComponent('Dummy'))).toEqual({ @@ -35,7 +35,7 @@ describe('ReplaceableComponentsState', () => { }); it('should replace a exist component', () => { - const store = spectator.get(Store); + const store = spectator.inject(Store); store.dispatch(new AddReplaceableComponent({ component: DummyComponent, key: 'Dummy' })); store.dispatch(new AddReplaceableComponent({ component: null, key: 'Dummy' })); expect(store.selectSnapshot(ReplaceableComponentsState.getComponent('Dummy'))).toEqual({ @@ -47,7 +47,7 @@ describe('ReplaceableComponentsState', () => { it('should call reloadRoute when reload parameter is given as true to AddReplaceableComponent', async () => { const spy = jest.spyOn(router, 'navigateByUrl'); - const store = spectator.get(Store); + const store = spectator.inject(Store); store.dispatch(new AddReplaceableComponent({ component: DummyComponent, key: 'Dummy' })); store.dispatch(new AddReplaceableComponent({ component: null, key: 'Dummy' }, true)); diff --git a/npm/ng-packs/packages/core/src/lib/tests/rest.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/rest.service.spec.ts index 96bbc197d0..ee9f07f5a8 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/rest.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/rest.service.spec.ts @@ -6,6 +6,7 @@ import { Rest } from '../models'; import { RestService } from '../services/rest.service'; import { ConfigState } from '../states/config.state'; import { CORE_OPTIONS } from '../tokens'; +import { OAuthService } from 'angular-oauth2-oidc'; describe('HttpClient testing', () => { let spectator: SpectatorHttp; @@ -16,11 +17,12 @@ describe('HttpClient testing', () => { dataService: RestService, imports: [NgxsModule.forRoot([ConfigState])], providers: [{ provide: CORE_OPTIONS, useValue: { environment: {} } }], + mocks: [OAuthService], }); beforeEach(() => { spectator = createHttp(); - store = spectator.get(Store); + store = spectator.inject(Store); store.reset({ ConfigState: { environment: { diff --git a/npm/ng-packs/packages/core/src/lib/tests/session-state.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/session-state.service.spec.ts index cff6f9e239..d1c4dd5024 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/session-state.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/session-state.service.spec.ts @@ -17,7 +17,7 @@ describe('SessionStateService', () => { beforeEach(() => { spectator = createService(); service = spectator.service; - store = spectator.get(Store); + store = spectator.inject(Store); }); test('should have the all SessionState static methods', () => { const reg = /(?<=static )(.*)(?=\()/gm; diff --git a/npm/ng-packs/packages/core/src/lib/tests/string-utils.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/string-utils.spec.ts new file mode 100644 index 0000000000..a0642e43ee --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/tests/string-utils.spec.ts @@ -0,0 +1,22 @@ +import { createTokenParser } from '../utils/string-utils'; + +describe('String Utils', () => { + describe('#createTokenParser', () => { + const parseTokens = createTokenParser('{subDomain}.{domain}.{gtld}|{domain}.{gtld}'); + + test.each` + url | subDomain | domain | gtld + ${'www.example.com'} | ${'www'} | ${'example'} | ${'com'} + ${'test.sub.example.com'} | ${'test.sub'} | ${'example'} | ${'com'} + ${'example.com'} | ${undefined} | ${'example'} | ${'com'} + `( + 'should return subDomain as $subDomain, domain as $domain, and gtld as $gtld when url is $url', + ({ url, subDomain, domain, gtld }) => { + const parsed = parseTokens(url); + expect(parsed.subDomain[0]).toBe(subDomain); + expect(parsed.domain[0]).toBe(domain); + expect(parsed.gtld[0]).toBe(gtld); + }, + ); + }); +}); diff --git a/npm/ng-packs/packages/core/src/lib/tests/subscription.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/subscription.service.spec.ts new file mode 100644 index 0000000000..d0c8c4754f --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/tests/subscription.service.spec.ts @@ -0,0 +1,109 @@ +import { of, Subscription, timer } from 'rxjs'; +import { SubscriptionService } from '../services/subscription.service'; + +describe('SubscriptionService', () => { + let service: SubscriptionService; + + beforeEach(() => { + service = new SubscriptionService(); + }); + + afterEach(() => { + service['subscription'].unsubscribe(); + }); + + describe('#addOne', () => { + it('should subscribe to given observable with next and error functions and return the Subscription instance', () => { + const next = jest.fn(); + const error = jest.fn(); + const subscription = service.addOne(of(null), next, error); + expect(subscription).toBeInstanceOf(Subscription); + expect(next).toHaveBeenCalledWith(null); + expect(next).toHaveBeenCalledTimes(1); + expect(error).not.toHaveBeenCalled(); + }); + + it('should subscribe to given observable with observer and return the Subscription instance', () => { + const observer = { next: jest.fn(), complete: jest.fn() }; + const subscription = service.addOne(of(null), observer); + expect(subscription).toBeInstanceOf(Subscription); + expect(observer.next).toHaveBeenCalledWith(null); + expect(observer.next).toHaveBeenCalledTimes(1); + expect(observer.complete).toHaveBeenCalledTimes(1); + }); + }); + + describe('#isClosed', () => { + it('should return true if subscriptions are alive and false if not', () => { + service.addOne(timer(1000), () => {}); + expect(service.isClosed).toBe(false); + + service['subscription'].unsubscribe(); + expect(service.isClosed).toBe(true); + }); + }); + + describe('#closeAll', () => { + it('should close all subscriptions and the parent subscription', () => { + const sub1 = service.addOne(timer(1000), () => {}); + const sub2 = service.addOne(timer(1000), () => {}); + + expect(sub1.closed).toBe(false); + expect(sub2.closed).toBe(false); + expect(service.isClosed).toBe(false); + + service.closeAll(); + + expect(sub1.closed).toBe(true); + expect(sub2.closed).toBe(true); + expect(service.isClosed).toBe(true); + }); + }); + + describe('#reset', () => { + it('should close all subscriptions but not the parent subscription', () => { + const sub1 = service.addOne(timer(1000), () => {}); + const sub2 = service.addOne(timer(1000), () => {}); + + expect(sub1.closed).toBe(false); + expect(sub2.closed).toBe(false); + expect(service.isClosed).toBe(false); + + service.reset(); + + expect(sub1.closed).toBe(true); + expect(sub2.closed).toBe(true); + expect(service.isClosed).toBe(false); + }); + }); + + describe('#closeOne', () => { + it('should unsubscribe from given subscription only', () => { + const sub1 = service.addOne(timer(1000), () => {}); + const sub2 = service.addOne(timer(1000), () => {}); + expect(service.isClosed).toBe(false); + + service.closeOne(sub1); + expect(sub1.closed).toBe(true); + expect(service.isClosed).toBe(false); + + service.closeOne(sub2); + expect(sub2.closed).toBe(true); + expect(service.isClosed).toBe(false); + }); + }); + + describe('#removeOne', () => { + it('should remove given subscription from list of subscriptions', () => { + const sub1 = service.addOne(timer(1000), () => {}); + const sub2 = service.addOne(timer(1000), () => {}); + expect(service.isClosed).toBe(false); + + service.removeOne(sub1); + expect(sub1.closed).toBe(false); + expect(service.isClosed).toBe(false); + + sub1.unsubscribe(); + }); + }); +}); diff --git a/npm/ng-packs/packages/core/src/lib/tokens/options.token.ts b/npm/ng-packs/packages/core/src/lib/tokens/options.token.ts index 2c4dffe9dc..4cbb13da76 100644 --- a/npm/ng-packs/packages/core/src/lib/tokens/options.token.ts +++ b/npm/ng-packs/packages/core/src/lib/tokens/options.token.ts @@ -1,4 +1,15 @@ import { InjectionToken } from '@angular/core'; import { ABP } from '../models/common'; +import differentLocales from '../constants/different-locales'; export const CORE_OPTIONS = new InjectionToken('CORE_OPTIONS'); + +export function coreOptionsFactory({ + cultureNameLocaleFileMap: localeNameMap = {}, + ...options +}: ABP.Root) { + return { + ...options, + cultureNameLocaleFileMap: { ...differentLocales, ...localeNameMap }, + } as ABP.Root; +} diff --git a/npm/ng-packs/packages/core/src/lib/utils/environment-utils.ts b/npm/ng-packs/packages/core/src/lib/utils/environment-utils.ts new file mode 100644 index 0000000000..c8d92603f3 --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/utils/environment-utils.ts @@ -0,0 +1,24 @@ +import { HttpClient } from '@angular/common/http'; +import { Injector } from '@angular/core'; +import { Store } from '@ngxs/store'; +import { catchError, switchMap } from 'rxjs/operators'; +import { SetEnvironment } from '../actions/config.actions'; +import { Config } from '../models/config'; +import { RestOccurError } from '../actions/rest.actions'; + +export function getRemoteEnv(injector: Injector, environment: Partial) { + const { remoteEnv } = environment; + const { headers = {}, method = 'GET', url } = remoteEnv || ({} as Config.RemoteEnv); + if (!url) return Promise.resolve(); + + const http = injector.get(HttpClient); + const store = injector.get(Store); + + return http + .request(method, url, { headers }) + .pipe( + catchError(err => store.dispatch(new RestOccurError(err))), // TODO: Condiser get handle function from a provider + switchMap(env => store.dispatch(new SetEnvironment({ ...environment, ...env }))), + ) + .toPromise(); +} diff --git a/npm/ng-packs/packages/core/src/lib/utils/index.ts b/npm/ng-packs/packages/core/src/lib/utils/index.ts index 6f6136b195..48efb760e9 100644 --- a/npm/ng-packs/packages/core/src/lib/utils/index.ts +++ b/npm/ng-packs/packages/core/src/lib/utils/index.ts @@ -1,12 +1,15 @@ export * from './array-utils'; export * from './common-utils'; +export * from './environment-utils'; export * from './factory-utils'; export * from './form-utils'; export * from './generator-utils'; export * from './initial-utils'; export * from './lazy-load-utils'; export * from './localization-utils'; +export * from './multi-tenancy-utils'; export * from './number-utils'; export * from './route-utils'; export * from './rxjs-utils'; +export * from './string-utils'; export * from './tree-utils'; diff --git a/npm/ng-packs/packages/core/src/lib/utils/initial-utils.ts b/npm/ng-packs/packages/core/src/lib/utils/initial-utils.ts index bfca50bcd2..a79077b89d 100644 --- a/npm/ng-packs/packages/core/src/lib/utils/initial-utils.ts +++ b/npm/ng-packs/packages/core/src/lib/utils/initial-utils.ts @@ -4,27 +4,21 @@ import { Store } from '@ngxs/store'; import { OAuthService } from 'angular-oauth2-oidc'; import { tap } from 'rxjs/operators'; import { GetAppConfiguration } from '../actions/config.actions'; -import differentLocales from '../constants/different-locales'; import { ABP } from '../models/common'; import { ConfigState } from '../states/config.state'; import { CORE_OPTIONS } from '../tokens/options.token'; - -export function configureOAuth(injector: Injector, options: ABP.Root) { - const fn = () => { - const oAuth = injector.get(OAuthService); - oAuth.configure(options.environment.oAuthConfig); - return Promise.resolve(); - }; - - return fn; -} +import { getRemoteEnv } from './environment-utils'; +import { parseTenantFromUrl } from './multi-tenancy-utils'; export function getInitialData(injector: Injector) { - const fn = () => { + const fn = async () => { const store: Store = injector.get(Store); - const { skipGetAppConfiguration } = injector.get(CORE_OPTIONS) as ABP.Root; + const options = injector.get(CORE_OPTIONS) as ABP.Root; + + await getRemoteEnv(injector, options.environment); + await parseTenantFromUrl(injector); - if (skipGetAppConfiguration) return; + if (options.skipGetAppConfiguration) return; return store .dispatch(new GetAppConfiguration()) @@ -35,7 +29,7 @@ export function getInitialData(injector: Injector) { return fn; } -function checkAccessToken(store: Store, injector: Injector) { +export function checkAccessToken(store: Store, injector: Injector) { const oAuth = injector.get(OAuthService); if (oAuth.hasValidAccessToken() && !store.selectSnapshot(ConfigState.getDeep('currentUser.id'))) { oAuth.logOut(); @@ -45,22 +39,25 @@ function checkAccessToken(store: Store, injector: Injector) { export function localeInitializer(injector: Injector) { const fn = () => { const store: Store = injector.get(Store); + const options = injector.get(CORE_OPTIONS); const lang = store.selectSnapshot(state => state.SessionState.language) || 'en'; return new Promise((resolve, reject) => { - registerLocale(lang).then(() => resolve('resolved'), reject); + registerLocale(lang, options.cultureNameLocaleFileMap).then( + () => resolve('resolved'), + reject, + ); }); }; return fn; } -export function registerLocale(locale: string) { +export function registerLocale(locale: string, localeNameMap: ABP.Dictionary) { return import( - /* webpackInclude: /(af|ar|am|ar-SA|as|az-Latn|be|bg|bn-BD|bn-IN|bs|ca|ca-ES-VALENCIA|cs|cy|da|de|de|el|en-GB|en|es|en|es-US|es-MX|et|eu|fa|fi|en|fr|fr|fr-CA|ga|gd|gl|gu|ha|he|hi|hr|hu|hy|id|ig|is|it|it|ja|ka|kk|km|kn|ko|kok|en|en|lb|lt|lv|en|mk|ml|mn|mr|ms|mt|nb|ne|nl|nl-BE|nn|en|or|pa|pa-Arab|pl|en|pt|pt-PT|en|en|ro|ru|rw|pa-Arab|si|sk|sl|sq|sr-Cyrl-BA|sr-Cyrl|sr-Latn|sv|sw|ta|te|tg|th|ti|tk|tn|tr|tt|ug|uk|ur|uz-Latn|vi|wo|xh|yo|zh-Hans|zh-Hant|zu)\.js$/ */ - /* webpackChunkName: "[request]"*/ - `@angular/common/locales/${differentLocales[locale] || locale}.js` + /* webpackChunkName: "_locale-[request]"*/ + `@angular/common/locales/${localeNameMap[locale] || locale}.js` ).then(module => { registerLocaleData(module.default); }); diff --git a/npm/ng-packs/packages/core/src/lib/utils/multi-tenancy-utils.ts b/npm/ng-packs/packages/core/src/lib/utils/multi-tenancy-utils.ts new file mode 100644 index 0000000000..eca1bd3dae --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/utils/multi-tenancy-utils.ts @@ -0,0 +1,74 @@ +import { Injector } from '@angular/core'; +import { Store } from '@ngxs/store'; +import clone from 'just-clone'; +import { switchMap, tap } from 'rxjs/operators'; +import { SetEnvironment } from '../actions'; +import { Config } from '../models/config'; +import { MultiTenancyService } from '../services/multi-tenancy.service'; +import { ConfigState } from '../states/config.state'; +import { createTokenParser } from './string-utils'; + +const tenancyPlaceholder = '{0}'; + +function getCurrentTenancyName(appBaseUrl: string): string { + if (appBaseUrl.charAt(appBaseUrl.length - 1) !== '/') appBaseUrl += '/'; + + const parseTokens = createTokenParser(appBaseUrl); + const token = tenancyPlaceholder.replace(/[}{]/g, ''); + return parseTokens(window.location.href)[token]?.[0]; +} + +export async function parseTenantFromUrl(injector: Injector) { + const store: Store = injector.get(Store); + const multiTenancyService = injector.get(MultiTenancyService); + const environment = store.selectSnapshot(ConfigState.getOne('environment')) as Config.Environment; + + const { baseUrl = '' } = environment.application; + const tenancyName = getCurrentTenancyName(baseUrl); + + if (tenancyName) { + multiTenancyService.isTenantBoxVisible = false; + + return setEnvironment(store, tenancyName) + .pipe( + switchMap(() => multiTenancyService.findTenantByName(tenancyName, { __tenant: '' })), + tap(res => { + multiTenancyService.domainTenant = res.success + ? { id: res.tenantId, name: res.name } + : null; + }), + ) + .toPromise(); + } + + return Promise.resolve(); +} + +function setEnvironment(store: Store, tenancyName: string) { + const environment = clone( + store.selectSnapshot(ConfigState.getOne('environment')), + ) as Config.Environment; + + if (environment.application.baseUrl) { + environment.application.baseUrl = environment.application.baseUrl.replace( + tenancyPlaceholder, + tenancyName, + ); + } + + environment.oAuthConfig.issuer = environment.oAuthConfig.issuer.replace( + tenancyPlaceholder, + tenancyName, + ); + + Object.keys(environment.apis).forEach(api => { + Object.keys(environment.apis[api]).forEach(key => { + environment.apis[api][key] = environment.apis[api][key].replace( + tenancyPlaceholder, + tenancyName, + ); + }); + }); + + return store.dispatch(new SetEnvironment(environment)); +} diff --git a/npm/ng-packs/packages/core/src/lib/utils/rxjs-utils.ts b/npm/ng-packs/packages/core/src/lib/utils/rxjs-utils.ts index ef6a75fe65..7e77bddba5 100644 --- a/npm/ng-packs/packages/core/src/lib/utils/rxjs-utils.ts +++ b/npm/ng-packs/packages/core/src/lib/utils/rxjs-utils.ts @@ -1,3 +1,4 @@ +// tslint:disable: max-line-length import { Observable, Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; @@ -5,13 +6,16 @@ function isFunction(value) { return typeof value === 'function'; } +/** + * @deprecated no longer working, please use SubscriptionService (https://docs.abp.io/en/abp/latest/UI/Angular/Subscription-Service) instead. + */ export const takeUntilDestroy = (componentInstance, destroyMethodName = 'ngOnDestroy') => ( - source: Observable + source: Observable, ) => { const originalDestroy = componentInstance[destroyMethodName]; if (isFunction(originalDestroy) === false) { throw new Error( - `${componentInstance.constructor.name} is using untilDestroyed but doesn't implement ${destroyMethodName}` + `${componentInstance.constructor.name} is using untilDestroyed but doesn't implement ${destroyMethodName}`, ); } if (!componentInstance['__takeUntilDestroy']) { diff --git a/npm/ng-packs/packages/core/src/lib/utils/string-utils.ts b/npm/ng-packs/packages/core/src/lib/utils/string-utils.ts new file mode 100644 index 0000000000..00db697325 --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/utils/string-utils.ts @@ -0,0 +1,17 @@ +export function createTokenParser(format: string) { + return (string: string) => { + const tokens: string[] = []; + const regex = format.replace(/\./g, '\\.').replace(/\{\s?([0-9a-zA-Z]+)\s?\}/g, (_, token) => { + tokens.push(token); + return '(.+)'; + }); + + const matches = (string.match(regex) || []).slice(1); + + return matches.reduce((acc, v, i) => { + const key = tokens[i]; + acc[key] = [...(acc[key] || []), v].filter(Boolean); + return acc; + }, {} as Record); + }; +} diff --git a/npm/ng-packs/packages/feature-management/package.json b/npm/ng-packs/packages/feature-management/package.json index 012c9fa5c0..ba69aa30eb 100644 --- a/npm/ng-packs/packages/feature-management/package.json +++ b/npm/ng-packs/packages/feature-management/package.json @@ -1,13 +1,13 @@ { "name": "@abp/ng.feature-management", - "version": "3.0.4", + "version": "3.0.5", "homepage": "https://abp.io", "repository": { "type": "git", "url": "https://github.com/abpframework/abp.git" }, "dependencies": { - "@abp/ng.theme.shared": "~3.0.4", + "@abp/ng.theme.shared": "~3.0.5", "tslib": "^2.0.0" }, "publishConfig": { diff --git a/npm/ng-packs/packages/feature-management/src/lib/tests/feature-management-state.service.spec.ts b/npm/ng-packs/packages/feature-management/src/lib/tests/feature-management-state.service.spec.ts index 59dc3701a2..5d59755cbd 100644 --- a/npm/ng-packs/packages/feature-management/src/lib/tests/feature-management-state.service.spec.ts +++ b/npm/ng-packs/packages/feature-management/src/lib/tests/feature-management-state.service.spec.ts @@ -16,7 +16,7 @@ describe('FeatureManagementStateService', () => { beforeEach(() => { spectator = createService(); service = spectator.service; - store = spectator.get(Store); + store = spectator.inject(Store); }); test('should have the all FeatureManagementState static methods', () => { diff --git a/npm/ng-packs/packages/identity/package.json b/npm/ng-packs/packages/identity/package.json index 9277b0dfcf..8f87f07827 100644 --- a/npm/ng-packs/packages/identity/package.json +++ b/npm/ng-packs/packages/identity/package.json @@ -1,14 +1,14 @@ { "name": "@abp/ng.identity", - "version": "3.0.4", + "version": "3.0.5", "homepage": "https://abp.io", "repository": { "type": "git", "url": "https://github.com/abpframework/abp.git" }, "dependencies": { - "@abp/ng.permission-management": "~3.0.4", - "@abp/ng.theme.shared": "~3.0.4", + "@abp/ng.permission-management": "~3.0.5", + "@abp/ng.theme.shared": "~3.0.5", "tslib": "^2.0.0" }, "publishConfig": { diff --git a/npm/ng-packs/packages/identity/src/lib/tests/identity-state.service.spec.ts b/npm/ng-packs/packages/identity/src/lib/tests/identity-state.service.spec.ts index dcf3193eea..ac0952d9c0 100644 --- a/npm/ng-packs/packages/identity/src/lib/tests/identity-state.service.spec.ts +++ b/npm/ng-packs/packages/identity/src/lib/tests/identity-state.service.spec.ts @@ -13,7 +13,7 @@ describe('IdentityStateService', () => { beforeEach(() => { spectator = createService(); service = spectator.service; - store = spectator.get(Store); + store = spectator.inject(Store); }); test('should have the all IdentityState static methods', () => { diff --git a/npm/ng-packs/packages/permission-management/package.json b/npm/ng-packs/packages/permission-management/package.json index 9eaed1cd60..5b5f028083 100644 --- a/npm/ng-packs/packages/permission-management/package.json +++ b/npm/ng-packs/packages/permission-management/package.json @@ -1,13 +1,13 @@ { "name": "@abp/ng.permission-management", - "version": "3.0.4", + "version": "3.0.5", "homepage": "https://abp.io", "repository": { "type": "git", "url": "https://github.com/abpframework/abp.git" }, "dependencies": { - "@abp/ng.theme.shared": "~3.0.4", + "@abp/ng.theme.shared": "~3.0.5", "tslib": "^2.0.0" }, "publishConfig": { diff --git a/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.ts b/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.ts index b6b1dcbdd3..c2220a30d2 100644 --- a/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.ts +++ b/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.ts @@ -1,7 +1,8 @@ +import { ApplicationConfiguration, ConfigState, GetAppConfiguration } from '@abp/ng.core'; import { Component, EventEmitter, Input, Output, Renderer2, TrackByFunction } from '@angular/core'; import { Select, Store } from '@ngxs/store'; -import { Observable } from 'rxjs'; -import { finalize, map, pluck, take, tap } from 'rxjs/operators'; +import { Observable, of } from 'rxjs'; +import { finalize, map, pluck, switchMap, take, tap } from 'rxjs/operators'; import { GetPermissions, UpdatePermissions } from '../actions/permission-management.actions'; import { PermissionManagement } from '../models/permission-management'; import { PermissionManagementState } from '../states/permission-management.state'; @@ -203,7 +204,6 @@ export class PermissionManagementComponent } submit() { - this.modalBusy = true; const unchangedPermissions = getPermissions( this.store.selectSnapshot(PermissionManagementState.getPermissionGroups), ); @@ -217,23 +217,29 @@ export class PermissionManagementComponent ) .map(({ name, isGranted }) => ({ name, isGranted })); - if (changedPermissions.length) { - this.store - .dispatch( - new UpdatePermissions({ - providerKey: this.providerKey, - providerName: this.providerName, - permissions: changedPermissions, - }), - ) - .pipe(finalize(() => (this.modalBusy = false))) - .subscribe(() => { - this.visible = false; - }); - } else { - this.modalBusy = false; + if (!changedPermissions.length) { this.visible = false; + return; } + + this.modalBusy = true; + this.store + .dispatch( + new UpdatePermissions({ + providerKey: this.providerKey, + providerName: this.providerName, + permissions: changedPermissions, + }), + ) + .pipe( + switchMap(() => + this.shouldFetchAppConfig() ? this.store.dispatch(GetAppConfiguration) : of(null), + ), + finalize(() => (this.modalBusy = false)), + ) + .subscribe(() => { + this.visible = false; + }); } openModal() { @@ -268,6 +274,18 @@ export class PermissionManagementComponent 0, ); } + + shouldFetchAppConfig() { + const currentUser = this.store.selectSnapshot( + ConfigState.getOne('currentUser'), + ) as ApplicationConfiguration.CurrentUser; + + if (this.providerName === 'R') return currentUser.roles.some(role => role === this.providerKey); + + if (this.providerName === 'U') return currentUser.id === this.providerKey; + + return false; + } } function findMargin( diff --git a/npm/ng-packs/packages/permission-management/src/lib/tests/permission-management-state.service.spec.ts b/npm/ng-packs/packages/permission-management/src/lib/tests/permission-management-state.service.spec.ts index f1d344c7ac..bdc2c9d490 100644 --- a/npm/ng-packs/packages/permission-management/src/lib/tests/permission-management-state.service.spec.ts +++ b/npm/ng-packs/packages/permission-management/src/lib/tests/permission-management-state.service.spec.ts @@ -16,7 +16,7 @@ describe('PermissionManagementStateService', () => { beforeEach(() => { spectator = createService(); service = spectator.service; - store = spectator.get(Store); + store = spectator.inject(Store); }); test('should have the all PermissionManagementState static methods', () => { const reg = /(?<=static )(.*)(?=\()/gm; diff --git a/npm/ng-packs/packages/setting-management/package.json b/npm/ng-packs/packages/setting-management/package.json index bee053b480..0819239b4e 100644 --- a/npm/ng-packs/packages/setting-management/package.json +++ b/npm/ng-packs/packages/setting-management/package.json @@ -1,13 +1,13 @@ { "name": "@abp/ng.setting-management", - "version": "3.0.4", + "version": "3.0.5", "homepage": "https://abp.io", "repository": { "type": "git", "url": "https://github.com/abpframework/abp.git" }, "dependencies": { - "@abp/ng.theme.shared": "~3.0.4", + "@abp/ng.theme.shared": "~3.0.5", "tslib": "^2.0.0" }, "publishConfig": { diff --git a/npm/ng-packs/packages/tenant-management/package.json b/npm/ng-packs/packages/tenant-management/package.json index ad4c289cd4..e6cae67f3e 100644 --- a/npm/ng-packs/packages/tenant-management/package.json +++ b/npm/ng-packs/packages/tenant-management/package.json @@ -1,14 +1,14 @@ { "name": "@abp/ng.tenant-management", - "version": "3.0.4", + "version": "3.0.5", "homepage": "https://abp.io", "repository": { "type": "git", "url": "https://github.com/abpframework/abp.git" }, "dependencies": { - "@abp/ng.feature-management": "~3.0.4", - "@abp/ng.theme.shared": "~3.0.4", + "@abp/ng.feature-management": "~3.0.5", + "@abp/ng.theme.shared": "~3.0.5", "tslib": "^2.0.0" }, "publishConfig": { diff --git a/npm/ng-packs/packages/tenant-management/src/lib/tests/tenant-management-state.service.spec.ts b/npm/ng-packs/packages/tenant-management/src/lib/tests/tenant-management-state.service.spec.ts index bd9017a2c7..befd819b70 100644 --- a/npm/ng-packs/packages/tenant-management/src/lib/tests/tenant-management-state.service.spec.ts +++ b/npm/ng-packs/packages/tenant-management/src/lib/tests/tenant-management-state.service.spec.ts @@ -16,7 +16,7 @@ describe('TenantManagementStateService', () => { beforeEach(() => { spectator = createService(); service = spectator.service; - store = spectator.get(Store); + store = spectator.inject(Store); }); test('should have the all TenantManagementState static methods', () => { diff --git a/npm/ng-packs/packages/theme-basic/package.json b/npm/ng-packs/packages/theme-basic/package.json index 85fd419ec1..34ce1d0879 100644 --- a/npm/ng-packs/packages/theme-basic/package.json +++ b/npm/ng-packs/packages/theme-basic/package.json @@ -1,13 +1,13 @@ { "name": "@abp/ng.theme.basic", - "version": "3.0.4", + "version": "3.0.5", "homepage": "https://abp.io", "repository": { "type": "git", "url": "https://github.com/abpframework/abp.git" }, "dependencies": { - "@abp/ng.theme.shared": "~3.0.4", + "@abp/ng.theme.shared": "~3.0.5", "tslib": "^2.0.0" }, "publishConfig": { diff --git a/npm/ng-packs/packages/theme-basic/src/lib/components/application-layout/application-layout.component.ts b/npm/ng-packs/packages/theme-basic/src/lib/components/application-layout/application-layout.component.ts index d3192057bb..b37516600e 100644 --- a/npm/ng-packs/packages/theme-basic/src/lib/components/application-layout/application-layout.component.ts +++ b/npm/ng-packs/packages/theme-basic/src/lib/components/application-layout/application-layout.component.ts @@ -1,4 +1,4 @@ -import { eLayoutType, takeUntilDestroy } from '@abp/ng.core'; +import { eLayoutType, SubscriptionService } from '@abp/ng.core'; import { collapseWithMargin, slideFromBottom } from '@abp/ng.theme.shared'; import { AfterViewInit, Component, OnDestroy } from '@angular/core'; import { fromEvent } from 'rxjs'; @@ -9,6 +9,7 @@ import { eThemeBasicComponents } from '../../enums/components'; selector: 'abp-layout-application', templateUrl: './application-layout.component.html', animations: [slideFromBottom, collapseWithMargin], + providers: [SubscriptionService], }) export class ApplicationLayoutComponent implements AfterViewInit, OnDestroy { // required for dynamic component @@ -24,6 +25,8 @@ export class ApplicationLayoutComponent implements AfterViewInit, OnDestroy { navItemsComponentKey = eThemeBasicComponents.NavItems; + constructor(private subscription: SubscriptionService) {} + private checkWindowWidth() { setTimeout(() => { if (window.innerWidth < 992) { @@ -43,11 +46,8 @@ export class ApplicationLayoutComponent implements AfterViewInit, OnDestroy { ngAfterViewInit() { this.checkWindowWidth(); - fromEvent(window, 'resize') - .pipe(takeUntilDestroy(this), debounceTime(150)) - .subscribe(() => { - this.checkWindowWidth(); - }); + const resize$ = fromEvent(window, 'resize').pipe(debounceTime(150)); + this.subscription.addOne(resize$, () => this.checkWindowWidth()); } ngOnDestroy() {} diff --git a/npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/nav-items.component.html b/npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/nav-items.component.html index 955ccb8878..d01e6cda38 100644 --- a/npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/nav-items.component.html +++ b/npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/nav-items.component.html @@ -1,16 +1,18 @@ diff --git a/npm/ng-packs/packages/theme-basic/src/lib/theme-basic.module.ts b/npm/ng-packs/packages/theme-basic/src/lib/theme-basic.module.ts index f599124e83..bfacbcf9a7 100644 --- a/npm/ng-packs/packages/theme-basic/src/lib/theme-basic.module.ts +++ b/npm/ng-packs/packages/theme-basic/src/lib/theme-basic.module.ts @@ -42,6 +42,20 @@ export const LAYOUTS = [ApplicationLayoutComponent, AccountLayoutComponent, Empt NgbCollapseModule, NgbDropdownModule, NgxValidateCoreModule, + ], + entryComponents: [...LAYOUTS, ValidationErrorComponent, CurrentUserComponent, LanguagesComponent], +}) +export class ThemeBasicModule { + static forRoot(): ModuleWithProviders { + return { + ngModule: RootThemeBasicModule, + providers: [BASIC_THEME_NAV_ITEM_PROVIDERS, BASIC_THEME_STYLES_PROVIDERS], + }; + } +} + +@NgModule({ + imports: [ NgxValidateCoreModule.forRoot({ targetSelector: '.form-group', blueprints: { @@ -63,13 +77,5 @@ export const LAYOUTS = [ApplicationLayoutComponent, AccountLayoutComponent, Empt errorTemplate: ValidationErrorComponent, }), ], - entryComponents: [...LAYOUTS, ValidationErrorComponent, CurrentUserComponent, LanguagesComponent], }) -export class ThemeBasicModule { - static forRoot(): ModuleWithProviders { - return { - ngModule: ThemeBasicModule, - providers: [BASIC_THEME_NAV_ITEM_PROVIDERS, BASIC_THEME_STYLES_PROVIDERS], - }; - } -} +export class RootThemeBasicModule {} diff --git a/npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-form/extensible-form-prop.component.html b/npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-form/extensible-form-prop.component.html index ef1647b540..30d115216b 100644 --- a/npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-form/extensible-form-prop.component.html +++ b/npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-form/extensible-form-prop.component.html @@ -1,15 +1,13 @@
    - + @@ -19,28 +17,26 @@ {{ prop.displayName | abpLocalization }} {{ asterisk }}
    - + - + - + - + diff --git a/npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-form/extensible-form-prop.component.ts b/npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-form/extensible-form-prop.component.ts index f796c2d5fe..4a28b14f22 100644 --- a/npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-form/extensible-form-prop.component.ts +++ b/npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-form/extensible-form-prop.component.ts @@ -1,14 +1,17 @@ -import { TrackByService } from '@abp/ng.core'; +import { ABP, TrackByService } from '@abp/ng.core'; import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, Optional, + SimpleChanges, SkipSelf, + OnChanges, } from '@angular/core'; -import { ControlContainer, Validators } from '@angular/forms'; +import { ControlContainer, Validators, ValidatorFn } from '@angular/forms'; import { NgbDateAdapter, NgbTimeAdapter } from '@ng-bootstrap/ng-bootstrap'; +import { Observable, of } from 'rxjs'; import { DateAdapter } from '../../adapters/date.adapter'; import { TimeAdapter } from '../../adapters/time.adapter'; import { ePropType } from '../../enums/props.enum'; @@ -30,15 +33,23 @@ import { selfFactory } from '../../utils/factory.util'; { provide: NgbTimeAdapter, useClass: TimeAdapter }, ], }) -export class ExtensibleFormPropComponent { +export class ExtensibleFormPropComponent implements OnChanges { @Input() data: PropData; @Input() prop: FormProp; + options$: Observable[]> = of([]); + + validators: ValidatorFn[] = []; + + readonly: boolean; + + disabled: boolean; + constructor(public readonly cdRef: ChangeDetectorRef, public readonly track: TrackByService) {} - getAsterisk(prop: FormProp, data: PropData): string { - return prop.validators(data).some(validator => validator === Validators.required) ? '*' : ''; + get asterisk(): string { + return this.validators.some(validator => validator === Validators.required) ? '*' : ''; } getComponent(prop: FormProp): string { @@ -77,4 +88,16 @@ export class ExtensibleFormPropComponent { return 'hidden'; } } + + ngOnChanges({ prop }: SimpleChanges) { + const options = prop.currentValue.options; + const readonly = prop.currentValue.readonly; + const disabled = prop.currentValue.disabled; + const validators = prop.currentValue.validators; + + if (options) this.options$ = options(this.data); + if (readonly) this.readonly = readonly(this.data); + if (disabled) this.disabled = disabled(this.data); + if (validators) this.validators = validators(this.data); + } } diff --git a/npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-form/extensible-form.component.ts b/npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-form/extensible-form.component.ts index 995b29b8d1..f15f173faa 100644 --- a/npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-form/extensible-form.component.ts +++ b/npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-form/extensible-form.component.ts @@ -1,20 +1,16 @@ import { TrackByService } from '@abp/ng.core'; import { - AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, Input, - OnDestroy, Optional, QueryList, SkipSelf, ViewChildren, } from '@angular/core'; import { ControlContainer, FormGroup } from '@angular/forms'; -import { Subscription } from 'rxjs'; -import { debounceTime } from 'rxjs/operators'; import { EXTRA_PROPERTIES_KEY } from '../../constants/extra-properties'; import { FormPropList } from '../../models/form-props'; import { ExtensionsService } from '../../services/extensions.service'; @@ -35,7 +31,7 @@ import { ExtensibleFormPropComponent } from './extensible-form-prop.component'; }, ], }) -export class ExtensibleFormComponent implements AfterViewInit, OnDestroy { +export class ExtensibleFormComponent { @ViewChildren(ExtensibleFormPropComponent) formProps: QueryList; @@ -46,7 +42,6 @@ export class ExtensibleFormComponent implements AfterViewInit, OnDestro this.record = record; } - private subscription = new Subscription(); extraPropertiesKey = EXTRA_PROPERTIES_KEY; propList: FormPropList; record: R; @@ -66,17 +61,4 @@ export class ExtensibleFormComponent implements AfterViewInit, OnDestro private extensions: ExtensionsService, @Inject(EXTENSIONS_IDENTIFIER) private identifier: string, ) {} - - ngAfterViewInit() { - this.subscription.add( - this.form.statusChanges.pipe(debounceTime(0)).subscribe(() => { - this.formProps.forEach(prop => prop.cdRef.markForCheck()); - this.cdRef.detectChanges(); - }), - ); - } - - ngOnDestroy() { - this.subscription.unsubscribe(); - } } diff --git a/npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-table/extensible-table.component.html b/npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-table/extensible-table.component.html index d30b005a98..dc09088430 100644 --- a/npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-table/extensible-table.component.html +++ b/npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-table/extensible-table.component.html @@ -27,11 +27,11 @@ [sortable]="prop.sortable" > - -
    + +
    diff --git a/npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-table/extensible-table.component.ts b/npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-table/extensible-table.component.ts index 7768d70c2c..adacc4efa8 100644 --- a/npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-table/extensible-table.component.ts +++ b/npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-table/extensible-table.component.ts @@ -9,6 +9,11 @@ import { LOCALE_ID, TemplateRef, TrackByFunction, + Type, + InjectionToken, + InjectFlags, + SimpleChanges, + OnChanges, } from '@angular/core'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; @@ -25,7 +30,7 @@ const DEFAULT_ACTIONS_COLUMN_WIDTH = 150; templateUrl: './extensible-table.component.html', changeDetection: ChangeDetectionStrategy.OnPush, }) -export class ExtensibleTableComponent { +export class ExtensibleTableComponent implements OnChanges { @Input() actionsText: string; @Input() data: R[]; @Input() list: ListService; @@ -35,6 +40,8 @@ export class ExtensibleTableComponent { } @Input() actionsTemplate: TemplateRef; + getInjected: (token: Type | InjectionToken, notFoundValue?: T, flags?: InjectFlags) => T; + readonly columnWidths: number[]; readonly propList: EntityPropList; @@ -42,6 +49,8 @@ export class ExtensibleTableComponent { readonly trackByFn: TrackByFunction> = (_, item) => item.name; constructor(@Inject(LOCALE_ID) private locale: string, injector: Injector) { + // tslint:disable-next-line + this.getInjected = injector.get.bind(injector); const extensions = injector.get(ExtensionsService); const name = injector.get(EXTENSIONS_IDENTIFIER); this.propList = extensions.entityProps.get(name).props; @@ -85,4 +94,20 @@ export class ExtensibleTableComponent { }), ); } + + ngOnChanges({ data }: SimpleChanges) { + if (!data?.currentValue) return; + + this.data = data.currentValue.map((record, index) => { + this.propList.forEach(prop => { + const propData = { getInjected: this.getInjected, record, index } as any; + record[`_${prop.value.name}`] = { + visible: prop.value.visible(propData), + value: this.getContent(prop.value, propData), + }; + }); + + return record; + }); + } } diff --git a/npm/ng-packs/packages/theme-shared/extensions/src/tests/form-props.util.spec.ts b/npm/ng-packs/packages/theme-shared/extensions/src/tests/form-props.util.spec.ts index af1edbb9fb..123912cb9a 100644 --- a/npm/ng-packs/packages/theme-shared/extensions/src/tests/form-props.util.spec.ts +++ b/npm/ng-packs/packages/theme-shared/extensions/src/tests/form-props.util.spec.ts @@ -126,8 +126,8 @@ describe('Form Prop Utils', () => { function* getInjected(spectator: SpectatorService) { yield spectator.service; - yield spectator.get(EXTENSIONS_IDENTIFIER); - yield spectator.get(LocalizationService); + yield spectator.inject(EXTENSIONS_IDENTIFIER); + yield spectator.inject(LocalizationService); } interface Foo { diff --git a/npm/ng-packs/packages/theme-shared/extensions/src/tests/state.util.spec.ts b/npm/ng-packs/packages/theme-shared/extensions/src/tests/state.util.spec.ts index 9592d4c9c1..86060b8597 100644 --- a/npm/ng-packs/packages/theme-shared/extensions/src/tests/state.util.spec.ts +++ b/npm/ng-packs/packages/theme-shared/extensions/src/tests/state.util.spec.ts @@ -13,6 +13,7 @@ import { getObjectExtensionEntitiesFromStore, mapEntitiesToContributors, } from '../lib/utils/state.util'; +import { OAuthService } from 'angular-oauth2-oidc'; describe('State Utils', () => { let spectator: SpectatorService; @@ -21,7 +22,7 @@ describe('State Utils', () => { const createService = createServiceFactory({ service: Store, imports: [NgxsModule.forRoot([ConfigState])], - mocks: [HttpClient], + mocks: [HttpClient, OAuthService], }); beforeEach(() => { diff --git a/npm/ng-packs/packages/theme-shared/package.json b/npm/ng-packs/packages/theme-shared/package.json index 542dd18d5f..1558f1c824 100644 --- a/npm/ng-packs/packages/theme-shared/package.json +++ b/npm/ng-packs/packages/theme-shared/package.json @@ -1,16 +1,16 @@ { "name": "@abp/ng.theme.shared", - "version": "3.0.4", + "version": "3.0.5", "homepage": "https://abp.io", "repository": { "type": "git", "url": "https://github.com/abpframework/abp.git" }, "dependencies": { - "@abp/ng.core": "~3.0.4", + "@abp/ng.core": "~3.0.5", "@fortawesome/fontawesome-free": "^5.13.1", "@ng-bootstrap/ng-bootstrap": "^6.1.0", - "@ngx-validate/core": "^0.0.9", + "@ngx-validate/core": "^0.0.10", "@swimlane/ngx-datatable": "^17.0.0", "bootstrap": "^4.5.0", "chart.js": "^2.9.3", diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/breadcrumb/breadcrumb.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/breadcrumb/breadcrumb.component.ts index c556776b63..11270c6dc1 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/breadcrumb/breadcrumb.component.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/breadcrumb/breadcrumb.component.ts @@ -1,11 +1,5 @@ -import { ABP, getRoutePath, RoutesService, takeUntilDestroy, TreeNode } from '@abp/ng.core'; -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - OnDestroy, - OnInit, -} from '@angular/core'; +import { ABP, getRoutePath, RoutesService, TreeNode, SubscriptionService } from '@abp/ng.core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core'; import { NavigationEnd, Router } from '@angular/router'; import { filter, map, startWith } from 'rxjs/operators'; import { eThemeSharedRouteNames } from '../../enums'; @@ -14,28 +8,27 @@ import { eThemeSharedRouteNames } from '../../enums'; selector: 'abp-breadcrumb', templateUrl: './breadcrumb.component.html', changeDetection: ChangeDetectionStrategy.OnPush, + providers: [SubscriptionService], }) -export class BreadcrumbComponent implements OnDestroy, OnInit { +export class BreadcrumbComponent implements OnInit { segments: Partial[] = []; constructor( public readonly cdRef: ChangeDetectorRef, private router: Router, private routes: RoutesService, + private subscription: SubscriptionService, ) {} - ngOnDestroy() {} - ngOnInit(): void { - this.router.events - .pipe( - takeUntilDestroy(this), + this.subscription.addOne( + this.router.events.pipe( filter(event => event instanceof NavigationEnd), // tslint:disable-next-line:deprecation startWith(null), map(() => this.routes.search({ path: getRoutePath(this.router) })), - ) - .subscribe(route => { + ), + route => { this.segments = []; if (route) { let node = { parent: route } as TreeNode; @@ -48,7 +41,8 @@ export class BreadcrumbComponent implements OnDestroy, OnInit { this.cdRef.detectChanges(); } - }); + }, + ); } } diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/http-error-wrapper/http-error-wrapper.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/http-error-wrapper/http-error-wrapper.component.ts index 0ada431f4f..fb1b2d4a6c 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/http-error-wrapper/http-error-wrapper.component.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/http-error-wrapper/http-error-wrapper.component.ts @@ -1,4 +1,4 @@ -import { Config, takeUntilDestroy } from '@abp/ng.core'; +import { Config, SubscriptionService } from '@abp/ng.core'; import { AfterViewInit, ApplicationRef, @@ -20,6 +20,7 @@ import snq from 'snq'; selector: 'abp-http-error-wrapper', templateUrl: './http-error-wrapper.component.html', styleUrls: ['http-error-wrapper.component.scss'], + providers: [SubscriptionService], }) export class HttpErrorWrapperComponent implements AfterViewInit, OnDestroy, OnInit { appRef: ApplicationRef; @@ -51,6 +52,8 @@ export class HttpErrorWrapperComponent implements AfterViewInit, OnDestroy, OnIn return this.status ? `[${this.status}]` : ''; } + constructor(private subscription: SubscriptionService) {} + ngOnInit() { this.backgroundColor = snq(() => window.getComputedStyle(document.body).getPropertyValue('background-color')) || @@ -71,15 +74,11 @@ export class HttpErrorWrapperComponent implements AfterViewInit, OnDestroy, OnIn customComponentRef.changeDetectorRef.detectChanges(); } - fromEvent(document, 'keyup') - .pipe( - takeUntilDestroy(this), - debounceTime(150), - filter((key: KeyboardEvent) => key && key.key === 'Escape'), - ) - .subscribe(() => { - this.destroy(); - }); + const keyup$ = fromEvent(document, 'keyup').pipe( + debounceTime(150), + filter((key: KeyboardEvent) => key && key.key === 'Escape'), + ); + this.subscription.addOne(keyup$, () => this.destroy()); } ngOnDestroy() {} diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/loader-bar/loader-bar.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/loader-bar/loader-bar.component.ts index 47ff4d057d..f94b6b3656 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/loader-bar/loader-bar.component.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/loader-bar/loader-bar.component.ts @@ -1,7 +1,6 @@ -import { StartLoader, StopLoader } from '@abp/ng.core'; +import { StartLoader, StopLoader, SubscriptionService } from '@abp/ng.core'; import { ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular/core'; import { NavigationEnd, NavigationError, NavigationStart, Router } from '@angular/router'; -import { takeUntilDestroy } from '@ngx-validate/core'; import { Actions, ofActionSuccessful } from '@ngxs/store'; import { Subscription, timer } from 'rxjs'; import { filter } from 'rxjs/operators'; @@ -22,6 +21,7 @@ import { filter } from 'rxjs/operators';
    `, styleUrls: ['./loader-bar.component.scss'], + providers: [SubscriptionService], }) export class LoaderBarComponent implements OnDestroy, OnInit { protected _isLoading: boolean; @@ -77,36 +77,38 @@ export class LoaderBarComponent implements OnDestroy, OnInit { return `0 0 10px rgba(${this.color}, 0.5)`; } - constructor(private actions: Actions, private router: Router, private cdRef: ChangeDetectorRef) {} + constructor( + private actions: Actions, + private router: Router, + private cdRef: ChangeDetectorRef, + private subscription: SubscriptionService, + ) {} private subscribeToLoadActions() { - this.actions - .pipe( - ofActionSuccessful(StartLoader, StopLoader), - filter(this.filter), - takeUntilDestroy(this), - ) - .subscribe(action => { + this.subscription.addOne( + this.actions.pipe(ofActionSuccessful(StartLoader, StopLoader), filter(this.filter)), + action => { if (action instanceof StartLoader) this.startLoading(); else this.stopLoading(); - }); + }, + ); } private subscribeToRouterEvents() { - this.router.events - .pipe( + this.subscription.addOne( + this.router.events.pipe( filter( event => event instanceof NavigationStart || event instanceof NavigationEnd || event instanceof NavigationError, ), - takeUntilDestroy(this), - ) - .subscribe(event => { + ), + event => { if (event instanceof NavigationStart) this.startLoading(); else this.stopLoading(); - }); + }, + ); } ngOnInit() { diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/modal/modal.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/modal/modal.component.ts index 83d62f0b5a..b16b2bb14c 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/modal/modal.component.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/modal/modal.component.ts @@ -1,4 +1,4 @@ -import { takeUntilDestroy } from '@abp/ng.core'; +import { SubscriptionService } from '@abp/ng.core'; import { Component, ContentChild, @@ -27,7 +27,7 @@ export type ModalSize = 'sm' | 'md' | 'lg' | 'xl'; templateUrl: './modal.component.html', animations: [fadeAnimation], styleUrls: ['./modal.component.scss'], - providers: [ModalService], + providers: [ModalService, SubscriptionService], }) export class ModalComponent implements OnDestroy { @Input() @@ -60,11 +60,11 @@ export class ModalComponent implements OnDestroy { @ContentChild(ButtonComponent, { static: false, read: ButtonComponent }) abpSubmit: ButtonComponent; - @ContentChild('abpHeader', {static: false}) abpHeader: TemplateRef; + @ContentChild('abpHeader', { static: false }) abpHeader: TemplateRef; - @ContentChild('abpBody', {static: false}) abpBody: TemplateRef; + @ContentChild('abpBody', { static: false }) abpBody: TemplateRef; - @ContentChild('abpFooter', {static: false}) abpFooter: TemplateRef; + @ContentChild('abpFooter', { static: false }) abpFooter: TemplateRef; @ContentChild('abpClose', { static: false, read: ElementRef }) abpClose: ElementRef; @@ -103,14 +103,15 @@ export class ModalComponent implements OnDestroy { private renderer: Renderer2, private confirmationService: ConfirmationService, private modalService: ModalService, + private subscription: SubscriptionService, ) { this.initToggleStream(); } private initToggleStream() { - this.toggle$ - .pipe(takeUntilDestroy(this), debounceTime(0), distinctUntilChanged()) - .subscribe(value => this.toggle(value)); + this.subscription.addOne(this.toggle$.pipe(debounceTime(0), distinctUntilChanged()), value => + this.toggle(value), + ); } private toggle(value: boolean) { diff --git a/npm/ng-packs/packages/theme-shared/src/lib/models/nav-item.ts b/npm/ng-packs/packages/theme-shared/src/lib/models/nav-item.ts index ea3d5c45a2..7fcb154cbf 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/models/nav-item.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/models/nav-item.ts @@ -1,10 +1,15 @@ import { Type } from '@angular/core'; -export interface NavItem { +export class NavItem { id: string | number; component?: Type; html?: string; action?: () => void; order?: number; requiredPolicy?: string; + visible?: () => boolean; + constructor(props: Partial) { + props = { ...props, visible: props.visible || (() => true) }; + Object.assign(this, props); + } } diff --git a/npm/ng-packs/packages/theme-shared/src/lib/services/nav-items.service.ts b/npm/ng-packs/packages/theme-shared/src/lib/services/nav-items.service.ts index 874fc4b6e3..ca361a2940 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/services/nav-items.service.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/services/nav-items.service.ts @@ -14,30 +14,31 @@ export class NavItemsService { return this._items$.asObservable(); } - addItems(items: NavItem[]) { - this._items$.next([...this.items, ...items].sort(sortItems)); + addItems(newItems: NavItem[]) { + const items = [...this.items]; + newItems.forEach(item => items.push(new NavItem(item))); + items.sort(sortItems); + this._items$.next(items); } removeItem(id: string | number) { const index = this.items.findIndex(item => item.id === id); - if (index > -1) { - this._items$.next([...this.items.slice(0, index), ...this.items.slice(index + 1)]); - } + if (index < 0) return; + + const items = [...this.items.slice(0, index), ...this.items.slice(index + 1)]; + this._items$.next(items); } patchItem(id: string | number, item: Partial>) { const index = this.items.findIndex(i => i.id === id); - if (index > -1) { - this._items$.next( - [ - ...this.items.slice(0, index), - { ...this.items[index], ...item }, - ...this.items.slice(index + 1), - ].sort(sortItems), - ); - } + if (index < 0) return; + + const items = [...this.items]; + items[index] = new NavItem({ ...items[index], ...item }); + items.sort(sortItems); + this._items$.next(items); } } diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/append-content.token.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/append-content.token.spec.ts index bcf48a0125..fe3b0ac37c 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/append-content.token.spec.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/append-content.token.spec.ts @@ -15,8 +15,8 @@ describe('AppendContentToken', () => { beforeEach(() => (spectator = createComponent())); it('should insert a style element to the DOM', () => { - spectator.get(THEME_SHARED_APPEND_CONTENT); - expect(spectator.get(DomInsertionService).has(styles)).toBe(true); + spectator.inject(THEME_SHARED_APPEND_CONTENT); + expect(spectator.inject(DomInsertionService).has(styles)).toBe(true); }); it('should be loaded the chart.js', done => { @@ -25,6 +25,6 @@ describe('AppendContentToken', () => { done(); }); - spectator.get(THEME_SHARED_APPEND_CONTENT); + spectator.inject(THEME_SHARED_APPEND_CONTENT); }); }); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/breadcrumb.component.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/breadcrumb.component.spec.ts index bb8522c4a5..1712baef99 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/breadcrumb.component.spec.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/breadcrumb.component.spec.ts @@ -54,8 +54,8 @@ describe('BreadcrumbComponent', () => { beforeEach(() => { spectator = createRouting(); - routes = spectator.get(RoutesService); - store = spectator.get(Store); + routes = spectator.inject(RoutesService); + store = spectator.inject(Store); }); it('should display the breadcrumb', async () => { diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/error.handler.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/error.handler.spec.ts index bacbb93ed0..89e9239b6e 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/error.handler.spec.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/error.handler.spec.ts @@ -52,7 +52,7 @@ describe('ErrorHandler', () => { beforeEach(() => { spectator = createService(); service = spectator.service; - store = spectator.get(Store); + store = spectator.inject(Store); store.selectSnapshot = jest.fn(() => '/x'); }); @@ -189,7 +189,7 @@ describe('ErrorHandler', () => { test('should call error method of ConfirmationService when authenticated error occurs with _AbpErrorFormat header', done => { spectator - .get(Actions) + .inject(Actions) .pipe(ofActionDispatched(Navigate)) .subscribe(({ path, queryParams, extras }) => { expect(path).toEqual(['/account/login']); @@ -281,7 +281,7 @@ describe('ErrorHandler with custom error component', () => { beforeEach(() => { spectator = createService(); service = spectator.service; - store = spectator.get(Store); + store = spectator.inject(Store); store.selectSnapshot = jest.fn(() => '/x'); }); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/loader-bar.component.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/loader-bar.component.spec.ts index e363dacfd1..894b5f60d1 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/loader-bar.component.spec.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/loader-bar.component.spec.ts @@ -1,9 +1,15 @@ -import { Router, RouteReuseStrategy, NavigationStart, NavigationEnd, NavigationError } from '@angular/router'; +import { + Router, + RouteReuseStrategy, + NavigationStart, + NavigationEnd, + NavigationError, +} from '@angular/router'; import { createHostFactory, SpectatorHost, SpyObject } from '@ngneat/spectator/jest'; import { Actions, NgxsModule, Store } from '@ngxs/store'; import { Subject, Subscription, Observable, Subscriber, timer } from 'rxjs'; import { LoaderBarComponent } from '../components/loader-bar/loader-bar.component'; -import { StartLoader, StopLoader } from '@abp/ng.core'; +import { StartLoader, StopLoader, SubscriptionService } from '@abp/ng.core'; import { HttpRequest } from '@angular/common/http'; describe('LoaderBarComponent', () => { @@ -16,13 +22,14 @@ describe('LoaderBarComponent', () => { mocks: [Router], imports: [NgxsModule.forRoot()], detectChanges: false, + providers: [SubscriptionService], }); beforeEach(() => { spectator = createHost(''); spectator.component.intervalPeriod = 1; spectator.component.stopDelay = 1; - router = spectator.get(Router); + router = spectator.inject(Router); (router as any).events = events$; }); @@ -34,7 +41,7 @@ describe('LoaderBarComponent', () => { it('should increase the progressLevel', done => { spectator.detectChanges(); - spectator.get(Store).dispatch(new StartLoader(new HttpRequest('GET', 'test'))); + spectator.inject(Store).dispatch(new StartLoader(new HttpRequest('GET', 'test'))); spectator.detectChanges(); setTimeout(() => { expect(spectator.component.progressLevel > 0).toBeTruthy(); @@ -44,7 +51,7 @@ describe('LoaderBarComponent', () => { test.skip('should be interval unsubscribed', done => { spectator.detectChanges(); - spectator.get(Store).dispatch(new StartLoader(new HttpRequest('GET', 'test'))); + spectator.inject(Store).dispatch(new StartLoader(new HttpRequest('GET', 'test'))); expect(spectator.component.interval.closed).toBe(false); timer(400).subscribe(() => { @@ -73,7 +80,7 @@ describe('LoaderBarComponent', () => { (router as any).events.next(new NavigationStart(1, 'test')); expect(spectator.component.interval.closed).toBe(false); - spectator.get(Store).dispatch(new StopLoader(new HttpRequest('GET', 'test'))); + spectator.inject(Store).dispatch(new StopLoader(new HttpRequest('GET', 'test'))); expect(spectator.component.progressLevel).toBe(100); timer(2).subscribe(() => { diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/modal-container.component.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/modal-container.component.spec.ts index 4ee654e9bd..9cb45127e5 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/modal-container.component.spec.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/modal-container.component.spec.ts @@ -23,7 +23,7 @@ describe('ModalContainerComponent', () => { let foo = document.querySelector('div.foo'); expect(foo).toBeNull(); - const cfResolver = spectator.get(ComponentFactoryResolver); + const cfResolver = spectator.inject(ComponentFactoryResolver); const factory = cfResolver.resolveComponentFactory(TestComponent); componentRef = spectator.component.container.createComponent(factory); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/modal.component.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/modal.component.spec.ts index 00562c4780..b321ee4542 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/modal.component.spec.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/modal.component.spec.ts @@ -78,7 +78,7 @@ describe('ModalComponent', () => { }); afterEach(() => { - const modalService = spectator.get(ModalService); + const modalService = spectator.inject(ModalService); modalService.clearModal(); }); @@ -120,7 +120,7 @@ describe('ModalComponent', () => { }); it('should open the confirmation popup and works correct', async () => { - const confirmationService = spectator.get(ConfirmationService); + const confirmationService = spectator.inject(ConfirmationService); const warnSpy = jest.spyOn(confirmationService, 'warn'); await wait0ms(); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/validation-utils.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/validation-utils.spec.ts index 3cda3b5403..22f445c36c 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/validation-utils.spec.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/validation-utils.spec.ts @@ -6,6 +6,7 @@ import { NgxsModule, Store } from '@ngxs/store'; import { HttpClient } from '@angular/common/http'; import { getPasswordValidators } from '../utils'; import { Validators } from '@angular/forms'; +import { OAuthService } from 'angular-oauth2-oidc'; @Component({ template: '', selector: 'abp-dummy' }) class DummyComponent {} @@ -15,14 +16,14 @@ describe('ValidationUtils', () => { const createComponent = createComponentFactory({ component: DummyComponent, imports: [NgxsModule.forRoot([ConfigState]), NgxValidateCoreModule.forRoot()], - mocks: [HttpClient], + mocks: [HttpClient, OAuthService], }); beforeEach(() => (spectator = createComponent())); describe('#getPasswordValidators', () => { it('should return password valdiators', () => { - const store = spectator.get(Store); + const store = spectator.inject(Store); store.reset({ ConfigState: { setting: { diff --git a/npm/ng-packs/scripts/build.ts b/npm/ng-packs/scripts/build.ts index 9b90d3a4c1..8d442f0740 100644 --- a/npm/ng-packs/scripts/build.ts +++ b/npm/ng-packs/scripts/build.ts @@ -23,7 +23,7 @@ import fse from 'fs-extra'; '--no-watch', '--sync', '--packages', - '@abp/ng.core,@abp/ng.theme.shared', + '@abp/ng.core,@abp/ng.theme.shared,@abp/ng.components', ], { stdout: 'inherit', cwd: '../' }, ); @@ -38,7 +38,7 @@ import fse from 'fs-extra'; '--no-watch', '--all-packages', '--excluded-packages', - '@abp/ng.core,@abp/ng.theme.shared,@abp/ng.feature-management,@abp/ng.permission-management', + '@abp/ng.core,@abp/ng.theme.shared,@abp/ng.components,@abp/ng.feature-management,@abp/ng.permission-management', ], { stdout: 'inherit', cwd: '../' }, ); diff --git a/npm/ng-packs/scripts/publish.ts b/npm/ng-packs/scripts/publish.ts index 3b16992357..1514d229fc 100644 --- a/npm/ng-packs/scripts/publish.ts +++ b/npm/ng-packs/scripts/publish.ts @@ -21,13 +21,13 @@ const publish = async () => { } const registry = program.preview - ? 'https://www.myget.org/F/abp-nightly/npm' + ? 'https://www.myget.org/F/abp-nightly/auth/8f2a5234-1bce-4dc7-b976-2983078590a9/npm/' : 'https://registry.npmjs.org'; try { await fse.remove('../dist'); - await execa('npm', ['install'], { stdout: 'inherit', cwd: '../' }); + await execa('yarn', ['install', '--ignore-scripts'], { stdout: 'inherit', cwd: '../' }); await fse.rename('../lerna.version.json', '../lerna.json'); diff --git a/npm/ng-packs/tsconfig.base.json b/npm/ng-packs/tsconfig.base.json index 02971ebe83..d73d137f99 100644 --- a/npm/ng-packs/tsconfig.base.json +++ b/npm/ng-packs/tsconfig.base.json @@ -17,7 +17,8 @@ "paths": { "@abp/ng.core": ["packages/core/src/public-api.ts"], "@abp/ng.theme.shared": ["packages/theme-shared/src/public-api.ts"], - "@abp/ng.theme.shared/extensions": ["packages/theme-shared/src/public-api.ts"], + "@abp/ng.theme.shared/extensions": ["packages/theme-shared/extensions/src/public-api.ts"], + "@abp/ng.components/tree": ["packages/components/tree/src/public-api.ts"], "@abp/ng.theme.basic": ["packages/theme-basic/src/public-api.ts"], "@abp/ng.account": ["packages/account/src/public-api.ts"], "@abp/ng.account/config": ["packages/account/config/src/public-api.ts"], diff --git a/npm/ng-packs/yarn.lock b/npm/ng-packs/yarn.lock index 6c27814650..56434e11b2 100644 --- a/npm/ng-packs/yarn.lock +++ b/npm/ng-packs/yarn.lock @@ -2,20 +2,20 @@ # yarn lockfile v1 -"@abp/ng.account@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/ng.account/-/ng.account-3.0.4.tgz#5c75a30c912294b40cbc43f218d61cbe67a58355" - integrity sha512-a+ZjFoaNlJAYW3Hd+hEhq/TCVENc3x9D4Grf5/zr8h1WcETdgjx8rvRxhj6l6i/Ry/mAgPHOKuObSbb+pxVo6w== +"@abp/ng.account@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/ng.account/-/ng.account-3.0.5.tgz#3f359e07be021c5743f42f32abc8371f93552df1" + integrity sha512-isG8VmENHookDJoq73dJfPbAdLxmzPGAJMDvg5Yn124yfHVkZQONszLGZxDNUFWti0S07Xose5dripNzcK2Clg== dependencies: - "@abp/ng.theme.shared" "~3.0.4" + "@abp/ng.theme.shared" "~3.0.5" tslib "^2.0.0" -"@abp/ng.core@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/ng.core/-/ng.core-3.0.4.tgz#ef86082c13d1ccbf0c48fb2b0ccd5a059e44215b" - integrity sha512-dBHCtcM4dVOvHJ88p8d71qpYpOvTjrvu78e6LO6qK7CsSoJq5B8EkYZgkV6MO2Kq5/p3mLHTtwuP3mkQSUfLJw== +"@abp/ng.core@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/ng.core/-/ng.core-3.0.5.tgz#8956c4e27689e80d981bae66e173524c79ca8678" + integrity sha512-PTluEXyea1d5oPtvkNx/SbmX3kBaHtdiJyN88vJoZXbdd5/OIZeeR5cL4Q58+kYG5xf1z6xkq7wNSAzRjTG7VA== dependencies: - "@abp/utils" "^3.0.3" + "@abp/utils" "^3.0.4" "@angular/localize" "~9.1.11" "@ngxs/router-plugin" "^3.6.2" "@ngxs/storage-plugin" "^3.6.2" @@ -27,74 +27,74 @@ ts-toolbelt "^6.9.9" tslib "^2.0.0" -"@abp/ng.feature-management@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/ng.feature-management/-/ng.feature-management-3.0.4.tgz#127a81c9f8b045c38bc41de4ab49d29acaa14f70" - integrity sha512-TVNmjLhhg0VbaUlaXeDUsgpz5BX26aMzAY0dahsXhyM/dti2slIlEyyTKf673a1ve84nUuigrDJiAhi6WntUbw== +"@abp/ng.feature-management@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/ng.feature-management/-/ng.feature-management-3.0.5.tgz#d947f9f05e9edd7c972c42ea903bf05d89efe8da" + integrity sha512-TUrKF5U2hjQ92/Ocig7uULNCew++dfIpCyoHdXrcWZHdhLiFcuuNLc0UElPjFe7WrIuPkka/28MEWHylKFAk4Q== dependencies: - "@abp/ng.theme.shared" "~3.0.4" + "@abp/ng.theme.shared" "~3.0.5" tslib "^2.0.0" -"@abp/ng.identity@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/ng.identity/-/ng.identity-3.0.4.tgz#321ab33db22d32013d2ea635616f6402717ebc40" - integrity sha512-ZJVh77gZVPKyexfLxo613NeiKWYSk/7q6QrMEzkeDM21R9VLyw5jGg00pw7hkmUldy5bObW/5VVCxJlCzSTwdg== +"@abp/ng.identity@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/ng.identity/-/ng.identity-3.0.5.tgz#474119e9d3ce2529f0b03a3241d41ed1d49faa65" + integrity sha512-v6hxUniSyIknOidXYuYFvV2edNksDIBgiB7LvqY/csmddH+fHJhct0W0bdk//83aj5N/Fy3FO5BvkeiGYSFMRg== dependencies: - "@abp/ng.permission-management" "~3.0.4" - "@abp/ng.theme.shared" "~3.0.4" + "@abp/ng.permission-management" "~3.0.5" + "@abp/ng.theme.shared" "~3.0.5" tslib "^2.0.0" -"@abp/ng.permission-management@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/ng.permission-management/-/ng.permission-management-3.0.4.tgz#511226c1c0e1445a453015d8d85b013e7a009f96" - integrity sha512-hIiUdQh9XVvfmLc5B+eQpj36PxCcBzOCRRaBuGhd2+lecEVUV12EgpWq0fzW1laL9FV1REmqIKP5uzKUeUw4Dw== +"@abp/ng.permission-management@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/ng.permission-management/-/ng.permission-management-3.0.5.tgz#2d496872be5af52e8f50022a5c36b3db989c9bc4" + integrity sha512-ukXX/v/ECU0R4SNKNwwdLG1+bbbS8r2GabaefX8IHYxq084VWXWlM7gTgStDIsvgC3Trh8Itoi7uVJHCU6bb0Q== dependencies: - "@abp/ng.theme.shared" "~3.0.4" + "@abp/ng.theme.shared" "~3.0.5" tslib "^2.0.0" -"@abp/ng.setting-management@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/ng.setting-management/-/ng.setting-management-3.0.4.tgz#880a8999e6f77a46bebb5c3ec2e53e652cb6b289" - integrity sha512-mnjxofTqE+n9C6x5nZkb0DQK/s0cZLwCFrhFoJdiQmfIewHU4AVbs7cT6L7FNewYwojPsCsppiR+y0cUaXDlMQ== +"@abp/ng.setting-management@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/ng.setting-management/-/ng.setting-management-3.0.5.tgz#57cc71d3357b73fe4f8890c077916c616301ee97" + integrity sha512-Cd6hTXS9XfIVmzwStICijEzaELDn4++xVUmVncWLJn9dWW5T9Mau+kkn7SeElzmv6U/iSEZH8/UHshktCvJ3Tg== dependencies: - "@abp/ng.theme.shared" "~3.0.4" + "@abp/ng.theme.shared" "~3.0.5" tslib "^2.0.0" -"@abp/ng.tenant-management@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/ng.tenant-management/-/ng.tenant-management-3.0.4.tgz#3e4edcb94b11b190451ef08115409f59cf164088" - integrity sha512-3lcq80Isa+gTZOKOyzQBd/6t4pe+mOakjZFWOBOpi4gmy4Mmun1KqbfaFoMW+v0wacQPGiznStdln0AeOXXj7g== +"@abp/ng.tenant-management@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/ng.tenant-management/-/ng.tenant-management-3.0.5.tgz#ade0e26b26dcea5445842a21f5a1f675705dfa50" + integrity sha512-2etqlpCB2iXCXSTX1D/YSl5WIXgTvPMFUaOQ8r1CFVxdOhsmWgS/8JZEEZMO5oJDKMyw/jibqtY9WeqgdsnJQg== dependencies: - "@abp/ng.feature-management" "~3.0.4" - "@abp/ng.theme.shared" "~3.0.4" + "@abp/ng.feature-management" "~3.0.5" + "@abp/ng.theme.shared" "~3.0.5" tslib "^2.0.0" -"@abp/ng.theme.basic@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/ng.theme.basic/-/ng.theme.basic-3.0.4.tgz#655e4388f6dd91eea10bdf14f313d92bbec3cf49" - integrity sha512-bSIYntH0gF/HJVYOvuguZfKkkmA6zALSVj6OYQPNo3HW7+N++loawnbMyEbiBSd0V8JolkFHeX/xvOGlY12jnw== +"@abp/ng.theme.basic@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/ng.theme.basic/-/ng.theme.basic-3.0.5.tgz#13d3da2547fde92a66eb5fc9f9798892226e63b2" + integrity sha512-COeCirRKpLcY2OE4cs9CrKObA1XRRaHXbRcKnMovPUcxC4xb47tQ5snZ4n4C9VYkRnlpSh8p2w51a8SQuxQjMg== dependencies: - "@abp/ng.theme.shared" "~3.0.4" + "@abp/ng.theme.shared" "~3.0.5" tslib "^2.0.0" -"@abp/ng.theme.shared@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/ng.theme.shared/-/ng.theme.shared-3.0.4.tgz#d12f560b7e90d91a659c1227bd4e8c25b4f8cd3e" - integrity sha512-0Z37zIiNU0ZVK7F5nP/7uS6ppPGlltUUvlf/l6RdOKi4aUbUtSRqDVoAbTl29AV14A8gicWkSFYKK+2wxoN09Q== +"@abp/ng.theme.shared@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/ng.theme.shared/-/ng.theme.shared-3.0.5.tgz#7d0187621d5e27bcc171d21d8ab0e5f7241edef4" + integrity sha512-LAokCastVsEIMH+HfQjkkt6OotKNrIFMCMlDs7Y7i74sXGGhhe+v4hHsWlAT4yQ0EydNYr1W10XoI7B3pzH1MQ== dependencies: - "@abp/ng.core" "~3.0.4" + "@abp/ng.core" "~3.0.5" "@fortawesome/fontawesome-free" "^5.13.1" "@ng-bootstrap/ng-bootstrap" "^6.1.0" - "@ngx-validate/core" "^0.0.9" + "@ngx-validate/core" "^0.0.10" "@swimlane/ngx-datatable" "^17.0.0" bootstrap "^4.5.0" chart.js "^2.9.3" tslib "^2.0.0" -"@abp/utils@^3.0.3", "@abp/utils@^3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-3.0.4.tgz#38068fa8f735e1889cf2f226128366825948814d" - integrity sha512-TnXRZIZcxv5+EctEPsZEi2cyRXbpl3A679wzQa6BACv+pM45cjGO1JR8P0VswKN4TVL0aQRMv26N6U/uie8sEQ== +"@abp/utils@^3.0.4", "@abp/utils@^3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-3.0.5.tgz#66b3da953500e73ff1f23d1b41168cde8216ce49" + integrity sha512-X9V1xLIEazItaf93nFSA4p94WT6QK3yA4eIyvU7GyyEkkOQdaakNwPbRwy930sd+I+/oXnIqM1vDsJu9QDhgvg== dependencies: just-compare "^1.3.0" @@ -108,31 +108,31 @@ jest-preset-angular "^8.1.2" lodash "^4.17.10" -"@angular-devkit/architect@0.1000.3": - version "0.1000.3" - resolved "https://registry.yarnpkg.com/@angular-devkit/architect/-/architect-0.1000.3.tgz#f23ea9d370edac8b0dd979d2574b12aa9923c3d4" - integrity sha512-8ZszTAkRvGGMXERFvyLT6SJPfJXjNNfHamA76uDPTBXy+EijJ1XVTUr1+SYEe73E4ovtxqxAnsApEFxS7/Ni5w== +"@angular-devkit/architect@0.1000.5": + version "0.1000.5" + resolved "https://registry.yarnpkg.com/@angular-devkit/architect/-/architect-0.1000.5.tgz#39488b92a366e49d50dda0af4eee3b0bb9425ec4" + integrity sha512-BhNqB7fmmrDQwxoJ3zdw5FslFD+nYGgMTiC48ERVYJVuN3BK6xtt2OGA7vho3eJ13OE5cPGUi8crQDRROgxxQg== dependencies: - "@angular-devkit/core" "10.0.3" + "@angular-devkit/core" "10.0.5" rxjs "6.5.5" "@angular-devkit/architect@>=0.900.0 < 0.1000.0": - version "0.901.11" - resolved "https://registry.yarnpkg.com/@angular-devkit/architect/-/architect-0.901.11.tgz#d429b7fc9952fe6a047a9645786a93eed60f8393" - integrity sha512-RmYOq1VEJdQLzwMno+C56WtgscAtoR/7i4tX5b5VxRa2RmQKTxowllYWwgrF5445VGUqzap9H6zJFXvlY2FA0w== + version "0.901.12" + resolved "https://registry.yarnpkg.com/@angular-devkit/architect/-/architect-0.901.12.tgz#e0f78b7282f851d27af6802dc7a261022421b2d5" + integrity sha512-gLlsxa+3JPV1m1gRvRMujOs4xKox6I5BkYmOD1zfu+dB6y3LuBAvHfXA6FaTDVOMBrmSlWnE4PmOmB6xd7wxMA== dependencies: - "@angular-devkit/core" "9.1.11" + "@angular-devkit/core" "9.1.12" rxjs "6.5.4" "@angular-devkit/build-angular@~0.1000.0": - version "0.1000.3" - resolved "https://registry.yarnpkg.com/@angular-devkit/build-angular/-/build-angular-0.1000.3.tgz#c5af97a1868adb2d0be9cb97e5d8b3c3c8428d4d" - integrity sha512-r3KJj39AwkYYzbixSM095l4fOGvhyByr0XvmAEu0l5dGGdL4tNXywvgXkNhEVRDo0jZYpTMegiTqzOik/9YCDw== - dependencies: - "@angular-devkit/architect" "0.1000.3" - "@angular-devkit/build-optimizer" "0.1000.3" - "@angular-devkit/build-webpack" "0.1000.3" - "@angular-devkit/core" "10.0.3" + version "0.1000.5" + resolved "https://registry.yarnpkg.com/@angular-devkit/build-angular/-/build-angular-0.1000.5.tgz#9be7c63d96d3adf7d6929a09618df9c57d61f76e" + integrity sha512-YOlYdOksFsirCV7l2S0T/k9phRtT5eR+Pk3uWHXGfrkCBzFlAHw1EqOlJLOOSuN+stGHHYnKES2WUVJRJXLlCQ== + dependencies: + "@angular-devkit/architect" "0.1000.5" + "@angular-devkit/build-optimizer" "0.1000.5" + "@angular-devkit/build-webpack" "0.1000.5" + "@angular-devkit/core" "10.0.5" "@babel/core" "7.9.6" "@babel/generator" "7.9.6" "@babel/plugin-transform-runtime" "7.9.6" @@ -140,8 +140,8 @@ "@babel/runtime" "7.9.6" "@babel/template" "7.8.6" "@jsdevtools/coverage-istanbul-loader" "3.0.3" - "@ngtools/webpack" "10.0.3" - ajv "6.12.2" + "@ngtools/webpack" "10.0.5" + ajv "6.12.3" autoprefixer "9.8.0" babel-loader "8.1.0" browserslist "^4.9.1" @@ -196,86 +196,86 @@ worker-plugin "4.0.3" "@angular-devkit/build-ng-packagr@~0.1000.0": - version "0.1000.3" - resolved "https://registry.yarnpkg.com/@angular-devkit/build-ng-packagr/-/build-ng-packagr-0.1000.3.tgz#b9f10e6e1fcc821c7b35a07903f6f7e6e3f41f97" - integrity sha512-k5dEjx8g+ZLLwlViue7931PNxbAbePJ1NdAmXodg2U0W4l5g22egiUFG1DGsMm7p/3LlZGv3q9Kwdg8Ox1acVg== + version "0.1000.5" + resolved "https://registry.yarnpkg.com/@angular-devkit/build-ng-packagr/-/build-ng-packagr-0.1000.5.tgz#681774fa8982bf092e7ee33a19842e531a36a4dc" + integrity sha512-MiYwlyz6LOQONEBZMiaN3U7/CpsttNKmphdMmsBZyIPBnlHIHeHgekUEjYSMJDI9C/31HsstVR2DIJjOD2nJmw== dependencies: - "@angular-devkit/architect" "0.1000.3" + "@angular-devkit/architect" "0.1000.5" rxjs "6.5.5" -"@angular-devkit/build-optimizer@0.1000.3": - version "0.1000.3" - resolved "https://registry.yarnpkg.com/@angular-devkit/build-optimizer/-/build-optimizer-0.1000.3.tgz#05f7a853a5907ad3381a598896a2e053e47e925d" - integrity sha512-6mFoubg08UCWC0fE2mGoawEt2R1VlGStvUNAP2PRCjoj1ZySa1NnVYoKk65cyAAA3K2o7vSoDZesNq1uABjZbg== +"@angular-devkit/build-optimizer@0.1000.5": + version "0.1000.5" + resolved "https://registry.yarnpkg.com/@angular-devkit/build-optimizer/-/build-optimizer-0.1000.5.tgz#dc1e6fa38b7ede190ab1f31867011e624fde83ed" + integrity sha512-H6jTF7TOYq909HR6M3Bu8lScqrG1qznK2XMefK3m1I1jBJ+WO4bL7hrhTp/CIY3d+47txep+XqhitDWD/6ikpg== dependencies: loader-utils "2.0.0" source-map "0.7.3" tslib "2.0.0" webpack-sources "1.4.3" -"@angular-devkit/build-webpack@0.1000.3": - version "0.1000.3" - resolved "https://registry.yarnpkg.com/@angular-devkit/build-webpack/-/build-webpack-0.1000.3.tgz#2709ccfc5201c2be9301551ef1d815c6aafedfbc" - integrity sha512-+vmn9d9THFubSWS28K1+nElUfOrhT576ptVZMd0a5S24momV8loW3J8iBOBfnGal/P86ZCAyP46kSirlAzH9Jg== +"@angular-devkit/build-webpack@0.1000.5": + version "0.1000.5" + resolved "https://registry.yarnpkg.com/@angular-devkit/build-webpack/-/build-webpack-0.1000.5.tgz#040f58661cb81e545d2e2872ebb0b55ab695e27d" + integrity sha512-233EbW+6IKJEV20IG9Qzlz8MdDedzoTheB+lx9tTPn21m0ph2vmpTsNOsmMWwvdBBgXbv3pW3SMbJiqDCh7u4w== dependencies: - "@angular-devkit/architect" "0.1000.3" - "@angular-devkit/core" "10.0.3" + "@angular-devkit/architect" "0.1000.5" + "@angular-devkit/core" "10.0.5" rxjs "6.5.5" -"@angular-devkit/core@10.0.3": - version "10.0.3" - resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-10.0.3.tgz#7654766c9f2d3c510beaad8bca3e2a71643c6b8e" - integrity sha512-m27ogjq44j80x64RnEswSvy8UewUqeCVJBbEuY6fzrWoaiCf12sgPlrSCwjwfhtQrLgl1e/i9zYA7U6ulGRXyg== +"@angular-devkit/core@10.0.5": + version "10.0.5" + resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-10.0.5.tgz#ad747edbf3344a87f86acb71bdeaac0380538c04" + integrity sha512-ZaF1EYniIo+yRLvvaKnLrjYCdKeg2U3YtbA932M2AIfjgfFv/9mnAbH4jmZsBZhCstxZPz+ItwJesdR20h4Z1Q== dependencies: - ajv "6.12.2" + ajv "6.12.3" fast-json-stable-stringify "2.1.0" magic-string "0.25.7" rxjs "6.5.5" source-map "0.7.3" -"@angular-devkit/core@8.3.28", "@angular-devkit/core@^8.0.3": - version "8.3.28" - resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-8.3.28.tgz#0a733132821d916788a8ed34eff0f9149a9f3a15" - integrity sha512-tM0oPjzftJMdfuAuZF2EspVKtJyhtS2IQ02S7eKTZqkh5p/oVv710aND2D2IN5qdFrzpUSearx6D+Mr/Jo8jrA== +"@angular-devkit/core@8.3.29", "@angular-devkit/core@^8.0.3": + version "8.3.29" + resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-8.3.29.tgz#3477edd6458653f83e6d78684b100c1bef81382f" + integrity sha512-4jdja9QPwR6XG14ZSunyyOWT3nE2WtZC5IMDIBZADxujXvhzOU0n4oWpy6/JVHLUAxYNNgzLz+/LQORRWndcPg== dependencies: - ajv "6.10.2" + ajv "6.12.3" fast-json-stable-stringify "2.0.0" magic-string "0.25.3" rxjs "6.4.0" source-map "0.7.3" -"@angular-devkit/core@9.1.11", "@angular-devkit/core@^9.0.0": - version "9.1.11" - resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-9.1.11.tgz#a2de3de721488164297bfccefcb08f28a7a9108e" - integrity sha512-uiEkDvWfMgPHuO4jVgBEr9Kl/LuxHaWYGD2ZtKsOnnHYZyRGp61ot7UcDF+KNdXTiq01JJH84VTd3IttEewmhQ== +"@angular-devkit/core@9.1.12", "@angular-devkit/core@^9.0.0": + version "9.1.12" + resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-9.1.12.tgz#7cebce51918fe7f3462d8c58c9fbd5a3e2c3b3e7" + integrity sha512-D/GnBeSlmdgGn7EhuE32HuPuRAjvUuxi7Q6WywBI8PSsXKAGnrypghBwMATNnOA24//CgbW2533Y9VWHaeXdeA== dependencies: - ajv "6.12.0" + ajv "6.12.3" fast-json-stable-stringify "2.1.0" magic-string "0.25.7" rxjs "6.5.4" source-map "0.7.3" -"@angular-devkit/schematics@10.0.3": - version "10.0.3" - resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-10.0.3.tgz#84e1a337692ba36cbae145b5e678592878df722f" - integrity sha512-TjA2ZSPCgUK9l4FiRTIQY7DceXMAvNzOMWffy9o3kv2HPtxG9kuBrQXk++Z99zpylK0cAsugV7t/5ANpUkrIiA== +"@angular-devkit/schematics@10.0.5": + version "10.0.5" + resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-10.0.5.tgz#a9365e06a433c89f0cf7e0a0937ab17816206b1e" + integrity sha512-UeeV8O0IydxCKj2oejygs80oIAQXPYrJHo0PQ/fa0RH6rtV3wZwirdy+yqq/BDGhtV/5VQSVCvu6uyAtGGMgNg== dependencies: - "@angular-devkit/core" "10.0.3" + "@angular-devkit/core" "10.0.5" ora "4.0.4" rxjs "6.5.5" "@angular-devkit/schematics@^8.0.6": - version "8.3.28" - resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-8.3.28.tgz#80b93755e7712f838fe6a3a4a5f3f44312188a0e" - integrity sha512-WFktBtTynViexVrgHiQV9rKfNS2YNONpi25A8+5tQR5kGKuQyHI3aEN0UMNA2sztWnPQ9AI8xz2uciQsHljgDw== + version "8.3.29" + resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-8.3.29.tgz#b3ba658b90fb3226a80ff12977be7dd583e99c49" + integrity sha512-AFJ9EK0XbcNlO5Dm9vr0OlBo1Nw6AaFXPR+DmHGBdcDDHxqEmYYLWfT+JU/8U2YFIdgrtlwvdtf6UQ3V2jdz1g== dependencies: - "@angular-devkit/core" "8.3.28" + "@angular-devkit/core" "8.3.29" rxjs "6.4.0" "@angular/animations@~10.0.0": - version "10.0.4" - resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-10.0.4.tgz#179e4e63e3f2468ee1fe961c12eb143dd8350b23" - integrity sha512-UzQiWhDHY6wixS1Nh+Jwpzq1weiLGXJPt3Pa4pETpt3Hg7MIUu62dik6OFWuGYQPbn9DJYH+CH+sRxN1GCVjww== + version "10.0.8" + resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-10.0.8.tgz#f033a7043f41075fae128ea6e91bcdaec3a4df2f" + integrity sha512-vxJRNz6CbguuoLxZpKbaCRcJRFg+/UeXvrUjRlUxwJpAni5hFYkWZUjjXvHDszI97q8FkDNLpGDPcggGqPfo4w== dependencies: tslib "^2.0.0" @@ -287,15 +287,15 @@ parse5 "^5.0.0" "@angular/cli@~10.0.0": - version "10.0.3" - resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-10.0.3.tgz#0b8c5e263deaa63d05b6efda790a2000b8a24a85" - integrity sha512-ONK8YG20KuakQetY0lPKDAOA3uBoLurdpSfFspFkcECyDimwJYSEydi3FUnCxEexeoKvrQWcol+q+u9YPoHCyg== - dependencies: - "@angular-devkit/architect" "0.1000.3" - "@angular-devkit/core" "10.0.3" - "@angular-devkit/schematics" "10.0.3" - "@schematics/angular" "10.0.3" - "@schematics/update" "0.1000.3" + version "10.0.5" + resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-10.0.5.tgz#04628246ee6e5428c124ea14656fb1b40f819408" + integrity sha512-Qsa90IGPApJS9HL6Ih7Z8S8mIdriWYEintsT9cfI0WMVYyjvSNZQBf1SvnNU3IEboV4RFnpfXJNmQz1Q4KsRFQ== + dependencies: + "@angular-devkit/architect" "0.1000.5" + "@angular-devkit/core" "10.0.5" + "@angular-devkit/schematics" "10.0.5" + "@schematics/angular" "10.0.5" + "@schematics/update" "0.1000.5" "@yarnpkg/lockfile" "1.1.0" ansi-colors "4.1.1" debug "4.1.1" @@ -313,16 +313,16 @@ uuid "8.1.0" "@angular/common@~10.0.0": - version "10.0.4" - resolved "https://registry.yarnpkg.com/@angular/common/-/common-10.0.4.tgz#f585e0b1ef723bb96e54af831138184999490e3d" - integrity sha512-9DJMD8GgHz7i2fMz0f1IHZlDSIz83uE6fcH8SIq1iCSWT2dubRRpCX000VpIyhAgfkCgdNCYXQ7VGNsZceoagQ== + version "10.0.8" + resolved "https://registry.yarnpkg.com/@angular/common/-/common-10.0.8.tgz#591263b42f8d03f3730e927251dae175e0d60669" + integrity sha512-Nzk5ckQ8y/qvTmqRdzpwUQELYD9N6DJC5yPWQw+remlkUTw24KX4KMUnt9Iy/2eFQC65MhZoTwostuKpjffrlQ== dependencies: tslib "^2.0.0" -"@angular/compiler-cli@~10.0.0": - version "10.0.4" - resolved "https://registry.yarnpkg.com/@angular/compiler-cli/-/compiler-cli-10.0.4.tgz#b241f8d41ca01dfed67b5ed4928ab0f13e2879af" - integrity sha512-uZKk6Ab4Pw8qcaXhpORuEoCbPSHWx3TWcs9OXIKIhzOOoMMe9OSt2SzOkHCrySSaik1IhQODnHww7sGRw5mxwQ== +"@angular/compiler-cli@10.0.0": + version "10.0.0" + resolved "https://registry.yarnpkg.com/@angular/compiler-cli/-/compiler-cli-10.0.0.tgz#6fe60e239b01fd10d3923a3de88877512e0e2d40" + integrity sha512-KusRkS1NKPrjhj3BXoxRgeejuJVy/ra4dcDV3hpscRmR8FSdim3rs2H+lYK2hZ26ISGsYUvC8cHNaMqP2U3X+g== dependencies: canonical-path "1.0.0" chokidar "^3.0.0" @@ -338,36 +338,36 @@ tslib "^2.0.0" yargs "15.3.0" -"@angular/compiler@~10.0.0": - version "10.0.4" - resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-10.0.4.tgz#786a5c1653a429233bee9f53614b5a82c62841ce" - integrity sha512-1rnEmSHJtrKC1QD+PyF36xwMnSqG4Slgi5+PYk3BxIa5vbWBibrYijtd/uCNhscfPSpfb06MVM2mRsrc+BmbQg== +"@angular/compiler@10.0.0": + version "10.0.0" + resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-10.0.0.tgz#15f0a0e5760ebc6fcbc06b0b3bb5f110282cf03d" + integrity sha512-meyJKPLLhkgjxF4dvdJq/sw7MI7KvpgYADoFt2K/5dt48ExXRj+kDlyQC6FAwm9mbgSea/A+JpDG8HMPU7AK5Q== dependencies: tslib "^2.0.0" "@angular/core@~10.0.0": - version "10.0.4" - resolved "https://registry.yarnpkg.com/@angular/core/-/core-10.0.4.tgz#700eda7711b50796298679d245e091cb54155071" - integrity sha512-lA8RDagJ/O0gUX95h00+nnUZs/1QmmhAVWVtHcuI12ueC836tJhLtPGnEx9ib9NXrgRyNwb8lO1xJPmmuQgdQQ== + version "10.0.8" + resolved "https://registry.yarnpkg.com/@angular/core/-/core-10.0.8.tgz#dd8855e9faa3f2ba37268144d15402d346b1be57" + integrity sha512-52M1krR/TRZsV9WKPd+r7IPVT8c5Nh+Im1z3/ZY7rG0HmxXsV7YzuTuKV7oyHbWPg0WPJAwyH0+qxBK3kpvc8w== dependencies: tslib "^2.0.0" "@angular/forms@~10.0.0": - version "10.0.4" - resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-10.0.4.tgz#d37fe47d5c91b0ef249066e904338905b2687847" - integrity sha512-SJSYZCfHua9fi/dks1q+ad9OGBblRUn3Q1V4B7r99fJYr39qRiIHcegikhY4h8H3Wk1bJRGJG7iXmxJhjWXK3Q== + version "10.0.8" + resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-10.0.8.tgz#e491010a78be6997d4ac228565de661f5505dc2f" + integrity sha512-7aEF0iO8pwcCtVgY0uNN7njVvdXWzXCTygd0SfqOTJRpCQwZ5IgICmUkpjlo8soTrOUC/8VRUstEKVltNzj9gA== dependencies: tslib "^2.0.0" "@angular/language-service@~10.0.0": - version "10.0.4" - resolved "https://registry.yarnpkg.com/@angular/language-service/-/language-service-10.0.4.tgz#fe61e5a82c494a3aaabcca4997aff94fa891d12e" - integrity sha512-m5dWL1Lg9M4kDA/kjpdyKsrx9arbIebrgsSzr/QG7cPon6SQMRs9ECZBM+3GalkQghrPO1mLgnrUwFBUH2QNPw== + version "10.0.8" + resolved "https://registry.yarnpkg.com/@angular/language-service/-/language-service-10.0.8.tgz#2566f5ae48c54134c3f8a22c4f40ac4e2e039436" + integrity sha512-RO8KjO9yLrVaIxoArLPuO+Q63gNorJ6C8lerLBNIUvpAzf2Pa1wUOZ/Vrt7lBZADC9/FfYfGhIihFBZuZOVmpA== "@angular/localize@~10.0.0": - version "10.0.4" - resolved "https://registry.yarnpkg.com/@angular/localize/-/localize-10.0.4.tgz#2c30eb604c931d98d52a2dea098ebb6a320431a3" - integrity sha512-MMMmgd6zFggWn01q/O8GYY/wRrbVxxR5H/XyQIqCjRQJgcrM0lwBnvVfFRGYa4oDHgik3Ri7Tbi1envhXSCMEA== + version "10.0.8" + resolved "https://registry.yarnpkg.com/@angular/localize/-/localize-10.0.8.tgz#167a1fed27ea11883247acfcfbdc264033172014" + integrity sha512-vtGLYHM1eguWqTNm037x15I24gx3YVbv6mbjG7AFh7nLRpXSn3wxiSSNlYzDJs0lne3qY4RBn3zSeMfRm8W+mg== dependencies: "@babel/core" "7.8.3" glob "7.1.2" @@ -383,23 +383,23 @@ yargs "15.3.0" "@angular/platform-browser-dynamic@~10.0.0": - version "10.0.4" - resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-10.0.4.tgz#77844511154eeb5eec5e68566767c215654f34e8" - integrity sha512-RoUMqYhUwF6+Mvk/aH0IZQ1D0SDEi9k5EZlx9CJ3RvNuKygk7to+S4vMWVpGxFQlwdS3bytRLKi+Kki6f4nLkg== + version "10.0.8" + resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-10.0.8.tgz#dd865d535a324a59a334a6f973bcd2d6333c2191" + integrity sha512-1hIourV0jF8188xwrweQnmP7QmcoRZu8D2wcy4EYyFU+i0mF6JXxJ97rfqVD3ueDr+Sd9VqXGZYFb0mO5G6MNg== dependencies: tslib "^2.0.0" "@angular/platform-browser@~10.0.0": - version "10.0.4" - resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-10.0.4.tgz#58131215e414be7ee9d4024e47f61b4a02166b28" - integrity sha512-iaZ8pFS5XUgPCO6/C47TzSFPzlzgleayq099cVOOx9z0t/SwUCSKt4AdAVhyQ8RTnx6l1JmmwBgRaXpScZlqzg== + version "10.0.8" + resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-10.0.8.tgz#9d9880f090664fc1908f42e4be0a899d435681c8" + integrity sha512-x6ZJz6K+YvsgZFTTD5Rv/uwJAGVWzCqvYUspq25MjvkYTFAEOc6kQ9cfsqS/dTqhPBDoTTjGu6qhIxgm4ovGjA== dependencies: tslib "^2.0.0" "@angular/router@~10.0.0": - version "10.0.4" - resolved "https://registry.yarnpkg.com/@angular/router/-/router-10.0.4.tgz#d68c2711b53f3bdc1508d9b7e90b181b4e09f42c" - integrity sha512-iDLWdmltU5pZ6M/fBKC5Kg2o9Aqb1YJ+oHXFu186BQAl2RNeNCmMQ0VaCxjpMgD/MoSxpuRuGQ6rRrCSFCxtcQ== + version "10.0.8" + resolved "https://registry.yarnpkg.com/@angular/router/-/router-10.0.8.tgz#8eec22a8c1050be4fb4003d177bee9e566df8aaf" + integrity sha512-Plub5gtfRv0Uk/e1Q66LVXRohzfMPqE9a4OqboFDjlWT5a94XSJaSHMgQHZ0esKoRidxijo3aRJOUSc/LAO9JQ== dependencies: tslib "^2.0.0" @@ -425,9 +425,9 @@ "@babel/highlight" "^7.10.4" "@babel/compat-data@^7.10.4", "@babel/compat-data@^7.9.6": - version "7.10.5" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.10.5.tgz#d38425e67ea96b1480a3f50404d1bf85676301a6" - integrity sha512-mPVoWNzIpYJHbWje0if7Ck36bpbtTvIxOi9+6WSK9wjGEXearAqlwBoTQvVjsAY2VIwgcs8V940geY3okzRCEw== + version "7.11.0" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.11.0.tgz#e9f73efe09af1355b723a7f39b11bad637d7c99c" + integrity sha512-TPSvJfv73ng0pfnEOh17bYMPQbI95+nGWc71Ss4vZdRBHTDqmM9Z8ZV4rYz8Ks7sfzc95n30k6ODIq5UGnXcYQ== dependencies: browserslist "^4.12.0" invariant "^2.2.4" @@ -477,18 +477,18 @@ source-map "^0.5.0" "@babel/core@^7.1.0", "@babel/core@^7.7.5": - version "7.10.5" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.10.5.tgz#1f15e2cca8ad9a1d78a38ddba612f5e7cdbbd330" - integrity sha512-O34LQooYVDXPl7QWCdW9p4NR+QlzOr7xShPPJz8GsuCU3/8ua/wqTr7gmnxXv+WBESiGU/G5s16i6tUvHkNb+w== + version "7.11.1" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.11.1.tgz#2c55b604e73a40dc21b0e52650b11c65cf276643" + integrity sha512-XqF7F6FWQdKGGWAzGELL+aCO1p+lRY5Tj5/tbT3St1G8NaH70jhhDIKknIZaDans0OQBG5wRAldROLHSt44BgQ== dependencies: "@babel/code-frame" "^7.10.4" - "@babel/generator" "^7.10.5" - "@babel/helper-module-transforms" "^7.10.5" + "@babel/generator" "^7.11.0" + "@babel/helper-module-transforms" "^7.11.0" "@babel/helpers" "^7.10.4" - "@babel/parser" "^7.10.5" + "@babel/parser" "^7.11.1" "@babel/template" "^7.10.4" - "@babel/traverse" "^7.10.5" - "@babel/types" "^7.10.5" + "@babel/traverse" "^7.11.0" + "@babel/types" "^7.11.0" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.1" @@ -508,12 +508,12 @@ lodash "^4.17.13" source-map "^0.5.0" -"@babel/generator@^7.10.5", "@babel/generator@^7.8.3", "@babel/generator@^7.9.6": - version "7.10.5" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.10.5.tgz#1b903554bc8c583ee8d25f1e8969732e6b829a69" - integrity sha512-3vXxr3FEW7E7lJZiWQ3bM4+v/Vyr9C+hpolQ8BGFr9Y8Ri2tFLWTixmwKBafDujO1WVah4fhZBeU1bieKdghig== +"@babel/generator@^7.11.0", "@babel/generator@^7.8.3", "@babel/generator@^7.9.6": + version "7.11.0" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.11.0.tgz#4b90c78d8c12825024568cbe83ee6c9af193585c" + integrity sha512-fEm3Uzw7Mc9Xi//qU20cBKatTfs2aOtKqmvy/Vm7RkJEGFQ4xc9myCfbXxqK//ZS8MR/ciOHw6meGASJuKmDfQ== dependencies: - "@babel/types" "^7.10.5" + "@babel/types" "^7.11.0" jsesc "^2.5.1" source-map "^0.5.0" @@ -593,11 +593,11 @@ "@babel/types" "^7.10.4" "@babel/helper-member-expression-to-functions@^7.10.4": - version "7.10.5" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.5.tgz#172f56e7a63e78112f3a04055f24365af702e7ee" - integrity sha512-HiqJpYD5+WopCXIAbQDG0zye5XYVvcO9w/DHp5GsaGkRUaamLj2bEtu6i8rnGGprAhHM3qidCMgp71HF4endhA== + version "7.11.0" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz#ae69c83d84ee82f4b42f96e2a09410935a8f26df" + integrity sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q== dependencies: - "@babel/types" "^7.10.5" + "@babel/types" "^7.11.0" "@babel/helper-module-imports@^7.10.4", "@babel/helper-module-imports@^7.8.3": version "7.10.4" @@ -606,17 +606,17 @@ dependencies: "@babel/types" "^7.10.4" -"@babel/helper-module-transforms@^7.10.4", "@babel/helper-module-transforms@^7.10.5", "@babel/helper-module-transforms@^7.9.0": - version "7.10.5" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.10.5.tgz#120c271c0b3353673fcdfd8c053db3c544a260d6" - integrity sha512-4P+CWMJ6/j1W915ITJaUkadLObmCRRSC234uctJfn/vHrsLNxsR8dwlcXv9ZhJWzl77awf+mWXSZEKt5t0OnlA== +"@babel/helper-module-transforms@^7.10.4", "@babel/helper-module-transforms@^7.10.5", "@babel/helper-module-transforms@^7.11.0", "@babel/helper-module-transforms@^7.9.0": + version "7.11.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.11.0.tgz#b16f250229e47211abdd84b34b64737c2ab2d359" + integrity sha512-02EVu8COMuTRO1TAzdMtpBPbe6aQ1w/8fePD2YgQmxZU4gpNWaL9gK3Jp7dxlkUlUCJOTaSeA+Hrm1BRQwqIhg== dependencies: "@babel/helper-module-imports" "^7.10.4" "@babel/helper-replace-supers" "^7.10.4" "@babel/helper-simple-access" "^7.10.4" - "@babel/helper-split-export-declaration" "^7.10.4" + "@babel/helper-split-export-declaration" "^7.11.0" "@babel/template" "^7.10.4" - "@babel/types" "^7.10.5" + "@babel/types" "^7.11.0" lodash "^4.17.19" "@babel/helper-optimise-call-expression@^7.10.4": @@ -667,12 +667,19 @@ "@babel/template" "^7.10.4" "@babel/types" "^7.10.4" -"@babel/helper-split-export-declaration@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.4.tgz#2c70576eaa3b5609b24cb99db2888cc3fc4251d1" - integrity sha512-pySBTeoUff56fL5CBU2hWm9TesA4r/rOkI9DyJLvvgz09MB9YtfIYe3iBriVaYNaPe+Alua0vBIOVOLs2buWhg== +"@babel/helper-skip-transparent-expression-wrappers@^7.11.0": + version "7.11.0" + resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.11.0.tgz#eec162f112c2f58d3af0af125e3bb57665146729" + integrity sha512-0XIdiQln4Elglgjbwo9wuJpL/K7AGCY26kmEt0+pRP0TAj4jjyNq1MjoRvikrTVqKcx4Gysxt4cXvVFXP/JO2Q== dependencies: - "@babel/types" "^7.10.4" + "@babel/types" "^7.11.0" + +"@babel/helper-split-export-declaration@^7.10.4", "@babel/helper-split-export-declaration@^7.11.0": + version "7.11.0" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz#f8a491244acf6a676158ac42072911ba83ad099f" + integrity sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg== + dependencies: + "@babel/types" "^7.11.0" "@babel/helper-validator-identifier@^7.10.4": version "7.10.4" @@ -707,10 +714,10 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.10.4", "@babel/parser@^7.10.5", "@babel/parser@^7.8.3", "@babel/parser@^7.8.6", "@babel/parser@^7.9.6": - version "7.10.5" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.10.5.tgz#e7c6bf5a7deff957cec9f04b551e2762909d826b" - integrity sha512-wfryxy4bE1UivvQKSQDU4/X6dr+i8bctjUjj8Zyt3DQy7NtPizJXT8M52nqpNKL+nq2PW8lxk4ZqLj0fD4B4hQ== +"@babel/parser@^7.1.0", "@babel/parser@^7.10.4", "@babel/parser@^7.11.0", "@babel/parser@^7.11.1", "@babel/parser@^7.8.3", "@babel/parser@^7.8.6", "@babel/parser@^7.9.6": + version "7.11.1" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.11.1.tgz#d91a387990b21e5d20047b336bb19b0553f02ff5" + integrity sha512-u9QMIRdKVF7hfEkb3nu2LgZDIzCQPv+yHD9Eg6ruoJLjkrQ9fFz4IBSlF/9XwoNri9+2F1IY+dYuOfZrXq8t3w== "@babel/plugin-proposal-async-generator-functions@^7.8.3": version "7.10.5" @@ -754,9 +761,9 @@ "@babel/plugin-syntax-numeric-separator" "^7.10.4" "@babel/plugin-proposal-object-rest-spread@^7.9.6": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.10.4.tgz#50129ac216b9a6a55b3853fdd923e74bf553a4c0" - integrity sha512-6vh4SqRuLLarjgeOf4EaROJAHjvu9Gl+/346PbDH9yWbJyfnJ/ah3jmYKYtswEyCoWZiidvVHjHshd4WgjB9BA== + version "7.11.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.11.0.tgz#bd81f95a1f746760ea43b6c2d3d62b11790ad0af" + integrity sha512-wzch41N4yztwoRw0ak+37wxwJM2oiIiy6huGCoqkvSTA9acYWcPfn9Y4aJqmFFJ70KTJUu29f3DQ43uJ9HXzEA== dependencies: "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-object-rest-spread" "^7.8.0" @@ -771,11 +778,12 @@ "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" "@babel/plugin-proposal-optional-chaining@^7.9.0": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.10.4.tgz#750f1255e930a1f82d8cdde45031f81a0d0adff7" - integrity sha512-ZIhQIEeavTgouyMSdZRap4VPPHqJJ3NEs2cuHs5p0erH+iz6khB0qfgU8g7UuJkG88+fBMy23ZiU+nuHvekJeQ== + version "7.11.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.11.0.tgz#de5866d0646f6afdaab8a566382fe3a221755076" + integrity sha512-v9fZIu3Y8562RRwhm1BbMRxtqZNFmFA2EG+pT2diuU8PT3H6T/KXoZ54KgYisfOFZHV6PfvAiBIZ9Rcz+/JCxA== dependencies: "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-skip-transparent-expression-wrappers" "^7.11.0" "@babel/plugin-syntax-optional-chaining" "^7.8.0" "@babel/plugin-proposal-unicode-property-regex@^7.4.4", "@babel/plugin-proposal-unicode-property-regex@^7.8.3": @@ -901,9 +909,9 @@ "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-transform-block-scoping@^7.8.3": - version "7.10.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.10.5.tgz#b81b8aafefbfe68f0f65f7ef397b9ece68a6037d" - integrity sha512-6Ycw3hjpQti0qssQcA6AMSFDHeNJ++R6dIMnpRqUjFeBBTmTDPa8zgF90OVfTvAo11mXZTlVUViY1g8ffrURLg== + version "7.11.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.11.1.tgz#5b7efe98852bef8d652c0b28144cd93a9e4b5215" + integrity sha512-00dYeDE0EVEHuuM+26+0w/SCL0BH2Qy7LwHuI4Hi4MH5gkC8/AqMN5uWFJIsoXZrAphiMm1iXzBw6L2T+eA0ew== dependencies: "@babel/helper-plugin-utils" "^7.10.4" @@ -1093,11 +1101,12 @@ "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-transform-spread@^7.8.3": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.10.4.tgz#4e2c85ea0d6abaee1b24dcfbbae426fe8d674cff" - integrity sha512-1e/51G/Ni+7uH5gktbWv+eCED9pP8ZpRhZB3jOaI3mmzfvJTWHkuyYTv0Z5PYtyM+Tr2Ccr9kUdQxn60fI5WuQ== + version "7.11.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.11.0.tgz#fa84d300f5e4f57752fe41a6d1b3c554f13f17cc" + integrity sha512-UwQYGOqIdQJe4aWNyS7noqAnN2VbaczPLiEtln+zPowRNlD+79w3oi2TWfYe0eZgd+gjZCbsydN7lzWysDt+gw== dependencies: "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-skip-transparent-expression-wrappers" "^7.11.0" "@babel/plugin-transform-sticky-regex@^7.8.3": version "7.10.4" @@ -1215,9 +1224,9 @@ regenerator-runtime "^0.13.4" "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4": - version "7.10.5" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.5.tgz#303d8bd440ecd5a491eae6117fd3367698674c5c" - integrity sha512-otddXKhdNn7d0ptoFRHtMLa8LqDxLYwTjB4nYgM1yy5N6gU/MUf8zqyyLltCH3yAVitBzmwK4us+DD0l/MauAg== + version "7.11.1" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.11.1.tgz#087afc57e7bf1073e792fe54f8fb3cfa752f9230" + integrity sha512-nH5y8fLvVl3HAb+ezbgcgwrH8QbClWo8xzkOu7+oyqngo3EVorwpWJQaqXPjGRpfj7mQvsJCl/S8knkfkPWqrw== dependencies: regenerator-runtime "^0.13.4" @@ -1239,25 +1248,25 @@ "@babel/parser" "^7.10.4" "@babel/types" "^7.10.4" -"@babel/traverse@^7.1.0", "@babel/traverse@^7.10.4", "@babel/traverse@^7.10.5", "@babel/traverse@^7.8.3", "@babel/traverse@^7.9.6": - version "7.10.5" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.10.5.tgz#77ce464f5b258be265af618d8fddf0536f20b564" - integrity sha512-yc/fyv2gUjPqzTz0WHeRJH2pv7jA9kA7mBX2tXl/x5iOE81uaVPuGPtaYk7wmkx4b67mQ7NqI8rmT2pF47KYKQ== +"@babel/traverse@^7.1.0", "@babel/traverse@^7.10.4", "@babel/traverse@^7.11.0", "@babel/traverse@^7.8.3", "@babel/traverse@^7.9.6": + version "7.11.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.11.0.tgz#9b996ce1b98f53f7c3e4175115605d56ed07dd24" + integrity sha512-ZB2V+LskoWKNpMq6E5UUCrjtDUh5IOTAyIl0dTjIEoXum/iKWkoIEKIRDnUucO6f+2FzNkE0oD4RLKoPIufDtg== dependencies: "@babel/code-frame" "^7.10.4" - "@babel/generator" "^7.10.5" + "@babel/generator" "^7.11.0" "@babel/helper-function-name" "^7.10.4" - "@babel/helper-split-export-declaration" "^7.10.4" - "@babel/parser" "^7.10.5" - "@babel/types" "^7.10.5" + "@babel/helper-split-export-declaration" "^7.11.0" + "@babel/parser" "^7.11.0" + "@babel/types" "^7.11.0" debug "^4.1.0" globals "^11.1.0" lodash "^4.17.19" -"@babel/types@^7.0.0", "@babel/types@^7.10.4", "@babel/types@^7.10.5", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.8.3", "@babel/types@^7.8.6", "@babel/types@^7.9.6": - version "7.10.5" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.10.5.tgz#d88ae7e2fde86bfbfe851d4d81afa70a997b5d15" - integrity sha512-ixV66KWfCI6GKoA/2H9v6bQdbfXEwwpOdQ8cRvb4F+eyvhlaHxWFMQB4+3d9QFJXZsiiiqVrewNV0DFEQpyT4Q== +"@babel/types@^7.0.0", "@babel/types@^7.10.4", "@babel/types@^7.10.5", "@babel/types@^7.11.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.8.3", "@babel/types@^7.8.6", "@babel/types@^7.9.6": + version "7.11.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.11.0.tgz#2ae6bf1ba9ae8c3c43824e5861269871b206e90d" + integrity sha512-O53yME4ZZI0jO1EVGtF1ePGl0LHirG4P1ibcD80XyzZcKhcMFeCXmh4Xb1ifGBIV233Qg12x4rBfQgA+tmOukA== dependencies: "@babel/helper-validator-identifier" "^7.10.4" lodash "^4.17.19" @@ -1581,13 +1590,14 @@ "@types/yargs" "^15.0.0" chalk "^3.0.0" -"@jest/types@^26.1.0": - version "26.1.0" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.1.0.tgz#f8afaaaeeb23b5cad49dd1f7779689941dcb6057" - integrity sha512-GXigDDsp6ZlNMhXQDeuy/iYCDsRIHJabWtDzvnn36+aqFfG14JmFV0e/iXxY4SP9vbXSiPNOWdehU5MeqrYHBQ== +"@jest/types@^26.2.0": + version "26.2.0" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.2.0.tgz#b28ca1fb517a4eb48c0addea7fcd9edc4ab45721" + integrity sha512-lvm3rJvctxd7+wxKSxxbzpDbr4FXDLaC57WEKdUIZ2cjTYuxYSc0zlyD7Z4Uqr5VdKxRUrtwIkiqBuvgf8uKJA== dependencies: "@types/istanbul-lib-coverage" "^2.0.0" "@types/istanbul-reports" "^1.1.1" + "@types/node" "*" "@types/yargs" "^15.0.0" chalk "^4.0.0" @@ -2301,28 +2311,28 @@ integrity sha512-wqwhnJFyEwvzWQJjXrEt+7oBTSvu2qPbdYvUFYhDVzOJLWB5M7YEhDAkMrfHQJ0pZNBMGr580FqYue+XiURY0Q== "@ngneat/spectator@^5.11.0": - version "5.12.0" - resolved "https://registry.yarnpkg.com/@ngneat/spectator/-/spectator-5.12.0.tgz#fe4752b75febb5797b7d3e2ba8167921b7adc756" - integrity sha512-q8F2eIp7uygnfBzn+2syGnYf4u9vDNT/WQSHAk+IffyPbQfM5EiPxPTY0vIccXg51zuZBPZVFtXMoFqN8zyglg== + version "5.13.0" + resolved "https://registry.yarnpkg.com/@ngneat/spectator/-/spectator-5.13.0.tgz#0a9720c3d3b60571c3619054f9831856997acf67" + integrity sha512-BlCmmgHMPtrdJECwctFmziRhHzSF3xxRnKiR/qJIerCd4csgpwGbhtEEh/2jnFEyLP6yLo4T4nCslwq3dHdFrQ== dependencies: "@testing-library/dom" "6.1.0" jquery "3.5.0" replace-in-file "^4.1.3" -"@ngtools/webpack@10.0.3": - version "10.0.3" - resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-10.0.3.tgz#2f8b2674108f7c36ffcdfc9e9aa024abfe3c7e04" - integrity sha512-0TuvYMCLtsApLtCHXeDBYGEoAQXzsRLpgFxPM5W7CGcj0ecthZO4NYrMAt+J8ky//KmbxqQSFHWmss2cbirIPA== +"@ngtools/webpack@10.0.5": + version "10.0.5" + resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-10.0.5.tgz#8123647aa73463e586ac5792d16751a36d5caaaa" + integrity sha512-jMHfLXFzmgRlZbHgS71BEIKDyHbkfKrJL1sWEs3Ylox+TEIYzJiGfnem5ZGpr8wplz8HMMJ+xngtABePvc3/0w== dependencies: - "@angular-devkit/core" "10.0.3" + "@angular-devkit/core" "10.0.5" enhanced-resolve "4.1.1" rxjs "6.5.5" webpack-sources "1.4.3" -"@ngx-validate/core@^0.0.8", "@ngx-validate/core@^0.0.9": - version "0.0.8" - resolved "https://registry.yarnpkg.com/@ngx-validate/core/-/core-0.0.8.tgz#8577405eb1af0f5002cdda7a86fbcda56280f116" - integrity sha512-caIG5ao76Xhf7T+pNA8crnpAwK0yqj3i0OAUGZRUq1W+kNgz+ZnrSd4F9an/W4g+38u/8gfUVvsfsm07ju6qYA== +"@ngx-validate/core@^0.0.10": + version "0.0.10" + resolved "https://registry.yarnpkg.com/@ngx-validate/core/-/core-0.0.10.tgz#a9ff36e5645533f5bc2d4850a980582bdfee8033" + integrity sha512-/uq22KteDF3WcJEMNaK7C9eT6Mxw8rlPqJfq2XteO2g3utzx/n/gKDdb0GzD/2eefjmO1qq98XQKgpFzQXoTag== dependencies: tslib "^1.9.0" @@ -2402,13 +2412,13 @@ "@octokit/types" "^5.0.0" "@octokit/endpoint@^6.0.1": - version "6.0.3" - resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-6.0.3.tgz#dd09b599662d7e1b66374a177ab620d8cdf73487" - integrity sha512-Y900+r0gIz+cWp6ytnkibbD95ucEzDSKzlEnaWS52hbCDNcCJYO5mRmWW7HRAnDc7am+N/5Lnd8MppSaTYx1Yg== + version "6.0.5" + resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-6.0.5.tgz#43a6adee813c5ffd2f719e20cfd14a1fee7c193a" + integrity sha512-70K5u6zd45ItOny6aHQAsea8HHQjlQq85yqOMe+Aj8dkhN2qSJ9T+Q3YjUjEYfPRBcuUWNgMn62DQnP/4LAIiQ== dependencies: "@octokit/types" "^5.0.0" - is-plain-object "^3.0.0" - universal-user-agent "^5.0.0" + is-plain-object "^4.0.0" + universal-user-agent "^6.0.0" "@octokit/plugin-enterprise-rest@^6.0.1": version "6.0.1" @@ -2454,18 +2464,18 @@ once "^1.4.0" "@octokit/request@^5.2.0": - version "5.4.5" - resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.4.5.tgz#8df65bd812047521f7e9db6ff118c06ba84ac10b" - integrity sha512-atAs5GAGbZedvJXXdjtKljin+e2SltEs48B3naJjqWupYl2IUBbB/CJisyjbNHcKpHzb3E+OYEZ46G8eakXgQg== + version "5.4.7" + resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.4.7.tgz#fd703ee092e0463ceba49ff7a3e61cb4cf8a0fde" + integrity sha512-FN22xUDP0i0uF38YMbOfx6TotpcENP5W8yJM1e/LieGXn6IoRxDMnBf7tx5RKSW4xuUZ/1P04NFZy5iY3Rax1A== dependencies: "@octokit/endpoint" "^6.0.1" "@octokit/request-error" "^2.0.0" "@octokit/types" "^5.0.0" deprecation "^2.0.0" - is-plain-object "^3.0.0" + is-plain-object "^4.0.0" node-fetch "^2.3.0" once "^1.4.0" - universal-user-agent "^5.0.0" + universal-user-agent "^6.0.0" "@octokit/rest@^16.28.4": version "16.43.2" @@ -2497,9 +2507,9 @@ "@types/node" ">= 8" "@octokit/types@^5.0.0", "@octokit/types@^5.0.1": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@octokit/types/-/types-5.1.0.tgz#4377a3f39edad3e60753fb5c3c310756f1ded57f" - integrity sha512-OFxUBgrEllAbdEmWp/wNmKIu5EuumKHG4sgy56vjZ8lXPgMhF05c76hmulfOdFHHYRpPj49ygOZJ8wgVsPecuA== + version "5.2.1" + resolved "https://registry.yarnpkg.com/@octokit/types/-/types-5.2.1.tgz#c212f03b0492faf215fa2ae506d5ec18038c2a36" + integrity sha512-PugtgEw8u++zAyBpDpSkR8K1OsT2l8QWp3ECL6bZHFoq9PfHDoKeGFWSuX2Z+Ghy93k1fkKf8tsmqNBv+8dEfQ== dependencies: "@types/node" ">= 8" @@ -2545,21 +2555,21 @@ estree-walker "^1.0.1" picomatch "^2.2.2" -"@schematics/angular@10.0.3": - version "10.0.3" - resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-10.0.3.tgz#a4d54389918a7f9c82345d4f639d930f22b62c6b" - integrity sha512-Or2pCqjpPbAvmbxtfMosGwQbNbSL4xodK5Key7678ZAPGB+rcxrVkBI9yxEJ/qzF/LrmMoKqy0JCmVLK7Grpog== +"@schematics/angular@10.0.5": + version "10.0.5" + resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-10.0.5.tgz#905f7c58547fdf9847fb004c1689bc0af7a09a7a" + integrity sha512-zg8QxgW3uLva/MSKMRYfV7dzj00SUki4nxYN4j1rw42VlwNPnFrPtzFVEilL6N7wFgoHP/6cZRgm4bfXYvLBvg== dependencies: - "@angular-devkit/core" "10.0.3" - "@angular-devkit/schematics" "10.0.3" + "@angular-devkit/core" "10.0.5" + "@angular-devkit/schematics" "10.0.5" -"@schematics/update@0.1000.3": - version "0.1000.3" - resolved "https://registry.yarnpkg.com/@schematics/update/-/update-0.1000.3.tgz#d361f23ae49cd3ac9b547b6f4c2470d7ca86aabd" - integrity sha512-Nncdklmzi1tyzkoAh7GlSslxriRhftlmfqPVmFHrrPRttYACtT/QH5qcWsrPgTPpHGINYEHrPjpeljsMoMchBQ== +"@schematics/update@0.1000.5": + version "0.1000.5" + resolved "https://registry.yarnpkg.com/@schematics/update/-/update-0.1000.5.tgz#7ce1012ee0b5fb88b149f84294eb49b911b01a3f" + integrity sha512-xodvq3X4B90u8myMEp9ESPnD2aC4YtNXj1FOcJ+BnguRA7q9rq9EL9Xqdef8sx3PObbSiKC0OFLyxgw76WuC3Q== dependencies: - "@angular-devkit/core" "10.0.3" - "@angular-devkit/schematics" "10.0.3" + "@angular-devkit/core" "10.0.5" + "@angular-devkit/schematics" "10.0.5" "@yarnpkg/lockfile" "1.1.0" ini "1.3.5" npm-package-arg "^8.0.0" @@ -2579,9 +2589,9 @@ integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== "@sinonjs/commons@^1.7.0": - version "1.8.0" - resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.0.tgz#c8d68821a854c555bba172f3b06959a0039b236d" - integrity sha512-wEj54PfsZ5jGSwMX68G8ZXFawcSglQSXqCftWX3ec8MDUzQdHgcKvw97awHbY0efQEL5iKUOAmmVtoYgmrSG4Q== + version "1.8.1" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.1.tgz#e7df00f98a203324f6dc7cc606cad9d4a8ab2217" + integrity sha512-892K+kWUUi3cl+LlqEWIDrhvLgdL79tECi8JZUyq6IviKy/DNhuzCRlbHUjxK89f4ypPMMaFnFuR9Ie6DoIMsw== dependencies: type-detect "4.0.8" @@ -2693,9 +2703,9 @@ "@types/istanbul-lib-report" "*" "@types/jasmine@^3.3.9": - version "3.5.11" - resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-3.5.11.tgz#ba8e80639dffbe277f49c708b45373a320d158e2" - integrity sha512-fg1rOd/DehQTIJTifGqGVY6q92lDgnLfs7C6t1ccSwQrMyoTGSoH6wWzhJDZb6ezhsdwAX4EIBLe8w5fXWmEng== + version "3.5.12" + resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-3.5.12.tgz#5c378c1545cdc7cb339cff5578f854b6d1e0a17d" + integrity sha512-vJaQ58oceFao+NzpKNqLOWwHPsqA7YEhKv+mOXvYU4/qh+BfVWIxaBtL0Ck5iCS67yOkNwGkDCrzepnzIWF+7g== "@types/jest@^25.2.3": version "25.2.3" @@ -2721,19 +2731,19 @@ integrity sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY= "@types/node@*", "@types/node@>= 8": - version "14.0.23" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.23.tgz#676fa0883450ed9da0bb24156213636290892806" - integrity sha512-Z4U8yDAl5TFkmYsZdFPdjeMa57NOvnaf1tljHzhouaPEp7LCj2JKkejpI1ODviIAQuW4CcQmxkQ77rnLsOOoKw== + version "14.0.27" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.27.tgz#a151873af5a5e851b51b3b065c9e63390a9e0eb1" + integrity sha512-kVrqXhbclHNHGu9ztnAwSncIgJv/FaxmzXJvGXNdcCpV1b8u1/Mi6z6m0vwy0LzKeXFTPLH0NzwmoJ3fNCIq0g== "@types/node@^12.11.1": - version "12.12.50" - resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.50.tgz#e9b2e85fafc15f2a8aa8fdd41091b983da5fd6ee" - integrity sha512-5ImO01Fb8YsEOYpV+aeyGYztcYcjGsBvN4D7G5r1ef2cuQOpymjWNQi5V0rKHE6PC2ru3HkoUr/Br2/8GUA84w== + version "12.12.53" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.53.tgz#be0d375933c3d15ef2380dafb3b0350ea7021129" + integrity sha512-51MYTDTyCziHb70wtGNFRwB4l+5JNvdqzFSkbDvpbftEgVUBEE+T5f7pROhWMp/fxp07oNIEQZd5bbfAH22ohQ== "@types/node@^8.0.31": - version "8.10.61" - resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.61.tgz#d299136ce54bcaf1abaa4a487f9e4bedf6b0d393" - integrity sha512-l+zSbvT8TPRaCxL1l9cwHCb0tSqGAGcjPJFItGGYat5oCTiq1uQQKYg5m7AF1mgnEBzFXGLJ2LRmNjtreRX76Q== + version "8.10.62" + resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.62.tgz#8d0d0db44a46ae7679d92e2e36d41bf1c3625d6a" + integrity sha512-76fupxOYVxk36kb7O/6KtrAPZ9jnSK3+qisAX4tQMEuGNdlvl7ycwatlHqjoE6jHfVtXFM3pCrCixZOidc5cuw== "@types/normalize-package-data@^2.4.0": version "2.4.0" @@ -2995,9 +3005,9 @@ JSONStream@^1.0.4, JSONStream@^1.3.4: through ">=2.2.7 <3" abab@^2.0.0, abab@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.3.tgz#623e2075e02eb2d3f2475e49f99c91846467907a" - integrity sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg== + version "2.0.4" + resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.4.tgz#6dfa57b417ca06d21b2478f0e638302f99c2405c" + integrity sha512-Eu9ELJWCz/c1e9gTiCY+FceWxcqzjYEbqMgtndnuSqZSUCOL73TWNK2mHfIj4Cw2E/ongOp+JISVNCmovt2KYQ== abbrev@1: version "1.1.1" @@ -3031,9 +3041,9 @@ acorn@^6.0.1, acorn@^6.4.1: integrity sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA== acorn@^7.1.0: - version "7.3.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.3.1.tgz#85010754db53c3fbaf3b9ea3e083aa5c5d147ffd" - integrity sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA== + version "7.4.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.0.tgz#e1ad486e6c54501634c6c397c5c121daa383607c" + integrity sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w== add-stream@^1.0.0: version "1.0.0" @@ -3091,41 +3101,11 @@ ajv-errors@^1.0.0: integrity sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ== ajv-keywords@^3.1.0, ajv-keywords@^3.4.1: - version "3.5.1" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.1.tgz#b83ca89c5d42d69031f424cad49aada0236c6957" - integrity sha512-KWcq3xN8fDjSB+IMoh2VaXVhRI0BBGxoYp3rx7Pkb6z0cFjYR9Q9l4yZqqals0/zsioCmocC5H6UvsGD4MoIBA== - -ajv@6.10.2: - version "6.10.2" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.2.tgz#d3cea04d6b017b2894ad69040fec8b623eb4bd52" - integrity sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw== - dependencies: - fast-deep-equal "^2.0.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -ajv@6.12.0: - version "6.12.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.0.tgz#06d60b96d87b8454a5adaba86e7854da629db4b7" - integrity sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -ajv@6.12.2: - version "6.12.2" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.2.tgz#c629c5eced17baf314437918d2da88c99d5958cd" - integrity sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" + version "3.5.2" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" + integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== -ajv@^6.1.0, ajv@^6.10.2, ajv@^6.12.2, ajv@^6.5.5: +ajv@6.12.3, ajv@^6.1.0, ajv@^6.10.2, ajv@^6.12.2, ajv@^6.12.3: version "6.12.3" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.3.tgz#18c5af38a111ddeb4f2697bd78d68abc1cabd706" integrity sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA== @@ -3474,13 +3454,13 @@ autoprefixer@9.8.0: postcss-value-parser "^4.1.0" autoprefixer@^9.6.5: - version "9.8.5" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.5.tgz#2c225de229ddafe1d1424c02791d0c3e10ccccaa" - integrity sha512-C2p5KkumJlsTHoNv9w31NrBRgXhf6eCMteJuHZi2xhkgC+5Vm40MEtCKPhc0qdgAOhox0YPy1SQHTAky05UoKg== + version "9.8.6" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.6.tgz#3b73594ca1bf9266320c5acf1588d74dea74210f" + integrity sha512-XrvP4VVHdRBCdX1S3WXVD8+RyG9qeb1D5Sn1DeLiG2xfSpzellk5k54xbUERJ3M5DggQxes39UGOTP8CFrEGbg== dependencies: browserslist "^4.12.0" - caniuse-lite "^1.0.30001097" - colorette "^1.2.0" + caniuse-lite "^1.0.30001109" + colorette "^1.2.1" normalize-range "^0.1.2" num2fraction "^1.2.2" postcss "^7.0.32" @@ -3698,9 +3678,9 @@ boolbase@^1.0.0, boolbase@~1.0.0: integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= bootstrap@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.5.0.tgz#97d9dbcb5a8972f8722c9962483543b907d9b9ec" - integrity sha512-Z93QoXvodoVslA+PWNdk23Hze4RBYIkpb5h8I2HY2Tu2h7A0LpAgLcyrhrSUyo2/Oxm2l1fRZPs1e5hnxnliXA== + version "4.5.1" + resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.5.1.tgz#f7322c7dd3e6376d430efc0c3f57e4d8005eb5b2" + integrity sha512-bxUooHBSbvefnIZfjD0LE8nfdPKrtiFy2sgrxQwUZ0UpFzpjVbVMUxaGIoo9XWT4B2LG1HX6UQg0UMOakT0prQ== boxen@^4.2.0: version "4.2.0" @@ -3804,15 +3784,15 @@ browserify-rsa@^4.0.0, browserify-rsa@^4.0.1: randombytes "^2.0.1" browserify-sign@^4.0.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.0.tgz#545d0b1b07e6b2c99211082bf1b12cce7a0b0e11" - integrity sha512-hEZC1KEeYuoHRqhGhTy6gWrpJA3ZDjFWv0DE61643ZnOXAKJb3u7yWcrU0mMc9SwAqK1n7myPGndkp0dFG7NFA== + version "4.2.1" + resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.1.tgz#eaf4add46dd54be3bb3b36c0cf15abbeba7956c3" + integrity sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg== dependencies: bn.js "^5.1.1" browserify-rsa "^4.0.1" create-hash "^1.2.0" create-hmac "^1.1.7" - elliptic "^6.5.2" + elliptic "^6.5.3" inherits "^2.0.4" parse-asn1 "^5.1.5" readable-stream "^3.6.0" @@ -4115,10 +4095,10 @@ caniuse-api@^3.0.0: lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001032, caniuse-lite@^1.0.30001061, caniuse-lite@^1.0.30001093, caniuse-lite@^1.0.30001097: - version "1.0.30001100" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001100.tgz#2a58615e0c01cf716ab349b20ca4d86ef944aa4e" - integrity sha512-0eYdp1+wFCnMlCj2oudciuQn2B9xAFq3WpgpcBIZTxk/1HNA/O2YA7rpeYhnOqsqAJq1AHUgx6i1jtafg7m2zA== +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001032, caniuse-lite@^1.0.30001061, caniuse-lite@^1.0.30001093, caniuse-lite@^1.0.30001109: + version "1.0.30001111" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001111.tgz#dd0ce822c70eb6c7c068e4a55c22e19ec1501298" + integrity sha512-xnDje2wchd/8mlJu8sXvWxOGvMgv+uT3iZ3bkIAynKOzToCssWCmkz/ZIkQBs/2pUB4uwnJKVORWQ31UkbVjOg== canonical-path@1.0.0: version "1.0.0" @@ -4201,7 +4181,7 @@ chartjs-color@^2.1.0: chartjs-color-string "^0.6.0" color-convert "^1.9.3" -"chokidar@>=2.0.0 <4.0.0", chokidar@^3.0.0, chokidar@^3.0.2, chokidar@^3.2.1, chokidar@^3.4.0: +"chokidar@>=2.0.0 <4.0.0", chokidar@^3.0.0, chokidar@^3.0.2, chokidar@^3.2.1, chokidar@^3.4.1: version "3.4.1" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.1.tgz#e905bdecf10eaa0a0b1db0c664481cc4cbc22ba1" integrity sha512-TQTJyr2stihpC4Sya9hs2Xh+O2wf+igjL36Y75xx2WdHuiICcn/XJza46Jwt0eT5hVpQOzo3FpY3cj3RVYLX0g== @@ -4305,9 +4285,9 @@ cli-cursor@^3.1.0: restore-cursor "^3.1.0" cli-spinners@^2.0.0, cli-spinners@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.3.0.tgz#0632239a4b5aa4c958610142c34bb7a651fc8df5" - integrity sha512-Xs2Hf2nzrvJMFKimOR7YR0QwZ8fc0u98kdtwN1eNAZzNQgH3vK2pXzff6GJtKh7S5hoJ87ECiAiZFS2fb5Ii2w== + version "2.4.0" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.4.0.tgz#c6256db216b878cfba4720e719cec7cf72685d7f" + integrity sha512-sJAofoarcm76ZGpuooaO0eDy8saEy+YoZBLjC4h8srt4jeBnkYeOgqxgsJQTpyt2LjI5PTfLJHSL+41Yu4fEJA== cli-width@^2.0.0: version "2.2.1" @@ -4455,7 +4435,7 @@ color@^3.0.0: color-convert "^1.9.1" color-string "^1.5.2" -colorette@^1.2.0: +colorette@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.1.tgz#4d0b921325c14faf92633086a536db6e89564b1b" integrity sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw== @@ -4924,12 +4904,12 @@ cosmiconfig@^6.0.0: yaml "^1.7.2" create-ecdh@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.3.tgz#c9111b6f33045c4697f144787f9254cdc77c45ff" - integrity sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw== + version "4.0.4" + resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e" + integrity sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A== dependencies: bn.js "^4.1.0" - elliptic "^6.0.0" + elliptic "^6.5.3" create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0: version "1.2.0" @@ -5051,13 +5031,12 @@ css-select@^2.0.0: nth-check "^1.0.2" css-selector-tokenizer@^0.7.1: - version "0.7.2" - resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.7.2.tgz#11e5e27c9a48d90284f22d45061c303d7a25ad87" - integrity sha512-yj856NGuAymN6r8bn8/Jl46pR+OC3eEvAhfGYDUe7YPtTPAYrSSw4oAniZ9Y8T5B92hjhwTBLUen0/vKPxf6pw== + version "0.7.3" + resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.7.3.tgz#735f26186e67c749aaf275783405cf0661fae8f1" + integrity sha512-jWQv3oCEL5kMErj4wRnK/OPoBi0D+P1FR2cDCKYPaMeD2eW3/mttav8HT4hT1CKopiJI/psEULjkClhvJo4Lvg== dependencies: cssesc "^3.0.0" fastparse "^1.1.2" - regexpu-core "^4.6.0" css-tree@1.0.0-alpha.37: version "1.0.0-alpha.37" @@ -5291,9 +5270,9 @@ data-urls@^2.0.0: whatwg-url "^8.0.0" date-fns@^2.10.0: - version "2.14.0" - resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.14.0.tgz#359a87a265bb34ef2e38f93ecf63ac453f9bc7ba" - integrity sha512-1zD+68jhFgDIM0rF05rcwYO8cExdNqxjq4xP1QKM60Q45mnO6zaMWB4tOzrIr4M4GSLntsKeE4c9Bdl2jhL/yw== + version "2.15.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.15.0.tgz#424de6b3778e4e69d3ff27046ec136af58ae5d5f" + integrity sha512-ZCPzAMJZn3rNUvvQIMlXhDr4A+Ar07eLeGsGREoWU19a3Pqf5oYa+ccd+B3F6XVtQY6HANMFdOQ8A+ipFnvJdQ== dateformat@^3.0.0: version "3.0.3" @@ -5697,11 +5676,11 @@ ee-first@1.1.1: integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= electron-to-chromium@^1.3.488: - version "1.3.498" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.498.tgz#fd7188c8a49d6d0b5df1df55a1f1a4bf2c177457" - integrity sha512-W1hGwaQEU8j9su2jeAr3aabkPuuXw+j8t73eajGAkEJWbfWiwbxBwQN/8Qmv2qCy3uCDm2rOAaZneYQM8VGC4w== + version "1.3.520" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.520.tgz#dfda0a14a4aed785cbddfdb505ea122f75978392" + integrity sha512-q6H9E1sXDCjRHP+X06vcP+N0ki8ZvYoRPZfKnDuiRX10WWXxEHzKFVf4O9rBFMpuPtR3M+2KAdJnugJoBBp3Rw== -elliptic@^6.0.0, elliptic@^6.5.2: +elliptic@^6.5.3: version "6.5.3" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.3.tgz#cb59eb2efdaf73a0bd78ccd7015a62ad6e0f93d6" integrity sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw== @@ -5782,9 +5761,9 @@ env-paths@^2.2.0: integrity sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA== envinfo@^7.3.1: - version "7.5.1" - resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.5.1.tgz#93c26897225a00457c75e734d354ea9106a72236" - integrity sha512-hQBkDf2iO4Nv0CNHpCuSBeaSrveU6nThVxFGTrq/eDlV716UQk09zChaJae4mZRsos1x4YLY2TaH3LHUae3ZmQ== + version "7.7.2" + resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.7.2.tgz#098f97a0e902f8141f9150553c92dbb282c4cabe" + integrity sha512-k3Eh5bKuQnZjm49/L7H4cHzs2FlL5QjbTB3JrPxoTI8aJG7hVMe4uKyJxSYH4ahseby2waUwk5OaKX/nAsaYgg== err-code@^1.0.0: version "1.1.2" @@ -5957,9 +5936,9 @@ eventemitter3@^4.0.0: integrity sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ== events@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.1.0.tgz#84279af1b34cb75aa88bf5ff291f6d0bd9b31a59" - integrity sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg== + version "3.2.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.2.0.tgz#93b87c18f8efcd4202a461aec4dfc0556b639379" + integrity sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg== eventsource@^1.0.7: version "1.0.7" @@ -6158,11 +6137,6 @@ extsprintf@^1.2.0: resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= -fast-deep-equal@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" - integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= - fast-deep-equal@^3.1.1: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -6852,11 +6826,11 @@ har-schema@^2.0.0: integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= har-validator@~5.1.3: - version "5.1.3" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" - integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== + version "5.1.5" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd" + integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w== dependencies: - ajv "^6.5.5" + ajv "^6.12.3" har-schema "^2.0.0" hard-rejection@^2.1.0: @@ -7565,9 +7539,9 @@ is-directory@^0.3.1: integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE= is-docker@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.0.0.tgz#2cb0df0e75e2d064fe1864c37cdeacb7b2dcf25b" - integrity sha512-pJEdRugimx4fBMra5z2/5iRdZ63OhYV0vr0Dwm5+xtW4D1FvRkB8hamMIhnWfyJeDdyr/aa7BDyNbtG38VxgoQ== + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.1.1.tgz#4125a88e44e450d384e09047ede71adc2d144156" + integrity sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw== is-extendable@^0.1.0, is-extendable@^0.1.1: version "0.1.1" @@ -7727,10 +7701,10 @@ is-plain-object@^2.0.3, is-plain-object@^2.0.4: dependencies: isobject "^3.0.1" -is-plain-object@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-3.0.1.tgz#662d92d24c0aa4302407b0d45d21f2251c85f85b" - integrity sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g== +is-plain-object@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-4.1.1.tgz#1a14d6452cbd50790edc7fdaa0aed5a40a35ebb5" + integrity sha512-5Aw8LLVsDlZsETVMhoMXzqsXwQqr/0vlnBYzIXJbYo2F4yYlhLHs+Ez7Bod7IIQKWkJbJfxrWD7pA1Dw1TKrwA== is-reference@^1.1.2: version "1.2.1" @@ -7740,9 +7714,9 @@ is-reference@^1.1.2: "@types/estree" "*" is-regex@^1.0.4, is-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.0.tgz#ece38e389e490df0dc21caea2bd596f987f767ff" - integrity sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw== + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.1.tgz#c6f98aacc546f6cec5468a07b7b153ab564a57b9" + integrity sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg== dependencies: has-symbols "^1.0.1" @@ -7894,10 +7868,10 @@ jasmine-core@~2.8.0: resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-2.8.0.tgz#bcc979ae1f9fd05701e45e52e65d3a5d63f1a24e" integrity sha1-vMl5rh+f0FcB5F5S5l06XWPxok4= -jasmine-core@~3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-3.5.0.tgz#132c23e645af96d85c8bca13c8758b18429fc1e4" - integrity sha512-nCeAiw37MIMA9w9IXso7bRaLl+c/ef3wnxsoSAlYrzS+Ot0zTG6nU8G/cIfGkqpkjX2wNaIW9RFG0TwIFnG6bA== +jasmine-core@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-3.6.0.tgz#491f3bb23941799c353ceb7a45b38a950ebc5a20" + integrity sha512-8uQYa7zJN8hq9z+g8z1bqCfdC8eoDAeVnM5sfqs7KHv9/ifoJ500m018fpFc7RDaO6SWCLCXwo/wPSNcdYTgcw== jasmine@2.8.0: version "2.8.0" @@ -7909,12 +7883,12 @@ jasmine@2.8.0: jasmine-core "~2.8.0" jasmine@^3.3.1: - version "3.5.0" - resolved "https://registry.yarnpkg.com/jasmine/-/jasmine-3.5.0.tgz#7101eabfd043a1fc82ac24e0ab6ec56081357f9e" - integrity sha512-DYypSryORqzsGoMazemIHUfMkXM7I7easFaxAvNM3Mr6Xz3Fy36TupTrAOxZWN8MVKEU5xECv22J4tUQf3uBzQ== + version "3.6.1" + resolved "https://registry.yarnpkg.com/jasmine/-/jasmine-3.6.1.tgz#a20456b309a669b547a3c24bb2120f16f70cfc65" + integrity sha512-Jqp8P6ZWkTVFGmJwBK46p+kJNrZCdqkQ4GL+PGuBXZwK1fM4ST9BizkYgIwCFqYYqnTizAy6+XG2Ej5dFrej9Q== dependencies: - glob "^7.1.4" - jasmine-core "~3.5.0" + fast-glob "^2.2.6" + jasmine-core "~3.6.0" jasminewd2@^2.1.0: version "2.2.0" @@ -8250,11 +8224,12 @@ jest-snapshot@^25.5.1: semver "^6.3.0" jest-util@26.x: - version "26.1.0" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-26.1.0.tgz#80e85d4ba820decacf41a691c2042d5276e5d8d8" - integrity sha512-rNMOwFQevljfNGvbzNQAxdmXQ+NawW/J72dmddsK0E8vgxXCMtwQ/EH0BiWEIxh0hhMcTsxwAxINt7Lh46Uzbg== + version "26.2.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-26.2.0.tgz#0597d2a27c559340957609f106c408c17c1d88ac" + integrity sha512-YmDwJxLZ1kFxpxPfhSJ0rIkiZOM0PQbRcfH0TzJOhqCisCAsI1WcmoQqO83My9xeVA2k4n+rzg2UuexVKzPpig== dependencies: - "@jest/types" "^26.1.0" + "@jest/types" "^26.2.0" + "@types/node" "*" chalk "^4.0.0" graceful-fs "^4.2.4" is-ci "^2.0.0" @@ -8312,10 +8287,11 @@ jest-worker@^25.5.0: supports-color "^7.0.0" jest-worker@^26.0.0: - version "26.1.0" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.1.0.tgz#65d5641af74e08ccd561c240e7db61284f82f33d" - integrity sha512-Z9P5pZ6UC+kakMbNJn+tA2RdVdNX5WH1x+5UCBZ9MxIK24pjYtFt96fK+UwBTrjLYm232g1xz0L3eTh51OW+yQ== + version "26.2.1" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.2.1.tgz#5d630ab93f666b53f911615bc13e662b382bd513" + integrity sha512-+XcGMMJDTeEGncRb5M5Zq9P7K4sQ1sirhjdOxsN1462h6lFo9w59bl2LVQmdGEEeU3m+maZCkS2Tcc9SfCHO4A== dependencies: + "@types/node" "*" merge-stream "^2.0.0" supports-color "^7.0.0" @@ -8583,9 +8559,9 @@ less-loader@6.1.0: schema-utils "^2.6.6" less@^3.10.3, less@^3.11.1: - version "3.12.0" - resolved "https://registry.yarnpkg.com/less/-/less-3.12.0.tgz#f23f9bd94ba72495994865d84e3b5ac6ee8a0363" - integrity sha512-3mmSHFRP9hGxxQgAKgChfau1LO3ksV/zyZf1qd2ENyBV778NA9Ids99wFRA20jE+5prT7oScKod8PoGlxSe1gg== + version "3.12.2" + resolved "https://registry.yarnpkg.com/less/-/less-3.12.2.tgz#157e6dd32a68869df8859314ad38e70211af3ab4" + integrity sha512-+1V2PCMFkL+OIj2/HrtrvZw0BC0sYLMICJfbQjuj/K8CEnlrFX6R5cKKgzzttsZDHyxQNL1jqMREjKN3ja/E3Q== dependencies: tslib "^1.10.0" optionalDependencies: @@ -8865,9 +8841,9 @@ lru-cache@^6.0.0: yallist "^4.0.0" macos-release@^2.2.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-2.4.0.tgz#837b39fc01785c3584f103c5599e0f0c8068b49e" - integrity sha512-ko6deozZYiAkqa/0gmcsz+p4jSy3gY7/ZsCEokPaYd8k+6/aXGkiTgr61+Owup7Sf+xjqW8u2ElhoM9SEcEfuA== + version "2.4.1" + resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-2.4.1.tgz#64033d0ec6a5e6375155a74b1a1eba8e509820ac" + integrity sha512-H/QHeBIN1fIGJX517pvK8IEK53yQOW7YcEI55oYtgjDdoCQQz7eJS94qt5kNrscReEyuD/JcdFCm2XBEcGOITg== magic-string@0.25.3: version "0.25.3" @@ -9224,9 +9200,9 @@ minipass-flush@^1.0.5: minipass "^3.0.0" minipass-pipeline@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/minipass-pipeline/-/minipass-pipeline-1.2.3.tgz#55f7839307d74859d6e8ada9c3ebe72cec216a34" - integrity sha512-cFOknTvng5vqnwOpDsZTWhNll6Jf8o2x+/diplafmxpuIymAjzoOolZG0VvQf3V2HgqzJNhnuKHYp2BqDgz8IQ== + version "1.2.4" + resolved "https://registry.yarnpkg.com/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz#68472f79711c084657c067c5c6ad93cddea8214c" + integrity sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A== dependencies: minipass "^3.0.0" @@ -9291,12 +9267,12 @@ mkdirp-promise@^5.0.1: dependencies: mkdirp "*" -mkdirp@*, mkdirp@1.x, mkdirp@^1.0.3, mkdirp@^1.0.4: +mkdirp@*, mkdirp@1.x, mkdirp@^1.0.3, mkdirp@^1.0.4, mkdirp@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== -mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@~0.5.1, mkdirp@~0.5.x: +mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.5, mkdirp@~0.5.1, mkdirp@~0.5.x: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== @@ -9412,9 +9388,9 @@ nanomatch@^1.2.9: to-regex "^3.0.1" native-request@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/native-request/-/native-request-1.0.5.tgz#da67637c0a0ed0758243d1a688d647612f2d5a0a" - integrity sha512-7wU3DvBGAJQxWuMR3F62zrhB7hxNj2DdlC/eBVrCgavc6+ZpFZOqS/PsR7QyUPLMkFk0GvvzoeeOAZGLLnObnA== + version "1.0.7" + resolved "https://registry.yarnpkg.com/native-request/-/native-request-1.0.7.tgz#ff742dc555b4c8f2f1c14b548639ba174e573856" + integrity sha512-9nRjinI9bmz+S7dgNtf4A70+/vPhnd+2krGpy4SUlADuOuSa24IDkNaZ+R/QT1wQ6S8jBdi6wE7fLekFZNfUpQ== natural-compare@^1.4.0: version "1.4.0" @@ -9437,14 +9413,14 @@ next-tick@~1.0.0: integrity sha1-yobR/ogoFpsBICCOPchCS524NCw= ng-packagr@^10.0.0: - version "10.0.2" - resolved "https://registry.yarnpkg.com/ng-packagr/-/ng-packagr-10.0.2.tgz#31aed1cc373a7b84baaef73c1dae29ccc667a57f" - integrity sha512-nmw6nQLwPBVIdU1M0Omo6BLeg5Iugj3hmq7pd+Un7hZ33T+D8vfB88jXUf/DklRPLIwRWlu+5evQNj76VPhYUw== + version "10.0.3" + resolved "https://registry.yarnpkg.com/ng-packagr/-/ng-packagr-10.0.3.tgz#ef3fa4c3dbc826695b8e12b307172e23504e1229" + integrity sha512-M10y3eeAbIaU+NvNZEeGnuoWxZis0GFj9EKetYmmsXMjHxsPqEAZ1PUkKioIo0RPXuR1N4QGW0MSegHp5kBFeg== dependencies: "@rollup/plugin-commonjs" "^14.0.0" "@rollup/plugin-json" "^4.0.0" "@rollup/plugin-node-resolve" "^8.0.0" - ajv "^6.10.2" + ajv "^6.12.3" autoprefixer "^9.6.5" browserslist "^4.7.0" chalk "^4.0.0" @@ -9590,9 +9566,9 @@ node-notifier@^6.0.0: which "^1.3.1" node-releases@^1.1.58: - version "1.1.59" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.59.tgz#4d648330641cec704bff10f8e4fe28e453ab8e8e" - integrity sha512-H3JrdUczbdiwxN5FuJPyCHnGHIFqQ0wWxo+9j1kAXAzqNMAHlo+4I/sYYxpyK0irQ73HgdiyzD32oqQDcU2Osw== + version "1.1.60" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.60.tgz#6948bdfce8286f0b5d0e5a88e8384e954dfe7084" + integrity sha512-gsO4vjEdQaTusZAEebUWp2a5d7dF5DYoIpDG7WySnk7BuZDW+GPpHXoXXuYawRBr/9t5q54tirPz79kFIWg4dA== node-sass-tilde-importer@^1.0.0: version "1.0.2" @@ -9915,9 +9891,9 @@ onetime@^2.0.0: mimic-fn "^1.0.0" onetime@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.0.tgz#fff0f3c91617fe62bb50189636e99ac8a6df7be5" - integrity sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q== + version "5.1.1" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.1.tgz#5c8016847b0d67fcedb7eef254751cfcdc7e9418" + integrity sha512-ZpZpjcJeugQfWsfyQlshVoowIIQ1qBGSVll4rfDq6JJVO//fesjoX808hXWfBjY+ROZgpKDI5TRSRBSoJiZ8eg== dependencies: mimic-fn "^2.1.0" @@ -10241,9 +10217,9 @@ parse-json@^4.0.0: json-parse-better-errors "^1.0.1" parse-json@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.0.0.tgz#73e5114c986d143efa3712d4ea24db9a4266f60f" - integrity sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw== + version "5.0.1" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.0.1.tgz#7cfe35c1ccd641bce3981467e6c2ece61b3b3878" + integrity sha512-ztoZ4/DYeXQq4E21v169sC8qWINGpcosGv9XhTDvg9/hWvx/zrFkc9BiWxR58OJLHGk28j5BL0SDLeV2WmFZlQ== dependencies: "@babel/code-frame" "^7.0.0" error-ex "^1.3.1" @@ -10458,13 +10434,13 @@ pnp-webpack-plugin@1.6.4: ts-pnp "^1.1.6" portfinder@^1.0.26: - version "1.0.26" - resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.26.tgz#475658d56ca30bed72ac7f1378ed350bd1b64e70" - integrity sha512-Xi7mKxJHHMI3rIUrnm/jjUgwhbYMkp/XKEcZX3aG4BrumLpq3nmoQMX+ClYnDZnZ/New7IatC1no5RX0zo1vXQ== + version "1.0.28" + resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.28.tgz#67c4622852bd5374dd1dd900f779f53462fac778" + integrity sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA== dependencies: async "^2.6.2" debug "^3.1.1" - mkdirp "^0.5.1" + mkdirp "^0.5.5" posix-character-classes@^0.1.0: version "0.1.1" @@ -10625,14 +10601,14 @@ postcss-modules-extract-imports@^2.0.0: postcss "^7.0.5" postcss-modules-local-by-default@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.2.tgz#e8a6561be914aaf3c052876377524ca90dbb7915" - integrity sha512-jM/V8eqM4oJ/22j0gx4jrp63GSvDH6v86OqyTHHUvk4/k1vceipZsaymiZ5PvocqZOl5SFHiFJqjs3la0wnfIQ== + version "3.0.3" + resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.3.tgz#bb14e0cc78279d504dbdcbfd7e0ca28993ffbbb0" + integrity sha512-e3xDq+LotiGesympRlKNgaJ0PCzoUIdpH0dj47iWAui/kyTgh3CiAr1qP54uodmJhl6p9rN6BoNcdEDVJx9RDw== dependencies: icss-utils "^4.1.1" - postcss "^7.0.16" + postcss "^7.0.32" postcss-selector-parser "^6.0.2" - postcss-value-parser "^4.0.0" + postcss-value-parser "^4.1.0" postcss-modules-scope@^2.2.0: version "2.2.0" @@ -10813,7 +10789,7 @@ postcss-value-parser@^3.0.0, postcss-value-parser@^3.2.3: resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== -postcss-value-parser@^4.0.0, postcss-value-parser@^4.0.2, postcss-value-parser@^4.0.3, postcss-value-parser@^4.1.0: +postcss-value-parser@^4.0.2, postcss-value-parser@^4.0.3, postcss-value-parser@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== @@ -10836,7 +10812,7 @@ postcss@7.0.31: source-map "^0.6.1" supports-color "^6.1.0" -postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.16, postcss@^7.0.2, postcss@^7.0.27, postcss@^7.0.29, postcss@^7.0.30, postcss@^7.0.32, postcss@^7.0.5, postcss@^7.0.6: +postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.2, postcss@^7.0.27, postcss@^7.0.29, postcss@^7.0.30, postcss@^7.0.32, postcss@^7.0.5, postcss@^7.0.6: version "7.0.32" resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.32.tgz#4310d6ee347053da3433db2be492883d62cec59d" integrity sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw== @@ -10886,11 +10862,11 @@ pretty-format@^25.2.1, pretty-format@^25.5.0: react-is "^16.12.0" pretty-format@^26.0.0: - version "26.1.0" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.1.0.tgz#272b9cd1f1a924ab5d443dc224899d7a65cb96ec" - integrity sha512-GmeO1PEYdM+non4BKCj+XsPJjFOJIPnsLewqhDVoqY1xo0yNmDas7tC2XwpMrRAHR3MaE2hPo37deX5OisJ2Wg== + version "26.2.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.2.0.tgz#83ecc8d7de676ff224225055e72bd64821cec4f1" + integrity sha512-qi/8IuBu2clY9G7qCXgCdD1Bf9w+sXakdHTRToknzMtVy0g7c4MBWaZy7MfB7ndKZovRO6XRwJiAYqq+MC7SDA== dependencies: - "@jest/types" "^26.1.0" + "@jest/types" "^26.2.0" ansi-regex "^5.0.0" ansi-styles "^4.0.0" react-is "^16.12.0" @@ -11358,11 +11334,16 @@ regenerate@^1.4.0: resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.1.tgz#cad92ad8e6b591773485fbe05a485caf4f457e6f" integrity sha512-j2+C8+NtXQgEKWk49MMP5P/u2GhnahTtVkRIHr5R5lVRlbKvmQ+oS+A5aLKWp2ma5VkT8sh6v+v4hbH0YHR66A== -regenerator-runtime@0.13.5, regenerator-runtime@^0.13.4: +regenerator-runtime@0.13.5: version "0.13.5" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz#d878a1d094b4306d10b9096484b33ebd55e26697" integrity sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA== +regenerator-runtime@^0.13.4: + version "0.13.7" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55" + integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew== + regenerator-transform@^0.14.2: version "0.14.5" resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.5.tgz#c98da154683671c9c4dcb16ece736517e1b7feb4" @@ -11391,7 +11372,7 @@ regexp.prototype.flags@^1.2.0: define-properties "^1.1.3" es-abstract "^1.17.0-next.1" -regexpu-core@^4.6.0, regexpu-core@^4.7.0: +regexpu-core@^4.7.0: version "4.7.0" resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.7.0.tgz#fcbf458c50431b0bb7b45d6967b8192d91f3d938" integrity sha512-TQ4KXRnIn6tz6tjnrXEkD/sshygKH/j5KzK86X8MkeHyZ8qst/LZ89j3X4/8HEIfHANTFIP/AbXakeRhWIl5YQ== @@ -11460,19 +11441,19 @@ replace-in-file@^4.1.3: glob "^7.1.6" yargs "^15.0.2" -request-promise-core@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.3.tgz#e9a3c081b51380dfea677336061fea879a829ee9" - integrity sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ== +request-promise-core@1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.4.tgz#3eedd4223208d419867b78ce815167d10593a22f" + integrity sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw== dependencies: - lodash "^4.17.15" + lodash "^4.17.19" request-promise-native@^1.0.7: - version "1.0.8" - resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.8.tgz#a455b960b826e44e2bf8999af64dff2bfe58cb36" - integrity sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ== + version "1.0.9" + resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.9.tgz#e407120526a5efdc9a39b28a5679bf47b9d9dc28" + integrity sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g== dependencies: - request-promise-core "1.1.3" + request-promise-core "1.1.4" stealthy-require "^1.1.1" tough-cookie "^2.3.3" @@ -11708,9 +11689,9 @@ rollup@2.10.9: fsevents "~2.1.2" rollup@^2.8.0: - version "2.21.0" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.21.0.tgz#d2e114533812043d5c9b7b0a83f1b2a242e4e1d6" - integrity sha512-BEGgy+wSzux7Ycq58pRiWEOBZaXRXTuvzl1gsm7gqmsAHxkWf9nyA5V2LN9fGSHhhDQd0/C13iRzSh4bbIpWZQ== + version "2.23.0" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.23.0.tgz#b7ab1fee0c0e60132fd0553c4df1e9cdacfada9d" + integrity sha512-vLNmZFUGVwrnqNAJ/BvuLk1MtWzu4IuoqsH9UWK5AIdO3rt8/CSiJNvPvCIvfzrbNsqKbNzPAG1V2O4eTe2XZg== optionalDependencies: fsevents "~2.1.2" @@ -11758,9 +11739,9 @@ rxjs@6.5.5, rxjs@~6.5.4: tslib "^1.9.0" rxjs@^6.4.0, rxjs@^6.5.0, rxjs@^6.5.2, rxjs@^6.5.3: - version "6.6.0" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.0.tgz#af2901eedf02e3a83ffa7f886240ff9018bbec84" - integrity sha512-3HMA8z/Oz61DUHe+SdOiQyzIf4tOx5oQHmMir7IZEu6TMqCLHT4LRcmNaUS0NwOz8VLvmmBduMsoaUvMaIiqzg== + version "6.6.2" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.2.tgz#8096a7ac03f2cc4fe5860ef6e572810d9e01c0d2" + integrity sha512-BHdBMVoWC2sL26w//BCu3YzKT4s2jip/WhwsGEDmeKYBhKDZeYezVUnHatYB7L85v5xs0BAQmg6BEYJEKxBabg== dependencies: tslib "^1.9.0" @@ -12641,7 +12622,7 @@ stylus-loader@3.0.2: lodash.clonedeep "^4.5.0" when "~3.6.x" -stylus@0.54.7, stylus@^0.54.7: +stylus@0.54.7: version "0.54.7" resolved "https://registry.yarnpkg.com/stylus/-/stylus-0.54.7.tgz#c6ce4793965ee538bcebe50f31537bfc04d88cd2" integrity sha512-Yw3WMTzVwevT6ZTrLCYNHAFmanMxdylelL3hkWNgPMeTCpMwpV3nXjpOHuBXtFv7aiO2xRuQS6OoAdgkNcSNug== @@ -12655,6 +12636,20 @@ stylus@0.54.7, stylus@^0.54.7: semver "^6.0.0" source-map "^0.7.3" +stylus@^0.54.7: + version "0.54.8" + resolved "https://registry.yarnpkg.com/stylus/-/stylus-0.54.8.tgz#3da3e65966bc567a7b044bfe0eece653e099d147" + integrity sha512-vr54Or4BZ7pJafo2mpf0ZcwA74rpuYCZbxrHBsH8kbcXOwSfvBFwsRfpGO5OD5fhG5HDCFW737PKaawI7OqEAg== + dependencies: + css-parse "~2.0.0" + debug "~3.1.0" + glob "^7.1.6" + mkdirp "~1.0.4" + safer-buffer "^2.1.2" + sax "~1.2.4" + semver "^6.3.0" + source-map "^0.7.3" + supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" @@ -12719,9 +12714,9 @@ symbol-tree@^3.2.2: integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== symlink-manager@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/symlink-manager/-/symlink-manager-1.5.0.tgz#106576eecfb611b6c320d3f643d714583248b33f" - integrity sha512-+5n/FYEhoxsLhMqxjTn74676xox3Nkvw71lMgk0jjeTwHnqiqTjp16IPONy6Rld25dCtrTgcDJ7lYkx+b1Diog== + version "1.5.1" + resolved "https://registry.yarnpkg.com/symlink-manager/-/symlink-manager-1.5.1.tgz#75721cacf2247ad6817b0ca5c033b87c8d4599fd" + integrity sha512-LKRsow5vrpOpk7dOxfj9uWDfb2g+f7GoaJdvTnUm5NyodQuHZJxHZpqsHvoKIvMyrlpkL4qbC8HXx2DbkQ+w8Q== dependencies: arg "^4.1.0" chokidar "^3.0.2" @@ -13062,9 +13057,9 @@ trim-off-newlines@^1.0.0: integrity sha1-n5up2e+odkw4dpi8v+sshI8RrbM= ts-jest@^26.0.0: - version "26.1.2" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-26.1.2.tgz#dd2e832ffae9cb803361483b6a3010a6413dc475" - integrity sha512-V4SyBDO9gOdEh+AF4KtXJeP+EeI4PkOrxcA8ptl4o8nCXUVM5Gg/8ngGKneS5BsZaR9DXVQNqj9k+iqGAnpGow== + version "26.1.4" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-26.1.4.tgz#87d41a96016a8efe4b8cc14501d3785459af6fa6" + integrity sha512-Nd7diUX6NZWfWq6FYyvcIPR/c7GbEF75fH1R6coOp3fbNzbRJBZZAn0ueVS0r8r9ral1VcrpneAFAwB3TsVS1Q== dependencies: bs-logger "0.x" buffer-from "1.x" @@ -13097,9 +13092,9 @@ ts-pnp@^1.1.6: integrity sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw== ts-toolbelt@^6.9.9: - version "6.13.6" - resolved "https://registry.yarnpkg.com/ts-toolbelt/-/ts-toolbelt-6.13.6.tgz#4c9fad69bb3389323fe60982f18cd06b72f7d42a" - integrity sha512-qqIexrhqCNOuSRCrJMOKor8UJxA4DYMB6fNpBmRAKJgmLXNj8TAiuCrs7Nm08fva+MEdQhEXmsnjBQarNSIWZg== + version "6.14.0" + resolved "https://registry.yarnpkg.com/ts-toolbelt/-/ts-toolbelt-6.14.0.tgz#6f1082c5f59b39ff5d71ff427207bb92ffca4534" + integrity sha512-/EihMSk7AQn1n1zad6765tZNr7OCxAP75nS/VGzcZlWLlDa2izLJZV7SJSNDweSKusKuTDyHZhKFyUZW6UUnqg== tsickle@^0.38.1: version "0.38.1" @@ -13111,15 +13106,15 @@ tslib@2.0.0, tslib@^2.0.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.0.tgz#18d13fc2dce04051e20f074cc8387fd8089ce4f3" integrity sha512-lTqkx847PI7xEDYJntxZH89L2/aXInsyF2luSafe/+0fHOMjlBNXdH6th7f70qxLDhul7KZK0zC8V5ZIyHl0/g== -tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: +tslib@^1.10.0, tslib@^1.13.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: version "1.13.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043" integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q== tslint@~6.1.0: - version "6.1.2" - resolved "https://registry.yarnpkg.com/tslint/-/tslint-6.1.2.tgz#2433c248512cc5a7b2ab88ad44a6b1b34c6911cf" - integrity sha512-UyNrLdK3E0fQG/xWNqAFAC5ugtFyPO4JJR1KyyfQAyzR8W0fTRrC91A8Wej4BntFzcvETdCSDa/4PnNYJQLYiA== + version "6.1.3" + resolved "https://registry.yarnpkg.com/tslint/-/tslint-6.1.3.tgz#5c23b2eccc32487d5523bd3a470e9aa31789d904" + integrity sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg== dependencies: "@babel/code-frame" "^7.0.0" builtin-modules "^1.1.1" @@ -13132,7 +13127,7 @@ tslint@~6.1.0: mkdirp "^0.5.3" resolve "^1.3.2" semver "^5.3.0" - tslib "^1.10.0" + tslib "^1.13.0" tsutils "^2.29.0" tsutils@^2.29.0: @@ -13227,14 +13222,14 @@ typedarray@^0.0.6: integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= typescript@^3.5.2, typescript@~3.9.5: - version "3.9.6" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.6.tgz#8f3e0198a34c3ae17091b35571d3afd31999365a" - integrity sha512-Pspx3oKAPJtjNwE92YS05HQoY7z2SFyOpHo9MqJor3BXAGNaPUs83CuVp9VISFkSjyRfiTpmKuAYGJB7S7hOxw== + version "3.9.7" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa" + integrity sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw== uglify-js@^3.1.4: - version "3.10.0" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.10.0.tgz#397a7e6e31ce820bfd1cb55b804ee140c587a9e7" - integrity sha512-Esj5HG5WAyrLIdYU74Z3JdG2PxdIusvj6IWHMtlyESxc7kcDz7zYlYjpnSokn1UbpV0d/QX9fan7gkCNd/9BQA== + version "3.10.1" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.10.1.tgz#dd14767eb7150de97f2573a5ff210db14fffe4ad" + integrity sha512-RjxApKkrPJB6kjJxQS3iZlf///REXWYxYJxO/MpmlQzVkDWVI3PSnCBWezMecmTU/TRkNxrl8bmsfFQCp+LO+Q== uid-number@0.0.6: version "0.0.6" @@ -13326,12 +13321,10 @@ universal-user-agent@^4.0.0: dependencies: os-name "^3.1.0" -universal-user-agent@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-5.0.0.tgz#a3182aa758069bf0e79952570ca757de3579c1d9" - integrity sha512-B5TPtzZleXyPrUMKCpEHFmVhMN6EhmJYjG5PQna9s7mXeSqGTLap4OpqLl5FCEFUI3UBmllkETwKf/db66Y54Q== - dependencies: - os-name "^3.1.0" +universal-user-agent@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.0.tgz#3381f8503b251c0d9cd21bc1de939ec9df5480ee" + integrity sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w== universalify@^0.1.0: version "0.1.2" @@ -13560,14 +13553,14 @@ watchpack-chokidar2@^2.0.0: chokidar "^2.1.8" watchpack@^1.6.1: - version "1.7.2" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.7.2.tgz#c02e4d4d49913c3e7e122c3325365af9d331e9aa" - integrity sha512-ymVbbQP40MFTp+cNMvpyBpBtygHnPzPkHqoIwRRj/0B8KhqQwV8LaKjtbaxF2lK4vl8zN9wCxS46IFCU5K4W0g== + version "1.7.4" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.7.4.tgz#6e9da53b3c80bb2d6508188f5b200410866cd30b" + integrity sha512-aWAgTW4MoSJzZPAicljkO1hsi1oKj/RRq/OJQh2PKI2UKL04c2Bs+MBOB+BBABHTXJpf9mCwHN7ANCvYsvY2sg== dependencies: graceful-fs "^4.1.2" neo-async "^2.5.0" optionalDependencies: - chokidar "^3.4.0" + chokidar "^3.4.1" watchpack-chokidar2 "^2.0.0" wbuf@^1.1.0, wbuf@^1.7.3: diff --git a/npm/packs/anchor-js/package.json b/npm/packs/anchor-js/package.json index ea89140113..46682b6f63 100644 --- a/npm/packs/anchor-js/package.json +++ b/npm/packs/anchor-js/package.json @@ -1,11 +1,11 @@ { - "version": "3.0.4", + "version": "3.0.5", "name": "@abp/anchor-js", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/core": "~3.0.4", + "@abp/core": "~3.0.5", "anchor-js": "^4.2.2" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431" diff --git a/npm/packs/aspnetcore.mvc.ui.theme.basic/package.json b/npm/packs/aspnetcore.mvc.ui.theme.basic/package.json index 465ba8a266..6fc6fed793 100644 --- a/npm/packs/aspnetcore.mvc.ui.theme.basic/package.json +++ b/npm/packs/aspnetcore.mvc.ui.theme.basic/package.json @@ -1,11 +1,11 @@ { - "version": "3.0.4", + "version": "3.0.5", "name": "@abp/aspnetcore.mvc.ui.theme.basic", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.shared": "~3.0.4" + "@abp/aspnetcore.mvc.ui.theme.shared": "~3.0.5" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431" } diff --git a/npm/packs/aspnetcore.mvc.ui.theme.shared/package.json b/npm/packs/aspnetcore.mvc.ui.theme.shared/package.json index 27d1d777c3..48c1f069cd 100644 --- a/npm/packs/aspnetcore.mvc.ui.theme.shared/package.json +++ b/npm/packs/aspnetcore.mvc.ui.theme.shared/package.json @@ -1,24 +1,24 @@ { - "version": "3.0.4", + "version": "3.0.5", "name": "@abp/aspnetcore.mvc.ui.theme.shared", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/aspnetcore.mvc.ui": "~3.0.4", - "@abp/bootstrap": "~3.0.4", - "@abp/bootstrap-datepicker": "~3.0.4", - "@abp/datatables.net-bs4": "~3.0.4", - "@abp/font-awesome": "~3.0.4", - "@abp/jquery-form": "~3.0.4", - "@abp/jquery-validation-unobtrusive": "~3.0.4", - "@abp/lodash": "~3.0.4", - "@abp/luxon": "~3.0.4", - "@abp/malihu-custom-scrollbar-plugin": "~3.0.4", - "@abp/select2": "~3.0.4", - "@abp/sweetalert": "~3.0.4", - "@abp/timeago": "~3.0.4", - "@abp/toastr": "~3.0.4" + "@abp/aspnetcore.mvc.ui": "~3.0.5", + "@abp/bootstrap": "~3.0.5", + "@abp/bootstrap-datepicker": "~3.0.5", + "@abp/datatables.net-bs4": "~3.0.5", + "@abp/font-awesome": "~3.0.5", + "@abp/jquery-form": "~3.0.5", + "@abp/jquery-validation-unobtrusive": "~3.0.5", + "@abp/lodash": "~3.0.5", + "@abp/luxon": "~3.0.5", + "@abp/malihu-custom-scrollbar-plugin": "~3.0.5", + "@abp/select2": "~3.0.5", + "@abp/sweetalert": "~3.0.5", + "@abp/timeago": "~3.0.5", + "@abp/toastr": "~3.0.5" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431" } diff --git a/npm/packs/aspnetcore.mvc.ui/package.json b/npm/packs/aspnetcore.mvc.ui/package.json index 64d2ca90d2..696d17b60d 100644 --- a/npm/packs/aspnetcore.mvc.ui/package.json +++ b/npm/packs/aspnetcore.mvc.ui/package.json @@ -1,5 +1,5 @@ { - "version": "3.0.4", + "version": "3.0.5", "name": "@abp/aspnetcore.mvc.ui", "publishConfig": { "access": "public" diff --git a/npm/packs/blogging/package.json b/npm/packs/blogging/package.json index 940e6d1a99..3d5e348c65 100644 --- a/npm/packs/blogging/package.json +++ b/npm/packs/blogging/package.json @@ -1,14 +1,14 @@ { - "version": "3.0.4", + "version": "3.0.5", "name": "@abp/blogging", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.shared": "~3.0.4", - "@abp/owl.carousel": "~3.0.4", - "@abp/prismjs": "~3.0.4", - "@abp/tui-editor": "~3.0.4" + "@abp/aspnetcore.mvc.ui.theme.shared": "~3.0.5", + "@abp/owl.carousel": "~3.0.5", + "@abp/prismjs": "~3.0.5", + "@abp/tui-editor": "~3.0.5" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431" } diff --git a/npm/packs/bootstrap-datepicker/package.json b/npm/packs/bootstrap-datepicker/package.json index 328a651d4d..8ed3926062 100644 --- a/npm/packs/bootstrap-datepicker/package.json +++ b/npm/packs/bootstrap-datepicker/package.json @@ -1,5 +1,5 @@ { - "version": "3.0.4", + "version": "3.0.5", "name": "@abp/bootstrap-datepicker", "publishConfig": { "access": "public" diff --git a/npm/packs/bootstrap/package.json b/npm/packs/bootstrap/package.json index e947de7d73..27f6429250 100644 --- a/npm/packs/bootstrap/package.json +++ b/npm/packs/bootstrap/package.json @@ -1,11 +1,11 @@ { - "version": "3.0.4", + "version": "3.0.5", "name": "@abp/bootstrap", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/core": "~3.0.4", + "@abp/core": "~3.0.5", "bootstrap": "^4.5.0", "bootstrap-v4-rtl": "4.4.1-2" }, diff --git a/npm/packs/chart.js/package.json b/npm/packs/chart.js/package.json index 63a83934e5..44706cfb48 100644 --- a/npm/packs/chart.js/package.json +++ b/npm/packs/chart.js/package.json @@ -1,5 +1,5 @@ { - "version": "3.0.4", + "version": "3.0.5", "name": "@abp/chart.js", "publishConfig": { "access": "public" diff --git a/npm/packs/clipboard/package.json b/npm/packs/clipboard/package.json index ce8091a714..d626350d91 100644 --- a/npm/packs/clipboard/package.json +++ b/npm/packs/clipboard/package.json @@ -1,11 +1,11 @@ { - "version": "3.0.4", + "version": "3.0.5", "name": "@abp/clipboard", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/core": "~3.0.4", + "@abp/core": "~3.0.5", "clipboard": "^2.0.6" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431" diff --git a/npm/packs/codemirror/package.json b/npm/packs/codemirror/package.json index 7fc667918a..b0c40c327f 100644 --- a/npm/packs/codemirror/package.json +++ b/npm/packs/codemirror/package.json @@ -1,11 +1,11 @@ { - "version": "3.0.4", + "version": "3.0.5", "name": "@abp/codemirror", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/core": "~3.0.4", + "@abp/core": "~3.0.5", "codemirror": "^5.54.0" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431" diff --git a/npm/packs/core/package.json b/npm/packs/core/package.json index 8da4452e26..a07cff3de4 100644 --- a/npm/packs/core/package.json +++ b/npm/packs/core/package.json @@ -1,11 +1,11 @@ { - "version": "3.0.4", + "version": "3.0.5", "name": "@abp/core", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/utils": "^3.0.4" + "@abp/utils": "^3.0.5" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431" } diff --git a/npm/packs/datatables.net-bs4/package.json b/npm/packs/datatables.net-bs4/package.json index 0152370cc0..6ed96fdcfe 100644 --- a/npm/packs/datatables.net-bs4/package.json +++ b/npm/packs/datatables.net-bs4/package.json @@ -1,11 +1,11 @@ { - "version": "3.0.4", + "version": "3.0.5", "name": "@abp/datatables.net-bs4", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/datatables.net": "~3.0.4", + "@abp/datatables.net": "~3.0.5", "datatables.net-bs4": "^1.10.21" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431" diff --git a/npm/packs/datatables.net/package.json b/npm/packs/datatables.net/package.json index 63dc5c09d0..4951603420 100644 --- a/npm/packs/datatables.net/package.json +++ b/npm/packs/datatables.net/package.json @@ -1,11 +1,11 @@ { - "version": "3.0.4", + "version": "3.0.5", "name": "@abp/datatables.net", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/jquery": "~3.0.4", + "@abp/jquery": "~3.0.5", "datatables.net": "^1.10.21" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431" diff --git a/npm/packs/docs/package.json b/npm/packs/docs/package.json index a1a61e5374..7e69852140 100644 --- a/npm/packs/docs/package.json +++ b/npm/packs/docs/package.json @@ -1,15 +1,15 @@ { - "version": "3.0.4", + "version": "3.0.5", "name": "@abp/docs", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/anchor-js": "~3.0.4", - "@abp/clipboard": "~3.0.4", - "@abp/malihu-custom-scrollbar-plugin": "~3.0.4", - "@abp/popper.js": "~3.0.4", - "@abp/prismjs": "~3.0.4" + "@abp/anchor-js": "~3.0.5", + "@abp/clipboard": "~3.0.5", + "@abp/malihu-custom-scrollbar-plugin": "~3.0.5", + "@abp/popper.js": "~3.0.5", + "@abp/prismjs": "~3.0.5" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431" } diff --git a/npm/packs/flag-icon-css/package.json b/npm/packs/flag-icon-css/package.json index 83075847dd..b202c8f700 100644 --- a/npm/packs/flag-icon-css/package.json +++ b/npm/packs/flag-icon-css/package.json @@ -1,5 +1,5 @@ { - "version": "3.0.4", + "version": "3.0.5", "name": "@abp/flag-icon-css", "publishConfig": { "access": "public" diff --git a/npm/packs/font-awesome/package.json b/npm/packs/font-awesome/package.json index 15671a9b4d..e957d1c62d 100644 --- a/npm/packs/font-awesome/package.json +++ b/npm/packs/font-awesome/package.json @@ -1,11 +1,11 @@ { - "version": "3.0.4", + "version": "3.0.5", "name": "@abp/font-awesome", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/core": "~3.0.4", + "@abp/core": "~3.0.5", "@fortawesome/fontawesome-free": "^5.13.0" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431" diff --git a/npm/packs/highlight.js/package.json b/npm/packs/highlight.js/package.json index b21fb57afa..2960c75ead 100644 --- a/npm/packs/highlight.js/package.json +++ b/npm/packs/highlight.js/package.json @@ -1,11 +1,11 @@ { - "version": "3.0.4", + "version": "3.0.5", "name": "@abp/highlight.js", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/core": "~3.0.4" + "@abp/core": "~3.0.5" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431" } diff --git a/npm/packs/jquery-form/package.json b/npm/packs/jquery-form/package.json index 3cfdcc3602..2cd395c4b1 100644 --- a/npm/packs/jquery-form/package.json +++ b/npm/packs/jquery-form/package.json @@ -1,11 +1,11 @@ { - "version": "3.0.4", + "version": "3.0.5", "name": "@abp/jquery-form", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/jquery": "~3.0.4", + "@abp/jquery": "~3.0.5", "jquery-form": "^4.3.0" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431" diff --git a/npm/packs/jquery-validation-unobtrusive/package.json b/npm/packs/jquery-validation-unobtrusive/package.json index 2d57c56413..5d507c8e20 100644 --- a/npm/packs/jquery-validation-unobtrusive/package.json +++ b/npm/packs/jquery-validation-unobtrusive/package.json @@ -1,11 +1,11 @@ { - "version": "3.0.4", + "version": "3.0.5", "name": "@abp/jquery-validation-unobtrusive", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/jquery-validation": "~3.0.4", + "@abp/jquery-validation": "~3.0.5", "jquery-validation-unobtrusive": "^3.2.11" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431" diff --git a/npm/packs/jquery-validation/package.json b/npm/packs/jquery-validation/package.json index a86fa9372a..9f007c3315 100644 --- a/npm/packs/jquery-validation/package.json +++ b/npm/packs/jquery-validation/package.json @@ -1,11 +1,11 @@ { - "version": "3.0.4", + "version": "3.0.5", "name": "@abp/jquery-validation", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/jquery": "~3.0.4", + "@abp/jquery": "~3.0.5", "jquery-validation": "^1.19.2" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431" diff --git a/npm/packs/jquery/package.json b/npm/packs/jquery/package.json index c58627aaf3..4079a3bc6c 100644 --- a/npm/packs/jquery/package.json +++ b/npm/packs/jquery/package.json @@ -1,11 +1,11 @@ { - "version": "3.0.4", + "version": "3.0.5", "name": "@abp/jquery", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/core": "~3.0.4", + "@abp/core": "~3.0.5", "jquery": "~3.5.1" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431" diff --git a/npm/packs/jstree/package.json b/npm/packs/jstree/package.json index fd8bddea83..32a3f486fc 100644 --- a/npm/packs/jstree/package.json +++ b/npm/packs/jstree/package.json @@ -1,11 +1,11 @@ { - "version": "3.0.4", + "version": "3.0.5", "name": "@abp/jstree", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/jquery": "~3.0.4", + "@abp/jquery": "~3.0.5", "jstree": "^3.3.9" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431" diff --git a/npm/packs/lodash/package.json b/npm/packs/lodash/package.json index 7014643df6..ad7e20f48e 100644 --- a/npm/packs/lodash/package.json +++ b/npm/packs/lodash/package.json @@ -1,11 +1,11 @@ { - "version": "3.0.4", + "version": "3.0.5", "name": "@abp/lodash", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/core": "~3.0.4", + "@abp/core": "~3.0.5", "lodash": "^4.17.15" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431" diff --git a/npm/packs/luxon/package.json b/npm/packs/luxon/package.json index 2ad8cafee7..57a6053319 100644 --- a/npm/packs/luxon/package.json +++ b/npm/packs/luxon/package.json @@ -1,5 +1,5 @@ { - "version": "3.0.4", + "version": "3.0.5", "name": "@abp/luxon", "publishConfig": { "access": "public" diff --git a/npm/packs/malihu-custom-scrollbar-plugin/package.json b/npm/packs/malihu-custom-scrollbar-plugin/package.json index 0a299b6b6b..b55ecc6e70 100644 --- a/npm/packs/malihu-custom-scrollbar-plugin/package.json +++ b/npm/packs/malihu-custom-scrollbar-plugin/package.json @@ -1,11 +1,11 @@ { - "version": "3.0.4", + "version": "3.0.5", "name": "@abp/malihu-custom-scrollbar-plugin", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/core": "~3.0.4", + "@abp/core": "~3.0.5", "malihu-custom-scrollbar-plugin": "^3.1.5" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431" diff --git a/npm/packs/markdown-it/package.json b/npm/packs/markdown-it/package.json index 46937fd395..a7f0b6ffe4 100644 --- a/npm/packs/markdown-it/package.json +++ b/npm/packs/markdown-it/package.json @@ -1,11 +1,11 @@ { - "version": "3.0.4", + "version": "3.0.5", "name": "@abp/markdown-it", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/core": "~3.0.4", + "@abp/core": "~3.0.5", "markdown-it": "^11.0.0" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431" diff --git a/npm/packs/owl.carousel/package.json b/npm/packs/owl.carousel/package.json index 395dbb4993..43e6158c89 100644 --- a/npm/packs/owl.carousel/package.json +++ b/npm/packs/owl.carousel/package.json @@ -1,11 +1,11 @@ { - "version": "3.0.4", + "version": "3.0.5", "name": "@abp/owl.carousel", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/core": "~3.0.4", + "@abp/core": "~3.0.5", "owl.carousel": "^2.3.4" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431" diff --git a/npm/packs/popper.js/package.json b/npm/packs/popper.js/package.json index 88e846b04b..a72938dde0 100644 --- a/npm/packs/popper.js/package.json +++ b/npm/packs/popper.js/package.json @@ -1,11 +1,11 @@ { - "version": "3.0.4", + "version": "3.0.5", "name": "@abp/popper.js", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/core": "~3.0.4", + "@abp/core": "~3.0.5", "popper.js": "^1.16.0" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431" diff --git a/npm/packs/prismjs/package.json b/npm/packs/prismjs/package.json index e9f07e79f0..50abdd0dd8 100644 --- a/npm/packs/prismjs/package.json +++ b/npm/packs/prismjs/package.json @@ -1,12 +1,12 @@ { - "version": "3.0.4", + "version": "3.0.5", "name": "@abp/prismjs", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/clipboard": "~3.0.4", - "@abp/core": "~3.0.4", + "@abp/clipboard": "~3.0.5", + "@abp/core": "~3.0.5", "prismjs": "^1.20.0" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431" diff --git a/npm/packs/select2/package.json b/npm/packs/select2/package.json index 26a0d670dc..c5962f82ba 100644 --- a/npm/packs/select2/package.json +++ b/npm/packs/select2/package.json @@ -1,11 +1,11 @@ { - "version": "3.0.4", + "version": "3.0.5", "name": "@abp/select2", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/core": "~3.0.4", + "@abp/core": "~3.0.5", "select2": "^4.0.13" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431" diff --git a/npm/packs/signalr/package.json b/npm/packs/signalr/package.json index 1a1b38f340..de52d4ffb7 100644 --- a/npm/packs/signalr/package.json +++ b/npm/packs/signalr/package.json @@ -1,11 +1,11 @@ { - "version": "3.0.4", + "version": "3.0.5", "name": "@abp/signalr", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/core": "~3.0.4", + "@abp/core": "~3.0.5", "@microsoft/signalr": "~3.1.5" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431" diff --git a/npm/packs/sweetalert/package.json b/npm/packs/sweetalert/package.json index 217a51da0a..f9c2242b4f 100644 --- a/npm/packs/sweetalert/package.json +++ b/npm/packs/sweetalert/package.json @@ -1,11 +1,11 @@ { - "version": "3.0.4", + "version": "3.0.5", "name": "@abp/sweetalert", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/core": "~3.0.4", + "@abp/core": "~3.0.5", "sweetalert": "^2.1.2" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431" diff --git a/npm/packs/timeago/package.json b/npm/packs/timeago/package.json index 062ca29477..24b105e01d 100644 --- a/npm/packs/timeago/package.json +++ b/npm/packs/timeago/package.json @@ -1,11 +1,11 @@ { - "version": "3.0.4", + "version": "3.0.5", "name": "@abp/timeago", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/jquery": "~3.0.4", + "@abp/jquery": "~3.0.5", "timeago": "^1.6.7" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431" diff --git a/npm/packs/toastr/package.json b/npm/packs/toastr/package.json index 68b1ccce2e..d723d028ab 100644 --- a/npm/packs/toastr/package.json +++ b/npm/packs/toastr/package.json @@ -1,11 +1,11 @@ { - "version": "3.0.4", + "version": "3.0.5", "name": "@abp/toastr", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/jquery": "~3.0.4", + "@abp/jquery": "~3.0.5", "toastr": "^2.1.4" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431" diff --git a/npm/packs/tui-editor/package.json b/npm/packs/tui-editor/package.json index 21e73780cd..c11f4e147d 100644 --- a/npm/packs/tui-editor/package.json +++ b/npm/packs/tui-editor/package.json @@ -1,14 +1,14 @@ { - "version": "3.0.4", + "version": "3.0.5", "name": "@abp/tui-editor", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/codemirror": "~3.0.4", - "@abp/highlight.js": "~3.0.4", - "@abp/jquery": "~3.0.4", - "@abp/markdown-it": "~3.0.4", + "@abp/codemirror": "~3.0.5", + "@abp/highlight.js": "~3.0.5", + "@abp/jquery": "~3.0.5", + "@abp/markdown-it": "~3.0.5", "tui-editor": "^1.4.10" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431" diff --git a/npm/packs/uppy/package.json b/npm/packs/uppy/package.json index bf20e7ff4f..1d305a4b97 100644 --- a/npm/packs/uppy/package.json +++ b/npm/packs/uppy/package.json @@ -1,11 +1,11 @@ { - "version": "3.0.4", + "version": "3.0.5", "name": "@abp/uppy", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/core": "~3.0.4", + "@abp/core": "~3.0.5", "uppy": "^1.16.1" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431" diff --git a/npm/packs/utils/package.json b/npm/packs/utils/package.json index a26fd4b3a1..56bc5fc2cb 100644 --- a/npm/packs/utils/package.json +++ b/npm/packs/utils/package.json @@ -1,6 +1,6 @@ { "name": "@abp/utils", - "version": "3.0.4", + "version": "3.0.5", "scripts": { "prepublish": "yarn install --ignore-scripts && node prepublish.js", "ng": "ng", diff --git a/npm/packs/virtual-file-explorer/package.json b/npm/packs/virtual-file-explorer/package.json index ca84fe0fb2..0399257896 100644 --- a/npm/packs/virtual-file-explorer/package.json +++ b/npm/packs/virtual-file-explorer/package.json @@ -1,12 +1,12 @@ { - "version": "3.0.4", + "version": "3.0.5", "name": "@abp/virtual-file-explorer", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/clipboard": "~3.0.4", - "@abp/prismjs": "~3.0.4" + "@abp/clipboard": "~3.0.5", + "@abp/prismjs": "~3.0.5" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431" } diff --git a/npm/preview-publish.ps1 b/npm/preview-publish.ps1 index 9749e64b7e..524c8afcaa 100644 --- a/npm/preview-publish.ps1 +++ b/npm/preview-publish.ps1 @@ -16,7 +16,7 @@ $Version = $NextVersion; } if(-Not $Registry) { -$Registry = "https://www.myget.org/F/abp-nightly/npm"; +$Registry = "https://www.myget.org/F/abp-nightly/auth/8f2a5234-1bce-4dc7-b976-2983078590a9/npm/"; } $commands = ( diff --git a/nupkg/common.ps1 b/nupkg/common.ps1 index 6d79ad6b37..99244d5d6c 100644 --- a/nupkg/common.ps1 +++ b/nupkg/common.ps1 @@ -61,6 +61,7 @@ $projects = ( "framework/src/Volo.Abp.BlobStoring.Aliyun", "framework/src/Volo.Abp.BlobStoring.Azure", "framework/src/Volo.Abp.BlobStoring.Minio", + "framework/src/Volo.Abp.BlobStoring.Aws", "framework/src/Volo.Abp.Caching", "framework/src/Volo.Abp.Caching.StackExchangeRedis", "framework/src/Volo.Abp.Castle.Core", diff --git a/templates/app/angular/package.json b/templates/app/angular/package.json index 406faca9bd..c40a748fb5 100644 --- a/templates/app/angular/package.json +++ b/templates/app/angular/package.json @@ -12,11 +12,11 @@ }, "private": true, "dependencies": { - "@abp/ng.account": "~3.0.4", - "@abp/ng.identity": "~3.0.4", - "@abp/ng.setting-management": "~3.0.4", - "@abp/ng.tenant-management": "~3.0.4", - "@abp/ng.theme.basic": "~3.0.4", + "@abp/ng.account": "~3.0.5", + "@abp/ng.identity": "~3.0.5", + "@abp/ng.setting-management": "~3.0.5", + "@abp/ng.tenant-management": "~3.0.5", + "@abp/ng.theme.basic": "~3.0.5", "@angular/animations": "~10.0.1", "@angular/common": "~10.0.1", "@angular/compiler": "~10.0.1", diff --git a/templates/app/angular/src/environments/environment.prod.ts b/templates/app/angular/src/environments/environment.prod.ts index 53f0ecdf89..cfe0401dee 100644 --- a/templates/app/angular/src/environments/environment.prod.ts +++ b/templates/app/angular/src/environments/environment.prod.ts @@ -1,6 +1,9 @@ +import { Config } from '@abp/ng.core'; + export const environment = { production: true, application: { + baseUrl: 'http://localhost:4200/', name: 'MyProjectName', logoUrl: '', }, @@ -9,7 +12,6 @@ export const environment = { clientId: 'MyProjectName_App', dummyClientSecret: '1q2w3e*', scope: 'MyProjectName', - showDebugInformation: true, oidc: false, requireHttps: true, }, @@ -21,4 +23,4 @@ export const environment = { localization: { defaultResourceName: 'MyProjectName', }, -}; +} as Config.Environment; diff --git a/templates/app/angular/src/environments/environment.ts b/templates/app/angular/src/environments/environment.ts index d5d0dff39e..2039e8a2dc 100644 --- a/templates/app/angular/src/environments/environment.ts +++ b/templates/app/angular/src/environments/environment.ts @@ -1,6 +1,9 @@ +import { Config } from '@abp/ng.core'; + export const environment = { production: false, application: { + baseUrl: 'http://localhost:4200/', name: 'MyProjectName', logoUrl: '', }, @@ -21,4 +24,4 @@ export const environment = { localization: { defaultResourceName: 'MyProjectName', }, -}; +} as Config.Environment; diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Application.Contracts/MyCompanyName.MyProjectName.Application.Contracts.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Application.Contracts/MyCompanyName.MyProjectName.Application.Contracts.csproj index f9c3f9a045..fe2e40eb14 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Application.Contracts/MyCompanyName.MyProjectName.Application.Contracts.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Application.Contracts/MyCompanyName.MyProjectName.Application.Contracts.csproj @@ -13,7 +13,6 @@ - diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Application.Contracts/MyProjectNameApplicationContractsModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Application.Contracts/MyProjectNameApplicationContractsModule.cs index d8e848e86b..5c3ba41364 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Application.Contracts/MyProjectNameApplicationContractsModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Application.Contracts/MyProjectNameApplicationContractsModule.cs @@ -1,5 +1,4 @@ using Volo.Abp.Account; -using Volo.Abp.AutoMapper; using Volo.Abp.FeatureManagement; using Volo.Abp.Identity; using Volo.Abp.Modularity; @@ -16,8 +15,7 @@ namespace MyCompanyName.MyProjectName typeof(AbpIdentityApplicationContractsModule), typeof(AbpPermissionManagementApplicationContractsModule), typeof(AbpTenantManagementApplicationContractsModule), - typeof(AbpObjectExtendingModule), - typeof(AbpAutoMapperModule) + typeof(AbpObjectExtendingModule) )] public class MyProjectNameApplicationContractsModule : AbpModule { @@ -25,13 +23,5 @@ namespace MyCompanyName.MyProjectName { MyProjectNameDtoExtensions.Configure(); } - - public override void ConfigureServices(ServiceConfigurationContext context) - { - Configure(options => - { - options.AddMaps(); - }); - } } } diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.DbMigrator/MyCompanyName.MyProjectName.DbMigrator.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.DbMigrator/MyCompanyName.MyProjectName.DbMigrator.csproj index f94d49a220..3d8faa57d9 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.DbMigrator/MyCompanyName.MyProjectName.DbMigrator.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.DbMigrator/MyCompanyName.MyProjectName.DbMigrator.csproj @@ -22,7 +22,7 @@ - + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/MyCompanyName.MyProjectName.Domain.Shared.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/MyCompanyName.MyProjectName.Domain.Shared.csproj index df627353c0..0b7f6483dd 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/MyCompanyName.MyProjectName.Domain.Shared.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/MyCompanyName.MyProjectName.Domain.Shared.csproj @@ -7,7 +7,7 @@ MyCompanyName.MyProjectName true - + @@ -25,7 +25,7 @@ - + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/MyProjectNameDomainSharedModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/MyProjectNameDomainSharedModule.cs index 7fb1e30637..885d98a7c2 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/MyProjectNameDomainSharedModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/MyProjectNameDomainSharedModule.cs @@ -5,6 +5,7 @@ using Volo.Abp.FeatureManagement; using Volo.Abp.Identity; using Volo.Abp.IdentityServer; using Volo.Abp.Localization; +using Volo.Abp.Localization.ExceptionHandling; using Volo.Abp.Modularity; using Volo.Abp.PermissionManagement; using Volo.Abp.SettingManagement; @@ -44,9 +45,14 @@ namespace MyCompanyName.MyProjectName .Add("en") .AddBaseTypes(typeof(AbpValidationResource)) .AddVirtualJson("/Localization/MyProjectName"); - + options.DefaultResourceType = typeof(MyProjectNameResource); }); + + Configure(options => + { + options.MapCodeNamespace("MyProjectName", typeof(MyProjectNameResource)); + }); } } } diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain/MyCompanyName.MyProjectName.Domain.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain/MyCompanyName.MyProjectName.Domain.csproj index 5f7dc8c7b0..415647a729 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain/MyCompanyName.MyProjectName.Domain.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain/MyCompanyName.MyProjectName.Domain.csproj @@ -12,6 +12,7 @@ + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain/MyProjectNameDomainModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain/MyProjectNameDomainModule.cs index 84e7f88791..014594801f 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain/MyProjectNameDomainModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain/MyProjectNameDomainModule.cs @@ -1,7 +1,10 @@ -using MyCompanyName.MyProjectName.MultiTenancy; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using MyCompanyName.MyProjectName.MultiTenancy; using MyCompanyName.MyProjectName.ObjectExtending; using Volo.Abp.AuditLogging; using Volo.Abp.BackgroundJobs; +using Volo.Abp.Emailing; using Volo.Abp.FeatureManagement; using Volo.Abp.Identity; using Volo.Abp.IdentityServer; @@ -24,8 +27,9 @@ namespace MyCompanyName.MyProjectName typeof(AbpIdentityServerDomainModule), typeof(AbpPermissionManagementDomainIdentityServerModule), typeof(AbpSettingManagementDomainModule), - typeof(AbpTenantManagementDomainModule) - )] + typeof(AbpTenantManagementDomainModule), + typeof(AbpEmailingModule) + )] public class MyProjectNameDomainModule : AbpModule { public override void PreConfigureServices(ServiceConfigurationContext context) @@ -39,6 +43,10 @@ namespace MyCompanyName.MyProjectName { options.IsEnabled = MultiTenancyConsts.IsEnabled; }); + +#if DEBUG + context.Services.Replace(ServiceDescriptor.Singleton()); +#endif } } } diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/Migrations/20200624023331_Initial.Designer.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations/Migrations/20200810022238_Initial.Designer.cs similarity index 91% rename from templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/Migrations/20200624023331_Initial.Designer.cs rename to templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations/Migrations/20200810022238_Initial.Designer.cs index 23c25b58cd..b4b16078a8 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/Migrations/20200624023331_Initial.Designer.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations/Migrations/20200810022238_Initial.Designer.cs @@ -10,8 +10,8 @@ using Volo.Abp.EntityFrameworkCore; namespace MyCompanyName.MyProjectName.Migrations { - [DbContext(typeof(IdentityServerHostMigrationsDbContext))] - [Migration("20200624023331_Initial")] + [DbContext(typeof(MyProjectNameMigrationsDbContext))] + [Migration("20200810022238_Initial")] partial class Initial { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -19,7 +19,7 @@ namespace MyCompanyName.MyProjectName.Migrations #pragma warning disable 612, 618 modelBuilder .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) - .HasAnnotation("ProductVersion", "3.1.5") + .HasAnnotation("ProductVersion", "3.1.6") .HasAnnotation("Relational:MaxIdentifierLength", 128) .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); @@ -275,6 +275,95 @@ namespace MyCompanyName.MyProjectName.Migrations b.ToTable("AbpEntityPropertyChanges"); }); + modelBuilder.Entity("Volo.Abp.BackgroundJobs.BackgroundJobRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsAbandoned") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("JobArgs") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasMaxLength(1048576); + + b.Property("JobName") + .IsRequired() + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("LastTryTime") + .HasColumnType("datetime2"); + + b.Property("NextTryTime") + .HasColumnType("datetime2"); + + b.Property("Priority") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint") + .HasDefaultValue((byte)15); + + b.Property("TryCount") + .ValueGeneratedOnAdd() + .HasColumnType("smallint") + .HasDefaultValue((short)0); + + b.HasKey("Id"); + + b.HasIndex("IsAbandoned", "NextTryTime"); + + b.ToTable("AbpBackgroundJobs"); + }); + + modelBuilder.Entity("Volo.Abp.FeatureManagement.FeatureValue", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ProviderName") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("Value") + .IsRequired() + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.HasKey("Id"); + + b.HasIndex("Name", "ProviderName", "ProviderKey"); + + b.ToTable("AbpFeatureValues"); + }); + modelBuilder.Entity("Volo.Abp.Identity.IdentityClaimType", b => { b.Property("Id") @@ -399,6 +488,81 @@ namespace MyCompanyName.MyProjectName.Migrations b.ToTable("AbpRoleClaims"); }); + modelBuilder.Entity("Volo.Abp.Identity.IdentitySecurityLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Action") + .HasColumnType("nvarchar(96)") + .HasMaxLength(96); + + b.Property("ApplicationName") + .HasColumnType("nvarchar(96)") + .HasMaxLength(96); + + b.Property("BrowserInfo") + .HasColumnType("nvarchar(512)") + .HasMaxLength(512); + + b.Property("ClientId") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ClientIpAddress") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CorrelationId") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("CreationTime") + .HasColumnType("datetime2"); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("Identity") + .HasColumnType("nvarchar(96)") + .HasMaxLength(96); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantName") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Action"); + + b.HasIndex("TenantId", "ApplicationName"); + + b.HasIndex("TenantId", "Identity"); + + b.HasIndex("TenantId", "UserId"); + + b.ToTable("AbpSecurityLogs"); + }); + modelBuilder.Entity("Volo.Abp.Identity.IdentityUser", b => { b.Property("Id") @@ -455,6 +619,12 @@ namespace MyCompanyName.MyProjectName.Migrations .HasColumnType("bit") .HasDefaultValue(false); + b.Property("IsExternal") + .ValueGeneratedOnAdd() + .HasColumnName("IsExternal") + .HasColumnType("bit") + .HasDefaultValue(false); + b.Property("LastModificationTime") .HasColumnName("LastModificationTime") .HasColumnType("datetime2"); diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/Migrations/20200624023331_Initial.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations/Migrations/20200810022238_Initial.cs similarity index 91% rename from templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/Migrations/20200624023331_Initial.cs rename to templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations/Migrations/20200810022238_Initial.cs index 9287186f86..57d7dadb66 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/Migrations/20200624023331_Initial.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations/Migrations/20200810022238_Initial.cs @@ -39,6 +39,27 @@ namespace MyCompanyName.MyProjectName.Migrations table.PrimaryKey("PK_AbpAuditLogs", x => x.Id); }); + migrationBuilder.CreateTable( + name: "AbpBackgroundJobs", + columns: table => new + { + Id = table.Column(nullable: false), + ExtraProperties = table.Column(nullable: true), + ConcurrencyStamp = table.Column(maxLength: 40, nullable: true), + JobName = table.Column(maxLength: 128, nullable: false), + JobArgs = table.Column(maxLength: 1048576, nullable: false), + TryCount = table.Column(nullable: false, defaultValue: (short)0), + CreationTime = table.Column(nullable: false), + NextTryTime = table.Column(nullable: false), + LastTryTime = table.Column(nullable: true), + IsAbandoned = table.Column(nullable: false, defaultValue: false), + Priority = table.Column(nullable: false, defaultValue: (byte)15) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpBackgroundJobs", x => x.Id); + }); + migrationBuilder.CreateTable( name: "AbpClaimTypes", columns: table => new @@ -59,6 +80,21 @@ namespace MyCompanyName.MyProjectName.Migrations table.PrimaryKey("PK_AbpClaimTypes", x => x.Id); }); + migrationBuilder.CreateTable( + name: "AbpFeatureValues", + columns: table => new + { + Id = table.Column(nullable: false), + Name = table.Column(maxLength: 128, nullable: false), + Value = table.Column(maxLength: 128, nullable: false), + ProviderName = table.Column(maxLength: 64, nullable: true), + ProviderKey = table.Column(maxLength: 64, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpFeatureValues", x => x.Id); + }); + migrationBuilder.CreateTable( name: "AbpOrganizationUnits", columns: table => new @@ -123,6 +159,31 @@ namespace MyCompanyName.MyProjectName.Migrations table.PrimaryKey("PK_AbpRoles", x => x.Id); }); + migrationBuilder.CreateTable( + name: "AbpSecurityLogs", + columns: table => new + { + Id = table.Column(nullable: false), + ExtraProperties = table.Column(nullable: true), + ConcurrencyStamp = table.Column(maxLength: 40, nullable: true), + TenantId = table.Column(nullable: true), + ApplicationName = table.Column(maxLength: 96, nullable: true), + Identity = table.Column(maxLength: 96, nullable: true), + Action = table.Column(maxLength: 96, nullable: true), + UserId = table.Column(nullable: true), + UserName = table.Column(maxLength: 256, nullable: true), + TenantName = table.Column(maxLength: 64, nullable: true), + ClientId = table.Column(maxLength: 64, nullable: true), + CorrelationId = table.Column(maxLength: 64, nullable: true), + ClientIpAddress = table.Column(maxLength: 64, nullable: true), + BrowserInfo = table.Column(maxLength: 512, nullable: true), + CreationTime = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpSecurityLogs", x => x.Id); + }); + migrationBuilder.CreateTable( name: "AbpSettings", columns: table => new @@ -183,6 +244,7 @@ namespace MyCompanyName.MyProjectName.Migrations EmailConfirmed = table.Column(nullable: false, defaultValue: false), PasswordHash = table.Column(maxLength: 256, nullable: true), SecurityStamp = table.Column(maxLength: 256, nullable: false), + IsExternal = table.Column(nullable: false, defaultValue: false), PhoneNumber = table.Column(maxLength: 16, nullable: true), PhoneNumberConfirmed = table.Column(nullable: false, defaultValue: false), TwoFactorEnabled = table.Column(nullable: false, defaultValue: false), @@ -887,6 +949,11 @@ namespace MyCompanyName.MyProjectName.Migrations table: "AbpAuditLogs", columns: new[] { "TenantId", "UserId", "ExecutionTime" }); + migrationBuilder.CreateIndex( + name: "IX_AbpBackgroundJobs_IsAbandoned_NextTryTime", + table: "AbpBackgroundJobs", + columns: new[] { "IsAbandoned", "NextTryTime" }); + migrationBuilder.CreateIndex( name: "IX_AbpEntityChanges_AuditLogId", table: "AbpEntityChanges", @@ -902,6 +969,11 @@ namespace MyCompanyName.MyProjectName.Migrations table: "AbpEntityPropertyChanges", column: "EntityChangeId"); + migrationBuilder.CreateIndex( + name: "IX_AbpFeatureValues_Name_ProviderName_ProviderKey", + table: "AbpFeatureValues", + columns: new[] { "Name", "ProviderName", "ProviderKey" }); + migrationBuilder.CreateIndex( name: "IX_AbpOrganizationUnitRoles_RoleId_OrganizationUnitId", table: "AbpOrganizationUnitRoles", @@ -932,6 +1004,26 @@ namespace MyCompanyName.MyProjectName.Migrations table: "AbpRoles", column: "NormalizedName"); + migrationBuilder.CreateIndex( + name: "IX_AbpSecurityLogs_TenantId_Action", + table: "AbpSecurityLogs", + columns: new[] { "TenantId", "Action" }); + + migrationBuilder.CreateIndex( + name: "IX_AbpSecurityLogs_TenantId_ApplicationName", + table: "AbpSecurityLogs", + columns: new[] { "TenantId", "ApplicationName" }); + + migrationBuilder.CreateIndex( + name: "IX_AbpSecurityLogs_TenantId_Identity", + table: "AbpSecurityLogs", + columns: new[] { "TenantId", "Identity" }); + + migrationBuilder.CreateIndex( + name: "IX_AbpSecurityLogs_TenantId_UserId", + table: "AbpSecurityLogs", + columns: new[] { "TenantId", "UserId" }); + migrationBuilder.CreateIndex( name: "IX_AbpSettings_Name_ProviderName_ProviderKey", table: "AbpSettings", @@ -1020,12 +1112,18 @@ namespace MyCompanyName.MyProjectName.Migrations migrationBuilder.DropTable( name: "AbpAuditLogActions"); + migrationBuilder.DropTable( + name: "AbpBackgroundJobs"); + migrationBuilder.DropTable( name: "AbpClaimTypes"); migrationBuilder.DropTable( name: "AbpEntityPropertyChanges"); + migrationBuilder.DropTable( + name: "AbpFeatureValues"); + migrationBuilder.DropTable( name: "AbpOrganizationUnitRoles"); @@ -1035,6 +1133,9 @@ namespace MyCompanyName.MyProjectName.Migrations migrationBuilder.DropTable( name: "AbpRoleClaims"); + migrationBuilder.DropTable( + name: "AbpSecurityLogs"); + migrationBuilder.DropTable( name: "AbpSettings"); diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations/Migrations/MyProjectNameMigrationsDbContextModelSnapshot.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations/Migrations/MyProjectNameMigrationsDbContextModelSnapshot.cs index 77d771b803..bd5937ba2c 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations/Migrations/MyProjectNameMigrationsDbContextModelSnapshot.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations/Migrations/MyProjectNameMigrationsDbContextModelSnapshot.cs @@ -17,7 +17,7 @@ namespace MyCompanyName.MyProjectName.Migrations #pragma warning disable 612, 618 modelBuilder .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) - .HasAnnotation("ProductVersion", "3.1.5") + .HasAnnotation("ProductVersion", "3.1.6") .HasAnnotation("Relational:MaxIdentifierLength", 128) .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); @@ -486,6 +486,81 @@ namespace MyCompanyName.MyProjectName.Migrations b.ToTable("AbpRoleClaims"); }); + modelBuilder.Entity("Volo.Abp.Identity.IdentitySecurityLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Action") + .HasColumnType("nvarchar(96)") + .HasMaxLength(96); + + b.Property("ApplicationName") + .HasColumnType("nvarchar(96)") + .HasMaxLength(96); + + b.Property("BrowserInfo") + .HasColumnType("nvarchar(512)") + .HasMaxLength(512); + + b.Property("ClientId") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ClientIpAddress") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CorrelationId") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("CreationTime") + .HasColumnType("datetime2"); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("Identity") + .HasColumnType("nvarchar(96)") + .HasMaxLength(96); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantName") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Action"); + + b.HasIndex("TenantId", "ApplicationName"); + + b.HasIndex("TenantId", "Identity"); + + b.HasIndex("TenantId", "UserId"); + + b.ToTable("AbpSecurityLogs"); + }); + modelBuilder.Entity("Volo.Abp.Identity.IdentityUser", b => { b.Property("Id") @@ -542,6 +617,12 @@ namespace MyCompanyName.MyProjectName.Migrations .HasColumnType("bit") .HasDefaultValue(false); + b.Property("IsExternal") + .ValueGeneratedOnAdd() + .HasColumnName("IsExternal") + .HasColumnType("bit") + .HasDefaultValue(false); + b.Property("LastModificationTime") .HasColumnName("LastModificationTime") .HasColumnType("datetime2"); diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations.csproj index 6df15f2ef8..299ff48fbd 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations.csproj @@ -12,7 +12,7 @@ - + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/MyCompanyName.MyProjectName.HttpApi.Host.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/MyCompanyName.MyProjectName.HttpApi.Host.csproj index a632006046..2776d2ed85 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/MyCompanyName.MyProjectName.HttpApi.Host.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/MyCompanyName.MyProjectName.HttpApi.Host.csproj @@ -15,8 +15,8 @@ - - + + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyCompanyName.MyProjectName.HttpApi.HostWithIds.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyCompanyName.MyProjectName.HttpApi.HostWithIds.csproj index 45db1e2280..c7faed9653 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyCompanyName.MyProjectName.HttpApi.HostWithIds.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyCompanyName.MyProjectName.HttpApi.HostWithIds.csproj @@ -15,7 +15,7 @@ - + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/package.json b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/package.json index 528a8910e5..5ffe5c0500 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/package.json +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/package.json @@ -3,6 +3,6 @@ "name": "my-app", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.basic": "^3.0.4" + "@abp/aspnetcore.mvc.ui.theme.basic": "^3.0.5" } } \ No newline at end of file diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/yarn.lock b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/yarn.lock index 845f2aaf18..7f4cc4007c 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/yarn.lock +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/yarn.lock @@ -2,37 +2,37 @@ # yarn lockfile v1 -"@abp/aspnetcore.mvc.ui.theme.basic@^3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-3.0.4.tgz#c58e3e06ba78279875cf2b5f96d34b83ce09f6df" - integrity sha512-tKKqVRKpwN7P4Mn+he3bcz4Qbt8bkZ++wk6iOujd6HLTkBNa0uKyM4L6+iCn1Hi62S6HeK2Xytx+b+IYeIlsuw== - dependencies: - "@abp/aspnetcore.mvc.ui.theme.shared" "~3.0.4" - -"@abp/aspnetcore.mvc.ui.theme.shared@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-3.0.4.tgz#50db612b6492d36c63dd9042000a9fb00fecec54" - integrity sha512-imd2T5XXkNvGQyXV/A9hA80qSeurKaUcW6HDkte8Y2fs4QfvkjgzWXi7neqF6F+HVbYYPg3rIBWC6QF1OXGn2w== - dependencies: - "@abp/aspnetcore.mvc.ui" "~3.0.4" - "@abp/bootstrap" "~3.0.4" - "@abp/bootstrap-datepicker" "~3.0.4" - "@abp/datatables.net-bs4" "~3.0.4" - "@abp/font-awesome" "~3.0.4" - "@abp/jquery-form" "~3.0.4" - "@abp/jquery-validation-unobtrusive" "~3.0.4" - "@abp/lodash" "~3.0.4" - "@abp/luxon" "~3.0.4" - "@abp/malihu-custom-scrollbar-plugin" "~3.0.4" - "@abp/select2" "~3.0.4" - "@abp/sweetalert" "~3.0.4" - "@abp/timeago" "~3.0.4" - "@abp/toastr" "~3.0.4" - -"@abp/aspnetcore.mvc.ui@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-3.0.4.tgz#4e4344d95a98a60c53037b351cb915d8ff7a7dcb" - integrity sha512-p7KI5CKkpD4EYzG4fCtygrtZhwMu7Z+IZFGbdqXelLbI5ACkbJyrn+N+9trPfS9papymeXY4p1E3v6xOH7rSHQ== +"@abp/aspnetcore.mvc.ui.theme.basic@^3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-3.0.5.tgz#c146f20226522f0305cc0fa31cfcf9d39be78901" + integrity sha512-ptNhD4xV2wSLW7vQ5KUgaRiH5Ov1OscZW2ZEoivnt3kqgtIC3DGw+SiE9vjFCvyY83fjRkIlW2PIfQxSySbteQ== + dependencies: + "@abp/aspnetcore.mvc.ui.theme.shared" "~3.0.5" + +"@abp/aspnetcore.mvc.ui.theme.shared@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-3.0.5.tgz#5b370902cf229963f9aa10dd64482d57299dde76" + integrity sha512-iobAH39xrCdnqdaABaiYZg4lqNSOwPkMCLnyc+ytxOMbu/YnNKahXPmRhgd8dR/l+iQY1+6T4SWfLdGswi3gAg== + dependencies: + "@abp/aspnetcore.mvc.ui" "~3.0.5" + "@abp/bootstrap" "~3.0.5" + "@abp/bootstrap-datepicker" "~3.0.5" + "@abp/datatables.net-bs4" "~3.0.5" + "@abp/font-awesome" "~3.0.5" + "@abp/jquery-form" "~3.0.5" + "@abp/jquery-validation-unobtrusive" "~3.0.5" + "@abp/lodash" "~3.0.5" + "@abp/luxon" "~3.0.5" + "@abp/malihu-custom-scrollbar-plugin" "~3.0.5" + "@abp/select2" "~3.0.5" + "@abp/sweetalert" "~3.0.5" + "@abp/timeago" "~3.0.5" + "@abp/toastr" "~3.0.5" + +"@abp/aspnetcore.mvc.ui@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-3.0.5.tgz#a050eaee328c56a3730f5abbf25035adf612d6f0" + integrity sha512-/EOgjXTzR+I1OK9KA7L3yzQ2RGSDW97+n3cQrSdLAUF+tnRvMJS0nytDlSJSRK2hb8/clFMaofZHCC81aUkYUg== dependencies: ansi-colors "^4.1.1" extend-object "^1.0.0" @@ -41,144 +41,144 @@ path "^0.12.7" rimraf "^3.0.2" -"@abp/bootstrap-datepicker@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-3.0.4.tgz#d21963f158a4d348a0acdc46209e67378aae8e55" - integrity sha512-25e9qm39dj9AijIchQIdLlBiBM5/fhASkEuLKNt6M8S/e3qwKLwbRhX90DKMFvOyKdxxcK1gX7tDuGHyG9A8mA== +"@abp/bootstrap-datepicker@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-3.0.5.tgz#3a7fd86dbcadc31c11be5b2b31c8cf8ac3035540" + integrity sha512-vIY1wT4RHoJAPhVK11Q5MS71iZbwsmM/0VTBvvs04mjRSSi6xhdyRYkA+l+P49fnOwhZNaaK19XwQdVVScDuhA== dependencies: bootstrap-datepicker "^1.9.0" -"@abp/bootstrap@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-3.0.4.tgz#3b0e2b74dc174a04ce669de1b140df833eee6031" - integrity sha512-DwxL1fGYOwChPwmMpwUxCKiIdzTfRf/YDkOLUiwI8eRrpABBeuE2sSHKfks6Rqh9Wqo/nlWXvfccgDRqweqhsA== +"@abp/bootstrap@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-3.0.5.tgz#91f2c8fe6a57fc5789ae8fe9c206b2c159e55880" + integrity sha512-LdydeJIbw5k1k7FkWaI1lTectZeTXmEIoccJtj9Qx4N9yGt40wGsjz9SOvUdJ0IKAgntIWNQP8ohWiBPgzT7Xg== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" bootstrap "^4.5.0" bootstrap-v4-rtl "4.4.1-2" -"@abp/core@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/core/-/core-3.0.4.tgz#281940525e280e9e3a1aa4cb4e8f64548ea026a3" - integrity sha512-ZPEH1fa5bf/f1ek9ZLQHx7u78iF6kRZ8Z2NBI36cNL13RyQbx485qJF+KXyqNlJoJAh0CAs0NsAJADU3utcDNA== +"@abp/core@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/core/-/core-3.0.5.tgz#9a7bd990d02edc8128a5be3233b6fbb5669b15b8" + integrity sha512-LdkSkZQfN4hugcGNxBkje0+eQomivwNFbnAkMFQbvCwYLwKB2yvk7GCemEetW1piqvyrUGz4jqhtunjoXps5JA== dependencies: - "@abp/utils" "^3.0.4" + "@abp/utils" "^3.0.5" -"@abp/datatables.net-bs4@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs4/-/datatables.net-bs4-3.0.4.tgz#e488336bd2c199146ea60464e0ba75aeb134957e" - integrity sha512-ID1XWKPxG9pv6mxBkku7JWvgXjTxJ1uShxmYv5kFYVuwOGMSvj8QVZInTHFcMdECopV56yjGPNOIFLeQj4Mw8Q== +"@abp/datatables.net-bs4@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs4/-/datatables.net-bs4-3.0.5.tgz#9b79935970d2d4074994240b5deb01dff5987c0a" + integrity sha512-jIWRBEG/NlhaE43i6Xzr18XJ5aTWA+WWslf7WgNOArG5slGBbH4lLLja4HswLP3NGdSmzIyHJR1p9XQ82nBkbQ== dependencies: - "@abp/datatables.net" "~3.0.4" + "@abp/datatables.net" "~3.0.5" datatables.net-bs4 "^1.10.21" -"@abp/datatables.net@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-3.0.4.tgz#7ef143b83143ad8d769be25296411a812a17b255" - integrity sha512-gdQwRuxO5fQWhG/DiUaf40DQ8dWIVGhOCyt6SZ8LQzq4uSaQqa0qrtTTTW2/aiee4MgJu2vJWYF5YCszIR/HhA== +"@abp/datatables.net@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-3.0.5.tgz#0a0a39e8fa54522bf8a09ce76ff3927207a79491" + integrity sha512-HClQ2tcZuWBAChf9oOJOtMP1zHv8jqloPEcw3iqbhS/tWZBw5VQ4TO+BVfZsqHDXZNnWSmR6/2xiKsK32nliew== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" datatables.net "^1.10.21" -"@abp/font-awesome@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-3.0.4.tgz#98f83d0bf8b5a2a4d988cb07f5cceda2e1bc1a09" - integrity sha512-AKhKCUXx8vdHIgyXUfbSLitfYor5dJbo4qMRRGqIJVSj+cyMkpgYbGjqr24GGijgZxdmOGL+s1b9iCUbIPgLNQ== +"@abp/font-awesome@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-3.0.5.tgz#62d58c34f0abf268e30bdf788dfbd3f45b19b412" + integrity sha512-/+31Vtw1S9vvuNy/rN/Q6awkgUaYNNYXESgfvqI0pqIkvlPgigvS8E68mHqGzsxyRqdZisI6JGd94AF+vJD7sQ== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" "@fortawesome/fontawesome-free" "^5.13.0" -"@abp/jquery-form@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-3.0.4.tgz#022eebbfe58b55e328984c0f119884c9464bc767" - integrity sha512-xDkk4rY0AmXBJE4qztzr2hZz+OVucVNWeagOEgeQHXJSKU/T+0kj0a1RxEHpyI24xBvEXAbe8PbByTbu0QXYew== +"@abp/jquery-form@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-3.0.5.tgz#9d2ea9b2509448ee163a163298a7a09480865f78" + integrity sha512-RKgvQciaMml33NvzA28aYyfNI+WQHRfKLFWvOvqc8qO4C3BvyrnwepuzZcnI/9UsELKLSLoFrfk3rJHhxnTJhg== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" jquery-form "^4.3.0" -"@abp/jquery-validation-unobtrusive@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-3.0.4.tgz#6cc942aadcdde33893e7a45c84aa022e033e4e8b" - integrity sha512-N9sza91HCyPvJo+d0RAfq4skae3TgAYg7KXhlwzRE2hqUQS+EICVARURWyxJW7s7Zu6+gxDTkXhBZRTW56deZA== +"@abp/jquery-validation-unobtrusive@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-3.0.5.tgz#b8912e1c4cfdd4fdff6ad4aa7ee8693b55a3be1f" + integrity sha512-kPifDA+1ErbeQBwn+BzwtWuTHXzBke/EMCmITuj59plM/60gZ1ZfVm8EzzTmKS7iLaVVeOqTKf0vEQTobNYffw== dependencies: - "@abp/jquery-validation" "~3.0.4" + "@abp/jquery-validation" "~3.0.5" jquery-validation-unobtrusive "^3.2.11" -"@abp/jquery-validation@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-3.0.4.tgz#3f96d1f83c75a7fc47e5e5e12c51dbc8da7582b1" - integrity sha512-2ljqas4YKU0PcwB0wQW2qhXejDHjW+N1XCxZDyCZ2wF9UxGlKBR8rFt76TJByXSotKE7n2hJRZaOh9ICZUow2g== +"@abp/jquery-validation@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-3.0.5.tgz#55e36b3c832a35447fac69af312dedf1dee22279" + integrity sha512-fdqxc5CJ6TpUAF8+NEsKCmXvvZiOPR5bzczcfvfovxLOjvHvdEO74XPGjsjlBNTdaKMhmNGZRK4JRLUz7fvTng== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" jquery-validation "^1.19.2" -"@abp/jquery@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-3.0.4.tgz#5f236d95cfae7f65012cd8778fd74490b7542442" - integrity sha512-g8N8Te5vf+8djdksfZoZ3tXaqlBC4Ra39pAWU/XerlLl64u+XmAX1s6yKRfC2kS8lf292GgiFYuNOkb77J6k4A== +"@abp/jquery@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-3.0.5.tgz#2e43ac19a8df6e1e0220d6d4991d33a93d890754" + integrity sha512-2d2l+smWKAVP4/b6GSO+8rmJ7pFXiUewpBDwkHm5qgkK6n6UGqIcK9lIgiS2YQ6rmL6qdtuNhhqtC9GF2TF2kQ== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" jquery "~3.5.1" -"@abp/lodash@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-3.0.4.tgz#ee60bc99f98b59fd9fc5ff4bbcf914a13faf209d" - integrity sha512-jxvv74Sm/fUKDBMylK4Ne/8GKm7GuHAdcLFtkoJQptdO+RjZpKJ86ULT+X5vbKskZaTj2fEmOR6l4hmfnBULTA== +"@abp/lodash@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-3.0.5.tgz#d5d9f14095d76239bc0e6f9804c24d1f92ffcab4" + integrity sha512-+B2vxfPwxoIIb8Ac8eHi6OgAn0qbjmPZuun7/es0/ouYYTbEhvhTAD20X3PGZlHrIYeDyS2ansuXm8gJI+/BzA== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" lodash "^4.17.15" -"@abp/luxon@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-3.0.4.tgz#aa1bf6a93d6254433101e50a205b276759969414" - integrity sha512-gX9dQJt9C6M1wmCSUIBoNr8CAt0jkUZFQ7LKKO/BoADGM/XFvY5vDbhL7zcwbwitgCOzmucoCxs/6rIxSOVlrg== +"@abp/luxon@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-3.0.5.tgz#083042c939ea58bf719e35114a6c8efb07b68688" + integrity sha512-0UVHO2CO89t34O35OA3JzWGWLBpg+e1NQXSfVtK90nmg/QmzGkHVM9m7Su0jjLNBt3N5EA5OPOhriot3bCKItw== dependencies: luxon "^1.24.1" -"@abp/malihu-custom-scrollbar-plugin@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-3.0.4.tgz#c54824b98363f4190cc6a37fcd421c2bfdc1e201" - integrity sha512-6LdSGctOr4ACtdohdSlb19vrFNJ5C0hy1VdbBEafHOBspXckvRVx4uJl2KfHmAC+bMg9apoK/wcHjVNdBHThYQ== +"@abp/malihu-custom-scrollbar-plugin@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-3.0.5.tgz#f908289927a63fd76fb8f60b00a37747d9edbe39" + integrity sha512-3GoXA8VtGyZWKiqaPCipPXYLLM7h2Y5Oecq5c5YlMQyW2akv7uHiQQF2GZW/nVsFu3HBbGTOWpKRRmVk3PEO9Q== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" malihu-custom-scrollbar-plugin "^3.1.5" -"@abp/select2@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-3.0.4.tgz#49db0cfbc9b08f6ae4949b2cb8bdef92d7d308dd" - integrity sha512-zKEgkWBFdGU2KiAMUKhucHqBfzDy2KNSefyqxpOiUd1L3PEKu+9o23HZRWzDupYltop+SO1Jw33CNoVAkKm4+A== +"@abp/select2@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-3.0.5.tgz#79d7754713c64a6549da520dc6867ef3e3dd0350" + integrity sha512-VbeSHdAdDy8exrjplHMMhLVV4oG7Gq9zBb5M52f0sIkA4DkXFhksXHcHNoOCfhaKAXpenGA03Z9GJewZ3MTpMw== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" select2 "^4.0.13" -"@abp/sweetalert@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/sweetalert/-/sweetalert-3.0.4.tgz#f85d11c5aabc6ceb509af1b8fd8d75bc3c74bd27" - integrity sha512-hO0qXUWxeMy/lfy3sb+5HvOmIPst6UvYJJGy3ZQR6DKEdkOktBX9sEDb7m6ij6xAIHqWIx1xLveul0oGteOG0Q== +"@abp/sweetalert@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/sweetalert/-/sweetalert-3.0.5.tgz#e3b92d23cf3633983980b6255bf63c43d422ceab" + integrity sha512-OUt4ANgj14GESfRG7+UqdONS8daOu+8x2OZDWv98hriuX0uCVBjwB2J0jRI/n653C68Mi0yHixGONDv9OLy/lg== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" sweetalert "^2.1.2" -"@abp/timeago@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-3.0.4.tgz#82d90a04f703a7db9c0acedcd2f3cea684e7ae18" - integrity sha512-7SbbqScTKCix25xWy5oLT0slJqUQsW6YgJgI0N41C3AsByRBXZeh5Gcep033gprTPmpgNY8JOWcEcCXySnYvfA== +"@abp/timeago@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-3.0.5.tgz#b191a7c43f3dc77645fe27273f2f6619f7d4eb31" + integrity sha512-QhvAOU4C+Qxh/gFuCSGZtLU4KwVhdv/jtK8ovWyqbGvQMCCIimqa8BFKIz0H5XWIJhHwwWblkpjICWgjWh6uXw== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" timeago "^1.6.7" -"@abp/toastr@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/toastr/-/toastr-3.0.4.tgz#884ab2343ddd59981c291c632e3b0630532f7e9c" - integrity sha512-NG0pVD8bdIXGpjPW9cozCzmyuAdnSLE4vDYLUReP1pZFp9B8SMJqpgPk9eFg0Y06aaHTVMKYBKRoq9xXS4w9Xg== +"@abp/toastr@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/toastr/-/toastr-3.0.5.tgz#fc83b678f5be3a22040d0421ad9db5b2eac9d4fc" + integrity sha512-qgi2DEu6FYpN/DYcOZrmXsAr1HqcIKMhBWeXgjfoYUIl/K5+OuUgbQ69D/jdc0jz8HU9a4Cw7Q8hhe4CkgssEw== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" toastr "^2.1.4" -"@abp/utils@^3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-3.0.4.tgz#38068fa8f735e1889cf2f226128366825948814d" - integrity sha512-TnXRZIZcxv5+EctEPsZEi2cyRXbpl3A679wzQa6BACv+pM45cjGO1JR8P0VswKN4TVL0aQRMv26N6U/uie8sEQ== +"@abp/utils@^3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-3.0.5.tgz#66b3da953500e73ff1f23d1b41168cde8216ce49" + integrity sha512-X9V1xLIEazItaf93nFSA4p94WT6QK3yA4eIyvU7GyyEkkOQdaakNwPbRwy930sd+I+/oXnIqM1vDsJu9QDhgvg== dependencies: just-compare "^1.3.0" diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.IdentityServer/MyCompanyName.MyProjectName.IdentityServer.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.IdentityServer/MyCompanyName.MyProjectName.IdentityServer.csproj index dd569c7092..a21593b5d3 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.IdentityServer/MyCompanyName.MyProjectName.IdentityServer.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.IdentityServer/MyCompanyName.MyProjectName.IdentityServer.csproj @@ -35,8 +35,8 @@ - - + + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.IdentityServer/package.json b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.IdentityServer/package.json index f311232e23..55812dc3ab 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.IdentityServer/package.json +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.IdentityServer/package.json @@ -3,6 +3,6 @@ "name": "my-app-identityserver", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.basic": "^3.0.4" + "@abp/aspnetcore.mvc.ui.theme.basic": "^3.0.5" } } \ No newline at end of file diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.IdentityServer/yarn.lock b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.IdentityServer/yarn.lock index 63a96d93c8..a583172e87 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.IdentityServer/yarn.lock +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.IdentityServer/yarn.lock @@ -2,37 +2,37 @@ # yarn lockfile v1 -"@abp/aspnetcore.mvc.ui.theme.basic@^3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-3.0.4.tgz#c58e3e06ba78279875cf2b5f96d34b83ce09f6df" - integrity sha512-tKKqVRKpwN7P4Mn+he3bcz4Qbt8bkZ++wk6iOujd6HLTkBNa0uKyM4L6+iCn1Hi62S6HeK2Xytx+b+IYeIlsuw== - dependencies: - "@abp/aspnetcore.mvc.ui.theme.shared" "~3.0.4" - -"@abp/aspnetcore.mvc.ui.theme.shared@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-3.0.4.tgz#50db612b6492d36c63dd9042000a9fb00fecec54" - integrity sha512-imd2T5XXkNvGQyXV/A9hA80qSeurKaUcW6HDkte8Y2fs4QfvkjgzWXi7neqF6F+HVbYYPg3rIBWC6QF1OXGn2w== - dependencies: - "@abp/aspnetcore.mvc.ui" "~3.0.4" - "@abp/bootstrap" "~3.0.4" - "@abp/bootstrap-datepicker" "~3.0.4" - "@abp/datatables.net-bs4" "~3.0.4" - "@abp/font-awesome" "~3.0.4" - "@abp/jquery-form" "~3.0.4" - "@abp/jquery-validation-unobtrusive" "~3.0.4" - "@abp/lodash" "~3.0.4" - "@abp/luxon" "~3.0.4" - "@abp/malihu-custom-scrollbar-plugin" "~3.0.4" - "@abp/select2" "~3.0.4" - "@abp/sweetalert" "~3.0.4" - "@abp/timeago" "~3.0.4" - "@abp/toastr" "~3.0.4" - -"@abp/aspnetcore.mvc.ui@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-3.0.4.tgz#4e4344d95a98a60c53037b351cb915d8ff7a7dcb" - integrity sha512-p7KI5CKkpD4EYzG4fCtygrtZhwMu7Z+IZFGbdqXelLbI5ACkbJyrn+N+9trPfS9papymeXY4p1E3v6xOH7rSHQ== +"@abp/aspnetcore.mvc.ui.theme.basic@^3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-3.0.5.tgz#c146f20226522f0305cc0fa31cfcf9d39be78901" + integrity sha512-ptNhD4xV2wSLW7vQ5KUgaRiH5Ov1OscZW2ZEoivnt3kqgtIC3DGw+SiE9vjFCvyY83fjRkIlW2PIfQxSySbteQ== + dependencies: + "@abp/aspnetcore.mvc.ui.theme.shared" "~3.0.5" + +"@abp/aspnetcore.mvc.ui.theme.shared@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-3.0.5.tgz#5b370902cf229963f9aa10dd64482d57299dde76" + integrity sha512-iobAH39xrCdnqdaABaiYZg4lqNSOwPkMCLnyc+ytxOMbu/YnNKahXPmRhgd8dR/l+iQY1+6T4SWfLdGswi3gAg== + dependencies: + "@abp/aspnetcore.mvc.ui" "~3.0.5" + "@abp/bootstrap" "~3.0.5" + "@abp/bootstrap-datepicker" "~3.0.5" + "@abp/datatables.net-bs4" "~3.0.5" + "@abp/font-awesome" "~3.0.5" + "@abp/jquery-form" "~3.0.5" + "@abp/jquery-validation-unobtrusive" "~3.0.5" + "@abp/lodash" "~3.0.5" + "@abp/luxon" "~3.0.5" + "@abp/malihu-custom-scrollbar-plugin" "~3.0.5" + "@abp/select2" "~3.0.5" + "@abp/sweetalert" "~3.0.5" + "@abp/timeago" "~3.0.5" + "@abp/toastr" "~3.0.5" + +"@abp/aspnetcore.mvc.ui@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-3.0.5.tgz#a050eaee328c56a3730f5abbf25035adf612d6f0" + integrity sha512-/EOgjXTzR+I1OK9KA7L3yzQ2RGSDW97+n3cQrSdLAUF+tnRvMJS0nytDlSJSRK2hb8/clFMaofZHCC81aUkYUg== dependencies: ansi-colors "^4.1.1" extend-object "^1.0.0" @@ -41,144 +41,144 @@ path "^0.12.7" rimraf "^3.0.2" -"@abp/bootstrap-datepicker@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-3.0.4.tgz#d21963f158a4d348a0acdc46209e67378aae8e55" - integrity sha512-25e9qm39dj9AijIchQIdLlBiBM5/fhASkEuLKNt6M8S/e3qwKLwbRhX90DKMFvOyKdxxcK1gX7tDuGHyG9A8mA== +"@abp/bootstrap-datepicker@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-3.0.5.tgz#3a7fd86dbcadc31c11be5b2b31c8cf8ac3035540" + integrity sha512-vIY1wT4RHoJAPhVK11Q5MS71iZbwsmM/0VTBvvs04mjRSSi6xhdyRYkA+l+P49fnOwhZNaaK19XwQdVVScDuhA== dependencies: bootstrap-datepicker "^1.9.0" -"@abp/bootstrap@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-3.0.4.tgz#3b0e2b74dc174a04ce669de1b140df833eee6031" - integrity sha512-DwxL1fGYOwChPwmMpwUxCKiIdzTfRf/YDkOLUiwI8eRrpABBeuE2sSHKfks6Rqh9Wqo/nlWXvfccgDRqweqhsA== +"@abp/bootstrap@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-3.0.5.tgz#91f2c8fe6a57fc5789ae8fe9c206b2c159e55880" + integrity sha512-LdydeJIbw5k1k7FkWaI1lTectZeTXmEIoccJtj9Qx4N9yGt40wGsjz9SOvUdJ0IKAgntIWNQP8ohWiBPgzT7Xg== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" bootstrap "^4.5.0" bootstrap-v4-rtl "4.4.1-2" -"@abp/core@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/core/-/core-3.0.4.tgz#281940525e280e9e3a1aa4cb4e8f64548ea026a3" - integrity sha512-ZPEH1fa5bf/f1ek9ZLQHx7u78iF6kRZ8Z2NBI36cNL13RyQbx485qJF+KXyqNlJoJAh0CAs0NsAJADU3utcDNA== +"@abp/core@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/core/-/core-3.0.5.tgz#9a7bd990d02edc8128a5be3233b6fbb5669b15b8" + integrity sha512-LdkSkZQfN4hugcGNxBkje0+eQomivwNFbnAkMFQbvCwYLwKB2yvk7GCemEetW1piqvyrUGz4jqhtunjoXps5JA== dependencies: - "@abp/utils" "^3.0.4" + "@abp/utils" "^3.0.5" -"@abp/datatables.net-bs4@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs4/-/datatables.net-bs4-3.0.4.tgz#e488336bd2c199146ea60464e0ba75aeb134957e" - integrity sha512-ID1XWKPxG9pv6mxBkku7JWvgXjTxJ1uShxmYv5kFYVuwOGMSvj8QVZInTHFcMdECopV56yjGPNOIFLeQj4Mw8Q== +"@abp/datatables.net-bs4@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs4/-/datatables.net-bs4-3.0.5.tgz#9b79935970d2d4074994240b5deb01dff5987c0a" + integrity sha512-jIWRBEG/NlhaE43i6Xzr18XJ5aTWA+WWslf7WgNOArG5slGBbH4lLLja4HswLP3NGdSmzIyHJR1p9XQ82nBkbQ== dependencies: - "@abp/datatables.net" "~3.0.4" + "@abp/datatables.net" "~3.0.5" datatables.net-bs4 "^1.10.21" -"@abp/datatables.net@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-3.0.4.tgz#7ef143b83143ad8d769be25296411a812a17b255" - integrity sha512-gdQwRuxO5fQWhG/DiUaf40DQ8dWIVGhOCyt6SZ8LQzq4uSaQqa0qrtTTTW2/aiee4MgJu2vJWYF5YCszIR/HhA== +"@abp/datatables.net@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-3.0.5.tgz#0a0a39e8fa54522bf8a09ce76ff3927207a79491" + integrity sha512-HClQ2tcZuWBAChf9oOJOtMP1zHv8jqloPEcw3iqbhS/tWZBw5VQ4TO+BVfZsqHDXZNnWSmR6/2xiKsK32nliew== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" datatables.net "^1.10.21" -"@abp/font-awesome@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-3.0.4.tgz#98f83d0bf8b5a2a4d988cb07f5cceda2e1bc1a09" - integrity sha512-AKhKCUXx8vdHIgyXUfbSLitfYor5dJbo4qMRRGqIJVSj+cyMkpgYbGjqr24GGijgZxdmOGL+s1b9iCUbIPgLNQ== +"@abp/font-awesome@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-3.0.5.tgz#62d58c34f0abf268e30bdf788dfbd3f45b19b412" + integrity sha512-/+31Vtw1S9vvuNy/rN/Q6awkgUaYNNYXESgfvqI0pqIkvlPgigvS8E68mHqGzsxyRqdZisI6JGd94AF+vJD7sQ== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" "@fortawesome/fontawesome-free" "^5.13.0" -"@abp/jquery-form@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-3.0.4.tgz#022eebbfe58b55e328984c0f119884c9464bc767" - integrity sha512-xDkk4rY0AmXBJE4qztzr2hZz+OVucVNWeagOEgeQHXJSKU/T+0kj0a1RxEHpyI24xBvEXAbe8PbByTbu0QXYew== +"@abp/jquery-form@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-3.0.5.tgz#9d2ea9b2509448ee163a163298a7a09480865f78" + integrity sha512-RKgvQciaMml33NvzA28aYyfNI+WQHRfKLFWvOvqc8qO4C3BvyrnwepuzZcnI/9UsELKLSLoFrfk3rJHhxnTJhg== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" jquery-form "^4.3.0" -"@abp/jquery-validation-unobtrusive@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-3.0.4.tgz#6cc942aadcdde33893e7a45c84aa022e033e4e8b" - integrity sha512-N9sza91HCyPvJo+d0RAfq4skae3TgAYg7KXhlwzRE2hqUQS+EICVARURWyxJW7s7Zu6+gxDTkXhBZRTW56deZA== +"@abp/jquery-validation-unobtrusive@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-3.0.5.tgz#b8912e1c4cfdd4fdff6ad4aa7ee8693b55a3be1f" + integrity sha512-kPifDA+1ErbeQBwn+BzwtWuTHXzBke/EMCmITuj59plM/60gZ1ZfVm8EzzTmKS7iLaVVeOqTKf0vEQTobNYffw== dependencies: - "@abp/jquery-validation" "~3.0.4" + "@abp/jquery-validation" "~3.0.5" jquery-validation-unobtrusive "^3.2.11" -"@abp/jquery-validation@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-3.0.4.tgz#3f96d1f83c75a7fc47e5e5e12c51dbc8da7582b1" - integrity sha512-2ljqas4YKU0PcwB0wQW2qhXejDHjW+N1XCxZDyCZ2wF9UxGlKBR8rFt76TJByXSotKE7n2hJRZaOh9ICZUow2g== +"@abp/jquery-validation@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-3.0.5.tgz#55e36b3c832a35447fac69af312dedf1dee22279" + integrity sha512-fdqxc5CJ6TpUAF8+NEsKCmXvvZiOPR5bzczcfvfovxLOjvHvdEO74XPGjsjlBNTdaKMhmNGZRK4JRLUz7fvTng== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" jquery-validation "^1.19.2" -"@abp/jquery@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-3.0.4.tgz#5f236d95cfae7f65012cd8778fd74490b7542442" - integrity sha512-g8N8Te5vf+8djdksfZoZ3tXaqlBC4Ra39pAWU/XerlLl64u+XmAX1s6yKRfC2kS8lf292GgiFYuNOkb77J6k4A== +"@abp/jquery@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-3.0.5.tgz#2e43ac19a8df6e1e0220d6d4991d33a93d890754" + integrity sha512-2d2l+smWKAVP4/b6GSO+8rmJ7pFXiUewpBDwkHm5qgkK6n6UGqIcK9lIgiS2YQ6rmL6qdtuNhhqtC9GF2TF2kQ== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" jquery "~3.5.1" -"@abp/lodash@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-3.0.4.tgz#ee60bc99f98b59fd9fc5ff4bbcf914a13faf209d" - integrity sha512-jxvv74Sm/fUKDBMylK4Ne/8GKm7GuHAdcLFtkoJQptdO+RjZpKJ86ULT+X5vbKskZaTj2fEmOR6l4hmfnBULTA== +"@abp/lodash@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-3.0.5.tgz#d5d9f14095d76239bc0e6f9804c24d1f92ffcab4" + integrity sha512-+B2vxfPwxoIIb8Ac8eHi6OgAn0qbjmPZuun7/es0/ouYYTbEhvhTAD20X3PGZlHrIYeDyS2ansuXm8gJI+/BzA== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" lodash "^4.17.15" -"@abp/luxon@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-3.0.4.tgz#aa1bf6a93d6254433101e50a205b276759969414" - integrity sha512-gX9dQJt9C6M1wmCSUIBoNr8CAt0jkUZFQ7LKKO/BoADGM/XFvY5vDbhL7zcwbwitgCOzmucoCxs/6rIxSOVlrg== +"@abp/luxon@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-3.0.5.tgz#083042c939ea58bf719e35114a6c8efb07b68688" + integrity sha512-0UVHO2CO89t34O35OA3JzWGWLBpg+e1NQXSfVtK90nmg/QmzGkHVM9m7Su0jjLNBt3N5EA5OPOhriot3bCKItw== dependencies: luxon "^1.24.1" -"@abp/malihu-custom-scrollbar-plugin@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-3.0.4.tgz#c54824b98363f4190cc6a37fcd421c2bfdc1e201" - integrity sha512-6LdSGctOr4ACtdohdSlb19vrFNJ5C0hy1VdbBEafHOBspXckvRVx4uJl2KfHmAC+bMg9apoK/wcHjVNdBHThYQ== +"@abp/malihu-custom-scrollbar-plugin@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-3.0.5.tgz#f908289927a63fd76fb8f60b00a37747d9edbe39" + integrity sha512-3GoXA8VtGyZWKiqaPCipPXYLLM7h2Y5Oecq5c5YlMQyW2akv7uHiQQF2GZW/nVsFu3HBbGTOWpKRRmVk3PEO9Q== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" malihu-custom-scrollbar-plugin "^3.1.5" -"@abp/select2@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-3.0.4.tgz#49db0cfbc9b08f6ae4949b2cb8bdef92d7d308dd" - integrity sha512-zKEgkWBFdGU2KiAMUKhucHqBfzDy2KNSefyqxpOiUd1L3PEKu+9o23HZRWzDupYltop+SO1Jw33CNoVAkKm4+A== +"@abp/select2@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-3.0.5.tgz#79d7754713c64a6549da520dc6867ef3e3dd0350" + integrity sha512-VbeSHdAdDy8exrjplHMMhLVV4oG7Gq9zBb5M52f0sIkA4DkXFhksXHcHNoOCfhaKAXpenGA03Z9GJewZ3MTpMw== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" select2 "^4.0.13" -"@abp/sweetalert@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/sweetalert/-/sweetalert-3.0.4.tgz#f85d11c5aabc6ceb509af1b8fd8d75bc3c74bd27" - integrity sha512-hO0qXUWxeMy/lfy3sb+5HvOmIPst6UvYJJGy3ZQR6DKEdkOktBX9sEDb7m6ij6xAIHqWIx1xLveul0oGteOG0Q== +"@abp/sweetalert@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/sweetalert/-/sweetalert-3.0.5.tgz#e3b92d23cf3633983980b6255bf63c43d422ceab" + integrity sha512-OUt4ANgj14GESfRG7+UqdONS8daOu+8x2OZDWv98hriuX0uCVBjwB2J0jRI/n653C68Mi0yHixGONDv9OLy/lg== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" sweetalert "^2.1.2" -"@abp/timeago@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-3.0.4.tgz#82d90a04f703a7db9c0acedcd2f3cea684e7ae18" - integrity sha512-7SbbqScTKCix25xWy5oLT0slJqUQsW6YgJgI0N41C3AsByRBXZeh5Gcep033gprTPmpgNY8JOWcEcCXySnYvfA== +"@abp/timeago@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-3.0.5.tgz#b191a7c43f3dc77645fe27273f2f6619f7d4eb31" + integrity sha512-QhvAOU4C+Qxh/gFuCSGZtLU4KwVhdv/jtK8ovWyqbGvQMCCIimqa8BFKIz0H5XWIJhHwwWblkpjICWgjWh6uXw== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" timeago "^1.6.7" -"@abp/toastr@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/toastr/-/toastr-3.0.4.tgz#884ab2343ddd59981c291c632e3b0630532f7e9c" - integrity sha512-NG0pVD8bdIXGpjPW9cozCzmyuAdnSLE4vDYLUReP1pZFp9B8SMJqpgPk9eFg0Y06aaHTVMKYBKRoq9xXS4w9Xg== +"@abp/toastr@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/toastr/-/toastr-3.0.5.tgz#fc83b678f5be3a22040d0421ad9db5b2eac9d4fc" + integrity sha512-qgi2DEu6FYpN/DYcOZrmXsAr1HqcIKMhBWeXgjfoYUIl/K5+OuUgbQ69D/jdc0jz8HU9a4Cw7Q8hhe4CkgssEw== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" toastr "^2.1.4" -"@abp/utils@^3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-3.0.4.tgz#38068fa8f735e1889cf2f226128366825948814d" - integrity sha512-TnXRZIZcxv5+EctEPsZEi2cyRXbpl3A679wzQa6BACv+pM45cjGO1JR8P0VswKN4TVL0aQRMv26N6U/uie8sEQ== +"@abp/utils@^3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-3.0.5.tgz#66b3da953500e73ff1f23d1b41168cde8216ce49" + integrity sha512-X9V1xLIEazItaf93nFSA4p94WT6QK3yA4eIyvU7GyyEkkOQdaakNwPbRwy930sd+I+/oXnIqM1vDsJu9QDhgvg== dependencies: just-compare "^1.3.0" diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyCompanyName.MyProjectName.Web.Host.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyCompanyName.MyProjectName.Web.Host.csproj index 16066b7e4d..469d83804c 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyCompanyName.MyProjectName.Web.Host.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyCompanyName.MyProjectName.Web.Host.csproj @@ -20,8 +20,8 @@ - - + + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/appsettings.json b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/appsettings.json index 18e3e01313..e9330b776e 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/appsettings.json +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/appsettings.json @@ -14,16 +14,5 @@ "Authority": "https://localhost:44301", "ClientId": "MyProjectName_Web", "ClientSecret": "1q2w3e*" - }, - "Settings": { - "Abp.Mailing.Smtp.Host": "127.0.0.1", - "Abp.Mailing.Smtp.Port": "25", - "Abp.Mailing.Smtp.UserName": "", - "Abp.Mailing.Smtp.Password": "", - "Abp.Mailing.Smtp.Domain": "", - "Abp.Mailing.Smtp.EnableSsl": "false", - "Abp.Mailing.Smtp.UseDefaultCredentials": "true", - "Abp.Mailing.DefaultFromAddress": "noreply@abp.io", - "Abp.Mailing.DefaultFromDisplayName": "ABP application" } } diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/package.json b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/package.json index 528a8910e5..5ffe5c0500 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/package.json +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/package.json @@ -3,6 +3,6 @@ "name": "my-app", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.basic": "^3.0.4" + "@abp/aspnetcore.mvc.ui.theme.basic": "^3.0.5" } } \ No newline at end of file diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/yarn.lock b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/yarn.lock index 63a96d93c8..a583172e87 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/yarn.lock +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/yarn.lock @@ -2,37 +2,37 @@ # yarn lockfile v1 -"@abp/aspnetcore.mvc.ui.theme.basic@^3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-3.0.4.tgz#c58e3e06ba78279875cf2b5f96d34b83ce09f6df" - integrity sha512-tKKqVRKpwN7P4Mn+he3bcz4Qbt8bkZ++wk6iOujd6HLTkBNa0uKyM4L6+iCn1Hi62S6HeK2Xytx+b+IYeIlsuw== - dependencies: - "@abp/aspnetcore.mvc.ui.theme.shared" "~3.0.4" - -"@abp/aspnetcore.mvc.ui.theme.shared@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-3.0.4.tgz#50db612b6492d36c63dd9042000a9fb00fecec54" - integrity sha512-imd2T5XXkNvGQyXV/A9hA80qSeurKaUcW6HDkte8Y2fs4QfvkjgzWXi7neqF6F+HVbYYPg3rIBWC6QF1OXGn2w== - dependencies: - "@abp/aspnetcore.mvc.ui" "~3.0.4" - "@abp/bootstrap" "~3.0.4" - "@abp/bootstrap-datepicker" "~3.0.4" - "@abp/datatables.net-bs4" "~3.0.4" - "@abp/font-awesome" "~3.0.4" - "@abp/jquery-form" "~3.0.4" - "@abp/jquery-validation-unobtrusive" "~3.0.4" - "@abp/lodash" "~3.0.4" - "@abp/luxon" "~3.0.4" - "@abp/malihu-custom-scrollbar-plugin" "~3.0.4" - "@abp/select2" "~3.0.4" - "@abp/sweetalert" "~3.0.4" - "@abp/timeago" "~3.0.4" - "@abp/toastr" "~3.0.4" - -"@abp/aspnetcore.mvc.ui@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-3.0.4.tgz#4e4344d95a98a60c53037b351cb915d8ff7a7dcb" - integrity sha512-p7KI5CKkpD4EYzG4fCtygrtZhwMu7Z+IZFGbdqXelLbI5ACkbJyrn+N+9trPfS9papymeXY4p1E3v6xOH7rSHQ== +"@abp/aspnetcore.mvc.ui.theme.basic@^3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-3.0.5.tgz#c146f20226522f0305cc0fa31cfcf9d39be78901" + integrity sha512-ptNhD4xV2wSLW7vQ5KUgaRiH5Ov1OscZW2ZEoivnt3kqgtIC3DGw+SiE9vjFCvyY83fjRkIlW2PIfQxSySbteQ== + dependencies: + "@abp/aspnetcore.mvc.ui.theme.shared" "~3.0.5" + +"@abp/aspnetcore.mvc.ui.theme.shared@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-3.0.5.tgz#5b370902cf229963f9aa10dd64482d57299dde76" + integrity sha512-iobAH39xrCdnqdaABaiYZg4lqNSOwPkMCLnyc+ytxOMbu/YnNKahXPmRhgd8dR/l+iQY1+6T4SWfLdGswi3gAg== + dependencies: + "@abp/aspnetcore.mvc.ui" "~3.0.5" + "@abp/bootstrap" "~3.0.5" + "@abp/bootstrap-datepicker" "~3.0.5" + "@abp/datatables.net-bs4" "~3.0.5" + "@abp/font-awesome" "~3.0.5" + "@abp/jquery-form" "~3.0.5" + "@abp/jquery-validation-unobtrusive" "~3.0.5" + "@abp/lodash" "~3.0.5" + "@abp/luxon" "~3.0.5" + "@abp/malihu-custom-scrollbar-plugin" "~3.0.5" + "@abp/select2" "~3.0.5" + "@abp/sweetalert" "~3.0.5" + "@abp/timeago" "~3.0.5" + "@abp/toastr" "~3.0.5" + +"@abp/aspnetcore.mvc.ui@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-3.0.5.tgz#a050eaee328c56a3730f5abbf25035adf612d6f0" + integrity sha512-/EOgjXTzR+I1OK9KA7L3yzQ2RGSDW97+n3cQrSdLAUF+tnRvMJS0nytDlSJSRK2hb8/clFMaofZHCC81aUkYUg== dependencies: ansi-colors "^4.1.1" extend-object "^1.0.0" @@ -41,144 +41,144 @@ path "^0.12.7" rimraf "^3.0.2" -"@abp/bootstrap-datepicker@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-3.0.4.tgz#d21963f158a4d348a0acdc46209e67378aae8e55" - integrity sha512-25e9qm39dj9AijIchQIdLlBiBM5/fhASkEuLKNt6M8S/e3qwKLwbRhX90DKMFvOyKdxxcK1gX7tDuGHyG9A8mA== +"@abp/bootstrap-datepicker@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-3.0.5.tgz#3a7fd86dbcadc31c11be5b2b31c8cf8ac3035540" + integrity sha512-vIY1wT4RHoJAPhVK11Q5MS71iZbwsmM/0VTBvvs04mjRSSi6xhdyRYkA+l+P49fnOwhZNaaK19XwQdVVScDuhA== dependencies: bootstrap-datepicker "^1.9.0" -"@abp/bootstrap@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-3.0.4.tgz#3b0e2b74dc174a04ce669de1b140df833eee6031" - integrity sha512-DwxL1fGYOwChPwmMpwUxCKiIdzTfRf/YDkOLUiwI8eRrpABBeuE2sSHKfks6Rqh9Wqo/nlWXvfccgDRqweqhsA== +"@abp/bootstrap@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-3.0.5.tgz#91f2c8fe6a57fc5789ae8fe9c206b2c159e55880" + integrity sha512-LdydeJIbw5k1k7FkWaI1lTectZeTXmEIoccJtj9Qx4N9yGt40wGsjz9SOvUdJ0IKAgntIWNQP8ohWiBPgzT7Xg== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" bootstrap "^4.5.0" bootstrap-v4-rtl "4.4.1-2" -"@abp/core@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/core/-/core-3.0.4.tgz#281940525e280e9e3a1aa4cb4e8f64548ea026a3" - integrity sha512-ZPEH1fa5bf/f1ek9ZLQHx7u78iF6kRZ8Z2NBI36cNL13RyQbx485qJF+KXyqNlJoJAh0CAs0NsAJADU3utcDNA== +"@abp/core@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/core/-/core-3.0.5.tgz#9a7bd990d02edc8128a5be3233b6fbb5669b15b8" + integrity sha512-LdkSkZQfN4hugcGNxBkje0+eQomivwNFbnAkMFQbvCwYLwKB2yvk7GCemEetW1piqvyrUGz4jqhtunjoXps5JA== dependencies: - "@abp/utils" "^3.0.4" + "@abp/utils" "^3.0.5" -"@abp/datatables.net-bs4@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs4/-/datatables.net-bs4-3.0.4.tgz#e488336bd2c199146ea60464e0ba75aeb134957e" - integrity sha512-ID1XWKPxG9pv6mxBkku7JWvgXjTxJ1uShxmYv5kFYVuwOGMSvj8QVZInTHFcMdECopV56yjGPNOIFLeQj4Mw8Q== +"@abp/datatables.net-bs4@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs4/-/datatables.net-bs4-3.0.5.tgz#9b79935970d2d4074994240b5deb01dff5987c0a" + integrity sha512-jIWRBEG/NlhaE43i6Xzr18XJ5aTWA+WWslf7WgNOArG5slGBbH4lLLja4HswLP3NGdSmzIyHJR1p9XQ82nBkbQ== dependencies: - "@abp/datatables.net" "~3.0.4" + "@abp/datatables.net" "~3.0.5" datatables.net-bs4 "^1.10.21" -"@abp/datatables.net@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-3.0.4.tgz#7ef143b83143ad8d769be25296411a812a17b255" - integrity sha512-gdQwRuxO5fQWhG/DiUaf40DQ8dWIVGhOCyt6SZ8LQzq4uSaQqa0qrtTTTW2/aiee4MgJu2vJWYF5YCszIR/HhA== +"@abp/datatables.net@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-3.0.5.tgz#0a0a39e8fa54522bf8a09ce76ff3927207a79491" + integrity sha512-HClQ2tcZuWBAChf9oOJOtMP1zHv8jqloPEcw3iqbhS/tWZBw5VQ4TO+BVfZsqHDXZNnWSmR6/2xiKsK32nliew== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" datatables.net "^1.10.21" -"@abp/font-awesome@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-3.0.4.tgz#98f83d0bf8b5a2a4d988cb07f5cceda2e1bc1a09" - integrity sha512-AKhKCUXx8vdHIgyXUfbSLitfYor5dJbo4qMRRGqIJVSj+cyMkpgYbGjqr24GGijgZxdmOGL+s1b9iCUbIPgLNQ== +"@abp/font-awesome@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-3.0.5.tgz#62d58c34f0abf268e30bdf788dfbd3f45b19b412" + integrity sha512-/+31Vtw1S9vvuNy/rN/Q6awkgUaYNNYXESgfvqI0pqIkvlPgigvS8E68mHqGzsxyRqdZisI6JGd94AF+vJD7sQ== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" "@fortawesome/fontawesome-free" "^5.13.0" -"@abp/jquery-form@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-3.0.4.tgz#022eebbfe58b55e328984c0f119884c9464bc767" - integrity sha512-xDkk4rY0AmXBJE4qztzr2hZz+OVucVNWeagOEgeQHXJSKU/T+0kj0a1RxEHpyI24xBvEXAbe8PbByTbu0QXYew== +"@abp/jquery-form@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-3.0.5.tgz#9d2ea9b2509448ee163a163298a7a09480865f78" + integrity sha512-RKgvQciaMml33NvzA28aYyfNI+WQHRfKLFWvOvqc8qO4C3BvyrnwepuzZcnI/9UsELKLSLoFrfk3rJHhxnTJhg== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" jquery-form "^4.3.0" -"@abp/jquery-validation-unobtrusive@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-3.0.4.tgz#6cc942aadcdde33893e7a45c84aa022e033e4e8b" - integrity sha512-N9sza91HCyPvJo+d0RAfq4skae3TgAYg7KXhlwzRE2hqUQS+EICVARURWyxJW7s7Zu6+gxDTkXhBZRTW56deZA== +"@abp/jquery-validation-unobtrusive@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-3.0.5.tgz#b8912e1c4cfdd4fdff6ad4aa7ee8693b55a3be1f" + integrity sha512-kPifDA+1ErbeQBwn+BzwtWuTHXzBke/EMCmITuj59plM/60gZ1ZfVm8EzzTmKS7iLaVVeOqTKf0vEQTobNYffw== dependencies: - "@abp/jquery-validation" "~3.0.4" + "@abp/jquery-validation" "~3.0.5" jquery-validation-unobtrusive "^3.2.11" -"@abp/jquery-validation@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-3.0.4.tgz#3f96d1f83c75a7fc47e5e5e12c51dbc8da7582b1" - integrity sha512-2ljqas4YKU0PcwB0wQW2qhXejDHjW+N1XCxZDyCZ2wF9UxGlKBR8rFt76TJByXSotKE7n2hJRZaOh9ICZUow2g== +"@abp/jquery-validation@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-3.0.5.tgz#55e36b3c832a35447fac69af312dedf1dee22279" + integrity sha512-fdqxc5CJ6TpUAF8+NEsKCmXvvZiOPR5bzczcfvfovxLOjvHvdEO74XPGjsjlBNTdaKMhmNGZRK4JRLUz7fvTng== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" jquery-validation "^1.19.2" -"@abp/jquery@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-3.0.4.tgz#5f236d95cfae7f65012cd8778fd74490b7542442" - integrity sha512-g8N8Te5vf+8djdksfZoZ3tXaqlBC4Ra39pAWU/XerlLl64u+XmAX1s6yKRfC2kS8lf292GgiFYuNOkb77J6k4A== +"@abp/jquery@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-3.0.5.tgz#2e43ac19a8df6e1e0220d6d4991d33a93d890754" + integrity sha512-2d2l+smWKAVP4/b6GSO+8rmJ7pFXiUewpBDwkHm5qgkK6n6UGqIcK9lIgiS2YQ6rmL6qdtuNhhqtC9GF2TF2kQ== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" jquery "~3.5.1" -"@abp/lodash@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-3.0.4.tgz#ee60bc99f98b59fd9fc5ff4bbcf914a13faf209d" - integrity sha512-jxvv74Sm/fUKDBMylK4Ne/8GKm7GuHAdcLFtkoJQptdO+RjZpKJ86ULT+X5vbKskZaTj2fEmOR6l4hmfnBULTA== +"@abp/lodash@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-3.0.5.tgz#d5d9f14095d76239bc0e6f9804c24d1f92ffcab4" + integrity sha512-+B2vxfPwxoIIb8Ac8eHi6OgAn0qbjmPZuun7/es0/ouYYTbEhvhTAD20X3PGZlHrIYeDyS2ansuXm8gJI+/BzA== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" lodash "^4.17.15" -"@abp/luxon@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-3.0.4.tgz#aa1bf6a93d6254433101e50a205b276759969414" - integrity sha512-gX9dQJt9C6M1wmCSUIBoNr8CAt0jkUZFQ7LKKO/BoADGM/XFvY5vDbhL7zcwbwitgCOzmucoCxs/6rIxSOVlrg== +"@abp/luxon@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-3.0.5.tgz#083042c939ea58bf719e35114a6c8efb07b68688" + integrity sha512-0UVHO2CO89t34O35OA3JzWGWLBpg+e1NQXSfVtK90nmg/QmzGkHVM9m7Su0jjLNBt3N5EA5OPOhriot3bCKItw== dependencies: luxon "^1.24.1" -"@abp/malihu-custom-scrollbar-plugin@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-3.0.4.tgz#c54824b98363f4190cc6a37fcd421c2bfdc1e201" - integrity sha512-6LdSGctOr4ACtdohdSlb19vrFNJ5C0hy1VdbBEafHOBspXckvRVx4uJl2KfHmAC+bMg9apoK/wcHjVNdBHThYQ== +"@abp/malihu-custom-scrollbar-plugin@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-3.0.5.tgz#f908289927a63fd76fb8f60b00a37747d9edbe39" + integrity sha512-3GoXA8VtGyZWKiqaPCipPXYLLM7h2Y5Oecq5c5YlMQyW2akv7uHiQQF2GZW/nVsFu3HBbGTOWpKRRmVk3PEO9Q== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" malihu-custom-scrollbar-plugin "^3.1.5" -"@abp/select2@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-3.0.4.tgz#49db0cfbc9b08f6ae4949b2cb8bdef92d7d308dd" - integrity sha512-zKEgkWBFdGU2KiAMUKhucHqBfzDy2KNSefyqxpOiUd1L3PEKu+9o23HZRWzDupYltop+SO1Jw33CNoVAkKm4+A== +"@abp/select2@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-3.0.5.tgz#79d7754713c64a6549da520dc6867ef3e3dd0350" + integrity sha512-VbeSHdAdDy8exrjplHMMhLVV4oG7Gq9zBb5M52f0sIkA4DkXFhksXHcHNoOCfhaKAXpenGA03Z9GJewZ3MTpMw== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" select2 "^4.0.13" -"@abp/sweetalert@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/sweetalert/-/sweetalert-3.0.4.tgz#f85d11c5aabc6ceb509af1b8fd8d75bc3c74bd27" - integrity sha512-hO0qXUWxeMy/lfy3sb+5HvOmIPst6UvYJJGy3ZQR6DKEdkOktBX9sEDb7m6ij6xAIHqWIx1xLveul0oGteOG0Q== +"@abp/sweetalert@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/sweetalert/-/sweetalert-3.0.5.tgz#e3b92d23cf3633983980b6255bf63c43d422ceab" + integrity sha512-OUt4ANgj14GESfRG7+UqdONS8daOu+8x2OZDWv98hriuX0uCVBjwB2J0jRI/n653C68Mi0yHixGONDv9OLy/lg== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" sweetalert "^2.1.2" -"@abp/timeago@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-3.0.4.tgz#82d90a04f703a7db9c0acedcd2f3cea684e7ae18" - integrity sha512-7SbbqScTKCix25xWy5oLT0slJqUQsW6YgJgI0N41C3AsByRBXZeh5Gcep033gprTPmpgNY8JOWcEcCXySnYvfA== +"@abp/timeago@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-3.0.5.tgz#b191a7c43f3dc77645fe27273f2f6619f7d4eb31" + integrity sha512-QhvAOU4C+Qxh/gFuCSGZtLU4KwVhdv/jtK8ovWyqbGvQMCCIimqa8BFKIz0H5XWIJhHwwWblkpjICWgjWh6uXw== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" timeago "^1.6.7" -"@abp/toastr@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/toastr/-/toastr-3.0.4.tgz#884ab2343ddd59981c291c632e3b0630532f7e9c" - integrity sha512-NG0pVD8bdIXGpjPW9cozCzmyuAdnSLE4vDYLUReP1pZFp9B8SMJqpgPk9eFg0Y06aaHTVMKYBKRoq9xXS4w9Xg== +"@abp/toastr@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/toastr/-/toastr-3.0.5.tgz#fc83b678f5be3a22040d0421ad9db5b2eac9d4fc" + integrity sha512-qgi2DEu6FYpN/DYcOZrmXsAr1HqcIKMhBWeXgjfoYUIl/K5+OuUgbQ69D/jdc0jz8HU9a4Cw7Q8hhe4CkgssEw== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" toastr "^2.1.4" -"@abp/utils@^3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-3.0.4.tgz#38068fa8f735e1889cf2f226128366825948814d" - integrity sha512-TnXRZIZcxv5+EctEPsZEi2cyRXbpl3A679wzQa6BACv+pM45cjGO1JR8P0VswKN4TVL0aQRMv26N6U/uie8sEQ== +"@abp/utils@^3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-3.0.5.tgz#66b3da953500e73ff1f23d1b41168cde8216ce49" + integrity sha512-X9V1xLIEazItaf93nFSA4p94WT6QK3yA4eIyvU7GyyEkkOQdaakNwPbRwy930sd+I+/oXnIqM1vDsJu9QDhgvg== dependencies: just-compare "^1.3.0" diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyCompanyName.MyProjectName.Web.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyCompanyName.MyProjectName.Web.csproj index 463b5e9584..de399f2ef5 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyCompanyName.MyProjectName.Web.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyCompanyName.MyProjectName.Web.csproj @@ -37,7 +37,7 @@ - + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/package.json b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/package.json index 528a8910e5..5ffe5c0500 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/package.json +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/package.json @@ -3,6 +3,6 @@ "name": "my-app", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.basic": "^3.0.4" + "@abp/aspnetcore.mvc.ui.theme.basic": "^3.0.5" } } \ No newline at end of file diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/yarn.lock b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/yarn.lock index 845f2aaf18..7f4cc4007c 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/yarn.lock +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/yarn.lock @@ -2,37 +2,37 @@ # yarn lockfile v1 -"@abp/aspnetcore.mvc.ui.theme.basic@^3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-3.0.4.tgz#c58e3e06ba78279875cf2b5f96d34b83ce09f6df" - integrity sha512-tKKqVRKpwN7P4Mn+he3bcz4Qbt8bkZ++wk6iOujd6HLTkBNa0uKyM4L6+iCn1Hi62S6HeK2Xytx+b+IYeIlsuw== - dependencies: - "@abp/aspnetcore.mvc.ui.theme.shared" "~3.0.4" - -"@abp/aspnetcore.mvc.ui.theme.shared@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-3.0.4.tgz#50db612b6492d36c63dd9042000a9fb00fecec54" - integrity sha512-imd2T5XXkNvGQyXV/A9hA80qSeurKaUcW6HDkte8Y2fs4QfvkjgzWXi7neqF6F+HVbYYPg3rIBWC6QF1OXGn2w== - dependencies: - "@abp/aspnetcore.mvc.ui" "~3.0.4" - "@abp/bootstrap" "~3.0.4" - "@abp/bootstrap-datepicker" "~3.0.4" - "@abp/datatables.net-bs4" "~3.0.4" - "@abp/font-awesome" "~3.0.4" - "@abp/jquery-form" "~3.0.4" - "@abp/jquery-validation-unobtrusive" "~3.0.4" - "@abp/lodash" "~3.0.4" - "@abp/luxon" "~3.0.4" - "@abp/malihu-custom-scrollbar-plugin" "~3.0.4" - "@abp/select2" "~3.0.4" - "@abp/sweetalert" "~3.0.4" - "@abp/timeago" "~3.0.4" - "@abp/toastr" "~3.0.4" - -"@abp/aspnetcore.mvc.ui@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-3.0.4.tgz#4e4344d95a98a60c53037b351cb915d8ff7a7dcb" - integrity sha512-p7KI5CKkpD4EYzG4fCtygrtZhwMu7Z+IZFGbdqXelLbI5ACkbJyrn+N+9trPfS9papymeXY4p1E3v6xOH7rSHQ== +"@abp/aspnetcore.mvc.ui.theme.basic@^3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-3.0.5.tgz#c146f20226522f0305cc0fa31cfcf9d39be78901" + integrity sha512-ptNhD4xV2wSLW7vQ5KUgaRiH5Ov1OscZW2ZEoivnt3kqgtIC3DGw+SiE9vjFCvyY83fjRkIlW2PIfQxSySbteQ== + dependencies: + "@abp/aspnetcore.mvc.ui.theme.shared" "~3.0.5" + +"@abp/aspnetcore.mvc.ui.theme.shared@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-3.0.5.tgz#5b370902cf229963f9aa10dd64482d57299dde76" + integrity sha512-iobAH39xrCdnqdaABaiYZg4lqNSOwPkMCLnyc+ytxOMbu/YnNKahXPmRhgd8dR/l+iQY1+6T4SWfLdGswi3gAg== + dependencies: + "@abp/aspnetcore.mvc.ui" "~3.0.5" + "@abp/bootstrap" "~3.0.5" + "@abp/bootstrap-datepicker" "~3.0.5" + "@abp/datatables.net-bs4" "~3.0.5" + "@abp/font-awesome" "~3.0.5" + "@abp/jquery-form" "~3.0.5" + "@abp/jquery-validation-unobtrusive" "~3.0.5" + "@abp/lodash" "~3.0.5" + "@abp/luxon" "~3.0.5" + "@abp/malihu-custom-scrollbar-plugin" "~3.0.5" + "@abp/select2" "~3.0.5" + "@abp/sweetalert" "~3.0.5" + "@abp/timeago" "~3.0.5" + "@abp/toastr" "~3.0.5" + +"@abp/aspnetcore.mvc.ui@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-3.0.5.tgz#a050eaee328c56a3730f5abbf25035adf612d6f0" + integrity sha512-/EOgjXTzR+I1OK9KA7L3yzQ2RGSDW97+n3cQrSdLAUF+tnRvMJS0nytDlSJSRK2hb8/clFMaofZHCC81aUkYUg== dependencies: ansi-colors "^4.1.1" extend-object "^1.0.0" @@ -41,144 +41,144 @@ path "^0.12.7" rimraf "^3.0.2" -"@abp/bootstrap-datepicker@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-3.0.4.tgz#d21963f158a4d348a0acdc46209e67378aae8e55" - integrity sha512-25e9qm39dj9AijIchQIdLlBiBM5/fhASkEuLKNt6M8S/e3qwKLwbRhX90DKMFvOyKdxxcK1gX7tDuGHyG9A8mA== +"@abp/bootstrap-datepicker@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-3.0.5.tgz#3a7fd86dbcadc31c11be5b2b31c8cf8ac3035540" + integrity sha512-vIY1wT4RHoJAPhVK11Q5MS71iZbwsmM/0VTBvvs04mjRSSi6xhdyRYkA+l+P49fnOwhZNaaK19XwQdVVScDuhA== dependencies: bootstrap-datepicker "^1.9.0" -"@abp/bootstrap@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-3.0.4.tgz#3b0e2b74dc174a04ce669de1b140df833eee6031" - integrity sha512-DwxL1fGYOwChPwmMpwUxCKiIdzTfRf/YDkOLUiwI8eRrpABBeuE2sSHKfks6Rqh9Wqo/nlWXvfccgDRqweqhsA== +"@abp/bootstrap@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-3.0.5.tgz#91f2c8fe6a57fc5789ae8fe9c206b2c159e55880" + integrity sha512-LdydeJIbw5k1k7FkWaI1lTectZeTXmEIoccJtj9Qx4N9yGt40wGsjz9SOvUdJ0IKAgntIWNQP8ohWiBPgzT7Xg== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" bootstrap "^4.5.0" bootstrap-v4-rtl "4.4.1-2" -"@abp/core@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/core/-/core-3.0.4.tgz#281940525e280e9e3a1aa4cb4e8f64548ea026a3" - integrity sha512-ZPEH1fa5bf/f1ek9ZLQHx7u78iF6kRZ8Z2NBI36cNL13RyQbx485qJF+KXyqNlJoJAh0CAs0NsAJADU3utcDNA== +"@abp/core@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/core/-/core-3.0.5.tgz#9a7bd990d02edc8128a5be3233b6fbb5669b15b8" + integrity sha512-LdkSkZQfN4hugcGNxBkje0+eQomivwNFbnAkMFQbvCwYLwKB2yvk7GCemEetW1piqvyrUGz4jqhtunjoXps5JA== dependencies: - "@abp/utils" "^3.0.4" + "@abp/utils" "^3.0.5" -"@abp/datatables.net-bs4@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs4/-/datatables.net-bs4-3.0.4.tgz#e488336bd2c199146ea60464e0ba75aeb134957e" - integrity sha512-ID1XWKPxG9pv6mxBkku7JWvgXjTxJ1uShxmYv5kFYVuwOGMSvj8QVZInTHFcMdECopV56yjGPNOIFLeQj4Mw8Q== +"@abp/datatables.net-bs4@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs4/-/datatables.net-bs4-3.0.5.tgz#9b79935970d2d4074994240b5deb01dff5987c0a" + integrity sha512-jIWRBEG/NlhaE43i6Xzr18XJ5aTWA+WWslf7WgNOArG5slGBbH4lLLja4HswLP3NGdSmzIyHJR1p9XQ82nBkbQ== dependencies: - "@abp/datatables.net" "~3.0.4" + "@abp/datatables.net" "~3.0.5" datatables.net-bs4 "^1.10.21" -"@abp/datatables.net@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-3.0.4.tgz#7ef143b83143ad8d769be25296411a812a17b255" - integrity sha512-gdQwRuxO5fQWhG/DiUaf40DQ8dWIVGhOCyt6SZ8LQzq4uSaQqa0qrtTTTW2/aiee4MgJu2vJWYF5YCszIR/HhA== +"@abp/datatables.net@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-3.0.5.tgz#0a0a39e8fa54522bf8a09ce76ff3927207a79491" + integrity sha512-HClQ2tcZuWBAChf9oOJOtMP1zHv8jqloPEcw3iqbhS/tWZBw5VQ4TO+BVfZsqHDXZNnWSmR6/2xiKsK32nliew== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" datatables.net "^1.10.21" -"@abp/font-awesome@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-3.0.4.tgz#98f83d0bf8b5a2a4d988cb07f5cceda2e1bc1a09" - integrity sha512-AKhKCUXx8vdHIgyXUfbSLitfYor5dJbo4qMRRGqIJVSj+cyMkpgYbGjqr24GGijgZxdmOGL+s1b9iCUbIPgLNQ== +"@abp/font-awesome@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-3.0.5.tgz#62d58c34f0abf268e30bdf788dfbd3f45b19b412" + integrity sha512-/+31Vtw1S9vvuNy/rN/Q6awkgUaYNNYXESgfvqI0pqIkvlPgigvS8E68mHqGzsxyRqdZisI6JGd94AF+vJD7sQ== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" "@fortawesome/fontawesome-free" "^5.13.0" -"@abp/jquery-form@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-3.0.4.tgz#022eebbfe58b55e328984c0f119884c9464bc767" - integrity sha512-xDkk4rY0AmXBJE4qztzr2hZz+OVucVNWeagOEgeQHXJSKU/T+0kj0a1RxEHpyI24xBvEXAbe8PbByTbu0QXYew== +"@abp/jquery-form@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-3.0.5.tgz#9d2ea9b2509448ee163a163298a7a09480865f78" + integrity sha512-RKgvQciaMml33NvzA28aYyfNI+WQHRfKLFWvOvqc8qO4C3BvyrnwepuzZcnI/9UsELKLSLoFrfk3rJHhxnTJhg== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" jquery-form "^4.3.0" -"@abp/jquery-validation-unobtrusive@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-3.0.4.tgz#6cc942aadcdde33893e7a45c84aa022e033e4e8b" - integrity sha512-N9sza91HCyPvJo+d0RAfq4skae3TgAYg7KXhlwzRE2hqUQS+EICVARURWyxJW7s7Zu6+gxDTkXhBZRTW56deZA== +"@abp/jquery-validation-unobtrusive@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-3.0.5.tgz#b8912e1c4cfdd4fdff6ad4aa7ee8693b55a3be1f" + integrity sha512-kPifDA+1ErbeQBwn+BzwtWuTHXzBke/EMCmITuj59plM/60gZ1ZfVm8EzzTmKS7iLaVVeOqTKf0vEQTobNYffw== dependencies: - "@abp/jquery-validation" "~3.0.4" + "@abp/jquery-validation" "~3.0.5" jquery-validation-unobtrusive "^3.2.11" -"@abp/jquery-validation@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-3.0.4.tgz#3f96d1f83c75a7fc47e5e5e12c51dbc8da7582b1" - integrity sha512-2ljqas4YKU0PcwB0wQW2qhXejDHjW+N1XCxZDyCZ2wF9UxGlKBR8rFt76TJByXSotKE7n2hJRZaOh9ICZUow2g== +"@abp/jquery-validation@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-3.0.5.tgz#55e36b3c832a35447fac69af312dedf1dee22279" + integrity sha512-fdqxc5CJ6TpUAF8+NEsKCmXvvZiOPR5bzczcfvfovxLOjvHvdEO74XPGjsjlBNTdaKMhmNGZRK4JRLUz7fvTng== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" jquery-validation "^1.19.2" -"@abp/jquery@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-3.0.4.tgz#5f236d95cfae7f65012cd8778fd74490b7542442" - integrity sha512-g8N8Te5vf+8djdksfZoZ3tXaqlBC4Ra39pAWU/XerlLl64u+XmAX1s6yKRfC2kS8lf292GgiFYuNOkb77J6k4A== +"@abp/jquery@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-3.0.5.tgz#2e43ac19a8df6e1e0220d6d4991d33a93d890754" + integrity sha512-2d2l+smWKAVP4/b6GSO+8rmJ7pFXiUewpBDwkHm5qgkK6n6UGqIcK9lIgiS2YQ6rmL6qdtuNhhqtC9GF2TF2kQ== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" jquery "~3.5.1" -"@abp/lodash@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-3.0.4.tgz#ee60bc99f98b59fd9fc5ff4bbcf914a13faf209d" - integrity sha512-jxvv74Sm/fUKDBMylK4Ne/8GKm7GuHAdcLFtkoJQptdO+RjZpKJ86ULT+X5vbKskZaTj2fEmOR6l4hmfnBULTA== +"@abp/lodash@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-3.0.5.tgz#d5d9f14095d76239bc0e6f9804c24d1f92ffcab4" + integrity sha512-+B2vxfPwxoIIb8Ac8eHi6OgAn0qbjmPZuun7/es0/ouYYTbEhvhTAD20X3PGZlHrIYeDyS2ansuXm8gJI+/BzA== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" lodash "^4.17.15" -"@abp/luxon@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-3.0.4.tgz#aa1bf6a93d6254433101e50a205b276759969414" - integrity sha512-gX9dQJt9C6M1wmCSUIBoNr8CAt0jkUZFQ7LKKO/BoADGM/XFvY5vDbhL7zcwbwitgCOzmucoCxs/6rIxSOVlrg== +"@abp/luxon@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-3.0.5.tgz#083042c939ea58bf719e35114a6c8efb07b68688" + integrity sha512-0UVHO2CO89t34O35OA3JzWGWLBpg+e1NQXSfVtK90nmg/QmzGkHVM9m7Su0jjLNBt3N5EA5OPOhriot3bCKItw== dependencies: luxon "^1.24.1" -"@abp/malihu-custom-scrollbar-plugin@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-3.0.4.tgz#c54824b98363f4190cc6a37fcd421c2bfdc1e201" - integrity sha512-6LdSGctOr4ACtdohdSlb19vrFNJ5C0hy1VdbBEafHOBspXckvRVx4uJl2KfHmAC+bMg9apoK/wcHjVNdBHThYQ== +"@abp/malihu-custom-scrollbar-plugin@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-3.0.5.tgz#f908289927a63fd76fb8f60b00a37747d9edbe39" + integrity sha512-3GoXA8VtGyZWKiqaPCipPXYLLM7h2Y5Oecq5c5YlMQyW2akv7uHiQQF2GZW/nVsFu3HBbGTOWpKRRmVk3PEO9Q== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" malihu-custom-scrollbar-plugin "^3.1.5" -"@abp/select2@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-3.0.4.tgz#49db0cfbc9b08f6ae4949b2cb8bdef92d7d308dd" - integrity sha512-zKEgkWBFdGU2KiAMUKhucHqBfzDy2KNSefyqxpOiUd1L3PEKu+9o23HZRWzDupYltop+SO1Jw33CNoVAkKm4+A== +"@abp/select2@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-3.0.5.tgz#79d7754713c64a6549da520dc6867ef3e3dd0350" + integrity sha512-VbeSHdAdDy8exrjplHMMhLVV4oG7Gq9zBb5M52f0sIkA4DkXFhksXHcHNoOCfhaKAXpenGA03Z9GJewZ3MTpMw== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" select2 "^4.0.13" -"@abp/sweetalert@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/sweetalert/-/sweetalert-3.0.4.tgz#f85d11c5aabc6ceb509af1b8fd8d75bc3c74bd27" - integrity sha512-hO0qXUWxeMy/lfy3sb+5HvOmIPst6UvYJJGy3ZQR6DKEdkOktBX9sEDb7m6ij6xAIHqWIx1xLveul0oGteOG0Q== +"@abp/sweetalert@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/sweetalert/-/sweetalert-3.0.5.tgz#e3b92d23cf3633983980b6255bf63c43d422ceab" + integrity sha512-OUt4ANgj14GESfRG7+UqdONS8daOu+8x2OZDWv98hriuX0uCVBjwB2J0jRI/n653C68Mi0yHixGONDv9OLy/lg== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" sweetalert "^2.1.2" -"@abp/timeago@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-3.0.4.tgz#82d90a04f703a7db9c0acedcd2f3cea684e7ae18" - integrity sha512-7SbbqScTKCix25xWy5oLT0slJqUQsW6YgJgI0N41C3AsByRBXZeh5Gcep033gprTPmpgNY8JOWcEcCXySnYvfA== +"@abp/timeago@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-3.0.5.tgz#b191a7c43f3dc77645fe27273f2f6619f7d4eb31" + integrity sha512-QhvAOU4C+Qxh/gFuCSGZtLU4KwVhdv/jtK8ovWyqbGvQMCCIimqa8BFKIz0H5XWIJhHwwWblkpjICWgjWh6uXw== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" timeago "^1.6.7" -"@abp/toastr@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/toastr/-/toastr-3.0.4.tgz#884ab2343ddd59981c291c632e3b0630532f7e9c" - integrity sha512-NG0pVD8bdIXGpjPW9cozCzmyuAdnSLE4vDYLUReP1pZFp9B8SMJqpgPk9eFg0Y06aaHTVMKYBKRoq9xXS4w9Xg== +"@abp/toastr@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/toastr/-/toastr-3.0.5.tgz#fc83b678f5be3a22040d0421ad9db5b2eac9d4fc" + integrity sha512-qgi2DEu6FYpN/DYcOZrmXsAr1HqcIKMhBWeXgjfoYUIl/K5+OuUgbQ69D/jdc0jz8HU9a4Cw7Q8hhe4CkgssEw== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" toastr "^2.1.4" -"@abp/utils@^3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-3.0.4.tgz#38068fa8f735e1889cf2f226128366825948814d" - integrity sha512-TnXRZIZcxv5+EctEPsZEi2cyRXbpl3A679wzQa6BACv+pM45cjGO1JR8P0VswKN4TVL0aQRMv26N6U/uie8sEQ== +"@abp/utils@^3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-3.0.5.tgz#66b3da953500e73ff1f23d1b41168cde8216ce49" + integrity sha512-X9V1xLIEazItaf93nFSA4p94WT6QK3yA4eIyvU7GyyEkkOQdaakNwPbRwy930sd+I+/oXnIqM1vDsJu9QDhgvg== dependencies: just-compare "^1.3.0" diff --git a/templates/app/aspnet-core/test/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp.csproj b/templates/app/aspnet-core/test/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp.csproj index 03d0de4810..b7f6fe7dec 100644 --- a/templates/app/aspnet-core/test/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp.csproj +++ b/templates/app/aspnet-core/test/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp.csproj @@ -20,9 +20,9 @@ - + - + diff --git a/templates/console/src/MyCompanyName.MyProjectName/MyCompanyName.MyProjectName.csproj b/templates/console/src/MyCompanyName.MyProjectName/MyCompanyName.MyProjectName.csproj index 35d9098fce..513dfb9fc7 100644 --- a/templates/console/src/MyCompanyName.MyProjectName/MyCompanyName.MyProjectName.csproj +++ b/templates/console/src/MyCompanyName.MyProjectName/MyCompanyName.MyProjectName.csproj @@ -12,11 +12,18 @@ - - + + + + + PreserveNewest + Always + + + diff --git a/templates/console/src/MyCompanyName.MyProjectName/MyProjectNameHostedService.cs b/templates/console/src/MyCompanyName.MyProjectName/MyProjectNameHostedService.cs index 5c5c9d89ee..6ddcd6630d 100644 --- a/templates/console/src/MyCompanyName.MyProjectName/MyProjectNameHostedService.cs +++ b/templates/console/src/MyCompanyName.MyProjectName/MyProjectNameHostedService.cs @@ -1,34 +1,41 @@ -using System.Threading; +using System; +using System.Threading; using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using Serilog; using Volo.Abp; namespace MyCompanyName.MyProjectName { public class MyProjectNameHostedService : IHostedService { - public Task StartAsync(CancellationToken cancellationToken) + private readonly IAbpApplicationWithExternalServiceProvider _application; + private readonly IServiceProvider _serviceProvider; + private readonly HelloWorldService _helloWorldService; + + public MyProjectNameHostedService( + IAbpApplicationWithExternalServiceProvider application, + IServiceProvider serviceProvider, + HelloWorldService helloWorldService) { - using (var application = AbpApplicationFactory.Create(options => - { - options.UseAutofac(); //Autofac integration - options.Services.AddLogging(c => c.AddSerilog()); - })) - { - application.Initialize(); + _application = application; + _serviceProvider = serviceProvider; + _helloWorldService = helloWorldService; + } - //Resolve a service and use it - var helloWorldService = application.ServiceProvider.GetService(); - helloWorldService.SayHello(); + public Task StartAsync(CancellationToken cancellationToken) + { + _application.Initialize(_serviceProvider); - application.Shutdown(); - } + _helloWorldService.SayHello(); return Task.CompletedTask; } - public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; + public Task StopAsync(CancellationToken cancellationToken) + { + _application.Shutdown(); + + return Task.CompletedTask; + } } } diff --git a/templates/console/src/MyCompanyName.MyProjectName/MyProjectNameModule.cs b/templates/console/src/MyCompanyName.MyProjectName/MyProjectNameModule.cs index 9938981484..2d5e600971 100644 --- a/templates/console/src/MyCompanyName.MyProjectName/MyProjectNameModule.cs +++ b/templates/console/src/MyCompanyName.MyProjectName/MyProjectNameModule.cs @@ -1,4 +1,6 @@ -using Volo.Abp.Autofac; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Volo.Abp.Autofac; using Volo.Abp.Modularity; namespace MyCompanyName.MyProjectName @@ -9,6 +11,12 @@ namespace MyCompanyName.MyProjectName )] public class MyProjectNameModule : AbpModule { + public override void ConfigureServices(ServiceConfigurationContext context) + { + var configuration = context.Services.GetConfiguration(); + var hostEnvironment = context.Services.GetSingletonInstance(); + context.Services.AddHostedService(); + } } } diff --git a/templates/console/src/MyCompanyName.MyProjectName/Program.cs b/templates/console/src/MyCompanyName.MyProjectName/Program.cs index b095d0db73..2e913e7d4b 100644 --- a/templates/console/src/MyCompanyName.MyProjectName/Program.cs +++ b/templates/console/src/MyCompanyName.MyProjectName/Program.cs @@ -1,5 +1,6 @@ using System; using System.Threading.Tasks; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Serilog; @@ -43,9 +44,15 @@ namespace MyCompanyName.MyProjectName internal static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) + .UseAutofac() + .UseSerilog() + .ConfigureAppConfiguration((context, config) => + { + //setup your additional configuration sources + }) .ConfigureServices((hostContext, services) => { - services.AddHostedService(); + services.AddApplication(); }); } } diff --git a/templates/console/src/MyCompanyName.MyProjectName/appsettings.json b/templates/console/src/MyCompanyName.MyProjectName/appsettings.json new file mode 100644 index 0000000000..d177980a92 --- /dev/null +++ b/templates/console/src/MyCompanyName.MyProjectName/appsettings.json @@ -0,0 +1,3 @@ +{ + +} diff --git a/templates/module/angular/package.json b/templates/module/angular/package.json index 4dffaa91c8..15c0eccdfd 100644 --- a/templates/module/angular/package.json +++ b/templates/module/angular/package.json @@ -15,11 +15,11 @@ }, "private": true, "dependencies": { - "@abp/ng.account": "~3.0.4", - "@abp/ng.identity": "~3.0.4", - "@abp/ng.setting-management": "~3.0.4", - "@abp/ng.tenant-management": "~3.0.4", - "@abp/ng.theme.basic": "~3.0.4", + "@abp/ng.account": "~3.0.5", + "@abp/ng.identity": "~3.0.5", + "@abp/ng.setting-management": "~3.0.5", + "@abp/ng.tenant-management": "~3.0.5", + "@abp/ng.theme.basic": "~3.0.5", "@angular/animations": "~10.0.0", "@angular/common": "~10.0.0", "@angular/compiler": "~10.0.0", diff --git a/templates/module/angular/projects/dev-app/src/environments/environment.prod.ts b/templates/module/angular/projects/dev-app/src/environments/environment.prod.ts index 9171185a9e..e7750a4873 100644 --- a/templates/module/angular/projects/dev-app/src/environments/environment.prod.ts +++ b/templates/module/angular/projects/dev-app/src/environments/environment.prod.ts @@ -1,6 +1,9 @@ +import { Config } from '@abp/ng.core'; + export const environment = { production: true, application: { + baseUrl: 'http://localhost:4200/', name: 'MyProjectName', logoUrl: '', }, @@ -9,7 +12,6 @@ export const environment = { clientId: 'MyProjectName_ConsoleTestApp', dummyClientSecret: '1q2w3e*', scope: 'MyProjectName', - showDebugInformation: true, oidc: false, requireHttps: true, }, @@ -21,4 +23,4 @@ export const environment = { url: 'https://localhost:44300', }, }, -}; +} as Config.Environment; diff --git a/templates/module/angular/projects/dev-app/src/environments/environment.ts b/templates/module/angular/projects/dev-app/src/environments/environment.ts index 452b154f4a..780fa891d9 100644 --- a/templates/module/angular/projects/dev-app/src/environments/environment.ts +++ b/templates/module/angular/projects/dev-app/src/environments/environment.ts @@ -1,6 +1,9 @@ +import { Config } from '@abp/ng.core'; + export const environment = { production: false, application: { + baseUrl: 'http://localhost:4200/', name: 'MyProjectName', logoUrl: '', }, @@ -9,7 +12,6 @@ export const environment = { clientId: 'MyProjectName_ConsoleTestApp', dummyClientSecret: '1q2w3e*', scope: 'MyProjectName', - showDebugInformation: true, oidc: false, requireHttps: true, }, @@ -21,4 +23,4 @@ export const environment = { url: 'https://localhost:44300', }, }, -}; +} as Config.Environment; diff --git a/templates/module/angular/projects/my-project-name/package.json b/templates/module/angular/projects/my-project-name/package.json index b95eed9618..b2b574a659 100644 --- a/templates/module/angular/projects/my-project-name/package.json +++ b/templates/module/angular/projects/my-project-name/package.json @@ -4,8 +4,8 @@ "peerDependencies": { "@angular/common": "^9.1.11", "@angular/core": "^9.1.11", - "@abp/ng.core": ">=3.0.4", - "@abp/ng.theme.shared": ">=3.0.4" + "@abp/ng.core": ">=3.0.5", + "@abp/ng.theme.shared": ">=3.0.5" }, "dependencies": { "tslib": "^2.0.0" diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/MyCompanyName.MyProjectName.HttpApi.Host.csproj b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/MyCompanyName.MyProjectName.HttpApi.Host.csproj index e6147da9e3..589ec5df65 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/MyCompanyName.MyProjectName.HttpApi.Host.csproj +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/MyCompanyName.MyProjectName.HttpApi.Host.csproj @@ -14,8 +14,8 @@ - - + + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations/Migrations/20200624023152_Initial.Designer.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/Migrations/20200810022322_Initial.Designer.cs similarity index 96% rename from templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations/Migrations/20200624023152_Initial.Designer.cs rename to templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/Migrations/20200810022322_Initial.Designer.cs index 174de5bc94..448351da20 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations/Migrations/20200624023152_Initial.Designer.cs +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/Migrations/20200810022322_Initial.Designer.cs @@ -10,8 +10,8 @@ using Volo.Abp.EntityFrameworkCore; namespace MyCompanyName.MyProjectName.Migrations { - [DbContext(typeof(MyProjectNameMigrationsDbContext))] - [Migration("20200624023152_Initial")] + [DbContext(typeof(IdentityServerHostMigrationsDbContext))] + [Migration("20200810022322_Initial")] partial class Initial { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -19,7 +19,7 @@ namespace MyCompanyName.MyProjectName.Migrations #pragma warning disable 612, 618 modelBuilder .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) - .HasAnnotation("ProductVersion", "3.1.5") + .HasAnnotation("ProductVersion", "3.1.6") .HasAnnotation("Relational:MaxIdentifierLength", 128) .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); @@ -275,95 +275,6 @@ namespace MyCompanyName.MyProjectName.Migrations b.ToTable("AbpEntityPropertyChanges"); }); - modelBuilder.Entity("Volo.Abp.BackgroundJobs.BackgroundJobRecord", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnName("ConcurrencyStamp") - .HasColumnType("nvarchar(40)") - .HasMaxLength(40); - - b.Property("CreationTime") - .HasColumnName("CreationTime") - .HasColumnType("datetime2"); - - b.Property("ExtraProperties") - .HasColumnName("ExtraProperties") - .HasColumnType("nvarchar(max)"); - - b.Property("IsAbandoned") - .ValueGeneratedOnAdd() - .HasColumnType("bit") - .HasDefaultValue(false); - - b.Property("JobArgs") - .IsRequired() - .HasColumnType("nvarchar(max)") - .HasMaxLength(1048576); - - b.Property("JobName") - .IsRequired() - .HasColumnType("nvarchar(128)") - .HasMaxLength(128); - - b.Property("LastTryTime") - .HasColumnType("datetime2"); - - b.Property("NextTryTime") - .HasColumnType("datetime2"); - - b.Property("Priority") - .ValueGeneratedOnAdd() - .HasColumnType("tinyint") - .HasDefaultValue((byte)15); - - b.Property("TryCount") - .ValueGeneratedOnAdd() - .HasColumnType("smallint") - .HasDefaultValue((short)0); - - b.HasKey("Id"); - - b.HasIndex("IsAbandoned", "NextTryTime"); - - b.ToTable("AbpBackgroundJobs"); - }); - - modelBuilder.Entity("Volo.Abp.FeatureManagement.FeatureValue", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("Name") - .IsRequired() - .HasColumnType("nvarchar(128)") - .HasMaxLength(128); - - b.Property("ProviderKey") - .HasColumnType("nvarchar(64)") - .HasMaxLength(64); - - b.Property("ProviderName") - .HasColumnType("nvarchar(64)") - .HasMaxLength(64); - - b.Property("Value") - .IsRequired() - .HasColumnType("nvarchar(128)") - .HasMaxLength(128); - - b.HasKey("Id"); - - b.HasIndex("Name", "ProviderName", "ProviderKey"); - - b.ToTable("AbpFeatureValues"); - }); - modelBuilder.Entity("Volo.Abp.Identity.IdentityClaimType", b => { b.Property("Id") @@ -488,6 +399,81 @@ namespace MyCompanyName.MyProjectName.Migrations b.ToTable("AbpRoleClaims"); }); + modelBuilder.Entity("Volo.Abp.Identity.IdentitySecurityLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Action") + .HasColumnType("nvarchar(96)") + .HasMaxLength(96); + + b.Property("ApplicationName") + .HasColumnType("nvarchar(96)") + .HasMaxLength(96); + + b.Property("BrowserInfo") + .HasColumnType("nvarchar(512)") + .HasMaxLength(512); + + b.Property("ClientId") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ClientIpAddress") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CorrelationId") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("CreationTime") + .HasColumnType("datetime2"); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("Identity") + .HasColumnType("nvarchar(96)") + .HasMaxLength(96); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantName") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Action"); + + b.HasIndex("TenantId", "ApplicationName"); + + b.HasIndex("TenantId", "Identity"); + + b.HasIndex("TenantId", "UserId"); + + b.ToTable("AbpSecurityLogs"); + }); + modelBuilder.Entity("Volo.Abp.Identity.IdentityUser", b => { b.Property("Id") @@ -544,6 +530,12 @@ namespace MyCompanyName.MyProjectName.Migrations .HasColumnType("bit") .HasDefaultValue(false); + b.Property("IsExternal") + .ValueGeneratedOnAdd() + .HasColumnName("IsExternal") + .HasColumnType("bit") + .HasDefaultValue(false); + b.Property("LastModificationTime") .HasColumnName("LastModificationTime") .HasColumnType("datetime2"); diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations/Migrations/20200624023152_Initial.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/Migrations/20200810022322_Initial.cs similarity index 96% rename from templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations/Migrations/20200624023152_Initial.cs rename to templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/Migrations/20200810022322_Initial.cs index b98f3342be..1ae2528b3a 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations/Migrations/20200624023152_Initial.cs +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/Migrations/20200810022322_Initial.cs @@ -39,27 +39,6 @@ namespace MyCompanyName.MyProjectName.Migrations table.PrimaryKey("PK_AbpAuditLogs", x => x.Id); }); - migrationBuilder.CreateTable( - name: "AbpBackgroundJobs", - columns: table => new - { - Id = table.Column(nullable: false), - ExtraProperties = table.Column(nullable: true), - ConcurrencyStamp = table.Column(maxLength: 40, nullable: true), - JobName = table.Column(maxLength: 128, nullable: false), - JobArgs = table.Column(maxLength: 1048576, nullable: false), - TryCount = table.Column(nullable: false, defaultValue: (short)0), - CreationTime = table.Column(nullable: false), - NextTryTime = table.Column(nullable: false), - LastTryTime = table.Column(nullable: true), - IsAbandoned = table.Column(nullable: false, defaultValue: false), - Priority = table.Column(nullable: false, defaultValue: (byte)15) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpBackgroundJobs", x => x.Id); - }); - migrationBuilder.CreateTable( name: "AbpClaimTypes", columns: table => new @@ -80,21 +59,6 @@ namespace MyCompanyName.MyProjectName.Migrations table.PrimaryKey("PK_AbpClaimTypes", x => x.Id); }); - migrationBuilder.CreateTable( - name: "AbpFeatureValues", - columns: table => new - { - Id = table.Column(nullable: false), - Name = table.Column(maxLength: 128, nullable: false), - Value = table.Column(maxLength: 128, nullable: false), - ProviderName = table.Column(maxLength: 64, nullable: true), - ProviderKey = table.Column(maxLength: 64, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpFeatureValues", x => x.Id); - }); - migrationBuilder.CreateTable( name: "AbpOrganizationUnits", columns: table => new @@ -159,6 +123,31 @@ namespace MyCompanyName.MyProjectName.Migrations table.PrimaryKey("PK_AbpRoles", x => x.Id); }); + migrationBuilder.CreateTable( + name: "AbpSecurityLogs", + columns: table => new + { + Id = table.Column(nullable: false), + ExtraProperties = table.Column(nullable: true), + ConcurrencyStamp = table.Column(maxLength: 40, nullable: true), + TenantId = table.Column(nullable: true), + ApplicationName = table.Column(maxLength: 96, nullable: true), + Identity = table.Column(maxLength: 96, nullable: true), + Action = table.Column(maxLength: 96, nullable: true), + UserId = table.Column(nullable: true), + UserName = table.Column(maxLength: 256, nullable: true), + TenantName = table.Column(maxLength: 64, nullable: true), + ClientId = table.Column(maxLength: 64, nullable: true), + CorrelationId = table.Column(maxLength: 64, nullable: true), + ClientIpAddress = table.Column(maxLength: 64, nullable: true), + BrowserInfo = table.Column(maxLength: 512, nullable: true), + CreationTime = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpSecurityLogs", x => x.Id); + }); + migrationBuilder.CreateTable( name: "AbpSettings", columns: table => new @@ -219,6 +208,7 @@ namespace MyCompanyName.MyProjectName.Migrations EmailConfirmed = table.Column(nullable: false, defaultValue: false), PasswordHash = table.Column(maxLength: 256, nullable: true), SecurityStamp = table.Column(maxLength: 256, nullable: false), + IsExternal = table.Column(nullable: false, defaultValue: false), PhoneNumber = table.Column(maxLength: 16, nullable: true), PhoneNumberConfirmed = table.Column(nullable: false, defaultValue: false), TwoFactorEnabled = table.Column(nullable: false, defaultValue: false), @@ -923,11 +913,6 @@ namespace MyCompanyName.MyProjectName.Migrations table: "AbpAuditLogs", columns: new[] { "TenantId", "UserId", "ExecutionTime" }); - migrationBuilder.CreateIndex( - name: "IX_AbpBackgroundJobs_IsAbandoned_NextTryTime", - table: "AbpBackgroundJobs", - columns: new[] { "IsAbandoned", "NextTryTime" }); - migrationBuilder.CreateIndex( name: "IX_AbpEntityChanges_AuditLogId", table: "AbpEntityChanges", @@ -943,11 +928,6 @@ namespace MyCompanyName.MyProjectName.Migrations table: "AbpEntityPropertyChanges", column: "EntityChangeId"); - migrationBuilder.CreateIndex( - name: "IX_AbpFeatureValues_Name_ProviderName_ProviderKey", - table: "AbpFeatureValues", - columns: new[] { "Name", "ProviderName", "ProviderKey" }); - migrationBuilder.CreateIndex( name: "IX_AbpOrganizationUnitRoles_RoleId_OrganizationUnitId", table: "AbpOrganizationUnitRoles", @@ -978,6 +958,26 @@ namespace MyCompanyName.MyProjectName.Migrations table: "AbpRoles", column: "NormalizedName"); + migrationBuilder.CreateIndex( + name: "IX_AbpSecurityLogs_TenantId_Action", + table: "AbpSecurityLogs", + columns: new[] { "TenantId", "Action" }); + + migrationBuilder.CreateIndex( + name: "IX_AbpSecurityLogs_TenantId_ApplicationName", + table: "AbpSecurityLogs", + columns: new[] { "TenantId", "ApplicationName" }); + + migrationBuilder.CreateIndex( + name: "IX_AbpSecurityLogs_TenantId_Identity", + table: "AbpSecurityLogs", + columns: new[] { "TenantId", "Identity" }); + + migrationBuilder.CreateIndex( + name: "IX_AbpSecurityLogs_TenantId_UserId", + table: "AbpSecurityLogs", + columns: new[] { "TenantId", "UserId" }); + migrationBuilder.CreateIndex( name: "IX_AbpSettings_Name_ProviderName_ProviderKey", table: "AbpSettings", @@ -1066,18 +1066,12 @@ namespace MyCompanyName.MyProjectName.Migrations migrationBuilder.DropTable( name: "AbpAuditLogActions"); - migrationBuilder.DropTable( - name: "AbpBackgroundJobs"); - migrationBuilder.DropTable( name: "AbpClaimTypes"); migrationBuilder.DropTable( name: "AbpEntityPropertyChanges"); - migrationBuilder.DropTable( - name: "AbpFeatureValues"); - migrationBuilder.DropTable( name: "AbpOrganizationUnitRoles"); @@ -1087,6 +1081,9 @@ namespace MyCompanyName.MyProjectName.Migrations migrationBuilder.DropTable( name: "AbpRoleClaims"); + migrationBuilder.DropTable( + name: "AbpSecurityLogs"); + migrationBuilder.DropTable( name: "AbpSettings"); diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/Migrations/IdentityServerHostMigrationsDbContextModelSnapshot.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/Migrations/IdentityServerHostMigrationsDbContextModelSnapshot.cs index 44c00e04d4..8cb7e3e88e 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/Migrations/IdentityServerHostMigrationsDbContextModelSnapshot.cs +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/Migrations/IdentityServerHostMigrationsDbContextModelSnapshot.cs @@ -17,7 +17,7 @@ namespace MyCompanyName.MyProjectName.Migrations #pragma warning disable 612, 618 modelBuilder .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) - .HasAnnotation("ProductVersion", "3.1.5") + .HasAnnotation("ProductVersion", "3.1.6") .HasAnnotation("Relational:MaxIdentifierLength", 128) .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); @@ -397,6 +397,81 @@ namespace MyCompanyName.MyProjectName.Migrations b.ToTable("AbpRoleClaims"); }); + modelBuilder.Entity("Volo.Abp.Identity.IdentitySecurityLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Action") + .HasColumnType("nvarchar(96)") + .HasMaxLength(96); + + b.Property("ApplicationName") + .HasColumnType("nvarchar(96)") + .HasMaxLength(96); + + b.Property("BrowserInfo") + .HasColumnType("nvarchar(512)") + .HasMaxLength(512); + + b.Property("ClientId") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ClientIpAddress") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CorrelationId") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("CreationTime") + .HasColumnType("datetime2"); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("Identity") + .HasColumnType("nvarchar(96)") + .HasMaxLength(96); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantName") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Action"); + + b.HasIndex("TenantId", "ApplicationName"); + + b.HasIndex("TenantId", "Identity"); + + b.HasIndex("TenantId", "UserId"); + + b.ToTable("AbpSecurityLogs"); + }); + modelBuilder.Entity("Volo.Abp.Identity.IdentityUser", b => { b.Property("Id") @@ -453,6 +528,12 @@ namespace MyCompanyName.MyProjectName.Migrations .HasColumnType("bit") .HasDefaultValue(false); + b.Property("IsExternal") + .ValueGeneratedOnAdd() + .HasColumnName("IsExternal") + .HasColumnType("bit") + .HasDefaultValue(false); + b.Property("LastModificationTime") .HasColumnName("LastModificationTime") .HasColumnType("datetime2"); diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/MyCompanyName.MyProjectName.IdentityServer.csproj b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/MyCompanyName.MyProjectName.IdentityServer.csproj index f5979f1512..17f9cdb076 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/MyCompanyName.MyProjectName.IdentityServer.csproj +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/MyCompanyName.MyProjectName.IdentityServer.csproj @@ -12,8 +12,8 @@ - - + + diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/package.json b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/package.json index f311232e23..55812dc3ab 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/package.json +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/package.json @@ -3,6 +3,6 @@ "name": "my-app-identityserver", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.basic": "^3.0.4" + "@abp/aspnetcore.mvc.ui.theme.basic": "^3.0.5" } } \ No newline at end of file diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/yarn.lock b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/yarn.lock index 63a96d93c8..a583172e87 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/yarn.lock +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/yarn.lock @@ -2,37 +2,37 @@ # yarn lockfile v1 -"@abp/aspnetcore.mvc.ui.theme.basic@^3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-3.0.4.tgz#c58e3e06ba78279875cf2b5f96d34b83ce09f6df" - integrity sha512-tKKqVRKpwN7P4Mn+he3bcz4Qbt8bkZ++wk6iOujd6HLTkBNa0uKyM4L6+iCn1Hi62S6HeK2Xytx+b+IYeIlsuw== - dependencies: - "@abp/aspnetcore.mvc.ui.theme.shared" "~3.0.4" - -"@abp/aspnetcore.mvc.ui.theme.shared@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-3.0.4.tgz#50db612b6492d36c63dd9042000a9fb00fecec54" - integrity sha512-imd2T5XXkNvGQyXV/A9hA80qSeurKaUcW6HDkte8Y2fs4QfvkjgzWXi7neqF6F+HVbYYPg3rIBWC6QF1OXGn2w== - dependencies: - "@abp/aspnetcore.mvc.ui" "~3.0.4" - "@abp/bootstrap" "~3.0.4" - "@abp/bootstrap-datepicker" "~3.0.4" - "@abp/datatables.net-bs4" "~3.0.4" - "@abp/font-awesome" "~3.0.4" - "@abp/jquery-form" "~3.0.4" - "@abp/jquery-validation-unobtrusive" "~3.0.4" - "@abp/lodash" "~3.0.4" - "@abp/luxon" "~3.0.4" - "@abp/malihu-custom-scrollbar-plugin" "~3.0.4" - "@abp/select2" "~3.0.4" - "@abp/sweetalert" "~3.0.4" - "@abp/timeago" "~3.0.4" - "@abp/toastr" "~3.0.4" - -"@abp/aspnetcore.mvc.ui@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-3.0.4.tgz#4e4344d95a98a60c53037b351cb915d8ff7a7dcb" - integrity sha512-p7KI5CKkpD4EYzG4fCtygrtZhwMu7Z+IZFGbdqXelLbI5ACkbJyrn+N+9trPfS9papymeXY4p1E3v6xOH7rSHQ== +"@abp/aspnetcore.mvc.ui.theme.basic@^3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-3.0.5.tgz#c146f20226522f0305cc0fa31cfcf9d39be78901" + integrity sha512-ptNhD4xV2wSLW7vQ5KUgaRiH5Ov1OscZW2ZEoivnt3kqgtIC3DGw+SiE9vjFCvyY83fjRkIlW2PIfQxSySbteQ== + dependencies: + "@abp/aspnetcore.mvc.ui.theme.shared" "~3.0.5" + +"@abp/aspnetcore.mvc.ui.theme.shared@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-3.0.5.tgz#5b370902cf229963f9aa10dd64482d57299dde76" + integrity sha512-iobAH39xrCdnqdaABaiYZg4lqNSOwPkMCLnyc+ytxOMbu/YnNKahXPmRhgd8dR/l+iQY1+6T4SWfLdGswi3gAg== + dependencies: + "@abp/aspnetcore.mvc.ui" "~3.0.5" + "@abp/bootstrap" "~3.0.5" + "@abp/bootstrap-datepicker" "~3.0.5" + "@abp/datatables.net-bs4" "~3.0.5" + "@abp/font-awesome" "~3.0.5" + "@abp/jquery-form" "~3.0.5" + "@abp/jquery-validation-unobtrusive" "~3.0.5" + "@abp/lodash" "~3.0.5" + "@abp/luxon" "~3.0.5" + "@abp/malihu-custom-scrollbar-plugin" "~3.0.5" + "@abp/select2" "~3.0.5" + "@abp/sweetalert" "~3.0.5" + "@abp/timeago" "~3.0.5" + "@abp/toastr" "~3.0.5" + +"@abp/aspnetcore.mvc.ui@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-3.0.5.tgz#a050eaee328c56a3730f5abbf25035adf612d6f0" + integrity sha512-/EOgjXTzR+I1OK9KA7L3yzQ2RGSDW97+n3cQrSdLAUF+tnRvMJS0nytDlSJSRK2hb8/clFMaofZHCC81aUkYUg== dependencies: ansi-colors "^4.1.1" extend-object "^1.0.0" @@ -41,144 +41,144 @@ path "^0.12.7" rimraf "^3.0.2" -"@abp/bootstrap-datepicker@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-3.0.4.tgz#d21963f158a4d348a0acdc46209e67378aae8e55" - integrity sha512-25e9qm39dj9AijIchQIdLlBiBM5/fhASkEuLKNt6M8S/e3qwKLwbRhX90DKMFvOyKdxxcK1gX7tDuGHyG9A8mA== +"@abp/bootstrap-datepicker@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-3.0.5.tgz#3a7fd86dbcadc31c11be5b2b31c8cf8ac3035540" + integrity sha512-vIY1wT4RHoJAPhVK11Q5MS71iZbwsmM/0VTBvvs04mjRSSi6xhdyRYkA+l+P49fnOwhZNaaK19XwQdVVScDuhA== dependencies: bootstrap-datepicker "^1.9.0" -"@abp/bootstrap@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-3.0.4.tgz#3b0e2b74dc174a04ce669de1b140df833eee6031" - integrity sha512-DwxL1fGYOwChPwmMpwUxCKiIdzTfRf/YDkOLUiwI8eRrpABBeuE2sSHKfks6Rqh9Wqo/nlWXvfccgDRqweqhsA== +"@abp/bootstrap@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-3.0.5.tgz#91f2c8fe6a57fc5789ae8fe9c206b2c159e55880" + integrity sha512-LdydeJIbw5k1k7FkWaI1lTectZeTXmEIoccJtj9Qx4N9yGt40wGsjz9SOvUdJ0IKAgntIWNQP8ohWiBPgzT7Xg== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" bootstrap "^4.5.0" bootstrap-v4-rtl "4.4.1-2" -"@abp/core@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/core/-/core-3.0.4.tgz#281940525e280e9e3a1aa4cb4e8f64548ea026a3" - integrity sha512-ZPEH1fa5bf/f1ek9ZLQHx7u78iF6kRZ8Z2NBI36cNL13RyQbx485qJF+KXyqNlJoJAh0CAs0NsAJADU3utcDNA== +"@abp/core@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/core/-/core-3.0.5.tgz#9a7bd990d02edc8128a5be3233b6fbb5669b15b8" + integrity sha512-LdkSkZQfN4hugcGNxBkje0+eQomivwNFbnAkMFQbvCwYLwKB2yvk7GCemEetW1piqvyrUGz4jqhtunjoXps5JA== dependencies: - "@abp/utils" "^3.0.4" + "@abp/utils" "^3.0.5" -"@abp/datatables.net-bs4@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs4/-/datatables.net-bs4-3.0.4.tgz#e488336bd2c199146ea60464e0ba75aeb134957e" - integrity sha512-ID1XWKPxG9pv6mxBkku7JWvgXjTxJ1uShxmYv5kFYVuwOGMSvj8QVZInTHFcMdECopV56yjGPNOIFLeQj4Mw8Q== +"@abp/datatables.net-bs4@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs4/-/datatables.net-bs4-3.0.5.tgz#9b79935970d2d4074994240b5deb01dff5987c0a" + integrity sha512-jIWRBEG/NlhaE43i6Xzr18XJ5aTWA+WWslf7WgNOArG5slGBbH4lLLja4HswLP3NGdSmzIyHJR1p9XQ82nBkbQ== dependencies: - "@abp/datatables.net" "~3.0.4" + "@abp/datatables.net" "~3.0.5" datatables.net-bs4 "^1.10.21" -"@abp/datatables.net@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-3.0.4.tgz#7ef143b83143ad8d769be25296411a812a17b255" - integrity sha512-gdQwRuxO5fQWhG/DiUaf40DQ8dWIVGhOCyt6SZ8LQzq4uSaQqa0qrtTTTW2/aiee4MgJu2vJWYF5YCszIR/HhA== +"@abp/datatables.net@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-3.0.5.tgz#0a0a39e8fa54522bf8a09ce76ff3927207a79491" + integrity sha512-HClQ2tcZuWBAChf9oOJOtMP1zHv8jqloPEcw3iqbhS/tWZBw5VQ4TO+BVfZsqHDXZNnWSmR6/2xiKsK32nliew== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" datatables.net "^1.10.21" -"@abp/font-awesome@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-3.0.4.tgz#98f83d0bf8b5a2a4d988cb07f5cceda2e1bc1a09" - integrity sha512-AKhKCUXx8vdHIgyXUfbSLitfYor5dJbo4qMRRGqIJVSj+cyMkpgYbGjqr24GGijgZxdmOGL+s1b9iCUbIPgLNQ== +"@abp/font-awesome@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-3.0.5.tgz#62d58c34f0abf268e30bdf788dfbd3f45b19b412" + integrity sha512-/+31Vtw1S9vvuNy/rN/Q6awkgUaYNNYXESgfvqI0pqIkvlPgigvS8E68mHqGzsxyRqdZisI6JGd94AF+vJD7sQ== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" "@fortawesome/fontawesome-free" "^5.13.0" -"@abp/jquery-form@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-3.0.4.tgz#022eebbfe58b55e328984c0f119884c9464bc767" - integrity sha512-xDkk4rY0AmXBJE4qztzr2hZz+OVucVNWeagOEgeQHXJSKU/T+0kj0a1RxEHpyI24xBvEXAbe8PbByTbu0QXYew== +"@abp/jquery-form@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-3.0.5.tgz#9d2ea9b2509448ee163a163298a7a09480865f78" + integrity sha512-RKgvQciaMml33NvzA28aYyfNI+WQHRfKLFWvOvqc8qO4C3BvyrnwepuzZcnI/9UsELKLSLoFrfk3rJHhxnTJhg== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" jquery-form "^4.3.0" -"@abp/jquery-validation-unobtrusive@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-3.0.4.tgz#6cc942aadcdde33893e7a45c84aa022e033e4e8b" - integrity sha512-N9sza91HCyPvJo+d0RAfq4skae3TgAYg7KXhlwzRE2hqUQS+EICVARURWyxJW7s7Zu6+gxDTkXhBZRTW56deZA== +"@abp/jquery-validation-unobtrusive@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-3.0.5.tgz#b8912e1c4cfdd4fdff6ad4aa7ee8693b55a3be1f" + integrity sha512-kPifDA+1ErbeQBwn+BzwtWuTHXzBke/EMCmITuj59plM/60gZ1ZfVm8EzzTmKS7iLaVVeOqTKf0vEQTobNYffw== dependencies: - "@abp/jquery-validation" "~3.0.4" + "@abp/jquery-validation" "~3.0.5" jquery-validation-unobtrusive "^3.2.11" -"@abp/jquery-validation@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-3.0.4.tgz#3f96d1f83c75a7fc47e5e5e12c51dbc8da7582b1" - integrity sha512-2ljqas4YKU0PcwB0wQW2qhXejDHjW+N1XCxZDyCZ2wF9UxGlKBR8rFt76TJByXSotKE7n2hJRZaOh9ICZUow2g== +"@abp/jquery-validation@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-3.0.5.tgz#55e36b3c832a35447fac69af312dedf1dee22279" + integrity sha512-fdqxc5CJ6TpUAF8+NEsKCmXvvZiOPR5bzczcfvfovxLOjvHvdEO74XPGjsjlBNTdaKMhmNGZRK4JRLUz7fvTng== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" jquery-validation "^1.19.2" -"@abp/jquery@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-3.0.4.tgz#5f236d95cfae7f65012cd8778fd74490b7542442" - integrity sha512-g8N8Te5vf+8djdksfZoZ3tXaqlBC4Ra39pAWU/XerlLl64u+XmAX1s6yKRfC2kS8lf292GgiFYuNOkb77J6k4A== +"@abp/jquery@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-3.0.5.tgz#2e43ac19a8df6e1e0220d6d4991d33a93d890754" + integrity sha512-2d2l+smWKAVP4/b6GSO+8rmJ7pFXiUewpBDwkHm5qgkK6n6UGqIcK9lIgiS2YQ6rmL6qdtuNhhqtC9GF2TF2kQ== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" jquery "~3.5.1" -"@abp/lodash@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-3.0.4.tgz#ee60bc99f98b59fd9fc5ff4bbcf914a13faf209d" - integrity sha512-jxvv74Sm/fUKDBMylK4Ne/8GKm7GuHAdcLFtkoJQptdO+RjZpKJ86ULT+X5vbKskZaTj2fEmOR6l4hmfnBULTA== +"@abp/lodash@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-3.0.5.tgz#d5d9f14095d76239bc0e6f9804c24d1f92ffcab4" + integrity sha512-+B2vxfPwxoIIb8Ac8eHi6OgAn0qbjmPZuun7/es0/ouYYTbEhvhTAD20X3PGZlHrIYeDyS2ansuXm8gJI+/BzA== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" lodash "^4.17.15" -"@abp/luxon@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-3.0.4.tgz#aa1bf6a93d6254433101e50a205b276759969414" - integrity sha512-gX9dQJt9C6M1wmCSUIBoNr8CAt0jkUZFQ7LKKO/BoADGM/XFvY5vDbhL7zcwbwitgCOzmucoCxs/6rIxSOVlrg== +"@abp/luxon@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-3.0.5.tgz#083042c939ea58bf719e35114a6c8efb07b68688" + integrity sha512-0UVHO2CO89t34O35OA3JzWGWLBpg+e1NQXSfVtK90nmg/QmzGkHVM9m7Su0jjLNBt3N5EA5OPOhriot3bCKItw== dependencies: luxon "^1.24.1" -"@abp/malihu-custom-scrollbar-plugin@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-3.0.4.tgz#c54824b98363f4190cc6a37fcd421c2bfdc1e201" - integrity sha512-6LdSGctOr4ACtdohdSlb19vrFNJ5C0hy1VdbBEafHOBspXckvRVx4uJl2KfHmAC+bMg9apoK/wcHjVNdBHThYQ== +"@abp/malihu-custom-scrollbar-plugin@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-3.0.5.tgz#f908289927a63fd76fb8f60b00a37747d9edbe39" + integrity sha512-3GoXA8VtGyZWKiqaPCipPXYLLM7h2Y5Oecq5c5YlMQyW2akv7uHiQQF2GZW/nVsFu3HBbGTOWpKRRmVk3PEO9Q== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" malihu-custom-scrollbar-plugin "^3.1.5" -"@abp/select2@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-3.0.4.tgz#49db0cfbc9b08f6ae4949b2cb8bdef92d7d308dd" - integrity sha512-zKEgkWBFdGU2KiAMUKhucHqBfzDy2KNSefyqxpOiUd1L3PEKu+9o23HZRWzDupYltop+SO1Jw33CNoVAkKm4+A== +"@abp/select2@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-3.0.5.tgz#79d7754713c64a6549da520dc6867ef3e3dd0350" + integrity sha512-VbeSHdAdDy8exrjplHMMhLVV4oG7Gq9zBb5M52f0sIkA4DkXFhksXHcHNoOCfhaKAXpenGA03Z9GJewZ3MTpMw== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" select2 "^4.0.13" -"@abp/sweetalert@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/sweetalert/-/sweetalert-3.0.4.tgz#f85d11c5aabc6ceb509af1b8fd8d75bc3c74bd27" - integrity sha512-hO0qXUWxeMy/lfy3sb+5HvOmIPst6UvYJJGy3ZQR6DKEdkOktBX9sEDb7m6ij6xAIHqWIx1xLveul0oGteOG0Q== +"@abp/sweetalert@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/sweetalert/-/sweetalert-3.0.5.tgz#e3b92d23cf3633983980b6255bf63c43d422ceab" + integrity sha512-OUt4ANgj14GESfRG7+UqdONS8daOu+8x2OZDWv98hriuX0uCVBjwB2J0jRI/n653C68Mi0yHixGONDv9OLy/lg== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" sweetalert "^2.1.2" -"@abp/timeago@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-3.0.4.tgz#82d90a04f703a7db9c0acedcd2f3cea684e7ae18" - integrity sha512-7SbbqScTKCix25xWy5oLT0slJqUQsW6YgJgI0N41C3AsByRBXZeh5Gcep033gprTPmpgNY8JOWcEcCXySnYvfA== +"@abp/timeago@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-3.0.5.tgz#b191a7c43f3dc77645fe27273f2f6619f7d4eb31" + integrity sha512-QhvAOU4C+Qxh/gFuCSGZtLU4KwVhdv/jtK8ovWyqbGvQMCCIimqa8BFKIz0H5XWIJhHwwWblkpjICWgjWh6uXw== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" timeago "^1.6.7" -"@abp/toastr@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/toastr/-/toastr-3.0.4.tgz#884ab2343ddd59981c291c632e3b0630532f7e9c" - integrity sha512-NG0pVD8bdIXGpjPW9cozCzmyuAdnSLE4vDYLUReP1pZFp9B8SMJqpgPk9eFg0Y06aaHTVMKYBKRoq9xXS4w9Xg== +"@abp/toastr@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/toastr/-/toastr-3.0.5.tgz#fc83b678f5be3a22040d0421ad9db5b2eac9d4fc" + integrity sha512-qgi2DEu6FYpN/DYcOZrmXsAr1HqcIKMhBWeXgjfoYUIl/K5+OuUgbQ69D/jdc0jz8HU9a4Cw7Q8hhe4CkgssEw== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" toastr "^2.1.4" -"@abp/utils@^3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-3.0.4.tgz#38068fa8f735e1889cf2f226128366825948814d" - integrity sha512-TnXRZIZcxv5+EctEPsZEi2cyRXbpl3A679wzQa6BACv+pM45cjGO1JR8P0VswKN4TVL0aQRMv26N6U/uie8sEQ== +"@abp/utils@^3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-3.0.5.tgz#66b3da953500e73ff1f23d1b41168cde8216ce49" + integrity sha512-X9V1xLIEazItaf93nFSA4p94WT6QK3yA4eIyvU7GyyEkkOQdaakNwPbRwy930sd+I+/oXnIqM1vDsJu9QDhgvg== dependencies: just-compare "^1.3.0" diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Host/MyCompanyName.MyProjectName.Web.Host.csproj b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Host/MyCompanyName.MyProjectName.Web.Host.csproj index 87e5d62c11..3923a71691 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Host/MyCompanyName.MyProjectName.Web.Host.csproj +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Host/MyCompanyName.MyProjectName.Web.Host.csproj @@ -13,8 +13,8 @@ - - + + @@ -28,7 +28,7 @@ - + @@ -52,5 +52,5 @@ Always - + diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Host/package.json b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Host/package.json index 528a8910e5..5ffe5c0500 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Host/package.json +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Host/package.json @@ -3,6 +3,6 @@ "name": "my-app", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.basic": "^3.0.4" + "@abp/aspnetcore.mvc.ui.theme.basic": "^3.0.5" } } \ No newline at end of file diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Host/yarn.lock b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Host/yarn.lock index 63a96d93c8..a583172e87 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Host/yarn.lock +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Host/yarn.lock @@ -2,37 +2,37 @@ # yarn lockfile v1 -"@abp/aspnetcore.mvc.ui.theme.basic@^3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-3.0.4.tgz#c58e3e06ba78279875cf2b5f96d34b83ce09f6df" - integrity sha512-tKKqVRKpwN7P4Mn+he3bcz4Qbt8bkZ++wk6iOujd6HLTkBNa0uKyM4L6+iCn1Hi62S6HeK2Xytx+b+IYeIlsuw== - dependencies: - "@abp/aspnetcore.mvc.ui.theme.shared" "~3.0.4" - -"@abp/aspnetcore.mvc.ui.theme.shared@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-3.0.4.tgz#50db612b6492d36c63dd9042000a9fb00fecec54" - integrity sha512-imd2T5XXkNvGQyXV/A9hA80qSeurKaUcW6HDkte8Y2fs4QfvkjgzWXi7neqF6F+HVbYYPg3rIBWC6QF1OXGn2w== - dependencies: - "@abp/aspnetcore.mvc.ui" "~3.0.4" - "@abp/bootstrap" "~3.0.4" - "@abp/bootstrap-datepicker" "~3.0.4" - "@abp/datatables.net-bs4" "~3.0.4" - "@abp/font-awesome" "~3.0.4" - "@abp/jquery-form" "~3.0.4" - "@abp/jquery-validation-unobtrusive" "~3.0.4" - "@abp/lodash" "~3.0.4" - "@abp/luxon" "~3.0.4" - "@abp/malihu-custom-scrollbar-plugin" "~3.0.4" - "@abp/select2" "~3.0.4" - "@abp/sweetalert" "~3.0.4" - "@abp/timeago" "~3.0.4" - "@abp/toastr" "~3.0.4" - -"@abp/aspnetcore.mvc.ui@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-3.0.4.tgz#4e4344d95a98a60c53037b351cb915d8ff7a7dcb" - integrity sha512-p7KI5CKkpD4EYzG4fCtygrtZhwMu7Z+IZFGbdqXelLbI5ACkbJyrn+N+9trPfS9papymeXY4p1E3v6xOH7rSHQ== +"@abp/aspnetcore.mvc.ui.theme.basic@^3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-3.0.5.tgz#c146f20226522f0305cc0fa31cfcf9d39be78901" + integrity sha512-ptNhD4xV2wSLW7vQ5KUgaRiH5Ov1OscZW2ZEoivnt3kqgtIC3DGw+SiE9vjFCvyY83fjRkIlW2PIfQxSySbteQ== + dependencies: + "@abp/aspnetcore.mvc.ui.theme.shared" "~3.0.5" + +"@abp/aspnetcore.mvc.ui.theme.shared@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-3.0.5.tgz#5b370902cf229963f9aa10dd64482d57299dde76" + integrity sha512-iobAH39xrCdnqdaABaiYZg4lqNSOwPkMCLnyc+ytxOMbu/YnNKahXPmRhgd8dR/l+iQY1+6T4SWfLdGswi3gAg== + dependencies: + "@abp/aspnetcore.mvc.ui" "~3.0.5" + "@abp/bootstrap" "~3.0.5" + "@abp/bootstrap-datepicker" "~3.0.5" + "@abp/datatables.net-bs4" "~3.0.5" + "@abp/font-awesome" "~3.0.5" + "@abp/jquery-form" "~3.0.5" + "@abp/jquery-validation-unobtrusive" "~3.0.5" + "@abp/lodash" "~3.0.5" + "@abp/luxon" "~3.0.5" + "@abp/malihu-custom-scrollbar-plugin" "~3.0.5" + "@abp/select2" "~3.0.5" + "@abp/sweetalert" "~3.0.5" + "@abp/timeago" "~3.0.5" + "@abp/toastr" "~3.0.5" + +"@abp/aspnetcore.mvc.ui@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-3.0.5.tgz#a050eaee328c56a3730f5abbf25035adf612d6f0" + integrity sha512-/EOgjXTzR+I1OK9KA7L3yzQ2RGSDW97+n3cQrSdLAUF+tnRvMJS0nytDlSJSRK2hb8/clFMaofZHCC81aUkYUg== dependencies: ansi-colors "^4.1.1" extend-object "^1.0.0" @@ -41,144 +41,144 @@ path "^0.12.7" rimraf "^3.0.2" -"@abp/bootstrap-datepicker@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-3.0.4.tgz#d21963f158a4d348a0acdc46209e67378aae8e55" - integrity sha512-25e9qm39dj9AijIchQIdLlBiBM5/fhASkEuLKNt6M8S/e3qwKLwbRhX90DKMFvOyKdxxcK1gX7tDuGHyG9A8mA== +"@abp/bootstrap-datepicker@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-3.0.5.tgz#3a7fd86dbcadc31c11be5b2b31c8cf8ac3035540" + integrity sha512-vIY1wT4RHoJAPhVK11Q5MS71iZbwsmM/0VTBvvs04mjRSSi6xhdyRYkA+l+P49fnOwhZNaaK19XwQdVVScDuhA== dependencies: bootstrap-datepicker "^1.9.0" -"@abp/bootstrap@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-3.0.4.tgz#3b0e2b74dc174a04ce669de1b140df833eee6031" - integrity sha512-DwxL1fGYOwChPwmMpwUxCKiIdzTfRf/YDkOLUiwI8eRrpABBeuE2sSHKfks6Rqh9Wqo/nlWXvfccgDRqweqhsA== +"@abp/bootstrap@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-3.0.5.tgz#91f2c8fe6a57fc5789ae8fe9c206b2c159e55880" + integrity sha512-LdydeJIbw5k1k7FkWaI1lTectZeTXmEIoccJtj9Qx4N9yGt40wGsjz9SOvUdJ0IKAgntIWNQP8ohWiBPgzT7Xg== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" bootstrap "^4.5.0" bootstrap-v4-rtl "4.4.1-2" -"@abp/core@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/core/-/core-3.0.4.tgz#281940525e280e9e3a1aa4cb4e8f64548ea026a3" - integrity sha512-ZPEH1fa5bf/f1ek9ZLQHx7u78iF6kRZ8Z2NBI36cNL13RyQbx485qJF+KXyqNlJoJAh0CAs0NsAJADU3utcDNA== +"@abp/core@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/core/-/core-3.0.5.tgz#9a7bd990d02edc8128a5be3233b6fbb5669b15b8" + integrity sha512-LdkSkZQfN4hugcGNxBkje0+eQomivwNFbnAkMFQbvCwYLwKB2yvk7GCemEetW1piqvyrUGz4jqhtunjoXps5JA== dependencies: - "@abp/utils" "^3.0.4" + "@abp/utils" "^3.0.5" -"@abp/datatables.net-bs4@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs4/-/datatables.net-bs4-3.0.4.tgz#e488336bd2c199146ea60464e0ba75aeb134957e" - integrity sha512-ID1XWKPxG9pv6mxBkku7JWvgXjTxJ1uShxmYv5kFYVuwOGMSvj8QVZInTHFcMdECopV56yjGPNOIFLeQj4Mw8Q== +"@abp/datatables.net-bs4@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs4/-/datatables.net-bs4-3.0.5.tgz#9b79935970d2d4074994240b5deb01dff5987c0a" + integrity sha512-jIWRBEG/NlhaE43i6Xzr18XJ5aTWA+WWslf7WgNOArG5slGBbH4lLLja4HswLP3NGdSmzIyHJR1p9XQ82nBkbQ== dependencies: - "@abp/datatables.net" "~3.0.4" + "@abp/datatables.net" "~3.0.5" datatables.net-bs4 "^1.10.21" -"@abp/datatables.net@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-3.0.4.tgz#7ef143b83143ad8d769be25296411a812a17b255" - integrity sha512-gdQwRuxO5fQWhG/DiUaf40DQ8dWIVGhOCyt6SZ8LQzq4uSaQqa0qrtTTTW2/aiee4MgJu2vJWYF5YCszIR/HhA== +"@abp/datatables.net@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-3.0.5.tgz#0a0a39e8fa54522bf8a09ce76ff3927207a79491" + integrity sha512-HClQ2tcZuWBAChf9oOJOtMP1zHv8jqloPEcw3iqbhS/tWZBw5VQ4TO+BVfZsqHDXZNnWSmR6/2xiKsK32nliew== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" datatables.net "^1.10.21" -"@abp/font-awesome@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-3.0.4.tgz#98f83d0bf8b5a2a4d988cb07f5cceda2e1bc1a09" - integrity sha512-AKhKCUXx8vdHIgyXUfbSLitfYor5dJbo4qMRRGqIJVSj+cyMkpgYbGjqr24GGijgZxdmOGL+s1b9iCUbIPgLNQ== +"@abp/font-awesome@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-3.0.5.tgz#62d58c34f0abf268e30bdf788dfbd3f45b19b412" + integrity sha512-/+31Vtw1S9vvuNy/rN/Q6awkgUaYNNYXESgfvqI0pqIkvlPgigvS8E68mHqGzsxyRqdZisI6JGd94AF+vJD7sQ== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" "@fortawesome/fontawesome-free" "^5.13.0" -"@abp/jquery-form@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-3.0.4.tgz#022eebbfe58b55e328984c0f119884c9464bc767" - integrity sha512-xDkk4rY0AmXBJE4qztzr2hZz+OVucVNWeagOEgeQHXJSKU/T+0kj0a1RxEHpyI24xBvEXAbe8PbByTbu0QXYew== +"@abp/jquery-form@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-3.0.5.tgz#9d2ea9b2509448ee163a163298a7a09480865f78" + integrity sha512-RKgvQciaMml33NvzA28aYyfNI+WQHRfKLFWvOvqc8qO4C3BvyrnwepuzZcnI/9UsELKLSLoFrfk3rJHhxnTJhg== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" jquery-form "^4.3.0" -"@abp/jquery-validation-unobtrusive@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-3.0.4.tgz#6cc942aadcdde33893e7a45c84aa022e033e4e8b" - integrity sha512-N9sza91HCyPvJo+d0RAfq4skae3TgAYg7KXhlwzRE2hqUQS+EICVARURWyxJW7s7Zu6+gxDTkXhBZRTW56deZA== +"@abp/jquery-validation-unobtrusive@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-3.0.5.tgz#b8912e1c4cfdd4fdff6ad4aa7ee8693b55a3be1f" + integrity sha512-kPifDA+1ErbeQBwn+BzwtWuTHXzBke/EMCmITuj59plM/60gZ1ZfVm8EzzTmKS7iLaVVeOqTKf0vEQTobNYffw== dependencies: - "@abp/jquery-validation" "~3.0.4" + "@abp/jquery-validation" "~3.0.5" jquery-validation-unobtrusive "^3.2.11" -"@abp/jquery-validation@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-3.0.4.tgz#3f96d1f83c75a7fc47e5e5e12c51dbc8da7582b1" - integrity sha512-2ljqas4YKU0PcwB0wQW2qhXejDHjW+N1XCxZDyCZ2wF9UxGlKBR8rFt76TJByXSotKE7n2hJRZaOh9ICZUow2g== +"@abp/jquery-validation@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-3.0.5.tgz#55e36b3c832a35447fac69af312dedf1dee22279" + integrity sha512-fdqxc5CJ6TpUAF8+NEsKCmXvvZiOPR5bzczcfvfovxLOjvHvdEO74XPGjsjlBNTdaKMhmNGZRK4JRLUz7fvTng== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" jquery-validation "^1.19.2" -"@abp/jquery@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-3.0.4.tgz#5f236d95cfae7f65012cd8778fd74490b7542442" - integrity sha512-g8N8Te5vf+8djdksfZoZ3tXaqlBC4Ra39pAWU/XerlLl64u+XmAX1s6yKRfC2kS8lf292GgiFYuNOkb77J6k4A== +"@abp/jquery@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-3.0.5.tgz#2e43ac19a8df6e1e0220d6d4991d33a93d890754" + integrity sha512-2d2l+smWKAVP4/b6GSO+8rmJ7pFXiUewpBDwkHm5qgkK6n6UGqIcK9lIgiS2YQ6rmL6qdtuNhhqtC9GF2TF2kQ== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" jquery "~3.5.1" -"@abp/lodash@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-3.0.4.tgz#ee60bc99f98b59fd9fc5ff4bbcf914a13faf209d" - integrity sha512-jxvv74Sm/fUKDBMylK4Ne/8GKm7GuHAdcLFtkoJQptdO+RjZpKJ86ULT+X5vbKskZaTj2fEmOR6l4hmfnBULTA== +"@abp/lodash@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-3.0.5.tgz#d5d9f14095d76239bc0e6f9804c24d1f92ffcab4" + integrity sha512-+B2vxfPwxoIIb8Ac8eHi6OgAn0qbjmPZuun7/es0/ouYYTbEhvhTAD20X3PGZlHrIYeDyS2ansuXm8gJI+/BzA== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" lodash "^4.17.15" -"@abp/luxon@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-3.0.4.tgz#aa1bf6a93d6254433101e50a205b276759969414" - integrity sha512-gX9dQJt9C6M1wmCSUIBoNr8CAt0jkUZFQ7LKKO/BoADGM/XFvY5vDbhL7zcwbwitgCOzmucoCxs/6rIxSOVlrg== +"@abp/luxon@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-3.0.5.tgz#083042c939ea58bf719e35114a6c8efb07b68688" + integrity sha512-0UVHO2CO89t34O35OA3JzWGWLBpg+e1NQXSfVtK90nmg/QmzGkHVM9m7Su0jjLNBt3N5EA5OPOhriot3bCKItw== dependencies: luxon "^1.24.1" -"@abp/malihu-custom-scrollbar-plugin@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-3.0.4.tgz#c54824b98363f4190cc6a37fcd421c2bfdc1e201" - integrity sha512-6LdSGctOr4ACtdohdSlb19vrFNJ5C0hy1VdbBEafHOBspXckvRVx4uJl2KfHmAC+bMg9apoK/wcHjVNdBHThYQ== +"@abp/malihu-custom-scrollbar-plugin@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-3.0.5.tgz#f908289927a63fd76fb8f60b00a37747d9edbe39" + integrity sha512-3GoXA8VtGyZWKiqaPCipPXYLLM7h2Y5Oecq5c5YlMQyW2akv7uHiQQF2GZW/nVsFu3HBbGTOWpKRRmVk3PEO9Q== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" malihu-custom-scrollbar-plugin "^3.1.5" -"@abp/select2@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-3.0.4.tgz#49db0cfbc9b08f6ae4949b2cb8bdef92d7d308dd" - integrity sha512-zKEgkWBFdGU2KiAMUKhucHqBfzDy2KNSefyqxpOiUd1L3PEKu+9o23HZRWzDupYltop+SO1Jw33CNoVAkKm4+A== +"@abp/select2@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-3.0.5.tgz#79d7754713c64a6549da520dc6867ef3e3dd0350" + integrity sha512-VbeSHdAdDy8exrjplHMMhLVV4oG7Gq9zBb5M52f0sIkA4DkXFhksXHcHNoOCfhaKAXpenGA03Z9GJewZ3MTpMw== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" select2 "^4.0.13" -"@abp/sweetalert@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/sweetalert/-/sweetalert-3.0.4.tgz#f85d11c5aabc6ceb509af1b8fd8d75bc3c74bd27" - integrity sha512-hO0qXUWxeMy/lfy3sb+5HvOmIPst6UvYJJGy3ZQR6DKEdkOktBX9sEDb7m6ij6xAIHqWIx1xLveul0oGteOG0Q== +"@abp/sweetalert@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/sweetalert/-/sweetalert-3.0.5.tgz#e3b92d23cf3633983980b6255bf63c43d422ceab" + integrity sha512-OUt4ANgj14GESfRG7+UqdONS8daOu+8x2OZDWv98hriuX0uCVBjwB2J0jRI/n653C68Mi0yHixGONDv9OLy/lg== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" sweetalert "^2.1.2" -"@abp/timeago@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-3.0.4.tgz#82d90a04f703a7db9c0acedcd2f3cea684e7ae18" - integrity sha512-7SbbqScTKCix25xWy5oLT0slJqUQsW6YgJgI0N41C3AsByRBXZeh5Gcep033gprTPmpgNY8JOWcEcCXySnYvfA== +"@abp/timeago@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-3.0.5.tgz#b191a7c43f3dc77645fe27273f2f6619f7d4eb31" + integrity sha512-QhvAOU4C+Qxh/gFuCSGZtLU4KwVhdv/jtK8ovWyqbGvQMCCIimqa8BFKIz0H5XWIJhHwwWblkpjICWgjWh6uXw== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" timeago "^1.6.7" -"@abp/toastr@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/toastr/-/toastr-3.0.4.tgz#884ab2343ddd59981c291c632e3b0630532f7e9c" - integrity sha512-NG0pVD8bdIXGpjPW9cozCzmyuAdnSLE4vDYLUReP1pZFp9B8SMJqpgPk9eFg0Y06aaHTVMKYBKRoq9xXS4w9Xg== +"@abp/toastr@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/toastr/-/toastr-3.0.5.tgz#fc83b678f5be3a22040d0421ad9db5b2eac9d4fc" + integrity sha512-qgi2DEu6FYpN/DYcOZrmXsAr1HqcIKMhBWeXgjfoYUIl/K5+OuUgbQ69D/jdc0jz8HU9a4Cw7Q8hhe4CkgssEw== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" toastr "^2.1.4" -"@abp/utils@^3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-3.0.4.tgz#38068fa8f735e1889cf2f226128366825948814d" - integrity sha512-TnXRZIZcxv5+EctEPsZEi2cyRXbpl3A679wzQa6BACv+pM45cjGO1JR8P0VswKN4TVL0aQRMv26N6U/uie8sEQ== +"@abp/utils@^3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-3.0.5.tgz#66b3da953500e73ff1f23d1b41168cde8216ce49" + integrity sha512-X9V1xLIEazItaf93nFSA4p94WT6QK3yA4eIyvU7GyyEkkOQdaakNwPbRwy930sd+I+/oXnIqM1vDsJu9QDhgvg== dependencies: just-compare "^1.3.0" diff --git a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200715191733_Added_UserReactions.Designer.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/20200810022405_Initial.Designer.cs similarity index 93% rename from modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200715191733_Added_UserReactions.Designer.cs rename to templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/20200810022405_Initial.Designer.cs index c5246843c6..9fa3f31af0 100644 --- a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200715191733_Added_UserReactions.Designer.cs +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/20200810022405_Initial.Designer.cs @@ -5,21 +5,21 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using MyCompanyName.MyProjectName.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore; -using Volo.CmsKit.EntityFrameworkCore; -namespace Volo.CmsKit.Migrations +namespace MyCompanyName.MyProjectName.Migrations { [DbContext(typeof(UnifiedDbContext))] - [Migration("20200715191733_Added_UserReactions")] - partial class Added_UserReactions + [Migration("20200810022405_Initial")] + partial class Initial { protected override void BuildTargetModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) - .HasAnnotation("ProductVersion", "3.1.5") + .HasAnnotation("ProductVersion", "3.1.6") .HasAnnotation("Relational:MaxIdentifierLength", 128) .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); @@ -399,6 +399,81 @@ namespace Volo.CmsKit.Migrations b.ToTable("AbpRoleClaims"); }); + modelBuilder.Entity("Volo.Abp.Identity.IdentitySecurityLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Action") + .HasColumnType("nvarchar(96)") + .HasMaxLength(96); + + b.Property("ApplicationName") + .HasColumnType("nvarchar(96)") + .HasMaxLength(96); + + b.Property("BrowserInfo") + .HasColumnType("nvarchar(512)") + .HasMaxLength(512); + + b.Property("ClientId") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ClientIpAddress") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CorrelationId") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("CreationTime") + .HasColumnType("datetime2"); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("Identity") + .HasColumnType("nvarchar(96)") + .HasMaxLength(96); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantName") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Action"); + + b.HasIndex("TenantId", "ApplicationName"); + + b.HasIndex("TenantId", "Identity"); + + b.HasIndex("TenantId", "UserId"); + + b.ToTable("AbpSecurityLogs"); + }); + modelBuilder.Entity("Volo.Abp.Identity.IdentityUser", b => { b.Property("Id") @@ -455,6 +530,12 @@ namespace Volo.CmsKit.Migrations .HasColumnType("bit") .HasDefaultValue(false); + b.Property("IsExternal") + .ValueGeneratedOnAdd() + .HasColumnName("IsExternal") + .HasColumnType("bit") + .HasDefaultValue(false); + b.Property("LastModificationTime") .HasColumnName("LastModificationTime") .HasColumnType("datetime2"); @@ -914,42 +995,6 @@ namespace Volo.CmsKit.Migrations b.ToTable("AbpTenantConnectionStrings"); }); - modelBuilder.Entity("Volo.CmsKit.Reactions.UserReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("CreationTime") - .HasColumnType("datetime2"); - - b.Property("EntityId") - .IsRequired() - .HasColumnType("nvarchar(64)") - .HasMaxLength(64); - - b.Property("EntityType") - .IsRequired() - .HasColumnType("nvarchar(64)") - .HasMaxLength(64); - - b.Property("ReactionName") - .IsRequired() - .HasColumnType("nvarchar(32)") - .HasMaxLength(32); - - b.Property("UserId") - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.HasIndex("EntityType", "EntityId"); - - b.HasIndex("UserId", "EntityType", "EntityId", "ReactionName"); - - b.ToTable("CmsUserReactions"); - }); - modelBuilder.Entity("Volo.Abp.AuditLogging.AuditLogAction", b => { b.HasOne("Volo.Abp.AuditLogging.AuditLog", null) diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/20200624023340_Initial.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/20200810022405_Initial.cs similarity index 92% rename from templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/20200624023340_Initial.cs rename to templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/20200810022405_Initial.cs index ad7940fa81..2e3df9b82a 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/20200624023340_Initial.cs +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/20200810022405_Initial.cs @@ -123,6 +123,31 @@ namespace MyCompanyName.MyProjectName.Migrations table.PrimaryKey("PK_AbpRoles", x => x.Id); }); + migrationBuilder.CreateTable( + name: "AbpSecurityLogs", + columns: table => new + { + Id = table.Column(nullable: false), + ExtraProperties = table.Column(nullable: true), + ConcurrencyStamp = table.Column(maxLength: 40, nullable: true), + TenantId = table.Column(nullable: true), + ApplicationName = table.Column(maxLength: 96, nullable: true), + Identity = table.Column(maxLength: 96, nullable: true), + Action = table.Column(maxLength: 96, nullable: true), + UserId = table.Column(nullable: true), + UserName = table.Column(maxLength: 256, nullable: true), + TenantName = table.Column(maxLength: 64, nullable: true), + ClientId = table.Column(maxLength: 64, nullable: true), + CorrelationId = table.Column(maxLength: 64, nullable: true), + ClientIpAddress = table.Column(maxLength: 64, nullable: true), + BrowserInfo = table.Column(maxLength: 512, nullable: true), + CreationTime = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpSecurityLogs", x => x.Id); + }); + migrationBuilder.CreateTable( name: "AbpSettings", columns: table => new @@ -183,6 +208,7 @@ namespace MyCompanyName.MyProjectName.Migrations EmailConfirmed = table.Column(nullable: false, defaultValue: false), PasswordHash = table.Column(maxLength: 256, nullable: true), SecurityStamp = table.Column(maxLength: 256, nullable: false), + IsExternal = table.Column(nullable: false, defaultValue: false), PhoneNumber = table.Column(maxLength: 16, nullable: true), PhoneNumberConfirmed = table.Column(nullable: false, defaultValue: false), TwoFactorEnabled = table.Column(nullable: false, defaultValue: false), @@ -515,6 +541,26 @@ namespace MyCompanyName.MyProjectName.Migrations table: "AbpRoles", column: "NormalizedName"); + migrationBuilder.CreateIndex( + name: "IX_AbpSecurityLogs_TenantId_Action", + table: "AbpSecurityLogs", + columns: new[] { "TenantId", "Action" }); + + migrationBuilder.CreateIndex( + name: "IX_AbpSecurityLogs_TenantId_ApplicationName", + table: "AbpSecurityLogs", + columns: new[] { "TenantId", "ApplicationName" }); + + migrationBuilder.CreateIndex( + name: "IX_AbpSecurityLogs_TenantId_Identity", + table: "AbpSecurityLogs", + columns: new[] { "TenantId", "Identity" }); + + migrationBuilder.CreateIndex( + name: "IX_AbpSecurityLogs_TenantId_UserId", + table: "AbpSecurityLogs", + columns: new[] { "TenantId", "UserId" }); + migrationBuilder.CreateIndex( name: "IX_AbpSettings_Name_ProviderName_ProviderKey", table: "AbpSettings", @@ -586,6 +632,9 @@ namespace MyCompanyName.MyProjectName.Migrations migrationBuilder.DropTable( name: "AbpRoleClaims"); + migrationBuilder.DropTable( + name: "AbpSecurityLogs"); + migrationBuilder.DropTable( name: "AbpSettings"); diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/UnifiedDbContextModelSnapshot.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/UnifiedDbContextModelSnapshot.cs index 157b210fc4..4d47e35a00 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/UnifiedDbContextModelSnapshot.cs +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/UnifiedDbContextModelSnapshot.cs @@ -17,7 +17,7 @@ namespace MyCompanyName.MyProjectName.Migrations #pragma warning disable 612, 618 modelBuilder .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) - .HasAnnotation("ProductVersion", "3.1.5") + .HasAnnotation("ProductVersion", "3.1.6") .HasAnnotation("Relational:MaxIdentifierLength", 128) .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); @@ -397,6 +397,81 @@ namespace MyCompanyName.MyProjectName.Migrations b.ToTable("AbpRoleClaims"); }); + modelBuilder.Entity("Volo.Abp.Identity.IdentitySecurityLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Action") + .HasColumnType("nvarchar(96)") + .HasMaxLength(96); + + b.Property("ApplicationName") + .HasColumnType("nvarchar(96)") + .HasMaxLength(96); + + b.Property("BrowserInfo") + .HasColumnType("nvarchar(512)") + .HasMaxLength(512); + + b.Property("ClientId") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ClientIpAddress") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CorrelationId") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("CreationTime") + .HasColumnType("datetime2"); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("Identity") + .HasColumnType("nvarchar(96)") + .HasMaxLength(96); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantName") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Action"); + + b.HasIndex("TenantId", "ApplicationName"); + + b.HasIndex("TenantId", "Identity"); + + b.HasIndex("TenantId", "UserId"); + + b.ToTable("AbpSecurityLogs"); + }); + modelBuilder.Entity("Volo.Abp.Identity.IdentityUser", b => { b.Property("Id") @@ -453,6 +528,12 @@ namespace MyCompanyName.MyProjectName.Migrations .HasColumnType("bit") .HasDefaultValue(false); + b.Property("IsExternal") + .ValueGeneratedOnAdd() + .HasColumnName("IsExternal") + .HasColumnType("bit") + .HasDefaultValue(false); + b.Property("LastModificationTime") .HasColumnName("LastModificationTime") .HasColumnType("datetime2"); diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/MyCompanyName.MyProjectName.Web.Unified.csproj b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/MyCompanyName.MyProjectName.Web.Unified.csproj index ba4f90ce29..3cd14d2086 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/MyCompanyName.MyProjectName.Web.Unified.csproj +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/MyCompanyName.MyProjectName.Web.Unified.csproj @@ -13,7 +13,7 @@ - + diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/package.json b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/package.json index 528a8910e5..5ffe5c0500 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/package.json +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/package.json @@ -3,6 +3,6 @@ "name": "my-app", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.basic": "^3.0.4" + "@abp/aspnetcore.mvc.ui.theme.basic": "^3.0.5" } } \ No newline at end of file diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/yarn.lock b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/yarn.lock index bab58d97f9..3261b074f0 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/yarn.lock +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/yarn.lock @@ -2,37 +2,37 @@ # yarn lockfile v1 -"@abp/aspnetcore.mvc.ui.theme.basic@^3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-3.0.4.tgz#c58e3e06ba78279875cf2b5f96d34b83ce09f6df" - integrity sha512-tKKqVRKpwN7P4Mn+he3bcz4Qbt8bkZ++wk6iOujd6HLTkBNa0uKyM4L6+iCn1Hi62S6HeK2Xytx+b+IYeIlsuw== - dependencies: - "@abp/aspnetcore.mvc.ui.theme.shared" "~3.0.4" - -"@abp/aspnetcore.mvc.ui.theme.shared@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-3.0.4.tgz#50db612b6492d36c63dd9042000a9fb00fecec54" - integrity sha512-imd2T5XXkNvGQyXV/A9hA80qSeurKaUcW6HDkte8Y2fs4QfvkjgzWXi7neqF6F+HVbYYPg3rIBWC6QF1OXGn2w== - dependencies: - "@abp/aspnetcore.mvc.ui" "~3.0.4" - "@abp/bootstrap" "~3.0.4" - "@abp/bootstrap-datepicker" "~3.0.4" - "@abp/datatables.net-bs4" "~3.0.4" - "@abp/font-awesome" "~3.0.4" - "@abp/jquery-form" "~3.0.4" - "@abp/jquery-validation-unobtrusive" "~3.0.4" - "@abp/lodash" "~3.0.4" - "@abp/luxon" "~3.0.4" - "@abp/malihu-custom-scrollbar-plugin" "~3.0.4" - "@abp/select2" "~3.0.4" - "@abp/sweetalert" "~3.0.4" - "@abp/timeago" "~3.0.4" - "@abp/toastr" "~3.0.4" - -"@abp/aspnetcore.mvc.ui@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-3.0.4.tgz#4e4344d95a98a60c53037b351cb915d8ff7a7dcb" - integrity sha512-p7KI5CKkpD4EYzG4fCtygrtZhwMu7Z+IZFGbdqXelLbI5ACkbJyrn+N+9trPfS9papymeXY4p1E3v6xOH7rSHQ== +"@abp/aspnetcore.mvc.ui.theme.basic@^3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-3.0.5.tgz#c146f20226522f0305cc0fa31cfcf9d39be78901" + integrity sha512-ptNhD4xV2wSLW7vQ5KUgaRiH5Ov1OscZW2ZEoivnt3kqgtIC3DGw+SiE9vjFCvyY83fjRkIlW2PIfQxSySbteQ== + dependencies: + "@abp/aspnetcore.mvc.ui.theme.shared" "~3.0.5" + +"@abp/aspnetcore.mvc.ui.theme.shared@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-3.0.5.tgz#5b370902cf229963f9aa10dd64482d57299dde76" + integrity sha512-iobAH39xrCdnqdaABaiYZg4lqNSOwPkMCLnyc+ytxOMbu/YnNKahXPmRhgd8dR/l+iQY1+6T4SWfLdGswi3gAg== + dependencies: + "@abp/aspnetcore.mvc.ui" "~3.0.5" + "@abp/bootstrap" "~3.0.5" + "@abp/bootstrap-datepicker" "~3.0.5" + "@abp/datatables.net-bs4" "~3.0.5" + "@abp/font-awesome" "~3.0.5" + "@abp/jquery-form" "~3.0.5" + "@abp/jquery-validation-unobtrusive" "~3.0.5" + "@abp/lodash" "~3.0.5" + "@abp/luxon" "~3.0.5" + "@abp/malihu-custom-scrollbar-plugin" "~3.0.5" + "@abp/select2" "~3.0.5" + "@abp/sweetalert" "~3.0.5" + "@abp/timeago" "~3.0.5" + "@abp/toastr" "~3.0.5" + +"@abp/aspnetcore.mvc.ui@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-3.0.5.tgz#a050eaee328c56a3730f5abbf25035adf612d6f0" + integrity sha512-/EOgjXTzR+I1OK9KA7L3yzQ2RGSDW97+n3cQrSdLAUF+tnRvMJS0nytDlSJSRK2hb8/clFMaofZHCC81aUkYUg== dependencies: ansi-colors "^4.1.1" extend-object "^1.0.0" @@ -41,144 +41,144 @@ path "^0.12.7" rimraf "^3.0.2" -"@abp/bootstrap-datepicker@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-3.0.4.tgz#d21963f158a4d348a0acdc46209e67378aae8e55" - integrity sha512-25e9qm39dj9AijIchQIdLlBiBM5/fhASkEuLKNt6M8S/e3qwKLwbRhX90DKMFvOyKdxxcK1gX7tDuGHyG9A8mA== +"@abp/bootstrap-datepicker@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-3.0.5.tgz#3a7fd86dbcadc31c11be5b2b31c8cf8ac3035540" + integrity sha512-vIY1wT4RHoJAPhVK11Q5MS71iZbwsmM/0VTBvvs04mjRSSi6xhdyRYkA+l+P49fnOwhZNaaK19XwQdVVScDuhA== dependencies: bootstrap-datepicker "^1.9.0" -"@abp/bootstrap@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-3.0.4.tgz#3b0e2b74dc174a04ce669de1b140df833eee6031" - integrity sha512-DwxL1fGYOwChPwmMpwUxCKiIdzTfRf/YDkOLUiwI8eRrpABBeuE2sSHKfks6Rqh9Wqo/nlWXvfccgDRqweqhsA== +"@abp/bootstrap@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-3.0.5.tgz#91f2c8fe6a57fc5789ae8fe9c206b2c159e55880" + integrity sha512-LdydeJIbw5k1k7FkWaI1lTectZeTXmEIoccJtj9Qx4N9yGt40wGsjz9SOvUdJ0IKAgntIWNQP8ohWiBPgzT7Xg== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" bootstrap "^4.5.0" bootstrap-v4-rtl "4.4.1-2" -"@abp/core@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/core/-/core-3.0.4.tgz#281940525e280e9e3a1aa4cb4e8f64548ea026a3" - integrity sha512-ZPEH1fa5bf/f1ek9ZLQHx7u78iF6kRZ8Z2NBI36cNL13RyQbx485qJF+KXyqNlJoJAh0CAs0NsAJADU3utcDNA== +"@abp/core@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/core/-/core-3.0.5.tgz#9a7bd990d02edc8128a5be3233b6fbb5669b15b8" + integrity sha512-LdkSkZQfN4hugcGNxBkje0+eQomivwNFbnAkMFQbvCwYLwKB2yvk7GCemEetW1piqvyrUGz4jqhtunjoXps5JA== dependencies: - "@abp/utils" "^3.0.4" + "@abp/utils" "^3.0.5" -"@abp/datatables.net-bs4@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs4/-/datatables.net-bs4-3.0.4.tgz#e488336bd2c199146ea60464e0ba75aeb134957e" - integrity sha512-ID1XWKPxG9pv6mxBkku7JWvgXjTxJ1uShxmYv5kFYVuwOGMSvj8QVZInTHFcMdECopV56yjGPNOIFLeQj4Mw8Q== +"@abp/datatables.net-bs4@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs4/-/datatables.net-bs4-3.0.5.tgz#9b79935970d2d4074994240b5deb01dff5987c0a" + integrity sha512-jIWRBEG/NlhaE43i6Xzr18XJ5aTWA+WWslf7WgNOArG5slGBbH4lLLja4HswLP3NGdSmzIyHJR1p9XQ82nBkbQ== dependencies: - "@abp/datatables.net" "~3.0.4" + "@abp/datatables.net" "~3.0.5" datatables.net-bs4 "^1.10.21" -"@abp/datatables.net@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-3.0.4.tgz#7ef143b83143ad8d769be25296411a812a17b255" - integrity sha512-gdQwRuxO5fQWhG/DiUaf40DQ8dWIVGhOCyt6SZ8LQzq4uSaQqa0qrtTTTW2/aiee4MgJu2vJWYF5YCszIR/HhA== +"@abp/datatables.net@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-3.0.5.tgz#0a0a39e8fa54522bf8a09ce76ff3927207a79491" + integrity sha512-HClQ2tcZuWBAChf9oOJOtMP1zHv8jqloPEcw3iqbhS/tWZBw5VQ4TO+BVfZsqHDXZNnWSmR6/2xiKsK32nliew== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" datatables.net "^1.10.21" -"@abp/font-awesome@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-3.0.4.tgz#98f83d0bf8b5a2a4d988cb07f5cceda2e1bc1a09" - integrity sha512-AKhKCUXx8vdHIgyXUfbSLitfYor5dJbo4qMRRGqIJVSj+cyMkpgYbGjqr24GGijgZxdmOGL+s1b9iCUbIPgLNQ== +"@abp/font-awesome@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-3.0.5.tgz#62d58c34f0abf268e30bdf788dfbd3f45b19b412" + integrity sha512-/+31Vtw1S9vvuNy/rN/Q6awkgUaYNNYXESgfvqI0pqIkvlPgigvS8E68mHqGzsxyRqdZisI6JGd94AF+vJD7sQ== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" "@fortawesome/fontawesome-free" "^5.13.0" -"@abp/jquery-form@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-3.0.4.tgz#022eebbfe58b55e328984c0f119884c9464bc767" - integrity sha512-xDkk4rY0AmXBJE4qztzr2hZz+OVucVNWeagOEgeQHXJSKU/T+0kj0a1RxEHpyI24xBvEXAbe8PbByTbu0QXYew== +"@abp/jquery-form@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-3.0.5.tgz#9d2ea9b2509448ee163a163298a7a09480865f78" + integrity sha512-RKgvQciaMml33NvzA28aYyfNI+WQHRfKLFWvOvqc8qO4C3BvyrnwepuzZcnI/9UsELKLSLoFrfk3rJHhxnTJhg== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" jquery-form "^4.3.0" -"@abp/jquery-validation-unobtrusive@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-3.0.4.tgz#6cc942aadcdde33893e7a45c84aa022e033e4e8b" - integrity sha512-N9sza91HCyPvJo+d0RAfq4skae3TgAYg7KXhlwzRE2hqUQS+EICVARURWyxJW7s7Zu6+gxDTkXhBZRTW56deZA== +"@abp/jquery-validation-unobtrusive@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-3.0.5.tgz#b8912e1c4cfdd4fdff6ad4aa7ee8693b55a3be1f" + integrity sha512-kPifDA+1ErbeQBwn+BzwtWuTHXzBke/EMCmITuj59plM/60gZ1ZfVm8EzzTmKS7iLaVVeOqTKf0vEQTobNYffw== dependencies: - "@abp/jquery-validation" "~3.0.4" + "@abp/jquery-validation" "~3.0.5" jquery-validation-unobtrusive "^3.2.11" -"@abp/jquery-validation@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-3.0.4.tgz#3f96d1f83c75a7fc47e5e5e12c51dbc8da7582b1" - integrity sha512-2ljqas4YKU0PcwB0wQW2qhXejDHjW+N1XCxZDyCZ2wF9UxGlKBR8rFt76TJByXSotKE7n2hJRZaOh9ICZUow2g== +"@abp/jquery-validation@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-3.0.5.tgz#55e36b3c832a35447fac69af312dedf1dee22279" + integrity sha512-fdqxc5CJ6TpUAF8+NEsKCmXvvZiOPR5bzczcfvfovxLOjvHvdEO74XPGjsjlBNTdaKMhmNGZRK4JRLUz7fvTng== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" jquery-validation "^1.19.2" -"@abp/jquery@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-3.0.4.tgz#5f236d95cfae7f65012cd8778fd74490b7542442" - integrity sha512-g8N8Te5vf+8djdksfZoZ3tXaqlBC4Ra39pAWU/XerlLl64u+XmAX1s6yKRfC2kS8lf292GgiFYuNOkb77J6k4A== +"@abp/jquery@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-3.0.5.tgz#2e43ac19a8df6e1e0220d6d4991d33a93d890754" + integrity sha512-2d2l+smWKAVP4/b6GSO+8rmJ7pFXiUewpBDwkHm5qgkK6n6UGqIcK9lIgiS2YQ6rmL6qdtuNhhqtC9GF2TF2kQ== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" jquery "~3.5.1" -"@abp/lodash@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-3.0.4.tgz#ee60bc99f98b59fd9fc5ff4bbcf914a13faf209d" - integrity sha512-jxvv74Sm/fUKDBMylK4Ne/8GKm7GuHAdcLFtkoJQptdO+RjZpKJ86ULT+X5vbKskZaTj2fEmOR6l4hmfnBULTA== +"@abp/lodash@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-3.0.5.tgz#d5d9f14095d76239bc0e6f9804c24d1f92ffcab4" + integrity sha512-+B2vxfPwxoIIb8Ac8eHi6OgAn0qbjmPZuun7/es0/ouYYTbEhvhTAD20X3PGZlHrIYeDyS2ansuXm8gJI+/BzA== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" lodash "^4.17.15" -"@abp/luxon@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-3.0.4.tgz#aa1bf6a93d6254433101e50a205b276759969414" - integrity sha512-gX9dQJt9C6M1wmCSUIBoNr8CAt0jkUZFQ7LKKO/BoADGM/XFvY5vDbhL7zcwbwitgCOzmucoCxs/6rIxSOVlrg== +"@abp/luxon@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-3.0.5.tgz#083042c939ea58bf719e35114a6c8efb07b68688" + integrity sha512-0UVHO2CO89t34O35OA3JzWGWLBpg+e1NQXSfVtK90nmg/QmzGkHVM9m7Su0jjLNBt3N5EA5OPOhriot3bCKItw== dependencies: luxon "^1.24.1" -"@abp/malihu-custom-scrollbar-plugin@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-3.0.4.tgz#c54824b98363f4190cc6a37fcd421c2bfdc1e201" - integrity sha512-6LdSGctOr4ACtdohdSlb19vrFNJ5C0hy1VdbBEafHOBspXckvRVx4uJl2KfHmAC+bMg9apoK/wcHjVNdBHThYQ== +"@abp/malihu-custom-scrollbar-plugin@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-3.0.5.tgz#f908289927a63fd76fb8f60b00a37747d9edbe39" + integrity sha512-3GoXA8VtGyZWKiqaPCipPXYLLM7h2Y5Oecq5c5YlMQyW2akv7uHiQQF2GZW/nVsFu3HBbGTOWpKRRmVk3PEO9Q== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" malihu-custom-scrollbar-plugin "^3.1.5" -"@abp/select2@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-3.0.4.tgz#49db0cfbc9b08f6ae4949b2cb8bdef92d7d308dd" - integrity sha512-zKEgkWBFdGU2KiAMUKhucHqBfzDy2KNSefyqxpOiUd1L3PEKu+9o23HZRWzDupYltop+SO1Jw33CNoVAkKm4+A== +"@abp/select2@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-3.0.5.tgz#79d7754713c64a6549da520dc6867ef3e3dd0350" + integrity sha512-VbeSHdAdDy8exrjplHMMhLVV4oG7Gq9zBb5M52f0sIkA4DkXFhksXHcHNoOCfhaKAXpenGA03Z9GJewZ3MTpMw== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" select2 "^4.0.13" -"@abp/sweetalert@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/sweetalert/-/sweetalert-3.0.4.tgz#f85d11c5aabc6ceb509af1b8fd8d75bc3c74bd27" - integrity sha512-hO0qXUWxeMy/lfy3sb+5HvOmIPst6UvYJJGy3ZQR6DKEdkOktBX9sEDb7m6ij6xAIHqWIx1xLveul0oGteOG0Q== +"@abp/sweetalert@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/sweetalert/-/sweetalert-3.0.5.tgz#e3b92d23cf3633983980b6255bf63c43d422ceab" + integrity sha512-OUt4ANgj14GESfRG7+UqdONS8daOu+8x2OZDWv98hriuX0uCVBjwB2J0jRI/n653C68Mi0yHixGONDv9OLy/lg== dependencies: - "@abp/core" "~3.0.4" + "@abp/core" "~3.0.5" sweetalert "^2.1.2" -"@abp/timeago@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-3.0.4.tgz#82d90a04f703a7db9c0acedcd2f3cea684e7ae18" - integrity sha512-7SbbqScTKCix25xWy5oLT0slJqUQsW6YgJgI0N41C3AsByRBXZeh5Gcep033gprTPmpgNY8JOWcEcCXySnYvfA== +"@abp/timeago@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-3.0.5.tgz#b191a7c43f3dc77645fe27273f2f6619f7d4eb31" + integrity sha512-QhvAOU4C+Qxh/gFuCSGZtLU4KwVhdv/jtK8ovWyqbGvQMCCIimqa8BFKIz0H5XWIJhHwwWblkpjICWgjWh6uXw== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" timeago "^1.6.7" -"@abp/toastr@~3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/toastr/-/toastr-3.0.4.tgz#884ab2343ddd59981c291c632e3b0630532f7e9c" - integrity sha512-NG0pVD8bdIXGpjPW9cozCzmyuAdnSLE4vDYLUReP1pZFp9B8SMJqpgPk9eFg0Y06aaHTVMKYBKRoq9xXS4w9Xg== +"@abp/toastr@~3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/toastr/-/toastr-3.0.5.tgz#fc83b678f5be3a22040d0421ad9db5b2eac9d4fc" + integrity sha512-qgi2DEu6FYpN/DYcOZrmXsAr1HqcIKMhBWeXgjfoYUIl/K5+OuUgbQ69D/jdc0jz8HU9a4Cw7Q8hhe4CkgssEw== dependencies: - "@abp/jquery" "~3.0.4" + "@abp/jquery" "~3.0.5" toastr "^2.1.4" -"@abp/utils@^3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-3.0.4.tgz#38068fa8f735e1889cf2f226128366825948814d" - integrity sha512-TnXRZIZcxv5+EctEPsZEi2cyRXbpl3A679wzQa6BACv+pM45cjGO1JR8P0VswKN4TVL0aQRMv26N6U/uie8sEQ== +"@abp/utils@^3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-3.0.5.tgz#66b3da953500e73ff1f23d1b41168cde8216ce49" + integrity sha512-X9V1xLIEazItaf93nFSA4p94WT6QK3yA4eIyvU7GyyEkkOQdaakNwPbRwy930sd+I+/oXnIqM1vDsJu9QDhgvg== dependencies: just-compare "^1.3.0" diff --git a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/MyCompanyName.MyProjectName.Domain.Shared.csproj b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/MyCompanyName.MyProjectName.Domain.Shared.csproj index 526c13a2e4..b1d4790e49 100644 --- a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/MyCompanyName.MyProjectName.Domain.Shared.csproj +++ b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/MyCompanyName.MyProjectName.Domain.Shared.csproj @@ -13,9 +13,9 @@ - + - + diff --git a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.MongoDB/MyCompanyName.MyProjectName.MongoDB.csproj b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.MongoDB/MyCompanyName.MyProjectName.MongoDB.csproj index 0d2c58c6a4..654c7a14f3 100644 --- a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.MongoDB/MyCompanyName.MyProjectName.MongoDB.csproj +++ b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.MongoDB/MyCompanyName.MyProjectName.MongoDB.csproj @@ -3,7 +3,7 @@ - netcoreapp3.1 + netstandard2.0 MyCompanyName.MyProjectName diff --git a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyCompanyName.MyProjectName.Web.csproj b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyCompanyName.MyProjectName.Web.csproj index e47d2294ba..90baabf104 100644 --- a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyCompanyName.MyProjectName.Web.csproj +++ b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyCompanyName.MyProjectName.Web.csproj @@ -21,7 +21,7 @@ - + diff --git a/templates/module/aspnet-core/test/MyCompanyName.MyProjectName.EntityFrameworkCore.Tests/MyCompanyName.MyProjectName.EntityFrameworkCore.Tests.csproj b/templates/module/aspnet-core/test/MyCompanyName.MyProjectName.EntityFrameworkCore.Tests/MyCompanyName.MyProjectName.EntityFrameworkCore.Tests.csproj index 9f0b4d1e0e..6997256568 100644 --- a/templates/module/aspnet-core/test/MyCompanyName.MyProjectName.EntityFrameworkCore.Tests/MyCompanyName.MyProjectName.EntityFrameworkCore.Tests.csproj +++ b/templates/module/aspnet-core/test/MyCompanyName.MyProjectName.EntityFrameworkCore.Tests/MyCompanyName.MyProjectName.EntityFrameworkCore.Tests.csproj @@ -9,7 +9,7 @@ - + diff --git a/templates/module/aspnet-core/test/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp.csproj b/templates/module/aspnet-core/test/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp.csproj index a7c57102a6..076ce8be71 100644 --- a/templates/module/aspnet-core/test/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp.csproj +++ b/templates/module/aspnet-core/test/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp.csproj @@ -20,7 +20,7 @@ - +