Browse Source

Merge branch 'dev' into stsrki/dev-blazor-server

pull/8074/head
Halil İbrahim Kalkan 5 years ago
parent
commit
27937be657
  1. 1
      .gitignore
  2. 4
      README.md
  3. 9
      abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json
  4. 5
      abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/tr.json
  5. 25
      abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/zh-Hans.json
  6. 4
      abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json
  7. 4
      abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/zh-Hans.json
  8. 248
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json
  9. 257
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/zh-Hans.json
  10. 5
      abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json
  11. 31
      abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/zh-Hans.json
  12. 4
      abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/de-DE.json
  13. 3
      abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en-GB.json
  14. 5
      abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json
  15. 4
      abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/es.json
  16. 7
      abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/zh-Hans.json
  17. 20
      docs/en/Background-Jobs-RabbitMq.md
  18. 6
      docs/en/Best-Practices/Entity-Framework-Core-Integration.md
  19. 2
      docs/en/Blob-Storing-Aliyun.md
  20. 2
      docs/en/Blob-Storing-Aws.md
  21. 2
      docs/en/Blob-Storing-Azure.md
  22. 21
      docs/en/CLI.md
  23. 2
      docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/How-To-Add-Custom-Property-To-The-User-Entity.md
  24. 8
      docs/en/Customizing-Application-Modules-Overriding-Services.md
  25. 22
      docs/en/Distributed-Event-Bus-RabbitMQ-Integration.md
  26. 16
      docs/en/Tutorials/Part-2.md
  27. 23
      docs/en/Tutorials/Part-3.md
  28. 2
      docs/en/Tutorials/Part-5.md
  29. 6
      docs/en/Tutorials/Part-6.md
  30. 176
      docs/en/Tutorials/Part-9.md
  31. 1
      docs/en/UI/Angular/Component-Replacement.md
  32. 101
      docs/en/UI/Angular/HTTP-Requests.md
  33. 288
      docs/en/UI/Angular/How-Replaceable-Components-Work-with-Extensions.md
  34. 4
      docs/en/UI/Angular/Multi-Tenancy.md
  35. 2
      docs/en/UI/Angular/Router-Events.md
  36. 4
      docs/en/UI/Angular/Service-Proxies.md
  37. BIN
      docs/en/UI/Angular/images/custom-error-handler-404-component.jpg
  38. BIN
      docs/en/UI/Angular/images/custom-error-handler-toaster-message.jpg
  39. BIN
      docs/en/UI/Angular/images/my-roles-component-form-extensions.jpg
  40. BIN
      docs/en/UI/Angular/images/my-roles-component-with-extensions.jpg
  41. 21
      docs/en/docs-nav.json
  42. 154
      docs/zh-Hans/Background-Jobs-RabbitMq.md
  43. 2
      docs/zh-Hans/Blob-Storing-Aliyun.md
  44. 2
      docs/zh-Hans/Blob-Storing-Aws.md
  45. 2
      docs/zh-Hans/Blob-Storing-Azure.md
  46. 20
      docs/zh-Hans/Distributed-Event-Bus-RabbitMQ-Integration.md
  47. 2
      docs/zh-Hans/Repositories.md
  48. 14
      framework/Volo.Abp.sln
  49. 7
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/LoginDisplay.razor
  50. 14
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/SecondLevelNavMenuItem.razor
  51. 1
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/TuiEditor/TuiEditorScriptContributor.cs
  52. 8
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Toolbars/ToolbarItem.cs
  53. 31
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Toolbars/ToolbarManager.cs
  54. 2
      framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo.Abp.AspNetCore.Mvc.UI.csproj
  55. 34
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpRemoteServiceApiDescriptionProvider.cs
  56. 2
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs
  57. 27
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProviderOptions.cs
  58. 33
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs
  59. 5
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowActionFilter.cs
  60. 5
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowPageFilter.cs
  61. 6
      framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/AbpApplicationBuilderExtensions.cs
  62. 30
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/AbpSecurityHeadersMiddleware.cs
  63. 17
      framework/src/Volo.Abp.Authorization/Microsoft/AspNetCore/Authorization/AbpAuthorizationServiceExtensions.cs
  64. 67
      framework/src/Volo.Abp.Autofac/Autofac/Extensions/DependencyInjection/ServiceCollectionExtensions.cs
  65. 11
      framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunBlobProvider.cs
  66. 2
      framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunBlobProviderConfiguration.cs
  67. 14
      framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobProvider.cs
  68. 2
      framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobProviderConfiguration.cs
  69. 14
      framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProvider.cs
  70. 2
      framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProviderConfiguration.cs
  71. 1
      framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/MinioBlobContainerConfigurationExtensions.cs
  72. 12
      framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/MinioBlobProvider.cs
  73. 2
      framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/MinioBlobProviderConfiguration.cs
  74. 59
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainer.cs
  75. 7
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainerFactory.cs
  76. 15
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobNormalizeNaming.cs
  77. 64
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobNormalizeNamingService.cs
  78. 2
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobProviderBase.cs
  79. 11
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/IBlobNormalizeNamingService.cs
  80. 3
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs
  81. 7
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddModuleCommand.cs
  82. 65
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/CreateMigrationAndRunMigrator.cs
  83. 152
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/CreateMigrationAndRunMigratorCommand.cs
  84. 79
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ListModulesCommand.cs
  85. 62
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs
  86. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ModuleProjectBuildPipelineBuilder.cs
  87. 9
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/DatabaseManagementSystemChangeStep.cs
  88. 78
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/MicroserviceServiceRandomPortStep.cs
  89. 30
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ProjectReferenceReplaceStep.cs
  90. 8
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/RemoveCmsKitStep.cs
  91. 9
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/RemoveEfCoreDependencyFromPublicStep.cs
  92. 11
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/RemoveGlobalFeaturesPackageStep.cs
  93. 4
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/RemoveProjectFromSolutionStep.cs
  94. 54
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/RemoveProjectFromTyeStep.cs
  95. 14
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/RemovePublicRedisStep.cs
  96. 36
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/SolutionRenameStep.cs
  97. 3
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/TemplateProjectBuildPipelineBuilder.cs
  98. 10
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Files/FileEntryExtensions.cs
  99. 7
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/SolutionName.cs
  100. 4
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/TemplateInfoProvider.cs

1
.gitignore

@ -269,6 +269,7 @@ modules/blog/app/Volo.BlogTestApp/Logs/*.*
modules/blogging/app/Volo.BloggingTestApp/Logs/*.*
modules/blogging/app/Volo.BloggingTestApp/wwwroot/files/*.*
modules/docs/app/VoloDocs.Web/Logs/*.*
modules/setting-management/app/Volo.Abp.SettingManagement.DemoApp/Logs/*.*
templates/module/app/MyCompanyName.MyProjectName.DemoApp/Logs/*.*
templates/mvc/src/MyCompanyName.MyProjectName.Web/Logs/*.*

4
README.md

@ -105,3 +105,7 @@ ABP is a community-driven open source project. See [the contribution guide](http
## Support the ABP Framework
Love ABP Framework? **Please give a star** to this repository :star:
## ABP Commercial
See also [ABP Commercial](https://commercial.abp.io/) if you are looking for pre-built application modules, professional themes, code generation tooling and premium support for the ABP Framework.

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

@ -35,6 +35,7 @@
"NameFilter": "Name",
"CreationTime": "Creation time",
"IsPro": "Is pro",
"IsFreeToActiveLicenseOwners": "Free to license owners",
"ShowOnModuleList": "Show on module list",
"EfCoreConfigureMethodName": "Configure method name",
"IsProFilter": "Is pro",
@ -90,6 +91,7 @@
"UserNotFound": "User not found",
"{0}WillBeRemovedFromDevelopers": "{0} Will be removed from developers, do you confirm?",
"{0}WillBeRemovedFromOwners": "{0} Will be removed from owners, do you confirm?",
"{0}WillBeRemovedFromMembers": "{0} Will be removed from members, do you confirm?",
"Computers": "Computers",
"UniqueComputerId": "Unique computer id",
"LastSeenDate": "Last seen date",
@ -212,7 +214,10 @@
"Currency": "Currency",
"Gateway": "Gateway",
"State": "State",
"FailReason": "Fail reason"
"FailReason": "Fail reason",
"ReIndexAllPosts": "Reindex All Posts",
"ReIndexAllPostsConfirmationMessage": "Are you sure you want to reindex all posts?",
"SuccessfullyReIndexAllPosts": "All posts have been successfully reindexed.",
"Permission:FullSearch": "Full text search"
}
}

5
abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/tr.json

@ -163,6 +163,9 @@
"Enum:ContentSource:2": "Video İçerik",
"DeleteCoverImage": "Kapak Fotoğrafını Sil",
"DeleteCoverImageConfirmationMessage": "Kapak fotoğrafını \"{0}\" isimli makale için silmek istediğinize emin misiniz?",
"DeleteCoverImageSuccessMessage": "Kapak fotoğrafı başarılı bir şekilde silinmiştir"
"DeleteCoverImageSuccessMessage": "Kapak fotoğrafı başarılı bir şekilde silinmiştir",
"ReIndexAllPosts": "Yeniden indeksle",
"ReIndexAllPostsConfirmationMessage": "Tüm gönderileri yeniden indeksleyeceğinizden emin misiniz?",
"SuccessfullyReIndexAllPosts": "Tüm gönderiler başarıyla yeniden indekslendi"
}
}

25
abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/zh-Hans.json

@ -35,6 +35,7 @@
"NameFilter": "名称",
"CreationTime": "创建时间",
"IsPro": "是否为专业版",
"IsFreeToActiveLicenseOwners": "许可证所有者免费",
"ShowOnModuleList": "展示模块列表",
"EfCoreConfigureMethodName": "配置方法法",
"IsProFilter": "是否为专业版",
@ -90,6 +91,7 @@
"UserNotFound": "用户不存在",
"{0}WillBeRemovedFromDevelopers": "{0} 将从开发者中移除, 你确定吗?",
"{0}WillBeRemovedFromOwners": "{0} 将从所有者中移除, 你确定吗?",
"{0}WillBeRemovedFromMembers": "{0} 将从成员中删除, 你确定吗?",
"Computers": "计算机",
"UniqueComputerId": "计算机唯一ID",
"LastSeenDate": "上次查看日期",
@ -194,6 +196,27 @@
"CoverImage": "封面图片",
"RemoveCacheConfirmationMessage": "你确定要删除\"{0}\" 文章缓存?",
"SuccessfullyRemoved": "清除成功",
"RemoveCache": "删除缓存"
"RemoveCache": "删除缓存",
"Language": "语言",
"Optional": "可选的",
"CreateArticleLanguageInfo": "发帖所用的语言",
"Enum:ContentSource:2": "视频发布",
"VideoPreview": "视频预览",
"VideoPreviewErrorMessage": "无法从YouTube获取给定的视频网址. 可能是由于视频是私有视频,或者给定的URL不可用.",
"DeleteCoverImage": "删除封面图片",
"DeleteCoverImageConfirmationMessage": "你确定要删除封面图片吗",
"DeleteCoverImageSuccessMessage": "成功删除封面图片",
"PaymentsOf": "付款的",
"ShowPaymentsOfOrganization": "显示付款",
"Date": "日期",
"Products": "商品",
"TotalAmount": "总金额",
"Currency": "货币",
"Gateway": "网关",
"State": "状态",
"FailReason": "失败原因",
"ReIndexAllPosts": "重新索引所有帖子",
"ReIndexAllPostsConfirmationMessage": "你确定要重新索引所有的帖子吗",
"SuccessfullyReIndexAllPosts": "成功索引所有的帖子"
}
}

4
abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json

@ -30,6 +30,8 @@
"Permission:License": "License",
"Permission:UserInfo": "Usere info",
"SeeDocuments": "See Documents",
"Samples": "Samples"
"Samples": "Samples",
"Framework": "Framework",
"Support": "Support"
}
}

4
abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/zh-Hans.json

@ -30,6 +30,8 @@
"Permission:License": "许可",
"Permission:UserInfo": "用户信息",
"SeeDocuments": "查看文档",
"Samples": "示例"
"Samples": "示例",
"Framework": "框架",
"Support": "支持"
}
}

248
abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json

@ -41,6 +41,250 @@
"Unsubscribe": "Unsubscribe",
"NotOrganizationMember": "You are not member of any organization.",
"UnsubscribeLicenseExpirationEmailSuccessTitle": "Successfully unsubscribed",
"UnsubscribeLicenseExpirationEmailSuccessMessage": "You will not receive license expiration date reminder emails anymore."
}
"UnsubscribeLicenseExpirationEmailSuccessMessage": "You will not receive license expiration date reminder emails anymore.",
"IndexPageHeroSection": "<span class=\"first-line\">A complete</span><span class=\"second-line\"> web development platform</span><span class=\"third-line\">built-on <img src=\"{0}\" width=\"110\" class=\"ml-1\" /> framework</span>",
"AbpCommercialShortDescription": "ABP Commercial provides pre-built application modules, rapid application development tooling, professional UI themes, premium support and more.",
"LiveDemo": "Live Demo",
"GetLicence" :"Get a Licence",
"Application": "Application",
"StartupTemplates": "Startup Templates",
"Startup": "Startup",
"Templates": "Templates",
"Developer": "Developer",
"Tools": "Tools",
"Premium": "Premium",
"PremiumSupport": "Premium Support",
"PremiumForumSupport": "Premium Forum Support",
"UI": "UI",
"Themes": "Themes",
"JoinOurNewsletter": "Join Our Newsletter",
"Send": "Send",
"Learn": "Learn",
"AdditionalServices": "Additional Services",
"WhatIsABPFramework": "WHAT IS THE ABP FRAMEWORK?",
"OpenSourceBaseFramework": "Open Source Base Framework",
"ABPFrameworkExplanation": "<p class=\"lead\">ABP Commercial is based on the ABP Framework, an open source and community driven web application framework for ASP.NET Core.</p><p>ABP Framework provides an excellent infrastructure to write maintainable, extensible and testable code with best practices.</p><p>Built on and integrated to popular tools you already know. Low learning curve, easy adaptation, comfortable development.</p>",
"Modular": "Modular",
"MicroserviceCompatible": "Microservice compatible",
"DomainDrivenDesignInfrastructure": "Domain Driven Design Infrastructure",
"MultiTenancy": "Multi-Tenancy",
"DistributedMessaging": "Distributed Messaging",
"DynamicProxying": "Dynamic Proxying",
"BackgroundJobs": "Background Jobs",
"AuditLogging": "Audit Logging",
"BLOBStoring": "BLOB Storing",
"BundlingMinification": "Bundling & Minification",
"AdvancedLocalization": "Advanced Localization",
"ManyMore": "Many more",
"ExploreTheABPFramework": "Explore the ABP Framework",
"WhyUseTheABPCommercial": "Why Use The ABP Commercial?",
"WhyUseTheABPCommercialExplanation": "<p class=\"lead mt- 5\">Building enterprise-grade web applications can be complex and time-consuming.</p><p>ABP Commercial offers the perfect base infrastructure necessary for all modern enterprise-grade ASP.NET Core based solutions. Right from the design to deployment, the entire development cycle is empowered by ABP's built-in features & modules.</p>",
"StartupTemplatesShortDescription": "Startup templates make you jump-start to your project in a few seconds.",
"UIFrameworksOptions": "UI frameworks options;",
"DatabaseProviderOptions": "Database provider options;",
"PreBuiltApplicationModules": "Pre-Built Application Modules",
"PreBuiltApplicationModulesShortDescription": "Most common application requirements are already developed for you as reusable modules.",
"Account": "Account",
"Blogging": "Blogging",
"Identity": "Identity",
"IdentityServer": "Identity Server",
"Saas": "Saas",
"LanguageManagement": "Language Management",
"TextTemplateManagement": "Text Template Management",
"See All Modules": "SeeAllModules",
"ABPSuite": "ABP Suite",
"AbpSuiteShortDescription": "ABP Suite is a complementary tool to the ABP Commercial.",
"AbpSuiteExplanation": "It allows you to build web pages in a matter of minutes. It's a .NET Core Global tool which can be installed from the command line. It can create a new ABP solution, generate CRUD pages from the database to the front-end.",
"Details": "Details",
"LeptonTheme": "Lepton Theme",
"ProfessionalModernUIThemes": "Professional, modern UI themes",
"LeptonThemeExplanation": "Lepton provides a gamut of Bootstrap admin themes that serve as a solid foundation for any project requiring admin dashboard.",
"DefaultTheme": "Default Theme",
"MaterialTheme": "Material Theme",
"Default2Theme": "Default 2 Theme",
"DarkTheme": "Dark Theme",
"DarkBlueTheme": "Dark Blue Theme",
"LightTheme": "Light Theme",
"ProudToWorkWith": "Proud to Work With",
"OurConsumers": "Hundreds of enterprises and developers over 50 countries worldwide rely on ABP Commercial.",
"JoinOurConsumers": "Join them and build amazing products fast.",
"AdditionalServicesExplanation": "Do you need additional or custom services? <strong>We and our partners can provide;</strong>",
"CustomProjectDevelopment": "Custom Project Development",
"CustomProjectDevelopmentExplanation": "Dedicated developers for your custom projects.",
"PortingExistingProjects": "Porting Existing Projects",
"PortingExistingProjectsExplanation": "Migrating your legacy projects to the ABP platform.",
"LiveSupport": "Live Support",
"LiveSupportExplanation": "Live remote support option when you need it.",
"Training": "Training",
"TrainingExplanation": "Dedicated training for your developers.",
"OnBoarding": "Onboarding",
"OnBoardingExplanation": "Help to setup your development, CI & CD environments.",
"PrioritizedTechnicalSupport": "Prioritized Technical Support",
"PremiumSupportExplanation": "Beside the great community support of the ABP framework, our support team answers technical questions and problems of the commercial users with high priority.",
"SeeTheSupportOptions": "See the Support Options",
"Contact": "Contact",
"TellUsWhatYouNeed": "Tell us what you need.",
"YourMessage": "Your Message",
"YourFullName": "Your full name",
"EmailField": "E-mail Address",
"YourEmailAddress": "Your e-mail address",
"HowMayWeHelpYou": "How may we help you",
"SendMessage": "Send Message",
"Success": "Success",
"WeWillReplyYou.": "Your message is sent! We will reply you in a short time.",
"GoHome": "Go Home",
"CreateLiveDemo": "Create Live Demo",
"RegisterToTheNewsletter": "Register to the newsletter to get information on happenings about ABP.IO, like new releases.",
"EnterYourEmailOrLogin": "Enter your e-mail address to create your demo or <a href=\"{0}\">Login</a> using your existing account.",
"ApplicationTemplate": "Application Template",
"ApplicationTemplateExplanation": "Application startup template is used to create a new web application.",
"EfCoreProvider": "Entity Framework (Supports SQL Server, MySQL, PostgreSQL, Oracle <a href=\"https://docs.microsoft.com/en-us/ef/core/providers/\">and others</a>)",
"AlreadyIncludedInTemplateModules": "Following modules are already included and configured in this template:",
"ApplicationTemplateArchitecture": "This application template also supports tiered architecture where UI layer, API layer and the authentication service are physically separated.",
"SeeTheGuideOrGoToTheLiveDemo": "See the developer guide for technical information about this template or go to the live demo.",
"DeveloperGuide": "Developer Guide",
"ModuleTemplate": "Module Template",
"ModuleTemplateExplanation1": "You want to create a module and reuse across different applications? This startup template prepares everything to start to create a reusable <strong>application module</strong> or a <strong>microservice</strong>.",
"ModuleTemplateExplanation2": "<p>You can support single or multiple UI frameworks, single or multiple database providers for a single module. The startup template is configured to run and <strong>test your module</strong> in a minimal application in addition to the unit and integration test infrastructure. </p> <p>See the developer guide for technical information about this template.</p>",
"WithAllStyleOptions": "with all style options",
"Demo": "Demo",
"SeeAllModules": "See All Modules",
"ABPCLIExplanation": "ABP CLI (Command Line Interface) is a command line tool to perform some common operations for ABP based solutions.",
"ABPSuiteEasilyCURD": "ABP Suite is a tool which allows you to easily create CRUD pages",
"WeAreHereToHelp": "We are Here to <span class=\"zero-text\">Help</span>",
"BrowseOrAskQuestion": "You can browse our help topics or search in frequently asked questions, or you can ask us a question by using the <a href=\"{0}\" class=\"text-success\">contact form</a>.",
"SearchQuestionPlaceholder": "Search in frequently asked questions",
"WhatIsTheABPCommercial": "What is the ABP Commercial?",
"WhatAreDifferencesThanAbpFramework": "What are the differences between the open source ABP Framework and the ABP Commercial?",
"ABPCommercialExplanation": "ABP Commercial is a set of premium modules, tools, themes and services built on top of the open source <a target=\"_blank\" href=\"{0}\">ABP framework</a>. ABP Commercial is being developed and supported by the same team behind the ABP framework.",
"WhatAreDifferencesThanABPFrameworkExplanation": "<p> <a target=\"_blank\" href=\"{0}\">ABP framework</a> is a modular, themeable, micro-service compatible application development framework for ASP.NET Core. It provides a complete architecture and a strong infrastructure to make you focusing on your own business code rather than repeating yourself for every new project. It is based on software development best practices and popular tools you already know. </p> <p> ABP framework is completely free, open source and community-driven. It also provides a free theme and some pre-built modules (e.g. identity management and tenant management).</p>",
"VisitTheFrameworkVSCommercialDocument": "Visit the following link, for more information <a href=\"{0}\" target=\"_blank\"> {1} </a>",
"ABPCommercialFollowingBenefits": "ABP Commercial adds the following benefits on top of the ABP framework;",
"Professional": "Professional",
"UIThemes": "UI themes",
"EnterpriseModules": "Enterprise ready, feature rich, pre-built <a href=\"{0}\">application modules</a> (e.g. Identity Server management, SaaS management, language management)",
"ToolingToSupport": "Tooling to support your development productivity (e.g. <a href=\"{0}\">ABP Suite</a>)",
"PremiumSupportLink": "Premium <a href=\"{0}\" target=\"_blank\">support</a>",
"WhatDoIDownloadABPCommercial": "What do I download when I purchase the ABP Commercial?",
"CreateUnlimitedSolutions": "Once you purchase an ABP Commercial license, you will be able to create unlimited solutions like described in the <a href=\"{0}\">Getting Started</a> document.",
"ABPCommercialSolutionExplanation": "When you create a new application, you get a Visual Studio solution (a startup template) based on your preferences. The downloaded solution has commercial modules and themes already installed and configured for you. You can remove a pre-installed module or add another module if you like. All modules and themes are used a NuGet/NPM packages by default.",
"StartDevelopWithTutorials": "The downloaded solution is well architected and documented. You can start to develop your own business code based on it following <a href=\"{0}\">the tutorials</a>",
"TryTheCommercialDemo": "You can try <a href=\"{0}\">the demo</a> to see a sample application created using the ABP Commercial startup template.",
"HowManyProducts": "How many different products/solutions can I build using the ABP Commercial?",
"HowManyProductsExplanation": "There is no limit to create an ABP project. You can create as many project as you want, develop and upload them to different servers.",
"HowManyDevelopers": "How many developers can work on the ABP Commercial?",
"HowManyDevelopersExplanation": "ABP Commercial licenses are per developer. Different license types have different developer limits. However, you can add more developers to any license type whenever you need. See <a href=\"{0}\">the prices</a> page for license types, developer limits and additional developer costs.",
"ChangingLicenseType": "Can I change my license type in the future?",
"ChangingLicenseTypeExplanation": "You can always add new developers in your same license type. See also \"How many developers can work on the ABP Commercial?\". You can also upgrade to a higher license by paying the calculated price difference. When you upgrade to a higher license plan, you get the benefits of the new plan, but the license upgrade does not change the license expiry date.",
"LicenseExtendUpgradeDiff": "What is the difference between license extend and upgrade?",
"LicenseExtendUpgradeDiffExplanation": "<strong>Extending:</strong> By extending/renewing your license, you will continue to get premium support and get major updates for the modules and themes. Besides, you will be able to continue creating new projects. And you will still be able to use ABP Suite which speeds up your development.<hr/><strong>Upgrading:</strong> By upgrading your license, you will promote to a higher license plan which will allow you to get additional benefits. See the <a href=\"/pricing\">license comparison table</a> to check the differences between the license plans.<strong>On the other hand, when you upgrade, your license expiry date will not change!</strong>To extend your license end date, you need to extend your license.",
"LicenseRenewalCost": "What is the license renewal cost after 1 year?",
"LicenseRenewalCostExplanation": "You can renew (extend) your license by paying %80 of the current license price. For example; Standard ABP Team License is <span class=\"text-muted\">$</span>{0} and renewal price of the Team License is <span class=\"text-muted\">$</span>{1}.",
"IsSourceCodeIncluded": "Does my license include the source code of the commercial modules and themes?",
"IsSourceCodeIncludedExplanation1": "Depends on the license type you've purchased:",
"IsSourceCodeIncludedExplanation2": "<strong>Team</strong>: Your solution uses the modules and the themes as NuGet and NPM packages. It doesn't include their source code. In this way, you can easily upgrade these modules and themes whenever a new version is available. However, you can not get the source code of the modules and the themes.",
"IsSourceCodeIncludedExplanation3": "<strong>Business/Enterprise</strong>: In addition to the Team license, you are able to download the source code of any module or theme you need. You can even remove the NuGet/NPM package references for a particular module and add its source code directly to your solution to fully change it.",
"IsSourceCodeIncludedExplanation4": "<p>Including the source code of a module to your solution gives you the maximum freedom to customize that module. However, then it will not be possible to automatically upgrade the module when a new version is released.</p><p>None of the licenses include the ABP Suite source code, which is an external tool that generates code for you and assist to your development.</p><p>See <a href=\"{0}\">the pricing</a> page for other differences between the license types.</p>",
"ChangingDevelopers": "Can I change the registered developers of my organization in the future?",
"ChangingDevelopersExplanation": "In addition to add new developers to your license, you can also change the existing developers (you can remove a developer and add a new one to the same seat) without any additional cost.",
"WhatHappensWhenLicenseEnds": "What happens when my license period ends?",
"WhatHappensWhenLicenseEndsExplanation1": "ABP Commercial has <a href=\"{0}\" target=\"_blank\">perpetual license type</a>.After your license expires, you can continue developing your project. And you are not obliged to renew your license. After your license expires;",
"WhatHappensWhenLicenseEndsExplanation2": "You can not create new solutions using the ABP Commercial, but you can continue to develop your existing applications forever.",
"WhatHappensWhenLicenseEndsExplanation3": "You will be able to get updates for the modules and themes within your MAJOR version. For example; if you are using v3.2.0 of a module, you can still get updates for v3.x.x (v3.3.0, v3.5.2... etc.) of that module. But you cannot get updates for the next major version (like v4.x, v5.x)",
"WhatHappensWhenLicenseEndsExplanation4": "You can not install new modules and themes added to the ABP Commercial platform after your license ends.",
"WhatHappensWhenLicenseEndsExplanation5": "You can not use the ABP Suite.",
"WhatHappensWhenLicenseEndsExplanation6": "You can not get the <a href=\"{0}\">premium support</a> anymore.",
"WhatHappensWhenLicenseEndsExplanation7": "You can renew your subscription if you want to continue to get these benefits. There is a 20% discount when you renew your subscription.",
"WhenShouldIRenewMyLicense": "When should I renew my license?",
"WhenShouldIRenewMyLicenseExplanation1": "If you renew your license within <strong>1 month</strong> after your license expires, %20 discount will be applied to total license price.",
"WhenShouldIRenewMyLicenseExplanation2": "If you renew your license after 1 month from your license expire date, the renew price will be same as license purchase price and there will be no discount for your renewal.",
"TrialPlan": "Do you have a trial plan?",
"TrialPlanExplanation": "For now, ABP Commercial doesn't have a trial plan. For the Team licenses we provide 30 days money back guarantee. You can just request a refund in the first 30 days. For the Business and Enterprise licenses, we provide 60% refund in 30 days. This is because Business and Enterprise licenses include the full source code of all the modules and the themes.",
"DoYouAcceptBankWireTransfer": "Do you accept bank wire transfer?",
"DoYouAcceptBankWireTransferExplanation": "Yes, we accept bank wire transfer.<br />After sending the license amount via bank wire transfer, send us your receipt and the requested license type via e-mail.<br />Our international bank account information:",
"HowToUpgrade": "How to upgrade existing applications when a new version is available?",
"HowToUpgradeExplanation1": "When you create a new application using ABP Commercial, all the modules and the theme are used as NuGet and NPM packages. So, you can easily upgrade the packages when a new version is available.",
"HowToUpgradeExplanation2": "In addition to the standard NuGet/NPM upgrades, <a href=\"{0}\">ABP CLI</a> provides an update command that automatically finds and upgrades all ABP related packages in your solution.",
"DatabaseSupport": "Which database systems are supported?",
"DatabaseSupportExplanation": "ABP Framework itself is database agnostic and can work with any database provider by its nature. See <a href=\"{0}\" target=\"_blank\">the data access document</a> for a list of currently implemented providers.",
"UISupport": "Which UI frameworks are supported?",
"Supported": "Supported",
"UISupportExplanation": "ABP Framework itself is UI framework agnostic and can work with any UI framework. However, startup templates, module UIs and themes were not implemented for all UI frameworks. See <a href=\"{0}\">the getting started document</a> for the up-to-date list of UI options.",
"MicroserviceSupport": "Does it support the micro-service architecture?",
"MicroserviceSupportExplanation1": "One of the major goals of the ABP framework is to provide a convenient infrastructure to create micro-service solutions. See the <a href=\"{0}\">micro-service architecture</a> document to understand how it helps to create micro-service systems.",
"MicroserviceSupportExplanation2": "All the ABP Commercial modules are designed to support micro-service deployment scenarios (with its own API and database) by following the <a href=\"{0}\">module development best practices</a> document.",
"MicroserviceSupportExplanation3": "We provide a sample <a href=\"{0}\">micro-service demo solution</a>that demonstrates a micro-service architecture implementation to help you to create your own solution.",
"MicroserviceSupportExplanation4": "So, the short answer is \"<strong>yes, it supports micro-service architecture</strong>\".",
"MicroserviceSupportExplanation5": "However, a micro-service system is a solution and every solution will have different requirements, network topology, communication scenarios, authentication possibilities, database separation/sharing decisions, runtime configurations, 3rd party system integrations and many more.",
"MicroserviceSupportExplanation6": "The ABP Framework and the ABP Commercial provides infrastructure for micro-service scenarios, micro-service compatible modules, samples and documentation to help you to build your own solution. But don't expect to directly download your dream solution pre-built for you. You will need to understand it and bring some parts together based on your requirements.",
"WhereCanIDownloadSourceCode": "Where can I download source-code?",
"WhereCanIDownloadSourceCodeExplanation": "You can download the source code of all ABP modules, Angular packages and themes via ABP Suite or ABP CLI. See <a href=\"{0}\">How to download source-code?</a>",
"ComputerLimitation": "How many computers can a developer login when developing ABP?",
"ComputerLimitationExplanation": "We specifically permit <strong>{0} computers</strong> per individual/licensed developer. Whenever there is a need for a developer to develop ABP Commercial products on a third machine, an e-mail should be sent to license@abp.io explaining the situation and we will then make the appropriate allocation in our system.",
"RefundPolicy": "Do you have a refund policy?",
"RefundPolicyExplanation": "You can request a refund within <strong>30 days</strong> of your license purchase. The Business and Enterprise license types have source-code download option, therefore refunds are not available for the Business and Enterprise (and any licenses that include a right to receive source-code). In addition, no refunds are made for renewals and second license purchases.",
"HowCanIRefundVat": "How can I refund VAT?",
"HowCanIRefundVatExplanation1": "If you made the payment using 2Checkout, you can refund VAT via your 2Checkout account:",
"HowCanIRefundVatExplanation2": "Log in into your <a href=\"https://secure.2checkout.com/cpanel/login.php\" target=\"_blank\">2Checkout</a> account",
"HowCanIRefundVatExplanation3": "Find the appropriate order and press \"Refund Belated VAT\" (enter your VAT ID)",
"HowCanIGetMyInvoice": "How can I get my invoice?",
"HowCanIGetMyInvoiceExplanation": "There are 2 payment gateways for purchasing a license: PayU and 2Checkout. If you purchase your license through 2Checkout gateway, it sends the PDF invoice to your email address, see <a href=\"https://knowledgecenter.2checkout.com/Documentation/03Billing-and-payments/Payment-operations/How-do-invoices-work\">2Checkout invoicing.</a> If you purchase through the PayU gateway or via bank wire transfer, we will prepare and send your invoice. You can request your invoice from the <a href=\"{0}\">organization management page.</a>",
"Forum": "Forum",
"SupportExplanation": "ABP Commercial licenses provides a premium forum support by a team consists of the ABP Framework experts.",
"PrivateTicket": "Private Ticket",
"PrivateTicketExplanation": "Enterprise License also includes a private support with e-mail and ticket system.",
"AbpSuiteExplanation1": "ABP Suite allows you to build web pages in a matter of minutes. It's a .NET Core Global tool that can be installed from the command line.",
"AbpSuiteExplanation2": "It can create a new ABP solution, generate CRUD pages from the database to the front-end. For technical overview see <a href=\"{0}\">the document</a>",
"FastEasy": "Fast & Easy",
"AbpSuiteExplanation3": "ABP Suite allows you to easily create CRUD pages. You just need to define your entity and its properties, let the rest to ABP Suite for you! ABP Suite generates all the necessary code for your CRUD page in a few seconds. It supports Angular, MVC and Blazor user interfaces.",
"RichOptions": "Rich Options",
"AbpSuiteExplanation4": "ABP Suite supports multiple UI options like <a href=\"https://docs.microsoft.com/en-us/aspnet/core/razor-pages\">Razor Pages</a> and <a href=\"https://angular.io\">Angular</a>.It also supports multiple databases like <a href=\"https://www.mongodb.com\">MongoDB</a> and all databases supported by <strong>EntityFramework Core</strong> (MS SQL Server, Oracle, MySql, PostgreSQL and <a href=\"https://docs.microsoft.com/en-us/ef/core/providers/?tabs=dotnet-core-cli\">more</a>).",
"AbpSuiteExplanation5": "Good thing is that, you don't have to worry about those options. ABP Suite understands your project type and generates the code for your project and places the generated code in correct place in your project.",
"SourceCode": "Source Code",
"AbpSuiteExplanation6": "ABP Suite generates the source code for you! It doesn't generate magic files to generate the web page. ABP Suite generates the source code for <strong>Entity, Repository, Application Service, Code First Migration, JavaScript/TypeScript and CSHTML/HTML</strong> and necessary Interfaces as well. ABP Suite also generates the code according to <strong>Best Practices</strong> of software development, so you don't have to worry about generated code's quality.",
"AbpSuiteExplanation7": "Since you have the source code of building blocks of the generated CRUD page in correct application layers, you can easily modify the source code and inject your custom/bussiness logic to the generated code.",
"CrossPlatform": "Cross Platform",
"AbpSuiteExplanation8": "ABP Suite is built with .NET Core and it is cross platform. It runs as a web application on your local computer. You can run it on <strong>Windows</strong>, <strong>Mac</strong> and <strong>Linux</strong>",
"OtherFeatures": "Other Features",
"OtherFeatures1": "Updates <strong>NuGet</strong> and <strong>NPM</strong> packages on your solution easily.",
"OtherFeatures2": "Regenerates already generated pages from scratch.",
"OtherFeatures3": "Creates new solutions",
"ThanksForCreatingProject": "Thanks for Creating Your Project!",
"HotToRunSolution": "How to run your solution?",
"HotToRunSolutionExplanation": "See getting started document to learn how to configure and run your solution.",
"GettingStarted": "Getting Started",
"WebAppDevTutorial": "Web App Dev Tutorial",
"WebAppDevTutorialExplanation": "See web application development tutorial document for a step by step development sample.",
"Document": "Document",
"UsingABPSuiteToCURD": "Using ABP Suite for CRUD Page Generation & Tooling",
"SeeABPSuiteDocument": "See ABP Suite document to learn the usage of ABP Suite.",
"AskQuestionsOnSupport": "You can ask questions on ABP Commercial Support.",
"Documentation": "Documentation",
"SeeModulesDocument": "See modules document for a list of all commercial (pro) modules and their documents.",
"Pricing": "Pricing",
"PricingExplanation": "Choose the features and functionality your business needs today. Easily upgrade as your business grows.",
"Team": "Team",
"Business": "Business",
"Enterprise": "Enterprise",
"Custom": "Custom",
"IncludedDeveloperLicenses": "Included developer licenses",
"CustomLicenceOrAdditionalServices": "Need custom licence or additional services?",
"CustomOrVolumeLicense": "Custom or volume license",
"LiveTrainingSupport": "Live training & support",
"AndMore": "and more",
"AdditionalDeveloperLicense": "Additional developer license",
"ProjectCount": "Project count",
"AllProModules": "All pro modules",
"AllProThemes": "All pro themes",
"AllProStartupTemplates": "All pro startup templates",
"SourceCodeOfAllModules": "Source code of all modules",
"SourceCodeOfAllThemes": "Source code of all themes",
"PerpetualLicense": "Perpetual license",
"UnlimitedServerDeployment": "Unlimited server deployment",
"YearUpgrade": "1 year upgrade",
"YearPremiumForumSupport": "1 year premium forum support",
"ForumSupportIncidentCountYear": "Forum support incident count/year",
"PrivateTicketEmailSupport": "Private ticket & email support",
"BuyNow": "Buy Now"
}
}

257
abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/zh-Hans.json

@ -16,7 +16,7 @@
"Name": "名称",
"EmailAddress": "电子邮件地址",
"Developers": "开发者",
"LicenseType": "许可类型",
"LicenseType": "许可类型",
"Manage": "管理",
"StartDate": "开始日期",
"EndDate": "结束日期",
@ -33,7 +33,258 @@
"EmailNotValid": "请输入有效的电子邮件地址",
"JoinOurMarketingNewsletter": "加入我们的营销简讯",
"WouldLikeToReceiveMarketingMaterials": "我想收到市场营销资料,例如产品交易和特别优惠.",
"StartUsingYourLicenseNow": "立即开始使用你的许可证",
"WelcomePage": "欢迎页面"
"StartUsingYourLicenseNow": "立即开始使用你的许可",
"WelcomePage": "欢迎页面",
"UnsubscriptionExpireEmail": "退订许可到期提醒邮件",
"UnsubscribeLicenseExpireEmailReminderMessage": "此电子邮件订阅仅提醒你许可的到期日期.",
"UnsubscribeFromLicenseExpireEmails": "如果你不想收到关于许可到期邮件,你可以在任何时间取消订阅.",
"Unsubscribe": "取消订阅",
"NotOrganizationMember": "你不是任何组织的成员.",
"UnsubscribeLicenseExpirationEmailSuccessTitle": "退订成功",
"UnsubscribeLicenseExpirationEmailSuccessMessage": "你不会再收到任何许可到期提醒邮件.",
"IndexPageHeroSection": "<span class=\"first-line\">一个完整的</span><span class=\"second-line\">web开发平台</span><span class=\"third-line\">基于 <img src=\"{0}\" width=\"110\" class=\"ml-1\" /> 框架</span>",
"AbpCommercialShortDescription": "ABP商业版提供了预构建的应用程序模块, 快速的应用程序开发工具, 专业的UI主题, 高级支持等.",
"LiveDemo": "在线演示",
"GetLicence" :"获得许可",
"Application": "应用程序",
"StartupTemplates": "启动模板",
"Startup": "启动",
"Templates": "模板",
"Developer": "开发人员",
"Tools": "工具",
"Premium": "高级",
"PremiumSupport": "高级支持",
"PremiumForumSupport": "高级论坛支持",
"UI": "UI",
"Themes": "主题",
"JoinOurNewsletter": "加入我们的时事通讯",
"Send": "发送",
"Learn": "学习",
"AdditionalServices": "额外的服务",
"WhatIsABPFramework": "什么是ABP框架?",
"OpenSourceBaseFramework": "开源的框架",
"ABPFrameworkExplanation": "<p class=\"lead\">ABP商业版基于ABP框架, 这是一个开源和社区驱动的ASP.NET Core web应用程序开发框架.</p><p>ABP框架提供了出色的基础设施, 使用最佳实践编写可维护,可扩展,可测试的代码.</p><p>基于你已经知道的流行工具. 低学习曲线,容易适应,舒适的开发体检.</p>",
"Modular": "模块化",
"MicroserviceCompatible": "微服务兼容",
"DomainDrivenDesignInfrastructure": "领域驱动设计基础设施",
"MultiTenancy": "多租户",
"DistributedMessaging": "分布式消息",
"DynamicProxying": "动态代理",
"BackgroundJobs": "后台作业",
"AuditLogging": "审计日志",
"BLOBStoring": "BLOB存储",
"BundlingMinification": "捆绑 & 压缩",
"AdvancedLocalization": "高级本地化",
"ManyMore": "以及更多",
"ExploreTheABPFramework": "探索ABP框架",
"WhyUseTheABPCommercial": "为什么使用ABP商业版?",
"WhyUseTheABPCommercialExplanation": "<p class=\"lead mt- 5\">构建企业级的web应用程序可能复杂和耗时.</p><p>ABP商业版提供所有现代企业级基于ASP.NET Core的解决方案所需要的基础设施. 从设计到部署,整个开发生命周期都由ABP的内置功能和模块提供支持.</p>",
"StartupTemplatesShortDescription": "启动模板使你在几秒内快速启动项目.",
"UIFrameworksOptions": "UI框架选项;",
"DatabaseProviderOptions": "数据库提供程序选项;",
"PreBuiltApplicationModules": "预构建的应用程序模块",
"PreBuiltApplicationModulesShortDescription": "大多数常见的应用程序需求已经做为可重用的模块为你开发了.",
"Account": "账户",
"Blogging": "博客",
"Identity": "Identity",
"IdentityServer": "Identity Server",
"Saas": "Saas",
"LanguageManagement": "语言管理",
"TextTemplateManagement": "文本模板管理",
"See All Modules": "查看所有的模块",
"ABPSuite": "ABP Suite",
"AbpSuiteShortDescription": "ABP Suite是ABP商业版的辅助工具.",
"AbpSuiteExplanation": "它让你在几分钟内构建web页面. 它是一个.Net Core全局工具,可以使用命令行安装. 它可以创建一个新的ABP解决方案,从数据库生成前端CURD页面.",
"Details": "详情",
"LeptonTheme": "Lepton主题",
"ProfessionalModernUIThemes": "专业, 现代化的UI主题",
"LeptonThemeExplanation": "Lepton提供了一系列的Bootstrap管理主题,为任何需要管理仪表盘的项目提供了坚实的基础.",
"DefaultTheme": "默认主题",
"MaterialTheme": "Material主题",
"Default2Theme": "默认主题2",
"DarkTheme": "深色主题",
"DarkBlueTheme": "深蓝主题",
"LightTheme": "浅色主题",
"ProudToWorkWith": "荣幸与你合作",
"OurConsumers": "全球50多个国家的数百家企业和开发商使用ABP商业版.",
"JoinOurConsumers": "加它他们并快速构建令人惊叹的产品.",
"AdditionalServicesExplanation": "你是否需要额外或自定义的服务? <strong>我们和我们的合作伙伴可以提供;</strong>",
"CustomProjectDevelopment": "自定义项目开发",
"CustomProjectDevelopmentExplanation": "专为你的自定义的开发人员.",
"PortingExistingProjects": "移植现有项目",
"PortingExistingProjectsExplanation": "将你的迁移传统项目迁移到ABP平台.",
"LiveSupport": "实时支持",
"LiveSupportExplanation": "在你需要时,可以实时提供远程支持选项.",
"Training": "培训",
"TrainingExplanation": "为你的开发人员提供专门培训.",
"OnBoarding": "管理",
"OnBoardingExplanation": "帮助设置你的开发,CI和CD环境.",
"PrioritizedTechnicalSupport": "优先的技术支持",
"PremiumSupportExplanation": "除了社区对ABP框架出色支持外,我们的支持团队会优先回答商业用户的技术问题.",
"SeeTheSupportOptions": "查看支持选项",
"Contact": "联系",
"TellUsWhatYouNeed": "告诉我们你需要什么.",
"YourMessage": "你的消息",
"YourFullName": "你的全名",
"EmailField": "E-mail地址",
"YourEmailAddress": "你的e-mail地址",
"HowMayWeHelpYou": "我们如何帮助你",
"SendMessage": "发送消息",
"Success": "成功",
"WeWillReplyYou.": "你的消息已经发送! 我们会在短时间内给你答复.",
"GoHome": "回到主页面",
"CreateLiveDemo": "创建在线演示",
"RegisterToTheNewsletter": "注册到时事简报以获取有关ABP.IO的消息,比如新发布的内容.",
"EnterYourEmailOrLogin": "输入你的e-mail地址来创建你的演示或者使用你的已有账号<a href=\"{0}\">登录</a>.",
"ApplicationTemplate": "应用程序模板",
"ApplicationTemplateExplanation": "应用程序启动模板用于创建新的web应用程序.",
"EfCoreProvider": "Entity Framework (支持 SQL Server, MySQL, PostgreSQL, Oracle <a href=\"https://docs.microsoft.com/zh-cn/ef/core/providers/\">和其它</a>)",
"AlreadyIncludedInTemplateModules": "以下模块已经包含并配置在此模板中:",
"ApplicationTemplateArchitecture": "应用程序模板还支持UI,API,身份验证服务器物理分离的分层架构.",
"SeeTheGuideOrGoToTheLiveDemo": "查看开发人员指南或者查看在线演示了解更多关于模板的信息.",
"DeveloperGuide": "开发人员指南",
"ModuleTemplate": "模块模块",
"ModuleTemplateExplanation1": "你是否想要创建可在不同应用程序之间重用的模块? 这个启动模板准备好了一切来创建可重用的<strong>应用程序模块</strong>或<strong>微服务</strong>.",
"ModuleTemplateExplanation2": "<p>你可以为一个模块支持单个或多个UI框架, 单个或多个数据库提供程序. 除了单元测试和集成测试基础架构外,启动模板被配置为在一个最小的应用程序中运行和<strong>测试你的模块</strong>. </p> <p>查看开发人员指南了解有关此模板的技术信息.</p>",
"WithAllStyleOptions": "使用所有的样式选项",
"Demo": "演示",
"SeeAllModules": "查看所有的模块",
"ABPCLIExplanation": "ABP CLI(命令行页面)是一个执行基于ABP解决方案的一些常见操作的命令行工具.",
"ABPSuiteEasilyCURD": "ABP Suite是一个使你轻松创建CURD页面的工具",
"WeAreHereToHelp": "我们在这里为你提供<span class=\"zero-text\">帮助</span>",
"BrowseOrAskQuestion": "你可以浏览我们的帮助主题或搜索常见的问题, 或者你可以使用<a href=\"{0}\" class=\"text-success\">联系表单</a>向我们提问.",
"SearchQuestionPlaceholder": "搜索常见的问题",
"WhatIsTheABPCommercial": "什么是ABP商业版?",
"WhatAreDifferencesThanAbpFramework": "ABP框架与ABP商业版有什么不同?",
"ABPCommercialExplanation": "ABP商业版是一套基于开源<a target=\"_blank\" href=\"{0}\">ABP框架</a>之上的高级模块,工具,主题和服务. ABP商业版由ABP框架背后的同一团队开发和支持.",
"WhatAreDifferencesThanABPFrameworkExplanation": "<p> <a target=\"_blank\" href=\"{0}\">ABP框架</a>是模块化,主题化,微服务兼容的ASP.NET Core应用程序开发框架. 它提供了一个完整的架构和强大的基础设施,让你专注于自己的业务代码而不是重复自己的每一个项目. 它基于软件开发的最佳实践和你已经知道的流行工具 </p> <p> ABP框架是完全免费,开源和由社区驱动的. 它还提供了一个免费的主题和一些预构建的模块 (如 identity管理和租户管理).</p>",
"VisitTheFrameworkVSCommercialDocument": "访问以下链接,了解更多信息 <a href=\"{0}\" target=\"_blank\"> {1} </a>",
"ABPCommercialFollowingBenefits": "ABP商业版在ABP框架之上添加了以下好处;",
"Professional": "专业的",
"UIThemes": "UI主题",
"EnterpriseModules": "企业就绪,功能丰富,预构建的<a href=\"{0}\">应用程序模块</a> (如 Identity Server管理, SaaS管理, 语言管理)",
"ToolingToSupport": "支持你的生产力的工具(如. <a href=\"{0}\">ABP Suite</a>)",
"PremiumSupportLink": "高级<a href=\"{0}\" target=\"_blank\">支持</a>",
"WhatDoIDownloadABPCommercial": "购买ABP商业版后我可以下载什么?",
"CreateUnlimitedSolutions": "一旦你购买了ABP商业许可, 你可以创建<a href=\"{0}\">入门</a>文档中描述的无限的解决方案.",
"ABPCommercialSolutionExplanation": "创建新应用程序时,将根据你的首选项获得Visual Studio解决方案(启动模板). 下载的解决方案已为你安装并配置了商业模块和主题. 你可以删除预装的模块,也可以根据需要添加其他模块. 默认情况下所有模块和主题都使用NuGet/NPM软件包.",
"StartDevelopWithTutorials": "下载的解决方案经过精心设计和记录, 你可以根据<a href=\"{0}\">教程</a>来开发自己的业务代码.",
"TryTheCommercialDemo": "你可以尝试<a href=\"{0}\">示例</a>来查看使用ABP商业模板创建的示例应用程序.",
"HowManyProducts": "使用ABP商业版,我可以构建多少不同的产品/解决方案?",
"HowManyProductsExplanation": "创建ABP项目没有限制. 你可以根据需要创建任意数量的项目,进行开发并将其部署到其他服务器.",
"HowManyDevelopers": "有多少开发者可以参与ABP商业版工作?",
"HowManyDevelopersExplanation": "ABP商业许可是针对每个开发人员的. 不同的许可类型具有不同的开发人员限制. 但是你可以在需要时将更多开发人员添加到任何许可类型. 查看许可类型,开发人员限制和额外的开发人员成本的<a href=\"{0}\">价格</a>页面.",
"ChangingLicenseType": "将来更改我的许可类型吗?",
"ChangingLicenseTypeExplanation": "你始终可以在同一许可中添加新的开发人员. 参阅 \"有多少开发者可以参与ABP商业版工作?\". 你还可以通过支付计算出的价格差来升级到更高的许可. 当你升级到更高的许可计划时,可以享受新计划的好处,但是许可升级不会更改许可的到期日期.",
"LicenseExtendUpgradeDiff": "许可扩展和升级有什么区别?",
"LicenseExtendUpgradeDiffExplanation": "<strong>扩展:</strong> 通过扩展/更新许可,你将继续获得高级支持,并获得有关模块和主题的重大更新. 此外你将能够继续创建新项目. 而且你仍然可以使用ABP Suite来加快开发速度.<hr/><strong>升级:</strong> 通过升级许可,你将升级到更高的许可计划,这将使你获得更多好处. 查看 <a href=\"/pricing\">许可比较表</a>来检查许可计划之间的差异. <strong>另一方面,当你升级时你的许可到期日期不会改变!</strong>要延长你的许可终止日期,你需要延长你的许可.",
"LicenseRenewalCost": "一年后的许可续期费用是多少?",
"LicenseRenewalCostExplanation": "你可以通过支付当前许可价格的%80来续订(扩展)你的许可. 例如; 标准ABP团队许可为 <span class=\"text-muted\">$</span>{0} 续订价格是 <span class=\"text-muted\">$</span>{1}.",
"IsSourceCodeIncluded": "我的许可是否包括商业模块和主题的源代码?",
"IsSourceCodeIncludedExplanation1": "取决于你购买的许可类型:",
"IsSourceCodeIncludedExplanation2": "<strong>团队</strong>: 你的解决方案将这些模块和主题作为NuGet和NPM包使用. 它不包括其源代码. 这样,只要有新版本可用,你就可以轻松升级这些模块和主题. 但是,你无法获取模块和主题的源代码.",
"IsSourceCodeIncludedExplanation3": "<strong>商业/企业</strong>: 除了团队许可外,你还可以下载所需的任何模块或主题的源代码. 你甚至可以删除特定模块的NuGet/NPM软件包引用,并将其源代码直接添加到你的解决方案中以完全更改它.",
"IsSourceCodeIncludedExplanation4": "<p>将模块的源代码包含到解决方案中,可以最大程度地自定义该模块. 但是当新版本发布时,将无法自动升级模块.</p><p>这些许可均不包含ABP Suite源代码,该源代码是一个外部工具,可以为你生成代码并帮助你进行开发</p><p>有关许可类型之间的其它差异查看<a href=\"{0}\">定价</a>页面.</p>",
"ChangingDevelopers": "我将来可以更改我组织的注册开发人员吗?",
"ChangingDevelopersExplanation": "除了将新的开发人员添加到你的许可中之外,你还可以更改现有的开发人员(可以删除一个开发人员并将新的开发人员添加到同一位置),而无需任何额外费用.",
"WhatHappensWhenLicenseEnds": "我的许可期限结束后会怎样?",
"WhatHappensWhenLicenseEndsExplanation1": "ABP商业版具有<a href=\"{0}\" target=\"_blank\">永久许可类型</a>.许可到期后, 你可以继续开发你的项目.并且你没有义务续订许可.许可到期后;",
"WhatHappensWhenLicenseEndsExplanation2": "你不能使用ABP商业版创建新的解决方案,但可以永远继续开发现有的应用程序.",
"WhatHappensWhenLicenseEndsExplanation3": "你能够在你的主版本中获得模块和主题的更新.例如;如果你使用的是模块的v3.2.0版本,你仍然可以获得v3.x.x版本的更新.(v3.3.0 v3.5.2…等等)的.但是你无法获得下一个主要版本(如v4.x,v5.x)的更新",
"WhatHappensWhenLicenseEndsExplanation4": "许可到期后,你无法安装添加到ABP商业平台的新模块和主题.",
"WhatHappensWhenLicenseEndsExplanation5": "你不能使用ABP Suite.",
"WhatHappensWhenLicenseEndsExplanation6": "你不再获得<a href=\"{0}\">高级支持</a>.",
"WhatHappensWhenLicenseEndsExplanation7": "如果你想继续获得这些好处,可以继续续订. 续订时有20%的折扣.",
"WhenShouldIRenewMyLicense": "我什么时候应该续订我的许可?",
"WhenShouldIRenewMyLicenseExplanation1": "如果你在许可到期后的<strong> 1个月</strong>内续订许可,则许可总价将享受%20的折扣.",
"WhenShouldIRenewMyLicenseExplanation2": "如果你在你的许可过期日期1个月后更新你的许可,更新价格将与许可购买价格相同,你的更新将没有折扣.",
"TrialPlan": "你们有试用计划吗?",
"TrialPlanExplanation": "目前,ABP商业版还没有试用计划.对于团队许可,我们提供30天的退款保证.你可以在30天内要求退款.对于企业营业执照,我们提供30天内60%的退款.这是因为商业和企业许可包含所有模块和主题的完整源代码.",
"DoYouAcceptBankWireTransfer": "你们接受银行电汇吗?",
"DoYouAcceptBankWireTransferExplanation": "是的,我们接受银行电汇.<br />通过银行电汇转账,通过电子邮件将收据和所要求的许可类型发送给我们.<br />我们的国际银行帐户信息:",
"HowToUpgrade": "可用新版本时如何升级现有应用程序?",
"HowToUpgradeExplanation1": "使用ABP商业版创建新应用程序时,所有模块和主题均用作NuGet和NPM软件包. 因此,当有新版本可用时,你可以轻松升级软件包.",
"HowToUpgradeExplanation2": "除了标准的NuGet/NPM升级之外,<a href=\"{0}\"> ABP CLI </a>还提供了一条更新命令,该命令可自动查找和升级解决方案中的所有与ABP相关的软件包.",
"DatabaseSupport": "支持哪些数据库系统?",
"DatabaseSupportExplanation": "ABP框架本身与数据库无关,并且就其性质而言,可以与任何数据库提供程序一起使用. 请参阅<a href=\"{0}\" target=\"_blank\">数据访问文档</a>以获取当前实现的提供程序的列表.",
"UISupport": "支持哪些UI框架?",
"Supported": "支持的",
"UISupportExplanation": "ABP框架本身与UI框架无关,并且可以与任何UI框架一起使用. 但是,并非针对所有UI框架都实现了启动模板,模块UI和主题. 有关UI选项的最新列表,请参见<a href=\"{0}\">入门文档</a>.",
"MicroserviceSupport": "它是否支持微服务架构?",
"MicroserviceSupportExplanation1": "ABP框架的主要目标之一是为创建微服务解决方案提供便利的基础架构. 请参阅<a href=\"{0}\">微服务体系结构</a>文档,以了解它如何帮助创建微服务系统.",
"MicroserviceSupportExplanation2": "所有的ABP商业模块通过<a href=\"{0}\">模块开发最佳实践</a>文档被设计为支持微服务部署场景(使用自己的API和数据库).",
"MicroserviceSupportExplanation3": "我们提供了一个示例<a href=\"{0}\">微服务演示解决方案</a>,该示例演示了一种微服务架构实现,可帮助你创建自己的解决方案.",
"MicroserviceSupportExplanation4": "所以简短的答案是 \"<strong>是的, 它支持微服务体系结构</strong>\".",
"MicroserviceSupportExplanation5": "但是,微服务系统是一个解决方案,每个解决方案都有不同的要求,网络拓扑,通信场景,身份验证可能性,数据库分离/共享决策,运行时配置,第三方系统集成等等.",
"MicroserviceSupportExplanation6": "ABP框架和ABP商业版提供了微服务方案,微服务兼容模块,示例和文档的基础结构,以帮助你构建自己的解决方案. 但是不要期望直接下载为你预先构建的梦想解决方案. 你将需要了解它,并根据需要将某些部分组合在一起.",
"WhereCanIDownloadSourceCode": "在哪里可以下载源代码?",
"WhereCanIDownloadSourceCodeExplanation": "你可以通过ABP Suite或ABP CLI下载所有ABP模块,Angular软件包和主题的源代码. 请参见<a href=\"{0}\">如何下载源代码?</a>",
"ComputerLimitation": "开发人员在开发ABP时可以登录到多少台计算机?",
"ComputerLimitationExplanation": "我们特别允许每个个人/许可的开发人员使用<strong> {0}台计算机</strong>. 每当需要开发人员在第三台计算机上开发ABP商业产品时,都应将电子邮件发送到license@abp.io,以说明情况,然后我们将在系统中进行适当分配.",
"RefundPolicy": "你们有退款政策吗?",
"RefundPolicyExplanation": "你可以在购买许可的<strong>30天</strong>内请求退款.商业和企业许可类型都有源代码下载选项,因此商业和企业(以及任何包含获得源代码的权利的许可)都不能退款.此外,续展和购买第二次许可也不退款.",
"HowCanIRefundVat": "我该如何退还增值税?",
"HowCanIRefundVatExplanation1": "如果你使用2Checkout付款,则可以通过2Checkout帐户退还增值税:",
"HowCanIRefundVatExplanation2": "登录到你的<a href=\"https://secure.2checkout.com/cpanel/login.php\" target=\"_blank\">2Checkout</a>账户",
"HowCanIRefundVatExplanation3": "找到适当的订单,然后按\"退还已延期的增值税\"(输入你的增值税ID",
"HowCanIGetMyInvoice": "我如何获得发票?",
"HowCanIGetMyInvoiceExplanation": "有两个用于购买许可的支付网关:PayU和2Checkout. 如果你通过2Checkout网关购买许可,则它将PDF发票发送到你的电子邮件地址,请参阅<a href=\"https://knowledgecenter.2checkout.com/Documentation/03Billing-and-payments/Payment-operations/How-do-invoices-work>2Checkout发票.</a>如果你是通过PayU网关或通过银行电汇购买的,我们将准备并发送你的发票. 你可以从<a href=\"{0}\">组织管理页面</a>索取发票.",
"Forum": "论坛",
"SupportExplanation": "ABP商业版许可包含由ABP框架专家组成的团队提供的高级论坛支持.",
"PrivateTicket": "私有票",
"PrivateTicketExplanation": "企业许可还包含带有电子邮件和票系统的私人支持.",
"AbpSuiteExplanation1": "ABP Suite使你在几分钟内构建web页面. 它是一个.NET Core Global工具,可以使用命令行安装.",
"AbpSuiteExplanation2": "它可以创建一个新的ABP解决方案, 从数据库生成前端CURD页面. 有关技术概述查看<a href=\"{0}\">文档</a>",
"FastEasy": "快速并且简单",
"AbpSuiteExplanation3": "ABP Suite允许你轻松的创建CURD页面. 你只需要定义你的实体和它的属性, 其它的让ABP Suite帮你完成! ABP Suite会在几秒内为你生成CURD页面和必要的代码. 它支持Angular, MVC和 Blazor用户界面.",
"RichOptions": "丰富的选项",
"AbpSuiteExplanation4": "ABP Suite支持多个UI选项,例如 <a href=\"https://docs.microsoft.com/en-us/aspnet/core/razor-pages\">Razor Pages</a>和<a href=\"https://angular.io\">Angular</a>.它还支持多个数据库, 如 <a href=\"https://www.mongodb.com\">MongoDB</a> 和<strong>EntityFramework Core</strong>支持的所有数据库(MS SQL Server, Oracle, MySql, PostgreSQL 和 <a href=\"https://docs.microsoft.com/zh-cn/ef/core/providers/?tabs=dotnet-core-cli\">更多</a>).",
"AbpSuiteExplanation5": "好消息是, 你不必担心这些选项. ABP Suite了解你的项目类型并且将生成的代码放到项目中正确的位置.",
"SourceCode": "源码",
"AbpSuiteExplanation6": "ABP Suite为你生成源代码! 它不会生成魔法文件来生成web页面. ABP Suite为<strong>实体, 仓储, 应用程序, Code First迁移, JavaScript/TypeScript 和 CSHTML/HTML</strong>以及必要的页面生成源代码. ABP Suite根据软件开发的<strong>最佳实践</strong>来生成代码, 所以你不必担心生成的代码的质量.",
"AbpSuiteExplanation7": "由于你有应用程序层中生成的CURD页面的构建块的源代码, 因此你可以轻松修改源码并将自定义/业务逻辑注入到所生成的代码中.",
"CrossPlatform": "跨平台",
"AbpSuiteExplanation8": "ABP Suite使用.NET Core构建并且它是跨平台的. 它在你的本地电脑中运行为web应用程序. 你可以在<strong>Windows</strong>, <strong>Mac</strong>和<strong>Linux</strong>上运行它",
"OtherFeatures": "其他功能",
"OtherFeatures1": "轻松更新你的解决方案的<strong>NuGet</strong>和<strong>NPM</strong>包.",
"OtherFeatures2": "重新生成已经生成的页面.",
"OtherFeatures3": "创建新的解决方案",
"ThanksForCreatingProject": "感谢你创建你的项目!",
"HotToRunSolution": "如何运行你的解决方案?",
"HotToRunSolutionExplanation": "查看入门文档来学习如何配置和运行你的解决方案.",
"GettingStarted": "入门",
"WebAppDevTutorial": "Web应用开发教程",
"WebAppDevTutorialExplanation": "查看web应用程序开发教程文档来一步一步的了解开发示例.",
"Document": "文档",
"UsingABPSuiteToCURD": "使用ABP Suite来生成CURD页面",
"SeeABPSuiteDocument": "查看ABP Suite文档学习如何使用ABP Suite.",
"AskQuestionsOnSupport": "你可以在ABP商业支持提出问题.",
"Documentation": "文档",
"SeeModulesDocument": "查看模块文档获取所有商业(专业)模块列表和它们的文档.",
"Pricing": "价格",
"PricingExplanation": "选择当前业务需要的特性和功能. 随着业务增长轻松升级.",
"Team": "团队",
"Business": "商业",
"Enterprise": "企业",
"Custom": "自定义",
"IncludedDeveloperLicenses": "包括开发人员许可",
"CustomLicenceOrAdditionalServices": "需要自定义许可与额外服务?",
"CustomOrVolumeLicense": "自定义或批量许可",
"LiveTrainingSupport": "现场培训和支持",
"AndMore": "和更多",
"AdditionalDeveloperLicense": "额外的开发人员许可",
"ProjectCount": "项目数量",
"AllProModules": "所有模块",
"AllProThemes": "所有主题",
"AllProStartupTemplates": "所有专业启动模板",
"SourceCodeOfAllModules": "所有模块的源码",
"SourceCodeOfAllThemes": "所有主题的源码",
"PerpetualLicense": "永久的许可",
"UnlimitedServerDeployment": "无限制的服务器部署",
"YearUpgrade": "1年升级",
"YearPremiumForumSupport": "1年高级论坛支持",
"ForumSupportIncidentCountYear": "论坛支持事件数量/年",
"PrivateTicketEmailSupport": "私有票和email支持",
"BuyNow": "现在购买"
}
}

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

@ -136,6 +136,9 @@
"GitHubUserNameValidationMessage": "Your Github username can not include whitespace, please be sure your Github username is correct.",
"PersonalSiteUrlValidationMessage": "Your personal site URL can not include whitespace, please be sure your personal site URL is correct.",
"TwitterUserNameValidationMessage": "Your Twitter username can not include whitespace, please be sure your Twitter username is correct.",
"LinkedinUrlValidationMessage": "Your Linkedin URL can not include whitespace, please be sure your Linkedin URL is correct."
"LinkedinUrlValidationMessage": "Your Linkedin URL can not include whitespace, please be sure your Linkedin URL is correct.",
"NoPostsFound": "No posts found!",
"SearchInPosts": "Search in posts...",
"MinimumSearchContent": "You must enter at least 3 characters!"
}
}

31
abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/zh-Hans.json

@ -110,7 +110,34 @@
"VideoContentForm": "视频内容来源",
"GithubPostForm": "Github文章来源",
"ExternalPostForm": "外部文章来源",
"PostSourceTypeChooses": "我们接受文章的三种来源类型;",
"Posts": "文章"
"HowToPost": "如何发布",
"Posts": "文章",
"VideoUrl": "视频Url",
"GithubArticleUrl": "Github文章Url",
"ExternalArticleUrl": "外部文章Url",
"CreatePostCoverInfo": "要创建一个有效的帖子,需要添加封面图片. 上传16:9的图片获取最佳视觉效果. 文件最大支持: 1MB",
"ThankYouForContribution": "感谢你对ABP社区的贡献",
"GithubArticle": "Github文章",
"GithubArticleSubmitStepOne": "<span class=\"font-weight-bold\">1.</span> 用Markdown格式在GitHub的任何公共存储库上写一篇文章. <a target=\"_blank\" href=\"https://github.com/abpframework/abp/blob/dev/docs/en/Community-Articles/2020-12-04-Event-Organizer/Post.md\">示例</a>",
"GithubArticleSubmitStepTwo": "<span class=\"font-weight-bold\">2.</span> 使用表单提交你的文章URL.",
"GithubArticleSubmitStepThree": "<span class=\"font-weight-bold\">3.</span> 你的文章将在这个网站中呈现.",
"YoutubeVideo": "Youtube视频",
"YoutubeVideoSubmitStepOne": "<span class=\"font-weight-bold\">1.</span> 在YouTube上发布你的视频.",
"YoutubeVideoSubmitStepTwo": "<span class=\"font-weight-bold\">2.</span> 使用表单提交视频URL.",
"YoutubeVideoSubmitStepThree": "<span class=\"font-weight-bold\">3.</span> 访客将能够在这个网站上直接观看你的视频内容.",
"ExternalContent": "外部内容",
"ExternalContentSubmitStepOne": "<span class=\"font-weight-bold\">1.</span> 在任何公开的平台创建内容(medium, 你自己的博客或者任何你喜欢的).",
"ExternalContentSubmitStepTwo": "<span class=\"font-weight-bold\">2.</span> 使用表单提交内容URL.",
"ExternalContentSubmitStepThree": "<span class=\"font-weight-bold\">3.</span> 访客被重定向到原网站的内容.",
"ChooseYourContentType": "请选择你想要添加内容的方式",
"PostContentViaGithub": "我想要使用<span class=\"font-weight-bold\"><i class=\"fa fa-github\"></i> GitHub</span>提交markdown格式的文章",
"PostContentViaYoutube": "我想要分享我在<span class=\"font-weight-bold\"><i class=\"fa fa-youtube\"></i> Youtube</span>的可用视频",
"PostContentViaExternalSource": "我想要添加我在<span class=\"font-weight-bold\">其他平台</span>发布的内容",
"GitHubUserNameValidationMessage": "您的Github用户名不能包含空格, 请确认你的Github用户名是正确的.",
"PersonalSiteUrlValidationMessage": "你的个人网站URL不能包含空格, 请确定你的个人网站URL是正确的.",
"TwitterUserNameValidationMessage": "你的Twitter用户名不能包含空格, 请确认你的Twitter用户名是正确的.",
"LinkedinUrlValidationMessage": "你的领英URL不能包含空格, 请确认你的领英URL是正确的",
"NoPostsFound": "没有发现帖子",
"SearchInPosts": "搜索帖子…"
}
}

4
abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/de-DE.json

@ -185,6 +185,8 @@
"SeeCliDocumentForMoreInformation": "Weitere Optionen finden Sie im <a href=\"{0}\">ABP CLI-Dokument</a> oder wählen Sie oben die Registerkarte \"Direkter Download\".",
"Optional": "Optional",
"LocalFrameworkRef": "Behalten Sie die lokale Projektreferenz für die Framework-Pakete bei.",
"SelectLanguage": "Sprache auswählen"
"SelectLanguage": "Sprache auswählen",
"LatestArticleOnCommunity": "Neuester Artikel zur ABP Community",
"Register": "Registrieren"
}
}

3
abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en-GB.json

@ -193,6 +193,7 @@
"MultipleDBOptions": "Multiple Database Providers",
"MultipleUIOptionsExplanation": "The core framework is designed as UI independent and can work with any type of UI system, while there are multiple pre-built and integrated options are provided out of the box.",
"MultipleDBOptionsExplanation": "The framework can work with any data source, while the following providers are officially developed and supported:",
"SelectLanguage": "Select language"
"SelectLanguage": "Select language",
"LatestArticleOnCommunity": "Latest Article on ABP Community"
}
}

5
abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json

@ -8,6 +8,7 @@
"ProjectName": "Project name",
"ProjectType": "Project type",
"DatabaseProvider": "Database provider",
"DatabaseManagementSystem": "Database management system",
"NTier": "N-Tier",
"IncludeUserInterface": "Include user interface",
"CreateNow": "Create now",
@ -193,6 +194,8 @@
"MultipleDBOptions": "Multiple Database Providers",
"MultipleUIOptionsExplanation": "The core framework is designed as UI independent and can work with any type of UI system, while there are multiple pre-built and integrated options are provided out of the box.",
"MultipleDBOptionsExplanation": "The framework can work with any data source, while the following providers are officially developed and supported;",
"SelectLanguage": "Select language"
"SelectLanguage": "Select language",
"LatestArticleOnCommunity": "Latest Article on ABP Community",
"Register": "Register"
}
}

4
abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/es.json

@ -185,6 +185,8 @@
"SeeCliDocumentForMoreInformation": "Ver el <a href=\"{0}\">documento ABP CLI </a> para más opciones o selecciona la \"Direct Download\" pestaña de arriba.",
"Optional": "Opcional",
"LocalFrameworkRef": "Mantén la referencia al proyecto local para los paquetes del framework.",
"SelectLanguage": "Vali keel"
"SelectLanguage": "Vali keel",
"LatestArticleOnCommunity": "Último artículo sobre la comunidad ABP",
"Register": "Registrarse"
}
}

7
abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/zh-Hans.json

@ -8,6 +8,7 @@
"ProjectName": "项目名称",
"ProjectType": "项目类型",
"DatabaseProvider": "数据库提供者",
"DatabaseManagementSystem": "数据库管理系统",
"NTier": "N层",
"IncludeUserInterface": "包含用户界面",
"CreateNow": "现在创建",
@ -193,6 +194,8 @@
"MultipleDBOptions": "多个数据库提供程序",
"MultipleUIOptionsExplanation": "核心框架设计为独立与UI,可以和任何类型的UI系统一起使用. 同时提供了多个开箱即用的预构建集成选项.",
"MultipleDBOptionsExplanation": "该框架可以使用任何数据源,并且以下提供程序已得到正式开发和支持;",
"SelectLanguage": "选择语言"
"SelectLanguage": "选择语言",
"LatestArticleOnCommunity": "关于ABP社区的最新文章",
"Register": "寄存器"
}
}
}

20
docs/en/Background-Jobs-RabbitMq.md

@ -66,6 +66,26 @@ Defining multiple connections is allowed. In this case, you can use different co
}
````
If you need to connect to the RabbitMQ cluster, you can use the `;` character to separate the host names.
**Example: Connect to the RabbitMQ cluster**
```json
{
"RabbitMQ": {
"Connections": {
"Default": {
"HostName": "123.123.123.123;234.234.234.234"
}
},
"EventBus": {
"ClientName": "MyClientName",
"ExchangeName": "MyExchangeName"
}
}
}
```
#### AbpRabbitMqOptions
`AbpRabbitMqOptions` class can be used to configure the connection strings for the RabbitMQ. You can configure this options inside the `ConfigureServices` of your [module](Module-Development-Basics.md).

6
docs/en/Best-Practices/Entity-Framework-Core-Integration.md

@ -16,11 +16,13 @@
[ConnectionStringName("AbpIdentity")]
public interface IIdentityDbContext : IEfCoreDbContext
{
DbSet<IdentityUser> Users { get; set; }
DbSet<IdentityRole> Roles { get; set; }
DbSet<IdentityUser> Users { get; }
DbSet<IdentityRole> Roles { get; }
}
````
* **Do not** define `set;` for the properties in this interface.
### DbContext class
* **Do** inherit the `DbContext` from the `AbpDbContext<TDbContext>` class.

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

@ -36,7 +36,7 @@ Configure<AbpBlobStoringOptions>(options =>
aliyun.Policy = "policy";
aliyun.DurationSeconds = "expiration date";
aliyun.ContainerName = "your aliyun container name";
aliyun.CreateContainerIfNotExists = false;
aliyun.CreateContainerIfNotExists = true;
});
});
});

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

@ -39,7 +39,7 @@ Configure<AbpBlobStoringOptions>(options =>
Aws.Policy = "policy";
Aws.DurationSeconds = "expiration date";
Aws.ContainerName = "your Aws container name";
Aws.CreateContainerIfNotExists = false;
Aws.CreateContainerIfNotExists = true;
});
});
});

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

@ -29,7 +29,7 @@ Configure<AbpBlobStoringOptions>(options =>
{
azure.ConnectionString = "your azure connection string";
azure.ContainerName = "your azure container name";
azure.CreateContainerIfNotExists = false;
azure.CreateContainerIfNotExists = true;
});
});
});

21
docs/en/CLI.md

@ -31,6 +31,7 @@ Here, the list of all available commands before explaining their details:
* **`update`**: Automatically updates all ABP related NuGet and NPM packages in a solution.
* **`add-package`**: Adds an ABP package to a project.
* **`add-module`**: Adds a [multi-package application module](https://docs.abp.io/en/abp/latest/Modules/Index) to a solution.
* **`list-modules`**: Lists names of open-source application modules.
* **`get-source`**: Downloads the source code of a module.
* **`generate-proxy`**: Generates client side proxies to use HTTP API endpoints.
* **`remove-proxy`**: Removes previously generated client side proxies.
@ -202,6 +203,26 @@ abp add-module ProductManagement --new --add-to-solution-file
* `--with-source-code`: Downloads the source code of the module to your solution folder and uses local project references instead of NuGet/NPM packages. This options is always `True` if `--new` is used.
* `--add-to-solution-file`: Adds the downloaded/created module to your solution file, so you will also see the projects of the module when you open the solution on a IDE. (only available when `--with-source-code` is `True`.)
### list-modules
Lists names of open-source application modules.
Usage
````bash
abp list-modules [options]
````
Example:
```bash
abp list-modules
```
#### Options
* `--include-pro-modules`: Includes commercial (pro) modules in the output.
### get-source
Downloads the source code of a module to your computer.

2
docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/How-To-Add-Custom-Property-To-The-User-Entity.md

@ -78,7 +78,7 @@ public static class CustomizeUserDemoEfCoreEntityExtensionMappings
nameof(AppUser.Title),
(entityBuilder, propertyBuilder) =>
{
propertyBuilder.IsRequired();
propertyBuilder.HasDefaultValue();
propertyBuilder.HasMaxLength(UserConsts.MaxTitleLength);
}
).MapEfCoreProperty<IdentityUser, int>(

8
docs/en/Customizing-Application-Modules-Overriding-Services.md

@ -196,12 +196,16 @@ This example replaces the `AccountController` (An API Controller defined in the
**`[ExposeServices(typeof(AccountController))]` is essential** here since it registers this controller for the `AccountController` in the dependency injection system. `[Dependency(ReplaceServices = true)]` is also recommended to clear the old registration (even the ASP.NET Core DI system selects the last registered one).
In addition, The `AccountController` will be removed from [`ApplicationModel`](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.applicationmodels.applicationmodel.controllers) because it defines `ExposeServicesAttribute`. If you don't want to remove it, you can configure `AbpAspNetCoreMvcOptions`:
In addition, the `MyAccountController` will be removed from [`ApplicationModel`](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.applicationmodels.applicationmodel.controllers) because it defines `ExposeServicesAttribute`.
If `IncludeSelf = true` is specified, i.e. `[ExposeServices(typeof(AccountController), IncludeSelf = true)]`, then `AccountController` will be removed instead. This is useful for **extending** a controller.
If you don't want to remove either controller, you can configure `AbpAspNetCoreMvcOptions`:
```csharp
Configure<AbpAspNetCoreMvcOptions>(options =>
{
options.IgnoredControllersOnModelExclusion.AddIfNotContains(typeof(AccountController));
options.IgnoredControllersOnModelExclusion.AddIfNotContains(typeof(MyAccountController));
});
```

22
docs/en/Distributed-Event-Bus-RabbitMQ-Integration.md

@ -90,7 +90,7 @@ You can use any of the [ConnectionFactry](http://rabbitmq.github.io/rabbitmq-dot
**Example: Specify the connection port**
````csharp
````json
{
"RabbitMQ": {
"Connections": {
@ -103,6 +103,26 @@ You can use any of the [ConnectionFactry](http://rabbitmq.github.io/rabbitmq-dot
}
````
If you need to connect to the RabbitMQ cluster, you can use the `;` character to separate the host names.
**Example: Connect to the RabbitMQ cluster**
```json
{
"RabbitMQ": {
"Connections": {
"Default": {
"HostName": "123.123.123.123;234.234.234.234"
}
},
"EventBus": {
"ClientName": "MyClientName",
"ExchangeName": "MyExchangeName"
}
}
}
```
### The Options Classes
`AbpRabbitMqOptions` and `AbpRabbitMqEventBusOptions` classes can be used to configure the connection strings and event bus options for the RabbitMQ.

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

@ -103,7 +103,7 @@ Before starting to the UI development, we first want to prepare the localization
Localization texts are located under the `Localization/BookStore` folder of the `Acme.BookStore.Domain.Shared` project:
![bookstore-localization-files](./images/bookstore-localization-files-v2.png)
![bookstore-localization-files](images/bookstore-localization-files-v2.png)
Open the `en.json` (*the English translations*) file and change the content as below:
@ -157,7 +157,7 @@ It's time to create something visible and usable! Instead of classic MVC, we wil
Create `Books` folder under the `Pages` folder of the `Acme.BookStore.Web` project. Add a new Razor Page by right clicking the Books folder then selecting **Add > Razor Page** menu item. Name it as `Index`:
![bookstore-add-index-page](./images/bookstore-add-index-page-v2.png)
![bookstore-add-index-page](images/bookstore-add-index-page-v2.png)
Open the `Index.cshtml` and change the whole content as shown below:
@ -208,7 +208,7 @@ context.Menu.AddItem(
Run the project, login to the application with the username `admin` and the password `1q2w3E*` and see the new menu item has been added to the main menu:
![bookstore-menu-items](./images/bookstore-new-menu-item.png)
![bookstore-menu-items](images/bookstore-new-menu-item.png)
When you click to the Books menu item under the Book Store parent, you are being redirected to the new empty Books Page.
@ -250,7 +250,7 @@ Change the `Pages/Books/Index.cshtml` as following:
Create an `Index.js` file under the `Pages/Books` folder:
![bookstore-index-js-file](./images/bookstore-index-js-file-v3.png)
![bookstore-index-js-file](images/bookstore-index-js-file-v3.png)
The content of the file is shown below:
@ -344,8 +344,6 @@ It's time to create something visible and usable! There are some tools that we w
- [Ng Bootstrap](https://ng-bootstrap.github.io/#/home) will be used as the UI component library.
- [Ngx-Datatable](https://swimlane.gitbook.io/ngx-datatable/) will be used as the datatable library.
### BookModule
Run the following command line to create a new module, named `BookModule` in the root folder of the angular application:
```bash
@ -457,7 +455,7 @@ abp generate-proxy
This command will create the following files under the `/src/app/proxy/books` folder:
![Generated files](./images/generated-proxies-3.png)
![Generated files](images/generated-proxies-3.png)
### BookComponent
@ -490,7 +488,7 @@ export class BookComponent implements OnInit {
```
* We imported and injected the generated `BookService`.
* We are using the [ListService](https://docs.abp.io/en/abp/latest/UI/Angular/List-Service), a utility service of the ABP Framework which provides easy pagination, sorting and searching.
* We are using the [ListService](../UI/Angular/List-Service.md), a utility service of the ABP Framework which provides easy pagination, sorting and searching.
Open the `/src/app/book/book.component.html` and replace the content as below:
@ -531,7 +529,7 @@ Open the `/src/app/book/book.component.html` and replace the content as below:
Now you can see the final result on your browser:
![Book list final result](./images/bookstore-book-list.png)
![Book list final result](images/bookstore-book-list.png)
{{else if UI == "Blazor"}}

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

@ -48,13 +48,13 @@ This part is also recorded as a video tutorial and **<a href="https://www.youtub
In this section, you will learn how to create a new modal dialog form to create a new book. The modal dialog will look like in the image below:
![bookstore-create-dialog](./images/bookstore-create-dialog-2.png)
![bookstore-create-dialog](images/bookstore-create-dialog-2.png)
### Create the Modal Form
Create a new razor page, named `CreateModal.cshtml` under the `Pages/Books` folder of the `Acme.BookStore.Web` project.
![bookstore-add-create-dialog](./images/bookstore-add-create-dialog-v2.png)
![bookstore-add-create-dialog](images/bookstore-add-create-dialog-v2.png)
#### CreateModal.cshtml.cs
@ -186,7 +186,7 @@ The final content of the `Index.cshtml` is shown below:
This adds a new button called **New book** to the **top-right** of the table:
![bookstore-new-book-button](./images/bookstore-new-book-button-2.png)
![bookstore-new-book-button](images/bookstore-new-book-button-2.png)
Open the `Pages/Books/Index.js` and add the following code just after the `Datatable` configuration:
@ -281,7 +281,7 @@ Now, you can **run the application** and add some new books using the new modal
Create a new razor page, named `EditModal.cshtml` under the `Pages/Books` folder of the `Acme.BookStore.Web` project:
![bookstore-add-edit-dialog](./images/bookstore-add-edit-dialog.png)
![bookstore-add-edit-dialog](images/bookstore-add-edit-dialog.png)
### EditModal.cshtml.cs
@ -484,7 +484,7 @@ You can run the application and edit any book by selecting the edit action on a
The final UI looks as below:
![bookstore-books-table-actions](./images/bookstore-edit-button-2.png)
![bookstore-books-table-actions](images/bookstore-edit-button-2.png)
## Deleting a Book
@ -721,7 +721,7 @@ Open `/src/app/book/book.component.html` and make the following changes:
You can open your browser and click **New book** button to see the new modal.
![Empty modal for new book](./images/bookstore-empty-new-book-modal.png)
![Empty modal for new book](images/bookstore-empty-new-book-modal.png)
### Create a Reactive Form
@ -732,8 +732,7 @@ Open `/src/app/book/book.component.ts` and replace the content as below:
```js
import { ListService, PagedResultDto } from '@abp/ng.core';
import { Component, OnInit } from '@angular/core';
// import bookTypeOptions from @proxy/books
import { BookService, BookDto, bookTypeOptions } from '@proxy/books';
import { BookService, BookDto, bookTypeOptions } from '@proxy/books'; // add bookTypeOptions
import { FormGroup, FormBuilder, Validators } from '@angular/forms'; // add this
@Component({
@ -959,7 +958,7 @@ export class BookComponent implements OnInit {
Now, you can open your browser to see the changes:
![Save button to the modal](./images/bookstore-new-book-form-v2.png)
![Save button to the modal](images/bookstore-new-book-form-v2.png)
## Updating a Book
@ -1087,7 +1086,7 @@ Open the `/src/app/book/book.component.html`  and add the following `ngx-datata
Added an "Actions" dropdown as the first column of the table that is shown below:
![Action buttons](./images/bookstore-actions-buttons.png)
![Action buttons](images/bookstore-actions-buttons.png)
Also, change the `ng-template #abpHeader` section as shown below:
@ -1151,11 +1150,11 @@ Open `/src/app/book/book.component.html` and modify the `ngbDropdownMenu` to add
The final actions dropdown UI looks like below:
![bookstore-final-actions-dropdown](./images/bookstore-final-actions-dropdown.png)
![bookstore-final-actions-dropdown](images/bookstore-final-actions-dropdown.png)
Clicking the "Delete" action calls the `delete` method which then shows a confirmation popup as shown below:
![bookstore-confirmation-popup](./images/bookstore-confirmation-popup.png)
![bookstore-confirmation-popup](images/bookstore-confirmation-popup.png)
{{end}}

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

@ -117,7 +117,7 @@ Finally, edit the localization file (`en.json` under the `Localization/BookStore
"Permission:Books.Delete": "Deleting the books"
````
> Localization key names are arbitrary and no forcing rule. But we prefer the convention used above.
> Localization key names are arbitrary and there is no forcing rule. But we prefer the convention used above.
### Permission Management UI

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

@ -124,7 +124,7 @@ Created this class inside the `Acme.BookStore.Domain.Shared` project since we wi
## AuthorManager: The Domain Service
`Author` constructor and `ChangeName` method is `internal`, so they can be usable only in the domain layer. Create an `AuthorManager` class in the `Authors` folder (namespace) of the `Acme.BookStore.Domain` project:
`Author` constructor and `ChangeName` methods are `internal`, so they can be used only in the domain layer. Create an `AuthorManager` class in the `Authors` folder (namespace) of the `Acme.BookStore.Domain` project:
````csharp
using System;
@ -186,7 +186,7 @@ namespace Acme.BookStore.Authors
* `AuthorManager` forces to create an author and change name of an author in a controlled way. The application layer (will be introduced later) will use these methods.
> **DDD tip**: Do not introduce domain service methods unless they are really needed and perform some core business rules. For this case, we needed to this service to be able to force the unique name constraint.
> **DDD tip**: Do not introduce domain service methods unless they are really needed and perform some core business rules. For this case, we needed this service to be able to force the unique name constraint.
Both methods checks if there is already an author with the given name and throws a special business exception, `AuthorAlreadyExistsException`, defined in the `Acme.BookStore.Domain` project (in the `Authors` folder) as shown below:
@ -226,7 +226,7 @@ This is a unique string represents the error code thrown by your application and
"BookStore:00001": "There is already an author with the same name: {name}"
````
Whenever you throw an `AuthorAlreadyExistsException`, the end use will see a this message on the UI.
Whenever you throw an `AuthorAlreadyExistsException`, the end user will see a nice error message on the UI.
## IAuthorRepository

176
docs/en/Tutorials/Part-9.md

@ -843,8 +843,10 @@ Create a new Razor Component Page, `/Pages/Authors.razor`, in the `Acme.BookStor
````xml
@page "/authors"
@using Acme.BookStore.Authors
@using Acme.BookStore.Localization
@inherits BookStoreComponentBase
@inject IAuthorAppService AuthorAppService
@inject AbpBlazorMessageLocalizerHelper<BookStoreResource> LH
<Card>
<CardHeader>
<Row>
@ -917,68 +919,104 @@ Create a new Razor Component Page, `/Pages/Authors.razor`, in the `Acme.BookStor
<Modal @ref="CreateAuthorModal">
<ModalBackdrop />
<ModalContent IsCentered="true">
<ModalHeader>
<ModalTitle>@L["NewAuthor"]</ModalTitle>
<CloseButton Clicked="CloseCreateAuthorModal" />
</ModalHeader>
<ModalBody>
<Field>
<FieldLabel>@L["Name"]</FieldLabel>
<TextEdit @bind-text="@NewAuthor.Name" />
</Field>
<Field>
<FieldLabel>@L["BirthDate"]</FieldLabel>
<DateEdit TValue="DateTime" @bind-Date="@NewAuthor.BirthDate" />
</Field>
<Field>
<FieldLabel>@L["ShortBio"]</FieldLabel>
<MemoEdit Rows="5" @bind-text="@NewAuthor.ShortBio" />
</Field>
</ModalBody>
<ModalFooter>
<Button Color="Color.Secondary"
Clicked="CloseCreateAuthorModal">
@L["Cancel"]
</Button>
<Button Color="Color.Primary"
Clicked="CreateAuthorAsync">
@L["Save"]
</Button>
</ModalFooter>
<Form>
<ModalHeader>
<ModalTitle>@L["NewAuthor"]</ModalTitle>
<CloseButton Clicked="CloseCreateAuthorModal" />
</ModalHeader>
<ModalBody>
<Validations @ref="@CreateValidationsRef" Model="@NewAuthor" ValidateOnLoad="false">
<Validation MessageLocalizer="@LH.Localize">
<Field>
<FieldLabel>@L["Name"]</FieldLabel>
<TextEdit @bind-Text="@NewAuthor.Name">
<Feedback>
<ValidationError/>
</Feedback>
</TextEdit>
</Field>
</Validation>
<Field>
<FieldLabel>@L["BirthDate"]</FieldLabel>
<DateEdit TValue="DateTime" @bind-Date="@NewAuthor.BirthDate"/>
</Field>
<Validation MessageLocalizer="@LH.Localize">
<Field>
<FieldLabel>@L["ShortBio"]</FieldLabel>
<MemoEdit Rows="5" @bind-Text="@NewAuthor.ShortBio">
<Feedback>
<ValidationError/>
</Feedback>
</MemoEdit>
</Field>
</Validation>
</Validations>
</ModalBody>
<ModalFooter>
<Button Color="Color.Secondary"
Clicked="CloseCreateAuthorModal">
@L["Cancel"]
</Button>
<Button Color="Color.Primary"
Type="@ButtonType.Submit"
PreventDefaultOnSubmit="true"
Clicked="CreateAuthorAsync">
@L["Save"]
</Button>
</ModalFooter>
</Form>
</ModalContent>
</Modal>
<Modal @ref="EditAuthorModal">
<ModalBackdrop />
<ModalContent IsCentered="true">
<ModalHeader>
<ModalTitle>@EditingAuthor.Name</ModalTitle>
<CloseButton Clicked="CloseEditAuthorModal" />
</ModalHeader>
<ModalBody>
<Field>
<FieldLabel>@L["Name"]</FieldLabel>
<TextEdit @bind-text="@EditingAuthor.Name" />
</Field>
<Field>
<FieldLabel>@L["BirthDate"]</FieldLabel>
<DateEdit TValue="DateTime" @bind-Date="@EditingAuthor.BirthDate" />
</Field>
<Field>
<FieldLabel>@L["ShortBio"]</FieldLabel>
<MemoEdit Rows="5" @bind-text="@EditingAuthor.ShortBio" />
</Field>
</ModalBody>
<ModalFooter>
<Button Color="Color.Secondary"
Clicked="CloseEditAuthorModal">
@L["Cancel"]
</Button>
<Button Color="Color.Primary"
Clicked="UpdateAuthorAsync">
@L["Save"]
</Button>
</ModalFooter>
<Form>
<ModalHeader>
<ModalTitle>@EditingAuthor.Name</ModalTitle>
<CloseButton Clicked="CloseEditAuthorModal" />
</ModalHeader>
<ModalBody>
<Validations @ref="@EditValidationsRef" Model="@EditingAuthor" ValidateOnLoad="false">
<Validation MessageLocalizer="@LH.Localize">
<Field>
<FieldLabel>@L["Name"]</FieldLabel>
<TextEdit @bind-Text="@EditingAuthor.Name">
<Feedback>
<ValidationError/>
</Feedback>
</TextEdit>
</Field>
</Validation>
<Field>
<FieldLabel>@L["BirthDate"]</FieldLabel>
<DateEdit TValue="DateTime" @bind-Date="@EditingAuthor.BirthDate"/>
</Field>
<Validation>
<Field>
<FieldLabel>@L["ShortBio"]</FieldLabel>
<MemoEdit Rows="5" @bind-Text="@EditingAuthor.ShortBio">
<Feedback>
<ValidationError/>
</Feedback>
</MemoEdit>
</Field>
</Validation>
</Validations>
</ModalBody>
<ModalFooter>
<Button Color="Color.Secondary"
Clicked="CloseEditAuthorModal">
@L["Cancel"]
</Button>
<Button Color="Color.Primary"
Type="@ButtonType.Submit"
PreventDefaultOnSubmit="true"
Clicked="UpdateAuthorAsync">
@L["Save"]
</Button>
</ModalFooter>
</Form>
</ModalContent>
</Modal>
````
@ -1025,6 +1063,10 @@ namespace Acme.BookStore.Blazor.Pages
private Modal CreateAuthorModal { get; set; }
private Modal EditAuthorModal { get; set; }
private Validations CreateValidationsRef;
private Validations EditValidationsRef;
public Authors()
{
NewAuthor = new CreateAuthorDto();
@ -1079,6 +1121,8 @@ namespace Acme.BookStore.Blazor.Pages
private void OpenCreateAuthorModal()
{
CreateValidationsRef.ClearAll();
NewAuthor = new CreateAuthorDto();
CreateAuthorModal.Show();
}
@ -1090,6 +1134,8 @@ namespace Acme.BookStore.Blazor.Pages
private void OpenEditAuthorModal(AuthorDto author)
{
EditValidationsRef.ClearAll();
EditingAuthorId = author.Id;
EditingAuthor = ObjectMapper.Map<AuthorDto, UpdateAuthorDto>(author);
EditAuthorModal.Show();
@ -1114,16 +1160,22 @@ namespace Acme.BookStore.Blazor.Pages
private async Task CreateAuthorAsync()
{
await AuthorAppService.CreateAsync(NewAuthor);
await GetAuthorsAsync();
CreateAuthorModal.Hide();
if (CreateValidationsRef.ValidateAll())
{
await AuthorAppService.CreateAsync(NewAuthor);
await GetAuthorsAsync();
CreateAuthorModal.Hide();
}
}
private async Task UpdateAuthorAsync()
{
await AuthorAppService.UpdateAsync(EditingAuthorId, EditingAuthor);
await GetAuthorsAsync();
EditAuthorModal.Hide();
if (EditValidationsRef.ValidateAll())
{
await AuthorAppService.UpdateAsync(EditingAuthorId, EditingAuthor);
await GetAuthorsAsync();
EditAuthorModal.Hide();
}
}
}
}

1
docs/en/UI/Angular/Component-Replacement.md

@ -546,4 +546,5 @@ The final UI looks like below:
## See Also
- [How Replaceable Components Work with Extensions](./How-Replaceable-Components-Work-with-Extensions.md)
- [How to Replace PermissionManagementComponent](./Permission-Management-Component-Replacement.md)

101
docs/en/UI/Angular/HTTP-Requests.md

@ -27,13 +27,13 @@ An `HttpInterceptor` is able to catch `HttpErrorResponse`  and can be used for
## RestService
ABP core module has a utility service for HTTP requests: `RestService`. Unless explicitly configured otherwise, it catches HTTP errors and dispatches a `RestOccurError` action. This action is then captured by the `ErrorHandler` introduced by the `ThemeSharedModule`. Since you should already import this module in your app, when the `RestService` is used, all HTTP errors get automatically handled by deafult.
ABP core module has a utility service for HTTP requests: `RestService`. Unless explicitly configured otherwise, it catches HTTP errors and dispatches a `RestOccurError` action. This action is then captured by the `ErrorHandler` introduced by the `ThemeSharedModule`. Since you should already import this module in your app, when the `RestService` is used, all HTTP errors get automatically handled by default.
### Getting Started with RestService
In order to use the `RestService`, you must inject it in your class as a dependency.
In order to use the `RestService`, you must inject it in your class as a dependency.
```js
import { RestService } from '@abp/ng.core';
@ -200,6 +200,101 @@ getSomeCustomHeaderValue() {
}
```
You may find `Rest.Observe` enum [here](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/rest.ts#L10).
You may find `Rest.Observe` enum [here](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/rest.ts#L10).
## HTTP Error Handling
When the `RestService` is used, all HTTP errors are automatically handled by `ErrorHandler` which is a service that exposed by the `@abp/ng.theme.shared` package.
### Custom HTTP Error Handler
A custom HTTP error handler can be registered to an injection token named `HTTP_ERROR_HANDLER`. If a custom handler function is registered, the `ErrorHandler` executes that function.
See an example:
```js
// http-error-handler.ts
import { ContentProjectionService, PROJECTION_STRATEGY } from '@abp/ng.core';
import { ToasterService } from '@abp/ng.theme.shared';
import { HttpErrorResponse } from '@angular/common/http';
import { Injector } from '@angular/core';
import { throwError } from 'rxjs';
import { Error404Component } from './error404/error404.component';
export function handleHttpErrors(injector: Injector, httpError: HttpErrorResponse) {
if (httpError.status === 400) {
const toaster = injector.get(ToasterService);
toaster.error(httpError.error?.error?.message || 'Bad request!', '400');
return;
}
if (httpError.status === 404) {
const contentProjection = injector.get(ContentProjectionService);
contentProjection.projectContent(PROJECTION_STRATEGY.AppendComponentToBody(Error404Component));
return;
}
return throwError(httpError);
}
// app.module.ts
import { Error404Component } from './error404/error404.component';
import { handleHttpErrors } from './http-error-handling';
import { HTTP_ERROR_HANDLER, ... } from '@abp/ng.theme.shared';
@NgModule({
// ...
providers: [
// ...
{ provide: HTTP_ERROR_HANDLER, useValue: handleHttpErrors }
],
declarations: [
//...
Error404Component],
})
export class AppModule {}
```
In the example above:
- Created a function named `handleHttpErrors` and defined as value of the `HTTP_ERROR_HANDLER` provider in app.module. After this, the function executes when an HTTP error occurs.
- 400 bad request errors is handled. When a 400 error occurs, backend error message will be displayed as shown below:
![custom-error-handler-toaster-message](images/custom-error-handler-toaster-message.jpg)
- 404 not found errors is handled. When a 404 error occurs, `Error404Component` will be appended to the `<body>` as shown below:
![custom-error-handler-404-component](images/custom-error-handler-404-component.jpg)
- Since `throwError(httpError)` is returned at bottom of the `handleHttpErrors`, the `ErrorHandler` will handle the HTTP errors except 400 and 404 errors.
**Note 1:** If you put `return` to next line of handling an error, default error handling will not work for that error.
```js
export function handleHttpErrors(injector: Injector, httpError: HttpErrorResponse) {
if (httpError.status === 403) {
// handle 403 errors here
return; // put return to skip default error handling
}
}
```
**Note 2:** If you put `return throwError(httpError)`, default error handling will work.
- `throwError` is a function. It can be imported from `rxjs`.
- `httpError` is the second parameter of the error handler function which is registered to the `HTTP_ERROR_HANDLER` provider. Type of the `httpError` is `HttpErrorResponse`.
```js
import { throwError } from 'rxjs';
export function handleHttpErrors(injector: Injector, httpError: HttpErrorResponse) {
if (httpError.status === 500) {
// handle 500 errors here
return;
}
// you can return the throwError(httpError) at bottom of the function to run the default handler of ABP for HTTP errors that you didn't handle above.
return throwError(httpError)
}
```

288
docs/en/UI/Angular/How-Replaceable-Components-Work-with-Extensions.md

@ -0,0 +1,288 @@
# How Replaceable Components Work with Extensions
Additional UI extensibility points ([Entity action extensions](https://docs.abp.io/en/abp/latest/UI/Angular/Entity-Action-Extensions), [data table column extensions](https://docs.abp.io/en/abp/latest/UI/Angular/Data-Table-Column-Extensions), [page toolbar extensions](https://docs.abp.io/en/abp/latest/UI/Angular/Page-Toolbar-Extensions) and others) are used in ABP pages to allow to control entity actions, table columns and page toolbar of a page. If you replace a page, you need to apply some configurations to be able to work extension components in your component. Let's see how to do this by replacing the roles page.
Create a new module called `MyRolesModule`:
```bash
yarn ng generate module my-roles --module app
```
Create a new component called `MyRolesComponent`:
```bash
yarn ng generate component my-roles/my-roles --flat --export
```
Open the generated `src/app/my-roles/my-roles.component.ts` file and replace its content with the following:
```js
import { ListService, PagedAndSortedResultRequestDto } from '@abp/ng.core';
import {
CreateRole,
DeleteRole,
eIdentityComponents,
GetRoleById,
GetRoles,
IdentityRoleDto,
IdentityState,
RolesComponent,
UpdateRole,
} from '@abp/ng.identity';
import { ePermissionManagementComponents } from '@abp/ng.permission-management';
import { Confirmation, ConfirmationService } from '@abp/ng.theme.shared';
import {
EXTENSIONS_IDENTIFIER,
FormPropData,
generateFormFromProps,
} from '@abp/ng.theme.shared/extensions';
import { Component, Injector, OnInit } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { Select, Store } from '@ngxs/store';
import { Observable } from 'rxjs';
import { finalize, pluck } from 'rxjs/operators';
@Component({
selector: 'app-my-roles',
templateUrl: './my-roles.component.html',
providers: [
ListService,
{
provide: EXTENSIONS_IDENTIFIER,
useValue: eIdentityComponents.Roles,
},
{ provide: RolesComponent, useExisting: MyRolesComponent },
],
})
export class MyRolesComponent implements OnInit {
@Select(IdentityState.getRoles)
data$: Observable<IdentityRoleDto[]>;
@Select(IdentityState.getRolesTotalCount)
totalCount$: Observable<number>;
form: FormGroup;
selected: IdentityRoleDto;
isModalVisible: boolean;
visiblePermissions = false;
providerKey: string;
modalBusy = false;
permissionManagementKey = ePermissionManagementComponents.PermissionManagement;
onVisiblePermissionChange = event => {
this.visiblePermissions = event;
};
constructor(
public readonly list: ListService<PagedAndSortedResultRequestDto>,
protected confirmationService: ConfirmationService,
protected store: Store,
protected injector: Injector
) {}
ngOnInit() {
this.hookToQuery();
}
buildForm() {
const data = new FormPropData(this.injector, this.selected);
this.form = generateFormFromProps(data);
}
openModal() {
this.buildForm();
this.isModalVisible = true;
}
add() {
this.selected = {} as IdentityRoleDto;
this.openModal();
}
edit(id: string) {
this.store
.dispatch(new GetRoleById(id))
.pipe(pluck('IdentityState', 'selectedRole'))
.subscribe(selectedRole => {
this.selected = selectedRole;
this.openModal();
});
}
save() {
if (!this.form.valid) return;
this.modalBusy = true;
this.store
.dispatch(
this.selected.id
? new UpdateRole({ ...this.selected, ...this.form.value, id: this.selected.id })
: new CreateRole(this.form.value)
)
.pipe(finalize(() => (this.modalBusy = false)))
.subscribe(() => {
this.isModalVisible = false;
this.list.get();
});
}
delete(id: string, name: string) {
this.confirmationService
.warn('AbpIdentity::RoleDeletionConfirmationMessage', 'AbpIdentity::AreYouSure', {
messageLocalizationParams: [name],
})
.subscribe((status: Confirmation.Status) => {
if (status === Confirmation.Status.confirm) {
this.store.dispatch(new DeleteRole(id)).subscribe(() => this.list.get());
}
});
}
private hookToQuery() {
this.list.hookToQuery(query => this.store.dispatch(new GetRoles(query))).subscribe();
}
openPermissionsModal(providerKey: string) {
this.providerKey = providerKey;
setTimeout(() => {
this.visiblePermissions = true;
}, 0);
}
sort(data) {
const { prop, dir } = data.sorts[0];
this.list.sortKey = prop;
this.list.sortOrder = dir;
}
}
```
```js
{
provide: EXTENSIONS_IDENTIFIER,
useValue: eIdentityComponents.Roles,
},
{
provide: RolesComponent,
useExisting: MyRolesComponent
}
```
The two providers we have defined in `MyRolesComponent` are required for the extension components to work correctly.
* With the first provider, we defined the extension identifier for using `RolesComponent`'s extension actions in the `MyRolesComponent`.
* With the second provider, we have replaced the `RolesComponent` injection with the `MyRolesComponent`. Default extension actions of the `RolesComponent` try to get `RolesComponent` instance. However, the actions can get the `MyRolesComponent` instance after defining the second provider.
Open the generated `src/app/my-role/my-role.component.html` file and replace its content with the following:
```html
<div id="identity-roles-wrapper" class="card">
<div class="card-header">
<div class="row">
<div class="col col-md-6">
<h5 class="card-title">My Roles</h5>
</div>
<div class="text-right col col-md-6">
<abp-page-toolbar [record]="data$ | async"></abp-page-toolbar>
</div>
</div>
</div>
<div class="card-body">
<abp-extensible-table
[data]="data$ | async"
[recordsTotal]="totalCount$ | async"
[list]="list"
></abp-extensible-table>
</div>
</div>
<abp-modal size="md" [(visible)]="isModalVisible" [busy]="modalBusy">
<ng-template #abpHeader>
<h3>{%{{{ (selected?.id ? 'AbpIdentity::Edit' : 'AbpIdentity::NewRole') | abpLocalization }}}%}</h3>
</ng-template>
<ng-template #abpBody>
<form [formGroup]="form" (ngSubmit)="save()" validateOnSubmit>
<abp-extensible-form [selectedRecord]="selected"></abp-extensible-form>
</form>
</ng-template>
<ng-template #abpFooter>
<button type="button" class="btn btn-secondary" #abpClose>
{%{{{ 'AbpIdentity::Cancel' | abpLocalization }}}%}
</button>
<abp-button iconClass="fa fa-check" [disabled]="form?.invalid" (click)="save()">{%{{{
'AbpIdentity::Save' | abpLocalization
}}}%}</abp-button>
</ng-template>
</abp-modal>
<abp-permission-management
#abpPermissionManagement="abpPermissionManagement"
*abpReplaceableTemplate="
{
inputs: {
providerName: { value: 'R' },
providerKey: { value: providerKey },
visible: { value: visiblePermissions, twoWay: true },
hideBadges: { value: true }
},
outputs: { visibleChange: onVisiblePermissionChange },
componentKey: permissionManagementKey
};
let init = initTemplate
"
(abpInit)="init(abpPermissionManagement)"
>
</abp-permission-management>
```
We have added the `abp-page-toolbar`, `abp-extensible-table`, and `abp-extensible-form` extension components to template of the `MyRolesComponent`.
You should import the required modules for the `MyRolesComponent` to `MyRolesModule`. Open the `src/my-roles/my-roles.module.ts` file and replace the content with the following:
```js
import { UiExtensionsModule } from '@abp/ng.theme.shared/extensions';
import { NgModule } from '@angular/core';
import { SharedModule } from '../shared/shared.module';
import { MyRolesComponent } from './my-roles.component';
import { PermissionManagementModule } from '@abp/ng.permission-management';
@NgModule({
declarations: [MyRolesComponent],
imports: [SharedModule, UiExtensionsModule, PermissionManagementModule],
exports: [MyRolesComponent],
})
export class MyRolesModule {}
```
- `UiExtensionsModule` imported to be able to use the extension components in your component.
- `PermissionManagementModule` imported to be able to use the `abp-permission-*management` in your component.
As the last step, it is needs to be replaced the `RolesComponent` with the `MyRolesComponent`. Open the `app.component.ts` and modify its content as shown below:
```js
import { ReplaceableComponentsService } from '@abp/ng.core';
import { eIdentityComponents } from '@abp/ng.identity';
import { MyRolesComponent } from './my-roles/my-roles.component';
@Component(/* component metadata */)
export class AppComponent {
constructor(private replaceableComponents: ReplaceableComponentsService) {
this.replaceableComponents.add({ component: MyRolesComponent, key: eIdentityComponents.Roles });
}
}
```
After the steps above, the `RolesComponent` has been successfully replaced with the `MyRolesComponent`. When you navigate to the `/identity/roles` URL, you will see the `MyRolesComponent`'s template and see the extension components working correctly.
![my-roles-component-with-extensions](./images/my-roles-component-with-extensions.jpg)
![my-roles-component-form-extensions](./images/my-roles-component-form-extensions.jpg)

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

@ -91,10 +91,10 @@ export const environment = {
> **Important Note:** The `application.baseUrl` and the `{0}` placeholder in the value of the `baseUrl` property are required to be able to get tenant from running URL. Other placeholders in API URLs are optional.
After the configuration above, if your app runs on the `mytenant1.mydomain.com`, the app will get tenant name as **mytenant1** and replace the environment object in `ConfigState` on app initialization as follows:
After the configuration above, if your app runs on the `mytenant1.mydomain.com`, the app will get tenant name as **mytenant1** and replace the environment object in `EnvironmentService` on app initialization as follows:
```js
// environment object in ConfigState
// environment object in EnvironmentService
{
//...

2
docs/en/UI/Angular/Router-Events.md

@ -1,6 +1,6 @@
# Router Events Simplified
`RouterEvents` is a utility service to provide an easy implementation for one of the most frequent needs in Angular templates: `TrackByFunction`. Please see [this page in Angular docs](https://angular.io/guide/template-syntax#ngfor-with-trackby) for its purpose.
`RouterEvents` is a utility service for filtering specific router events and reacting to them. Please see [this page in Angular docs](https://angular.io/api/router/Event) for available router events.

4
docs/en/UI/Angular/Service-Proxies.md

@ -137,3 +137,7 @@ export class BookComponent implements OnInit {
```
> Please [see this article](https://github.com/abpframework/abp/blob/dev/docs/en/Blog-Posts/2020-09-07%20Angular-Service-Proxies/POST.md) to learn more about service proxies.
### Known Limitations
When you run a project on Visual Studio using IIS Express as the web server, there will be no remote access to your endpoints. This is the default behavior of IIS Express since it explicitly protects you from the security risks of running over the network. However, that will cause the proxy generator to fail because it needs a response from the `/api/abp/api-definition` endpoint. You may serve your endpoints via Kestrel to avoid this. Running `dotnet run` in your command line (at your project folder) will do that for you.

BIN
docs/en/UI/Angular/images/custom-error-handler-404-component.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

BIN
docs/en/UI/Angular/images/custom-error-handler-toaster-message.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

BIN
docs/en/UI/Angular/images/my-roles-component-form-extensions.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

BIN
docs/en/UI/Angular/images/my-roles-component-with-extensions.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

21
docs/en/docs-nav.json

@ -859,6 +859,27 @@
{
"text": "Custom Setting Page",
"path": "UI/Angular/Custom-Setting-Page.md"
},
{
"text": "Extensions",
"items": [
{
"text": "Entity Action Extensions",
"path": "UI/Angular/Entity-Action-Extensions.md"
},
{
"text": "Data Table Column Extensions",
"path": "UI/Angular/Data-Table-Column-Extensions.md"
},
{
"text": "Page Toolbar Extensions",
"path": "UI/Angular/Page-Toolbar-Extensions.md"
},
{
"text": "Dynamic Form Extensions",
"path": "UI/Angular/Dynamic-Form-Extensions.md"
}
]
}
]
}

154
docs/zh-Hans/Background-Jobs-RabbitMq.md

@ -1,3 +1,153 @@
# RabbitMQ Background Job Manager
# RabbitMQ 后台作业管理
待添加
RabbitMQ 是一个标准的消息队列中间件,虽然它常用于消息传递/分布式事件,但也非常适合存储 FIFO(先进先出) 顺序的后台作业.
ABP Framework 提供了 [Volo.Abp.BackgroundJobs.RabbitMQ](https://www.nuget.org/packages/Volo.Abp.BackgroundJobs.RabbitMQ) 包,将使用 RabbitMQ 来执行后台作业.
> 参阅 [后台作业文档](Background-Jobs.md) 学习如何使用后台作业系统,本文只介绍了如何安装和配置 RabbitMQ 集成.
## 安装
使用 ABP CLI 将 [Volo.Abp.BackgroundJobs.RabbitMQ](https://www.nuget.org/packages/Volo.Abp.BackgroundJobs.RabbitMQ) 包添加到你的项目:
- 如果之前没有安装过 [ABP CLI](https://docs.abp.io/en/abp/latest/CLI),请先安装它.
- 跳转到待安装后台作业管理的项目目录中(包含 `.csproj` 文件的目录),打开终端管理器.
- 执行 `abp add-package Volo.Abp.BackgroundJobs.RabbitMQ` 命令.
如果你想要手动安装,请先用 NuGet 包管理器安装 [Volo.Abp.BackgroundJobs.RabbitMQ](https://www.nuget.org/packages/Volo.Abp.BackgroundJobs.RabbitMQ) 包到指定项目,之后使在你的 [模块](Module-Development-Basics.md) 上面添加 `[DependsOn(typeof(AbpBackgroundJobsRabbitMqModule))]` 配置依赖.
## 配置
### 默认配置
默认配置将会使用标准端口和主机名(localhost)连接到 RabbitMQ 服务,**你不需要进行额外配置**.
### RabbitMQ 连接
你可以使用 ASP.NET Core 的 [标准配置系统](Configuration.md) 对 RabbitMQ 进行详细配置,比如 `appsettings.json` 或者是 [选项类](Options.md).
#### 通过 `appsettings.json` 文件配置
这种方式是配置 RabbitMQ 连接最简单的方式,你可以使用其他的配置源(例如环境变量).这些强大的功能都是由 [ASP.NET Core](https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/configuration/) 提供的支持.
**示例: 配置默认的 RabbitMQ 连接**
```json
{
"RabbitMQ": {
"Connections": {
"Default": {
"HostName": "123.123.123.123",
"Port": "5672"
}
}
}
}
```
你可以在配置文件使用所有 [ConnectionFactry](http://rabbitmq.github.io/rabbitmq-dotnet-client/api/RabbitMQ.Client.ConnectionFactory.html#properties) 的属性,关于这些属性的具体含义,可以查看 RabbitMQ 的 [官方文档](https://www.rabbitmq.com/dotnet-api-guide.html#exchanges-and-queues).
目前我们允许定义多个连接,多连接的情况适用于不同的后台作业,具体配置信息可以参考下面的 RabbitMQ 后台作业配置说明.
**示例: 定义两个 RabbitMQ 连接**
```json
{
"RabbitMQ": {
"Connections": {
"Default": {
"HostName": "123.123.123.123"
},
"SecondConnection": {
"HostName": "321.321.321.321"
}
}
}
}
```
如果需要连接到 RabbitMQ 集群,你可以指定多个 HostName.
**示例: 连接到 RabbitMQ 集群**
```json
{
"RabbitMQ": {
"Connections": {
"Default": {
"HostName": "123.123.123.123;234.234.234.234"
}
},
"EventBus": {
"ClientName": "MyClientName",
"ExchangeName": "MyExchangeName"
}
}
}
```
#### 使用选项类
`AbpRabbitMqOptions` 类型用于配置 RabbitMQ 的连接字符串,你可以在 [模块](Module-Development-Basics.md) 的 `ConfigureService` 方法中进行配置.
**示例: 配置 RabbitMQ 连接**
```csharp
Configure<AbpRabbitMqOptions>(options =>
{
options.Connections.Default.UserName = "user";
options.Connections.Default.Password = "pass";
options.Connections.Default.HostName = "123.123.123.123";
options.Connections.Default.Port = 5672;
});
```
关于选项类,可以结合 `appsettings.json` 文件一起使用.针对同一个属性,在选项类里面对该值进行了设定,会覆盖掉 `appsettings.json` 的值.
### RabbitMQ 后台作业配置说明
#### 后台作业队列的名称
默认情况下,每个后台作业都会使用一个单独的队列,结合标准前缀和作业名称来构造一个完整的队列名称.默认的前缀为 `AbpBackgroundJobs`,所以有一个作业的名称是 `EmailSending` 的话,在 RabbitMQ 的队列名称就是 `AbpBackgroundJobs.EmailSending`.
> 在后台作业的参数类上,可以使用 `BackgroundJobName` 特性指定后台作业的名称.否则的话,后台作业的名称将会是后台作业类的全名(也包含命名空间).
#### 后台作业使用的连接
默认情况下,后台作业都会使用 `Default` 作为默认连接.
#### 自定义
`AbpRabbitMqBackgroundJobOptions` 可以自定义队列名和作业使用的 RabbitMQ 连接.
**示例: **
```csharp
Configure<AbpRabbitMqBackgroundJobOptions>(options =>
{
options.DefaultQueueNamePrefix = "my_app_jobs.";
options.JobQueues[typeof(EmailSendingArgs)] =
new JobQueueConfiguration(
typeof(EmailSendingArgs),
queueName: "my_app_jobs.emails",
connectionName: "SecondConnection"
);
});
```
- 这个示例将默认的队列名前缀设置为 `my_app_jobs.`,如果多个项目都使用的同一个 RabbitMQ 服务,设置不同的前缀可以避免执行其他项目的后台作业.
- 这里还设置了 `EmailSendingArgs` 绑定的 RabbitMQ 连接.
`JobQueueConfiguration` 类的构造函数中,还有一些其他的可选参数.
- `queueName`: 指定后台作业对应的队列名称(全名).
- `connectionName`: 后台作业对应的 RabbitMQ 连接名称,默认是 `Default`.
- `durable`: 可选参数,默认为 `true`.
- `exclusive`: 可选参数,默认为 `false`.
- `autoDelete`: 可选参数,默认为 `false`.
如果你想要更多地了解 `durable`,`exclusive`,`autoDelete` 的用法,请阅读 RabbitMQ 提供的文档.
## 另请参阅
- [后台作业](Background-Jobs.md)

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

@ -36,7 +36,7 @@ Configure<AbpBlobStoringOptions>(options =>
aliyun.Policy = "policy";
aliyun.DurationSeconds = "expiration date";
aliyun.ContainerName = "your aliyun container name";
aliyun.CreateContainerIfNotExists = false;
aliyun.CreateContainerIfNotExists = true;
});
});
});

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

@ -39,7 +39,7 @@ Configure<AbpBlobStoringOptions>(options =>
Aws.Policy = "policy";
Aws.DurationSeconds = "expiration date";
Aws.ContainerName = "your Aws container name";
Aws.CreateContainerIfNotExists = false;
Aws.CreateContainerIfNotExists = true;
});
});
});

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

@ -29,7 +29,7 @@ Configure<AbpBlobStoringOptions>(options =>
{
azure.ConnectionString = "your azure connection string";
azure.ContainerName = "your azure container name";
azure.CreateContainerIfNotExists = false;
azure.CreateContainerIfNotExists = true;
});
});
});

20
docs/zh-Hans/Distributed-Event-Bus-RabbitMQ-Integration.md

@ -103,6 +103,26 @@
}
````
如果需要连接到 RabbitMQ 集群,你可以指定多个 HostName。
**示例: 连接到 RabbitMQ 集群**
```json
{
"RabbitMQ": {
"Connections": {
"Default": {
"HostName": "123.123.123.123;234.234.234.234"
}
},
"EventBus": {
"ClientName": "MyClientName",
"ExchangeName": "MyExchangeName"
}
}
}
```
### 选项类
`AbpRabbitMqOptions``AbpRabbitMqEventBusOptions` 类用于配置RabbitMQ的连接字符串和事件总线选项.

2
docs/zh-Hans/Repositories.md

@ -150,7 +150,7 @@ var people = _personRepository
* 这里异步方法**不是标准LINQ方法**,它们定义在[Microsoft.EntityFrameworkCore](https://www.nuget.org/packages/Microsoft.EntityFrameworkCore)Nuget包中.
* 标准模板应用层与领域层**不引用**EF Core 包以实现数据库提供程序独立.
根据你的需求和开发模式,你可以根据以下选项使用异步方法.s
根据你的需求和开发模式,你可以根据以下选项使用异步方法.
> 强烈建议使用异步方法! 在执行数据库查询时不要使用同步LINQ方法,以便能够开发可伸缩的应用程序.

14
framework/Volo.Abp.sln

@ -375,6 +375,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.AspNetCore.Compone
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.AspNetCore.Components.UI.BasicTheme.Server", "src\Volo.Abp.AspNetCore.Components.UI.BasicTheme.Server\Volo.Abp.AspNetCore.Components.UI.BasicTheme.Server.csproj", "{F35E5CFC-569F-4D7D-A30F-DD8AE97FEC5A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Sms.Aliyun", "src\Volo.Abp.Sms.Aliyun\Volo.Abp.Sms.Aliyun.csproj", "{ACFBA3FB-18CE-4655-9D14-1F1F5C3DFC30}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Sms.Aliyun.Tests", "test\Volo.Abp.Sms.Aliyun.Tests\Volo.Abp.Sms.Aliyun.Tests.csproj", "{DADEA538-3CA1-4ADE-A7E6-EF77A0CE4401}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -1117,6 +1121,14 @@ Global
{F35E5CFC-569F-4D7D-A30F-DD8AE97FEC5A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F35E5CFC-569F-4D7D-A30F-DD8AE97FEC5A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F35E5CFC-569F-4D7D-A30F-DD8AE97FEC5A}.Release|Any CPU.Build.0 = Release|Any CPU
{ACFBA3FB-18CE-4655-9D14-1F1F5C3DFC30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ACFBA3FB-18CE-4655-9D14-1F1F5C3DFC30}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ACFBA3FB-18CE-4655-9D14-1F1F5C3DFC30}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ACFBA3FB-18CE-4655-9D14-1F1F5C3DFC30}.Release|Any CPU.Build.0 = Release|Any CPU
{DADEA538-3CA1-4ADE-A7E6-EF77A0CE4401}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DADEA538-3CA1-4ADE-A7E6-EF77A0CE4401}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DADEA538-3CA1-4ADE-A7E6-EF77A0CE4401}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DADEA538-3CA1-4ADE-A7E6-EF77A0CE4401}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -1306,6 +1318,8 @@ Global
{8FDB3BF7-AD89-43F6-8DEB-C3E29B8801FE} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{41743609-6CDC-46F7-8C83-BCFBA5555AC0} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{F35E5CFC-569F-4D7D-A30F-DD8AE97FEC5A} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{ACFBA3FB-18CE-4655-9D14-1F1F5C3DFC30} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{DADEA538-3CA1-4ADE-A7E6-EF77A0CE4401} = {447C8A77-E5F0-4538-8687-7383196D04EA}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5}

7
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/LoginDisplay.razor

@ -1,11 +1,14 @@
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@using Microsoft.Extensions.Localization
@using Volo.Abp.Users
@using Volo.Abp.MultiTenancy
@using global::Localization.Resources.AbpUi
@inject ICurrentUser CurrentUser
@inject ICurrentTenant CurrentTenant
@inject IJSRuntime JsRuntime
@inject NavigationManager Navigation
@inject SignOutSessionStateManager SignOutManager
@inject IStringLocalizer<AbpUiResource> UiLocalizer
<AuthorizeView>
<Authorized>
<Dropdown>
@ -28,12 +31,12 @@
}
}
<DropdownDivider />
<DropdownItem Clicked="BeginSignOut">Logout</DropdownItem>
<DropdownItem Clicked="BeginSignOut">@UiLocalizer["Logout"]</DropdownItem>
</DropdownMenu>
</Dropdown>
</Authorized>
<NotAuthorized>
<a class="nav-link" href="authentication/login">Log in</a>
<a class="nav-link" href="authentication/login">@UiLocalizer["Login"]</a>
</NotAuthorized>
</AuthorizeView>
@code{

14
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/SecondLevelNavMenuItem.razor

@ -10,14 +10,12 @@
if (MenuItem.Url != null)
{
<a class="dropdown-item @cssClass @disabled" href="@url" target="@MenuItem.Target" id="@elementId">
@if (MenuItem.Icon != null)
{
if (MenuItem.Icon.StartsWith("fa"))
{
<i class="@MenuItem.Icon"></i>
}
}
@MenuItem.DisplayName
<span class="lp-icon">
<i class="@(MenuItem.Icon ?? "")"></i>
</span>
<span class="lp-text">
@MenuItem.DisplayName
</span>
</a>
}
}

1
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/TuiEditor/TuiEditorScriptContributor.cs

@ -16,6 +16,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.TuiEditor
{
public override void ConfigureBundle(BundleConfigurationContext context)
{
context.Files.AddIfNotContains("/libs/tui-editor/tui-editor-jquery-patch.js");
context.Files.AddIfNotContains("/libs/to-mark/to-mark.min.js");
if (context.FileProvider.GetFileInfo("/libs/tui-code-snippet/tui-code-snippet.min.js").Exists)

8
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Toolbars/ToolbarItem.cs

@ -14,10 +14,14 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Toolbars
public int Order { get; set; }
public ToolbarItem([NotNull] Type componentType, int order = 0)
[CanBeNull]
public string RequiredPermissionName { get; set; }
public ToolbarItem([NotNull] Type componentType, int order = 0, string requiredPermissionName = null)
{
Order = order;
ComponentType = Check.NotNull(componentType, nameof(componentType));
RequiredPermissionName = requiredPermissionName;
}
}
}
}

31
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Toolbars/ToolbarManager.cs

@ -1,8 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Volo.Abp.AspNetCore.Mvc.UI.Theming;
using Volo.Abp.Authorization.Permissions;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Toolbars
@ -14,7 +17,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Toolbars
protected IServiceProvider ServiceProvider { get; }
public ToolbarManager(
IOptions<AbpToolbarOptions> options,
IOptions<AbpToolbarOptions> options,
IServiceProvider serviceProvider,
IThemeManager themeManager)
{
@ -35,9 +38,33 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Toolbars
{
await contributor.ConfigureToolbarAsync(context);
}
await CheckPermissionsAsync(scope.ServiceProvider, toolbar);
}
return toolbar;
}
protected virtual async Task CheckPermissionsAsync(IServiceProvider serviceProvider, Toolbar toolbar)
{
var requiredPermissionItems = toolbar.Items.Where(x => !x.RequiredPermissionName.IsNullOrWhiteSpace()).ToList();
if (requiredPermissionItems.Any())
{
var permissionChecker = serviceProvider.GetRequiredService<IPermissionChecker>();
var grantResult = await permissionChecker.IsGrantedAsync(requiredPermissionItems.Select(x => x.RequiredPermissionName).Distinct().ToArray());
var toBeDeleted = new HashSet<ToolbarItem>();
foreach (var item in requiredPermissionItems)
{
if (grantResult.Result[item.RequiredPermissionName!] != PermissionGrantResult.Granted)
{
toBeDeleted.Add(item);
}
}
toolbar.Items.RemoveAll(toBeDeleted.Contains);
}
}
}
}
}

2
framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo.Abp.AspNetCore.Mvc.UI.csproj

@ -15,7 +15,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="NUglify" Version="1.11.4" />
<PackageReference Include="NUglify" Version="1.13.2" />
</ItemGroup>
<ItemGroup>

34
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpRemoteServiceApiDescriptionProvider.cs

@ -3,6 +3,7 @@ using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Options;
@ -19,7 +20,7 @@ namespace Volo.Abp.AspNetCore.Mvc.ApiExploring
public AbpRemoteServiceApiDescriptionProvider(
IModelMetadataProvider modelMetadataProvider,
IOptions<MvcOptions> mvcOptionsAccessor,
IOptions<MvcOptions> mvcOptionsAccessor,
IOptions<AbpRemoteServiceApiDescriptionProviderOptions> optionsAccessor)
{
_modelMetadataProvider = modelMetadataProvider;
@ -41,7 +42,7 @@ namespace Volo.Abp.AspNetCore.Mvc.ApiExploring
{
foreach (var apiResponseType in GetApiResponseTypes())
{
foreach (var result in context.Results.Where(x => IsRemoteService(x.ActionDescriptor)))
foreach (var result in context.Results.Where(IsRemoteService))
{
var actionProducesResponseTypeAttributes =
ReflectionHelper.GetAttributesOfMemberOrDeclaringType<ProducesResponseTypeAttribute>(
@ -62,7 +63,7 @@ namespace Volo.Abp.AspNetCore.Mvc.ApiExploring
foreach (var apiResponse in _options.SupportedResponseTypes)
{
apiResponse.ModelMetadata = _modelMetadataProvider.GetMetadataForType(apiResponse.Type);
foreach (var responseTypeMetadataProvider in _mvcOptions.OutputFormatters.OfType<IApiResponseTypeMetadataProvider>())
{
var formatterSupportedContentTypes = responseTypeMetadataProvider.GetSupportedContentTypes(null, apiResponse.Type);
@ -85,10 +86,29 @@ namespace Volo.Abp.AspNetCore.Mvc.ApiExploring
return _options.SupportedResponseTypes;
}
protected virtual bool IsRemoteService(ActionDescriptor actionDescriptor)
protected virtual bool IsRemoteService(ApiDescription actionDescriptor)
{
var remoteServiceAttr = ReflectionHelper.GetSingleAttributeOfMemberOrDeclaringTypeOrDefault<RemoteServiceAttribute>(actionDescriptor.GetMethodInfo());
return remoteServiceAttr != null && remoteServiceAttr.IsEnabled;
if (actionDescriptor.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor)
{
var remoteServiceAttr = ReflectionHelper.GetSingleAttributeOfMemberOrDeclaringTypeOrDefault<RemoteServiceAttribute>(controllerActionDescriptor.MethodInfo);
if (remoteServiceAttr != null && remoteServiceAttr.IsEnabled)
{
return true;
}
remoteServiceAttr = ReflectionHelper.GetSingleAttributeOfMemberOrDeclaringTypeOrDefault<RemoteServiceAttribute>(controllerActionDescriptor.ControllerTypeInfo);
if (remoteServiceAttr != null && remoteServiceAttr.IsEnabled)
{
return true;
}
if (typeof(IRemoteService).IsAssignableFrom(controllerActionDescriptor.ControllerTypeInfo))
{
return true;
}
}
return false;
}
}
}
}

2
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs

@ -84,7 +84,6 @@ namespace Volo.Abp.AspNetCore.Mvc
);
var controllerModel = moduleModel.GetOrAddController(
controllerType.FullName,
_options.ControllerNameGenerator(controllerType, setting),
controllerType,
_modelOptions.IgnoredInterfaces
@ -272,6 +271,7 @@ namespace Volo.Abp.AspNetCore.Mvc
actionModel.AddParameter(ParameterApiDescriptionModel.Create(
parameterDescription.Name,
_options.ApiParameterNameGenerator?.Invoke(parameterDescription),
matchedMethodParamName,
parameterDescription.Type,
parameterDescription.RouteInfo?.IsOptional ?? false,

27
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProviderOptions.cs

@ -2,6 +2,9 @@
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
using Volo.Abp.Application.Services;
using Volo.Abp.AspNetCore.Mvc.Conventions;
@ -13,6 +16,8 @@ namespace Volo.Abp.AspNetCore.Mvc
public Func<MethodInfo, string> ActionNameGenerator { get; set; }
public Func<ApiParameterDescription, string> ApiParameterNameGenerator { get; set; }
public AspNetCoreApiDescriptionModelProviderOptions()
{
ControllerNameGenerator = (controllerType, setting) =>
@ -52,6 +57,28 @@ namespace Volo.Abp.AspNetCore.Mvc
return methodNameBuilder.ToString();
};
ApiParameterNameGenerator = (apiParameterDescription) =>
{
if (apiParameterDescription.ModelMetadata is DefaultModelMetadata defaultModelMetadata)
{
var jsonPropertyNameAttribute = (System.Text.Json.Serialization.JsonPropertyNameAttribute)
defaultModelMetadata?.Attributes?.PropertyAttributes?.FirstOrDefault(x => x is System.Text.Json.Serialization.JsonPropertyNameAttribute);
if (jsonPropertyNameAttribute != null)
{
return jsonPropertyNameAttribute.Name;
}
var jsonPropertyAttribute = (Newtonsoft.Json.JsonPropertyAttribute)
defaultModelMetadata?.Attributes?.PropertyAttributes?.FirstOrDefault(x => x is Newtonsoft.Json.JsonPropertyAttribute);
if (jsonPropertyAttribute != null)
{
return jsonPropertyAttribute.PropertyName;
}
}
return null;
};
}
}
}

33
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs

@ -73,7 +73,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Conventions
protected virtual void RemoveDuplicateControllers(ApplicationModel application)
{
var derivedControllerModels = new List<ControllerModel>();
var controllerModelsToRemove = new List<ControllerModel>();
foreach (var controllerModel in application.Controllers)
{
@ -87,19 +87,42 @@ namespace Volo.Abp.AspNetCore.Mvc.Conventions
continue;
}
var exposeServicesAttr = ReflectionHelper.GetSingleAttributeOrDefault<ExposeServicesAttribute>(controllerModel.ControllerType);
if (exposeServicesAttr.IncludeSelf)
{
var exposedControllerModels = application.Controllers
.Where(cm => exposeServicesAttr.ServiceTypes.Contains(cm.ControllerType))
.ToArray();
controllerModelsToRemove.AddRange(exposedControllerModels);
Logger.LogInformation($"Removing the controller{(exposedControllerModels.Length > 1 ? "s" : "")} {exposeServicesAttr.ServiceTypes.Select(c => c.AssemblyQualifiedName).JoinAsString(", ")} from the application model since {(exposedControllerModels.Length > 1 ? "they are" : "it is")} replaced by the controller: {controllerModel.ControllerType.AssemblyQualifiedName}");
continue;
}
var baseControllerTypes = controllerModel.ControllerType
.GetBaseClasses(typeof(Controller), includeObject: false)
.Where(t => !t.IsAbstract)
.ToArray();
if (baseControllerTypes.Length > 0)
if (baseControllerTypes.Length == 0)
{
derivedControllerModels.Add(controllerModel);
Logger.LogInformation($"Removing the controller {controllerModel.ControllerType.AssemblyQualifiedName} from the application model since it replaces the controller(s): {baseControllerTypes.Select(c => c.AssemblyQualifiedName).JoinAsString(", ")}");
continue;
}
var baseControllerModels = application.Controllers
.Where(cm => baseControllerTypes.Contains(cm.ControllerType))
.ToArray();
if (baseControllerModels.Length == 0)
{
continue;
}
controllerModelsToRemove.Add(controllerModel);
Logger.LogInformation($"Removing the controller {controllerModel.ControllerType.AssemblyQualifiedName} from the application model since it replaces the controller(s): {baseControllerTypes.Select(c => c.AssemblyQualifiedName).JoinAsString(", ")}");
}
application.Controllers.RemoveAll(derivedControllerModels);
application.Controllers.RemoveAll(controllerModelsToRemove);
}
protected virtual void ConfigureRemoteService(ControllerModel controller, [CanBeNull] ConventionalControllerSetting configuration)

5
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowActionFilter.cs

@ -50,7 +50,6 @@ namespace Volo.Abp.AspNetCore.Mvc.Uow
return;
}
//Begin a new, independent unit of work
using (var uow = unitOfWorkManager.Begin(options))
{
var result = await next();
@ -58,6 +57,10 @@ namespace Volo.Abp.AspNetCore.Mvc.Uow
{
await uow.CompleteAsync(context.HttpContext.RequestAborted);
}
else
{
await uow.RollbackAsync(context.HttpContext.RequestAborted);
}
}
}

5
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowPageFilter.cs

@ -55,7 +55,6 @@ namespace Volo.Abp.AspNetCore.Mvc.Uow
return;
}
//Begin a new, independent unit of work
using (var uow = unitOfWorkManager.Begin(options))
{
var result = await next();
@ -63,6 +62,10 @@ namespace Volo.Abp.AspNetCore.Mvc.Uow
{
await uow.CompleteAsync(context.HttpContext.RequestAborted);
}
else
{
await uow.RollbackAsync(context.HttpContext.RequestAborted);
}
}
}

6
framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/AbpApplicationBuilderExtensions.cs

@ -6,6 +6,7 @@ using Microsoft.Extensions.Hosting;
using Volo.Abp;
using Volo.Abp.AspNetCore.Auditing;
using Volo.Abp.AspNetCore.ExceptionHandling;
using Volo.Abp.AspNetCore.Security;
using Volo.Abp.AspNetCore.Security.Claims;
using Volo.Abp.AspNetCore.Tracing;
using Volo.Abp.AspNetCore.Uow;
@ -82,5 +83,10 @@ namespace Microsoft.AspNetCore.Builder
{
return app.UseMiddleware<AbpClaimsMapMiddleware>();
}
public static IApplicationBuilder UseAbpSecurityHeaders(this IApplicationBuilder app)
{
return app.UseMiddleware<AbpSecurityHeadersMiddleware>();
}
}
}

30
framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/AbpSecurityHeadersMiddleware.cs

@ -0,0 +1,30 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.AspNetCore.Security
{
public class AbpSecurityHeadersMiddleware : IMiddleware, ITransientDependency
{
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
/*X-Content-Type-Options header tells the browser to not try and “guess” what a mimetype of a resource might be, and to just take what mimetype the server has returned as fact.*/
AddHeaderIfNotExists(context, "X-Content-Type-Options", "nosniff");
/*X-XSS-Protection is a feature of Internet Explorer, Chrome and Safari that stops pages from loading when they detect reflected cross-site scripting (XSS) attacks*/
AddHeaderIfNotExists(context, "X-XSS-Protection", "1; mode=block");
/*The X-Frame-Options HTTP response header can be used to indicate whether or not a browser should be allowed to render a page in a <frame>, <iframe> or <object>. SAMEORIGIN makes it being displayed in a frame on the same origin as the page itself. The spec leaves it up to browser vendors to decide whether this option applies to the top level, the parent, or the whole chain*/
AddHeaderIfNotExists(context, "X-Frame-Options", "SAMEORIGIN");
await next.Invoke(context);
}
protected virtual void AddHeaderIfNotExists(HttpContext context, string key, string value)
{
context.Response.Headers.AddIfNotContains(new KeyValuePair<string, StringValues>(key, value));
}
}
}

17
framework/src/Volo.Abp.Authorization/Microsoft/AspNetCore/Authorization/AbpAuthorizationServiceExtensions.cs

@ -65,6 +65,23 @@ namespace Microsoft.AspNetCore.Authorization
{
return (await authorizationService.AuthorizeAsync(policyName)).Succeeded;
}
public static async Task<bool> IsGrantedAnyAsync(
this IAuthorizationService authorizationService,
params string[] policyNames)
{
Check.NotNullOrEmpty(policyNames, nameof(policyNames));
foreach (var policyName in policyNames)
{
if ((await authorizationService.AuthorizeAsync(policyName)).Succeeded)
{
return true;
}
}
return false;
}
public static async Task<bool> IsGrantedAsync(this IAuthorizationService authorizationService, object resource, IAuthorizationRequirement requirement)
{

67
framework/src/Volo.Abp.Autofac/Autofac/Extensions/DependencyInjection/ServiceCollectionExtensions.cs

@ -1,67 +0,0 @@
// This software is part of the Autofac IoC container
// Copyright © 2017 Autofac Contributors
// http://autofac.org
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp;
namespace Autofac.Extensions.DependencyInjection
{
/// <summary>
/// Extension methods on <see cref="IServiceCollection"/> to register the <see cref="IServiceProviderFactory{TContainerBuilder}"/>.
/// </summary>
public static class ServiceCollectionExtensions
{
/// <summary>
/// Adds the <see cref="AutofacServiceProviderFactory"/> to the service collection.
/// </summary>
/// <param name="services">The service collection to add the factory to.</param>
/// <param name="configurationAction">Action on a <see cref="ContainerBuilder"/> that adds component registrations to the container.</param>
/// <returns>The service collection.</returns>
public static IServiceCollection AddAutofac(this IServiceCollection services, Action<ContainerBuilder> configurationAction = null)
{
return services
.ClearServiceProviderFactories()
.AddSingleton<IServiceProviderFactory<ContainerBuilder>>(new AutofacServiceProviderFactory(configurationAction));
}
private static IServiceCollection ClearServiceProviderFactories([NotNull] this IServiceCollection services)
{
Check.NotNull(services, nameof(services));
services.RemoveAll(
service => service.ImplementationInstance?
.GetType()
.GetInterfaces()
.Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IServiceProviderFactory<>)) == true
);
return services;
}
}
}

11
framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunBlobProvider.cs

@ -10,13 +10,16 @@ namespace Volo.Abp.BlobStoring.Aliyun
{
protected IOssClientFactory OssClientFactory { get; }
protected IAliyunBlobNameCalculator AliyunBlobNameCalculator { get; }
protected IBlobNormalizeNamingService BlobNormalizeNamingService { get; }
public AliyunBlobProvider(
IOssClientFactory ossClientFactory,
IAliyunBlobNameCalculator aliyunBlobNameCalculator)
IAliyunBlobNameCalculator aliyunBlobNameCalculator,
IBlobNormalizeNamingService blobNormalizeNamingService)
{
OssClientFactory = ossClientFactory;
AliyunBlobNameCalculator = aliyunBlobNameCalculator;
BlobNormalizeNamingService = blobNormalizeNamingService;
}
protected virtual IOss GetOssClient(BlobContainerConfiguration blobContainerConfiguration)
@ -88,15 +91,15 @@ namespace Volo.Abp.BlobStoring.Aliyun
return memoryStream;
}
private static string GetContainerName(BlobProviderArgs args)
protected virtual string GetContainerName(BlobProviderArgs args)
{
var configuration = args.Configuration.GetAliyunConfiguration();
return configuration.ContainerName.IsNullOrWhiteSpace()
? args.ContainerName
: configuration.ContainerName;
: BlobNormalizeNamingService.NormalizeContainerName(args.Configuration, configuration.ContainerName);
}
private bool BlobExists(IOss ossClient,string containerName, string blobName)
protected virtual bool BlobExists(IOss ossClient,string containerName, string blobName)
{
// Make sure Blob Container exists.
return ossClient.DoesBucketExist(containerName) &&

2
framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunBlobProviderConfiguration.cs

@ -82,7 +82,7 @@ namespace Volo.Abp.BlobStoring.Aliyun
public string ContainerName
{
get => _containerConfiguration.GetConfigurationOrDefault<string>(AliyunBlobProviderConfigurationNames.ContainerName);
set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.ContainerName, Check.NotNullOrWhiteSpace(value, nameof(value)));
set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.ContainerName, value);
}
/// <summary>

14
framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobProvider.cs

@ -12,12 +12,16 @@ namespace Volo.Abp.BlobStoring.Aws
{
protected IAwsBlobNameCalculator AwsBlobNameCalculator { get; }
protected IAmazonS3ClientFactory AmazonS3ClientFactory { get; }
protected IBlobNormalizeNamingService BlobNormalizeNamingService { get; }
public AwsBlobProvider(IAwsBlobNameCalculator awsBlobNameCalculator,
IAmazonS3ClientFactory amazonS3ClientFactory)
public AwsBlobProvider(
IAwsBlobNameCalculator awsBlobNameCalculator,
IAmazonS3ClientFactory amazonS3ClientFactory,
IBlobNormalizeNamingService blobNormalizeNamingService)
{
AwsBlobNameCalculator = awsBlobNameCalculator;
AmazonS3ClientFactory = amazonS3ClientFactory;
BlobNormalizeNamingService = blobNormalizeNamingService;
}
public override async Task SaveAsync(BlobProviderSaveArgs args)
@ -111,7 +115,7 @@ namespace Volo.Abp.BlobStoring.Aws
return await AmazonS3ClientFactory.GetAmazonS3Client(configuration);
}
private async Task<bool> BlobExistsAsync(AmazonS3Client amazonS3Client, string containerName, string blobName)
protected virtual async Task<bool> BlobExistsAsync(AmazonS3Client amazonS3Client, string containerName, string blobName)
{
// Make sure Blob Container exists.
if (!await AmazonS3Util.DoesS3BucketExistV2Async(amazonS3Client, containerName))
@ -147,12 +151,12 @@ namespace Volo.Abp.BlobStoring.Aws
}
}
private static string GetContainerName(BlobProviderArgs args)
protected virtual string GetContainerName(BlobProviderArgs args)
{
var configuration = args.Configuration.GetAwsConfiguration();
return configuration.ContainerName.IsNullOrWhiteSpace()
? args.ContainerName
: configuration.ContainerName;
: BlobNormalizeNamingService.NormalizeContainerName(args.Configuration, configuration.ContainerName);
}
}
}

2
framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobProviderConfiguration.cs

@ -82,7 +82,7 @@ namespace Volo.Abp.BlobStoring.Aws
public string ContainerName
{
get => _containerConfiguration.GetConfigurationOrDefault<string>(AwsBlobProviderConfigurationNames.ContainerName);
set => _containerConfiguration.SetConfiguration(AwsBlobProviderConfigurationNames.ContainerName, Check.NotNullOrWhiteSpace(value, nameof(value)));
set => _containerConfiguration.SetConfiguration(AwsBlobProviderConfigurationNames.ContainerName, value);
}
/// <summary>

14
framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProvider.cs

@ -9,10 +9,14 @@ namespace Volo.Abp.BlobStoring.Azure
public class AzureBlobProvider : BlobProviderBase, ITransientDependency
{
protected IAzureBlobNameCalculator AzureBlobNameCalculator { get; }
protected IBlobNormalizeNamingService BlobNormalizeNamingService { get; }
public AzureBlobProvider(IAzureBlobNameCalculator azureBlobNameCalculator)
public AzureBlobProvider(
IAzureBlobNameCalculator azureBlobNameCalculator,
IBlobNormalizeNamingService blobNormalizeNamingService)
{
AzureBlobNameCalculator = azureBlobNameCalculator;
BlobNormalizeNamingService = blobNormalizeNamingService;
}
public override async Task SaveAsync(BlobProviderSaveArgs args)
@ -87,22 +91,22 @@ namespace Volo.Abp.BlobStoring.Azure
await blobContainerClient.CreateIfNotExistsAsync();
}
private async Task<bool> BlobExistsAsync(BlobProviderArgs args, string blobName)
protected virtual async Task<bool> BlobExistsAsync(BlobProviderArgs args, string blobName)
{
// Make sure Blob Container exists.
return await ContainerExistsAsync(GetBlobContainerClient(args)) &&
(await GetBlobClient(args, blobName).ExistsAsync()).Value;
}
private static string GetContainerName(BlobProviderArgs args)
protected virtual string GetContainerName(BlobProviderArgs args)
{
var configuration = args.Configuration.GetAzureConfiguration();
return configuration.ContainerName.IsNullOrWhiteSpace()
? args.ContainerName
: configuration.ContainerName;
: BlobNormalizeNamingService.NormalizeContainerName(args.Configuration, configuration.ContainerName);
}
private static async Task<bool> ContainerExistsAsync(BlobContainerClient blobContainerClient)
protected virtual async Task<bool> ContainerExistsAsync(BlobContainerClient blobContainerClient)
{
return (await blobContainerClient.ExistsAsync()).Value;
}

2
framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProviderConfiguration.cs

@ -17,7 +17,7 @@
public string ContainerName
{
get => _containerConfiguration.GetConfigurationOrDefault<string>(AzureBlobProviderConfigurationNames.ContainerName);
set => _containerConfiguration.SetConfiguration(AzureBlobProviderConfigurationNames.ContainerName, Check.NotNullOrWhiteSpace(value, nameof(value)));
set => _containerConfiguration.SetConfiguration(AzureBlobProviderConfigurationNames.ContainerName, value);
}
/// <summary>

1
framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/MinioBlobContainerConfigurationExtensions.cs

@ -15,6 +15,7 @@ namespace Volo.Abp.BlobStoring.Minio
Action<MinioBlobProviderConfiguration> minioConfigureAction)
{
containerConfiguration.ProviderType = typeof(MinioBlobProvider);
containerConfiguration.NamingNormalizers.TryAdd<MinioBlobNamingNormalizer>();
minioConfigureAction(new MinioBlobProviderConfiguration(containerConfiguration));

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

@ -10,10 +10,14 @@ namespace Volo.Abp.BlobStoring.Minio
public class MinioBlobProvider : BlobProviderBase, ITransientDependency
{
protected IMinioBlobNameCalculator MinioBlobNameCalculator { get; }
protected IBlobNormalizeNamingService BlobNormalizeNamingService { get; }
public MinioBlobProvider(IMinioBlobNameCalculator minioBlobNameCalculator)
public MinioBlobProvider(
IMinioBlobNameCalculator minioBlobNameCalculator,
IBlobNormalizeNamingService blobNormalizeNamingService)
{
MinioBlobNameCalculator = minioBlobNameCalculator;
BlobNormalizeNamingService = blobNormalizeNamingService;
}
public override async Task SaveAsync(BlobProviderSaveArgs args)
@ -108,7 +112,7 @@ namespace Volo.Abp.BlobStoring.Minio
}
}
private async Task<bool> BlobExistsAsync(MinioClient client, string containerName , string blobName)
protected virtual async Task<bool> BlobExistsAsync(MinioClient client, string containerName , string blobName)
{
// Make sure Blob Container exists.
if (await client.BucketExistsAsync(containerName))
@ -133,13 +137,13 @@ namespace Volo.Abp.BlobStoring.Minio
return false;
}
private static string GetContainerName(BlobProviderArgs args)
protected virtual string GetContainerName(BlobProviderArgs args)
{
var configuration = args.Configuration.GetMinioConfiguration();
return configuration.BucketName.IsNullOrWhiteSpace()
? args.ContainerName
: configuration.BucketName;
: BlobNormalizeNamingService.NormalizeContainerName(args.Configuration, configuration.BucketName);
}
}
}

2
framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/MinioBlobProviderConfiguration.cs

@ -5,7 +5,7 @@
public string BucketName
{
get => _containerConfiguration.GetConfigurationOrDefault<string>(MinioBlobProviderConfigurationNames.BucketName);
set => _containerConfiguration.SetConfiguration(MinioBlobProviderConfigurationNames.BucketName, Check.NotNullOrWhiteSpace(value, nameof(value)));
set => _containerConfiguration.SetConfiguration(MinioBlobProviderConfigurationNames.BucketName, value);
}
/// <summary>

59
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainer.cs

@ -1,9 +1,7 @@
using System;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Threading;
@ -88,12 +86,15 @@ namespace Volo.Abp.BlobStoring
protected IServiceProvider ServiceProvider { get; }
protected IBlobNormalizeNamingService BlobNormalizeNamingService { get; }
public BlobContainer(
string containerName,
BlobContainerConfiguration configuration,
IBlobProvider provider,
ICurrentTenant currentTenant,
ICancellationTokenProvider cancellationTokenProvider,
IBlobNormalizeNamingService blobNormalizeNamingService,
IServiceProvider serviceProvider)
{
ContainerName = containerName;
@ -101,6 +102,7 @@ namespace Volo.Abp.BlobStoring
Provider = provider;
CurrentTenant = currentTenant;
CancellationTokenProvider = cancellationTokenProvider;
BlobNormalizeNamingService = blobNormalizeNamingService;
ServiceProvider = serviceProvider;
}
@ -112,13 +114,13 @@ namespace Volo.Abp.BlobStoring
{
using (CurrentTenant.Change(GetTenantIdOrNull()))
{
var (normalizedContainerName, normalizedBlobName) = NormalizeNaming(ContainerName, name);
var blobNormalizeNaming = BlobNormalizeNamingService.NormalizeNaming(Configuration, ContainerName, name);
await Provider.SaveAsync(
new BlobProviderSaveArgs(
normalizedContainerName,
blobNormalizeNaming.ContainerName,
Configuration,
normalizedBlobName,
blobNormalizeNaming.BlobName,
stream,
overrideExisting,
CancellationTokenProvider.FallbackToProvider(cancellationToken)
@ -133,14 +135,14 @@ namespace Volo.Abp.BlobStoring
{
using (CurrentTenant.Change(GetTenantIdOrNull()))
{
var (normalizedContainerName, normalizedBlobName) =
NormalizeNaming(ContainerName, name);
var blobNormalizeNaming =
BlobNormalizeNamingService.NormalizeNaming(Configuration, ContainerName, name);
return await Provider.DeleteAsync(
new BlobProviderDeleteArgs(
normalizedContainerName,
blobNormalizeNaming.ContainerName,
Configuration,
normalizedBlobName,
blobNormalizeNaming.BlobName,
CancellationTokenProvider.FallbackToProvider(cancellationToken)
)
);
@ -153,14 +155,14 @@ namespace Volo.Abp.BlobStoring
{
using (CurrentTenant.Change(GetTenantIdOrNull()))
{
var (normalizedContainerName, normalizedBlobName) =
NormalizeNaming(ContainerName, name);
var blobNormalizeNaming =
BlobNormalizeNamingService.NormalizeNaming(Configuration, ContainerName, name);
return await Provider.ExistsAsync(
new BlobProviderExistsArgs(
normalizedContainerName,
blobNormalizeNaming.ContainerName,
Configuration,
normalizedBlobName,
blobNormalizeNaming.BlobName,
CancellationTokenProvider.FallbackToProvider(cancellationToken)
)
);
@ -189,14 +191,14 @@ namespace Volo.Abp.BlobStoring
{
using (CurrentTenant.Change(GetTenantIdOrNull()))
{
var (normalizedContainerName, normalizedBlobName) =
NormalizeNaming(ContainerName, name);
var blobNormalizeNaming =
BlobNormalizeNamingService.NormalizeNaming(Configuration, ContainerName, name);
return await Provider.GetOrNullAsync(
new BlobProviderGetArgs(
normalizedContainerName,
blobNormalizeNaming.ContainerName,
Configuration,
normalizedBlobName,
blobNormalizeNaming.BlobName,
CancellationTokenProvider.FallbackToProvider(cancellationToken)
)
);
@ -212,28 +214,5 @@ namespace Volo.Abp.BlobStoring
return CurrentTenant.Id;
}
protected virtual (string, string) NormalizeNaming(string containerName, string blobName)
{
if (!Configuration.NamingNormalizers.Any())
{
return (containerName, blobName);
}
using (var scope = ServiceProvider.CreateScope())
{
foreach (var normalizerType in Configuration.NamingNormalizers)
{
var normalizer = scope.ServiceProvider
.GetRequiredService(normalizerType)
.As<IBlobNamingNormalizer>();
containerName = normalizer.NormalizeContainerName(containerName);
blobName = normalizer.NormalizeBlobName(blobName);
}
return (containerName, blobName);
}
}
}
}

7
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainerFactory.cs

@ -17,18 +17,22 @@ namespace Volo.Abp.BlobStoring
protected IServiceProvider ServiceProvider { get; }
protected IBlobNormalizeNamingService BlobNormalizeNamingService { get; }
public BlobContainerFactory(
IBlobContainerConfigurationProvider configurationProvider,
ICurrentTenant currentTenant,
ICancellationTokenProvider cancellationTokenProvider,
IBlobProviderSelector providerSelector,
IServiceProvider serviceProvider)
IServiceProvider serviceProvider,
IBlobNormalizeNamingService blobNormalizeNamingService)
{
ConfigurationProvider = configurationProvider;
CurrentTenant = currentTenant;
CancellationTokenProvider = cancellationTokenProvider;
ProviderSelector = providerSelector;
ServiceProvider = serviceProvider;
BlobNormalizeNamingService = blobNormalizeNamingService;
}
public virtual IBlobContainer Create(string name)
@ -41,6 +45,7 @@ namespace Volo.Abp.BlobStoring
ProviderSelector.Get(name),
CurrentTenant,
CancellationTokenProvider,
BlobNormalizeNamingService,
ServiceProvider
);
}

15
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobNormalizeNaming.cs

@ -0,0 +1,15 @@
namespace Volo.Abp.BlobStoring
{
public class BlobNormalizeNaming
{
public string ContainerName { get; }
public string BlobName { get; }
public BlobNormalizeNaming(string containerName, string blobName)
{
ContainerName = containerName;
BlobName = blobName;
}
}
}

64
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobNormalizeNamingService.cs

@ -0,0 +1,64 @@
using System;
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.BlobStoring
{
public class BlobNormalizeNamingService : IBlobNormalizeNamingService, ITransientDependency
{
protected IServiceProvider ServiceProvider { get; }
public BlobNormalizeNamingService(IServiceProvider serviceProvider)
{
ServiceProvider = serviceProvider;
}
public BlobNormalizeNaming NormalizeNaming(
BlobContainerConfiguration configuration,
string containerName,
string blobName)
{
if (!configuration.NamingNormalizers.Any())
{
return new BlobNormalizeNaming(containerName, blobName);
}
using (var scope = ServiceProvider.CreateScope())
{
foreach (var normalizerType in configuration.NamingNormalizers)
{
var normalizer = scope.ServiceProvider
.GetRequiredService(normalizerType)
.As<IBlobNamingNormalizer>();
containerName = containerName.IsNullOrWhiteSpace()? containerName: normalizer.NormalizeContainerName(containerName);
blobName = blobName.IsNullOrWhiteSpace()? blobName: normalizer.NormalizeBlobName(blobName);
}
return new BlobNormalizeNaming(containerName, blobName);
}
}
public string NormalizeContainerName(BlobContainerConfiguration configuration, string containerName)
{
if (!configuration.NamingNormalizers.Any())
{
return containerName;
}
return NormalizeNaming(configuration, containerName, null).ContainerName;
}
public string NormalizeBlobName(BlobContainerConfiguration configuration, string blobName)
{
if (!configuration.NamingNormalizers.Any())
{
return blobName;
}
return NormalizeNaming(configuration, null, blobName).BlobName;
}
}
}

2
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobProviderBase.cs

@ -13,4 +13,4 @@ namespace Volo.Abp.BlobStoring
public abstract Task<Stream> GetOrNullAsync(BlobProviderGetArgs args);
}
}
}

11
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/IBlobNormalizeNamingService.cs

@ -0,0 +1,11 @@
namespace Volo.Abp.BlobStoring
{
public interface IBlobNormalizeNamingService
{
BlobNormalizeNaming NormalizeNaming(BlobContainerConfiguration configuration, string containerName, string blobName);
string NormalizeContainerName(BlobContainerConfiguration configuration, string containerName);
string NormalizeBlobName(BlobContainerConfiguration configuration, string blobName);
}
}

3
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs

@ -34,6 +34,7 @@ namespace Volo.Abp.Cli
options.Commands["update"] = typeof(UpdateCommand);
options.Commands["add-package"] = typeof(AddPackageCommand);
options.Commands["add-module"] = typeof(AddModuleCommand);
options.Commands["list-modules"] = typeof(ListModulesCommand);
options.Commands["login"] = typeof(LoginCommand);
options.Commands["logout"] = typeof(LogoutCommand);
options.Commands[GenerateProxyCommand.Name] = typeof(GenerateProxyCommand);
@ -45,7 +46,7 @@ namespace Volo.Abp.Cli
options.Commands["translate"] = typeof(TranslateCommand);
options.Commands["build"] = typeof(BuildCommand);
options.Commands["bundle"] = typeof(BundleCommand);
options.Commands["create-migration-and-run-migrator"] = typeof(CreateMigrationAndRunMigrator);
options.Commands["create-migration-and-run-migrator"] = typeof(CreateMigrationAndRunMigratorCommand);
});
}
}

7
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddModuleCommand.cs

@ -59,7 +59,6 @@ namespace Volo.Abp.Cli.Commands
await SolutionModuleAdder.AddAsync(
solutionFile,
commandLineArgs.Target,
commandLineArgs.Options.GetOrNull(Options.StartupProject.Short, Options.StartupProject.Long),
version,
skipDbMigrations,
withSourceCode,
@ -167,12 +166,6 @@ namespace Volo.Abp.Cli.Commands
public const string Skip = "skip-db-migrations";
}
public static class StartupProject
{
public const string Short = "sp";
public const string Long = "startup-project";
}
public static class SourceCode
{
public const string Long = "with-source-code";

65
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/CreateMigrationAndRunMigrator.cs

@ -1,65 +0,0 @@
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp.Cli.Args;
using Volo.Abp.Cli.Utils;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.Cli.Commands
{
public class CreateMigrationAndRunMigrator : IConsoleCommand, ITransientDependency
{
public virtual async Task ExecuteAsync(CommandLineArgs commandLineArgs)
{
if (commandLineArgs.Target.IsNullOrEmpty())
{
throw new CliUsageException(
"DbMigrations folder path is missing!"
);
}
var dbMigratorProjectPath = GetDbMigratorProjectPath(commandLineArgs.Target);
if (dbMigratorProjectPath == null)
{
throw new Exception("DbMigrator is not found!");
}
var output = CmdHelper.RunCmdAndGetOutput($"cd \"{commandLineArgs.Target}\" && dotnet ef migrations add Initial -s \"{dbMigratorProjectPath}\"");
if (output.Contains("Done.") && output.Contains("To undo this action") && output.Contains("ef migrations remove")) // Migration added successfully
{
CmdHelper.RunCmd("cd \"" + Path.GetDirectoryName(dbMigratorProjectPath) + "\" && dotnet run");
}
else
{
throw new Exception("Migrations failed: " + output);
}
}
private string GetDbMigratorProjectPath(string dbMigrationsFolderPath)
{
var srcFolder = Directory.GetParent(dbMigrationsFolderPath);
var dbMigratorFolderPath = Directory.GetDirectories(srcFolder.FullName).FirstOrDefault(d => d.EndsWith(".DbMigrator"));
if (dbMigratorFolderPath == null)
{
return null;
}
return Directory.GetFiles(dbMigratorFolderPath).FirstOrDefault(f => f.EndsWith(".csproj"));
}
public string GetUsageInfo()
{
return string.Empty;
}
public string GetShortDescription()
{
return string.Empty;
}
}
}

152
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/CreateMigrationAndRunMigratorCommand.cs

@ -0,0 +1,152 @@
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Volo.Abp.Cli.Args;
using Volo.Abp.Cli.Utils;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.Cli.Commands
{
public class CreateMigrationAndRunMigratorCommand : IConsoleCommand, ITransientDependency
{
public ILogger<CreateMigrationAndRunMigratorCommand> Logger { get; set; }
public CreateMigrationAndRunMigratorCommand()
{
Logger = NullLogger<CreateMigrationAndRunMigratorCommand>.Instance;
}
public virtual async Task ExecuteAsync(CommandLineArgs commandLineArgs)
{
if (commandLineArgs.Target.IsNullOrEmpty())
{
throw new CliUsageException("DbMigrations folder path is missing!");
}
var dbMigrationsFolder = commandLineArgs.Target;
var dbMigratorProjectPath = GetDbMigratorProjectPath(dbMigrationsFolder);
if (dbMigratorProjectPath == null)
{
throw new Exception("DbMigrator is not found!");
}
if (!IsDotNetEfToolInstalled())
{
InstallDotnetEfTool();
}
var tenantDbContextName = FindTenantDbContextName(dbMigrationsFolder);
var dbContextName = tenantDbContextName != null ?
FindDbContextName(dbMigrationsFolder)
: null;
var migrationOutput = AddMigrationAndGetOutput(dbMigrationsFolder, dbContextName, "Migrations");
var tenantMigrationOutput = tenantDbContextName != null ?
AddMigrationAndGetOutput(dbMigrationsFolder, tenantDbContextName, "TenantMigrations")
: null;
if (CheckMigrationOutput(migrationOutput) && CheckMigrationOutput(tenantMigrationOutput))
{
// Migration added successfully
CmdHelper.RunCmd("cd \"" + Path.GetDirectoryName(dbMigratorProjectPath) + "\" && dotnet run");
await Task.CompletedTask;
}
else
{
var exceptionMsg = "Migrations failed! A migration command didn't run successfully:" +
Environment.NewLine +
Environment.NewLine + migrationOutput +
Environment.NewLine +
Environment.NewLine + tenantMigrationOutput;
Logger.LogError(exceptionMsg);
throw new Exception(exceptionMsg);
}
}
private string FindTenantDbContextName(string dbMigrationsFolder)
{
var tenantDbContext = Directory
.GetFiles(dbMigrationsFolder, "*TenantMigrationsDbContext.cs", SearchOption.AllDirectories)
.FirstOrDefault();
if (tenantDbContext == null)
{
return null;
}
return Path.GetFileName(tenantDbContext).RemovePostFix(".cs");
}
private string FindDbContextName(string dbMigrationsFolder)
{
var dbContext = Directory
.GetFiles(dbMigrationsFolder, "*MigrationsDbContext.cs", SearchOption.AllDirectories)
.FirstOrDefault(fp => !fp.EndsWith("TenantMigrationsDbContext.cs"));
if (dbContext == null)
{
return null;
}
return Path.GetFileName(dbContext).RemovePostFix(".cs");
}
private static string AddMigrationAndGetOutput(string dbMigrationsFolder, string dbContext, string outputDirectory)
{
var dbContextOption = string.IsNullOrWhiteSpace(dbContext)
? string.Empty
: $"--context {dbContext}";
var addMigrationCmd = $"cd \"{dbMigrationsFolder}\" && " +
$"dotnet ef migrations add Initial --output-dir {outputDirectory} {dbContextOption}";
return CmdHelper.RunCmdAndGetOutput(addMigrationCmd);
}
private static bool IsDotNetEfToolInstalled()
{
var output = CmdHelper.RunCmdAndGetOutput("dotnet tool list -g");
return output.Contains("dotnet-ef");
}
private static bool CheckMigrationOutput(string output)
{
return output == null || (output.Contains("Done.") &&
output.Contains("To undo this action") &&
output.Contains("ef migrations remove"));
}
private void InstallDotnetEfTool()
{
Logger.LogInformation("Installing dotnet-ef tool...");
CmdHelper.RunCmd("dotnet tool install --global dotnet-ef");
Logger.LogInformation("dotnet-ef tool is installed.");
}
private static string GetDbMigratorProjectPath(string dbMigrationsFolderPath)
{
var srcFolder = Directory.GetParent(dbMigrationsFolderPath);
var dbMigratorDirectory = Directory.GetDirectories(srcFolder.FullName)
.FirstOrDefault(d => d.EndsWith(".DbMigrator"));
return dbMigratorDirectory == null
? null
: Directory.GetFiles(dbMigratorDirectory).FirstOrDefault(f => f.EndsWith(".csproj"));
}
public string GetUsageInfo()
{
return string.Empty;
}
public string GetShortDescription()
{
return string.Empty;
}
}
}

79
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ListModulesCommand.cs

@ -0,0 +1,79 @@
using System;
using System.Linq;
using System.Text;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using System.Threading.Tasks;
using Volo.Abp.Cli.Args;
using Volo.Abp.Cli.ProjectBuilding;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.Cli.Commands
{
public class ListModulesCommand : IConsoleCommand, ITransientDependency
{
public ModuleInfoProvider ModuleInfoProvider { get; }
public ILogger<ListModulesCommand> Logger { get; set; }
public ListModulesCommand(ModuleInfoProvider moduleInfoProvider)
{
ModuleInfoProvider = moduleInfoProvider;
Logger = NullLogger<ListModulesCommand>.Instance;
}
public async Task ExecuteAsync(CommandLineArgs commandLineArgs)
{
var modules = await ModuleInfoProvider.GetModuleListAsync();
var freeModules = modules.Where(m => !m.IsPro).ToList();
var proModules = modules.Where(m => m.IsPro).ToList();
var output = new StringBuilder(Environment.NewLine);
output.AppendLine("Open Source Application Modules");
output.AppendLine();
foreach (var module in freeModules)
{
output.AppendLine($"> {module.DisplayName.PadRight(50)} ({module.Name})");
}
if (commandLineArgs.Options.ContainsKey("include-pro-modules"))
{
output.AppendLine();
output.AppendLine("Commercial (Pro) Application Modules");
output.AppendLine();
foreach (var module in proModules)
{
output.AppendLine($"> {module.DisplayName.PadRight(50)} ({module.Name})");
}
}
Logger.LogInformation(output.ToString());
}
public string GetUsageInfo()
{
var sb = new StringBuilder();
sb.AppendLine("");
sb.AppendLine("'list-modules' command is used for listing open source application modules.");
sb.AppendLine("");
sb.AppendLine("Usage:");
sb.AppendLine(" abp list-modules");
sb.AppendLine(" abp list-modules --include-pro-modules");
sb.AppendLine("");
sb.AppendLine("Options:");
sb.AppendLine(" --include-pro-modules Includes commercial (pro) modules in the output.");
sb.AppendLine("");
sb.AppendLine("See the documentation for more info: https://docs.abp.io/en/abp/latest/CLI");
return sb.ToString();
}
public string GetShortDescription()
{
return "List open source application modules";
}
}
}

62
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs

@ -147,19 +147,34 @@ namespace Volo.Abp.Cli.Commands
}
var createSolutionFolder = GetCreateSolutionFolderPreference(commandLineArgs);
if (!createSolutionFolder)
{
Logger.LogInformation("Create Solution Folder: no");
}
var outputFolder = commandLineArgs.Options.GetOrNull(Options.OutputFolder.Short, Options.OutputFolder.Long);
var outputFolderRoot =
outputFolder != null ? Path.GetFullPath(outputFolder) : Directory.GetCurrentDirectory();
outputFolder = createSolutionFolder ?
Path.Combine(outputFolderRoot, SolutionName.Parse(projectName).FullName) :
outputFolderRoot;
SolutionName solutionName;
if (MicroserviceServiceTemplateBase.IsMicroserviceServiceTemplate(template))
{
var microserviceSolutionName = FindMicroserviceSolutionName(outputFolderRoot);
if (microserviceSolutionName == null)
{
throw new CliUsageException("This command should be run inside a folder that contains a microservice solution!");
}
solutionName = SolutionName.Parse(microserviceSolutionName, projectName);
outputFolder = MicroserviceServiceTemplateBase.CalculateTargetFolder(outputFolderRoot, projectName);
uiFramework = uiFramework == UiFramework.NotSpecified ? FindMicroserviceSolutionUiFramework(outputFolderRoot) : uiFramework;
}
else
{
solutionName = SolutionName.Parse(projectName);
outputFolder = createSolutionFolder ?
Path.Combine(outputFolderRoot, SolutionName.Parse(projectName).FullName) :
outputFolderRoot;
}
Volo.Abp.IO.DirectoryHelper.CreateIfNotExists(outputFolder);
@ -176,7 +191,7 @@ namespace Volo.Abp.Cli.Commands
var result = await TemplateProjectBuilder.BuildAsync(
new ProjectBuildArgs(
SolutionName.Parse(projectName),
solutionName,
template,
version,
databaseProvider,
@ -245,6 +260,32 @@ namespace Volo.Abp.Cli.Commands
}
}
private string FindMicroserviceSolutionName(string outputFolderRoot)
{
var slnFile = Directory.GetFiles(outputFolderRoot, "*.sln").FirstOrDefault();
if (slnFile == null)
{
return null;
}
return Path.GetFileName(slnFile).RemovePostFix(".sln");
}
private UiFramework FindMicroserviceSolutionUiFramework(string outputFolderRoot)
{
if (Directory.Exists(Path.Combine(outputFolderRoot, "applications", "blazor")))
{
return UiFramework.Blazor;
}
if (Directory.Exists(Path.Combine(outputFolderRoot, "applications", "web")))
{
return UiFramework.Mvc;
}
return UiFramework.None;
}
private void OpenThanksPage(UiFramework uiFramework, DatabaseProvider databaseProvider, bool tiered, bool commercial)
{
uiFramework = uiFramework == UiFramework.NotSpecified || uiFramework == UiFramework.None ? UiFramework.Mvc : uiFramework;
@ -383,6 +424,11 @@ namespace Volo.Abp.Cli.Commands
protected virtual UiFramework GetUiFramework(CommandLineArgs commandLineArgs)
{
if (commandLineArgs.Options.ContainsKey("no-ui"))
{
return UiFramework.None;
}
var optionValue = commandLineArgs.Options.GetOrNull(Options.UiFramework.Short, Options.UiFramework.Long);
switch (optionValue)
{

2
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ModuleProjectBuildPipelineBuilder.cs

@ -1,4 +1,5 @@
using Volo.Abp.Cli.ProjectBuilding.Building.Steps;
using Volo.Abp.Cli.ProjectBuilding.Templates;
namespace Volo.Abp.Cli.ProjectBuilding.Building
{
@ -12,6 +13,7 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building
pipeline.Steps.Add(new ProjectReferenceReplaceStep());
pipeline.Steps.Add(new ReplaceCommonPropsStep());
pipeline.Steps.Add(new ReplaceConfigureAwaitPropsStep());
pipeline.Steps.Add(new UpdateNuGetConfigStep("/NuGet.Config"));
pipeline.Steps.Add(new CreateProjectResultZipStep());
return pipeline;

9
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/DatabaseManagementSystemChangeStep.cs

@ -54,7 +54,8 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps
private void AdjustOracleDbContextOptionsBuilder(ProjectBuildContext context)
{
var dbContextFactoryFile = context.Files.First(f => f.Name.EndsWith("MigrationsDbContextFactory.cs", StringComparison.OrdinalIgnoreCase));
var dbContextFactoryFile = context.Files.FirstOrDefault(f => f.Name.EndsWith("MigrationsDbContextFactoryBase.cs", StringComparison.OrdinalIgnoreCase))
?? context.Files.First(f => f.Name.EndsWith("MigrationsDbContextFactory.cs", StringComparison.OrdinalIgnoreCase));
dbContextFactoryFile.ReplaceText("new DbContextOptionsBuilder",
$"(DbContextOptionsBuilder<{context.BuildArgs.SolutionName.ProjectName}MigrationsDbContext>) new DbContextOptionsBuilder");
@ -62,7 +63,8 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps
private void AddMySqlServerVersion(ProjectBuildContext context)
{
var dbContextFactoryFile = context.Files.First(f => f.Name.EndsWith("MigrationsDbContextFactory.cs", StringComparison.OrdinalIgnoreCase));
var dbContextFactoryFile = context.Files.FirstOrDefault(f => f.Name.EndsWith("MigrationsDbContextFactoryBase.cs", StringComparison.OrdinalIgnoreCase))
?? context.Files.First(f => f.Name.EndsWith("MigrationsDbContextFactory.cs", StringComparison.OrdinalIgnoreCase));
dbContextFactoryFile.ReplaceText("configuration.GetConnectionString(\"Default\")",
"configuration.GetConnectionString(\"Default\"), MySqlServerVersion.LatestSupportedServerVersion");
@ -90,7 +92,8 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps
var efCoreModuleClass = context.Files.First(f => f.Name.EndsWith("EntityFrameworkCoreModule.cs", StringComparison.OrdinalIgnoreCase));
efCoreModuleClass.ReplaceText(oldUseMethod, newUseMethodForEfModule);
var dbContextFactoryFile = context.Files.First(f => f.Name.EndsWith("MigrationsDbContextFactory.cs", StringComparison.OrdinalIgnoreCase));
var dbContextFactoryFile = context.Files.FirstOrDefault(f => f.Name.EndsWith("MigrationsDbContextFactoryBase.cs", StringComparison.OrdinalIgnoreCase))
?? context.Files.First(f => f.Name.EndsWith("MigrationsDbContextFactory.cs", StringComparison.OrdinalIgnoreCase));
dbContextFactoryFile.ReplaceText(oldUseMethod, newUseMethodForDbContext);
}
}

78
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/MicroserviceServiceRandomPortStep.cs

@ -0,0 +1,78 @@
using System;
using System.IO;
using System.Linq;
using Volo.Abp.Cli.Commands;
namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps
{
public class MicroserviceServiceRandomPortStep : ProjectBuildPipelineStep
{
private readonly string _defaultPort = string.Empty;
private string _tyeFileContent = null;
public MicroserviceServiceRandomPortStep(string defaultPort)
{
_defaultPort = defaultPort;
}
public override void Execute(ProjectBuildContext context)
{
var newPort = GetNewRandomPort(context);
var targetFiles = context.Files.Where(f=> f.Name.EndsWith("launchSettings.json") || f.Name.EndsWith("appsettings.json")).ToList();
foreach (var file in targetFiles)
{
file.SetContent(file.Content.Replace(_defaultPort, newPort));
}
}
private string GetNewRandomPort(ProjectBuildContext context)
{
string newPort;
var rnd = new Random();
var tryCount = 0;
do
{
newPort = rnd.Next(44350, 45350).ToString();
if (tryCount++ > 2000)
{
break;
}
} while (PortExistsForAnotherService(context, newPort));
return newPort;
}
private bool PortExistsForAnotherService(ProjectBuildContext context, string newPort)
{
return ReadTyeFileContent(context).SplitToLines().Any(l => l.Contains("port") && l.Contains(newPort));
}
private string ReadTyeFileContent(ProjectBuildContext context)
{
if (_tyeFileContent != null)
{
return _tyeFileContent;
}
var solutionFolderPath = context.BuildArgs.ExtraProperties[NewCommand.Options.OutputFolder.Short] ??
context.BuildArgs.ExtraProperties[NewCommand.Options.OutputFolder.Long] ??
Directory.GetCurrentDirectory();
var tyeFilePath = Path.Combine(solutionFolderPath, "tye.yaml");
if (!File.Exists(tyeFilePath))
{
return String.Empty;
}
_tyeFileContent = File.ReadAllText(tyeFilePath);
return _tyeFileContent;
}
}
}

30
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ProjectReferenceReplaceStep.cs

@ -5,6 +5,7 @@ using System.Linq;
using System.Text.RegularExpressions;
using System.Xml;
using Volo.Abp.Cli.ProjectBuilding.Files;
using Volo.Abp.Cli.ProjectBuilding.Templates.Microservice;
using Volo.Abp.Cli.Utils;
namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps
@ -25,7 +26,7 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps
var localVoloRepoPath = context.BuildArgs.VoloGitHubLocalRepositoryPath;
new ProjectReferenceReplacer.LocalProjectPathReferenceReplacer(
context.Files,
context,
context.Module?.Namespace ?? "MyCompanyName.MyProjectName",
localAbpRepoPath,
localVoloRepoPath
@ -41,7 +42,7 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps
}
new ProjectReferenceReplacer.NugetReferenceReplacer(
context.Files,
context,
context.Module?.Namespace ?? "MyCompanyName.MyProjectName",
nugetPackageVersion
).Run();
@ -70,13 +71,15 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps
private abstract class ProjectReferenceReplacer
{
private readonly List<FileEntry> _entries;
private readonly bool _isMicroserviceServiceTemplate;
private readonly string _projectName;
protected ProjectReferenceReplacer(
List<FileEntry> entries,
ProjectBuildContext context,
string projectName)
{
_entries = entries;
_entries = context.Files;
_isMicroserviceServiceTemplate = MicroserviceServiceTemplateBase.IsMicroserviceServiceTemplate(context.Template?.Name);
_projectName = projectName;
}
@ -113,14 +116,15 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps
var oldNodeIncludeValue = oldNode.Attributes["Include"].Value;
// ReSharper disable once PossibleNullReferenceException : Can not be null because nodes are selected with include attribute filter in previous method
if (oldNodeIncludeValue.Contains(_projectName) && _entries.Any(e=>e.Name.EndsWith(GetProjectNameWithExtensionFromProjectReference(oldNodeIncludeValue))))
if (oldNodeIncludeValue.Contains(_projectName))
{
continue;
if (_isMicroserviceServiceTemplate || _entries.Any(e=>e.Name.EndsWith(GetProjectNameWithExtensionFromProjectReference(oldNodeIncludeValue))))
{
continue;
}
}
XmlNode newNode = null;
newNode = GetNewReferenceNode(doc, oldNodeIncludeValue);
XmlNode newNode = GetNewReferenceNode(doc, oldNodeIncludeValue);
oldNode.ParentNode.ReplaceChild(newNode, oldNode);
}
@ -145,8 +149,8 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps
{
private readonly string _nugetPackageVersion;
public NugetReferenceReplacer(List<FileEntry> entries, string projectName, string nugetPackageVersion)
: base(entries, projectName)
public NugetReferenceReplacer(ProjectBuildContext context, string projectName, string nugetPackageVersion)
: base(context, projectName)
{
_nugetPackageVersion = nugetPackageVersion;
}
@ -183,8 +187,8 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps
private readonly string _gitHubAbpLocalRepositoryPath;
private readonly string _gitHubVoloLocalRepositoryPath;
public LocalProjectPathReferenceReplacer(List<FileEntry> entries, string projectName, string gitHubAbpLocalRepositoryPath, string gitHubVoloLocalRepositoryPath)
: base(entries, projectName)
public LocalProjectPathReferenceReplacer(ProjectBuildContext context, string projectName, string gitHubAbpLocalRepositoryPath, string gitHubVoloLocalRepositoryPath)
: base(context, projectName)
{
_gitHubAbpLocalRepositoryPath = gitHubAbpLocalRepositoryPath;
_gitHubVoloLocalRepositoryPath = gitHubVoloLocalRepositoryPath;

8
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/RemoveCmsKitStep.cs

@ -9,11 +9,9 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps
{
var commonFiles = context.Files.Where(f =>
f.Name.EndsWith(".csproj") ||
f.Name.EndsWith("Module.cs") ||
f.Name.EndsWith("MyProjectNameMigrationsDbContext.cs") ||
f.Name.EndsWith("MyProjectNameGlobalFeatureConfigurator.cs") ||
(f.Name.EndsWith(".cshtml") && f.Name.Contains("MyCompanyName.MyProjectName.Web.Public"))
);
f.Name.EndsWith(".cs") ||
f.Name.EndsWith(".json") ||
f.Name.EndsWith(".cshtml"));
foreach (var file in commonFiles)
{

9
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/RemoveEfCoreDependencyFromPublicStep.cs

@ -7,8 +7,13 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps
{
public override void Execute(ProjectBuildContext context)
{
context.Files.FirstOrDefault(f => f.Name.EndsWith("MyCompanyName.MyProjectName.Web.Public.csproj"))?.RemoveTemplateCodeIfNot("EFCORE");
context.Files.FirstOrDefault(f => f.Name.EndsWith("MyProjectNameWebPublicModule.cs"))?.RemoveTemplateCodeIfNot("EFCORE");
foreach (var file in context.Files)
{
if (file.Name.EndsWith(".cs") || file.Name.EndsWith(".csproj") || file.Name.EndsWith(".json"))
{
file.RemoveTemplateCodeIfNot("EFCORE");
}
}
}
}
}

11
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/RemoveGlobalFeaturesPackageStep.cs

@ -7,8 +7,15 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps
{
public override void Execute(ProjectBuildContext context)
{
context.Files.FirstOrDefault(f => f.Name.EndsWith("MyCompanyName.MyProjectName.Domain.Shared.csproj"))?.RemoveTemplateCodeIf("CMS-KIT");
context.Files.FirstOrDefault(f => f.Name.EndsWith("MyProjectNameDomainSharedModule.cs"))?.RemoveTemplateCodeIf("CMS-KIT");
var commonFiles = context.Files.Where(f =>
f.Name.EndsWith(".csproj") ||
f.Name.EndsWith(".cs") ||
f.Name.EndsWith(".cshtml"));
foreach (var file in commonFiles)
{
file.RemoveTemplateCodeIf("CMS-KIT");
}
}
}
}

4
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/RemoveProjectFromSolutionStep.cs

@ -85,11 +85,11 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps
private void SetSolutionAndProjectPathsIfNull(ProjectBuildContext context)
{
if (_solutionFilePath == null)
{
_solutionFilePath = context.FindFile("/aspnet-core/MyCompanyName.MyProjectName.sln")?.Name ??
context.FindFile("/MyCompanyName.MyProjectName.sln")?.Name;
context.FindFile("/MyCompanyName.MyProjectName.sln")?.Name ??
context.FindFile("/MyCompanyName.MyProjectName.MicroserviceName.sln")?.Name;
}
if (_projectFolderPath == null)
{

54
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/RemoveProjectFromTyeStep.cs

@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps
{
public class RemoveProjectFromTyeStep : ProjectBuildPipelineStep
{
private readonly string _name;
public RemoveProjectFromTyeStep(string name)
{
_name = name;
}
public override void Execute(ProjectBuildContext context)
{
var tyeFile = context.Files.FirstOrDefault(f => f.Name == "/tye.yaml");
if (tyeFile == null)
{
return;
}
var lines = tyeFile.GetLines();
var newLines = new List<string>();
var nameLine = $"- name:";
var isOneOfTargetLines = false;
foreach (var line in lines)
{
if (line.Equals($"{nameLine} {_name}"))
{
isOneOfTargetLines = true;
continue;
}
if (line.StartsWith(nameLine))
{
isOneOfTargetLines = false;
}
if (!isOneOfTargetLines)
{
newLines.Add(line);
}
}
tyeFile.SetContent(String.Join(Environment.NewLine, newLines));
}
}
}

14
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/RemovePublicRedisStep.cs

@ -8,16 +8,12 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps
{
public override void Execute(ProjectBuildContext context)
{
context.Files.FirstOrDefault(f => f.Name.EndsWith("MyCompanyName.MyProjectName.Web.csproj"))?.RemoveTemplateCodeIfNot("PUBLIC-REDIS");
context.Files.FirstOrDefault(f => f.Name.EndsWith("MyProjectNameWebModule.cs"))?.RemoveTemplateCodeIfNot("PUBLIC-REDIS");
context.Files.FirstOrDefault(f => f.Name.EndsWith("MyCompanyName.MyProjectName.HttpApi.HostWithIds.csproj"))?.RemoveTemplateCodeIfNot("PUBLIC-REDIS");
context.Files.FirstOrDefault(f => f.Name.EndsWith("MyCompanyName.MyProjectName.HttpApi.Host.csproj"))?.RemoveTemplateCodeIfNot("PUBLIC-REDIS");
context.Files.FirstOrDefault(f => f.Name.EndsWith("MyProjectNameHttpApiHostModule.cs"))?.RemoveTemplateCodeIfNot("PUBLIC-REDIS");
var appSettingsFiles = context.Files.Where(f => f.Name.EndsWith("appSettings.json", StringComparison.InvariantCultureIgnoreCase)).ToList();
foreach (var appSettings in appSettingsFiles)
foreach (var file in context.Files)
{
appSettings.RemoveTemplateCodeIfNot("PUBLIC-REDIS");
if (file.Name.EndsWith(".cs") || file.Name.EndsWith(".csproj") || file.Name.EndsWith(".json"))
{
file.RemoveTemplateCodeIfNot("PUBLIC-REDIS");
}
}
}
}

36
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/SolutionRenameStep.cs

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using Volo.Abp.Cli.ProjectBuilding.Files;
using Volo.Abp.Cli.ProjectBuilding.Templates.Microservice;
namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps
{
@ -8,13 +9,34 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps
{
public override void Execute(ProjectBuildContext context)
{
new SolutionRenamer(
context.Files,
"MyCompanyName",
"MyProjectName",
context.BuildArgs.SolutionName.CompanyName,
context.BuildArgs.SolutionName.ProjectName
).Run();
if (MicroserviceServiceTemplateBase.IsMicroserviceServiceTemplate(context.BuildArgs.TemplateName))
{
new SolutionRenamer(
context.Files,
"MyCompanyName.MyProjectName",
"MicroserviceName",
context.BuildArgs.SolutionName.CompanyName,
context.BuildArgs.SolutionName.ProjectName
).Run();
new SolutionRenamer(
context.Files,
null,
"MyProjectName",
null,
SolutionName.Parse(context.BuildArgs.SolutionName.CompanyName).ProjectName
).Run();
}
else
{
new SolutionRenamer(
context.Files,
"MyCompanyName",
"MyProjectName",
context.BuildArgs.SolutionName.CompanyName,
context.BuildArgs.SolutionName.ProjectName
).Run();
}
}
private class SolutionRenamer

3
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/TemplateProjectBuildPipelineBuilder.cs

@ -33,7 +33,8 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building
}
if ((context.BuildArgs.UiFramework == UiFramework.Mvc || context.BuildArgs.UiFramework == UiFramework.Blazor)
&& context.BuildArgs.MobileApp == MobileApp.None && context.Template.Name != MicroserviceProTemplate.TemplateName)
&& context.BuildArgs.MobileApp == MobileApp.None && context.Template.Name != MicroserviceProTemplate.TemplateName
&& context.Template.Name != MicroserviceServiceProTemplate.TemplateName)
{
pipeline.Steps.Add(new RemoveRootFolderStep());
}

10
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Files/FileEntryExtensions.cs

@ -28,6 +28,11 @@ namespace Volo.Abp.Cli.ProjectBuilding.Files
public static void RemoveTemplateCodeMarkers(this FileEntry file)
{
if (!file.Content.Contains("</TEMPLATE-REMOVE>"))
{
return;
}
file.NormalizeLineEndings();
var lines = file.GetLines();
@ -59,6 +64,11 @@ namespace Volo.Abp.Cli.ProjectBuilding.Files
private static void RemoveMarkedTemplateCode(this FileEntry file, string beginMark)
{
if (!file.Content.Contains("</TEMPLATE-REMOVE>"))
{
return;
}
file.NormalizeLineEndings();
var lines = file.GetLines();

7
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/SolutionName.cs

@ -17,6 +17,11 @@ namespace Volo.Abp.Cli.ProjectBuilding
ProjectName = projectName;
}
public static SolutionName Parse(string fullName, string microserviceName)
{
return new SolutionName(fullName + "." + microserviceName, fullName, microserviceName);
}
public static SolutionName Parse(string fullName)
{
if (fullName.Length < 1)
@ -47,4 +52,4 @@ namespace Volo.Abp.Cli.ProjectBuilding
return new SolutionName(fullName, companyName, projectName);
}
}
}
}

4
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/TemplateInfoProvider.cs

@ -28,7 +28,7 @@ namespace Volo.Abp.Cli.ProjectBuilding
public TemplateInfoProvider(ICancellationTokenProvider cancellationTokenProvider,
IRemoteServiceExceptionHandler remoteServiceExceptionHandler,
AuthService authService,
AuthService authService,
CliHttpClientFactory cliHttpClientFactory)
{
CancellationTokenProvider = cancellationTokenProvider;
@ -56,6 +56,8 @@ namespace Volo.Abp.Cli.ProjectBuilding
return new AppProTemplate();
case MicroserviceProTemplate.TemplateName:
return new MicroserviceProTemplate();
case MicroserviceServiceProTemplate.TemplateName:
return new MicroserviceServiceProTemplate();
case ModuleTemplate.TemplateName:
return new ModuleTemplate();
case ModuleProTemplate.TemplateName:

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

Loading…
Cancel
Save