@ -1,4 +1,15 @@ |
|||
ui-angular: |
|||
- npm/ng-packs/* |
|||
- npm/ng-packs/**/* |
|||
- npm/ng-packs/**/**/* |
|||
- npm/ng-packs/**/**/**/* |
|||
- npm/ng-packs/**/**/**/**/* |
|||
- npm/ng-packs/**/**/**/**/**/* |
|||
- templates/app/angular/* |
|||
- templates/app/angular/**/* |
|||
- templates/app/angular/**/**/* |
|||
- templates/app/angular/**/**/**/* |
|||
- templates/module/angular/* |
|||
- templates/module/angular/**/* |
|||
- templates/module/angular/**/**/* |
|||
- templates/module/angular/**/**/**/* |
|||
|
|||
@ -1,17 +1,12 @@ |
|||
name: "Pull Request Labeler" |
|||
name: Pull request labeler |
|||
on: |
|||
pull_request: |
|||
paths: |
|||
- npm/ng-packs/**/* |
|||
- templates/app/angular/**/* |
|||
- templates/module/angular/**/* |
|||
branches: |
|||
- master |
|||
- dev |
|||
schedule: |
|||
- cron: '0 12 */1 * *' |
|||
jobs: |
|||
labeler: |
|||
runs-on: ubuntu-18.04 |
|||
runs-on: ubuntu-latest |
|||
steps: |
|||
- uses: actions/labeler@v2 |
|||
with: |
|||
repo-token: "${{ secrets.GITHUB_TOKEN }}" |
|||
- uses: paulfantom/periodic-labeler@master |
|||
env: |
|||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
|||
GITHUB_REPOSITORY: ${{ github.repository }} |
|||
|
|||
@ -0,0 +1,13 @@ |
|||
{ |
|||
"culture": "sl", |
|||
"texts": { |
|||
"Account": "Račun", |
|||
"Welcome": "Dobrodošli", |
|||
"UseOneOfTheFollowingLinksToContinue": "Za nadaljevanje uporabite eno od naslednjih povezav", |
|||
"FrameworkHomePage": "Domača stran razvojnega okolja", |
|||
"FrameworkDocumentation": "Dokumentacija razvojnega okolja", |
|||
"OfficialBlog": "Uradni blog", |
|||
"CommercialHomePage": "Domača stran različice Commercial", |
|||
"CommercialSupportWebSite": "Spletna stran za komercialno podporo" |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
{ |
|||
"culture": "zh-Hant", |
|||
"texts": { |
|||
"Account": "帳號", |
|||
"Welcome": "歡迎", |
|||
"UseOneOfTheFollowingLinksToContinue": "使用下面的連結繼續", |
|||
"FrameworkHomePage": "框架首頁", |
|||
"FrameworkDocumentation": "框架文件", |
|||
"OfficialBlog": "官方部落格", |
|||
"CommercialHomePage": "商業版首頁", |
|||
"CommercialSupportWebSite": "商業版支援網站" |
|||
} |
|||
} |
|||
@ -0,0 +1,90 @@ |
|||
{ |
|||
"culture": "sl", |
|||
"texts": { |
|||
"Permission:Organizations": "Organizacije", |
|||
"Permission:Manage": "Upravljaj organizacije", |
|||
"Permission:NpmPackages": "NPM paketi", |
|||
"Permission:NugetPackages": "Nuget paketi", |
|||
"Permission:Maintenance": "Vzdrževanje", |
|||
"Permission:Maintain": "Vzdrževanje", |
|||
"Permission:ClearCaches": "Počisti predpomnilnike", |
|||
"Permission:Modules": "Moduli", |
|||
"Permission:Packages": "Paketi", |
|||
"Permission:Edit": "Urejanje", |
|||
"Permission:Delete": "Brisanje", |
|||
"Permission:Create": "Ustvarjanje", |
|||
"Menu:Organizations": "Organizacije", |
|||
"Menu:Packages": "Paketi", |
|||
"NpmPackageDeletionWarningMessage": "Ta paket NPM bo izbrisan. Ali to potrjujete?", |
|||
"NugetPackageDeletionWarningMessage": "Ta Nuget paket bo izbrisan. Ali to potrjujete?", |
|||
"ModuleDeletionWarningMessage": "Ta modul bo izbrisan. Ali to potrjujete?", |
|||
"Name": "Naziv", |
|||
"DisplayName": "Naziv za prikaz", |
|||
"ShortDescription": "Kratek opis", |
|||
"NameFilter": "Naziv", |
|||
"CreationTime": "Čas nastanka", |
|||
"IsPro": "Je pro", |
|||
"EfCoreConfigureMethodName": "Konfiguriraj ime metode", |
|||
"IsProFilter": "Je pro", |
|||
"ApplicationType": "Tip aplikacije", |
|||
"Target": "Cilj", |
|||
"TargetFilter": "Cilj", |
|||
"ModuleClass": "Razred modula", |
|||
"NugetPackageTarget.DomainShared": "Domain Shared", |
|||
"NugetPackageTarget.Domain": "Domain", |
|||
"NugetPackageTarget.Application": "Application", |
|||
"NugetPackageTarget.ApplicationContracts": "Application Contracts", |
|||
"NugetPackageTarget.HttpApi": "Http Api", |
|||
"NugetPackageTarget.HttpApiClient": "Http Api Client", |
|||
"NugetPackageTarget.Web": "Web", |
|||
"NugetPackageTarget.EntityFrameworkCore": "DeleteAllEntityFramework Core", |
|||
"NugetPackageTarget.MongoDB": "MongoDB", |
|||
"Edit": "Uredi", |
|||
"Delete": "Izbriši", |
|||
"Refresh": "Osveži", |
|||
"NpmPackages": "NPM paketi", |
|||
"NugetPackages": "Nuget paketi", |
|||
"NpmPackageCount": "Število NPM paketov", |
|||
"NugetPackageCount": "Število Nuget paketov", |
|||
"Module": "Moduli", |
|||
"ModuleInfo": "Informacije o modulu", |
|||
"CreateANpmPackage": "Ustvari NPM paket", |
|||
"CreateAModule": "Ustvari modul", |
|||
"CreateANugetPackage": "Ustvari Nuget paket", |
|||
"AddNew": "Dodaj novega", |
|||
"PackageAlreadyExist{0}": "Paket \"{0}\" je že dodan.", |
|||
"ModuleAlreadyExist{0}": "Modul \"{0}\" je že dodan.", |
|||
"ClearCache": "Počisti predpomnilnik", |
|||
"SuccessfullyCleared": "Uspešno izbrisano", |
|||
"Menu:NpmPackages": "NPM paketi", |
|||
"Menu:Modules": "Moduli", |
|||
"Menu:Maintenance": "Vzdrževanje", |
|||
"Menu:NugetPackages": "Nuget paketi", |
|||
"CreateAnOrganization": "Ustvari organizacijo", |
|||
"Organizations": "Organizacije", |
|||
"LongName": "Dolg naziv", |
|||
"LicenseType": "Tip licence", |
|||
"LicenseStartTime": "Čas začetka licence", |
|||
"LicenseEndTime": "Čas konca licence", |
|||
"AllowedDeveloperCount": "Dovoljeno število razvijalcev", |
|||
"UserNameOrEmailAddress": "Uporabniško ime ali e-poštni naslov", |
|||
"AddOwner": "Dodaj lastnika", |
|||
"UserName": "Uporabniško ime", |
|||
"Email": "E-poštni naslov", |
|||
"Developers": "Razvijalci", |
|||
"AddDeveloper": "Dodaj razvijalca", |
|||
"Create": "Ustvari", |
|||
"UserNotFound": "Uporabnika ni mogoče najti", |
|||
"{0}WillBeRemovedFromMembers": "{0} bo odstranjen iz članov", |
|||
"Computers": "Računalniki", |
|||
"UniqueComputerId": "Unikatni id računalnika", |
|||
"LastSeenDate": "Zadnjič viden", |
|||
"{0}Computer{1}WillBeRemovedFromRecords": "Računalnik {0} ({1}) bo odstranjen iz zapisov", |
|||
"OrganizationDeletionWarningMessage": "Organizacija bo izbrisana", |
|||
"This{0}AlreadyExistInThisOrganization": "{0} že obstaja v tej organizaciji", |
|||
"AreYouSureYouWantToDeleteAllComputers": "Ali ste prepričani, da želite izbrisati vse računalnike?", |
|||
"DeleteAll": "Izbriši vse", |
|||
"DoYouWantToCreateNewUser": "Ali želite ustvariti novega uporabnika?", |
|||
"MasterModules": "Glavni moduli" |
|||
} |
|||
} |
|||
@ -0,0 +1,60 @@ |
|||
{ |
|||
"culture": "zh-Hant", |
|||
"texts": { |
|||
"Permission:NpmPackages": "NPM套件", |
|||
"Permission:NugetPackages": "Nuget套件", |
|||
"Permission:Maintenance": "維護", |
|||
"Permission:Maintain": "維護", |
|||
"Permission:ClearCaches": "清除快取", |
|||
"Permission:Modules": "模組", |
|||
"Permission:Packages": "套件", |
|||
"Permission:Edit": "編輯", |
|||
"Permission:Delete": "刪除", |
|||
"Permission:Create": "建立", |
|||
"Menu:Packages": "套件", |
|||
"NpmPackageDeletionWarningMessage": "該NPM套件將會被刪除. 你確定嗎?", |
|||
"NugetPackageDeletionWarningMessage": "該Nuget套件將會被刪除. 你確定嗎?", |
|||
"ModuleDeletionWarningMessage": "該模組將會被刪除. 你確定嗎?", |
|||
"Name": "名稱", |
|||
"DisplayName": "顯示名稱", |
|||
"ShortDescription": "簡述", |
|||
"NameFilter": "名稱", |
|||
"CreationTime": "建立時間", |
|||
"IsPro": "是否為專業版", |
|||
"EfCoreConfigureMethodName": "設定方法", |
|||
"IsProFilter": "是否為專業版", |
|||
"ApplicationType": "應用程式類型", |
|||
"Target": "目標", |
|||
"TargetFilter": "目標", |
|||
"ModuleClass": "模組分類", |
|||
"NugetPackageTarget.DomainShared": "Domain Shared", |
|||
"NugetPackageTarget.Domain": "Domain", |
|||
"NugetPackageTarget.Application": "Application", |
|||
"NugetPackageTarget.ApplicationContracts": "Application Contracts", |
|||
"NugetPackageTarget.HttpApi": "Http Api", |
|||
"NugetPackageTarget.HttpApiClient": "Http Api Client", |
|||
"NugetPackageTarget.Web": "Web", |
|||
"NugetPackageTarget.EntityFrameworkCore": "EntityFramework Core", |
|||
"NugetPackageTarget.MongoDB": "MongoDB", |
|||
"Edit": "編輯", |
|||
"Delete": "刪除", |
|||
"Refresh": "刷新", |
|||
"NpmPackages": "NPM套件", |
|||
"NugetPackages": "Nuget套件", |
|||
"NpmPackageCount": "NPM套件數量", |
|||
"NugetPackageCount": "Nuget套件數量", |
|||
"Module": "模組", |
|||
"ModuleInfo": "模組信息", |
|||
"CreateANpmPackage": "建立NPM套件", |
|||
"CreateAModule": "建立模組", |
|||
"CreateANugetPackage": "建立Nuget套件", |
|||
"AddNew": "建立", |
|||
"PackageAlreadyExist{0}": "\"{0}\"已經被添加.", |
|||
"ClearCache": "清除快取", |
|||
"SuccessfullyCleared": "清除成功", |
|||
"Menu:NpmPackages": "NPM套件", |
|||
"Menu:Modules": "模組", |
|||
"Menu:Maintenance": "維護", |
|||
"Menu:NugetPackages": "Nuget套件" |
|||
} |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
{ |
|||
"culture": "sl", |
|||
"texts": { |
|||
"Volo.AbpIo.Domain:010004": "Doseženo je največje število članov!", |
|||
"Volo.AbpIo.Domain:010005": "Doseženo je največje število lastnikov!", |
|||
"Volo.AbpIo.Domain:010006": "Ta uporabnik je že lastnik te organizacije!", |
|||
"Volo.AbpIo.Domain:010007": "Ta uporabnik je že razvijalec v tej organizaciji!", |
|||
"Volo.AbpIo.Domain:010008": "Dovoljeno število razvijalcev ne sme biti manjše od trenutnega števila razvijalcev!", |
|||
"Volo.AbpIo.Domain:010009": "Dovoljeno število razvijalcev ne sme biti manjše od 0!", |
|||
"Volo.AbpIo.Domain:010010": "Največje število mac naslovov je preseženo!", |
|||
"Volo.AbpIo.Domain:010011": "Osebne licence ne sme imeti več kot 1 razvijalec!", |
|||
"Volo.AbpIo.Domain:010012": "Licence ni mogoče podaljšati en mesec po poteku licence!", |
|||
"Volo.AbpIo.Domain:020001": "Tega paketa NPM ni mogoče izbrisati, ker so \"{NugetPackages}\" Nuget paketi odvisni od tega paketa.", |
|||
"Volo.AbpIo.Domain:020002": "Tega paketa NPM ni mogoče izbrisati, ker \"{Modules}\" moduli uporabljajo ta paket.", |
|||
"Volo.AbpIo.Domain:020003": "Tega paketa NPM ni mogoče izbrisati, ker \"{Modules}\" moduli uporabljajo ta paket in \"{NugetPackages}\" Nuget paketi so odvisni od tega paketa.", |
|||
"Volo.AbpIo.Domain:020004": "Nuget paketa ni mogoče izbrisati, ker \"{Modules}\" moduli uporabljajo ta paket.", |
|||
"WantToLearn?": "Se želite naučiti?", |
|||
"ReadyToGetStarted?": "Pripravljeni, da bi začeli?", |
|||
"JoinOurCommunity": "Pridružite se naši skupnosti", |
|||
"GetStartedUpper": "ZAČNI", |
|||
"ForkMeOnGitHub": "Naredi vejitev na GitHub", |
|||
"Features": "Funkcionalnosti", |
|||
"GetStarted": "Začni", |
|||
"Documents": "Dokumenti", |
|||
"Community": "Skupnost", |
|||
"ContributionGuide": "Vodič za prispevke", |
|||
"Blog": "Blog", |
|||
"Commercial": "Commercial", |
|||
"SeeDocuments": "Poglej dokumente" |
|||
} |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
{ |
|||
"culture": "zh-Hant", |
|||
"texts": { |
|||
"Volo.AbpIo.Domain:010004": "超過最大成員數!", |
|||
"Volo.AbpIo.Domain:010005": "超過最大擁有者數!", |
|||
"Volo.AbpIo.Domain:010006": "使用者已經是該組織的擁有者!", |
|||
"Volo.AbpIo.Domain:010007": "該使用者已經是該組織的開發者!", |
|||
"Volo.AbpIo.Domain:010008": "允許的開發者數量不能低於當前開發者數量!", |
|||
"Volo.AbpIo.Domain:010009": "允許的開發者數量不能小於零!", |
|||
"Volo.AbpIo.Domain:010010": "超出了最大mac地址數!", |
|||
"Volo.AbpIo.Domain:010011": "個人許可不允許超過1個開發者!", |
|||
"Volo.AbpIo.Domain:010012": "許可過期後許可不可延長1個月!", |
|||
"Volo.AbpIo.Domain:020001": "不能刪除該NPM套件因為\"{NugetPackages}\"Nuget套件依賴此套件.", |
|||
"Volo.AbpIo.Domain:020002": "不能刪除該NPM套件因為\"{Modules}\"模組正在使用此套件.", |
|||
"Volo.AbpIo.Domain:020003": "不能刪除該NPM套件因為\"{Modules}\"模組正在使用此套件並且\"{NugetPackages}\"Nuget套件依賴此套件.", |
|||
"Volo.AbpIo.Domain:020004": "不能刪除該Nuget套件因為\"{Modules}\"模組正在使用此套件.", |
|||
"WantToLearn?": "想學習嗎?", |
|||
"ReadyToGetStarted?": "準備開始了嗎?", |
|||
"JoinOurCommunity": "加入我們的社群", |
|||
"GetStartedUpper": "開始", |
|||
"ForkMeOnGitHub": "Fork me on GitHub", |
|||
"Features": "功能", |
|||
"GetStarted": "開始", |
|||
"Documents": "文件", |
|||
"Community": "社群", |
|||
"ContributionGuide": "貢獻指南", |
|||
"Blog": "部落格", |
|||
"Commercial": "商業版", |
|||
"SeeDocuments": "查看文件" |
|||
} |
|||
} |
|||
@ -0,0 +1,5 @@ |
|||
{ |
|||
"culture": "sl", |
|||
"texts": { |
|||
} |
|||
} |
|||
@ -0,0 +1,5 @@ |
|||
{ |
|||
"culture": "zh-Hant", |
|||
"texts": { |
|||
} |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
{ |
|||
"culture": "sl", |
|||
"texts": { |
|||
"OrganizationManagement": "Upravljanje organizacije", |
|||
"OrganizationList": "Seznam organizacij", |
|||
"Volo.AbpIo.Commercial:010003": "Niste lastnik te organizacije!", |
|||
"OrganizationNotFoundMessage": "Nobene organizacije ni bilo mogoče najti!", |
|||
"DeveloperCount": "Dodeljeni / skupno število razvijalcev", |
|||
"QuestionCount": "Preostala / skupno število vprašanj", |
|||
"Unlimited": "Neomejeno", |
|||
"Owners": "Lastniki", |
|||
"AddMember": "Dodaj člana", |
|||
"AddOwner": "Dodaj lastnika", |
|||
"AddDeveloper": "Dodaj razvijalce", |
|||
"UserName": "Uporabniško ime", |
|||
"Name": "Ime", |
|||
"EmailAddress": "E-poštni naslov", |
|||
"Developers": "Razvijalci", |
|||
"LicenseType": "Tip licence", |
|||
"Manage": "Upravljaj", |
|||
"StartDate": "Datum začetka", |
|||
"EndDate": "Datum konca", |
|||
"Modules": "Moduli", |
|||
"LicenseExtendMessage": "Končni datum veljavnosti licence je podaljšan na {0}", |
|||
"LicenseUpgradeMessage": "Vaša licenca je nadgrajena na {0}", |
|||
"LicenseAddDeveloperMessage": "{0} razvijalcev je bilo dodanih k vaši licenci", |
|||
"Volo.AbpIo.Commercial:010004": "Navedenega uporabnika ni mogoče najti! Uporabnik mora biti registriran.", |
|||
"MyOrganizations": "Moje organizacije", |
|||
"ApiKey": "Ključ API", |
|||
"UserNameNotFound": "Ni uporabnika z uporabniškim imenom {0}", |
|||
"SuccessfullyAddedToNewsletter": "Hvala, ker ste se naročili na naše novice!", |
|||
"ManageProfile": "Upravljaj svoj profil", |
|||
"EmailNotValid": "Vnesite veljaven e-poštni naslov." |
|||
} |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
{ |
|||
"culture": "zh-Hant", |
|||
"texts": { |
|||
"OrganizationManagement": "組織管理", |
|||
"OrganizationList": "組織列表", |
|||
"Volo.AbpIo.Commercial:010003": "您不是該組織的擁有者!", |
|||
"OrganizationNotFoundMessage": "找不到任何組織!", |
|||
"DeveloperCount": "開發者數量", |
|||
"Owners": "擁有者", |
|||
"AddMember": "加入成員", |
|||
"AddOwner": "加入擁有者", |
|||
"AddDeveloper": "加入開發者", |
|||
"UserName": "使用者名稱", |
|||
"Name": "名稱", |
|||
"EmailAddress": "電子信箱地址", |
|||
"Developers": "開發者", |
|||
"LicenseType": "許可證類型", |
|||
"Manage": "管理", |
|||
"StartDate": "開始日期", |
|||
"EndDate": "結束日期", |
|||
"Modules": "模組", |
|||
"LicenseExtendMessage": "您的許可已經延長至{0}", |
|||
"LicenseUpgradeMessage": "您的許可已升級為{0}", |
|||
"LicenseAddDeveloperMessage": "{0}個開發者已加入到您的許可", |
|||
"Volo.AbpIo.Commercial:010004": "不能找到指定的使用者! 使用者必須已經註冊.", |
|||
"MyOrganizations": "我的組織", |
|||
"ApiKey": "API key", |
|||
"UserNameNotFound": "沒有使用者名稱為{0}的使用者" |
|||
} |
|||
} |
|||
@ -0,0 +1,5 @@ |
|||
{ |
|||
"culture": "sl", |
|||
"texts": { |
|||
} |
|||
} |
|||
@ -0,0 +1,5 @@ |
|||
{ |
|||
"culture": "zh-Hant", |
|||
"texts": { |
|||
} |
|||
} |
|||
@ -0,0 +1,5 @@ |
|||
{ |
|||
"culture": "sl", |
|||
"texts": { |
|||
} |
|||
} |
|||
@ -0,0 +1,5 @@ |
|||
{ |
|||
"culture": "zh-Hant", |
|||
"texts": { |
|||
} |
|||
} |
|||
@ -0,0 +1,159 @@ |
|||
{ |
|||
"culture": "zh-Hant", |
|||
"texts": { |
|||
"GetStarted": "開始", |
|||
"Create": "建立", |
|||
"NewProject": "新專案", |
|||
"DirectDownload": "直接下載", |
|||
"ProjectName": "專案名稱", |
|||
"ProjectType": "專案類型", |
|||
"DatabaseProvider": "資料庫提供者", |
|||
"NTier": "N層", |
|||
"IncludeUserInterface": "包含使用者介面", |
|||
"CreateNow": "現在建立", |
|||
"TheStartupProject": "起始專案", |
|||
"Tutorial": "教學", |
|||
"UsingCLI": "使用CLI", |
|||
"SeeDetails": "看詳細", |
|||
"AbpShortDescription": "ABP是用於建立現代Web應用程式的完整架構和強大的基礎設施! 遵循最佳實作和約定,為您提供SOLID開發經驗.", |
|||
"SourceCodeUpper": "原始碼", |
|||
"LatestReleaseLogs": "最新發佈日誌", |
|||
"Infrastructure": "基礎設施", |
|||
"Architecture": "架構", |
|||
"Modular": "模組化", |
|||
"DontRepeatYourself": "不要重複工作", |
|||
"DeveloperFocused": "專注於開發者", |
|||
"FullStackApplicationInfrastructure": "全端應用程式基礎設施", |
|||
"DomainDrivenDesign": "領域驅動設計", |
|||
"DomainDrivenDesignExplanation": "依據DDD模式和準則進行設計和開發. 為您的應用程式提供分層模型.", |
|||
"Authorization": "授權", |
|||
"AuthorizationExplanation": "具有使用者,角色和細膩度權限系統的高級授權. 建立於Microsoft Identity套件上.", |
|||
"MultiTenancy": "多租戶", |
|||
"MultiTenancyExplanationShort": "SaaS應用程式變得簡單! 從資料庫到UI的多租戶整合.", |
|||
"CrossCuttingConcerns": "橫切關注點", |
|||
"CrossCuttingConcernsExplanationShort": "完整的基礎架構,用於授權,驗證,異常處理,快取,稽核日誌,交易管理等.", |
|||
"BuiltInBundlingMinification": "內建Bundling & Minification", |
|||
"BuiltInBundlingMinificationExplanation": "無須使用外部工具進行Bundling & Minification. ABP提供了一種更簡單,動態,功能強大,模組化和內建的方式!", |
|||
"VirtualFileSystem": "虛擬文件系統", |
|||
"VirtualFileSystemExplanation": "將檢視,腳本,樣式,圖片...嵌入到套件/類別庫中,並在不同的應用程式中重複使用.", |
|||
"Theming": "主題", |
|||
"ThemingExplanationShort": "使用和訂製基於bootstrap的標準UI主題,或建立自己的主題.", |
|||
"BootstrapTagHelpersDynamicForms": "Bootstrap Tag Helpers和動態表單", |
|||
"BootstrapTagHelpersDynamicFormsExplanation": "內建的背景作業系統可以整合到Hangfire,RabbitMQ或您喜歡的任何工具中.", //TODO explanation doesn't match. |
|||
"HTTPAPIsDynamicProxies": "HTTP APIs和動態代理", |
|||
"HTTPAPIsDynamicProxiesExplanation": "自動將應用程式服務公開為REST樣式的HTTP API,並與動態JavaScript和C#代理一起使用.", |
|||
"CompleteArchitectureInfo": "現在架構用來建立可維護的軟體解決方案.", |
|||
"DomainDrivenDesignBasedLayeringModelExplanation": "幫助您實現基於DDD的分層架構並建構可維護的程式碼.", |
|||
"DomainDrivenDesignBasedLayeringModelExplanationCont": "提供啟動模板,抽象,基礎類別,服務,文件和指南以幫助您開發基於DDD模式和準則的應用程式.", |
|||
"MicroserviceCompatibleModelExplanation": "核心框架和預先建構模組在設計時就考慮了微服務架構.", |
|||
"MicroserviceCompatibleModelExplanationCont": "提供基礎結構,整合,範例和文件,以更輕鬆地實現微服務解決方案,而如果您要使用整體應用程式,也不會帶來額外的複雜性.", |
|||
"ModularInfo": "ABP提供了完整的模組化系統,使您能夠開發可重複使用的應用程式模組.", |
|||
"PreBuiltModulesThemes": "預先建構模組和主題", |
|||
"PreBuiltModulesThemesExplanation": "開源和商業模組和主題已準備好在您的業務應用程式中使用.", |
|||
"NuGetNPMPackages": "NuGet和NPM套件", |
|||
"NuGetNPMPackagesExplanation": "作為NuGet和NPM套件發佈.易於安裝和升級.", |
|||
"ExtensibleReplaceable": "可擴展/可替換", |
|||
"ExtensibleReplaceableExplanation": "所有服務和模組在設計時都考慮了可擴展性.您可以替換服務,頁面,樣式,組件...", |
|||
"CrossCuttingConcernsExplanation2": "保持原始碼整潔,專注於您自己的業務邏輯.", |
|||
"CrossCuttingConcernsExplanation3": "不要浪費時間一次又一次地實作共通用的應用程式需求.", |
|||
"AuthenticationAuthorization": "認證與授權", |
|||
"ExceptionHandling": "異常處理", |
|||
"Validation": "驗證", |
|||
"DatabaseConnection": "資料庫連接", |
|||
"TransactionManagement": "交易管理", |
|||
"AuditLogging": "稽核日誌", |
|||
"Caching": "快取", |
|||
"Multitenancy": "多租戶", |
|||
"DataFiltering": "資料過濾", |
|||
"ConventionOverConfiguration": "習慣優於設定", |
|||
"ConventionOverConfigurationExplanation": "預設情況下,ABP使用最小或零設定實現共通用的應用程式.", |
|||
"ConventionOverConfigurationExplanationList1": "自動註冊已知服務以進行依賴注入.", |
|||
"ConventionOverConfigurationExplanationList2": "通過命名習慣將應用程式服務公開為HTTP API.", |
|||
"ConventionOverConfigurationExplanationList3": "為C#和JavaScript建立動態HTTP客戶端代理.", |
|||
"ConventionOverConfigurationExplanationList4": "為您的實體提供預設Repository.", |
|||
"ConventionOverConfigurationExplanationList5": "根據Web請求或應用程式服務方法管理工作單元.", |
|||
"ConventionOverConfigurationExplanationList6": "為實體發佈建立,更新和刪除事件.", |
|||
"BaseClasses": "基礎類別", |
|||
"BaseClassesExplanation": "共通用應用程式模式的預先建構基礎類別.", |
|||
"DeveloperFocusedExplanation": "ABP是為了開發者", |
|||
"DeveloperFocusedExplanationCont": "它旨在簡化您的日常軟體開發,同時又不限制您在需要時進行底層工作。", |
|||
"SeeAllFeatures": "瀏覽所有功能", |
|||
"CLI_CommandLineInterface": "CLI (命令列介面)", |
|||
"CLI_CommandLineInterfaceExplanation": "CLI會自動建立新專案並將模組添加到您的應用程式.", |
|||
"StartupTemplates": "起始模板", |
|||
"StartupTemplatesExplanation": "各種起始模板為您提供了完整設定的解結方案,以快速啟動您的開發.", |
|||
"BasedOnFamiliarTools": "基於熟悉的工具", |
|||
"BasedOnFamiliarToolsExplanation": "建立在您已經知道的主流工具上並與其整合.學習曲線低,適應性強,舒適的開發.", |
|||
"ORMIndependent": "ORM獨立", |
|||
"ORMIndependentExplanation": "核心框架獨立於ORM/資料庫,並且可以與任何資料庫一起使用.Entity Framework Core和MongoDB提供者已經可用.", |
|||
"Features": "功能", |
|||
"ABPCLI": "ABP CLI", |
|||
"Modularity": "模組化", |
|||
"BootstrapTagHelpers": "Bootstrap Tag Helpers", |
|||
"DynamicForms": "動態表單", |
|||
"BundlingMinification": "Bundling & Minification", |
|||
"BackgroundJobs": "背景作業", |
|||
"DDDInfrastructure": "DDD基礎設施", |
|||
"DomainDrivenDesignInfrastructure": "Domain Driven Design基礎設施", |
|||
"AutoRESTAPIs": "自動REST APIs", |
|||
"DynamicClientProxies": "動態客戶端代理", |
|||
"DistributedEventBus": "分散式事件匯流排", |
|||
"DistributedEventBusWithRabbitMQIntegration": "具有RabbitMQ整合的分散式事件匯流排", |
|||
"TestInfrastructure": "測試基礎設施", |
|||
"AuditLoggingEntityHistories": "稽核日誌和實體歷史", |
|||
"ObjectToObjectMapping": "物件對應", |
|||
"EmailSMSAbstractions": "電子郵件和簡訊抽象", |
|||
"EmailSMSAbstractionsWithTemplatingSupport": "具有模板支援的電子郵件和簡訊抽象", |
|||
"Localization": "本地化", |
|||
"SettingManagement": "設定管理", |
|||
"ExtensionMethods": "擴充方法", |
|||
"ExtensionMethodsHelpers": "擴充方法和助手", |
|||
"AspectOrientedProgramming": "切面導向設計", |
|||
"DependencyInjection": "依賴注入", |
|||
"DependencyInjectionByConventions": "依照習慣的依賴注入", |
|||
"ABPCLIExplanation": "ABP CLI(命令列介面)是用於對ABP解決方案執行常見操作的命令列工具.", |
|||
"ModularityExplanation": "ABP提供了一個完整的基礎設施來建構您自己的應用程式模組,這些模組可能具有實體,服務,資料庫整合,API,UI組件等.", //TODO: strong "your own application modules",- |
|||
"MultiTenancyExplanation": "ABP框建不僅支援開發多租戶應用程式,而且使您的程式碼幾乎無須知道多租戶.", |
|||
"MultiTenancyExplanation2": "可以自動確定當前租戶,將不同租戶的資料互相隔離.", |
|||
"MultiTenancyExplanation3": "支援單一資料庫,或每個租戶單獨資料庫或者混合方式.", |
|||
"MultiTenancyExplanation4": "您專注於業務邏輯,並讓該框架為您處理多租戶.", |
|||
"BootstrapTagHelpersExplanation": "與其手動撰寫重複細節的bootstrap組件,不如使用ABP的tag helper來簡化它並利用自動完成.您當然也可以在需要時直接使用Bootstrap.", |
|||
"DynamicFormsExplanation": "動態表單和tag helpers可以利用C#類別做為模型來建立完整的表單.", |
|||
"AuthenticationAuthorizationExplanation": "整合到ASP.NET Core Identity和IdentityServer4的豐富身份驗證和授權選項.提供可擴展且詳細的權限系統.", |
|||
"CrossCuttingConcernsExplanation": "部要重複資幾一次又一次地實作所有這寫常見的東西.專注於您的業務邏輯,並讓ABP按照習慣自動執行.", |
|||
"DatabaseConnectionTransactionManagement": "資料庫連接和交易管理", |
|||
"CorrelationIdTracking": "關聯ID追蹤", |
|||
"BundlingMinificationExplanation": "ABP提供了一個簡單,動態,功能強大,模組化的內建Bundling & Minification系統.", |
|||
"VirtualFileSystemnExplanation": "虛擬文件系統使管理文件系統(硬碟)上不存在的文件成為可能.它主要用於將(js,css,image,cshtml...)文件嵌入到組件(Assembly)中,並在運行時像實體文件一樣使用他們.", |
|||
"ThemingExplanation": "主題系統允許通過基於最新的Bootstrap框架定義一組通用基礎類別庫和佈局來獨立開發應用程式和模組主題.", |
|||
"DomainDrivenDesignInfrastructureExplanation": "基於領域驅動設計模式和準則建構分層應用程式的完整基礎設施;", |
|||
"Specification": "規範", |
|||
"Repository": "倉儲", |
|||
"DomainService": "領域服務", |
|||
"ValueObject": "值對象", |
|||
"ApplicationService": "應用服務", |
|||
"DataTransferObject": "資料傳輸對象", |
|||
"AggregateRootEntity": "聚合根, 實體", |
|||
"AutoRESTAPIsExplanation": "ABP可以依照習慣自動將您的應用服務設定為API控制器.", |
|||
"DynamicClientProxiesExplanation": "從JavaScript和C#客戶端輕鬆使用您的API.", |
|||
"DistributedEventBusWithRabbitMQIntegrationExplanation": "使用帶有RabbitMQ整合的內建分散式事件匯流排,可以輕鬆發佈和使用分散式事件.", |
|||
"TestInfrastructureExplanation": "框架已經考慮了單元和整合測試.為您提供基礎類別,使其更容易.起始模板已預先設定用於測試.", |
|||
"AuditLoggingEntityHistoriesExplanation": "針對關鍵業務應用的內建稽核日誌紀錄.請求,服務,方法級別的稽核日誌紀錄以及具有屬性級別詳細資訊的實體歷史紀錄.", |
|||
"EmailSMSAbstractionsWithTemplatingSupportExplanation": "IEmailSender和ISmsSender抽象使您的應用程式邏輯與基礎設施解耦.先進的電子郵件模板系統允許建立和在地化電子郵件模板,並在需要實輕鬆使用.", |
|||
"LocalizationExplanation": "在地化系統允許在純JSON文件中建立資源,並使用它們來在地化UI.它支援繼承,擴展和JavaScript整合等高級方案,同時與AspNet Core的在地化系統完全兼容.", |
|||
"SettingManagementExplanation": "定義應用程式的設定,並根據當前設定,租戶和使用者在運行時獲取值.", |
|||
"ExtensionMethodsHelpersExplanation": "即使式瑣碎的程式碼部分,也不要重複.標準類型的擴展方法和助手使您的程式碼更加清晰和易於撰寫.", |
|||
"AspectOrientedProgrammingExplanation": "提供合適的基礎設施來建立動態代理並實現切面導向設計.攔截任何類別,並在每次方法執行之前和之後執行程式碼.", |
|||
"DependencyInjectionByConventionsExplanation": "無須手動註冊類別以進行依賴注入.按照習慣自動註冊常用服務類型.對於其他類型的服務,您可以使用介面和屬性來使其變得更輕鬆.", |
|||
"DataFilteringExplanation": "定義和使用資料過濾器,這些過濾器在您從資料庫中查詢實體時會自動應用.當您實現簡單的介面時,可立即使用軟刪除功能和多租戶過濾器.", |
|||
"PublishEvents": "發佈事件", |
|||
"HandleEvents": "處理事件", |
|||
"AndMore": "更多...", |
|||
"Code": "編碼", |
|||
"Result": "結果", |
|||
"SeeTheDocumentForMoreInformation": "查看<a href=\"{1}\">{0} 文件</a>獲得更多訊息", |
|||
"IndexPageHeroSection": "<span class=\"third-line shine2\"><strong>asp.net core的</strong></span><span class=\"first-line shine\"><strong>開源</strong></span><span class=\"second-line text-uppercase\">Web應用程式<br />框架 </span>", |
|||
"UiFramework": "UI框架", |
|||
"EmailAddress": "電子信箱地址" |
|||
} |
|||
} |
|||
@ -1,40 +0,0 @@ |
|||
# COMMON PATHS |
|||
|
|||
$rootFolder = (Get-Item -Path "./" -Verbose).FullName |
|||
|
|||
# List of solutions |
|||
|
|||
$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", |
|||
"templates/module/aspnet-core", |
|||
"templates/app/aspnet-core", |
|||
"abp_io/AbpIoLocalization" |
|||
) |
|||
|
|||
# Build all solutions |
|||
|
|||
foreach ($solutionPath in $solutionPaths) { |
|||
$solutionAbsPath = (Join-Path $rootFolder $solutionPath) |
|||
Set-Location $solutionAbsPath |
|||
dotnet build --configuration Release |
|||
if (-Not $?) { |
|||
Write-Host ("Build failed for the solution: " + $solutionPath) |
|||
Set-Location $rootFolder |
|||
exit $LASTEXITCODE |
|||
} |
|||
} |
|||
|
|||
Set-Location $rootFolder |
|||
@ -1,40 +0,0 @@ |
|||
# COMMON PATHS |
|||
|
|||
$rootFolder = (Get-Item -Path "./" -Verbose).FullName |
|||
|
|||
# List of solutions |
|||
|
|||
$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", |
|||
"templates/module/aspnet-core", |
|||
"templates/app/aspnet-core", |
|||
"abp_io/AbpIoLocalization" |
|||
) |
|||
|
|||
# Build all solutions |
|||
|
|||
foreach ($solutionPath in $solutionPaths) { |
|||
$solutionAbsPath = (Join-Path $rootFolder $solutionPath) |
|||
Set-Location $solutionAbsPath |
|||
dotnet build |
|||
if (-Not $?) { |
|||
Write-Host ("Build failed for the solution: " + $solutionPath) |
|||
Set-Location $rootFolder |
|||
exit $LASTEXITCODE |
|||
} |
|||
} |
|||
|
|||
Set-Location $rootFolder |
|||
@ -0,0 +1,16 @@ |
|||
. ".\common.ps1" |
|||
|
|||
# Build all solutions |
|||
|
|||
foreach ($solutionPath in $solutionPaths) { |
|||
$solutionAbsPath = (Join-Path $rootFolder $solutionPath) |
|||
Set-Location $solutionAbsPath |
|||
dotnet build --configuration Release |
|||
if (-Not $?) { |
|||
Write-Host ("Build failed for the solution: " + $solutionPath) |
|||
Set-Location $rootFolder |
|||
exit $LASTEXITCODE |
|||
} |
|||
} |
|||
|
|||
Set-Location $rootFolder |
|||
@ -0,0 +1,16 @@ |
|||
. ".\common.ps1" |
|||
|
|||
# Build all solutions |
|||
|
|||
foreach ($solutionPath in $solutionPaths) { |
|||
$solutionAbsPath = (Join-Path $rootFolder $solutionPath) |
|||
Set-Location $solutionAbsPath |
|||
dotnet build |
|||
if (-Not $?) { |
|||
Write-Host ("Build failed for the solution: " + $solutionPath) |
|||
Set-Location $rootFolder |
|||
exit $LASTEXITCODE |
|||
} |
|||
} |
|||
|
|||
Set-Location $rootFolder |
|||
@ -0,0 +1,26 @@ |
|||
# COMMON PATHS |
|||
|
|||
$rootFolder = (Get-Item -Path "./" -Verbose).FullName |
|||
|
|||
# List of solutions |
|||
|
|||
$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", |
|||
"../templates/module/aspnet-core", |
|||
"../templates/app/aspnet-core", |
|||
"../samples/MicroserviceDemo", |
|||
"../abp_io/AbpIoLocalization" |
|||
) |
|||
@ -0,0 +1,16 @@ |
|||
. ".\common.ps1" |
|||
|
|||
# Test all solutions |
|||
|
|||
foreach ($solutionPath in $solutionPaths) { |
|||
$solutionAbsPath = (Join-Path $rootFolder $solutionPath) |
|||
Set-Location $solutionAbsPath |
|||
dotnet test --no-build --no-restore |
|||
if (-Not $?) { |
|||
Write-Host ("Test failed for the solution: " + $solutionPath) |
|||
Set-Location $rootFolder |
|||
exit $LASTEXITCODE |
|||
} |
|||
} |
|||
|
|||
Set-Location $rootFolder |
|||
@ -0,0 +1,9 @@ |
|||
<Project> |
|||
<ItemGroup> |
|||
<PackageReference Include="ConfigureAwait.Fody" Version="3.3.1" /> |
|||
<PackageReference Include="Fody" Version="6.0.6"> |
|||
<PrivateAssets>all</PrivateAssets> |
|||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> |
|||
</PackageReference> |
|||
</ItemGroup> |
|||
</Project> |
|||
@ -1,74 +0,0 @@ |
|||
## Entity Framework Core PostgreSQL integrace |
|||
|
|||
> Podívejte se na [Entity Framework Core integrační dokument](../Entity-Framework-Core.md) pro základy integrace EF Core. |
|||
|
|||
### Aktualizace projektu EntityFrameworkCore |
|||
|
|||
- V projektu `Acme.BookStore.EntityFrameworkCore` nahraďte balík `Volo.Abp.EntityFrameworkCore.SqlServer` za `Volo.Abp.EntityFrameworkCore.PostgreSql` |
|||
- Aktualizace pro použití PostgreSQL v `BookStoreEntityFrameworkCoreModule` |
|||
- Nahraďte `AbpEntityFrameworkCoreSqlServerModule` za `AbpEntityFrameworkCorePostgreSqlModule` |
|||
- Nahraďte `options.UseSqlServer()` za `options.UsePostgreSql()` |
|||
- V jiných projektech aktualizujte PostgreSQL connection string v nezbytných `appsettings.json` souborech |
|||
- Více informací v [PostgreSQL connection strings](https://www.connectionstrings.com/postgresql/), v tomto dokumentu věnujte pozornost sekci `Npgsql` |
|||
|
|||
### Aktualizace projektu EntityFrameworkCore.DbMigrations |
|||
- Aktualizace pro použití PostgreSQL v `XXXMigrationsDbContextFactory` |
|||
- Nahraďte `new DbContextOptionsBuilder<XXXMigrationsDbContext>().UseSqlServer()` za `new DbContextOptionsBuilder<XXXMigrationsDbContext>().UseNpgsql()` |
|||
|
|||
|
|||
### Odstranění stávajících migrací |
|||
|
|||
Smažte všechny stavající migrační soubory (včetně `DbContextModelSnapshot`) |
|||
|
|||
 |
|||
|
|||
### Znovu vygenerujte počáteční migraci |
|||
|
|||
Nastavte správný spouštěcí projekt (obvykle web projekt) |
|||
|
|||
 |
|||
|
|||
Otevřete **Package Manager Console** (Tools -> Nuget Package Manager -> Package Manager Console), zvolte `.EntityFrameworkCore.DbMigrations` jako **Default project** a proveďte následující příkaz: |
|||
|
|||
Proveďte příkaz `Add-Migration`: |
|||
```` |
|||
PM> Add-Migration Initial |
|||
```` |
|||
|
|||
### Aktualizace databáze |
|||
|
|||
K vytvoření databáze máte dvě možnosti. |
|||
|
|||
#### Použití DbMigrator aplikace |
|||
|
|||
Řešení obsahuje konzolovou aplikaci (v tomto příkladu nazvanou `Acme.BookStore.DbMigrator`), která může vytvářet databáze, aplikovat migrace a vkládat seed data. Je užitečná jak pro vývojové, tak pro produkční prostředí. |
|||
|
|||
> Projekt `.DbMigrator` má vlastní `appsettings.json`. Takže pokud jste změnili connection string uvedený výše, musíte změnit také tento. |
|||
|
|||
Klikněte pravým na projekt `.DbMigrator` a vyberte **Set as StartUp Project**: |
|||
|
|||
 |
|||
|
|||
Zmáčkněte F5 (nebo Ctrl+F5) ke spuštění aplikace. Výstup bude vypadat následovně: |
|||
|
|||
 |
|||
|
|||
#### Použití EF Core Update-Database příkazu |
|||
|
|||
Ef Core má `Update-Database` příkaz, který v případě potřeby vytvoří databázi a aplikuje čekající migrace. |
|||
|
|||
Nastavte správný spouštěcí projekt (obvykle web projekt) |
|||
|
|||
 |
|||
|
|||
Otevřete **Package Manager Console** (Tools -> Nuget Package Manager -> Package Manager Console), vyberte projekt `.EntityFrameworkCore.DbMigrations` jako **Default Project** and spusťte následující příkaz: |
|||
|
|||
```` |
|||
PM> Update-Database |
|||
```` |
|||
|
|||
Dojde k vytvoření nové databáze na základě nakonfigurovaného connection stringu. |
|||
|
|||
 |
|||
|
|||
> Použití nástroje `.DbMigrator` je doporučený způsob, jelikož zároveň vloží seed data nutné k správnému běhu webové aplikace. |
|||
@ -0,0 +1,39 @@ |
|||
# Přepnutí na EF Core PostgreSQL providera |
|||
|
|||
Tento dokument vysvětluje, jak přepnout na poskytovatele databáze **PostgreSQL** pro **[spouštěcí šablonu aplikace](Startup-Templates/Application.md)**, která je dodávána s předem nakonfigurovaným SQL poskytovatelem. |
|||
|
|||
## Výměna balíku Volo.Abp.EntityFrameworkCore.SqlServer |
|||
|
|||
Projekt `.EntityFrameworkCore` v řešení závisí na NuGet balíku [Volo.Abp.EntityFrameworkCore.SqlServer](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore.SqlServer). Odstraňte tento balík a přidejte stejnou verzi balíku [Volo.Abp.EntityFrameworkCore.PostgreSql](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore.PostgreSql). |
|||
|
|||
## Nahrazení závislosti modulu |
|||
|
|||
Najděte třídu ***YourProjectName*EntityFrameworkCoreModule** v projektu `.EntityFrameworkCore`, odstraňte `typeof(AbpEntityFrameworkCoreSqlServerModule)` z atributu `DependsOn`, přidejte `typeof(AbpEntityFrameworkCorePostgreSqlModule)` (také nahraďte `using Volo.Abp.EntityFrameworkCore.SqlServer;` za `using Volo.Abp.EntityFrameworkCore.PostgreSql;`). |
|||
|
|||
## UsePostgreSql() |
|||
|
|||
Najděte volání `UseSqlServer()` v *YourProjectName*EntityFrameworkCoreModule.cs uvnitř projektu `.EntityFrameworkCore` a nahraďte za `UsePostgreSql()`. |
|||
|
|||
Najděte volání `UseSqlServer()` v *YourProjectName*MigrationsDbContextFactory.cs uvnitř projektu `.EntityFrameworkCore.DbMigrations` a nahraďte za `UseNpgsql()`. |
|||
|
|||
> V závislosti na struktuře řešení můžete najít více volání `UseSqlServer()`, které je třeba změnit. |
|||
|
|||
## Změna connection stringů |
|||
|
|||
PostgreSql connection stringy se od těch pro SQL Server liší. Je proto potřeba zkontrolovat všechny soubory `appsettings.json` v řešení a connection stringy v nich nahradit. Podívejte se na [connectionstrings.com](https://www.connectionstrings.com/postgresql/) pro více detailů o možnostech PostgreSql connection stringů. |
|||
|
|||
Typicky je potřeba změnit `appsettings.json` v projektech `.DbMigrator` a `.Web` projects, ale to záleží na vaší struktuře řešení. |
|||
|
|||
## Regenerace migrací |
|||
|
|||
Startovací šablona používá [Entity Framework Core Code First migrace](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/). EF Core migrace závisí na zvoleném DBMS poskytovateli. Tudíž změna DBMS poskytovatele způsobí selhání migrace. |
|||
* Smažte složku Migrations v projektu `.EntityFrameworkCore.DbMigrations` and znovu sestavte řešení. |
|||
* Spusťte `Add-Migration "Initial"` v Package Manager Console (je nutné zvolit `.DbMigrator` (nebo `.Web`) projekt jako startovací projekt v Solution Explorer a zvolit projekt `.EntityFrameworkCore.DbMigrations` jako výchozí v Package Manager Console). |
|||
|
|||
Tímto vytvoříte migraci databáze se všemi nakonfigurovanými databázovými objekty (tabulkami). |
|||
|
|||
Spusťte projekt `.DbMigrator` k vytvoření databáze a vložení počátečních dat. |
|||
|
|||
## Spuštění aplikace |
|||
|
|||
Vše je připraveno. Stačí už jen spustit aplikaci a užívat si kódování. |
|||
|
After Width: | Height: | Size: 9.4 KiB |
@ -0,0 +1,140 @@ |
|||
# Auto API Controllers |
|||
|
|||
Once you create an [application service](../Application-Services.md), you generally want to create an API controller to expose this service as an HTTP (REST) API endpoint. A typical API controller does nothing but redirects method calls to the application service and configures the REST API using attributes like [HttpGet], [HttpPost], [Route]... etc. |
|||
|
|||
ABP can **automagically** configure your application services as API Controllers by convention. Most of time you don't care about its detailed configuration, but it's possible to fully customize it. |
|||
|
|||
## Configuration |
|||
|
|||
Basic configuration is simple. Just configure `AbpAspNetCoreMvcOptions` and use `ConventionalControllers.Create` method as shown below: |
|||
|
|||
````csharp |
|||
[DependsOn(BookStoreApplicationModule)] |
|||
public class BookStoreWebModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<AbpAspNetCoreMvcOptions>(options => |
|||
{ |
|||
options |
|||
.ConventionalControllers |
|||
.Create(typeof(BookStoreApplicationModule).Assembly); |
|||
}); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
This example code configures all the application services in the assembly containing the class `BookStoreApplicationModule`. The figure below shows the resulting API on the [Swagger UI](https://swagger.io/tools/swagger-ui/). |
|||
|
|||
 |
|||
|
|||
### Examples |
|||
|
|||
Some example method names and the corresponding routes calculated by convention: |
|||
|
|||
| Service Method Name | HTTP Method | Route | |
|||
| ----------------------------------------------------- | ----------- | -------------------------- | |
|||
| GetAsync(Guid id) | GET | /api/app/book/{id} | |
|||
| GetListAsync() | GET | /api/app/book | |
|||
| CreateAsync(CreateBookDto input) | POST | /api/app/book | |
|||
| UpdateAsync(Guid id, UpdateBookDto input) | PUT | /api/app/book/{id} | |
|||
| DeleteAsync(Guid id) | DELETE | /api/app/book/{id} | |
|||
| GetEditorsAsync(Guid id) | GET | /api/app/book/{id}/editors | |
|||
| CreateEditorAsync(Guid id, BookEditorCreateDto input) | POST | /api/app/book/{id}/editor | |
|||
|
|||
### HTTP Method |
|||
|
|||
ABP uses a naming convention while determining the HTTP method for a service method (action): |
|||
|
|||
- **Get**: Used if the method name starts with 'GetList', 'GetAll' or 'Get'. |
|||
- **Put**: Used if the method name starts with 'Put' or 'Update'. |
|||
- **Delete**: Used if the method name starts with 'Delete' or 'Remove'. |
|||
- **Post**: Used if the method name starts with 'Create', 'Add', 'Insert' or 'Post'. |
|||
- **Patch**: Used if the method name starts with 'Patch'. |
|||
- Otherwise, **Post** is used **by default**. |
|||
|
|||
If you need to customize HTTP method for a particular method, then you can use one of the standard ASP.NET Core attributes ([HttpPost], [HttpGet], [HttpPut]... etc.). This requires to add [Microsoft.AspNetCore.Mvc.Core](https://www.nuget.org/packages/Microsoft.AspNetCore.Mvc.Core) nuget package to your project that contains the service. |
|||
|
|||
### Route |
|||
|
|||
Route is calculated based on some conventions: |
|||
|
|||
* It always starts with '**/api**'. |
|||
* Continues with a **route path**. Default value is '**/app**' and can be configured as like below: |
|||
|
|||
````csharp |
|||
Configure<AbpAspNetCoreMvcOptions>(options => |
|||
{ |
|||
options.ConventionalControllers |
|||
.Create(typeof(BookStoreApplicationModule).Assembly, opts => |
|||
{ |
|||
opts.RootPath = "volosoft/book-store"; |
|||
}); |
|||
}); |
|||
```` |
|||
|
|||
Then the route for getting a book will be '**/api/volosoft/book-store/book/{id}**'. This sample uses two-level root path, but you generally use a single level depth. |
|||
|
|||
* Continues with the **normalized controller/service name**. Normalization removes 'AppService', 'ApplicationService' and 'Service' postfixes and converts it to **camelCase**. If your application service class name is 'BookAppService' then it becomes only '/book'. |
|||
* If you want to customize naming, then set the `UrlControllerNameNormalizer` option. It's a func delegate which allows you to determine the name per controller/service. |
|||
* If the method has an '**id**' parameter then it adds '**/{id}**' ro the route. |
|||
* Then it adds the action name if necessary. Action name is obtained from the method name on the service and normalized by; |
|||
* Removing '**Async**' postfix. If the method name is 'GetPhonesAsync' then it becomes 'GetPhones'. |
|||
* Removing **HTTP method prefix**. 'GetList', 'GetAll', 'Get', 'Put', 'Update', 'Delete', 'Remove', 'Create', 'Add', 'Insert', 'Post' and 'Patch' prefixes are removed based on the selected HTTP method. So, 'GetPhones' becomes 'Phones' since 'Get' prefix is a duplicate for a GET request. |
|||
* Converting the result to **camelCase**. |
|||
* If the resulting action name is **empty** then it's not added to the route. If it's not empty, it's added to the route (like '/phones'). For 'GetAllAsync' method name it will be empty, for 'GetPhonesAsync' method name it will be 'phones'. |
|||
* Normalization can be customized by setting the `UrlActionNameNormalizer` option. It's an action delegate that is called for every method. |
|||
* If there is another parameter with 'Id' postfix, then it's also added to the route as the final route segment (like '/phoneId'). |
|||
|
|||
## Service Selection |
|||
|
|||
Creating conventional HTTP API controllers are not unique to application services actually. |
|||
|
|||
### IRemoteService Interface |
|||
|
|||
If a class implements the `IRemoteService` interface then it's automatically selected to be a conventional API controller. Since application services inherently implement it, they are considered as natural API controllers. |
|||
|
|||
### RemoteService Attribute |
|||
|
|||
`RemoteService` attribute can be used to mark a class as a remote service or disable for a particular class that inherently implements the `IRemoteService` interface. Example: |
|||
|
|||
````csharp |
|||
[RemoteService(IsEnabled = false)] //or simply [RemoteService(false)] |
|||
public class PersonAppService : ApplicationService |
|||
{ |
|||
|
|||
} |
|||
```` |
|||
|
|||
### TypePredicate Option |
|||
|
|||
You can further filter classes to become an API controller by providing the `TypePredicate` option: |
|||
|
|||
````csharp |
|||
services.Configure<AbpAspNetCoreMvcOptions>(options => |
|||
{ |
|||
options.ConventionalControllers |
|||
.Create(typeof(BookStoreApplicationModule).Assembly, opts => |
|||
{ |
|||
opts.TypePredicate = type => { return true; }; |
|||
}); |
|||
}); |
|||
```` |
|||
|
|||
Instead of returning `true` for every type, you can check it and return `false` if you don't want to expose this type as an API controller. |
|||
|
|||
## API Explorer |
|||
|
|||
API Exploring a service that makes possible to investigate API structure by the clients. Swagger uses it to create a documentation and test UI for an endpoint. |
|||
|
|||
API Explorer is automatically enabled for conventional HTTP API controllers by default. Use `RemoteService` attribute to control it per class or method level. Example: |
|||
|
|||
````csharp |
|||
[RemoteService(IsMetadataEnabled = false)] |
|||
public class PersonAppService : ApplicationService |
|||
{ |
|||
|
|||
} |
|||
```` |
|||
|
|||
Disabled `IsMetadataEnabled` which hides this service from API explorer and it will not be discoverable. However, it still can be usable for the clients know the exact API path/route. |
|||
@ -0,0 +1,165 @@ |
|||
# Dynamic C# API Clients |
|||
|
|||
ABP can dynamically create C# API client proxies to call remote HTTP services (REST APIs). In this way, you don't need to deal with `HttpClient` and other low level HTTP features to call remote services and get results. |
|||
|
|||
## Service Interface |
|||
|
|||
Your service/controller should implement an interface that is shared between the server and the client. So, first define a service interface in a shared library project. Example: |
|||
|
|||
````csharp |
|||
public interface IBookAppService : IApplicationService |
|||
{ |
|||
Task<List<BookDto>> GetListAsync(); |
|||
} |
|||
```` |
|||
|
|||
Your interface should implement the `IRemoteService` interface to be automatically discovered. Since the `IApplicationService` inherits the `IRemoteService` interface, the `IBookAppService` above satisfies this condition. |
|||
|
|||
Implement this class in your service application. You can use [auto API controller system](Auto-API-Controllers.md) to expose the service as a REST API endpoint. |
|||
|
|||
## Client Proxy Generation |
|||
|
|||
First, add [Volo.Abp.Http.Client](https://www.nuget.org/packages/Volo.Abp.Http.Client) nuget package to your client project: |
|||
|
|||
```` |
|||
Install-Package Volo.Abp.Http.Client |
|||
```` |
|||
|
|||
Then add `AbpHttpClientModule` dependency to your module: |
|||
|
|||
````csharp |
|||
[DependsOn(typeof(AbpHttpClientModule))] //add the dependency |
|||
public class MyClientAppModule : AbpModule |
|||
{ |
|||
} |
|||
```` |
|||
|
|||
Now, it's ready to create the client proxies. Example: |
|||
|
|||
````csharp |
|||
[DependsOn( |
|||
typeof(AbpHttpClientModule), //used to create client proxies |
|||
typeof(BookStoreApplicationModule) //contains the application service interfaces |
|||
)] |
|||
public class MyClientAppModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
//Create dynamic client proxies |
|||
context.Services.AddHttpClientProxies( |
|||
typeof(BookStoreApplicationModule).Assembly |
|||
); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
`AddHttpClientProxies` method gets an assembly, finds all service interfaces in the given assembly, creates and registers proxy classes. |
|||
|
|||
### Endpoint Configuration |
|||
|
|||
`RemoteServices` section in the `appsettings.json` file is used to get remote service address by default. Simplest configuration is shown below: |
|||
|
|||
```` |
|||
{ |
|||
"RemoteServices": { |
|||
"Default": { |
|||
"BaseUrl": "http://localhost:53929/" |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
See the "AbpRemoteServiceOptions" section below for more detailed configuration. |
|||
|
|||
## Usage |
|||
|
|||
It's straightforward to use. Just inject the service interface in the client application code: |
|||
|
|||
````csharp |
|||
public class MyService : ITransientDependency |
|||
{ |
|||
private readonly IBookAppService _bookService; |
|||
|
|||
public MyService(IBookAppService bookService) |
|||
{ |
|||
_bookService = bookService; |
|||
} |
|||
|
|||
public async Task DoIt() |
|||
{ |
|||
var books = await _bookService.GetListAsync(); |
|||
foreach (var book in books) |
|||
{ |
|||
Console.WriteLine($"[BOOK {book.Id}] Name={book.Name}"); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
This sample injects the `IBookAppService` service interface defined above. The dynamic client proxy implementation makes an HTTP call whenever a service method is called by the client. |
|||
|
|||
### IHttpClientProxy Interface |
|||
|
|||
While you can inject `IBookAppService` like above to use the client proxy, you could inject `IHttpClientProxy<IBookAppService>` for a more explicit usage. In this case you will use the `Service` property of the `IHttpClientProxy<T>` interface. |
|||
|
|||
## Configuration |
|||
|
|||
### AbpRemoteServiceOptions |
|||
|
|||
`AbpRemoteServiceOptions` is automatically set from the `appsettings.json` by default. Alternatively, you can use `Configure` method to set or override it. Example: |
|||
|
|||
````csharp |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
context.Services.Configure<AbpRemoteServiceOptions>(options => |
|||
{ |
|||
options.RemoteServices.Default = |
|||
new RemoteServiceConfiguration("http://localhost:53929/"); |
|||
}); |
|||
|
|||
//... |
|||
} |
|||
```` |
|||
|
|||
### Multiple Remote Service Endpoints |
|||
|
|||
The examples above have configured the "Default" remote service endpoint. You may have different endpoints for different services (as like in a microservice approach where each microservice has different endpoints). In this case, you can add other endpoints to your configuration file: |
|||
|
|||
````json |
|||
{ |
|||
"RemoteServices": { |
|||
"Default": { |
|||
"BaseUrl": "http://localhost:53929/" |
|||
}, |
|||
"BookStore": { |
|||
"BaseUrl": "http://localhost:48392/" |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
`AddHttpClientProxies` method can get an additional parameter for the remote service name. Example: |
|||
|
|||
````csharp |
|||
context.Services.AddHttpClientProxies( |
|||
typeof(BookStoreApplicationModule).Assembly, |
|||
remoteServiceName: "BookStore" |
|||
); |
|||
```` |
|||
|
|||
`remoteServiceName` parameter matches the service endpoint configured via `AbpRemoteServiceOptions`. If the `BookStore` endpoint is not defined then it fallbacks to the `Default` endpoint. |
|||
|
|||
### As Default Services |
|||
|
|||
When you create a service proxy for `IBookAppService`, you can directly inject the `IBookAppService` to use the proxy client (as shown in the usage section). You can pass `asDefaultServices: false` to the `AddHttpClientProxies` method to disable this feature. |
|||
|
|||
````csharp |
|||
context.Services.AddHttpClientProxies( |
|||
typeof(BookStoreApplicationModule).Assembly, |
|||
asDefaultServices: false |
|||
); |
|||
```` |
|||
|
|||
Using `asDefaultServices: false` may only be needed if your application has already an implementation of the service and you do not want to override/replace the other implementation by your client proxy. |
|||
|
|||
> If you disable `asDefaultServices`, you can only use `IHttpClientProxy<T>` interface to use the client proxies (see the related section above). |
|||
@ -0,0 +1,3 @@ |
|||
# abp.auth JavaScript API |
|||
|
|||
TODO |
|||
@ -0,0 +1,24 @@ |
|||
# JavaScript API |
|||
|
|||
ABP provides some JavaScript APIs for ASP.NET Core MVC / Razor Pages applications. They can be used to perform some common application requirements in the client side. |
|||
|
|||
## APIs |
|||
|
|||
* abp.ajax |
|||
* [abp.auth](Auth.md) |
|||
* abp.currentUser |
|||
* abp.dom |
|||
* abp.event |
|||
* abp.features |
|||
* abp.localization |
|||
* abp.log |
|||
* abp.ModalManager |
|||
* abp.notify |
|||
* abp.security |
|||
* abp.setting |
|||
* abp.ui |
|||
* abp.utils |
|||
* abp.ResourceLoader |
|||
* abp.WidgetManager |
|||
* Other APIs |
|||
|
|||
@ -0,0 +1,3 @@ |
|||
## Ambient Context Pattern |
|||
|
|||
TODO |
|||
@ -0,0 +1,734 @@ |
|||
# ASP.NET Boilerplate v5+ to ABP Framework Migration |
|||
|
|||
ABP Framework is **the successor** of the open source [ASP.NET Boilerplate](https://aspnetboilerplate.com/) framework. This guide aims to help you to **migrate your existing solutions** (you developed with the ASP.NET Boilerplate framework) to the ABP Framework. |
|||
|
|||
## Introduction |
|||
|
|||
**ASP.NET Boilerplate** is being **actively developed** [since 2013](https://github.com/aspnetboilerplate/aspnetboilerplate/graphs/contributors). It is loved, used and contributed by the community. It started as a side project of [a developer](http://halilibrahimkalkan.com/), but now it is officially maintained and improved by the company [Volosoft](https://volosoft.com/) in addition to the great community support. |
|||
|
|||
ABP Framework has the same goal of the ASP.NET Boilerplate framework: **Don't Repeat Yourself**! It provides infrastructure, tools and startup templates to make a developer's life easier while developing enterprise software solutions. |
|||
|
|||
See [the introduction blog post](https://blog.abp.io/abp/Abp-vNext-Announcement) if you wonder why we needed to re-write the ASP.NET Boilerplate framework. |
|||
|
|||
### Should I Migrate? |
|||
|
|||
No, you don't have to! |
|||
|
|||
* ASP.NET Boilerplate is still in active development and maintenance. |
|||
* It also works on the latest ASP.NET Core and related libraries and tools. It is up to date. |
|||
|
|||
However, if you want to take the advantage of the new ABP Framework [features](https://abp.io/features) and the new architecture opportunities (like support for NoSQL databases, microservice compatibility, advanced modularity), you can use this document as a guide. |
|||
|
|||
### What About the ASP.NET Zero? |
|||
|
|||
[ASP.NET Zero](https://aspnetzero.com/) is a commercial product developed by the core ASP.NET Boilerplate team, on top of the ASP.NET Boilerplate framework. It provides pre-built application [features](https://aspnetzero.com/Features), code generation tooling and a nice looking modern UI. It is trusted and used by thousands of companies from all around the World. |
|||
|
|||
We have created the [ABP Commercial](https://commercial.abp.io/) as an alternative to the ASP.NET Zero. ABP Commercial is more modular and upgradeable compared to the ASP.NET Zero. It currently has less features compared to ASP.NET Zero, but the gap will be closed by the time (it also has some features don't exist in the ASP.NET Zero). |
|||
|
|||
We think ASP.NET Zero is still a good choice while starting a new application. It is production ready and mature solution delivered as a full source code. It is being actively developed and we are constantly adding new features. |
|||
|
|||
We don't suggest to migrate your ASP.NET Zero based solution to the ABP Commercial if; |
|||
|
|||
* Your ASP.NET Zero solution is mature and it is in maintenance rather than a rapid development. |
|||
* You don't have enough development time to perform the migration. |
|||
* A monolithic solution fits in your business. |
|||
* You've customized existing ASP.NET Zero features too much based on your requirements. |
|||
|
|||
We also suggest you to compare the features of two products based on your needs. |
|||
|
|||
If you have an ASP.NET Zero based solution and want to migrate to the ABP Commercial, this guide will also help you. |
|||
|
|||
### ASP.NET MVC 5.x Projects |
|||
|
|||
The ABP Framework doesn't support ASP.NET MVC 5.x, it only works with ASP.NET Core. So, if you migrate your ASP.NET MVC 5.x based projects, you will also deal with the .NET Core migration. |
|||
|
|||
## The Migration Progress |
|||
|
|||
We've designed the ABP Framework by **getting the best parts** of the ASP.NET Boilerplate framework, so it will be familiar to you if you've developed ASP.NET Boilerplate based applications. |
|||
|
|||
In the ASP.NET Boilerplate, we have not worked much on the UI side, but used some free themes (we've used [metronic theme](https://keenthemes.com/metronic/) for ASP.NET Zero on the other side). In the ABP Framework, we worked a lot on the UI side (especially for the MVC / Razor Pages UI, because Angular already has a good modular system of its own). So, the **most challenging part** of the migration will be the **User Interface** of your solution. |
|||
|
|||
ABP Framework is (and ASP.NET Boilerplate was) designed based on the [Domain Driven Design](https://docs.abp.io/en/abp/latest/Domain-Driven-Design) patterns & principles and the startup templates are layered based on the DDD layers. So, this guide respects to that layering model and explains the migration layer by layer. |
|||
|
|||
## Creating the Solution |
|||
|
|||
First step of the migration is to create a new solution. We suggest you to create a fresh new project using [the startup templates](https://abp.io/get-started) (see [this document](https://docs.abp.io/en/commercial/latest/getting-started) for the ABP Commercial). |
|||
|
|||
After creating the project and running the application, you can copy your code from your existing solution to the new solution step by step, layer by layer. |
|||
|
|||
### About Pre-Built Modules |
|||
|
|||
The startup projects for the ABP Framework use the [pre-built modules](https://docs.abp.io/en/abp/latest/Modules/Index) (not all of them, but the essentials) and themes as NuGet/NPM packages. So, you don't see the source code of the modules/themes in your solution. This has an advantage that you can easily update these packages when a new version is released. However, you can not easily customize them as their source code in your hands. |
|||
|
|||
We suggest to continue to use these modules as package references, in this way you can get new features easily (see [abp update command](https://docs.abp.io/en/abp/latest/CLI#update)). In this case, you have a few options to customize or extend the functionality of the used modules; |
|||
|
|||
* You can create your own entity and share the same database table with an entity in a used module. An example of this is the `AppUser` entity comes in the startup template. |
|||
* You can [replace](https://docs.abp.io/en/abp/latest/Dependency-Injection#replace-a-service) a domain service, application service, controller, page model or other types of services with your own implementation. We suggest you to inherit from the existing implementation and override the method you need. |
|||
* You can replace a `.cshtml` view, page, view component, partial view... with your own one using the [Virtual File System](https://docs.abp.io/en/abp/latest/Virtual-File-System). |
|||
* You can override javascript, css, image or any other type of static files using the [Virtual File System](https://docs.abp.io/en/abp/latest/Virtual-File-System). |
|||
|
|||
More extend/customization options will be developed and documented by the time. However, if you need to fully change the module implementation, it is best to add the [source code](https://github.com/abpframework/abp/tree/dev/modules) of the related module into your own solution and remove the package dependencies. |
|||
|
|||
The source code of the modules and the themes are [MIT](https://opensource.org/licenses/MIT) licensed, you can fully own and customize it without any limitation (for the ABP Commercial, you can download the source code of a [module](https://commercial.abp.io/modules)/[theme](https://commercial.abp.io/themes) if you have a [license](https://commercial.abp.io/pricing) type that includes the source code). |
|||
|
|||
## The Domain Layer |
|||
|
|||
Most of your domain layer code will remain same, while you need to perform some minor changes in your domain objects. |
|||
|
|||
### Aggregate Roots & Entities |
|||
|
|||
The ABP Framework and the ASP.NET Boilerplate both have the `IEntity` and `IEntity<T>` interfaces and `Entity` and `Entity<T>` base classes to define entities but they have some differences. |
|||
|
|||
If you have an entity in the ASP.NET Boilerplate application like that: |
|||
|
|||
````csharp |
|||
public class Person : Entity //Default PK is int for the ASP.NET Boilerplate |
|||
{ |
|||
... |
|||
} |
|||
```` |
|||
|
|||
Then your primary key (the `Id` property in the base class) is `int` which is the **default primary key** (PK) type for the ASP.NET Boilerplate. If you want to set another type of PK, you need to explicitly declare it: |
|||
|
|||
````csharp |
|||
public class Person : Entity<Guid> //Set explicit PK in the ASP.NET Boilerplate |
|||
{ |
|||
... |
|||
} |
|||
```` |
|||
|
|||
ABP Framework behaves differently and expects to **always explicitly set** the PK type: |
|||
|
|||
````csharp |
|||
public class Person : Entity<Guid> //Set explicit PK in the ASP.NET Boilerplate |
|||
{ |
|||
... |
|||
} |
|||
```` |
|||
|
|||
`Id` property (and the corresponding PK in the database) will be `Guid` in this case. |
|||
|
|||
#### Composite Primary Keys |
|||
|
|||
ABP Framework also has a non-generic `Entity` base class, but this time it has no `Id` property. Its purpose is to allow you to create entities with composite PKs. See [the documentation](https://docs.abp.io/en/abp/latest/Entities#entities-with-composite-keys) to learn more about the composite PKs. |
|||
|
|||
#### Aggregate Root |
|||
|
|||
It is best practice now to use the `AggregateRoot` base class instead of `Entity` for aggregate root entities. See [the documentation](https://docs.abp.io/en/abp/latest/Entities#aggregateroot-class) to learn more about the aggregate roots. |
|||
|
|||
In opposite to the ASP.NET Boilerplate, the ABP Framework creates default repositories (`IRepository<T>`) **only for the aggregate roots**. It doesn't create for other types derived from the `Entity`. |
|||
|
|||
If you still want to create default repositories for all entity types, find the *YourProjectName*EntityFrameworkCoreModule class in your solution and change `options.AddDefaultRepositories()` to `options.AddDefaultRepositories(includeAllEntities: true)` (it may be already like that for the application startup template). |
|||
|
|||
#### Migrating the Existing Entities |
|||
|
|||
We suggest & use the GUID as the PK type for all the ABP Framework modules. However, you can continue to use your existing PK types to migrate your database tables easier. |
|||
|
|||
The challenging part will be the primary keys of the ASP.NET Boilerplate related entities, like Users, Roles, Tenants, Settings... etc. Our suggestion is to copy data from existing database to the new database tables using a tool or in a manual way (be careful about the foreign key values). |
|||
|
|||
#### Documentation |
|||
|
|||
See the documentation for details on the entities: |
|||
|
|||
* [ASP.NET Boilerplate - Entity documentation](https://aspnetboilerplate.com/Pages/Documents/Entities) |
|||
* [ABP Framework - Entity documentation](https://docs.abp.io/en/abp/latest/Entities) |
|||
|
|||
### Repositories |
|||
|
|||
> ABP Framework creates default repositories (`IRepository<T>`) **only for the aggregate roots**. It doesn't create for other types derived from the `Entity`. See the "Aggregate Root" section above for more information. |
|||
|
|||
The ABP Framework and the ASP.NET Boilerplate both have the default generic repository system, but has some differences. |
|||
|
|||
#### Injecting the Repositories |
|||
|
|||
In the ASP.NET Boilerplate, there are two default repository interfaces you can directly inject and use: |
|||
|
|||
* `IRepository<TEntity>` (e.g. `IRepository<Person>`) is used for entities with `int` primary key (PK) which is the default PK type. |
|||
* `IRepository<TEntity, TKey>` (e.g. `IRepository<Person, Guid>`) is used for entities with other types of PKs. |
|||
|
|||
ABP Framework doesn't have a default PK type, so you need to **explicitly declare the PK type** of your entity, like `IRepository<Person, int>` or `IRepository<Person, Guid>`. |
|||
|
|||
ABP Framework also has the `IRepository<TEntity>` (without PK), but it is mostly used when your entity has a composite PK (because this repository has no methods work with the `Id` property). See [the documentation](https://docs.abp.io/en/abp/latest/Entities#entities-with-composite-keys) to learn more about the **composite PKs**. |
|||
|
|||
#### Restricted Repositories |
|||
|
|||
ABP Framework additionally provides a few repository interfaces: |
|||
|
|||
* `IBasicRepository<TEntity, TKey>` has the same methods with the `IRepository` except it doesn't have `IQueryable` support. It can be useful if you don't want to expose complex querying code to the application layer. In this case, you typically want to create custom repositories to encapsulate the querying logic. It is also useful for database providers those don't support `IQueryable`. |
|||
* `IReadOnlyRepository<TEntity,TKey>` has the methods get data from the database, but doesn't contain any method change the database. |
|||
* `IReadOnlyBasicRepository<TEntity, TKey>` is similar to the read only repository but also doesn't support `IQueryable`. |
|||
|
|||
All the interfaces also have versions without `TKey` (like ``IReadOnlyRepository<TEntity>`) those can be used for composite PKs just like explained above. |
|||
|
|||
#### GetAll() vs IQueryable |
|||
|
|||
ASP.NET Boilerplate's repository has a `GetAll()` method that is used to obtain an `IQueryable` object to execute LINQ on it. An example application service calls the `GetAll()` method: |
|||
|
|||
````csharp |
|||
public class PersonAppService : ApplicationService, IPersonAppService |
|||
{ |
|||
private readonly IRepository<Person, Guid> _personRepository; |
|||
|
|||
public PersonAppService(IRepository<Person, Guid> personRepository) |
|||
{ |
|||
_personRepository = personRepository; |
|||
} |
|||
|
|||
public async Task DoIt() |
|||
{ |
|||
var people = await _personRepository |
|||
.GetAll() //GetAll() returns IQueryable |
|||
.Where(p => p.BirthYear > 2000) //Use LINQ extension methods |
|||
.ToListAsync(); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
ABP Framework's repository doesn't have this method. Instead, it implements the `IQueryable` itself. So, you can directly use LINQ on the repository: |
|||
|
|||
````csharp |
|||
public class PersonAppService : ApplicationService, IPersonAppService |
|||
{ |
|||
private readonly IRepository<Person, Guid> _personRepository; |
|||
|
|||
public PersonAppService(IRepository<Person, Guid> personRepository) |
|||
{ |
|||
_personRepository = personRepository; |
|||
} |
|||
|
|||
public async Task DoIt() |
|||
{ |
|||
var people = await _personRepository |
|||
.Where(p => p.BirthYear > 2000) //Use LINQ extension methods |
|||
.ToListAsync(); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
> Note that in order to use the async LINQ extension methods (like `ToListAsync` here), you may need to depend on the database provider (like EF Core) since these methods are defined in the database provider package, they are not standard LINQ methods. |
|||
|
|||
#### FirstOrDefault(predicate), Single()... Methods |
|||
|
|||
ABP Framework repository has not such methods get predicate (expression) since the repository itself is `IQueryable` and all these methods are already standard LINQ extension methods those can be directly used. |
|||
|
|||
However, it provides the following methods those can be used to query a single entity by its Id: |
|||
|
|||
* `FindAsync(id)` returns the entity or null if not found. |
|||
* `GetAsync(id)` method returns the entity or throws an `EntityNotFoundException` (which causes HTTP 404 status code) if not found. |
|||
|
|||
#### Sync vs Async |
|||
|
|||
ABP Framework repository has no sync methods (like `Insert`). All the methods are async (like `InsertAsync`). So, if your application has sync repository method usages, convert them to async versions. |
|||
|
|||
In general, ABP Framework forces you to completely use async everywhere, because mixing async & sync methods is not a recommended approach. |
|||
|
|||
#### Documentation |
|||
|
|||
See the documentation for details on the repositories: |
|||
|
|||
* [ASP.NET Boilerplate - Repository documentation](https://aspnetboilerplate.com/Pages/Documents/Repositories) |
|||
* [ABP Framework - Repository documentation](https://docs.abp.io/en/abp/latest/Repositories) |
|||
|
|||
### Domain Services |
|||
|
|||
Your domain service logic mostly remains same on the migration. ABP Framework also defines the base `DomainService` class and the `IDomainService` interface just works like the ASP.NET Boilerplate. |
|||
|
|||
## The Application Layer |
|||
|
|||
Your application service logic remains similar on the migration. ABP Framework also defines the base `ApplicationService` class and the `IApplicationService` interface just works like the ASP.NET Boilerplate, but there are some differences in details. |
|||
|
|||
### Declarative Authorization |
|||
|
|||
ASP.NET Boilerplate has `AbpAuthorize` and `AbpMvcAuthorize` attributes for declarative authorization. Example usage: |
|||
|
|||
````csharp |
|||
[AbpAuthorize("MyUserDeletionPermissionName")] |
|||
public async Task DeleteUserAsync(...) |
|||
{ |
|||
... |
|||
} |
|||
```` |
|||
|
|||
ABP Framework doesn't has such a custom attribute. It uses the standard `Authorize` attribute in all layers. |
|||
|
|||
````csharp |
|||
[Authorize("MyUserDeletionPermissionName")] |
|||
public async Task DeleteUserAsync(...) |
|||
{ |
|||
... |
|||
} |
|||
```` |
|||
|
|||
This is possible with the better integration to the Microsoft Authorization Extensions libraries. See the Authorization section below for more information about the authorization system. |
|||
|
|||
### CrudAppService and AsyncCrudAppService Classes |
|||
|
|||
ASP.NET Boilerplate has `CrudAppService` (with sync service methods) and `AsyncCrudAppService` (with async service methods) classes. |
|||
|
|||
ABP Framework only has the `CrudAppService` which actually has only the async methods (instead of sync methods). |
|||
|
|||
ABP Framework's `CrudAppService` method signatures are slightly different than the old one. For example, old update method signature was ` Task<TEntityDto> UpdateAsync(TUpdateInput input) ` while the new one is ` Task<TGetOutputDto> UpdateAsync(TKey id, TUpdateInput input) `. The main difference is that it gets the Id of the updating entity as a separate parameter instead of including in the input DTO. |
|||
|
|||
### Data Transfer Objects (DTOs) |
|||
|
|||
There are similar base DTO classes (like `EntityDto`) in the ABP Framework too. So, you can find the corresponding DTO base class if you need. |
|||
|
|||
#### Validation |
|||
|
|||
You can continue to use the data annotation attributes to validate your DTOs just like in the ASP.NET Boilerplate. |
|||
|
|||
ABP Framework doesn't include the ` ICustomValidate ` that does exists in the ASP.NET Boilerplate. Instead, you should implement the standard `IValidatableObject` interface for your custom validation logic. |
|||
|
|||
## The Infrastructure Layer |
|||
|
|||
### Namespaces |
|||
|
|||
ASP.NET Boilerplate uses the `Abp.*` namespaces while the ABP Framework uses the `Volo.Abp.*` namespaces for the framework and pre-built fundamental modules. |
|||
|
|||
In addition, there are also some pre-built application modules (like docs and blog modules) those are using the `Volo.*` namespaces (like `Volo.Blogging.*` and `Volo.Docs.*`). We consider these modules as standalone open source products developed by Volosoft rather than add-ons or generic modules completing the ABP Framework and used in the applications. We've developed them as a module to make them re-usable as a part of a bigger solution. |
|||
|
|||
### Module System |
|||
|
|||
Both of the ASP.NET Boilerplate and the ABP Framework have the `AbpModule` while they are a bit different. |
|||
|
|||
ASP.NET Boilerplate's `AbpModule` class has `PreInitialize`, `Initialize` and `PostInitialize` methods you can override and configure the framework and the depended modules. You can also register and resolve dependencies in these methods. |
|||
|
|||
ABP Framework's `AbpModule` class has the `ConfigureServices` and `OnApplicationInitialization` methods (and their Pre and Post versions). It is similar to ASP.NET Core's Startup class. You configure other services and register dependencies in the `ConfigureServices`. However, you can now resolve dependencies in that point. You can resolve dependencies and configure the ASP.NET Core pipeline in the `OnApplicationInitialization` method while you can not register dependencies here. So, the new module classes separate dependency registration phase from dependency resolution phase since it follows the ASP.NET Core's approach. |
|||
|
|||
### Dependency Injection |
|||
|
|||
#### The DI Framework |
|||
|
|||
ASP.NET Boilerplate is using the [Castle Windsor](http://www.castleproject.org/projects/windsor/) as the dependency injection framework. This is a fundamental dependency of the ASP.NET Boilerplate framework. We've got a lot of feedback to make the ASP.NET Boilerplate DI framework agnostic, but it was not so easy because of the design. |
|||
|
|||
ABP Framework is dependency injection framework independent since it uses Microsoft's [Dependency Injection Extensions](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection) library as an abstraction. None of the ABP Framework or module packages depends on any specific library. |
|||
|
|||
However, ABP Framework doesn't use the Microsoft's base DI library because it has some missing features ABP Framework needs to: Property Injection and Interception. All the startup templates and the samples are using the [Autofac](https://autofac.org/) as the DI library and it is the only [officially integrated](Autofac-Integration.md) library to the ABP Framework. We suggest you to use the Autofac with the ABP Framework if you have not a good reason. If you have a good reason, please create an [issue](https://github.com/abpframework/abp/issues/new) on GitHub to request it or just implement it and send a pull request :) |
|||
|
|||
#### Registering the Dependencies |
|||
|
|||
Registering the dependencies are similar and mostly handled by the framework conventionally (like repositories, application services, controllers... etc). Implement the same `ITransientDependency`, `ISingletonDependency` and `IScopedDependency` interfaces for the services not registered by conventions. |
|||
|
|||
When you need to manually register dependencies, use the `context.Services` in the `ConfigureServices` method of your module. Example: |
|||
|
|||
````csharp |
|||
public class BlogModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
//Register an instance as singleton |
|||
context.Services.AddSingleton<TaxCalculator>(new TaxCalculator(taxRatio: 0.18)); |
|||
|
|||
//Register a factory method that resolves from IServiceProvider |
|||
context.Services.AddScoped<ITaxCalculator>( |
|||
sp => sp.GetRequiredService<TaxCalculator>() |
|||
); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
See the ABP Framework [dependency injection document](https://docs.abp.io/en/abp/latest/Dependency-Injection) for details. |
|||
|
|||
### Configuration vs Options System |
|||
|
|||
ASP.NET Boilerplate has its own configuration system to configure the framework and the modules. For example, you could disable the audit logging in the `Initialize` method of your [module](https://aspnetboilerplate.com/Pages/Documents/Module-System): |
|||
|
|||
````csharp |
|||
public override void Initialize() |
|||
{ |
|||
Configuration.Auditing.IsEnabled = false; |
|||
} |
|||
```` |
|||
|
|||
ABP Framework uses [the options pattern](Options.md) to configure the framework and the modules. You typically configure the options in the `ConfigureServices` method of your [module](Module-Development-Basics.md): |
|||
|
|||
````csharp |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<AbpAuditingOptions>(options => |
|||
{ |
|||
options.IsEnabled = false; |
|||
}); |
|||
} |
|||
```` |
|||
|
|||
Instead of a central configuration object, there are separated option classes for every module and feature those are defined in the related documents. |
|||
|
|||
### IAbpSession vs ICurrentUser and ICurrentTenant |
|||
|
|||
ASP.NET Boilerplate's `IAbpSession` service is used to obtain the current user and tenant information, like ` UserId ` and `TenantId`. |
|||
|
|||
ABP Framework doesn't have the same service. Instead, use `ICurrentUser` and `ICurrentTenant` services. These services are defined as base properties in some common classes (like `ApplicationService` and `AbpController`), so you generally don't need to manually inject them. They also have much properties compared to the `IAbpSession`. |
|||
|
|||
### Authorization |
|||
|
|||
ABP Framework extends the [ASP.NET Core Authorization](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/introduction) by adding **permissions** as auto [policies](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/policies) and allowing the authorization system to be usable in the [application services](Application-Services.md) too. |
|||
|
|||
#### AbpAutorize vs Autorize |
|||
|
|||
Use the standard `[Autorize]` and `[AllowAnonymous]` attributes instead of ASP.NET Boilerplate's custom `[AbpAutorize]` and `[AbpAllowAnonymous]` attributes. |
|||
|
|||
#### IPermissionChecker vs IAuthorizationService |
|||
|
|||
Use the standard `IAuthorizationService` to check permissions instead of the ASP.NET Boilerplate's `IPermissionChecker` service. While `IPermissionChecker` also exists in the ABP Framework, it is used to explicitly use the permissions. Using `IAuthorizationService` is the recommended way since it covers other type of policy checks too. |
|||
|
|||
#### AuthorizationProvider vs PermissionDefinitionProvider |
|||
|
|||
You inherit from the `AuthorizationProvider` in the ASP.NET Boilerplate to define your permissions. ABP Framework replaces it by the `PermissionDefinitionProvider` base class. So, define your permissions by inheriting from the `PermissionDefinitionProvider` class. |
|||
|
|||
### Unit of Work |
|||
|
|||
Unit of work system has been designed to work seamlessly. For most of the cases, you don't need to change anything. |
|||
|
|||
`UnitOfWork` attribute of the ABP Framework doesn't have the `ScopeOption` (type of `TransactionScopeOption`) property. Instead, use `IUnitOfWorkManager.Begin()` method with `requiresNew = true` to create an independent inner transaction in a transaction scope. |
|||
|
|||
#### Data Filters |
|||
|
|||
ASP.NET Boilerplate implements the data filtering system as a part of the unit of work. ABP Framework has a separate `IDataFilter` service. |
|||
|
|||
See the [data filtering document](Data-Filtering.md) to learn how to enable/disable a filter. |
|||
|
|||
See [the UOW documentation](Unit-Of-Work.md) for more about the UOW system. |
|||
|
|||
### Multi-Tenancy |
|||
|
|||
#### IMustHaveTenant & IMayHaveTenant vs IMultiTenant |
|||
|
|||
ASP.NET Boilerplate defines `IMustHaveTenant` and `IMayHaveTenant` interfaces to implement them for your entities. In this way, your entities are automatically filtered according to the current tenant. Because of the design, there was a problem: You had to create a "Default" tenant in the database with "1" as the Id if you want to create a non multi-tenant application (this "Default" tenant was used as the single tenant). |
|||
|
|||
ABP Framework has a single interface for multi-tenant entities: `IMultiTenant` which defines a nullable `TenantId` property of type `Guid`. If your application is not multi-tenant, then your entities will have null TenantId (instead of a default one). |
|||
|
|||
On the migration, you need to change the TenantId field type and replace these interfaces with the `IMultiTenant` |
|||
|
|||
#### Switch Between Tenants |
|||
|
|||
In some cases you might need to switch to a tenant for a code scope and work with the tenant's data in this scope. |
|||
|
|||
In ASP.NET Boilerplate, it is done using the `IUnitOfWorkManager` service: |
|||
|
|||
````csharp |
|||
public async Task<List<Product>> GetProducts(int tenantId) |
|||
{ |
|||
using (_unitOfWorkManager.Current.SetTenantId(tenantId)) |
|||
{ |
|||
return await _productRepository.GetAllListAsync(); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
In the ABP Framework it is done with the `ICurrentTenant` service: |
|||
|
|||
````csharp |
|||
public async Task<List<Product>> GetProducts(Guid tenantId) |
|||
{ |
|||
using (_currentTenant.Change(tenantId)) |
|||
{ |
|||
return await _productRepository.GetListAsync(); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
Pass `null` to the `Change` method to switch to the host side. |
|||
|
|||
### Caching |
|||
|
|||
ASP.NET Boilerplate has its [own distributed caching abstraction](https://aspnetboilerplate.com/Pages/Documents/Caching) which has in-memory and Redis implementations. You typically inject the `ICacheManager` service and use its `GetCache(...)` method to obtain a cache, then get and set objects in the cache. |
|||
|
|||
ABP Framework uses and extends ASP.NET Core's [distributed caching abstraction](Caching.md). It defines the `IDistributedCache<T>` services to inject a cache and get/set objects. |
|||
|
|||
### Logging |
|||
|
|||
ASP.NET Boilerplate uses Castle Windsor's [logging facility](http://docs.castleproject.org/Windsor.Logging-Facility.ashx) as an abstraction and supports multiple logging providers including Log4Net (the default one comes with the startup projects) and Serilog. You typically property-inject the logger: |
|||
|
|||
````csharp |
|||
using Castle.Core.Logging; //1: Import Logging namespace |
|||
|
|||
public class TaskAppService : ITaskAppService |
|||
{ |
|||
//2: Getting a logger using property injection |
|||
public ILogger Logger { get; set; } |
|||
|
|||
public TaskAppService() |
|||
{ |
|||
//3: Do not write logs if no Logger supplied. |
|||
Logger = NullLogger.Instance; |
|||
} |
|||
|
|||
public void CreateTask(CreateTaskInput input) |
|||
{ |
|||
//4: Write logs |
|||
Logger.Info("Creating a new task with description: " + input.Description); |
|||
//... |
|||
} |
|||
} |
|||
```` |
|||
|
|||
ABP Framework depends on Microsoft's [logging extensions](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging) library which is also an abstraction and there are many providers implement it. Startup templates are using the Serilog as the pre-configured logging libary while it is easy to change in your project. The usage pattern is similar: |
|||
|
|||
````csharp |
|||
//1: Import the Logging namespaces |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Logging.Abstractions; |
|||
|
|||
public class TaskAppService : ITaskAppService |
|||
{ |
|||
//2: Getting a logger using property injection |
|||
public ILogger<TaskAppService> Logger { get; set; } |
|||
|
|||
public TaskAppService() |
|||
{ |
|||
//3: Do not write logs if no Logger supplied. |
|||
Logger = NullLogger<TaskAppService>.Instance; |
|||
} |
|||
|
|||
public void CreateTask(CreateTaskInput input) |
|||
{ |
|||
//4: Write logs |
|||
Logger.Info("Creating a new task with description: " + input.Description); |
|||
//... |
|||
} |
|||
} |
|||
```` |
|||
|
|||
You inject the `ILogger<T>` instead of the `ILogger`. |
|||
|
|||
### Object to Object Mapping |
|||
|
|||
#### IObjectMapper Service |
|||
|
|||
ASP.NET Boilerplate defines an `IObjectMapper` service ([see](https://aspnetboilerplate.com/Pages/Documents/Object-To-Object-Mapping)) and has an integration to the [AutoMapper](https://automapper.org/) library. |
|||
|
|||
Example usage: Create a `User` object with the given `CreateUserInput` object: |
|||
|
|||
````csharp |
|||
public void CreateUser(CreateUserInput input) |
|||
{ |
|||
var user = ObjectMapper.Map<User>(input); |
|||
... |
|||
} |
|||
```` |
|||
|
|||
Example: Update an existing `User` properties with the given `UpdateUserInput` object: |
|||
|
|||
````csharp |
|||
public async Task UpdateUserAsync(Guid id, UpdateUserInput input) |
|||
{ |
|||
var user = await _userRepository.GetAsync(id); |
|||
ObjectMapper.Map(input, user); |
|||
} |
|||
```` |
|||
|
|||
ABP Framework has the same `IObjectMapper` service ([see](Object-To-Object-Mapping.md)) and the AutoMapper integration with a slightly different mapping methods. |
|||
|
|||
Example usage: Create a `User` object with the given `CreateUserInput` object: |
|||
|
|||
````csharp |
|||
public void CreateUser(CreateUserInput input) |
|||
{ |
|||
var user = ObjectMapper.Map<CreateUserInput, User>(input); |
|||
} |
|||
```` |
|||
|
|||
This time you need to explicitly declare the source type and target type (while ASP.NET Boilerplate was requiring only the target type). |
|||
|
|||
Example: Update an existing `User` properties with the given `UpdateUserInput` object: |
|||
|
|||
````csharp |
|||
public async Task UpdateUserAsync(Guid id, UpdateUserInput input) |
|||
{ |
|||
var user = await _userRepository.GetAsync(id); |
|||
ObjectMapper.Map<UpdateUserInput, User>(input, user); |
|||
} |
|||
```` |
|||
|
|||
Again, ABP Framework expects to explicitly set the source and target types. |
|||
|
|||
#### AutoMapper Integration |
|||
|
|||
##### Auto Mapping Attributes |
|||
|
|||
ASP.NET Boilerplate has `AutoMapTo`, `AutoMapFrom` and `AutoMap` attributes to automatically create mappings for the declared types. Example: |
|||
|
|||
````csharp |
|||
[AutoMapTo(typeof(User))] |
|||
public class CreateUserInput |
|||
{ |
|||
public string Name { get; set; } |
|||
public string Surname { get; set; } |
|||
... |
|||
} |
|||
```` |
|||
|
|||
ABP Framework has no such attributes, because AutoMapper as a [similar attribute](https://automapper.readthedocs.io/en/latest/Attribute-mapping.html) now. You need to switch to AutoMapper's attribute. |
|||
|
|||
##### Mapping Definitions |
|||
|
|||
ABP Framework follows AutoMapper principles closely. You can define classes derived from the `Profile` class to define your mappings. |
|||
|
|||
##### Configuration Validation |
|||
|
|||
Configuration validation is a best practice for the AutoMapper to maintain your mapping configuration in a safe way. |
|||
|
|||
See [the documentation](Object-To-Object-Mapping.md) for more information related to the object mapping. |
|||
|
|||
### Setting Management |
|||
|
|||
#### Defining the Settings |
|||
|
|||
In an ASP.NET Boilerplate based application, you create a class deriving from the `SettingProvider` class, implement the `GetSettingDefinitions` method and add your class to the `Configuration.Settings.Providers` list. |
|||
|
|||
In the ABP Framework, you need to derive your class from the `SettingDefinitionProvider` and implement the `Define` method. You don't need to register your class since the ABP Framework automatically discovers it. |
|||
|
|||
#### Getting the Setting Values |
|||
|
|||
ASP.NET Boilerplate provides the `ISettingManager` to read the setting values in the server side and `abp.setting.get(...)` method in the JavaScript side. |
|||
|
|||
ABP Framework has the `ISettingProvider` service to read the setting values in the server side and `abp.setting.get(...)` method in the JavaScript side. |
|||
|
|||
#### Setting the Setting Values |
|||
|
|||
For ASP.NET Boilerplate, you use the same `ISettingManager` service to change the setting values. |
|||
|
|||
ABP Framework separates it and provides the setting management module (pre-added to the startup projects) which has the ` ISettingManager ` to change the setting values. This separation was introduced to support tiered deployment scenarios (where `ISettingProvider` can also work in the client application while `ISettingManager ` can also work in the server (API) side). |
|||
|
|||
### Clock |
|||
|
|||
ASP.NET Boilerplate has a static `Clock` service ([see](https://aspnetboilerplate.com/Pages/Documents/Timing)) which is used to abstract the `DateTime` kind, so you can easily switch between Local and UTC times. You don't inject it, but just use the `Clock.Now` static method to obtain the current time. |
|||
|
|||
ABP Framework has the `IClock` service ([see](Clock.md)) which has a similar goal, but now you need to inject it whenever you need it. |
|||
|
|||
### Event Bus |
|||
|
|||
ASP.NET Boilerplate has an in-process event bus system. You typically inject the `IEventBus` (or use the static instance `EventBus.Default`) to trigger an event. It automatically triggers events for entity changes (like `EntityCreatingEventData` and `EntityUpdatedEventData`). You create a class by implementing the `IEventHandler<T>` interface. |
|||
|
|||
ABP Framework separates the event bus into two services: `ILocalEventBus` and `IDistributedEventBus`. |
|||
|
|||
The local event bus is similar to the event bus of the ASP.NET Boilerplate while the distributed event bus is new feature introduced in the ABP Framework. |
|||
|
|||
So, to migrate your code; |
|||
|
|||
* Use the `ILocalEventBus` instead of the `IEventBus`. |
|||
* Implement the `ILocalEventHandler` instead of the `IEventHandler`. |
|||
|
|||
> Note that ABP Framework has also an `IEventBus` interface, but it does exists to be a common interface for the local and distributed event bus. It is not injected and directly used. |
|||
|
|||
### Feature Management |
|||
|
|||
Feature system is used in multi-tenant applications to define features of your application check if given feature is available for the current tenant. |
|||
|
|||
#### Defining Features |
|||
|
|||
In the ASP.NET Boilerplate ([see](https://aspnetboilerplate.com/Pages/Documents/Feature-Management)), you create a class inheriting from the `FeatureProvider`, override the `SetFeatures` method and add your class to the `Configuration.Features.Providers` list. |
|||
|
|||
In the ABP Framework ([see](Features.md)), you derive your class from the `FeatureDefinitionProvider` and override the `Define` method. No need to add your class to the configuration, it is automatically discovered by the framework. |
|||
|
|||
#### Checking Features |
|||
|
|||
You can continue to use the `RequiresFeature` attribute and `IFeatureChecker` service to check if a feature is enabled for the current tenant. |
|||
|
|||
#### Changing the Feature Values |
|||
|
|||
In the ABP Framework you use the `IFeatureManager` to change a feature value for a tenant. |
|||
|
|||
### Audit Logging |
|||
|
|||
The ASP.NET Boilerplate ([see](https://aspnetboilerplate.com/Pages/Documents/Audit-Logging)) and the ABP Framework ([see](Audit-Logging.md)) has similar audit logging systems. ABP Framework requires to add `UseAuditing()` middleware to the ASP.NET Core pipeline, which is already added in the startup templates. So, most of the times it will be work out of the box. |
|||
|
|||
### Localization |
|||
|
|||
ASP.NET Boilerplate supports XML and JSON files to define the localization key-values for the UI ([see](https://aspnetboilerplate.com/Pages/Documents/Localization)). ABP Framework only supports the JSON formatter localization files ([see](Localization.md)). So, you need to convert your XML file to JSON. |
|||
|
|||
The ASP.NET Boilerplate has its own the `ILocalizationManager` service to be injected and used for the localization in the server side. |
|||
|
|||
The ABP Framework uses [Microsoft localization extension](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/localization) library, so it is completely integrated to ASP.NET Core. You use the `IStringLocalizer<T>` service to get a localized text. Example: |
|||
|
|||
````csharp |
|||
public class MyService |
|||
{ |
|||
private readonly IStringLocalizer<TestResource> _localizer; |
|||
|
|||
public MyService(IStringLocalizer<TestResource> localizer) |
|||
{ |
|||
_localizer = localizer; |
|||
} |
|||
|
|||
public void Foo() |
|||
{ |
|||
var str = _localizer["HelloWorld"]; //Get a localized text |
|||
} |
|||
} |
|||
```` |
|||
|
|||
So, you need to replace `ILocalizationManager` usage by the `IStringLocalizer`. |
|||
|
|||
It also provides API used in the client side: |
|||
|
|||
````js |
|||
var testResource = abp.localization.getResource('Test'); |
|||
var str = testResource('HelloWorld'); |
|||
```` |
|||
|
|||
It was like `abp.localization.localize(...)` in the ASP.NET Boilerplate. |
|||
|
|||
### Navigation vs Menu |
|||
|
|||
In ASP.NET you create a class deriving from the `NavigationProvider` to define your menu elements. Menu items has `requiredPermissionName` attributes to restrict access to a menu element. Menu items were static and your class is executed only one time. |
|||
|
|||
Int the ABP Framework you need to create a class implements the `IMenuContributor` interface. Your class is executed whenever the menu needs to be rendered. So, you can conditionally add menu items. |
|||
|
|||
As an example, this is the menu contributor of the tenant management module: |
|||
|
|||
````csharp |
|||
public class AbpTenantManagementWebMainMenuContributor : IMenuContributor |
|||
{ |
|||
public async Task ConfigureMenuAsync(MenuConfigurationContext context) |
|||
{ |
|||
//Add items only to the main menu |
|||
if (context.Menu.Name != StandardMenus.Main) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
//Get the standard administration menu item |
|||
var administrationMenu = context.Menu.GetAdministration(); |
|||
|
|||
//Resolve some needed services from the DI container |
|||
var authorizationService = context.ServiceProvider |
|||
.GetRequiredService<IAuthorizationService>(); |
|||
var l = context.ServiceProvider |
|||
.GetRequiredService<IStringLocalizer<AbpTenantManagementResource>>(); |
|||
|
|||
var tenantManagementMenuItem = new ApplicationMenuItem( |
|||
TenantManagementMenuNames.GroupName, |
|||
l["Menu:TenantManagement"], |
|||
icon: "fa fa-users"); |
|||
|
|||
administrationMenu.AddItem(tenantManagementMenuItem); |
|||
|
|||
//Conditionally add the "Tenants" menu item based on the permission |
|||
if (await authorizationService |
|||
.IsGrantedAsync(TenantManagementPermissions.Tenants.Default)) |
|||
{ |
|||
tenantManagementMenuItem.AddItem( |
|||
new ApplicationMenuItem( |
|||
TenantManagementMenuNames.Tenants, |
|||
l["Tenants"], |
|||
url: "/TenantManagement/Tenants")); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
So, you need to check permission using the `IAuthorizationService` if you want to show a menu item only when the user has the related permission. |
|||
|
|||
> Navigation/Menu system is only for ASP.NET Core MVC / Razor Pages applications. Angular applications has a different system implemented in the startup templates. |
|||
|
|||
## Missing Features |
|||
|
|||
The following features are not present for the ABP Framework. Here, a list of some major missing features (and the related issue for that feature waiting on the ABP Framework GitHub repository): |
|||
|
|||
* [Multi-Lingual Entities](https://aspnetboilerplate.com/Pages/Documents/Multi-Lingual-Entities) ([#1754](https://github.com/abpframework/abp/issues/1754)) |
|||
* [Real time notification system](https://aspnetboilerplate.com/Pages/Documents/Notification-System) ([#633](https://github.com/abpframework/abp/issues/633)) |
|||
* [NHibernate Integration](https://aspnetboilerplate.com/Pages/Documents/NHibernate-Integration) ([#339](https://github.com/abpframework/abp/issues/339)) - We don't intent to work on this, but any community contribution welcome. |
|||
|
|||
Some of these features will eventually be implemented. However, you can implement them yourself if they are important for you. If you want, you can [contribute](Contribution/Index.md) to the framework, it is appreciated. |
|||
@ -1,140 +1,3 @@ |
|||
# Auto API Controllers |
|||
This document has moved. |
|||
|
|||
Once you create an [application service](../Application-Services.md), you generally want to create an API controller to expose this service as an HTTP (REST) API endpoint. A typical API controller does nothing but redirects method calls to the application service and configures the REST API using attributes like [HttpGet], [HttpPost], [Route]... etc. |
|||
|
|||
ABP can **automagically** configure your application services as API Controllers by convention. Most of time you don't care about its detailed configuration, but it's possible to fully customize it. |
|||
|
|||
## Configuration |
|||
|
|||
Basic configuration is simple. Just configure `AbpAspNetCoreMvcOptions` and use `ConventionalControllers.Create` method as shown below: |
|||
|
|||
````csharp |
|||
[DependsOn(BookStoreApplicationModule)] |
|||
public class BookStoreWebModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<AbpAspNetCoreMvcOptions>(options => |
|||
{ |
|||
options |
|||
.ConventionalControllers |
|||
.Create(typeof(BookStoreApplicationModule).Assembly); |
|||
}); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
This example code configures all the application services in the assembly containing the class `BookStoreApplicationModule`. The figure below shows the resulting API on the [Swagger UI](https://swagger.io/tools/swagger-ui/). |
|||
|
|||
 |
|||
|
|||
### Examples |
|||
|
|||
Some example method names and the corresponding routes calculated by convention: |
|||
|
|||
| Service Method Name | HTTP Method | Route | |
|||
| ----------------------------------------------------- | ----------- | -------------------------- | |
|||
| GetAsync(Guid id) | GET | /api/app/book/{id} | |
|||
| GetListAsync() | GET | /api/app/book | |
|||
| CreateAsync(CreateBookDto input) | POST | /api/app/book | |
|||
| UpdateAsync(Guid id, UpdateBookDto input) | PUT | /api/app/book/{id} | |
|||
| DeleteAsync(Guid id) | DELETE | /api/app/book/{id} | |
|||
| GetEditorsAsync(Guid id) | GET | /api/app/book/{id}/editors | |
|||
| CreateEditorAsync(Guid id, BookEditorCreateDto input) | POST | /api/app/book/{id}/editor | |
|||
|
|||
### HTTP Method |
|||
|
|||
ABP uses a naming convention while determining the HTTP method for a service method (action): |
|||
|
|||
- **Get**: Used if the method name starts with 'GetList', 'GetAll' or 'Get'. |
|||
- **Put**: Used if the method name starts with 'Put' or 'Update'. |
|||
- **Delete**: Used if the method name starts with 'Delete' or 'Remove'. |
|||
- **Post**: Used if the method name starts with 'Create', 'Add', 'Insert' or 'Post'. |
|||
- **Patch**: Used if the method name starts with 'Patch'. |
|||
- Otherwise, **Post** is used **by default**. |
|||
|
|||
If you need to customize HTTP method for a particular method, then you can use one of the standard ASP.NET Core attributes ([HttpPost], [HttpGet], [HttpPut]... etc.). This requires to add [Microsoft.AspNetCore.Mvc.Core](https://www.nuget.org/packages/Microsoft.AspNetCore.Mvc.Core) nuget package to your project that contains the service. |
|||
|
|||
### Route |
|||
|
|||
Route is calculated based on some conventions: |
|||
|
|||
* It always starts with '**/api**'. |
|||
* Continues with a **route path**. Default value is '**/app**' and can be configured as like below: |
|||
|
|||
````csharp |
|||
Configure<AbpAspNetCoreMvcOptions>(options => |
|||
{ |
|||
options.ConventionalControllers |
|||
.Create(typeof(BookStoreApplicationModule).Assembly, opts => |
|||
{ |
|||
opts.RootPath = "volosoft/book-store"; |
|||
}); |
|||
}); |
|||
```` |
|||
|
|||
Then the route for getting a book will be '**/api/volosoft/book-store/book/{id}**'. This sample uses two-level root path, but you generally use a single level depth. |
|||
|
|||
* Continues with the **normalized controller/service name**. Normalization removes 'AppService', 'ApplicationService' and 'Service' postfixes and converts it to **camelCase**. If your application service class name is 'BookAppService' then it becomes only '/book'. |
|||
* If you want to customize naming, then set the `UrlControllerNameNormalizer` option. It's a func delegate which allows you to determine the name per controller/service. |
|||
* If the method has an '**id**' parameter then it adds '**/{id}**' ro the route. |
|||
* Then it adds the action name if necessary. Action name is obtained from the method name on the service and normalized by; |
|||
* Removing '**Async**' postfix. If the method name is 'GetPhonesAsync' then it becomes 'GetPhones'. |
|||
* Removing **HTTP method prefix**. 'GetList', 'GetAll', 'Get', 'Put', 'Update', 'Delete', 'Remove', 'Create', 'Add', 'Insert', 'Post' and 'Patch' prefixes are removed based on the selected HTTP method. So, 'GetPhones' becomes 'Phones' since 'Get' prefix is a duplicate for a GET request. |
|||
* Converting the result to **camelCase**. |
|||
* If the resulting action name is **empty** then it's not added to the route. If it's not empty, it's added to the route (like '/phones'). For 'GetAllAsync' method name it will be empty, for 'GetPhonesAsync' method name it will be 'phones'. |
|||
* Normalization can be customized by setting the `UrlActionNameNormalizer` option. It's an action delegate that is called for every method. |
|||
* If there is another parameter with 'Id' postfix, then it's also added to the route as the final route segment (like '/phoneId'). |
|||
|
|||
## Service Selection |
|||
|
|||
Creating conventional HTTP API controllers are not unique to application services actually. |
|||
|
|||
### IRemoteService Interface |
|||
|
|||
If a class implements the `IRemoteService` interface then it's automatically selected to be a conventional API controller. Since application services inherently implement it, they are considered as natural API controllers. |
|||
|
|||
### RemoteService Attribute |
|||
|
|||
`RemoteService` attribute can be used to mark a class as a remote service or disable for a particular class that inherently implements the `IRemoteService` interface. Example: |
|||
|
|||
````csharp |
|||
[RemoteService(IsEnabled = false)] //or simply [RemoteService(false)] |
|||
public class PersonAppService : ApplicationService |
|||
{ |
|||
|
|||
} |
|||
```` |
|||
|
|||
### TypePredicate Option |
|||
|
|||
You can further filter classes to become an API controller by providing the `TypePredicate` option: |
|||
|
|||
````csharp |
|||
services.Configure<AbpAspNetCoreMvcOptions>(options => |
|||
{ |
|||
options.ConventionalControllers |
|||
.Create(typeof(BookStoreApplicationModule).Assembly, opts => |
|||
{ |
|||
opts.TypePredicate = type => { return true; }; |
|||
}); |
|||
}); |
|||
```` |
|||
|
|||
Instead of returning `true` for every type, you can check it and return `false` if you don't want to expose this type as an API controller. |
|||
|
|||
## API Explorer |
|||
|
|||
API Exploring a service that makes possible to investigate API structure by the clients. Swagger uses it to create a documentation and test UI for an endpoint. |
|||
|
|||
API Explorer is automatically enabled for conventional HTTP API controllers by default. Use `RemoteService` attribute to control it per class or method level. Example: |
|||
|
|||
````csharp |
|||
[RemoteService(IsMetadataEnabled = false)] |
|||
public class PersonAppService : ApplicationService |
|||
{ |
|||
|
|||
} |
|||
```` |
|||
|
|||
Disabled `IsMetadataEnabled` which hides this service from API explorer and it will not be discoverable. However, it still can be usable for the clients know the exact API path/route. |
|||
[Click to navigate to Auto API Controllers document](../API/Auto-API-Controllers.md) |
|||
@ -1,352 +1,4 @@ |
|||
|
|||
# ASP.NET Core MVC Bundling & Minification |
|||
This document has moved. |
|||
|
|||
There are many ways of bundling & minification of client side resources (JavaScript and CSS files). Most common ways are: |
|||
|
|||
* Using the [Bundler & Minifier](https://marketplace.visualstudio.com/items?itemName=MadsKristensen.BundlerMinifier) Visual Studio extension or the [NuGet package](https://www.nuget.org/packages/BuildBundlerMinifier/). |
|||
* Using [Gulp](https://gulpjs.com/)/[Grunt](https://gruntjs.com/) task managers and their plugins. |
|||
|
|||
ABP offers a simple, dynamic, powerful, modular and built-in way. |
|||
|
|||
## Volo.Abp.AspNetCore.Mvc.UI.Bundling Package |
|||
|
|||
> This package is already installed by default with the startup templates. So, most of the time, you don't need to install it manually. |
|||
|
|||
Install the `Volo.Abp.AspNetCore.Mvc.UI.Bundling` nuget package to your project: |
|||
|
|||
```` |
|||
install-package Volo.Abp.AspNetCore.Mvc.UI.Bundling |
|||
```` |
|||
|
|||
Then you can add the `AbpAspNetCoreMvcUiBundlingModule` dependency to your module: |
|||
|
|||
````C# |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Bundling; |
|||
|
|||
namespace MyCompany.MyProject |
|||
{ |
|||
[DependsOn(typeof(AbpAspNetCoreMvcUiBundlingModule))] |
|||
public class MyWebModule : AbpModule |
|||
{ |
|||
//... |
|||
} |
|||
} |
|||
```` |
|||
|
|||
## Razor Bundling Tag Helpers |
|||
|
|||
The simplest way of creating a bundle is to use `abp-script-bundle` or `abp-style-bundle` tag helpers. Example: |
|||
|
|||
````html |
|||
<abp-style-bundle name="MyGlobalBundle"> |
|||
<abp-style src="/libs/bootstrap/css/bootstrap.css" /> |
|||
<abp-style src="/libs/font-awesome/css/font-awesome.css" /> |
|||
<abp-style src="/libs/toastr/toastr.css" /> |
|||
<abp-style src="/styles/my-global-style.css" /> |
|||
</abp-style-bundle> |
|||
```` |
|||
|
|||
This bundle defines a style bundle with a **unique name**: `MyGlobalBundle`. It's very easy to understand how to use it. Let's see how it *works*: |
|||
|
|||
* ABP creates the bundle as **lazy** from the provided files when it's **first requested**. For the subsequent calls, it's returned from the **cache**. That means if you conditionally add the files to the bundle, it's executed only once and any changes of the condition will not effect the bundle for the next requests. |
|||
* ABP adds bundle files **individually** to the page for the `development` environment. It automatically bundles & minifies for other environments (`staging`, `production`...). |
|||
* The bundle files may be **physical** files or [**virtual/embedded** files](../Virtual-File-System.md). |
|||
* ABP automatically adds **version query string** to the bundle file URL to prevent browsers from caching when the bundle is being updated. (like ?_v=67872834243042 - generated from last change date of the related files). The versioning works even if the bundle files are individually added to the page (on the development environment). |
|||
|
|||
### Importing The Bundling Tag Helpers |
|||
|
|||
> This is already imported by default with the startup templates. So, most of the time, you don't need to add it manually. |
|||
|
|||
In order to use bundle tag helpers, you need to add it into your `_ViewImports.cshtml` file or into your page: |
|||
|
|||
```` |
|||
@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bundling |
|||
```` |
|||
|
|||
### Unnamed Bundles |
|||
|
|||
The `name` is **optional** for the razor bundle tag helpers. If you don't define a name, it's automatically **calculated** based on the used bundle file names (they are **concatenated** and **hashed**). Example: |
|||
|
|||
````html |
|||
<abp-style-bundle> |
|||
<abp-style src="/libs/bootstrap/css/bootstrap.css" /> |
|||
<abp-style src="/libs/font-awesome/css/font-awesome.css" /> |
|||
<abp-style src="/libs/toastr/toastr.css" /> |
|||
@if (ViewBag.IncludeCustomStyles != false) |
|||
{ |
|||
<abp-style src="/styles/my-global-style.css" /> |
|||
} |
|||
</abp-style-bundle> |
|||
```` |
|||
|
|||
This will potentially create **two different bundles** (one incudes the `my-global-style.css` and other does not). |
|||
|
|||
Advantages of **unnamed** bundles: |
|||
|
|||
* Can **conditionally add items** to the bundle. But this may lead to multiple variations of the bundle based on the conditions. |
|||
|
|||
Advantages of **named** bundles: |
|||
|
|||
* Other **modules can contribute** to the bundle by its name (see the sections below). |
|||
|
|||
### Single File |
|||
|
|||
If you need to just add a single file to the page, you can use the `abp-script` or `abp-style` tag without a wrapping in the `abp-script-bundle` or `abp-style-bundle` tag. Example: |
|||
|
|||
````xml |
|||
<abp-script src="/scripts/my-script.js" /> |
|||
```` |
|||
|
|||
The bundle name will be *scripts.my-scripts* for the example above ("/" is replaced by "."). All bundling features are work as expected for single file bundles too. |
|||
|
|||
## Bundling Options |
|||
|
|||
If you need to use same bundle in **multiple pages** or want to use some more **powerful features**, you can configure bundles **by code** in your [module](../Module-Development-Basics.md) class. |
|||
|
|||
### Creating A New Bundle |
|||
|
|||
Example usage: |
|||
|
|||
````C# |
|||
[DependsOn(typeof(AbpAspNetCoreMvcUiBundlingModule))] |
|||
public class MyWebModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<AbpBundlingOptions>(options => |
|||
{ |
|||
options |
|||
.ScriptBundles |
|||
.Add("MyGlobalBundle", bundle => { |
|||
bundle.AddFiles( |
|||
"/libs/jquery/jquery.js", |
|||
"/libs/bootstrap/js/bootstrap.js", |
|||
"/libs/toastr/toastr.min.js", |
|||
"/scripts/my-global-scripts.js" |
|||
); |
|||
}); |
|||
}); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
> You can use the same name (*MyGlobalBundle* here) for a script & style bundle since they are added to different collections (`ScriptBundles` and `StyleBundles`). |
|||
|
|||
After defining such a bundle, it can be included into a page using the same tag helpers defined above. Example: |
|||
|
|||
````html |
|||
<abp-script-bundle name="MyGlobalBundle" /> |
|||
```` |
|||
|
|||
This time, no file defined in the tag helper definition because the bundle files are defined by the code. |
|||
|
|||
### Configuring An Existing Bundle |
|||
|
|||
ABP supports [modularity](../Module-Development-Basics.md) for bundling as well. A module can modify an existing bundle that is created by a dependant module. Example: |
|||
|
|||
````C# |
|||
[DependsOn(typeof(MyWebModule))] |
|||
public class MyWebExtensionModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<AbpBundlingOptions>(options => |
|||
{ |
|||
options |
|||
.ScriptBundles |
|||
.Configure("MyGlobalBundle", bundle => { |
|||
bundle.AddFiles( |
|||
"/scripts/my-extension-script.js" |
|||
); |
|||
}); |
|||
}); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
> It's not possible to configure unnamed bundle tag helpers by code, because their name are not known at the development time. It's suggested to always use a name for a bundle tag helper. |
|||
|
|||
## Bundle Contributors |
|||
|
|||
Adding files to an existing bundle seems useful. What if you need to **replace** a file in the bundle or you want to **conditionally** add files? Defining a bundle contributor provides extra power for such cases. |
|||
|
|||
An example bundle contributor that replaces bootstrap.css with a customized version: |
|||
|
|||
````C# |
|||
public class MyExtensionGlobalStyleContributor : BundleContributor |
|||
{ |
|||
public override void ConfigureBundle(BundleConfigurationContext context) |
|||
{ |
|||
context.Files.ReplaceOne( |
|||
"/libs/bootstrap/css/bootstrap.css", |
|||
"/styles/extensions/bootstrap-customized.css" |
|||
); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
Then you can use this contributor as like below: |
|||
|
|||
````C# |
|||
services.Configure<AbpBundlingOptions>(options => |
|||
{ |
|||
options |
|||
.ScriptBundles |
|||
.Configure("MyGlobalBundle", bundle => { |
|||
bundle.AddContributors(typeof(MyExtensionGlobalStyleContributor)); |
|||
}); |
|||
}); |
|||
```` |
|||
|
|||
> You can also add contributors while creating a new bundle. |
|||
|
|||
Contributors can also be used in the bundle tag helpers. Example: |
|||
|
|||
````xml |
|||
<abp-style-bundle> |
|||
<abp-style type="@typeof(BootstrapStyleContributor)" /> |
|||
<abp-style src="/libs/font-awesome/css/font-awesome.css" /> |
|||
<abp-style src="/libs/toastr/toastr.css" /> |
|||
</abp-style-bundle> |
|||
```` |
|||
|
|||
`abp-style` and `abp-script` tags can get `type` attributes (instead of `src` attributes) as shown in this sample. When you add a bundle contributor, its dependencies are also automatically added to the bundle. |
|||
|
|||
### Contributor Dependencies |
|||
|
|||
A bundle contributor can have one or more dependencies to other contributors. |
|||
Example: |
|||
|
|||
````C# |
|||
[DependsOn(typeof(MyDependedBundleContributor))] //Define the dependency |
|||
public class MyExtensionStyleBundleContributor : BundleContributor |
|||
{ |
|||
//... |
|||
} |
|||
```` |
|||
|
|||
When a bundle contributor is added, its dependencies are **automatically and recursively** added. Dependencies added by the **dependency order** by preventing **duplicates**. Duplicates are prevented even if they are in separated bundles. ABP organizes all bundles in a page and eliminates duplications. |
|||
|
|||
Creating contributors and defining dependencies is a way of organizing bundle creation across different modules. |
|||
|
|||
### Contributor Extensions |
|||
|
|||
In some advanced scenarios, you may want to do some additional configuration whenever a bundle contributor is used. Contributor extensions works seamlessly when the extended contributor is used. |
|||
|
|||
The example below adds some styles for prism.js library: |
|||
|
|||
````csharp |
|||
public class MyPrismjsStyleExtension : BundleContributor |
|||
{ |
|||
public override void ConfigureBundle(BundleConfigurationContext context) |
|||
{ |
|||
context.Files.AddIfNotContains("/libs/prismjs/plugins/toolbar/prism-toolbar.css"); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
Then you can configure `BundleContributorOptions` to extend existing `PrismjsStyleBundleContributor`. |
|||
|
|||
````csharp |
|||
Configure<BundleContributorOptions>(options => |
|||
{ |
|||
options |
|||
.Extensions<PrismjsStyleBundleContributor>() |
|||
.Add<MyPrismjsStyleExtension>(); |
|||
}); |
|||
```` |
|||
|
|||
Whenever `PrismjsStyleBundleContributor` is added into a bundle, `MyPrismjsStyleExtension` will also be automatically added. |
|||
|
|||
### Accessing to the IServiceProvider |
|||
|
|||
While it is rarely needed, `BundleConfigurationContext` has a `ServiceProvider` property that you can resolve service dependencies inside the `ConfigureBundle` method. |
|||
|
|||
### Standard Package Contributors |
|||
|
|||
Adding a specific NPM package resource (js, css files) into a bundle is pretty straight forward for that package. For example you always add the `bootstrap.css` file for the bootstrap NPM package. |
|||
|
|||
There are built-in contributors for all [standard NPM packages](Client-Side-Package-Management.md). For example, if your contributor depends on the bootstrap, you can just declare it, instead of adding the bootstrap.css yourself. |
|||
|
|||
````C# |
|||
[DependsOn(typeof(BootstrapStyleContributor))] //Define the bootstrap style dependency |
|||
public class MyExtensionStyleBundleContributor : BundleContributor |
|||
{ |
|||
//... |
|||
} |
|||
```` |
|||
|
|||
Using the built-in contributors for standard packages; |
|||
|
|||
* Prevents you typing **the invalid resource paths**. |
|||
* Prevents changing your contributor if the resource **path changes** (the dependant contributor will handle it). |
|||
* Prevents multiple modules adding the **duplicate files**. |
|||
* Manages **dependencies recursively** (adds dependencies of dependencies, if necessary). |
|||
|
|||
#### Volo.Abp.AspNetCore.Mvc.UI.Packages Package |
|||
|
|||
> This package is already installed by default in the startup templates. So, most of the time, you don't need to install it manually. |
|||
|
|||
Standard package contributors are defined in the `Volo.Abp.AspNetCore.Mvc.UI.Packages` NuGet package. |
|||
To install it to your project: |
|||
|
|||
```` |
|||
install-package Volo.Abp.AspNetCore.Mvc.UI.Packages |
|||
```` |
|||
|
|||
Then add the `AbpAspNetCoreMvcUiPackagesModule` module dependency to your own module; |
|||
|
|||
````C# |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Bundling; |
|||
|
|||
namespace MyCompany.MyProject |
|||
{ |
|||
[DependsOn(typeof(AbpAspNetCoreMvcUiPackagesModule))] |
|||
public class MyWebModule : AbpModule |
|||
{ |
|||
//... |
|||
} |
|||
} |
|||
```` |
|||
|
|||
### Bundle Inheritance |
|||
|
|||
In some specific cases, it may be needed to create a **new** bundle **inherited** from other bundle(s). Inheriting from a bundle (recursively) inherits all files/contributors of that bundle. Then the derived bundle can add or modify files/contributors **without modifying** the original bundle. |
|||
Example: |
|||
|
|||
````c# |
|||
services.Configure<AbpBundlingOptions>(options => |
|||
{ |
|||
options |
|||
.StyleBundles |
|||
.Add("MyTheme.MyGlobalBundle", bundle => { |
|||
bundle |
|||
.AddBaseBundles("MyGlobalBundle") //Can add multiple |
|||
.AddFiles( |
|||
"/styles/mytheme-global-styles.css" |
|||
); |
|||
}); |
|||
}); |
|||
```` |
|||
|
|||
## Themes |
|||
|
|||
Themes uses the standard package contributors to add library resources to page layouts. Themes may also define some standard/global bundles, so any module can contribute to these standard/global bundles. See the [theming documentation](Theming.md) for more. |
|||
|
|||
## Best Practices & Suggestions |
|||
|
|||
It's suggested to define multiple bundles for an application, each one is used for different purposes. |
|||
|
|||
* **Global bundle**: Global style/script bundles are included to every page in the application. Themes already defines global style & script bundles. Your module can contribute to them. |
|||
* **Layout bundles**: This is a specific bundle to an individual layout. Only contains resources shared among all the pages use the layout. Use the bundling tag helpers to create the bundle as a good practice. |
|||
* **Module bundles**: For shared resources among the pages of an individual module. |
|||
* **Page bundles**: Specific bundles created for each page. Use the bundling tag helpers to create the bundle as a best practice. |
|||
|
|||
Establish a balance between performance, network bandwidth usage and count of many bundles. |
|||
|
|||
## See Also |
|||
|
|||
* [Client Side Package Management](Client-Side-Package-Management.md) |
|||
* [Theming](Theming.md) |
|||
[Click to navigate to ASP.NET Core MVC Bundling & Minification document](../UI/AspNetCore/Bundling-Minification.md) |
|||
@ -1,116 +1,4 @@ |
|||
|
|||
## ASP.NET Core MVC Client Side Package Management |
|||
This document has moved. |
|||
|
|||
ABP framework can work with any type of client side package management systems. You can even decide to use no package management system and manage your dependencies manually. |
|||
|
|||
However, ABP framework works best with **NPM/Yarn**. By default, built-in modules are configured to work with NPM/Yarn. |
|||
|
|||
Finally, we suggest the [**Yarn**](https://yarnpkg.com/) over the NPM since it's faster, stable and also compatible with the NPM. |
|||
|
|||
### @ABP NPM Packages |
|||
|
|||
ABP is a modular platform. Every developer can create modules and the modules should work together in a **compatible** and **stable** state. |
|||
|
|||
One challenge is the **versions of the dependant NPM packages**. What if two different modules use the same JavaScript library but its different (and potentially incompatible) versions. |
|||
|
|||
To solve the versioning problem, we created a **standard set of packages** those depends on some common third-party libraries. Some example packages are [@abp/jquery](https://www.npmjs.com/package/@abp/jquery), [@abp/bootstrap](https://www.npmjs.com/package/@abp/bootstrap) and [@abp/font-awesome](https://www.npmjs.com/package/@abp/font-awesome). You can see the **list of packages** from the [Github repository](https://github.com/volosoft/abp/tree/master/npm/packs). |
|||
|
|||
The benefit of a **standard package** is: |
|||
|
|||
* It depends on a **standard version** of a package. Depending on this package is **safe** because all modules depend on the same version. |
|||
* It contains the gulp task to copy library resources (js, css, img... files) from the **node_modules** folder to **wwwroot/libs** folder. See the *Mapping The Library Resources* section for more. |
|||
|
|||
Depending on a standard package is easy. Just add it to your **package.json** file like you normally do. Example: |
|||
|
|||
```` |
|||
{ |
|||
... |
|||
"dependencies": { |
|||
"@abp/bootstrap": "^1.0.0" |
|||
} |
|||
} |
|||
```` |
|||
|
|||
It's suggested to depend on a standard package instead of directly depending on a third-party package. |
|||
|
|||
#### Package Installation |
|||
|
|||
After depending on a NPM package, all you should do is to run the **yarn** command from the command line to install all the packages and their dependencies: |
|||
|
|||
```` |
|||
yarn |
|||
```` |
|||
|
|||
Alternatively, you can use `npm install` but [Yarn](https://yarnpkg.com/) is suggested as mentioned before. |
|||
|
|||
#### Package Contribution |
|||
|
|||
If you need a third-party NPM package that is not in the standard set of packages, you can create a Pull Request on the Github [repository](https://github.com/volosoft/abp). A pull request that follows these rules is accepted: |
|||
|
|||
* Package name should be named as `@abp/package-name` for a `package-name` on NPM (example: `@abp/bootstrap` for the `bootstrap` package). |
|||
* It should be the **latest stable** version of the package. |
|||
* It should only depend a **single** third-party package. It can depend on multiple `@abp/*` packages. |
|||
* The package should include a `abp.resourcemapping.js` file formatted as defined in the *Mapping The Library Resources* section. This file should only map resources for the depended package. |
|||
* You also need to create [bundle contributor(s)](Bundling-Minification.md) for the package you have created. |
|||
|
|||
See current standard packages for examples. |
|||
|
|||
### Mapping The Library Resources |
|||
|
|||
Using NPM packages and NPM/Yarn tool is the de facto standard for client side libraries. NPM/Yarn tool creates a **node_modules** folder in the root folder of your web project. |
|||
|
|||
Next challenge is copying needed resources (js, css, img... files) from the `node_modules` into a folder inside the **wwwroot** folder to make it accessible to the clients/browsers. |
|||
|
|||
ABP defines a [Gulp](https://gulpjs.com/) based task to **copy resources** from **node_modules** to **wwwroot/libs** folder. Each **standard package** (see the *@ABP NPM Packages* section) defines the mapping for its own files. So, most of the time, you only configure dependencies. |
|||
|
|||
The **startup templates** are already configured to work all these out of the box. This section will explain the configuration options. |
|||
|
|||
#### Resource Mapping Definition File |
|||
|
|||
A module should define a JavaScript file named `abp.resourcemapping.js` which is formatted as in the example below: |
|||
|
|||
````js |
|||
module.exports = { |
|||
aliases: { |
|||
"@node_modules": "./node_modules", |
|||
"@libs": "./wwwroot/libs" |
|||
}, |
|||
clean: [ |
|||
"@libs" |
|||
], |
|||
mappings: { |
|||
|
|||
} |
|||
} |
|||
```` |
|||
|
|||
* **aliases** section defines standard aliases (placeholders) that can be used in the mapping paths. **@node_modules** and **@libs** are required (by the standard packages), you can define your own aliases to reduce duplication. |
|||
* **clean** section is a list of folders to clean before copying the files. |
|||
* **mappings** section is a list of mappings of files/folders to copy. This example does not copy any resource itself, but depends on a standard package. |
|||
|
|||
An example mapping configuration is shown below: |
|||
|
|||
````js |
|||
mappings: { |
|||
"@node_modules/bootstrap/dist/css/bootstrap.css": "@libs/bootstrap/css/", |
|||
"@node_modules/bootstrap/dist/js/bootstrap.bundle.js": "@libs/bootstrap/js/", |
|||
"@node_modules/bootstrap-datepicker/dist/locales/*.*": "@libs/bootstrap-datepicker/locales/" |
|||
} |
|||
```` |
|||
|
|||
#### Using The Gulp |
|||
|
|||
Once you properly configure the `abp.resourcemapping.js` file, you can run the gulp command from the command line: |
|||
|
|||
```` |
|||
gulp |
|||
```` |
|||
|
|||
When you run the `gulp`, all packages will copy their own resources into the **wwwroot/libs** folder. Running `yarn & gulp` is only necessary if you make a change in your dependencies in the **package.json** file. |
|||
|
|||
> When you run the Gulp command, dependencies of the application are resolved using the package.json file. The Gulp task automatically discovers and maps all resources from all dependencies (recursively). |
|||
|
|||
#### See Also |
|||
|
|||
* [Bundling & Minification](Bundling-Minification.md) |
|||
* [Theming](Theming.md) |
|||
[Click to navigate to ASP.NET Core MVC Client Side Package Management document](../UI/AspNetCore/Client-Side-Package-Management.md) |
|||
|
|||
@ -1,165 +1,3 @@ |
|||
# Dynamic C# API Clients |
|||
This document has moved. |
|||
|
|||
ABP can dynamically create C# API client proxies to call remote HTTP services (REST APIs). In this way, you don't need to deal with `HttpClient` and other low level HTTP features to call remote services and get results. |
|||
|
|||
## Service Interface |
|||
|
|||
Your service/controller should implement an interface that is shared between the server and the client. So, first define a service interface in a shared library project. Example: |
|||
|
|||
````csharp |
|||
public interface IBookAppService : IApplicationService |
|||
{ |
|||
Task<List<BookDto>> GetListAsync(); |
|||
} |
|||
```` |
|||
|
|||
Your interface should implement the `IRemoteService` interface to be automatically discovered. Since the `IApplicationService` inherits the `IRemoteService` interface, the `IBookAppService` above satisfies this condition. |
|||
|
|||
Implement this class in your service application. You can use [auto API controller system](Auto-API-Controllers.md) to expose the service as a REST API endpoint. |
|||
|
|||
## Client Proxy Generation |
|||
|
|||
First, add [Volo.Abp.Http.Client](https://www.nuget.org/packages/Volo.Abp.Http.Client) nuget package to your client project: |
|||
|
|||
```` |
|||
Install-Package Volo.Abp.Http.Client |
|||
```` |
|||
|
|||
Then add `AbpHttpClientModule` dependency to your module: |
|||
|
|||
````csharp |
|||
[DependsOn(typeof(AbpHttpClientModule))] //add the dependency |
|||
public class MyClientAppModule : AbpModule |
|||
{ |
|||
} |
|||
```` |
|||
|
|||
Now, it's ready to create the client proxies. Example: |
|||
|
|||
````csharp |
|||
[DependsOn( |
|||
typeof(AbpHttpClientModule), //used to create client proxies |
|||
typeof(BookStoreApplicationModule) //contains the application service interfaces |
|||
)] |
|||
public class MyClientAppModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
//Create dynamic client proxies |
|||
context.Services.AddHttpClientProxies( |
|||
typeof(BookStoreApplicationModule).Assembly |
|||
); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
`AddHttpClientProxies` method gets an assembly, finds all service interfaces in the given assembly, creates and registers proxy classes. |
|||
|
|||
### Endpoint Configuration |
|||
|
|||
`RemoteServices` section in the `appsettings.json` file is used to get remote service address by default. Simplest configuration is shown below: |
|||
|
|||
```` |
|||
{ |
|||
"RemoteServices": { |
|||
"Default": { |
|||
"BaseUrl": "http://localhost:53929/" |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
See the "RemoteServiceOptions" section below for more detailed configuration. |
|||
|
|||
## Usage |
|||
|
|||
It's straightforward to use. Just inject the service interface in the client application code: |
|||
|
|||
````csharp |
|||
public class MyService : ITransientDependency |
|||
{ |
|||
private readonly IBookAppService _bookService; |
|||
|
|||
public MyService(IBookAppService bookService) |
|||
{ |
|||
_bookService = bookService; |
|||
} |
|||
|
|||
public async Task DoIt() |
|||
{ |
|||
var books = await _bookService.GetListAsync(); |
|||
foreach (var book in books) |
|||
{ |
|||
Console.WriteLine($"[BOOK {book.Id}] Name={book.Name}"); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
This sample injects the `IBookAppService` service interface defined above. The dynamic client proxy implementation makes an HTTP call whenever a service method is called by the client. |
|||
|
|||
### IHttpClientProxy Interface |
|||
|
|||
While you can inject `IBookAppService` like above to use the client proxy, you could inject `IHttpClientProxy<IBookAppService>` for a more explicit usage. In this case you will use the `Service` property of the `IHttpClientProxy<T>` interface. |
|||
|
|||
## Configuration |
|||
|
|||
### RemoteServiceOptions |
|||
|
|||
`RemoteServiceOptions` is automatically set from the `appsettings.json` by default. Alternatively, you can use `Configure` method to set or override it. Example: |
|||
|
|||
````csharp |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
context.Services.Configure<RemoteServiceOptions>(options => |
|||
{ |
|||
options.RemoteServices.Default = |
|||
new RemoteServiceConfiguration("http://localhost:53929/"); |
|||
}); |
|||
|
|||
//... |
|||
} |
|||
```` |
|||
|
|||
### Multiple Remote Service Endpoints |
|||
|
|||
The examples above have configured the "Default" remote service endpoint. You may have different endpoints for different services (as like in a microservice approach where each microservice has different endpoints). In this case, you can add other endpoints to your configuration file: |
|||
|
|||
````json |
|||
{ |
|||
"RemoteServices": { |
|||
"Default": { |
|||
"BaseUrl": "http://localhost:53929/" |
|||
}, |
|||
"BookStore": { |
|||
"BaseUrl": "http://localhost:48392/" |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
`AddHttpClientProxies` method can get an additional parameter for the remote service name. Example: |
|||
|
|||
````csharp |
|||
context.Services.AddHttpClientProxies( |
|||
typeof(BookStoreApplicationModule).Assembly, |
|||
remoteServiceName: "BookStore" |
|||
); |
|||
```` |
|||
|
|||
`remoteServiceName` parameter matches the service endpoint configured via `RemoteServiceOptions`. If the `BookStore` endpoint is not defined then it fallbacks to the `Default` endpoint. |
|||
|
|||
### As Default Services |
|||
|
|||
When you create a service proxy for `IBookAppService`, you can directly inject the `IBookAppService` to use the proxy client (as shown in the usage section). You can pass `asDefaultServices: false` to the `AddHttpClientProxies` method to disable this feature. |
|||
|
|||
````csharp |
|||
context.Services.AddHttpClientProxies( |
|||
typeof(BookStoreApplicationModule).Assembly, |
|||
asDefaultServices: false |
|||
); |
|||
```` |
|||
|
|||
Using `asDefaultServices: false` may only be needed if your application has already an implementation of the service and you do not want to override/replace the other implementation by your client proxy. |
|||
|
|||
> If you disable `asDefaultServices`, you can only use `IHttpClientProxy<T>` interface to use the client proxies (see the related section above). |
|||
[Click to navigate to Dynamic C# API Clients document](../API/Dynamic-CSharp-API-Clients.md) |
|||
|
|||
@ -1,3 +1,3 @@ |
|||
# abp.auth JavaScript API |
|||
This document has moved. |
|||
|
|||
TODO |
|||
[Click to navigate to JavaScript Auth document](../../API/JavaScript-API/Auth.md) |
|||
@ -1,24 +1,3 @@ |
|||
# JavaScript API |
|||
|
|||
ABP provides some JavaScript APIs for ASP.NET Core MVC / Razor Pages applications. They can be used to perform some common application requirements in the client side. |
|||
|
|||
## APIs |
|||
|
|||
* abp.ajax |
|||
* [abp.auth](Auth.md) |
|||
* abp.currentUser |
|||
* abp.dom |
|||
* abp.event |
|||
* abp.features |
|||
* abp.localization |
|||
* abp.log |
|||
* abp.ModalManager |
|||
* abp.notify |
|||
* abp.security |
|||
* abp.setting |
|||
* abp.ui |
|||
* abp.utils |
|||
* abp.ResourceLoader |
|||
* abp.WidgetManager |
|||
* Other APIs |
|||
This document has moved. |
|||
|
|||
[Click to navigate to JavaScript API document](../../API/JavaScript-API/Index.md) |
|||
@ -1,3 +1,3 @@ |
|||
## Dynamic Forms |
|||
This document has moved. |
|||
|
|||
This is not documented yet. You can see a [demo](http://bootstrap-taghelpers.abp.io/Components/DynamicForms) for now. |
|||
[Click to navigate to Dynamic Forms document](../../UI/AspNetCore/Tag-Helpers/Dynamic-Forms.md) |
|||
@ -1,3 +1,3 @@ |
|||
## ABP Tag Helpers |
|||
This document has moved. |
|||
|
|||
"ABP tag helpers" is not documented yet. You can see a [demo of components](http://bootstrap-taghelpers.abp.io/) for now. |
|||
[Click to navigate to ABP Tag Helpers document](../../UI/AspNetCore/Tag-Helpers/Index.md) |
|||
|
|||
@ -1,3 +1,4 @@ |
|||
# Theming |
|||
|
|||
TODO |
|||
This document has moved. |
|||
|
|||
[Click to navigate to Theming document](../UI/AspNetCore/Theming.md) |
|||
@ -1,505 +1,4 @@ |
|||
# Widgets |
|||
|
|||
ABP provides a model and infrastructure to create **reusable widgets**. Widget system is an extension to [ASP.NET Core's ViewComponents](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/view-components). Widgets are especially useful when you want to; |
|||
This document has moved. |
|||
|
|||
* Have **scripts & styles** dependencies for your widget. |
|||
* Create **dashboards** with widgets used inside. |
|||
* Define widgets in reusable **[modules](../Module-Development-Basics.md)**. |
|||
* Co-operate widgets with **[authorization](../Authorization.md)** and **[bundling](Bundling-Minification.md)** systems. |
|||
|
|||
## Basic Widget Definition |
|||
|
|||
### Create a View Component |
|||
|
|||
As the first step, create a new regular ASP.NET Core View Component: |
|||
|
|||
 |
|||
|
|||
**MySimpleWidgetViewComponent.cs**: |
|||
|
|||
````csharp |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
|
|||
namespace DashboardDemo.Web.Pages.Components.MySimpleWidget |
|||
{ |
|||
public class MySimpleWidgetViewComponent : AbpViewComponent |
|||
{ |
|||
public IViewComponentResult Invoke() |
|||
{ |
|||
return View(); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
Inheriting from `AbpViewComponent` is not required. You could inherit from ASP.NET Core's standard `ViewComponent`. `AbpViewComponent` only defines some base useful properties. |
|||
|
|||
You can inject a service and use in the `Invoke` method to get some data from the service. You may need to make Invoke method async, like `public async Task<IViewComponentResult> InvokeAsync()`. See [ASP.NET Core's ViewComponents](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/view-components) document fore all different usages. |
|||
|
|||
**Default.cshtml**: |
|||
|
|||
```xml |
|||
<div class="my-simple-widget"> |
|||
<h2>My Simple Widget</h2> |
|||
<p>This is a simple widget!</p> |
|||
</div> |
|||
``` |
|||
|
|||
### Define the Widget |
|||
|
|||
Add a `Widget` attribute to the `MySimpleWidgetViewComponent` class to mark this view component as a widget: |
|||
|
|||
````csharp |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Widgets; |
|||
|
|||
namespace DashboardDemo.Web.Pages.Components.MySimpleWidget |
|||
{ |
|||
[Widget] |
|||
public class MySimpleWidgetViewComponent : AbpViewComponent |
|||
{ |
|||
public IViewComponentResult Invoke() |
|||
{ |
|||
return View(); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
## Rendering a Widget |
|||
|
|||
Rendering a widget is pretty standard. Use the `Component.InvokeAsync` method in a razor view/page as you do for any view component. Examples: |
|||
|
|||
````xml |
|||
@await Component.InvokeAsync("MySimpleWidget") |
|||
@await Component.InvokeAsync(typeof(MySimpleWidgetViewComponent)) |
|||
```` |
|||
|
|||
First approach uses the widget name while second approach uses the view component type. |
|||
|
|||
### Widgets with Arguments |
|||
|
|||
ASP.NET Core's view component system allows you to accept arguments for view components. The sample view component below accepts `startDate` and `endDate` and uses these arguments to retrieve data from a service. |
|||
|
|||
````csharp |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Widgets; |
|||
|
|||
namespace DashboardDemo.Web.Pages.Shared.Components.CountersWidget |
|||
{ |
|||
[Widget] |
|||
public class CountersWidgetViewComponent : AbpViewComponent |
|||
{ |
|||
private readonly IDashboardAppService _dashboardAppService; |
|||
|
|||
public CountersWidgetViewComponent(IDashboardAppService dashboardAppService) |
|||
{ |
|||
_dashboardAppService = dashboardAppService; |
|||
} |
|||
|
|||
public async Task<IViewComponentResult> InvokeAsync( |
|||
DateTime startDate, DateTime endDate) |
|||
{ |
|||
var result = await _dashboardAppService.GetCountersWidgetAsync( |
|||
new CountersWidgetInputDto |
|||
{ |
|||
StartDate = startDate, |
|||
EndDate = endDate |
|||
} |
|||
); |
|||
|
|||
return View(result); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
Now, you need to pass an anonymous object to pass arguments as shown below: |
|||
|
|||
````xml |
|||
@await Component.InvokeAsync("CountersWidget", new |
|||
{ |
|||
startDate = DateTime.Now.Subtract(TimeSpan.FromDays(7)), |
|||
endDate = DateTime.Now |
|||
}) |
|||
```` |
|||
|
|||
## Widget Name |
|||
|
|||
Default name of the view components are calculated based on the name of the view component type. If your view component type is `MySimpleWidgetViewComponent` then the widget name will be `MySimpleWidget` (removes `ViewComponent` postfix). This is how ASP.NET Core calculates a view component's name. |
|||
|
|||
To customize widget's name, just use the standard `ViewComponent` attribute of ASP.NET Core: |
|||
|
|||
```csharp |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Widgets; |
|||
|
|||
namespace DashboardDemo.Web.Pages.Components.MySimpleWidget |
|||
{ |
|||
[Widget] |
|||
[ViewComponent(Name = "MyCustomNamedWidget")] |
|||
public class MySimpleWidgetViewComponent : AbpViewComponent |
|||
{ |
|||
public IViewComponentResult Invoke() |
|||
{ |
|||
return View("~/Pages/Components/MySimpleWidget/Default.cshtml"); |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
ABP will respect to the custom name by handling the widget. |
|||
|
|||
> If the view component name and the folder name of the view component don't match, you may need to manually write the view path as done in this example. |
|||
|
|||
### Display Name |
|||
|
|||
You can also define a human-readable, localizable display name for the widget. This display name then can be used on the UI when needed. Display name is optional and can be defined using properties of the `Widget` attribute: |
|||
|
|||
````csharp |
|||
using DashboardDemo.Localization; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Widgets; |
|||
|
|||
namespace DashboardDemo.Web.Pages.Components.MySimpleWidget |
|||
{ |
|||
[Widget( |
|||
DisplayName = "MySimpleWidgetDisplayName", //Localization key |
|||
DisplayNameResource = typeof(DashboardDemoResource) //localization resource |
|||
)] |
|||
public class MySimpleWidgetViewComponent : AbpViewComponent |
|||
{ |
|||
public IViewComponentResult Invoke() |
|||
{ |
|||
return View(); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
See [the localization document](../Localization.md) to learn about localization resources and keys. |
|||
|
|||
## Style & Script Dependencies |
|||
|
|||
There are some challenges when your widget has script and style files; |
|||
|
|||
* Any page uses the widget should also include the **its script & styles** files into the page. |
|||
* The page should also care about **depended libraries/files** of the widget. |
|||
|
|||
ABP solves these issues when you properly relate the resources with the widget. You don't care about dependencies of the widget while using it. |
|||
|
|||
### Defining as Simple File Paths |
|||
|
|||
The example widget below adds a style and a script file: |
|||
|
|||
````csharp |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Widgets; |
|||
|
|||
namespace DashboardDemo.Web.Pages.Components.MySimpleWidget |
|||
{ |
|||
[Widget( |
|||
StyleFiles = new[] { "/Pages/Components/MySimpleWidget/Default.css" }, |
|||
ScriptFiles = new[] { "/Pages/Components/MySimpleWidget/Default.js" } |
|||
)] |
|||
public class MySimpleWidgetViewComponent : AbpViewComponent |
|||
{ |
|||
public IViewComponentResult Invoke() |
|||
{ |
|||
return View(); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
ABP takes account these dependencies and properly adds to the view/page when you use the widget. Style/script files can be **physical or virtual**. It is completely integrated to the [Virtual File System](../Virtual-File-System.md). |
|||
|
|||
### Defining Bundle Contributors |
|||
|
|||
All resources for used widgets in a page are added as a **bundle** (bundled & minified in production if you don't configure otherwise). In addition to adding a simple file, you can take full power of the bundle contributors. |
|||
|
|||
The sample code below does the same with the code above, but defines and uses bundle contributors: |
|||
|
|||
````csharp |
|||
using System.Collections.Generic; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Bundling; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Widgets; |
|||
|
|||
namespace DashboardDemo.Web.Pages.Components.MySimpleWidget |
|||
{ |
|||
[Widget( |
|||
StyleTypes = new []{ typeof(MySimpleWidgetStyleBundleContributor) }, |
|||
ScriptTypes = new[]{ typeof(MySimpleWidgetScriptBundleContributor) } |
|||
)] |
|||
public class MySimpleWidgetViewComponent : AbpViewComponent |
|||
{ |
|||
public IViewComponentResult Invoke() |
|||
{ |
|||
return View(); |
|||
} |
|||
} |
|||
|
|||
public class MySimpleWidgetStyleBundleContributor : BundleContributor |
|||
{ |
|||
public override void ConfigureBundle(BundleConfigurationContext context) |
|||
{ |
|||
context.Files |
|||
.AddIfNotContains("/Pages/Components/MySimpleWidget/Default.css"); |
|||
} |
|||
} |
|||
|
|||
public class MySimpleWidgetScriptBundleContributor : BundleContributor |
|||
{ |
|||
public override void ConfigureBundle(BundleConfigurationContext context) |
|||
{ |
|||
context.Files |
|||
.AddIfNotContains("/Pages/Components/MySimpleWidget/Default.js"); |
|||
} |
|||
} |
|||
} |
|||
|
|||
```` |
|||
|
|||
Bundle contribution system is very powerful. If your widget uses a JavaScript library to render a chart, then you can declare it as a dependency, so the JavaScript library is automatically added to the page if it wasn't added before. In this way, the page using your widget doesn't care about the dependencies. |
|||
|
|||
See the [bundling & minification](Bundling-Minification.md) documentation for more information about that system. |
|||
|
|||
## RefreshUrl |
|||
|
|||
A widget may design a `RefreshUrl` that is used whenever the widget needs to be refreshed. If it is defined, the widget is re-rendered on the server side on every refresh (see the refresh `method` of the `WidgetManager` below). |
|||
|
|||
````csharp |
|||
[Widget(RefreshUrl = "Widgets/Counters")] |
|||
public class CountersWidgetViewComponent : AbpViewComponent |
|||
{ |
|||
|
|||
} |
|||
```` |
|||
|
|||
Once you define a `RefreshUrl` for your widget, you need to provide an endpoint to render and return it: |
|||
|
|||
````csharp |
|||
[Route("Widgets")] |
|||
public class CountersWidgetController : AbpController |
|||
{ |
|||
[HttpGet] |
|||
[Route("Counters")] |
|||
public IActionResult Counters(DateTime startDate, DateTime endDate) |
|||
{ |
|||
return ViewComponent("CountersWidget", new {startDate, endDate}); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
`Widgets/Counters` route matches to the `RefreshUrl` declared before. |
|||
|
|||
> A widget supposed to be refreshed in two ways: In the first way, when you use a `RefreshUrl`, it re-rendered on the server and replaced by the HTML returned from server. In the second way the widget gets data (generally a JSON object) from server and refreshes itself in the client side (see the refresh method in the Widget JavaScript API section). |
|||
|
|||
## JavaScript API |
|||
|
|||
A widget may need to be rendered and refreshed in the client side. In such cases, you can use ABP's `WidgetManager` and define APIs for your widgets. |
|||
|
|||
### WidgetManager |
|||
|
|||
`WidgetManager` is used to initialize and refresh one or more widgets. Create a new `WidgetManager` as shown below: |
|||
|
|||
````js |
|||
$(function() { |
|||
var myWidgetManager = new abp.WidgetManager('#MyDashboardWidgetsArea'); |
|||
}) |
|||
```` |
|||
|
|||
`MyDashboardWidgetsArea` may contain one or more widgets inside. |
|||
|
|||
> Using the `WidgetManager` inside document.ready (like above) is a good practice since its functions use the DOM and need DOM to be ready. |
|||
|
|||
#### WidgetManager.init() |
|||
|
|||
`init` simply initializes the `WidgetManager` and calls `init` methods of the related widgets if they define (see Widget JavaScript API section below) |
|||
|
|||
```js |
|||
myWidgetManager.init(); |
|||
``` |
|||
|
|||
#### WidgetManager.refresh() |
|||
|
|||
`refresh` method refreshes all widgets related to this `WidgetManager`: |
|||
|
|||
```` |
|||
myWidgetManager.refresh(); |
|||
```` |
|||
|
|||
#### WidgetManager Options |
|||
|
|||
WidgetManager has some additional options. |
|||
|
|||
##### Filter Form |
|||
|
|||
If your widgets require parameters/filters then you will generally have a form to filter the widgets. In such cases, you can create a form that has some form elements and a dashboard area with some widgets inside. Example: |
|||
|
|||
````xml |
|||
<form method="get" id="MyDashboardFilterForm"> |
|||
...form elements |
|||
</form> |
|||
|
|||
<div id="MyDashboardWidgetsArea" data-widget-filter="#MyDashboardFilterForm"> |
|||
...widgets |
|||
</div> |
|||
```` |
|||
|
|||
`data-widget-filter` attribute relates the form with the widgets. Whenever the form is submitted, all the widgets are automatically refreshed with the form fields as the filter. |
|||
|
|||
Instead of the `data-widget-filter` attribute, you can use the `filterForm` parameter of the `WidgetManager` constructor. Example: |
|||
|
|||
````js |
|||
var myWidgetManager = new abp.WidgetManager({ |
|||
wrapper: '#MyDashboardWidgetsArea', |
|||
filterForm: '#MyDashboardFilterForm' |
|||
}); |
|||
```` |
|||
|
|||
##### Filter Callback |
|||
|
|||
You may want to have a better control to provide filters while initializing and refreshing the widgets. In this case, you can use the `filterCallback` option: |
|||
|
|||
````js |
|||
var myWidgetManager = new abp.WidgetManager({ |
|||
wrapper: '#MyDashboardWidgetsArea', |
|||
filterCallback: function() { |
|||
return $('#MyDashboardFilterForm').serializeFormToObject(); |
|||
} |
|||
}); |
|||
```` |
|||
|
|||
This example shows the default implementation of the `filterCallback`. You can return any JavaScript object with fields. Example: |
|||
|
|||
````js |
|||
filterCallback: function() { |
|||
return { |
|||
'startDate': $('#StartDateInput').val(), |
|||
'endDate': $('#EndDateInput').val() |
|||
}; |
|||
} |
|||
```` |
|||
|
|||
The returning filters are passed to all widgets on `init` and `refresh`. |
|||
|
|||
### Widget JavaScript API |
|||
|
|||
A widget can define a JavaScript API that is invoked by the `WidgetManager` when needed. The code sample below can be used to start to define an API for a widget. |
|||
|
|||
````js |
|||
(function () { |
|||
abp.widgets.NewUserStatisticWidget = function ($wrapper) { |
|||
|
|||
var getFilters = function () { |
|||
return { |
|||
... |
|||
}; |
|||
} |
|||
|
|||
var refresh = function (filters) { |
|||
... |
|||
}; |
|||
|
|||
var init = function (filters) { |
|||
... |
|||
}; |
|||
|
|||
return { |
|||
getFilters: getFilters, |
|||
init: init, |
|||
refresh: refresh |
|||
}; |
|||
}; |
|||
})(); |
|||
```` |
|||
|
|||
`NewUserStatisticWidget` is the name of the widget here. It should match the widget name defined in the server side. All of the functions are optional. |
|||
|
|||
#### getFilters |
|||
|
|||
If the widget has internal custom filters, this function should return the filter object. Example: |
|||
|
|||
````js |
|||
var getFilters = function() { |
|||
return { |
|||
frequency: $wrapper.find('.frequency-filter option:selected').val() |
|||
}; |
|||
} |
|||
```` |
|||
|
|||
This method is used by the `WidgetManager` while building filters. |
|||
|
|||
#### init |
|||
|
|||
Used to initialize the widget when needed. It has a filter argument that can be used while getting data from server. `init` method is used when `WidgetManager.init()` function is called. It is also called if your widget requires a full re-load on refresh. See the `RefreshUrl` widget option. |
|||
|
|||
#### refresh |
|||
|
|||
Used to refresh the widget when needed. It has a filter argument that can be used while getting data from server. `refresh` method is used whenever `WidgetManager.refresh()` function is called. |
|||
|
|||
## Authorization |
|||
|
|||
Some widgets may need to be available only for authenticated or authorized users. In this case, use the following properties of the `Widget` attribute: |
|||
|
|||
* `RequiresAuthentication` (`bool`): Set to true to make this widget usable only for authentication users (user have logged in to the application). |
|||
* `RequiredPolicies` (`List<string>`): A list of policy names to authorize the user. See [the authorization document](../Authorization.md) for more info about policies. |
|||
|
|||
Example: |
|||
|
|||
````csharp |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Widgets; |
|||
|
|||
namespace DashboardDemo.Web.Pages.Components.MySimpleWidget |
|||
{ |
|||
[Widget(RequiredPolicies = new[] { "MyPolicyName" })] |
|||
public class MySimpleWidgetViewComponent : AbpViewComponent |
|||
{ |
|||
public IViewComponentResult Invoke() |
|||
{ |
|||
return View(); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
## WidgetOptions |
|||
|
|||
As alternative to the `Widget` attribute, you can use the `WidgetOptions` to configure widgets: |
|||
|
|||
```csharp |
|||
Configure<WidgetOptions>(options => |
|||
{ |
|||
options.Widgets.Add<MySimpleWidgetViewComponent>(); |
|||
}); |
|||
``` |
|||
|
|||
Write this into the `ConfigureServices` method of your [module](../Module-Development-Basics.md). All the configuration done with the `Widget` attribute is also possible with the `WidgetOptions`. Example configuration that adds a style for the widget: |
|||
|
|||
````csharp |
|||
Configure<WidgetOptions>(options => |
|||
{ |
|||
options.Widgets |
|||
.Add<MySimpleWidgetViewComponent>() |
|||
.WithStyles("/Pages/Components/MySimpleWidget/Default.css"); |
|||
}); |
|||
```` |
|||
|
|||
> Tip: `WidgetOptions` can also be used to get an existing widget and change its configuration. This is especially useful if you want to modify the configuration of a widget inside a module used by your application. Use `options.Widgets.Find` to get an existing `WidgetDefinition`. |
|||
|
|||
## See Also |
|||
|
|||
* [Example project (source code)](https://github.com/abpframework/abp/tree/dev/samples/DashboardDemo). |
|||
[Click to navigate to Widgets document](../UI/AspNetCore/Widgets.md) |
|||
|
|||
@ -1,3 +1,373 @@ |
|||
# Audit Logging |
|||
|
|||
TODO |
|||
[Wikipedia](https://en.wikipedia.org/wiki/Audit_trail): "*An audit trail (also called **audit log**) is a security-relevant chronological record, set of records, and/or destination and source of records that provide documentary evidence of the sequence of activities that have affected at any time a specific operation, procedure, or event*". |
|||
|
|||
ABP Framework provides an **extensible audit logging system** that automates the audit logging by **convention** and provides **configuration** points to control the level of the audit logs. |
|||
|
|||
An **audit log object** (see the Audit Log Object section below) is typically created & saved per web request. It includes; |
|||
|
|||
* **Request & response details** (like URL, Http method, Browser info, HTTP status code... etc.). |
|||
* **Performed actions** (controller actions and application service method calls with their parameters). |
|||
* **Entity changes** occurred in the web request. |
|||
* **Exception** information (if there was an error while executing the request). |
|||
* **Request duration** (to measure the performance of the application). |
|||
|
|||
> [Startup templates](Startup-Templates/Index.md) are configured for the audit logging system which is suitable for most of the applications. Use this document for a detailed control over the audit log system. |
|||
|
|||
### Database Provider Support |
|||
|
|||
* Fully supported by the [Entity Framework Core](Entity-Framework-Core.md) provider. |
|||
* Entity change logging is not supported by the [MongoDB](MongoDB.md) provider. Other features work as expected. |
|||
|
|||
## UseAuditing() |
|||
|
|||
`UseAuditing()` middleware should be added to the ASP.NET Core request pipeline in order to create and save the audit logs. If you've created your applications using [the startup templates](Startup-Templates/Index.md), it is already added. |
|||
|
|||
## AbpAuditingOptions |
|||
|
|||
`AbpAuditingOptions` is the main [options object](Options.md) to configure the audit log system. You can configure it in the `ConfigureServices` method of your [module](Module-Development-Basics.md): |
|||
|
|||
````csharp |
|||
Configure<AbpAuditingOptions>(options => |
|||
{ |
|||
options.IsEnabled = false; //Disables the auditing system |
|||
}); |
|||
```` |
|||
|
|||
Here, a list of the options you can configure: |
|||
|
|||
* `IsEnabled` (default: `true`): A root switch to enable or disable the auditing system. Other options is not used if this value is `false`. |
|||
* `HideErrors` (default: `true`): Audit log system hides and write regular [logs](Logging.md) if any error occurs while saving the audit log objects. If saving the audit logs is critical for your system, set this to `false` to throw exception in case of hiding the errors. |
|||
* `IsEnabledForAnonymousUsers` (default: `true`): If you want to write audit logs only for the authenticated users, set this to `false`. If you save audit logs for anonymous users, you will see `null` for `UserId` values for these users. |
|||
* `AlwaysLogOnException` (default: `true`): If you set to true, it always saves the audit log on an exception/error case without checking other options (except `IsEnabled`, which completely disables the audit logging). |
|||
* `IsEnabledForGetRequests` (default: `false`): HTTP GET requests should not make any change in the database normally and audit log system doesn't save audit log objects for GET request. Set this to `true` to enable it also for the GET requests. |
|||
* `ApplicationName`: If multiple applications saving audit logs into a single database, set this property to your application name, so you can distinguish the logs of different applications. |
|||
* `IgnoredTypes`: A list of `Type`s to be ignored for audit logging. If this is an entity type, changes for this type of entities will not be saved. This list is also used while serializing the action parameters. |
|||
* `EntityHistorySelectors`: A list of selectors those are used to determine if an entity type is selected for saving the entity change. See the section below for details. |
|||
* `Contributors`: A list of `AuditLogContributor` implementations. A contributor is a way of extending the audit log system. See the "Audit Log Contributors" section below. |
|||
|
|||
### Entity History Selectors |
|||
|
|||
Saving all changes of all your entities would require a lot of database space. For this reason, **audit log system doesn't save any change for the entities unless you explicitly configure it**. |
|||
|
|||
To save all changes of all entities, simply use the `AddAllEntities()` extension method. |
|||
|
|||
````csharp |
|||
Configure<AbpAuditingOptions>(options => |
|||
{ |
|||
options.EntityHistorySelectors.AddAllEntities(); |
|||
}); |
|||
```` |
|||
|
|||
`options.EntityHistorySelectors` actually a list of type predicate. You can write a lambda expression to define your filter. |
|||
|
|||
The example selector below does the same of the `AddAllEntities()` extension method defined above: |
|||
|
|||
````csharp |
|||
Configure<AbpAuditingOptions>(options => |
|||
{ |
|||
options.EntityHistorySelectors.Add( |
|||
new NamedTypeSelector( |
|||
"MySelectorName", |
|||
type => |
|||
{ |
|||
if (typeof(IEntity).IsAssignableFrom(type)) |
|||
{ |
|||
return true; |
|||
} |
|||
else |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
) |
|||
); |
|||
}); |
|||
```` |
|||
|
|||
The condition `typeof(IEntity).IsAssignableFrom(type)` will be `true` for any class implements the `IEntity` interface (this is technically all the entities in your application). You can conditionally check and return `true` or `false` based on your preference. |
|||
|
|||
`options.EntityHistorySelectors` is a flexible and dynamic way of selecting the entities for audit logging. Another way is to use the `Audited` and `DisableAuditing` attributes per entity. |
|||
|
|||
## Enabling/Disabling Audit Logging for Services |
|||
|
|||
### Enable/Disable for Controllers & Actions |
|||
|
|||
All the controller actions are logged by default (see `IsEnabledForGetRequests` above for GET requests). |
|||
|
|||
You can use the `[DisableAuditing]` to disable it for a specific controller type: |
|||
|
|||
````csharp |
|||
[DisableAuditing] |
|||
public class HomeController : AbpController |
|||
{ |
|||
//... |
|||
} |
|||
```` |
|||
|
|||
Use `[DisableAuditing]` for any action to control it in the action level: |
|||
|
|||
````csharp |
|||
public class HomeController : AbpController |
|||
{ |
|||
[DisableAuditing] |
|||
public async Task<ActionResult> Home() |
|||
{ |
|||
//... |
|||
} |
|||
|
|||
public async Task<ActionResult> OtherActionLogged() |
|||
{ |
|||
//... |
|||
} |
|||
} |
|||
```` |
|||
|
|||
### Enable/Disable for Application Services & Methods |
|||
|
|||
[Application service](Application-Services.md) method calls also included into the audit log by default. You can use the `[DisableAuditing]` in service or method level. |
|||
|
|||
#### Enable/Disable for Other Services |
|||
|
|||
Action audit logging can be enabled for any type of class (registered to and resolved from the [dependency injection](Dependency-Injection.md)) while it is only enabled for the controllers and the application services by default. |
|||
|
|||
Use `[Audited]` and `[DisableAuditing]` for any class or method that need to be audit logged. In addition, your class can (directly or inherently) implement the `IAuditingEnabled` interface to enable the audit logging for that class by default. |
|||
|
|||
### Enable/Disable for Entities & Properties |
|||
|
|||
An entity is ignored on entity change audit logging in the following cases; |
|||
|
|||
* If you add an entity type to the `AbpAuditingOptions.IgnoredTypes` (as explained before), it is completely ignored in the audit logging system. |
|||
* If the object is not an [entity](Entities.md) (not implements `IEntity` directly or inherently - All entities implement this interface by default). |
|||
* If entity type is not public. |
|||
|
|||
Otherwise, you can use `Audited` to enable entity change audit logging for an entity: |
|||
|
|||
````csharp |
|||
[Audited] |
|||
public class MyEntity : Entity<Guid> |
|||
{ |
|||
//... |
|||
} |
|||
```` |
|||
|
|||
Or disable it for an entity: |
|||
|
|||
````csharp |
|||
[DisableAuditing] |
|||
public class MyEntity : Entity<Guid> |
|||
{ |
|||
//... |
|||
} |
|||
```` |
|||
|
|||
Disabling audit logging can be necessary only if the entity is being selected by the `AbpAuditingOptions.EntityHistorySelectors` that explained before. |
|||
|
|||
You can disable auditing only some properties of your entities for a detailed control over the audit logging: |
|||
|
|||
````csharp |
|||
[Audited] |
|||
public class MyUser : Entity<Guid> |
|||
{ |
|||
public string Name { get; set; } |
|||
|
|||
public string Email { get; set; } |
|||
|
|||
[DisableAuditing] //Ignore the Passoword on audit logging |
|||
public string Password { get; set; } |
|||
} |
|||
```` |
|||
|
|||
Audit log system will save changes for the `MyUser` entity while it ignores the `Password` property which can be dangerous to save for security purposes. |
|||
|
|||
In some cases, you may want to save a few properties but ignore all others. Writing `[DisableAuditing]` for all the other properties would be tedious. In such cases, use `[Audited]` only for the desired properties and mark the entity with the `[DisableAuditing]` attribute: |
|||
|
|||
````csharp |
|||
[DisableAuditing] |
|||
public class MyUser : Entity<Guid> |
|||
{ |
|||
[Audited] //Only log the Name change |
|||
public string Name { get; set; } |
|||
|
|||
public string Email { get; set; } |
|||
|
|||
public string Password { get; set; } |
|||
} |
|||
```` |
|||
|
|||
## IAuditingStore |
|||
|
|||
`IAuditingStore` is an interface that is used to save the audit log objects (explained below) by the ABP Framework. If you need to save the audit log objects to a custom data store, you can implement the `IAuditingStore` in your own application and replace using the [dependency injection system](Dependency-Injection.md). |
|||
|
|||
`SimpleLogAuditingStore` is used if no audit store was registered. It simply writes the audit object to the standard [logging system](Logging.md). |
|||
|
|||
[The Audit Logging Module](Modules/Audit-Logging.md) has been configured in [the startup templates](Startup-Templates/Index.md) saves audit log objects to a database (it supports multiple database providers). So, most of the times you don't care about how `IAuditingStore` was implemented and used. |
|||
|
|||
## Audit Log Object |
|||
|
|||
An **audit log object** is created for each **web request** by default. An audit log object can be represented by the following relation diagram: |
|||
|
|||
 |
|||
|
|||
* **AuditLogInfo**: The root object with the following properties: |
|||
* `ApplicationName`: When you save audit logs of different applications to the same database, this property is used to distinguish the logs of the applications. |
|||
* `UserId`: Id of the current user, if the user has logged in. |
|||
* `UserName`: User name of the current user, if the user has logged in (this value is here to not depend on the identity module/system for lookup). |
|||
* `TenantId`: Id of the current tenant, for a multi-tenant application. |
|||
* `TenantName`: Name of the current tenant, for a multi-tenant application. |
|||
* `ExecutionTime`: The time when this audit log object has been created. |
|||
* `ExecutionDuration`: Total execution duration of the request, in milliseconds. This can be used to observe the performance of the application. |
|||
* `ClientId`: Id of the current client, if the client has been authenticated. A client is generally a 3rd-party application using the system over an HTTP API. |
|||
* `ClientName`: Name of the current client, if available. |
|||
* `ClientIpAddress`: IP address of the client/user device. |
|||
* `CorrelationId`: Current [Correlation Id](CorrelationId.md). Correlation Id is used to relate the audit logs written by different applications (or microservices) in a single logical operation. |
|||
* `BrowserInfo`: Browser name/version info of the current user, if available. |
|||
* `HttpMethod`: HTTP method of the current request (GET, POST, PUT, DELETE... etc.). |
|||
* `HttpStatusCode`: HTTP response status code for this request. |
|||
* `Url`: URL of the request. |
|||
* **AuditLogActionInfo**: An audit log action is typically a controller action or an [application service](Application-Services.md) method call during the web request. One audit log may contain multiple actions. An action object has the following properties: |
|||
* `ServiceName`: Name of the executed controller/service. |
|||
* `MethodName`: Name of the executed method of the controller/service. |
|||
* `Parameters`: A JSON formatted text representing the parameters passed to the method. |
|||
* `ExecutionTime`: The time when this method was executed. |
|||
* `ExecutionDuration`: Duration of the method execution, in milliseconds. This can be used to observe the performance of the method. |
|||
* **EntityChangeInfo**: Represents a change of an entity in this web request. An audit log may contain zero or more entity changes. An entity change has the following properties: |
|||
* `ChangeTime`: The time when the entity was changed. |
|||
* `ChangeType`: An enum with the following fields: `Created` (0), `Updated` (1) and `Deleted` (2). |
|||
* `EntityId`: Id of the entity that was changed. |
|||
* `EntityTenantId`: Id of the tenant this entity belongs to. |
|||
* `EntityTypeFullName`: Type (class) name of the entity with full namespace (like *Acme.BookStore.Book* for the Book entity). |
|||
* **EntityPropertyChangeInfo**: Represents a change of a property of an entity. An entity change info (explained above) may contain one or more property change with the following properties: |
|||
* `NewValue`: New value of the property. It is `null` if the entity was deleted. |
|||
* `OriginalValue`: Old/original value before the change. It is `null` if the entity was newly created. |
|||
* `PropertyName`: The name of the property on the entity class. |
|||
* `PropertyTypeFullName`: Type (class) name of the property with full namespace. |
|||
* **Exception**: An audit log object may contain zero or more exception. In this way, you can get a report of the failed requests. |
|||
* **Comment**: An arbitrary string value to add custom messages to the audit log entry. An audit log object may contain zero or more comments. |
|||
|
|||
In addition to the standard properties explained above, `AuditLogInfo`, `AuditLogActionInfo` and `EntityChangeInfo` objects implement the `IHasExtraProperties` interface, so you can add custom properties to these objects. |
|||
|
|||
## Audit Log Contributors |
|||
|
|||
You can extend the auditing system by creating a class that is derived from the `AuditLogContributor` class which defines the `PreContribute` and the `PostContribute` methods. |
|||
|
|||
The only pre-built contributor is the `AspNetCoreAuditLogContributor` class which sets the related properties for an HTTP request. |
|||
|
|||
A contributor can set properties and collections of the `AuditLogInfo` class to add more information. |
|||
|
|||
Example: |
|||
|
|||
````csharp |
|||
public class MyAuditLogContributor : AuditLogContributor |
|||
{ |
|||
public override void PreContribute(AuditLogContributionContext context) |
|||
{ |
|||
var currentUser = context.ServiceProvider.GetRequiredService<ICurrentUser>(); |
|||
context.AuditInfo.SetProperty( |
|||
"MyCustomClaimValue", |
|||
currentUser.FindClaimValue("MyCustomClaim") |
|||
); |
|||
} |
|||
|
|||
public override void PostContribute(AuditLogContributionContext context) |
|||
{ |
|||
context.AuditInfo.Comments.Add("Some comment..."); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
* `context.ServiceProvider` can be used to resolve services from the [dependency injection](Dependency-Injection.md). |
|||
* `context.AuditInfo` can be used to access to the current audit log object to manipulate it. |
|||
|
|||
After creating such a contributor, you must add it to the `AbpAuditingOptions.Contributors` list: |
|||
|
|||
````csharp |
|||
Configure<AbpAuditingOptions>(options => |
|||
{ |
|||
options.Contributors.Add(new MyAuditLogContributor()); |
|||
}); |
|||
```` |
|||
|
|||
## IAuditLogScope & IAuditingManager |
|||
|
|||
This section explains the `IAuditLogScope` & `IAuditingManager` services for advanced use cases. |
|||
|
|||
An **audit log scope** is an [ambient scope](Ambient-Context-Pattern.md) that **builds** and **saves** an audit log object (explained before). By default, an audit log scope is created for a web request by the Audit Log Middleware (see `UseAuditing()` section above). |
|||
|
|||
### Access to the Current Audit Log Scope |
|||
|
|||
Audit log contributors, was explained above, is a global way of manipulating the audit log object. It is good if you can get a value from a service. |
|||
|
|||
If you need to manipulate the audit log object in an arbitrary point of your application, you can access to the current audit log scope and get the current audit log object (independent of how the scope is managed). Example: |
|||
|
|||
````csharp |
|||
public class MyService : ITransientDependency |
|||
{ |
|||
private readonly IAuditingManager _auditingManager; |
|||
|
|||
public MyService(IAuditingManager auditingManager) |
|||
{ |
|||
_auditingManager = auditingManager; |
|||
} |
|||
|
|||
public async Task DoItAsync() |
|||
{ |
|||
var currentAuditLogScope = _auditingManager.Current; |
|||
if (currentAuditLogScope != null) |
|||
{ |
|||
currentAuditLogScope.Log.Comments.Add( |
|||
"Executed the MyService.DoItAsync method :)" |
|||
); |
|||
|
|||
currentAuditLogScope.Log.SetProperty("MyCustomProperty", 42); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
Always check if `_auditingManager.Current` is null or not, because it is controlled in an outer scope and you can't know if an audit log scope was created before calling your method. |
|||
|
|||
### Manually Create an Audit Log Scope |
|||
|
|||
You rarely need to create a manual audit log scope, but if you need, you can create an audit log scope using the `IAuditingManager` as like in the following example: |
|||
|
|||
````csharp |
|||
public class MyService : ITransientDependency |
|||
{ |
|||
private readonly IAuditingManager _auditingManager; |
|||
|
|||
public MyService(IAuditingManager auditingManager) |
|||
{ |
|||
_auditingManager = auditingManager; |
|||
} |
|||
|
|||
public async Task DoItAsync() |
|||
{ |
|||
using (var auditingScope = _auditingManager.BeginScope()) |
|||
{ |
|||
try |
|||
{ |
|||
//Call other services... |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
//Add exceptions |
|||
_auditingManager.Current.Log.Exceptions.Add(ex); |
|||
} |
|||
finally |
|||
{ |
|||
//Always save the log |
|||
await auditingScope.SaveAsync(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
You can call other services, they may call others, they may change entities and so on. All these interactions are saved as a single audit log object in the finally block. |
|||
|
|||
## The Audit Logging Module |
|||
|
|||
The Audit Logging Module basically implements the `IAuditingStore` to save the audit log objects to a database. It supports multiple database providers. This module is added to the startup templates by default. |
|||
|
|||
See [the Audit Logging Module document](Modules/Audit-Logging.md) for more about it. |
|||
|
|||
@ -1,3 +0,0 @@ |
|||
## AutoMapper Integration |
|||
|
|||
TODO |
|||
@ -1,3 +1,84 @@ |
|||
# Hangfire Background Job Manager |
|||
|
|||
TODO |
|||
[Hangfire](https://www.hangfire.io/) is an advanced background job manager. You can integrate Hangfire with the ABP Framework to use it instead of the [default background job manager](Background-Jobs.md). In this way, you can use the same background job API for Hangfire and your code will be independent of Hangfire. If you like, you can directly use Hangfire's API, too. |
|||
|
|||
> See the [background jobs document](Background-Jobs.md) to learn how to use the background job system. This document only shows how to install and configure the Hangfire integration. |
|||
|
|||
## Installation |
|||
|
|||
It is suggested to use the [ABP CLI](CLI.md) to install this package. |
|||
|
|||
### Using the ABP CLI |
|||
|
|||
Open a command line window in the folder of the project (.csproj file) and type the following command: |
|||
|
|||
````bash |
|||
abp add-package Volo.Abp.BackgroundJobs.HangFire |
|||
```` |
|||
|
|||
### Manual Installation |
|||
|
|||
If you want to manually install; |
|||
|
|||
1. Add the [Volo.Abp.BackgroundJobs.HangFire](https://www.nuget.org/packages/Volo.Abp.BackgroundJobs.HangFire) NuGet package to your project: |
|||
|
|||
```` |
|||
Install-Package Volo.Abp.BackgroundJobs.HangFire |
|||
```` |
|||
|
|||
2. Add the `AbpBackgroundJobsHangfireModule` to the dependency list of your module: |
|||
|
|||
````csharp |
|||
[DependsOn( |
|||
//...other dependencies |
|||
typeof(AbpBackgroundJobsHangfireModule) //Add the new module dependency |
|||
)] |
|||
public class YourModule : AbpModule |
|||
{ |
|||
} |
|||
```` |
|||
|
|||
## Configuration |
|||
|
|||
You can install any storage for Hangfire. The most common one is SQL Server (see the [Hangfire.SqlServer](https://www.nuget.org/packages/Hangfire.SqlServer) NuGet package). |
|||
|
|||
After you have installed these NuGet packages, you need to configure your project to use Hangfire. |
|||
|
|||
1.First, we change the `Module` class (example: `<YourProjectName>HttpApiHostModule`) to add Hangfire configuration of the storage and connection string in the `ConfigureServices` method: |
|||
|
|||
````csharp |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
var configuration = context.Services.GetConfiguration(); |
|||
var hostingEnvironment = context.Services.GetHostingEnvironment(); |
|||
|
|||
//... other configarations. |
|||
|
|||
ConfigureHangfire(context, configuration); |
|||
} |
|||
|
|||
private void ConfigureHangfire(ServiceConfigurationContext context, IConfiguration configuration) |
|||
{ |
|||
context.Services.AddHangfire(config => |
|||
{ |
|||
config.UseSqlServerStorage(configuration.GetConnectionString("Default")); |
|||
}); |
|||
} |
|||
```` |
|||
|
|||
2. We need to add `UseHangfireServer` call in the `OnApplicationInitialization` method in `Module` class |
|||
|
|||
If you want to use hangfire's dashboard, you can add it, too: by `UseHangfireDashboard` |
|||
|
|||
````csharp |
|||
public override void OnApplicationInitialization(ApplicationInitializationContext context) |
|||
{ |
|||
var app = context.GetApplicationBuilder(); |
|||
|
|||
// ... others |
|||
|
|||
app.UseHangfireServer(); |
|||
app.UseHangfireDashboard(); |
|||
|
|||
} |
|||
```` |
|||
|
|||
@ -0,0 +1,73 @@ |
|||
# Quartz Background Job Manager |
|||
|
|||
[Quartz](https://www.quartz-scheduler.net/) is an advanced background job manager. You can integrate Quartz with the ABP Framework to use it instead of the [default background job manager](Background-Jobs.md). In this way, you can use the same background job API for Quartz and your code will be independent of Quartz. If you like, you can directly use Quartz's API, too. |
|||
|
|||
> See the [background jobs document](Background-Jobs.md) to learn how to use the background job system. This document only shows how to install and configure the Quartz integration. |
|||
|
|||
## Installation |
|||
|
|||
It is suggested to use the [ABP CLI](CLI.md) to install this package. |
|||
|
|||
### Using the ABP CLI |
|||
|
|||
Open a command line window in the folder of the project (.csproj file) and type the following command: |
|||
|
|||
````bash |
|||
abp add-package Volo.Abp.BackgroundJobs.Quartz |
|||
```` |
|||
|
|||
### Manual Installation |
|||
|
|||
If you want to manually install; |
|||
|
|||
1. Add the [Volo.Abp.BackgroundJobs.Quartz](https://www.nuget.org/packages/Volo.Abp.BackgroundJobs.Quartz) NuGet package to your project: |
|||
|
|||
```` |
|||
Install-Package Volo.Abp.BackgroundJobs.Quartz |
|||
```` |
|||
|
|||
2. Add the `AbpBackgroundJobsQuartzModule` to the dependency list of your module: |
|||
|
|||
````csharp |
|||
[DependsOn( |
|||
//...other dependencies |
|||
typeof(AbpBackgroundJobsQuartzModule) //Add the new module dependency |
|||
)] |
|||
public class YourModule : AbpModule |
|||
{ |
|||
} |
|||
```` |
|||
|
|||
## Configuration |
|||
|
|||
Quartz is a very configurable library,and the ABP framework provides `AbpQuartzPreOptions` for this. You can use the `PreConfigure` method in your module class to pre-configure this option. ABP will use it when initializing the Quartz module. For example: |
|||
|
|||
````csharp |
|||
[DependsOn( |
|||
//...other dependencies |
|||
typeof(AbpBackgroundJobsQuartzModule) //Add the new module dependency |
|||
)] |
|||
public class YourModule : AbpModule |
|||
{ |
|||
public override void PreConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
var configuration = context.Services.GetConfiguration(); |
|||
|
|||
PreConfigure<AbpQuartzPreOptions>(options => |
|||
{ |
|||
options.Properties = new NameValueCollection |
|||
{ |
|||
["quartz.jobStore.dataSource"] = "BackgroundJobsDemoApp", |
|||
["quartz.jobStore.type"] = "Quartz.Impl.AdoJobStore.JobStoreTX, Quartz", |
|||
["quartz.jobStore.tablePrefix"] = "QRTZ_", |
|||
["quartz.serializer.type"] = "json", |
|||
["quartz.dataSource.BackgroundJobsDemoApp.connectionString"] = configuration.GetConnectionString("Quartz"), |
|||
["quartz.dataSource.BackgroundJobsDemoApp.provider"] = "SqlServer", |
|||
["quartz.jobStore.driverDelegateType"] = "Quartz.Impl.AdoJobStore.SqlServerDelegate, Quartz", |
|||
}; |
|||
}); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
Quartz stores job and scheduling information **in memory by default**. In the example, we use the pre-configuration of [options pattern](Options.md) to change it to the database. For more configuration of Quartz, please refer to the Quartz's [documentation](https://www.quartz-scheduler.net/documentation/quartz-3.x/tutorial/index.html). |
|||
@ -0,0 +1,68 @@ |
|||
# 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. |
|||
|
|||
## Installation |
|||
|
|||
It is suggested to use the [ABP CLI](CLI.md) to install this package. |
|||
|
|||
### Using the ABP CLI |
|||
|
|||
Open a command line window in the folder of the project (.csproj file) and type the following command: |
|||
|
|||
````bash |
|||
abp add-package Volo.Abp.BackgroundWorkers.Quartz |
|||
```` |
|||
|
|||
### Manual Installation |
|||
|
|||
If you want to manually install; |
|||
|
|||
1. Add the [Volo.Abp.BackgroundWorkers.Quartz](https://www.nuget.org/packages/Volo.Abp.BackgroundWorkers.Quartz) NuGet package to your project: |
|||
|
|||
```` |
|||
Install-Package Volo.Abp.BackgroundWorkers.Quartz |
|||
```` |
|||
|
|||
2. Add the `AbpBackgroundWorkersQuartzModule` to the dependency list of your module: |
|||
|
|||
````csharp |
|||
[DependsOn( |
|||
//...other dependencies |
|||
typeof(AbpBackgroundWorkersQuartzModule) //Add the new module dependency |
|||
)] |
|||
public class YourModule : AbpModule |
|||
{ |
|||
} |
|||
```` |
|||
|
|||
### Configuration |
|||
|
|||
See [Configuration](Background-Jobs-Quartz#Configuration). |
|||
|
|||
### Create a Background Worker |
|||
|
|||
A background work is a class that derives from the `QuartzBackgroundWorkerBase` base class. for example. A simple worker class is shown below: |
|||
|
|||
```` csharp |
|||
public class MyLogWorker : QuartzBackgroundWorkerBase |
|||
{ |
|||
public MyLogWorker() |
|||
{ |
|||
JobDetail = JobBuilder.Create<MyLogWorker>().Build(); |
|||
Trigger = TriggerBuilder.Create().StartNow().Build(); |
|||
} |
|||
|
|||
public override Task Execute(IJobExecutionContext context) |
|||
{ |
|||
Logger.LogInformation("Executed MyLogWorker..!"); |
|||
return Task.CompletedTask; |
|||
} |
|||
} |
|||
```` |
|||
|
|||
We simply implemented the Execute method to write a log. The background worker is a **singleton by default**. If you want, you can also implement a [dependency interface](Dependency-Injection#DependencyInterfaces) to register it as another life cycle. |
|||
|
|||
### More |
|||
|
|||
Please see Quartz's [documentation](https://www.quartz-scheduler.net/documentation/index.html) for more information. |
|||
@ -0,0 +1,140 @@ |
|||
# Background Workers |
|||
|
|||
## Introduction |
|||
|
|||
Background workers are simple independent threads in the application running in the background. Generally, they run periodically to perform some tasks. Examples; |
|||
|
|||
* A background worker can run periodically to **delete old logs**. |
|||
* A background worker can run periodically to **determine inactive users** and **send emails** to get users to return to your application. |
|||
|
|||
|
|||
## Create a Background Worker |
|||
|
|||
A background worker should directly or indirectly implement the `IBackgroundWorker` interface. |
|||
|
|||
> A background worker is inherently [singleton](Dependency-Injection.md). So, only a single instance of your worker class is instantiated and run. |
|||
|
|||
### BackgroundWorkerBase |
|||
|
|||
`BackgroundWorkerBase` is an easy way to create a background worker. |
|||
|
|||
````csharp |
|||
public class MyWorker : BackgroundWorkerBase |
|||
{ |
|||
public override Task StartAsync(CancellationToken cancellationToken = default) |
|||
{ |
|||
//... |
|||
} |
|||
|
|||
public override Task StopAsync(CancellationToken cancellationToken = default) |
|||
{ |
|||
//... |
|||
} |
|||
} |
|||
```` |
|||
|
|||
Start your worker in the `StartAsync` (which is called when the application begins) and stop in the `StopAsync` (which is called when the application shuts down). |
|||
|
|||
> You can directly implement the `IBackgroundWorker`, but `BackgroundWorkerBase` provides some useful properties like `Logger`. |
|||
|
|||
### AsyncPeriodicBackgroundWorkerBase |
|||
|
|||
Assume that we want to make a user passive, if the user has not logged in to the application in last 30 days. `AsyncPeriodicBackgroundWorkerBase` class simplifies to create periodic workers, so we will use it for the example below: |
|||
|
|||
````csharp |
|||
public class PassiveUserCheckerWorker : AsyncPeriodicBackgroundWorkerBase |
|||
{ |
|||
public PassiveUserCheckerWorker( |
|||
AbpTimer timer, |
|||
IServiceScopeFactory serviceScopeFactory |
|||
) : base( |
|||
timer, |
|||
serviceScopeFactory) |
|||
{ |
|||
Timer.Period = 600000; //10 minutes |
|||
} |
|||
|
|||
protected override async Task DoWorkAsync( |
|||
PeriodicBackgroundWorkerContext workerContext) |
|||
{ |
|||
Logger.LogInformation("Starting: Setting status of inactive users..."); |
|||
|
|||
//Resolve dependencies |
|||
var userRepository = workerContext |
|||
.ServiceProvider |
|||
.GetRequiredService<IUserRepository>(); |
|||
|
|||
//Do the work |
|||
await userRepository.UpdateInactiveUserStatusesAsync(); |
|||
|
|||
Logger.LogInformation("Completed: Setting status of inactive users..."); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
* `AsyncPeriodicBackgroundWorkerBase` uses the `AbpTimer` (a thread-safe timer) object to determine **the period**. We can set its `Period` property in the constructor. |
|||
* It required to implement the `DoWorkAsync` method to **execute** the periodic work. |
|||
* It is a good practice to **resolve dependencies** from the `PeriodicBackgroundWorkerContext` instead of constructor injection. Because `AsyncPeriodicBackgroundWorkerBase` uses a `IServiceScope` that is **disposed** when your work finishes. |
|||
* `AsyncPeriodicBackgroundWorkerBase` **catches and logs exceptions** thrown by the `DoWorkAsync` method. |
|||
|
|||
|
|||
## Register Background Worker |
|||
|
|||
After creating a background worker class, you should to add it to the `IBackgroundWorkerManager`. The most common place is the `OnApplicationInitialization` method of your module class: |
|||
|
|||
````csharp |
|||
[DependsOn(typeof(AbpBackgroundWorkersModule))] |
|||
public class MyModule : AbpModule |
|||
{ |
|||
public override void OnApplicationInitialization( |
|||
ApplicationInitializationContext context) |
|||
{ |
|||
context.AddBackgroundWorker<PassiveUserCheckerWorker>(); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
`context.AddBackgroundWorker(...)` is a shortcut extension method for the expression below: |
|||
|
|||
````csharp |
|||
context.ServiceProvider |
|||
.GetRequiredService<IBackgroundWorkerManager>() |
|||
.Add( |
|||
context |
|||
.ServiceProvider |
|||
.GetRequiredService<PassiveUserCheckerWorker>() |
|||
); |
|||
```` |
|||
|
|||
So, it resolves the given background worker and adds to the `IBackgroundWorkerManager`. |
|||
|
|||
While we generally add workers in `OnApplicationInitialization`, there are no restrictions on that. You can inject `IBackgroundWorkerManager` anywhere and add workers at runtime. Background worker manager will stop and release all the registered workers when your application is being shut down. |
|||
|
|||
## Options |
|||
|
|||
`AbpBackgroundWorkerOptions` class is used to [set options](Options.md) for the background workers. Currently, there is only one option: |
|||
|
|||
* `IsEnabled` (default: true): Used to **enable/disable** the background worker system for your application. |
|||
|
|||
> See the [Options](Options.md) document to learn how to set options. |
|||
|
|||
## Making Your Application Always Run |
|||
|
|||
Background workers only work if your application is running. If you host the background job execution in your web application (this is the default behavior), you should ensure that your web application is configured to always be running. Otherwise, background jobs only work while your application is in use. |
|||
|
|||
## Running On a Cluster |
|||
|
|||
Be careful if you run multiple instances of your application simultaneously in a clustered environment. In that case, every application runs the same worker which may create conflicts if your workers are running on the same resources (processing the same data, for example). |
|||
|
|||
If that's a problem for your workers, you have two options; |
|||
|
|||
* Disable the background worker system using the `AbpBackgroundWorkerOptions` described above, for all the application instances, except one of them. |
|||
* Disable the background worker system for all the application instances and create another special application that runs on a single server and execute the workers. |
|||
|
|||
## Quartz Integration |
|||
|
|||
ABP Framework's background worker system is good to implement periodic tasks. However, you may want to use an advanced task scheduler like [Quartz](https://www.quartz-scheduler.net/). See the community contributed [quartz integration](Background-Workers-Quartz.md) for the background workers. |
|||
|
|||
## See Also |
|||
* [Quartz Integration for the background workers](Background-Workers-Quartz.md) |
|||
* [Background Jobs](Background-Jobs.md) |
|||
@ -1,73 +0,0 @@ |
|||
## Entity Framework Core PostgreSQL Integration |
|||
|
|||
> See [Entity Framework Core Integration document](../Entity-Framework-Core.md) for the basics of the EF Core integration. |
|||
|
|||
### EntityFrameworkCore Project Update |
|||
|
|||
- In `Acme.BookStore.EntityFrameworkCore` project replace package `Volo.Abp.EntityFrameworkCore.SqlServer` with `Volo.Abp.EntityFrameworkCore.PostgreSql` |
|||
- Update to use PostgreSQL in `BookStoreEntityFrameworkCoreModule` |
|||
- Replace the `AbpEntityFrameworkCoreSqlServerModule` with the `AbpEntityFrameworkCorePostgreSqlModule` |
|||
- Replace the `options.UseSqlServer()` with the `options.UsePostgreSql()` |
|||
- In other projects update the PostgreSQL connection string in necessary `appsettings.json` files |
|||
- more info of [PostgreSQL connection strings](https://www.connectionstrings.com/postgresql/),You need to pay attention to `Npgsql` in this document |
|||
|
|||
### EntityFrameworkCore.DbMigrations Project Update |
|||
- Update to use PostgreSQL in `XXXMigrationsDbContextFactory` |
|||
- Replace the `new DbContextOptionsBuilder<XXXMigrationsDbContext>().UseSqlServer()` with the `new DbContextOptionsBuilder<XXXMigrationsDbContext>().UseNpgsql()` |
|||
|
|||
### Delete Existing Migrations |
|||
|
|||
Delete all existing migration files (including `DbContextModelSnapshot`) |
|||
|
|||
 |
|||
|
|||
### Regenerate Initial Migration |
|||
|
|||
Set the correct startup project (usually a web project) |
|||
|
|||
 |
|||
|
|||
Open the **Package Manager Console** (Tools -> Nuget Package Manager -> Package Manager Console), select the `.EntityFrameworkCore.DbMigrations` as the **Default project** and execute the following command: |
|||
|
|||
Run `Add-Migration` command. |
|||
```` |
|||
PM> Add-Migration Initial |
|||
```` |
|||
|
|||
### Update the Database |
|||
|
|||
You have two options to create the database. |
|||
|
|||
#### Using the DbMigrator Application |
|||
|
|||
The solution contains a console application (named `Acme.BookStore.DbMigrator` in this sample) that can create database, apply migrations and seed initial data. It is useful on development as well as on production environment. |
|||
|
|||
> `.DbMigrator` project has its own `appsettings.json`. So, if you have changed the connection string above, you should also change this one. |
|||
|
|||
Right click to the `.DbMigrator` project and select **Set as StartUp Project**: |
|||
|
|||
 |
|||
|
|||
Hit F5 (or Ctrl+F5) to run the application. It will have an output like shown below: |
|||
|
|||
 |
|||
|
|||
#### Using EF Core Update-Database Command |
|||
|
|||
Ef Core has `Update-Database` command which creates database if necessary and applies pending migrations. |
|||
|
|||
Set the correct startup project (usually a web project) |
|||
|
|||
 |
|||
|
|||
Open the **Package Manager Console** (Tools -> Nuget Package Manager -> Package Manager Console), select the `.EntityFrameworkCore.DbMigrations` as the **Default project** and execute the following command: |
|||
|
|||
```` |
|||
PM> Update-Database |
|||
```` |
|||
|
|||
This will create a new database based on the configured connection string. |
|||
|
|||
 |
|||
|
|||
> Using the `.DbMigrator` tool is the suggested way, because it also seeds the initial data to be able to properly run the web application. |
|||
@ -0,0 +1,164 @@ |
|||
# ABP Framework v2.0 and the ABP Commercial |
|||
|
|||
ABP Framework v2.0 has been released in this week. This post explains why we have released an **early major version** and what is changed with version 2.0. |
|||
|
|||
In addition to the v2.0 release, we are excited to announce the **ABP Commercial**, which is a set of professional modules, tools, themes, and services built on top of the open-source ABP framework. |
|||
|
|||
## ABP Framework v2.0 |
|||
|
|||
### Why 2.0 instead of 1.2? |
|||
|
|||
It was planned to release v1.2 after the [v1.1.2](https://github.com/abpframework/abp/releases/tag/1.1.2) release. However, [it is reported](https://github.com/abpframework/abp/issues/2026) that v1.x has some **performance** and **stability** issues on Linux, especially when you deploy your application to **Linux** containers with **low CPU and memory** resources. |
|||
|
|||
We have investigated the problem deeply and have seen that the root cause of the problem was related to the implementation of **intercepting `async` methods**. Besides, there were some **`async` over `sync`** usages that effected the thread pool optimization. |
|||
|
|||
Finally, we **solved all the problems** with the great help of the **community**. But we also had some important **design decisions** which cause some **breaking changes** and we had to change the major version number of the framework because of the [semantic versioning](https://semver.org/). |
|||
|
|||
Most of the applications won't be affected by [the breaking changes](https://github.com/abpframework/abp/releases), or it will be trivial to make these necessary changes. |
|||
|
|||
### Breaking Changes |
|||
|
|||
#### Removed Some Sync APIs |
|||
|
|||
Some of the interceptors are required to use `async` APIs. When they intercept `sync` methods, they need to call `async` over `sync`. This eventually ends up with `async` over `sync` problem. That's why we have [removed some sync APIs](https://github.com/abpframework/abp/pull/2464). |
|||
|
|||
**`Async` over `sync`** pattern is a classical problem of `C#` when you need to **call an `async` method inside a `sync` method**. While there are some workarounds to this problem, they all have **disadvantages** and it is suggested to **not write** such code at all. You can find many documents related to this topic on the web. |
|||
|
|||
To avoid this problem, we have removed: |
|||
|
|||
- `sync` [repository](https://docs.abp.io/en/abp/latest/Repositories) methods (like `insert`, `update`, etc...), |
|||
- `sync` APIs of the [unit of work](https://docs.abp.io/en/abp/latest/Unit-Of-Work), |
|||
- `sync` APIs of the [background jobs](https://docs.abp.io/en/abp/latest/Background-Jobs), |
|||
- `sync` APIs of the [audit logging](https://docs.abp.io/en/abp/latest/Audit-Logging), |
|||
- some other rarely used `sync` APIs. |
|||
|
|||
If you get any compile error, just use the `async` versions of these APIs. |
|||
|
|||
#### Always Async! |
|||
|
|||
Beginning from the v2.0, the ABP framework assumes that you are writing your application code `async` first. Otherwise, some framework functionalities may not properly work. |
|||
|
|||
It is suggested to write `async` to all your [application services](https://docs.abp.io/en/abp/latest/Application-Services), [repository methods](https://docs.abp.io/en/abp/latest/Repositories), controller actions, page handlers. |
|||
|
|||
Even if your application service method doesn't need to be `async` , set it as `async` , because interceptors perform `async` operations (for authorization, unit of work, etc...). You can return `Task.Completed` from a method that doesn't make an `async` call. |
|||
|
|||
Example: |
|||
|
|||
````csharp |
|||
public Task<int> GetValueAsync() |
|||
{ |
|||
//this method doesn't make any async call. |
|||
return Task.CompletedTask(42); |
|||
} |
|||
```` |
|||
|
|||
The example above normally doesn't need to be `async` because it doesn't perform an `async` call. However, making it `async` helps the ABP framework to run interceptors without `async` over sync calls. |
|||
|
|||
This rule doesn't force you to write every method `async` . This would not be good and would be tedious. It is only needed for the intercepted services (especially for [application services](https://docs.abp.io/en/abp/latest/Application-Services) and [repository methods](https://docs.abp.io/en/abp/latest/Repositories)) |
|||
|
|||
#### Other Breaking Changes |
|||
|
|||
See [the release notes](https://github.com/abpframework/abp/releases/tag/2.0.0) for the other breaking changes. Most of them will not affect your application code. |
|||
|
|||
### New Features |
|||
|
|||
This release also contains some new features and tens of enhancements: |
|||
|
|||
- [#2597](https://github.com/abpframework/abp/pull/2597) New `Volo.Abp.AspNetCore.Serilog` package. |
|||
- [#2526](https://github.com/abpframework/abp/issues/2526) Client-side validation for the dynamic `C#` client proxies. |
|||
- [#2374](https://github.com/abpframework/abp/issues/2374) `Async` background jobs. |
|||
- [#265](https://github.com/abpframework/abp/issues/265) Managing the application shutdown. |
|||
- [#2472](https://github.com/abpframework/abp/issues/2472) Implemented `DeviceFlowCodes` and `TokenCleanupService` for the `IdentityServer` module. |
|||
|
|||
See [the release notes](https://github.com/abpframework/abp/releases/tag/2.0.0) for the complete list of features, enhancements and bug fixes. |
|||
|
|||
### Documentation |
|||
|
|||
We have completed some missing documentation with the v2.0 release. In the following weeks, we will mostly focus on the documentation and tutorials. |
|||
|
|||
## ABP Commercial |
|||
|
|||
[ABP Commercial](https://commercial.abp.io/) is a set of professional **modules, tools, themes, and services** built on top of the open-source ABP framework. |
|||
|
|||
- It provides [professional modules](https://commercial.abp.io/modules) in addition to the ABP Framework's free & [open source modules](https://docs.abp.io/en/abp/latest/Modules/Index). |
|||
- It includes a beautiful a [UI theme](https://commercial.abp.io/themes) with 5 different styles. |
|||
- It provides the [ABP Suite](https://commercial.abp.io/tools/suite); A tool to assist your development to make you more productive. It currently can create full-stack CRUD pages in a few seconds by configuring your entity properties. More functionalities will be added over time. |
|||
- [Premium support](https://commercial.abp.io/support) for enterprise companies. |
|||
|
|||
In addition to these standard set of features, we will provide customer basis services. See the [commercial.abp.io](https://commercial.abp.io/) web site for other details. |
|||
|
|||
### ABP Framework vs the ABP Commercial |
|||
|
|||
The ABP Commercial **is not a paid version** of the ABP Framework. You can consider it as **set of additional benefits** for professional companies. You can use it to save your time and develop your product faster. |
|||
|
|||
ABP Framework is **open source & free** and will always be like that! |
|||
|
|||
As a principle, we build the main infrastructure as open-source and sell additional pre-built application features, themes, and tools. The main idea similar to the [ASP.NET Boilerplate](https://aspnetboilerplate.com/) & the [ASP.NET Zero](https://aspnetzero.com/) products. |
|||
|
|||
Buying a commercial license saves your significant time and effort and you can focus on your own business, besides you get dedicated and high priority support. Also, you will be supporting the ABP core team since we are spending most of our time to develop, maintain and support the open-source ABP Framework. |
|||
|
|||
With the introduction of the ABP Commercial, now ABP becomes a platform. We call it as the **ABP.IO Platform** which consists of the open source ABP Framework and the ABP Commercial. |
|||
|
|||
### Demo |
|||
|
|||
If you are wondering how exactly looks like the ABP Commercial application startup template, you can easily [create a demo](https://commercial.abp.io/demo) and see it in action. The demo includes all the pre-built modules and the theme. |
|||
|
|||
Here, a screenshot from the IdentityServer management module UI: |
|||
|
|||
 |
|||
|
|||
This is another screenshot from a demo application using the material design style of the theme: |
|||
|
|||
 |
|||
|
|||
### Pricing |
|||
|
|||
You can build **unlimited projects/products**, sell to **unlimited customers**, host **unlimited servers** without any restriction. Pricing is mostly based on the **developer count**, **support level** and **source code** requirement. There are three main packages; |
|||
|
|||
- **Team license**: Includes all the modules, themes and tools. Allows developing your product with up to 3 developers. You can buy additional developer licenses. |
|||
- **Business license**: Allows downloading the source code of all the modules and the themes. Also, it includes 5 developer licenses by default. You can buy additional developer licenses. |
|||
- **Enterprise license**: Provides unlimited and private support in addition to the benefits of the business license. |
|||
|
|||
See the [pricing page](https://commercial.abp.io/pricing) for details. In addition to the standard packages, we are also providing custom services and custom licensing. [Contact us](https://commercial.abp.io/contact) if you have any questions. |
|||
|
|||
#### License Comparison |
|||
|
|||
The license price changes based on your developer count, support level and source-code access. |
|||
|
|||
##### The Source-Code |
|||
|
|||
Team license doesn't include the source-code of the pre-built modules & themes. It uses all these modules as **NuGet & NPM packages**. In this way, you can easily **get new features and bug fixes** by just updating the package dependencies. But you can't access their source-code. So you don't have the possibility to embed a module's source code into your application and freely change the source-code. |
|||
|
|||
Pre-built modules provide some level of **customization** and **extensibility** and allow you to override services, UI parts and so on. We are working on to make them much more customizable and extensible. If you don't need to make major changes in the pre-built modules, the team license will be ideal for you, because it is cheaper and allows you to easily get new features and bug fixes. |
|||
|
|||
Business and Enterprise licenses allow you to **download the source-code** of any module or the theme when you need it. They also use the same startup template with the team license, so all modules are used as `NuGet` & `NPM` packages by default. But in case of need, you can remove the package dependencies for a module and embed its source-code into your own solution to completely customize it. In this case, upgrading the module will not be as easy as before when a new version is available. You don't have to upgrade it, surely! But if you want, you should do it yourself using some merge tool or Git branch system. |
|||
|
|||
#### License Lifetime |
|||
|
|||
ABP Commercial license is **perpetual**, which means you can **use it forever** and continue to develop your applications. |
|||
|
|||
However, the following services are covered for one year: |
|||
|
|||
- Premium **support** ends after one year. You can continue to get community support. |
|||
- You can not get **updates** of the modules & the themes after one year. You can continue to use the last obtained version. You can even get bug fixes and enhancements for your current major version. |
|||
- You can use the **ABP Suite** tool for one year. |
|||
|
|||
If you want to continue to get these benefits, you can extend your license period. Renewing price is 20% less than the regular price. |
|||
|
|||
## NDC London 2020 |
|||
|
|||
Just like the [previous year](https://medium.com/volosoft/impressions-of-ndc-london-2019-f8f391bb7a9c), we are a partner of the famous software development conference: [NDC London](https://ndc-london.com/)! In the previous year, we were there with the [ASP.NET Boilerplate](https://aspnetboilerplate.com/) & [ASP.NET Zero](https://aspnetzero.com/) theme: |
|||
|
|||
 |
|||
|
|||
This year, we will be focusing on the **ABP.IO Platform** (The Open Source ABP Framework and the ABP Commercial). Our booth wall will be like that: |
|||
|
|||
 |
|||
|
|||
If you attend to the conference, remember to visit our booth. We would be glad to talk about the ABP platform features, goals and software development in general. |
|||
|
|||
### Would you like to meet the ABP Team? |
|||
|
|||
If you are in London and want to have a coffee with us, we will be available at February 1st afternoon. [@hibrahimkalkan](https://twitter.com/hibrahimkalkan) and [@ismcagdas](https://twitter.com/ismcagdas) will be there. |
|||
|
|||
Just write to info@abp.io if you want to meet :) |
|||
|
After Width: | Height: | Size: 134 KiB |
|
After Width: | Height: | Size: 75 KiB |
|
After Width: | Height: | Size: 148 KiB |
|
After Width: | Height: | Size: 236 KiB |
|
After Width: | Height: | Size: 2.7 MiB |
@ -0,0 +1,142 @@ |
|||
# ABP Framework v2.3.0 Has Been Released! |
|||
|
|||
In the days of **coronavirus**, we have released **ABP Framework v2.3** and this post will explain **what's new** with this release and **what we've done** in the last two weeks. |
|||
|
|||
## About the Coronavirus & Our Team |
|||
|
|||
**We are very sad** about the coronavirus case. As [Volosoft](https://volosoft.com/) team, we have **remote workers** working in their home in different countries. Beginning from the last week, we've **completely started to work remotely** from home including our main office employees. |
|||
|
|||
We believe in and pray for that the humanity will overcome this issue in a short time. |
|||
|
|||
## About the Release Cycle |
|||
|
|||
Beginning from the ABP v2.1.0, we have started to release feature versions once **in two weeks**, on Thursdays. This is the 3rd release after that decision and we see that it works fine for now and improved our agility. |
|||
|
|||
We will continue to release **feature versions** (like v2.4, v2.5) in every two weeks. In addition, we may release **hotfix versions** (like v2.3.1, v2.3.2) whenever needed. |
|||
|
|||
## What's New in ABP Framework v2.3.0 |
|||
|
|||
We've completed & merged **[104](https://github.com/abpframework/abp/milestone/30?closed=1) issues and pull requests** with **393 commits** in this two weeks development period. |
|||
|
|||
I will introduce some new features and enhancements introduced with this release. |
|||
|
|||
### React Native Mobile Application |
|||
|
|||
We have finally completed the **react native mobile application**. It currently allows you to **login**, manage your **users** and **tenants**. It utilizes the same setting, authorization and localization systems of the ABP Framework. |
|||
|
|||
A few screenshots from the application: |
|||
|
|||
 |
|||
|
|||
It doesn't have much functionality but it is a **perfect starting point** for your own mobile application since it is completely integrated to the backend and supports multi-tenancy. |
|||
|
|||
### Angular TypeScript Proxy Generator |
|||
|
|||
It is common to call a REST endpoint in the server from our Angular applications. In this case, we generally create **services** (those have methods for each service method on he server side) and **model objects** (matches to [DTOs](https://docs.abp.io/en/abp/latest/Data-Transfer-Objects) in the server side). |
|||
|
|||
In addition to manually creating such server-interacting services, we could use tools like [NSWAG](https://github.com/RicoSuter/NSwag) to generate service proxies for us. But NSWAG has the following problems we've experienced: |
|||
|
|||
* It generates a **big, single** .ts file which has some problems; |
|||
* It get **too large** when your application grows. |
|||
* It doesn't fit into the **[modular](https://docs.abp.io/en/abp/latest/Module-Development-Basics) approach** of the ABP framework. |
|||
* It creates a bit **ugly code**. We want to have a clean code (just like if we write manually). |
|||
* It can not generate the same **method signature** declared in the server side (because swagger.json doesn't exactly reflect the method signature of the backend service). We've created an endpoint that exposes server side method contacts to allow clients generate a better aligned client proxies. |
|||
|
|||
So, we've decided to create an ABP CLI command to automatically generate the typescript client proxies ([#2222](https://github.com/abpframework/abp/issues/2222)) for your REST API developed with the ABP Framework. |
|||
|
|||
It is easy to use. Just run the following command in the **root folder** of the angular application: |
|||
|
|||
````bash |
|||
abp generate-proxy |
|||
```` |
|||
|
|||
It only creates proxies only for your own application's services. It doesn't create proxies for the services of the application modules you're using (by default). There are several options. See the [CLI documentation](https://docs.abp.io/en/abp/latest/CLI). |
|||
|
|||
### CRUD Application Services for Entities with Composite Keys |
|||
|
|||
` CrudAppService ` is a useful base class to create CRUD application services for your entities. But it doesn't support entities with **composite primary keys**. `AbstractKeyCrudAppService` is the new base class that is developed to support entities with composite primary keys. See [the documentation](https://docs.abp.io/en/abp/latest/Application-Services#abstractkeycrudappservice) for more. |
|||
|
|||
### Add Source Code of the Modules |
|||
|
|||
The application startup template comes with some [application modules](https://docs.abp.io/en/abp/latest/Modules/Index) **pre-installed** as **NuGet & NPM packages**. This have a few important advantages: |
|||
|
|||
* You can **easily [upgrade](https://docs.abp.io/en/abp/latest/CLI#update)** these modules when a new version is available. |
|||
* Your solution becomes **cleaner**, so you can focus on your own code. |
|||
|
|||
However, when you need to make **major customizations** for a depended module, it is not easy as its source code is in your applications. To solve this problem, we've introduces a new command to the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) that **replaces** NuGet packages with their **source code** in your solution. The usage is simple: |
|||
|
|||
````bash |
|||
abp add-module --with-source-code |
|||
```` |
|||
|
|||
This command adds a module with source code or replaces with its source code if it is already added as package references. |
|||
|
|||
> It is suggested to **save your changes** to your source control system before using this command since it makes a lot of changes in your source code. |
|||
|
|||
In addition, we've documented how to customize depended modules without changing their source code (see the section below). It is suggested to use modules as packages to easily upgrade them in the future. |
|||
|
|||
> Source code of the free modules are licensed under **MIT**, so you can freely change them and add into your solution. |
|||
|
|||
### Switch to Preview |
|||
|
|||
ABP Framework is rapidly evolving and we are frequently releasing new versions. However, if you want to follow it closer, you can use the **daily preview packages**. |
|||
|
|||
We've created an ABP CLI command to easily **update to the latest preview packages** for your solution. Run the following command in the root folder of your solution: |
|||
|
|||
````bash |
|||
abp switch-to-preview |
|||
```` |
|||
|
|||
It will change the versions of all ABP related NuGet and NPM packages. You can **switch back to the latest stable** when you want: |
|||
|
|||
````bash |
|||
abp switch-to-stable |
|||
```` |
|||
|
|||
See the [ABP CLI document](https://docs.abp.io/en/abp/latest/CLI#switch-to-preview) fore more. |
|||
|
|||
### Documentation Improvements |
|||
|
|||
#### Extending/Customizing Depended Application Modules |
|||
|
|||
We've created a huge documentation that explains how to customize a depended module without changing its source code. See [the documentation](https://docs.abp.io/en/abp/latest/Customizing-Application-Modules-Guide). |
|||
|
|||
In addition to the documentation, we've revised all the modules ([#3166](https://github.com/abpframework/abp/issues/3166)) to make their services easily extensible & customizable. |
|||
|
|||
#### EF Core Migration Guide |
|||
|
|||
We've recently created a guide to explain the migration system that is used by the ABP startup templates. [This guide](https://docs.abp.io/en/abp/latest/Entity-Framework-Core-Migrations) also explains how to customize the migration structure, split your modules across multiple databases, reusing a module's table and son on. |
|||
|
|||
#### Migration from the ASP.NET Boilerplate |
|||
|
|||
If you have a solution built on the ASP.NET Boilerplate, we've [created a guide](https://docs.abp.io/en/abp/latest/AspNet-Boilerplate-Migration-Guide) that tries to help you if you want to migrate your solution to the new ABP Framework. |
|||
|
|||
### Some Other Features |
|||
|
|||
#### The Framework |
|||
|
|||
* Add `IRepository.GetAsync` and `IRepository.FindAsync` methods ([#3184](https://github.com/abpframework/abp/issues/3148)). |
|||
|
|||
#### Modules |
|||
|
|||
* Get password & email address of the admin while creating a new tenant, for the tenant management module ([#3088](https://github.com/abpframework/abp/issues/3088)). |
|||
* Elastic search integrated full text search for the docs module ([#2901](https://github.com/abpframework/abp/pull/2901)). |
|||
* New Quartz background worker module ([#2762](https://github.com/abpframework/abp/issues/2762)) |
|||
|
|||
#### Samples |
|||
|
|||
* Add multi-tenancy support to the microservice demo ([#3032](https://github.com/abpframework/abp/pull/3032)). |
|||
|
|||
See [the release notes](https://github.com/abpframework/abp/releases/tag/2.3.0) for all feature, enhancement and bugfixes. |
|||
|
|||
## What's Next? |
|||
|
|||
We have the following goals for the next few months: |
|||
|
|||
* Complete the **documentation and samples**, write more tutorials. |
|||
* Make the framework and existing modules more **customizable and extensible**. |
|||
* Integrate to **gRPC** & implement gRPC endpoint for pre-built modules ([#2882](https://github.com/abpframework/abp/issues/2882)). |
|||
* Create a **Blazor UI** for the ABP Framework & implement it for all the modules and startup templates ([#394](https://github.com/abpframework/abp/issues/394)). |
|||
* Add **new features** to pre-built modules and create new modules for the [ABP Commercial](https://commercial.abp.io/). |
|||
|
|||
See [the GitHub milestones](https://github.com/abpframework/abp/milestones) for details. |
|||
|
After Width: | Height: | Size: 541 KiB |
|
After Width: | Height: | Size: 179 KiB |
@ -0,0 +1,3 @@ |
|||
# Clock |
|||
|
|||
TODO |
|||
@ -1,11 +1,88 @@ |
|||
# Data Access |
|||
# Connection Strings |
|||
|
|||
ABP framework was designed as database agnostic, it can work any type of data source by the help of the [repository](Repositories.md) and [unit of work](Unit-Of-Work.md) abstractions. |
|||
ABP Framework is designed to be [modular](Module-Development-Basics.md), [microservice compatible](Microservice-Architecture.md) and [multi-tenancy](Multi-Tenancy.md) aware. Connection string management is also designed to support these scenarios; |
|||
|
|||
However, currently the following providers are implements: |
|||
* Allows to set separate connection strings for every module, so every module can have its own physical database. Modules even might be configured to use different DBMSs. |
|||
* Allows to set separate connection string and use a separate database per tenant (in a SaaS application). |
|||
|
|||
* [Entity Framework Core](Entity-Framework-Core.md) (works with [various DBMS and providers](https://docs.microsoft.com/en-us/ef/core/providers/?tabs=dotnet-core-cli).) |
|||
* [MongoDB](MongoDB.md) |
|||
* [Dapper](Dapper.md) |
|||
It also supports hybrid scenarios; |
|||
|
|||
More providers might be added in the next releases. |
|||
* Allows to group modules into databases (all modules into a single shared database, 2 modules to database A, 3 modules to database B, 1 module to database C and rest of the modules to database D... etc.) |
|||
* Allows to group tenants into databases, just like the modules. |
|||
* Allows to separate databases per tenant per module (which might be harder to maintain for you because of too many databases, but the ABP framework supports it). |
|||
|
|||
All the [pre-built application modules](Modules/Index.md) are designed to be compatible these scenarios. |
|||
|
|||
## Configure the Connection Strings |
|||
|
|||
See the following configuration: |
|||
|
|||
````json |
|||
"ConnectionStrings": { |
|||
"Default": "Server=localhost;Database=MyMainDb;Trusted_Connection=True;", |
|||
"AbpIdentityServer": "Server=localhost;Database=MyIdsDb;Trusted_Connection=True;", |
|||
"AbpPermissionManagement": "Server=localhost;Database=MyPermissionDb;Trusted_Connection=True;" |
|||
} |
|||
```` |
|||
|
|||
> ABP uses the `IConfiguration` service to get the application configuration. While the simplest way to write configuration into the `appsettings.json` file, it is not limited to this file. You can use environment variables, user secrets, Azure Key Vault... etc. See the [configuration](Configuration.md) document for more. |
|||
|
|||
This configuration defines three different connection strings: |
|||
|
|||
* `MyMainDb` (the `Default` connection string) is the main connection string of the application. If you don't specify a connection string for a module, it fallbacks to the `Default` connection string. The [application startup template](Startup-Templates/Application.md) is configured to use a single connection string, so all the modules uses a single shared database. |
|||
* `MyIdsDb` is used by the [IdentityServer](Modules/IdentityServer.md) module. |
|||
* `MyPermissionDb` is used by the [Permission Management](Modules/Permission-Management.md) module. |
|||
|
|||
[Pre-built application modules](Modules/Index.md) define constants for the connection string names. For example, the IdentityServer module defines a ` ConnectionStringName ` constant in the ` AbpIdentityServerDbProperties ` class (located in the ` Volo.Abp.IdentityServer ` namespace). Other modules similarly define constants, so you can investigate the connection string name. |
|||
|
|||
### AbpDbConnectionOptions |
|||
|
|||
ABP actually uses the `AbpDbConnectionOptions` to get the connection strings. If you set the connection strings as explained above, `AbpDbConnectionOptions` is automatically filled. However, you can set or override the connection strings using [the options pattern](Options.md). You can configure the `AbpDbConnectionOptions` in the `ConfigureServices` method of your [module](Module-Development-Basics.md) as shown below: |
|||
|
|||
````csharp |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<AbpDbConnectionOptions>(options => |
|||
{ |
|||
options.ConnectionStrings.Default = "..."; |
|||
options.ConnectionStrings["AbpPermissionManagement"] = "..."; |
|||
}); |
|||
} |
|||
```` |
|||
|
|||
## Set the Connection String Name |
|||
|
|||
A module typically has a unique connection string name associated to its `DbContext` class using the `ConnectionStringName` attribute. Example: |
|||
|
|||
````csharp |
|||
[ConnectionStringName("AbpIdentityServer")] |
|||
public class IdentityServerDbContext |
|||
: AbpDbContext<IdentityServerDbContext>, IIdentityServerDbContext |
|||
{ |
|||
} |
|||
```` |
|||
|
|||
For [Entity Framework Core](Entity-Framework-Core.md) and [MongoDB](MongoDB.md), write this to your `DbContext` class (and the interface if it has). |
|||
|
|||
> If you are developing a reusable, database provider independent module see also [the best practices guide](Best-Practices/Index.md). |
|||
|
|||
## Database Migrations for the Entity Framework Core |
|||
|
|||
Relational databases require to create the database and the database schema (tables, views... etc.) before using it. |
|||
|
|||
The startup template (with EF Core ORM) comes with a single database and a `.EntityFrameworkCore.DbMigrations` project that contains the migration files for that database. This project mainly defines a *YourProjectName*MigrationsDbContext that calls the `Configure...()` methods of the used modules, like `builder.ConfigurePermissionManagement()`. |
|||
|
|||
Once you want to separate a module's database, you typically will need to create a second migration path. The easiest way to create a copy of the `.EntityFrameworkCore.DbMigrations` project with the `DbContext` inside it, change its content to only call the `Configure...()` methods of the modules needs to be stored in the second database and re-create the initial migration. In this case, you also need to change the `.DbMigrator` application to be able to work with these second database too. In this way, you will have a separate migrations DbContext per database. |
|||
|
|||
## Multi-Tenancy |
|||
|
|||
See [the multi-tenancy document](Multi-Tenancy.md) to learn how to use separate databases for tenants. |
|||
|
|||
## Replace the Connection String Resolver |
|||
|
|||
ABP defines the `IConnectionStringResolver` and uses it whenever it needs a connection string. It has two pre-built implementations: |
|||
|
|||
* `DefaultConnectionStringResolver` uses the `AbpDbConnectionOptions` to select the connection string based on the rules defined in the "Configure the Connection Strings" section above. |
|||
* `MultiTenantConnectionStringResolver` used for multi-tenant applications and tries to get the configured connection string for the current tenant if available. It uses the `ITenantStore` to find the connection strings. It inherits from the `DefaultConnectionStringResolver` and fallbacks to the base logic if no connection string specified for the current tenant. |
|||
|
|||
If you need a custom logic to determine the connection string, implement the `IConnectionStringResolver` interface (optionally derive from the existing implementations) and replace the existing implementation using the [dependency injection](Dependency-Injection.md) system. |
|||
@ -0,0 +1,177 @@ |
|||
# Customizing the Application Modules: Extending Entities |
|||
|
|||
In some cases, you may want to add some additional properties (and database fields) for an entity defined in a depended module. This section will cover some different approaches to make this possible. |
|||
|
|||
## Extra Properties |
|||
|
|||
[Extra properties](Entities.md) is a way of storing some additional data on an entity without changing it. The entity should implement the `IHasExtraProperties` interface to allow it. All the aggregate root entities defined in the pre-built modules implement the `IHasExtraProperties` interface, so you can store extra properties on these objects. |
|||
|
|||
Example: |
|||
|
|||
````csharp |
|||
//SET AN EXTRA PROPERTY |
|||
var user = await _identityUserRepository.GetAsync(userId); |
|||
user.SetProperty("Title", "My custom title value!"); |
|||
await _identityUserRepository.UpdateAsync(user); |
|||
|
|||
//GET AN EXTRA PROPERTY |
|||
var user = await _identityUserRepository.GetAsync(userId); |
|||
return user.GetProperty<string>("Title"); |
|||
```` |
|||
|
|||
This approach is very easy to use and available out of the box. No extra code needed. You can store more than one property at the same time by using different property names (like `Title` here). |
|||
|
|||
Extra properties are stored as a single `JSON` formatted string value in the database for the EF Core. For MongoDB, they are stored as separate fields of the document. |
|||
|
|||
See the [entities document](Entities.md) for more about the extra properties system. |
|||
|
|||
> It is possible to perform a **business logic** based on the value of an extra property. You can [override a service method](Customizing-Application-Modules-Overriding-Services.md), then get or set the value as shown above. |
|||
|
|||
## Entity Extensions (EF Core) |
|||
|
|||
As mentioned above, all extra properties of an entity are stored as a single JSON object in the database table. This is not so natural especially when you want to; |
|||
|
|||
* Create **indexes** and **foreign keys** for an extra property. |
|||
* Write **SQL** or **LINQ** using the extra property (search table by the property value, for example). |
|||
* Creating your **own entity** maps to the same table, but defines an extra property as a **regular property** in the entity (see the [EF Core migration document](Entity-Framework-Core-Migrations.md) for more). |
|||
|
|||
To overcome the difficulties described above, ABP Framework entity extension system for the Entity Framework Core that allows you to use the same extra properties API defined above, but store a desired property as a separate field in the database table. |
|||
|
|||
Assume that you want to add a `SocialSecurityNumber` to the `IdentityUser` entity of the [Identity Module](Modules/Identity.md). You can use the `ObjectExtensionManager`: |
|||
|
|||
````csharp |
|||
ObjectExtensionManager.Instance |
|||
.MapEfCoreProperty<IdentityUser, string>( |
|||
"SocialSecurityNumber", |
|||
b => { b.HasMaxLength(32); } |
|||
); |
|||
```` |
|||
|
|||
* You provide the `IdentityUser` as the entity name, `string` as the type of the new property, `SocialSecurityNumber` as the property name (also, the field name in the database table). |
|||
* You also need to provide an action that defines the database mapping properties using the [EF Core Fluent API](https://docs.microsoft.com/en-us/ef/core/modeling/entity-properties). |
|||
|
|||
> This code part must be executed before the related `DbContext` used. The [application startup template](Startup-Templates/Application.md) defines a static class named `YourProjectNameEntityExtensions`. You can define your extensions in this class to ensure that it is executed in the proper time. Otherwise, you should handle it yourself. |
|||
|
|||
Once you define an entity extension, you then need to use the standard [Add-Migration](https://docs.microsoft.com/en-us/ef/core/miscellaneous/cli/powershell#add-migration) and [Update-Database](https://docs.microsoft.com/en-us/ef/core/miscellaneous/cli/powershell#update-database) commands of the EF Core to create a code first migration class and update your database. |
|||
|
|||
You can then use the same extra properties system defined in the previous section to manipulate the property over the entity. |
|||
|
|||
## Creating a New Entity Maps to the Same Database Table/Collection |
|||
|
|||
Another approach can be **creating your own entity** mapped to **the same database table** (or collection for a MongoDB database). |
|||
|
|||
`AppUser` entity in the [application startup template](Startup-Templates/Application.md) already implements this approach. [EF Core Migrations document](Entity-Framework-Core-Migrations.md) describes how to implement it and manage **EF Core database migrations** in such a case. It is also possible for MongoDB, while this time you won't deal with the database migration problems. |
|||
|
|||
## Creating a New Entity with Its Own Database Table/Collection |
|||
|
|||
Mapping your entity to an **existing table** of a depended module has a few disadvantages; |
|||
|
|||
* You deal with the **database migration structure** for EF Core. While it is possible, you should extra care about the migration code especially when you want to add **relations** between entities. |
|||
* Your application database and the module database will be the **same physical database**. Normally, a module database can be separated if needed, but using the same table restricts it. |
|||
|
|||
If you want to **loose couple** your entity with the entity defined by the module, you can create your own database table/collection and map your entity to your own table in your own database. |
|||
|
|||
In this case, you need to deal with the **synchronization problems**, especially if you want to **duplicate** some properties/fields of the related entity. There are a few solutions; |
|||
|
|||
* If you are building a **monolithic** application (or managing your entity and the related module entity within the same process), you can use the [local event bus](Local-Event-Bus.md) to listen changes. |
|||
* If you are building a **distributed** system where the module entity is managed (created/updated/deleted) on a different process/service than your entity is managed, then you can subscribe to the [distributed event bus](Distributed-Event-Bus.md) for change events. |
|||
|
|||
Once you handle the event, you can update your own entity in your own database. |
|||
|
|||
### Subscribing to Local Events |
|||
|
|||
[Local Event Bus](Local-Event-Bus.md) system is a way to publish and subscribe to events occurring in the same application. |
|||
|
|||
Assume that you want to get informed when a `IdentityUser` entity changes (created, updated or deleted). You can create a class that implements the `ILocalEventHandler<EntityChangedEventData<IdentityUser>>` interface. |
|||
|
|||
````csharp |
|||
public class MyLocalIdentityUserChangeEventHandler : |
|||
ILocalEventHandler<EntityChangedEventData<IdentityUser>>, |
|||
ITransientDependency |
|||
{ |
|||
public async Task HandleEventAsync(EntityChangedEventData<IdentityUser> eventData) |
|||
{ |
|||
var userId = eventData.Entity.Id; |
|||
var userName = eventData.Entity.UserName; |
|||
|
|||
//... |
|||
} |
|||
} |
|||
```` |
|||
|
|||
* `EntityChangedEventData<T>` covers create, update and delete events for the given entity. If you need, you can subscribe to create, update and delete events individually (in the same class or different classes). |
|||
* This code will be executed **out of the local transaction**, because it listens the `EntityChanged` event. You can subscribe to the `EntityChangingEventData<T>` to perform your event handler in **the same local (in-process) transaction** if the current [unit of work](Unit-Of-Work.md) is transactional. |
|||
|
|||
> Reminder: This approach needs to change the `IdentityUser` entity in the same process contains the handler class. It perfectly works even for a clustered environment (when multiple instances of the same application are running on multiple servers). |
|||
|
|||
### Subscribing to Distributed Events |
|||
|
|||
[Distributed Event Bus](Distributed-Event-Bus.md) system is a way to publish an event in one application and receive the event in the same or different application running on the same or different server. |
|||
|
|||
Assume that you want to get informed when a `IdentityUser` entity created, updated or deleted. You can create a class like below: |
|||
|
|||
````csharp |
|||
public class MyDistributedIdentityUserChangeEventHandler : |
|||
IDistributedEventHandler<EntityCreatedEto<EntityEto>>, |
|||
IDistributedEventHandler<EntityUpdatedEto<EntityEto>>, |
|||
IDistributedEventHandler<EntityDeletedEto<EntityEto>>, |
|||
ITransientDependency |
|||
{ |
|||
public async Task HandleEventAsync(EntityCreatedEto<EntityEto> eventData) |
|||
{ |
|||
if (eventData.Entity.EntityType == "Volo.Abp.Identity.IdentityUser") |
|||
{ |
|||
var userId = Guid.Parse(eventData.Entity.KeysAsString); |
|||
//...handle the "created" event |
|||
} |
|||
} |
|||
|
|||
public async Task HandleEventAsync(EntityUpdatedEto<EntityEto> eventData) |
|||
{ |
|||
if (eventData.Entity.EntityType == "Volo.Abp.Identity.IdentityUser") |
|||
{ |
|||
var userId = Guid.Parse(eventData.Entity.KeysAsString); |
|||
//...handle the "updated" event |
|||
} |
|||
} |
|||
|
|||
public async Task HandleEventAsync(EntityDeletedEto<EntityEto> eventData) |
|||
{ |
|||
if (eventData.Entity.EntityType == "Volo.Abp.Identity.IdentityUser") |
|||
{ |
|||
var userId = Guid.Parse(eventData.Entity.KeysAsString); |
|||
//...handle the "deleted" event |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
* It implements multiple `IDistributedEventHandler` interfaces: **Created**, **Updated** and **Deleted**. Because, the distributed event bus system publishes events individually. There is no "Changed" event like the local event bus. |
|||
* It subscribes to `EntityEto`, which is a generic event class that is **automatically published** for all type of entities by the ABP framework. This is why it checks the **entity type** (checking the entity type as string since we assume that there is no type safe reference to the `IdentityUser` entity). |
|||
|
|||
Pre-built application modules do not define specialized event types yet (like `IdentityUserEto` - "ETO" means "Event Transfer Object"). This feature is on the road map and will be available in a short term ([follow this issue](https://github.com/abpframework/abp/issues/3033)). Once it is implemented, you will be able to subscribe to individual entity types. Example: |
|||
|
|||
````csharp |
|||
public class MyDistributedIdentityUserCreatedEventHandler : |
|||
IDistributedEventHandler<EntityCreatedEto<IdentityUserEto>>, |
|||
ITransientDependency |
|||
{ |
|||
public async Task HandleEventAsync(EntityCreatedEto<IdentityUserEto> eventData) |
|||
{ |
|||
var userId = eventData.Entity.Id; |
|||
var userName = eventData.Entity.UserName; |
|||
//...handle the "created" event |
|||
} |
|||
|
|||
//... |
|||
} |
|||
```` |
|||
|
|||
* This handler is executed only when a new user has been created. |
|||
|
|||
> The only pre-defined specialized event class is the `UserEto`. For example, you can subscribe to the `EntityCreatedEto<UserEto>` to get notified when a user has created. This event also works for the Identity module. |
|||
|
|||
## See Also |
|||
|
|||
* [Migration System for the EF Core](Entity-Framework-Core-Migrations.md) |
|||
* [Customizing the Existing Modules](Customizing-Application-Modules-Guide.md) |
|||
@ -0,0 +1,62 @@ |
|||
# Customizing the Existing Modules |
|||
|
|||
ABP Framework provides was designed to support to build fully [modular applications](Module-Development-Basics.md) and systems. It also provides some [pre-built application modules](Modules/Index.md) those are **ready to use** in any kind of application. |
|||
|
|||
For example, you can **re-use** the [Identity Management Module](Modules/Identity.md) to add user, role and permission management to your application. The [application startup template](Startup-Templates/Application.md) already comes with Identity and some other modules **pre-installed**. |
|||
|
|||
## Re-Using an Application Module |
|||
|
|||
You have two options to re-use an application module. |
|||
|
|||
### As Package References |
|||
|
|||
You can add **NuGet** & **NPM** package references of the related module to your application and configure the module (based on its documentation) to integrate to your application. |
|||
|
|||
As mentioned before, the [application startup template](Startup-Templates/Application.md) already comes with some **fundamental modules pre-installed**. It uses the modules as NuGet & NPM package references. |
|||
|
|||
This approach has the following benefits: |
|||
|
|||
* Your solution will be **clean** and only contains your **own application code**. |
|||
* You can **easily upgrade** a module when a new version is available. `abp update` [CLI](CLI.md) command makes it even easier. In this way, you can continue to get **new features and bug fixes**. |
|||
|
|||
However, there is a drawback: |
|||
|
|||
* You may not able to **customize** the module because the module source is not in your solution. |
|||
|
|||
This document explains **how to customize or extend** a depended module without need to change its source code. While it is limited compared to a full source code change opportunity, there are still some good ways to make some customizations. |
|||
|
|||
If you don't think to make huge changes on the pre-built modules, re-using them as package reference is the recommended way. |
|||
|
|||
### Including the Source Code |
|||
|
|||
If you want to make **huge changes** or add **major features** on a pre-built module, but the available extension points are not enough, you can consider to directly work the source code of the depended module. |
|||
|
|||
In this case, you typically **add the source code** of the module to your solution and **replace package references** by local project references. **[ABP CLI](CLI.md)** automates this process for you. |
|||
|
|||
#### Separating the Module Solution |
|||
|
|||
You may prefer to not include the module source code **directly into your solution**. Every module consists of 10+ project files and adding **multiple modules** may impact on the **size** of your solution **load & development time.** Also, you may have different development teams working on different modules, so you don't want to make the module code available to the application development team. |
|||
|
|||
In any case, you can create a **separate solution** for the desired module and depend on the module as project references out of the solution. We do it like that for the [abp repository](https://github.com/abpframework/abp/). |
|||
|
|||
> One problem we see is Visual Studio doesn't play nice with this kind of approach (it doesn't support well to have references to local projects out of the solution directory). If you get error while building the application (depends on an external module), run `dotnet restore` in the command line after opening the application's solution in the Visual Studio. |
|||
|
|||
#### Publishing the Customized Module as Packages |
|||
|
|||
One alternative scenario could be re-packaging the module source code (as NuGet/NPM packages) and using as package references. You can use a local private NuGet/NPM server for your company. |
|||
|
|||
## Module Customization / Extending Approaches |
|||
|
|||
This section suggests some approaches if you decided to use pre-built application modules as NuGet/NPM package references. The following documents explain how to customize/extend existing modules in different ways: |
|||
|
|||
* [Extending Entities](Customizing-Application-Modules-Extending-Entities.md) |
|||
* [Overriding Services](Customizing-Application-Modules-Overriding-Services.md) |
|||
* [Overriding the User Interface](Customizing-Application-Modules-Overriding-User-Interface.md) |
|||
|
|||
### See Also |
|||
|
|||
Also, see the following documents: |
|||
|
|||
* See [the localization document](Localization.md) to learn how to extend existing localization resources. |
|||
* See [the settings document](Settings.md) to learn how to change setting definitions of a depended module. |
|||
* See [the authorization document](Authorization.md) to learn how to change permission definitions of a depended module. |
|||
@ -0,0 +1,265 @@ |
|||
# Customizing the Application Modules: Overriding Services |
|||
|
|||
You may need to **change behavior (business logic)** of a depended module for your application. In this case, you can use the power of the [dependency injection system](Dependency-Injection.md) to replace a service, controller or even a page model of the depended module by your own implementation. |
|||
|
|||
**Replacing a service** is possible for any type of class registered to the dependency injection, including services of the ABP Framework. |
|||
|
|||
You have different options can be used based on your requirement those will be explained in the next sections. |
|||
|
|||
> Notice that some service methods may not be virtual, so you may not be able to override. We make all virtual by design. If you find any method that is not overridable, please [create an issue](https://github.com/abpframework/abp/issues/new) or do it yourself and send a **pull request** on GitHub. |
|||
|
|||
## Replacing an Interface |
|||
|
|||
If given service defines an interface, like the `IdentityUserAppService` class implements the `IIdentityUserAppService`, you can re-implement the same interface and replace the current implementation by your class. Example: |
|||
|
|||
````csharp |
|||
public class MyIdentityUserAppService : IIdentityUserAppService, ITransientDependency |
|||
{ |
|||
//... |
|||
} |
|||
```` |
|||
|
|||
`MyIdentityUserAppService` replaces the `IIdentityUserAppService` by naming convention (since both ends with `IdentityUserAppService`). If your class name doesn't match, you need to manually expose the service interface: |
|||
|
|||
````csharp |
|||
[ExposeServices(typeof(IIdentityUserAppService))] |
|||
public class TestAppService : IIdentityUserAppService, ITransientDependency |
|||
{ |
|||
//... |
|||
} |
|||
```` |
|||
|
|||
The dependency injection system allows to register multiple services for the same interface. The last registered one is used when the interface is injected. It is a good practice to explicitly replace the service. |
|||
|
|||
Example: |
|||
|
|||
````csharp |
|||
[Dependency(ReplaceServices = true)] |
|||
[ExposeServices(typeof(IIdentityUserAppService))] |
|||
public class TestAppService : IIdentityUserAppService, ITransientDependency |
|||
{ |
|||
//... |
|||
} |
|||
```` |
|||
|
|||
In this way, there will be a single implementation of the `IIdentityUserAppService` interface, while it doesn't change the result for this case. Replacing a service is also possible by code: |
|||
|
|||
````csharp |
|||
context.Services.Replace( |
|||
ServiceDescriptor.Transient<IIdentityUserAppService, MyIdentityUserAppService>() |
|||
); |
|||
```` |
|||
|
|||
You can write this inside the `ConfigureServices` method of your [module](Module-Development-Basics.md). |
|||
|
|||
## Overriding a Service Class |
|||
|
|||
In most cases, you will want to change one or a few methods of the current implementation for a service. Re-implementing the complete interface would not be efficient in this case. As a better approach, inherit from the original class and override the desired method. |
|||
|
|||
### Example: Overriding an Application Service |
|||
|
|||
````csharp |
|||
[Dependency(ReplaceServices = true)] |
|||
public class MyIdentityUserAppService : IdentityUserAppService |
|||
{ |
|||
//... |
|||
public MyIdentityUserAppService( |
|||
IdentityUserManager userManager, |
|||
IIdentityUserRepository userRepository, |
|||
IGuidGenerator guidGenerator |
|||
) : base( |
|||
userManager, |
|||
userRepository, |
|||
guidGenerator) |
|||
{ |
|||
} |
|||
|
|||
public override async Task<IdentityUserDto> CreateAsync(IdentityUserCreateDto input) |
|||
{ |
|||
if (input.PhoneNumber.IsNullOrWhiteSpace()) |
|||
{ |
|||
throw new AbpValidationException( |
|||
"Phone number is required for new users!", |
|||
new List<ValidationResult> |
|||
{ |
|||
new ValidationResult( |
|||
"Phone number can not be empty!", |
|||
new []{"PhoneNumber"} |
|||
) |
|||
} |
|||
); } |
|||
|
|||
return await base.CreateAsync(input); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
This class **overrides** the `CreateAsync` method of the `IdentityUserAppService` [application service](Application-Services.md) to check the phone number. Then calls the base method to continue to the **underlying business logic**. In this way, you can perform additional business logic **before** and **after** the base logic. |
|||
|
|||
You could completely **re-write** the entire business logic for a user creation without calling the base method. |
|||
|
|||
### Example: Overriding a Domain Service |
|||
|
|||
````csharp |
|||
[Dependency(ReplaceServices = true)] |
|||
[ExposeServices(typeof(IdentityUserManager))] |
|||
public class MyIdentityUserManager : IdentityUserManager |
|||
{ |
|||
public MyIdentityUserManager( |
|||
IdentityUserStore store, |
|||
IOptions<IdentityOptions> optionsAccessor, |
|||
IPasswordHasher<IdentityUser> passwordHasher, |
|||
IEnumerable<IUserValidator<IdentityUser>> userValidators, |
|||
IEnumerable<IPasswordValidator<IdentityUser>> passwordValidators, |
|||
ILookupNormalizer keyNormalizer, |
|||
IdentityErrorDescriber errors, |
|||
IServiceProvider services, |
|||
ILogger<IdentityUserManager> logger, |
|||
ICancellationTokenProvider cancellationTokenProvider |
|||
) : base( |
|||
store, |
|||
optionsAccessor, |
|||
passwordHasher, |
|||
userValidators, |
|||
passwordValidators, |
|||
keyNormalizer, |
|||
errors, |
|||
services, |
|||
logger, |
|||
cancellationTokenProvider) |
|||
{ |
|||
} |
|||
|
|||
public override async Task<IdentityResult> CreateAsync(IdentityUser user) |
|||
{ |
|||
if (user.PhoneNumber.IsNullOrWhiteSpace()) |
|||
{ |
|||
throw new AbpValidationException( |
|||
"Phone number is required for new users!", |
|||
new List<ValidationResult> |
|||
{ |
|||
new ValidationResult( |
|||
"Phone number can not be empty!", |
|||
new []{"PhoneNumber"} |
|||
) |
|||
} |
|||
); |
|||
} |
|||
|
|||
return await base.CreateAsync(user); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
This example class inherits from the `IdentityUserManager` [domain service](Domain-Services.md) and overrides the `CreateAsync` method to perform the same phone number check implemented above. The result is same, but this time we've implemented it inside the domain service assuming that this is a **core domain logic** for our system. |
|||
|
|||
> `[ExposeServices(typeof(IdentityUserManager))]` attribute is **required** here since `IdentityUserManager` does not define an interface (like `IIdentityUserManager`) and dependency injection system doesn't expose services for inherited classes (like it does for the implemented interfaces) by convention. |
|||
|
|||
Check the [localization system](Localization.md) to learn how to localize the error messages. |
|||
|
|||
### Overriding Other Classes |
|||
|
|||
Overriding controllers, framework services, view component classes and any other type of classes registered to dependency injection can be overridden just like the examples above. |
|||
|
|||
## Extending Data Transfer Objects |
|||
|
|||
**Extending [entities](Entities.md)** is possible as described in the [Extending Entities document](Customizing-Application-Modules-Extending-Entities.md). In this way, you can add **custom properties** to entities and perform **additional business logic** by overriding the related services as described above. |
|||
|
|||
It is also possible to extend Data Transfer Objects (**DTOs**) used by the application services. In this way, you can get extra properties from the UI (or client) and return extra properties from the service. |
|||
|
|||
### Example |
|||
|
|||
Assuming that you've already added a `SocialSecurityNumber` as described in the [Extending Entities document](Customizing-Application-Modules-Extending-Entities.md) and want to include this information while getting the list of users from the `GetListAsync` method of the `IdentityUserAppService`. |
|||
|
|||
You can use the [object extension system](Object-Extensions.md) to add the property to the `IdentityUserDto`. Write this code inside the `YourProjectNameDtoExtensions` class comes with the application startup template: |
|||
|
|||
````csharp |
|||
ObjectExtensionManager.Instance |
|||
.AddOrUpdateProperty<IdentityUserDto, string>( |
|||
"SocialSecurityNumber" |
|||
); |
|||
```` |
|||
|
|||
This code defines a `SocialSecurityNumber` to the `IdentityUserDto` class as a `string` type. That's all. Now, if you call the `/api/identity/users` HTTP API (which uses the `IdentityUserAppService` internally) from a REST API client, you will see the `SocialSecurityNumber` value in the `extraProperties` section. |
|||
|
|||
````json |
|||
{ |
|||
"totalCount": 1, |
|||
"items": [{ |
|||
"tenantId": null, |
|||
"userName": "admin", |
|||
"name": "admin", |
|||
"surname": null, |
|||
"email": "admin@abp.io", |
|||
"emailConfirmed": false, |
|||
"phoneNumber": null, |
|||
"phoneNumberConfirmed": false, |
|||
"twoFactorEnabled": false, |
|||
"lockoutEnabled": true, |
|||
"lockoutEnd": null, |
|||
"concurrencyStamp": "b4c371a0ab604de28af472fa79c3b70c", |
|||
"isDeleted": false, |
|||
"deleterId": null, |
|||
"deletionTime": null, |
|||
"lastModificationTime": "2020-04-09T21:25:47.0740706", |
|||
"lastModifierId": null, |
|||
"creationTime": "2020-04-09T21:25:46.8308744", |
|||
"creatorId": null, |
|||
"id": "8edecb8f-1894-a9b1-833b-39f4725db2a3", |
|||
"extraProperties": { |
|||
"SocialSecurityNumber": "123456789" |
|||
} |
|||
}] |
|||
} |
|||
```` |
|||
|
|||
Manually added the `123456789` value to the database for now. |
|||
|
|||
All pre-built modules support extra properties in their DTOs, so you can configure easily. |
|||
|
|||
### Definition Check |
|||
|
|||
When you [define](Customizing-Application-Modules-Extending-Entities.md) an extra property for an entity, it doesn't automatically appear in all the related DTOs, because of the security. The extra property may contain a sensitive data and you may not want to expose it to the clients by default. |
|||
|
|||
So, you need to explicitly define the same property for the corresponding DTO if you want to make it available for the DTO (as just done above). If you want to allow to set it on user creation, you also need to define it for the `IdentityUserCreateDto`. |
|||
|
|||
If the property is not so secure, this can be tedious. Object extension system allows you to ignore this definition check for a desired property. See the example below: |
|||
|
|||
````csharp |
|||
ObjectExtensionManager.Instance |
|||
.AddOrUpdateProperty<IdentityUser, string>( |
|||
"SocialSecurityNumber", |
|||
options => |
|||
{ |
|||
options.MapEfCore(b => b.HasMaxLength(32)); |
|||
options.CheckPairDefinitionOnMapping = false; |
|||
} |
|||
); |
|||
```` |
|||
|
|||
This is another approach to define a property for an entity (`ObjectExtensionManager` has more, see [its document](Object-Extensions.md)). This time, we set `CheckPairDefinitionOnMapping` to false to skip definition check while mapping entities to DTOs and vice verse. |
|||
|
|||
If you don't like this approach but want to add a single property to multiple objects (DTOs) easier, `AddOrUpdateProperty` can get an array of types to add the extra property: |
|||
|
|||
````csharp |
|||
ObjectExtensionManager.Instance |
|||
.AddOrUpdateProperty<string>( |
|||
new[] |
|||
{ |
|||
typeof(IdentityUserDto), |
|||
typeof(IdentityUserCreateDto), |
|||
typeof(IdentityUserUpdateDto) |
|||
}, |
|||
"SocialSecurityNumber" |
|||
); |
|||
```` |
|||
|
|||
### About the User Interface |
|||
|
|||
This system allows you to add extra properties to entities and DTOs and execute custom business code, however it does nothing related to the User Interface. |
|||
|
|||
See [Overriding the User Interface](Customizing-Application-Modules-Overriding-User-Interface.md) guide for the UI part. |
|||
|
|||
## How to Find the Services? |
|||
|
|||
[Module documents](Modules/Index.md) includes the list of the major services they define. In addition, you can investigate [their source code](https://github.com/abpframework/abp/tree/dev/modules) to explore all the services. |
|||
@ -0,0 +1,6 @@ |
|||
# Overriding the User Interface |
|||
|
|||
You may want to override a page, a component, a JavaScript, CSS or an image file of your depended module. Overriding the UI completely depends on the UI framework you're using. Select the UI framework to continue: |
|||
|
|||
* [ASP.NET Core (MVC / Razor Pages)](UI/AspNetCore/Customization-User-Interface.md) |
|||
* [Angular](UI/Angular/Customization-User-Interface.md) |
|||
@ -1,11 +1,15 @@ |
|||
# Data Access |
|||
|
|||
ABP framework was designed as database agnostic, it can work any type of data source by the help of the [repository](Repositories.md) and [unit of work](Unit-Of-Work.md) abstractions. |
|||
## Database Providers |
|||
|
|||
However, currently the following providers are implements: |
|||
ABP framework was designed as database agnostic. It can work any type of data source by the help of the [repository](Repositories.md) and [unit of work](Unit-Of-Work.md) abstractions. However, currently the following providers are implemented: |
|||
|
|||
* [Entity Framework Core](Entity-Framework-Core.md) (works with [various DBMS and providers](https://docs.microsoft.com/en-us/ef/core/providers/?tabs=dotnet-core-cli).) |
|||
* [Entity Framework Core](Entity-Framework-Core.md) (works with [various DBMS and providers](https://docs.microsoft.com/en-us/ef/core/providers/).) |
|||
* [MongoDB](MongoDB.md) |
|||
* [Dapper](Dapper.md) |
|||
|
|||
More providers might be added in the next releases. |
|||
More providers will be added in the future. |
|||
|
|||
## See Also |
|||
|
|||
* [Connection Strings](Connection-Strings.md) |
|||