Browse Source

Merge branch 'dev' into masum-update-nx-angular-16

pull/16739/head
Masum ULU 3 years ago
committed by GitHub
parent
commit
9b50409418
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 94
      CODE_OF_CONDUCT.md
  2. 3
      README.md
  3. 3
      abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json
  4. 2
      abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json
  5. 14
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json
  6. 5
      abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json
  7. 12
      abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json
  8. 4
      configureawait.props
  9. 45
      docs/en/API/Auto-API-Controllers.md
  10. 25
      docs/en/API/Swagger-Integration.md
  11. 6
      docs/en/Background-Jobs-Hangfire.md
  12. 5
      docs/en/CLI.md
  13. 6
      docs/en/Community-Articles/2022-11-14-How-to-add-a-custom-grant-type-in-OpenIddict/POST.md
  14. 2
      docs/en/Contribution/How-to-Contribute-abp.io-as-a-frontend-developer.md
  15. 45
      docs/en/Distributed-Event-Bus.md
  16. 35
      docs/en/Getting-Started-React-Native.md
  17. 380
      docs/en/Image-Manipulation.md
  18. 33
      docs/en/KB/Windows-Path-Too-Long-Fix.md
  19. 27
      docs/en/Local-Event-Bus.md
  20. 12
      docs/en/Modules/OpenIddict.md
  21. 13
      docs/en/Startup-Templates/Application.md
  22. 2
      docs/en/UI/Angular/Card-Component.md
  23. 51
      docs/en/UI/Angular/Checkbox-Component.md
  24. 49
      docs/en/UI/Angular/FormInput-Component.md
  25. BIN
      docs/en/UI/Angular/images/form-checkbox.png
  26. BIN
      docs/en/UI/Angular/images/form-input.png
  27. 33
      docs/en/UI/AspNetCore/Security-Headers.md
  28. 4
      docs/en/UI/AspNetCore/Tag-Helpers/Dropdowns.md
  29. 8
      docs/en/docs-nav.json
  30. BIN
      docs/en/images/layered-project-dependencies-blazor-server.png
  31. BIN
      docs/en/images/layered-project-dependencies-blazor-wasm.png
  32. BIN
      docs/en/images/rn-options.png
  33. 2
      docs/zh-Hans/CLI.md
  34. 2
      docs/zh-Hans/Deployment/Clustered-Environment.md
  35. 2
      docs/zh-Hans/Deployment/Index.md
  36. 2
      docs/zh-Hans/docs-nav.json
  37. 70
      framework/Volo.Abp.sln
  38. 4
      framework/src/Volo.Abp.AspNetCore.Authentication.OpenIdConnect/Volo.Abp.AspNetCore.Authentication.OpenIdConnect.csproj
  39. 78
      framework/src/Volo.Abp.AspNetCore.Components.Server/Microsoft/AspNetCore/Authentication/Cookies/CookieAuthenticationOptionsExtensions.cs
  40. 1
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo.Abp.AspNetCore.Components.WebAssembly.csproj
  41. 2
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/AbpAspNetCoreComponentsWebAssemblyModule.cs
  42. 40
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/ClientProxyExceptionEventHandler.cs
  43. 1
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/Extensibility/WebAssemblyLookupApiRequestService.cs
  44. 1
      framework/src/Volo.Abp.AspNetCore.Components/Volo.Abp.AspNetCore.Components.csproj
  45. 4
      framework/src/Volo.Abp.AspNetCore.Components/Volo/Abp/AspNetCore/Components/AbpAspNetCoreComponentsModule.cs
  46. 69
      framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/AbpAspNetCoreMultiTenancyOptions.cs
  47. 22
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpDynamicformTagHelper.cs
  48. 14
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpDynamicformTagHelperService.cs
  49. 7
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerBaseTagHelper.cs
  50. 55
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerBaseTagHelperService.cs
  51. 9
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerTagHelperService.cs
  52. 12
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperResourceService.cs
  53. 5
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperScriptService.cs
  54. 10
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperStyleService.cs
  55. 22
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/ScriptNonceTagHelper.cs
  56. 3
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalScriptContributor.cs
  57. 16
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/dom-event-handlers.js
  58. 3
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs
  59. 3
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcOptions.cs
  60. 38
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs
  61. 40
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Infrastructure/AbpMemoryPoolHttpResponseStreamWriterFactory.cs
  62. 78
      framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Internal/ResponseContentTypeHelper.cs
  63. 100
      framework/src/Volo.Abp.AspNetCore/Microsoft/Extensions/DependencyInjection/CookieAuthenticationOptionsExtensions.cs
  64. 2
      framework/src/Volo.Abp.AspNetCore/Volo.Abp.AspNetCore.csproj
  65. 1
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/AbpAspNetCoreConsts.cs
  66. 14
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Controllers/ReplaceControllersAttribute.cs
  67. 23
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/AbpSecurityHeaderNonceHelper.cs
  68. 96
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/AbpSecurityHeadersMiddleware.cs
  69. 13
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/AbpSecurityHeadersOptions.cs
  70. 2
      framework/src/Volo.Abp.Authorization.Abstractions/Volo.Abp.Authorization.Abstractions.csproj
  71. 2
      framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/AbpAuthorizationAbstractionsModule.cs
  72. 4
      framework/src/Volo.Abp.Authorization/Volo.Abp.Authorization.csproj
  73. 4
      framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AbpAuthorizationModule.cs
  74. 1
      framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo.Abp.BackgroundJobs.Abstractions.csproj
  75. 4
      framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/AbpBackgroundJobsAbstractionsModule.cs
  76. 1
      framework/src/Volo.Abp.BackgroundJobs/Volo.Abp.BackgroundJobs.csproj
  77. 4
      framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/AbpBackgroundJobsModule.cs
  78. 21
      framework/src/Volo.Abp.BlazoriseUI/Components/AbpExtensibleDataGrid.razor
  79. 8
      framework/src/Volo.Abp.BlazoriseUI/Volo.Abp.BlazoriseUI.csproj
  80. 2
      framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo.Abp.Caching.StackExchangeRedis.csproj
  81. 2
      framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo/Abp/Caching/StackExchangeRedis/AbpCachingStackExchangeRedisModule.cs
  82. 60
      framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo/Abp/Caching/StackExchangeRedis/AbpRedisCache.cs
  83. 1
      framework/src/Volo.Abp.Caching/Volo.Abp.Caching.csproj
  84. 10
      framework/src/Volo.Abp.Caching/Volo/Abp/Caching/AbpDistributedCacheOptions.cs
  85. 2
      framework/src/Volo.Abp.Caching/Volo/Abp/Caching/CacheNameAttribute.cs
  86. 131
      framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs
  87. 4
      framework/src/Volo.Abp.Caching/Volo/Abp/Caching/ICacheSupportsMultipleItems.cs
  88. 32
      framework/src/Volo.Abp.Caching/Volo/Abp/Caching/IDistributedCache.cs
  89. 2
      framework/src/Volo.Abp.Caching/Volo/Abp/Caching/UnitOfWorkCacheItem.cs
  90. 2
      framework/src/Volo.Abp.Caching/Volo/Abp/Caching/UnitOfWorkCacheItemExtensions.cs
  91. 3
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs
  92. 59
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ClearDownloadCacheCommand.cs
  93. 37
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs
  94. 38
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ProjectCreationCommandBase.cs
  95. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/SourceCodeDownloadService.cs
  96. 12
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs
  97. 724
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ChangeThemeStep.cs
  98. 3
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/TemplateCodeDeleteStep.cs
  99. 3
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ISourceCodeStore.cs
  100. 6
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ProjectBuildArgs.cs

94
CODE_OF_CONDUCT.md

@ -0,0 +1,94 @@
# Contributor Covenant Code of Conduct
## Preamble
The ABP Framework was created to implement common generic web application features in a common code base. This approach helps developers to decouple line of business application features from their custom business logic. Besides, the ABP Framework helps create MVPs and kick-start your project as quickly as possible. All the contributors try to save valuable time by automating repetitive tasks at the framework level. This code of conduct has been adopted by many [other open source communities](http://contributor-covenant.org/adopters/) and we feel it expresses our values well.
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our community include:
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
- Focusing on what is best not just for us as individuals but for the overall community
Examples of unacceptable behavior include:
- The use of sexualized language or imagery, and sexual attention or advances of any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others’ private information, such as a physical or email address, without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned with this Code of Conduct and will communicate reasons for moderation decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces and when an individual officially represents the community in public spaces. Examples of representing our community include:
- Using an official e-mail address.
- Posting via an official social media account.
- Acting as an appointed representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [info@abp.io](mailto:info@abp.io). All complaints will be reviewed and investigated promptly and fairly. All community leaders must to respect the privacy and security of the reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact:** Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
**Consequence:** A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact:** A violation through a single incident or series of actions.
**Consequence:** A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period. This includes avoiding interactions in community spaces and external channels like social media. Violating these terms may lead to a temporary or permanent ban.
### 3. Temporary Ban
**Community Impact:** A serious violation of community standards, including sustained inappropriate behavior.
**Consequence:** A temporary ban from any interaction or public communication with the community for a specified period. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact:** Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
**Consequence:** A permanent ban from any sort of public interaction within the community.
## Attribution
This document is adapted from the [Contributor Covenant](https://www.contributor-covenant.org/), version 2.0, available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla’s code of conduct enforcement ladder](https://github.com/mozilla/diversity).
For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq.
Translations are available at https://www.contributor-covenant.org/translations.

3
README.md

@ -6,7 +6,8 @@
[![NuGet (with prereleases)](https://img.shields.io/nuget/vpre/Volo.Abp.Core.svg?style=flat-square)](https://www.nuget.org/packages/Volo.Abp.Core)
[![MyGet (nightly builds)](https://img.shields.io/myget/abp-nightly/vpre/Volo.Abp.svg?style=flat-square)](https://docs.abp.io/en/abp/latest/Nightly-Builds)
[![NuGet Download](https://img.shields.io/nuget/dt/Volo.Abp.Core.svg?style=flat-square)](https://www.nuget.org/packages/Volo.Abp.Core)
[![Code of Conduct](https://img.shields.io/badge/Contributor%20Covenant-v2.0%20adopted-ff69b4.svg)](https://github.com/abpframework/abp/blob/dev/CODE_OF_CONDUCT.md)
[![CLA Signed](https://cla-assistant.io/readme/badge/abpframework/abp)](https://cla-assistant.io/abpframework/abp)
[![ABP Discord server](https://img.shields.io/discord/951497912645476422?label=Discord)](https://discord.gg/abp)
ABP Framework is a complete **infrastructure** based on **ASP.NET Core** to create **modern web applications** and **APIs** by following the software development **best practices** and the **latest technologies**. Check out https://abp.io

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

@ -460,6 +460,7 @@
"ReloadFromSourceConfirmationMessage": "This post will be refreshed from \"{0}\". Do you want to continue?",
"UnitPrice": "Unit Price",
"OverallDiscountAmount": "Overall Discount Amount",
"DiscountAmount": "Discount Amount"
"DiscountAmount": "Discount Amount",
"FullChangeHistory": "Full Change History"
}
}

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

@ -70,7 +70,7 @@
"MeetTheABP": "Meet the ABP",
"CompleteWebDevelopment": "A Complete Web Development",
"Platform": "Platform",
"ABPDescription": "ABP Framework is a complete infrastructure to create modern web applications by following the software development best practices and conventions.",
"ABPDescription": "An open-source framework for web application development for ASP.NET Core. It offers complete infrastructure by following the best practices of software development.",
"StrongInfrastructure": "Strong Infrastructure",
"CompleteArchitecture": "Complete Architecture",
"DeveloperFocused": "Developer Focused",

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

@ -161,7 +161,7 @@
"WhatIsTheABPCommercial": "What is ABP Commercial?",
"WhatAreDifferencesThanAbpFramework": "What are the differences between the open source ABP Framework and ABP Commercial?",
"AbpCommercialMetaTitle": " {0} | ABP Commercial",
"AbpCommercialMetaDescription": "ABP Commercial is a set of pre-built application modules, rapid development tooling, UI themes and services built on top of the open-source ABP framework.",
"AbpCommercialMetaDescription": "A comprehensive web development platform on ABP Framework with pre-built modules, startup templates, rapid dev tools, pro UI themes & premium support.",
"ABPCommercialExplanation": "ABP Commercial is a set of premium modules, tools, themes and services that are 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, microservice compatible application development framework for ASP.NET Core. It provides a complete architecture and a strong infrastructure to let you focus on your own business code rather than repeating yourself for every new project. It is based on the best practices of software development 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>",
@ -539,6 +539,8 @@
"Pricing_Page_Testimonial_5": "ABP Framework is not only a framework, but it is also a guide for project development/management, because it provides DDD, GenericRepository, DI, Microservice, and Modularity training. Even if you are not going to use the framework itself, you can develop yourself with docs.abp.io which is well and professionally prepared (OpenIddict, Redis, Quartz etc.). Because many things are pre-built, it shortens project development time significantly (Such as login page, exception handling, data filtering, seeding, audit logging, localization, auto API controller etc.). As an example from our application, I have used Local Event Bus for stock control. So, I am able to manage order movements by writing stock handler. It is wonderful not to lose time for CreationTime, CreatorId. They are being filled automatically.",
"Pricing_Page_Testimonial_6": "ABP Framework is a good framework but it needs time to understand the different layers, classes, and libraries it used (specially ABP). I spent a lot of time reading the code base, but ABP Commercial save us time to create the project specialty entities (AR) and the repository linked to each of them. I liked also the approach used in ABP is very mature, we know is based on DDD and monolith.",
"Pricing_Page_Testimonial_7": "As a startup we need to iterate quickly and spend minimal time on boilerplate and non-core features.\nOur engineers range from highly experienced to junior engineers, we needed a common understanding and a way to share technical and domain knowledge, ABP allowed us to do this due to their great guides and documentation. \nThere are things we haven't had to worry about since they work out of the box with ABP. \nABP helped us streamline rapid prototyping and development, less than 4 weeks from feature inception to production. With all its premium features included in the license, ABP has given us, \"Startup in a Box\" on the Software Engineering Side.",
"Pricing_Page_Testimonial_8": "I would recommend ABP commercial to all those who want to expand the range of products available to their customers. It's fantastic when need to use a distributed enterprise enviroment (Angualr, WPF, Win&Linux). In addition to their products, we love their support, which makes our job faster and easier. We already know that we have found a great partner for the future who will support us in expanding our business.",
"Pricing_Page_Testimonial_9": "We are a company of 2 employees that's been in business for over 20 years.\nIn terms of our experience with ABP Commercial, we were approached by a client who requested that we develop a new human resources application in a modern environment to replace their 25-year-old Access application. We decided to transition from a desktop solution to a web-based one.\n\nAt the time, we had very little knowledge of web applications and .NET but we stumbled upon ABP Commercial, and with the help of ABP Framework, technical documentation, and ABP Suite, we were able to not only develop the application to the client's specifications but also successfully work within a .NET environment within a year.",
"AbpBookDownloadArea_ClaimYourEBook": "Claim your <span class='gradient-framework'>Mastering ABP Framework</span> E-Book",
"AddMemberModal_Warning_1": "If the <strong>username</strong> you are trying to add doesn't exist in the system, please ask your team member to register on <a href='{0}/Account/Register'>{0}</a> and share the username of his/her account with you.",
"MyOrganizations_Detail_WelcomeMessage": "Welcome to your organization, {0}",
@ -832,6 +834,14 @@
"Volo.AbpIo.Commercial:040001": "API Access Key is incorrect.",
"GetLepton": "Get Lepton Now",
"MyOrganizations_Detail_LicenseStartDate": "License Start Date",
"MyOrganizations_Detail_LicenseExpiryDate": "Expiry Date"
"MyOrganizations_Detail_LicenseExpiryDate": "Expiry Date",
"BlazoriseSupport": "How do I get the Blazorise license key and support from the Blazorise team?",
"BlazoriseSupportExplanation": "Follow the steps below to get support from the Blazorise team and get your Blazorise license key:",
"BlazoriseSupportExplanation1": "Sign up for a new account at <a href=\"https://blazorise.com/support/register\">blazorise.com/support/register</a> with the same email address as your abp.io account. Leave the \"License Key\" entry blank. <strong>It must be the same email address as your email account on abp.io</strong>.",
"BlazoriseSupportExplanation2": "Verify your email address by checking your email box. Check your spam box if you don't see an email in your inbox!",
"BlazoriseSupportExplanation3": "Log into the Blazorise support website at <a href=\"https://blazorise.com/support/login\">blazorise.com/support/login</a>.",
"BlazoriseSupportExplanation4": "If you have an active ABP Commercial license, you will also have a Blazorise PRO license. You can get your Blazorise license key at <a href=\"https://blazorise.com/support/user/manage/license\">blazorise.com/support/user/manage/license</a>.",
"BlazoriseSupportExplanation5": "You can post your questions on the support website and generate a product token for your application.",
"AbpLiveTrainingPackages": "ABP Live Training Packages"
}
}

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

@ -181,13 +181,14 @@
"Post_Create_Page_CreateNewPost": "Create New Post",
"Post_Index_Page_MetaDescription": "ABP Community's purpose is to create a contribution environment for developers who use the ABP framework.",
"Layout_Title": "{0} | ABP Community",
"Layout_MetaDescription": "ABP Community is an environment where people can share posts about ABP framework and follows the projects.",
"Layout_MetaDescription": "A hub for ABP Framework, .NET, and software development. Access articles, tutorials, news, and contribute to the ABP community.",
"Index_Page_CommunityIntroduction": "This is a hub for ABP Framework, .NET and software development. You can read the articles, watch the video tutorials, get informed about ABP’s development progress and ABP-related events, help other developers and share your expertise with the ABP community.",
"TagsInArticle": "Tags in article",
"IConsentToMedium": "I consent to the publication of this post at https://medium.com/volosoft.",
"SearchResultsFor": "Search results for <span class=\"fw-bold\">\"{0}\"</span>",
"SeeMoreVideos": "See more videos",
"DiscordPageTitle": "ABP Discord Community",
"ViewVideo": "View Video"
"ViewVideo": "View Video",
"AbpCommunityTitleContent": "ABP Community - Open Source ABP Framework"
}
}

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

@ -420,14 +420,16 @@
"CreateSolutionFolder": "Create Solution Folder",
"CreateSolutionFolderOption": "Specifies if the project will be in a new folder in the output folder or directly the output folder.",
"BooksPageTitle": "ABP Books",
"PackageDetailPage_NuGetPackageInstallationOptions": "There are two ways to install <code>{0}</code> NuGet package to your project",
"PackageDetailPage_NuGetPackageInstallationOptions": "There are three ways to install <code>{0}</code> NuGet package to your project",
"PackageDetailPage_InstallingWithABPCLI": "1: Installing with the ABP CLI",
"PackageDetailPage_InstallingWithABPCLIDescription1": "If you haven't installed the <a href=\"https://docs.abp.io/en/abp/latest/CLI\">ABP CLI</a>, first install by executing the following command in a command-line terminal",
"PackageDetailPage_InstallingWithABPCLIDescription2": "Once you have installed the ABP CLI, open a command-line terminal in the location of the project (<code>.csproj</code> file) you want to install it and execute the following command",
"PackageDetailPage_InstallingWithABPCLIDescription3": "It will add the <code>{0}</code> package reference to your project and the <code>{1}</code> dependency to your module class.",
"PackageDetailPage_ManualInstallation": "2: Manual Installation",
"PackageDetailPage_ManualInstallationDesription1": "Add <code>{0}</code> NuGet package reference to your project using your IDE or executing the following command",
"PackageDetailPage_ManualInstallationDesription2": "Then add the <code>{0}</code> dependency to your <a href=\"https://docs.abp.io/en/abp/latest/Module-Development-Basics\">module class</a> as shown in the following example",
"PackageDetailPage_SeeDocumentation": "<a href=\"{0}\">See the documentation</a> to learn how to use this package in your applications."
"PackageDetailPage_ManualInstallation": "2: Installing with the Dotnet CLI",
"PackageDetailPage_ManualInstallationDescription1": "Add <code>{0}</code> NuGet package reference to your project using your IDE or executing the following command",
"PackageDetailPage_ManualInstallationDescription2": "Then add the <code>{0}</code> dependency to your <a href=\"https://docs.abp.io/en/abp/latest/Module-Development-Basics\">module class</a> as shown in the following example",
"PackageDetailPage_SeeDocumentation": "<a href=\"{0}\">See the documentation</a> to learn how to use this package in your applications.",
"PackageDetailPage_InstallingUsingPMC": "3: Installing with the Package Manager Console",
"PackageDetailPage_InstallingUsingPMCDescription1": "Open the <strong>Package Manager Console</strong> in Visual Studio (Tools -> Nuget Package Manager -> Package Manager Console) and execute the following command"
}
}

4
configureawait.props

@ -1,9 +1,9 @@
<Project>
<ItemGroup>
<ItemGroup Condition="'$(Configuration)' == 'Release'">
<PackageReference Include="ConfigureAwait.Fody" Version="3.3.1" PrivateAssets="All" />
<PackageReference Include="Fody" Version="6.6.1">
<PrivateAssets>All</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>
</Project>

45
docs/en/API/Auto-API-Controllers.md

@ -169,3 +169,48 @@ public class PersonAppService : ApplicationService
````
Disabled `IsMetadataEnabled` which hides this service from API explorer and it will not be discoverable. However, it still can be usable for the clients know the exact API path/route.
## Replace or Remove Controllers.
In addition to [Overriding a Controller](../Customizing-Application-Modules-Overriding-Services.md#example-overriding-a-controller), you can also use a completely independent **Controller** to replace the controller in the framework or module.
They have the same [route](https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/routing?view=aspnetcore-7.0), but can have **different** input and output parameters.
### Replace built-in AbpApplicationConfigurationController
The `ReplaceControllersAttribute` indicates the replaced controller type.
````csharp
[ReplaceControllers(typeof(AbpApplicationConfigurationController))]
[Area("abp")]
[RemoteService(Name = "abp")]
public class ReplaceBuiltInController : AbpController
{
[HttpGet("api/abp/application-configuration")]
public virtual Task<MyApplicationConfigurationDto> GetAsync(MyApplicationConfigurationRequestOptions options)
{
return Task.FromResult(new MyApplicationConfigurationDto());
}
}
public class MyApplicationConfigurationRequestOptions : ApplicationConfigurationRequestOptions
{
}
public class MyApplicationConfigurationDto : ApplicationConfigurationDto
{
}
````
### Remove contoller
Configure `ControllersToRemove` of `AbpAspNetCoreMvcOptions` to remove the controllers.
````csharp
services.Configure<AbpAspNetCoreMvcOptions>(options =>
{
options.ControllersToRemove.Add(typeof(AbpLanguagesController));
});
````

25
docs/en/API/Swagger-Integration.md

@ -43,7 +43,7 @@ If you want to manually install;
## Configuration
First, we need to use `AddAbpSwaggerGen` extension to configure Swagger in `ConfigureServices` method of our module.
First, we need to use `AddAbpSwaggerGen` extension to configure Swagger in `ConfigureServices` method of our module:
```csharp
public override void ConfigureServices(ServiceConfigurationContext context)
@ -63,21 +63,21 @@ public override void ConfigureServices(ServiceConfigurationContext context)
}
```
Then we can use Swagger UI by calling `UseAbpSwaggerUI` method in the `OnApplicationInitialization` method of our module.
Then we can use Swagger UI by calling `UseAbpSwaggerUI` method in the `OnApplicationInitialization` method of our module:
```csharp
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();
//... other configarations.
//... other configurations.
app.UseAbpSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/v1/swagger.json", "Test API");
});
//... other configarations.
//... other configurations.
}
```
@ -103,9 +103,7 @@ For non MVC/Tiered applications, we need to configure Swagger with OAUTH to hand
> ABP Framework uses OpenIddict by default. To get more information about OpenIddict, check this [documentation](../Modules/OpenIddict.md).
To do that, we need to use `AddAbpSwaggerGenWithOAuth` extension to configure Swagger with OAuth issuer and scopes in `ConfigureServices` method of our module.
To do that, we need to use `AddAbpSwaggerGenWithOAuth` extension to configure Swagger with OAuth issuer and scopes in `ConfigureServices` method of our module:
```csharp
public override void ConfigureServices(ServiceConfigurationContext context)
@ -130,17 +128,14 @@ public override void ConfigureServices(ServiceConfigurationContext context)
}
```
Then we can use Swagger UI by calling `UseAbpSwaggerUI` method in the `OnApplicationInitialization` method of our module.
> Do not forget to set `OAuthClientId` and `OAuthClientSecret`.
Then we can use Swagger UI by calling `UseAbpSwaggerUI` method in the `OnApplicationInitialization` method of our module:
```csharp
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();
//... other configarations.
//... other configurations.
app.UseAbpSwaggerUI(options =>
{
@ -151,6 +146,8 @@ public override void OnApplicationInitialization(ApplicationInitializationContex
options.OAuthClientSecret("1q2w3e*"); // clientSecret
});
//... other configarations.
//... other configurations.
}
```
```
> Do not forget to set `OAuthClientId` and `OAuthClientSecret`.

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

@ -52,7 +52,7 @@ After you have installed these NuGet packages, you need to configure your projec
var configuration = context.Services.GetConfiguration();
var hostingEnvironment = context.Services.GetHostingEnvironment();
//... other configarations.
//... other configurations.
ConfigureHangfire(context, configuration);
}
@ -68,7 +68,7 @@ After you have installed these NuGet packages, you need to configure your projec
> You have to configure a storage for Hangfire.
2. If you want to use hangfire's dashboard, you can add `UseHangfireDashboard` call in the `OnApplicationInitialization` method in `Module` class
2. If you want to use hangfire's dashboard, you can add `UseHangfireDashboard` call in the `OnApplicationInitialization` method in `Module` class:
````csharp
public override void OnApplicationInitialization(ApplicationInitializationContext context)
@ -84,7 +84,7 @@ After you have installed these NuGet packages, you need to configure your projec
### Specifying Queue
You can use the [`QueueAttribute`](https://docs.hangfire.io/en/latest/background-processing/configuring-queues.html) to specify the queue.
You can use the [`QueueAttribute`](https://docs.hangfire.io/en/latest/background-processing/configuring-queues.html) to specify the queue:
````csharp
using System.Threading.Tasks;

5
docs/en/CLI.md

@ -48,6 +48,7 @@ Here, is the list of all available commands before explaining their details:
* **`logout`**: Logouts from your computer if you've authenticated before.
* **`bundle`**: Generates script and style references for ABP Blazor and MAUI Blazor project.
* **`install-libs`**: Install NPM Packages for MVC / Razor Pages and Blazor Server UI types.
* **`clear-download-cache`** Clears the templates download cache.
### help
@ -165,6 +166,7 @@ For more samples, go to [ABP CLI Create Solution Samples](CLI-New-Command-Sample
* `--local-framework-ref --abp-path`: Uses local projects references to the ABP framework instead of using the NuGet packages. This can be useful if you download the ABP Framework source code and have a local reference to the framework from your application.
* `--no-random-port`: Uses template's default ports.
* `--skip-installing-libs` or `-sib`: Skip installing client side packages.
* `--skip-cache` or `-sc`: Always download the latest from our server and refresh their templates folder cache.
* `--with-public-website`: **Public Website** is a front-facing website for describing your project, listing your products and doing SEO for marketing purposes. Users can login and register on your website with this website.
See some [examples for the new command](CLI-New-Command-Samples.md) here.
@ -362,8 +364,11 @@ abp generate-proxy -t csharp -url https://localhost:44302/
* `--api-name` or `-a`: The name of the API endpoint defined in the `/src/environments/environment.ts`. Default value: `default`.
* `--source` or `-s`: Specifies the Angular project name to resolve the root namespace & API definition URL from. Default value: `defaultProject`.
* `--target`: Specifies the Angular project name to place generated code in. Default value: `defaultProject`.
* `--module`: Backend module name. Default value: `app`.
* `--entry-point`: Targets the Angular project to place the generated code.
* `--url`: Specifies api definition url. Default value is API Name's url in environment file.
* `--prompt` or `-p`: Asks the options from the command line prompt (for the unspecified options).
* `js`: JavaScript. work in the `*.Web` project directory. There are some additional options for this client:
* `--output` or `-o`: JavaScript file path or folder to place generated code in.
* `--module` or `-m`: Specifies the name of the backend module you wish to generate proxies for. Default value: `app`.

6
docs/en/Community-Articles/2022-11-14-How-to-add-a-custom-grant-type-in-OpenIddict/POST.md

@ -71,7 +71,13 @@ public class MyTokenExtensionGrant : ITokenExtensionGrant
var claimsPrincipal = await userClaimsPrincipalFactory.CreateAsync(user);
claimsPrincipal.SetScopes(principal.GetScopes());
claimsPrincipal.SetResources(await GetResourcesAsync(context, principal.GetScopes()));
//abp version < 7.3
await context.HttpContext.RequestServices.GetRequiredService<AbpOpenIddictClaimDestinationsManager>().SetAsync(principal);
//For abp version >= 7.3
await context.HttpContext.RequestServices.GetRequiredService<AbpOpenIddictClaimsPrincipalManager>().HandleAsync(context.Request, principal);
return new SignInResult(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, claimsPrincipal);
}

2
docs/en/Contribution/How-to-Contribute-abp.io-as-a-frontend-developer.md

@ -55,4 +55,4 @@ Your backend should be running successfully
There is a demo app. The path of the demo app is `npm\ng-packs\apps\dev-app`. The demo app is connected to the packages with local references. Open the terminal in `npm\ng-packs\apps\dev-app` and execute `yarn` or `npm i` in terminal. After the package installed run `npm start` or `yarn start`.
The repo uses Nx and packages connected with `local references`. The packages path is `npm\ng-packs\packages`
The repo uses Nx and packages connected with `local references`. The packages path is `npm\ng-packs\packages`

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

@ -185,6 +185,51 @@ If you perform **database operations** and use the [repositories](Repositories.m
> The handler class must be registered to the dependency injection (DI). The sample above uses the `ITransientDependency` to accomplish it. See the [DI document](Dependency-Injection.md) for more options.
## Monitoring Distributed Events
The ABP Framework allows you to stay informed when your application **receives** or **sends** a distributed event. This capability enables you to track the event flow within your application and take appropriate actions based on the received or sent distributed events.
### Received Events
The `DistributedEventReceived` local event is published when your application receives an event from the distributed event bus. `DistributedEventReceived` class has the following fields:
- **`Source`:** It represents the source of the distributed event. Source can be `Direct`, `Inbox`, `Outbox`.
- **`EventName`:** It represents the [name](#event-name) of the event received.
- **`EventData`:** It represents the actual data associated with the event received. Since it is of type `object`, it can hold any type of data.
**Example: Get informed when your application receives an event from the distributed event bus**
```csharp
public class DistributedEventReceivedHandler : ILocalEventHandler<DistributedEventReceived>, ITransientDependency
{
public async Task HandleEventAsync(DistributedEventReceived eventData)
{
// TODO: IMPLEMENT YOUR LOGIC...
}
}
```
### Sent Events
The `DistributedEventSent` local event is published when your application sends an event to the distributed event bus. `DistributedEventSent` class has the following fields:
- **`Source`:** It represents the source of the distributed event. Source can be `Direct`, `Inbox`, `Outbox`.
- **`EventName`:** It represents the [name](#event-name) of the event sent.
- **`EventData`:** It represents the actual data associated with the event sent. Since it is of type `object`, it can hold any type of data.
**Example: Get informed when your application sends an event to the distributed event bus**
```csharp
public class DistributedEventSentHandler : ILocalEventHandler<DistributedEventSent>, ITransientDependency
{
public async Task HandleEventAsync(DistributedEventSent eventData)
{
// TODO: IMPLEMENT YOUR LOGIC...
}
}
```
You can seamlessly integrate event-tracking capabilities into your application by subscribing to the `DistributedEventReceived` and `DistributedEventSent` local events as above examples. This empowers you to effectively monitor the messaging flow, diagnose any potential issues, and gain valuable insights into the behavior of your distributed messaging system.
## Pre-Defined Events

35
docs/en/Getting-Started-React-Native.md

@ -1,11 +1,11 @@
# Getting Started with the React Native
````json
```json
//[doc-params]
{
"Tiered": ["No", "Yes"]
"Tiered": ["No", "Yes"]
}
````
```
ABP platform provide basic [React Native](https://reactnative.dev/) startup template to develop mobile applications **integrated to your ABP based backends**.
@ -20,7 +20,6 @@ Please follow the steps below to prepare your development environment for React
3. **[Optional] Install VS Code:** [VS Code](https://code.visualstudio.com/) is a free, open-source IDE which works seamlessly with TypeScript. Although you can use any IDE including Visual Studio or Rider, VS Code will most likely deliver the best developer experience when it comes to React Native projects.
4. **Install an Emulator:** React Native applications need an Android emulator or an iOS simulator to run on your OS. See the [Android Studio Emulator](https://docs.expo.io/workflow/android-simulator/) or [iOS Simulator](https://docs.expo.io/workflow/ios-simulator/) on expo.io documentation to learn how to set up an emulator.
## How to Start a New React Native Project
You have multiple options to initiate a new React Native project that works with ABP:
@ -56,6 +55,7 @@ Please do the following:
> When you are using OpenIddict, You should remove 'clientSecret' on Environment.js (if exists) and disable "HTTPS-only" settings. (Openiddict has default since Version 6.0)
### How to disable Https-only in Openiddict.
You should add this code on {{ if Tiered == "No" }}`MyProjectNameHttpApiHostModule`{{ else if Tiered == "Yes" }}`MyProjectNameAuthServerModule`{{ end }}.
```csharp
@ -79,26 +79,27 @@ A React Native application running on an Android emulator or a physical phone **
{{ if Tiered == "No"}}
![React Native host project local IP entry](images/rn-host-local-ip.png)
* Open the `appsettings.json` in the `.HttpApi.Host` folder. Replace the `localhost` address on the `SelfUrl` and `Authority` properties with your local IP address.
* Open the `launchSettings.json` in the `.HttpApi.Host/Properties` folder. Replace the `localhost` address on the `applicationUrl` properties with your local IP address.
- Open the `appsettings.json` file in the `.HttpApi.Host` folder. Replace the `localhost` address on the `SelfUrl` and `Authority` properties with your local IP address.
- Open the `launchSettings.json` file in the `.HttpApi.Host/Properties` folder. Replace the `localhost` address on the `applicationUrl` properties with your local IP address.
{{ else if Tiered == "Yes" }}
![React Native tiered project local IP entry](images/rn-tiered-local-ip.png)
* Open the `appsettings.json` in the `.AuthServer` folder. Replace the `localhost` address on the `SelfUrl` property with your local IP address.
* Open the `launchSettings.json` in the `.AuthServer/Properties` folder. Replace the `localhost` address on the `applicationUrl` properties with your local IP address.
* Open the `appsettings.json` in the `.HttpApi.Host` folder. Replace the `localhost` address on the `Authority` property with your local IP address.
* Open the `launchSettings.json` in the `.HttpApi.Host/Properties` folder. Replace the `localhost` address on the `applicationUrl` properties with your local IP address.
- Open the `appsettings.json` file in the `.AuthServer` folder. Replace the `localhost` address on the `SelfUrl` property with your local IP address.
- Open the `launchSettings.json` file in the `.AuthServer/Properties` folder. Replace the `localhost` address on the `applicationUrl` properties with your local IP address.
- Open the `appsettings.json` file in the `.HttpApi.Host` folder. Replace the `localhost` address on the `Authority` property with your local IP address.
- Open the `launchSettings.json` file in the `.HttpApi.Host/Properties` folder. Replace the `localhost` address on the `applicationUrl` properties with your local IP address.
{{ end }}
Run the backend application as described in the [getting started document](Getting-Started.md).
> You should turn off the "Https Restriction" if you're using OpenIddict as a central identity management solution. Because the IOS Simulator doesn't support self-signed certificates and OpenIddict is set to only work with HTTPS by default.
## How to disable the Https-only settings of OpenIddict
Go to MyProjectNameHttpApiHostModule.cs under the host project. Add put these codes under the `PreConfigureServices` function.
Go to MyProjectNameHttpApiHostModule.cs under the host project. And put these codes under the `PreConfigureServices` function.
```csharp
#if DEBUG
@ -109,7 +110,6 @@ Run the backend application as described in the [getting started document](Getti
#endif
```
## How to Configure & Run the React Native Application
1. Make sure the [database migration is complete](./Getting-Started?UI=NG&DB=EF&Tiered=No#create-the-database) and the [API is up and running](./Getting-Started?UI=NG&DB=EF&Tiered=No#run-the-application).
@ -128,21 +128,20 @@ Run the backend application as described in the [getting started document](Getti
{{ end }}
4. Run `yarn start` or `npm start`. Wait Expo CLI to start. Expo CLI opens the management interface on the `http://localhost:19002/` address.
4. Run `yarn start` or `npm start`. Wait for the Expo CLI to print the opitons.
> The React Native application was generated with [Expo](https://expo.io/). Expo is a set of tools built around React Native to help you quickly start an app and, while it has many features.
![expo-interface](images/rn-expo-interface.png)
![expo-cli-options](images/rn-options.png)
In the above management interface, you can start the application with an Android emulator, an iOS simulator or a physical phone by the scan the QR code with the [Expo Client](https://expo.io/tools#client).
In the above image, you can start the application with an Android emulator, an iOS simulator or a physical phone by scanning the QR code with the [Expo Client](https://expo.io/tools#client) or choosing the option.
![React Native login screen on iPhone 11](images/rn-login-iphone.png)
Enter **admin** as the username and **1q2w3E*** as the password to login to the application.
Enter **admin** as the username and **1q2w3E\*** as the password to login to the application.
The application is up and running. You can continue to develop your application based on this startup template.
## See Also
* [React Native project structure](./Startup-Templates/Application#react-native)
- [React Native project structure](./Startup-Templates/Application#react-native)

380
docs/en/Image-Manipulation.md

@ -0,0 +1,380 @@
# Image Manipulation
ABP Framework provides services to compress and resize images and implements these services with popular [ImageSharp](https://sixlabors.com/products/imagesharp/) and [Magick.NET](https://github.com/dlemstra/Magick.NET) libraries. You can use these services in your reusable modules, libraries and applications, so you don't depend on a specific imaging library.
> The image resizer/compressor system is designed to be extensible. You can implement your own image resizer/compressor contributor and use it in your application.
## Installation
You can add this package to your application by either using the [ABP CLI](CLI.md) or manually installing it. Using the [ABP CLI](CLI.md) is the recommended approach.
### Using the ABP CLI
Open a command line terminal in the folder of your project (.csproj file) and type the following command:
```bash
abp add-package Volo.Abp.Imaging.Abstractions
```
### Manual Installation
If you want to manually install;
1. Add the [Volo.Abp.Imaging.Abstractions](https://www.nuget.org/packages/Volo.Abp.Imaging.Abstractions) NuGet package to your project:
```
Install-Package Volo.Abp.Imaging.Abstractions
```
2. Add the `AbpImagingAbstractionsModule` to the dependency list of your module:
```csharp
[DependsOn(
//...other dependencies
typeof(AbpImagingAbstractionsModule) //Add the new module dependency
)]
public class YourModule : AbpModule
{
}
```
## Providers
ABP Framework provides two image resizer/compressor implementations out of the box:
* [Magick.NET](#magicknet-provider)
* [ImageSharp](#imagesharp-provider)
You should install one of these provides to make it actually working.
> If none of the provider packages installed into your application, compress/resize operations return the untouched input image.
## IImageResizer
You can [inject](Dependency-Injection.md) the `IImageResizer` service and use it for image resize operations. Here is the available methods of the `IImageResizer` service:
```csharp
public interface IImageResizer
{
/* Works with a Stream object that represents an image */
Task<ImageResizeResult<Stream>> ResizeAsync(
Stream stream,
ImageResizeArgs resizeArgs,
string mimeType = null,
CancellationToken cancellationToken = default
);
/* Works with a byte array that contains an image file */
Task<ImageResizeResult<byte[]>> ResizeAsync(
byte[] bytes,
ImageResizeArgs resizeArgs,
string mimeType = null,
CancellationToken cancellationToken = default
);
}
```
**Example usage:**
```csharp
var result = await _imageResizer.ResizeAsync(
stream, /* A stream object that represents an image */
new ImageResizeArgs
{
Width = 100,
Height = 100,
Mode = ImageResizeMode.Crop
},
mimeType: "image/jpeg"
);
```
> You can use `MimeTypes.Image.Jpeg` constant instead of the `image/jpeg` magic string used in that example.
### ImageResizeArgs
The `ImageResizeArgs` is a class that is used to define the resize operation parameters. It has the following properties:
* `Width`: The width of the resized image.
* `Height`: The height of the resized image.
* `Mode`: The resize mode (see the [ImageResizeMode](#imageresizemode) section for more information).
### ImageResizeMode
The `ImageResizeMode` is an enum that is used to define the resize mode. It has the following values:
```csharp
public enum ImageResizeMode : byte
{
None = 0,
Stretch = 1,
BoxPad = 2,
Min = 3,
Max = 4,
Crop = 5,
Pad = 6,
Default = 7
}
```
> See the [ImageSharp documentation](https://docs.sixlabors.com/api/ImageSharp/SixLabors.ImageSharp.Processing.ResizeMode.html) for more information about the resize modes.
### ImageResizeResult
The `ImageResizeResult` is a generic class that is used to return the result of the image resize operations. It has the following properties:
* `Result`: The resized image (stream or byte array).
* `State`: The result of the resize operation (type: `ImageProcessState`).
### ImageProcessState
The `ImageProcessState` is an enum that is used to return the the result of the image resize operations. It has the following values:
```csharp
public enum ImageProcessState : byte
{
Done = 1,
Canceled = 2,
Unsupported = 3,
}
```
### ImageResizeOptions
`ImageResizeOptions` is an [options object](Options.md) that is used to configure the image resize system. It has the following properties:
* `DefaultResizeMode`: The default resize mode. (Default: `ImageResizeMode.None`)
## IImageCompressor
You can [inject](Dependency-Injection.md) the `IImageCompressor` service and use it for image compression operations. Here is the available methods of the `IImageCompressor` service:
```csharp
public interface IImageCompressor
{
/* Works with a Stream object that represents an image */
Task<ImageCompressResult<Stream>> CompressAsync(
Stream stream,
string mimeType = null,
CancellationToken cancellationToken = default
);
/* Works with a byte array that contains an image file */
Task<ImageCompressResult<byte[]>> CompressAsync(
byte[] bytes,
string mimeType = null,
CancellationToken cancellationToken = default
);
}
```
**Example usage:**
```csharp
var result = await _imageCompressor.CompressAsync(
stream, /* A stream object that represents an image */
mimeType: "image/jpeg"
);
```
### ImageCompressResult
The `ImageCompressResult` is a generic class that is used to return the result of the image compression operations. It has the following properties:
* `Result`: The compressed image (stream or byte array).
* `State`: The result of the compress operation (type: `ImageProcessState`).
### ImageProcessState
The `ImageProcessState` is an enum that is used to return the the result of the image compress operations. It has the following values:
```csharp
public enum ImageProcessState : byte
{
Done = 1,
Canceled = 2,
Unsupported = 3,
}
```
## Magick.NET Provider
`Volo.Abp.Imaging.MagickNet` NuGet package implements the image operations using the [Magick.NET](https://github.com/dlemstra/Magick.NET) library.
## Installation
You can add this package to your application by either using the [ABP CLI](CLI.md) or manually installing it. Using the [ABP CLI](CLI.md) is the recommended approach.
### Using the ABP CLI
Open a command line terminal in the folder of your project (.csproj file) and type the following command:
```bash
abp add-package Volo.Abp.Imaging.MagickNet
```
### Manual Installation
If you want to manually install;
1. Add the [Volo.Abp.Imaging.MagickNet](https://www.nuget.org/packages/Volo.Abp.Imaging.MagickNet) NuGet package to your project:
```
Install-Package Volo.Abp.Imaging.MagickNet
```
2. Add `AbpImagingMagickNetModule` to your [module](Module-Development-Basics.md)'s dependency list:
```csharp
[DependsOn(typeof(AbpImagingMagickNetModule))]
public class MyModule : AbpModule
{
//...
}
```
### Configuration
`MagickNetCompressOptions` is an [options object](Options.md) that is used to configure the Magick.NET image compression system. It has the following properties:
* `OptimalCompression`: Indicates whether the optimal compression is enabled or not. (Default: `false`)
* `IgnoreUnsupportedFormats`: Indicates whether the unsupported formats are ignored or not. (Default: `false`)
* `Lossless`: Indicates whether the lossless compression is enabled or not. (Default: `false`)
## ImageSharp Provider
`Volo.Abp.Imaging.ImageSharp` NuGet package implements the image operations using the [ImageSharp](https://github.com/SixLabors/ImageSharp) library.
## Installation
You can add this package to your application by either using the [ABP CLI](CLI.md) or manually installing it. Using the [ABP CLI](CLI.md) is the recommended approach.
### Using the ABP CLI
Open a command line terminal in the folder of your project (.csproj file) and type the following command:
```bash
abp add-package Volo.Abp.Imaging.ImageSharp
```
### Manual Installation
If you want to manually install;
1. Add the [Volo.Abp.Imaging.ImageSharp](https://www.nuget.org/packages/Volo.Abp.Imaging.ImageSharp) NuGet package to your project:
```
Install-Package Volo.Abp.Imaging.ImageSharp
```
2. Add `AbpImagingImageSharpModule` to your [module](Module-Development-Basics.md)'s dependency list:
```csharp
[DependsOn(typeof(AbpImagingImageSharpModule))]
public class MyModule : AbpModule
{
//...
}
```
### Configuration
`ImageSharpCompressOptions` is an [options object](Options.md) that is used to configure the ImageSharp image compression system. It has the following properties:
* `DefaultQuality`: The default quality of the JPEG and WebP encoders. (Default: `75`)
* [`JpegEncoder`](https://docs.sixlabors.com/api/ImageSharp/SixLabors.ImageSharp.Formats.Jpeg.JpegEncoder.html): The JPEG encoder. (Default: `JpegEncoder` with `Quality` set to `DefaultQuality`)
* [`PngEncoder`](https://docs.sixlabors.com/api/ImageSharp/SixLabors.ImageSharp.Formats.Png.PngEncoder.html): The PNG encoder. (Default: `PngEncoder` with `IgnoreMetadata` set to `true` and `CompressionLevel` set to `PngCompressionLevel.BestCompression`)
* [`WebPEncoder`](https://docs.sixlabors.com/api/ImageSharp/SixLabors.ImageSharp.Formats.Webp.WebpEncoder.html): The WebP encoder. (Default: `WebPEncoder` with `Quality` set to `DefaultQuality`)
**Example usage:**
```csharp
Configure<ImageSharpCompressOptions>(options =>
{
options.JpegEncoder = new JpegEncoder
{
Quality = 60
};
options.PngEncoder = new PngEncoder
{
CompressionLevel = PngCompressionLevel.BestCompression
};
options.WebPEncoder = new WebPEncoder
{
Quality = 65
};
});
```
## ASP.NET Core Integration
`Volo.Abp.Imaging.AspNetCore` NuGet package defines attributes for controller actions that can automatically compress and/or resize uploaded files.
## Installation
You can add this package to your application by either using the [ABP CLI](CLI.md) or manually installing it. Using the [ABP CLI](CLI.md) is the recommended approach.
### Using the ABP CLI
Open a command line terminal in the folder of your project (.csproj file) and type the following command:
```bash
abp add-package Volo.Abp.Imaging.AspNetCore
```
### Manual Installation
If you want to manually install;
1. Add the [Volo.Abp.Imaging.AspNetCore](https://www.nuget.org/packages/Volo.Abp.Imaging.AspNetCore) NuGet package to your project:
```
Install-Package Volo.Abp.Imaging.AspNetCore
```
2. Add `AbpImagingAspNetCoreModule` to your [module](Module-Development-Basics.md)'s dependency list:
```csharp
[DependsOn(typeof(AbpImagingAspNetCoreModule))]
public class MyModule : AbpModule
{
//...
}
```
### CompressImageAttribute
The `CompressImageAttribute` is used to compress the image before. `IFormFile`, `IRemoteStreamContent`, `Stream` and `IEnumrable<byte>` types are supported. It has the following properties:
* `Parameters`: Names of the the parameters that are used to configure the image compression system. This is useful if your action has some non-image parameters. If you don't specify the parameters names, all of the method parameters are considered as image.
**Example usage:**
```csharp
[HttpPost]
[CompressImage] /* Compresses the given file (automatically determines the file mime type) */
public async Task<IActionResult> Upload(IFormFile file)
{
//...
}
```
### ResizeImageAttribute
The `ResizeImageAttribute` is used to resize the image before requesting the action. `IFormFile`, `IRemoteStreamContent`, `Stream` and `IEnumrable<byte>` types are supported. It has the following properties:
* `Parameters`: Names of the the parameters that are used to configure the image resize system. This is useful if your action has some non-image parameters. If you don't specify the parameters names, all of the method parameters are considered as image.
* `Width`: Target width of the resized image.
* `Height`: Target height of the resized image.
* `Mode`: The resize mode (see the [ImageResizeMode](#imageresizemode) section for more information).
**Example usage:**
```csharp
[HttpPost]
[ResizeImage(Width = 100, Height = 100, Mode = ImageResizeMode.Crop)]
public async Task<IActionResult> Upload(IFormFile file)
{
//...
}
```

33
docs/en/KB/Windows-Path-Too-Long-Fix.md

@ -1,10 +1,39 @@
# How to Fix "Filename too long" Error on Windows
If you encounter the "filename too long" or "unzip" error on Windows, it's probably related to the Windows maximum file path limitation. Windows has a maximum file path limitation of 250 characters. To solve this, [enable the long path option in Windows 10](https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=cmd#enable-long-paths-in-windows-10-version-1607-and-later).
If you encounter the "filename too long" or "unzip" error on Windows, it's probably related to the Windows maximum file path limitation. Windows has a maximum file path limitation of 255 characters.
## Solution 1
Try [enabling the long path option in Windows 10](https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=cmd#enable-long-paths-in-windows-10-version-1607-and-later).
If you face long path errors related to Git, try the following command to enable long paths in Windows.
```
git config --system core.longpaths true
```
See https://github.com/msysgit/msysgit/wiki/Git-cannot-create-a-file-or-directory-with-a-long-path
See https://github.com/msysgit/msysgit/wiki/Git-cannot-create-a-file-or-directory-with-a-long-path
## Solution 2
You may encounter a "DirectoryNotFoundException - Could not find a part of the path" exception in Windows while using certain .NET MAUI build tools. This is related to some 32 bit .NET MAUI build tools. To resolve this issue, you can try placing the solution in the root directory of your drive, such as `C:\Projects\`. However, please note that this solution is specific to this particular exception and may not be applicable to all cases of the Windows long path issue.
## Solution 3
You can define an alias for a path in Windows by creating a symbolic link using the `mklink` command in the command prompt. Here's an example:
```
mklink /D C:\MyProject C:\my\long\path\to\solution\
```
> Your **solution (.sln)** file should be in `C:\my\long\path\to\solution\`. Keep in mind that, if you have relative paths in your .csproj file, it will not work!
This command creates a symbolic link named `MyProject` in the root of the `C:` drive that points to the `C:\my\long\path\to\solution\` directory. You can then use `C:\MyProject` to access the contents of the `C:\my\long\path\to\solution\` directory.
> Note that you need to run the command prompt as an administrator to the create symbolic links.
Then you can try building your project with `dotnet build` command.
```
dotnet build C:\MyProject\MyProjectName.sln
```

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

@ -149,7 +149,7 @@ namespace AbpDemo
}
````
That's all. `MyHandler` is **automatically discovered** by the ABP Framework and `HandleEventAsync` is called whenever a `StockCountChangedEvent` occurs. You can inject any service and perform any required logic in your handler class.
That's all. `MyHandler` is **automatically discovered** by the ABP Framework and `HandleEventAsync` is called whenever a `StockCountChangedEvent` occurs. You can inject any service and perform any required logic in your handler class.
* **One or more handlers** can subscribe to the same event.
* A single event handler class can **subscribe to multiple events** by implementing the `ILocalEventHandler<TEvent>` interface for each event type.
@ -158,6 +158,29 @@ If you perform **database operations** and use the [repositories](Repositories.m
> The handler class must be registered to the dependency injection (DI). The sample above uses the `ITransientDependency` to accomplish it. See the [DI document](Dependency-Injection.md) for more options.
### LocalEventHandlerOrder Attribute
`LocalEventHandlerOrder` attribute can be used to set the execution order for the event handlers, which can be helpful if you want to handle your event handlers in a specific order.
````csharp
[LocalEventHandlerOrder(-1)]
public class MyHandler
: ILocalEventHandler<StockCountChangedEvent>,
ITransientDependency
{
public async Task HandleEventAsync(StockCountChangedEvent eventData)
{
//TODO: your code that does something on the event
}
}
````
> By default, all event handlers have an order value of 0. Thus, if you want to take certain event handlers to be executed before other event handlers, you can set the order value as a negative value.
#### LocalEventHandlerOrderAttribute Properties
* `Order` (`int`): Used to set the execution order for a certain event handler.
### Transaction & Exception Behavior
Event handlers are always executed in the same [unit of work](Unit-Of-Work.md) scope, that means in the same database transaction with the code that published the event. If an event handler throws an exception, the unit of work (database transaction) is rolled back. So, **use try-catch yourself** in the event handler if you want to hide the error.
@ -205,7 +228,7 @@ The pre-built event types are;
* `EntityDeletedEventData<T>` is published just after an entity was successfully deleted.
* `EntityChangedEventData<T>` is published just after an entity was successfully created, updated or deleted. It can be a shortcut if you need to listen any type of change - instead of subscribing to the individual events.
#### How It Was Implemented?
### How It Was Implemented?
Pre-build events are published when you save changes to the database;

12
docs/en/Modules/OpenIddict.md

@ -323,16 +323,16 @@ Configure<TokenCleanupOptions>(options =>
[Claims Principal Factory](https://docs.abp.io/en/abp/latest/Authorization#claims-principal-factory) can be used to add/remove claims to the `ClaimsPrincipal`.
The `AbpDefaultOpenIddictClaimDestinationsProvider` service will add `Name`, `Email,` and `Role` types of Claims to `access_token` and `id_token`, other claims are only added to `access_token` by default, and remove the `SecurityStampClaimType` secret claim of `Identity`.
The `AbpDefaultOpenIddictClaimsPrincipalHandler` service will add `Name`, `Email,` and `Role` types of Claims to `access_token` and `id_token`, other claims are only added to `access_token` by default, and remove the `SecurityStampClaimType` secret claim of `Identity`.
Create a service that inherits from `IAbpOpenIddictClaimDestinationsProvider` and add it to DI to fully control the destinations of claims.
Create a service that inherits from `IAbpOpenIddictClaimsPrincipalHandler` and add it to DI to fully control the destinations of claims.
```cs
public class MyClaimDestinationsProvider : IAbpOpenIddictClaimDestinationsProvider, ITransientDependency
public class MyClaimDestinationsHandler : IAbpOpenIddictClaimsPrincipalHandler, ITransientDependency
{
public virtual Task SetDestinationsAsync(AbpOpenIddictClaimDestinationsProviderContext context)
public virtual Task HandleAsync(AbpOpenIddictClaimsPrincipalHandlerContext context)
{
foreach (var claim in context.Claims)
foreach (var claim in context.Principal.Claims)
{
if (claim.Type == MyClaims.MyClaimsType)
{
@ -351,7 +351,7 @@ public class MyClaimDestinationsProvider : IAbpOpenIddictClaimDestinationsProvid
Configure<AbpOpenIddictClaimDestinationsOptions>(options =>
{
options.ClaimDestinationsProvider.Add<MyClaimDestinationsProvider>();
options.ClaimsPrincipalHandlers.Add<MyClaimDestinationsHandler>();
});
```

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

@ -261,6 +261,19 @@ You should run the application with the given order:
* Then run the `.HttpApi.Host` since it is used by the `.Web` application.
* Finally, you can run the `.Web` project and login to the application (using `admin` as the username and `1q2w3E*` as the password).
### Blazor UI
If you choose `Blazor` as the UI Framework (using the `-u blazor` or `-u blazor-server` option), the solution will have a project named `.Blazor`. This project contains the Blazor UI application. According to your choice, it will be a Blazor WebAssembly or Blazor Server application. If Blazor WebAssembly is selected, the solution will also have a `.HttpApi.Host`. This project is an ASP.NET Core application that hosts the backend application for the Blazor single page application.
#### .Blazor Project (Server)
The Blazor Server project is similar to the ASP.NET Core MVC project. It replaces `.Web` project with `.Blazor` in the solution structure above. It has the same folder structure and the same application flow. Since it's an ASP.NET Core application, it can contain **.cshtml** files and **.razor** components at the same time. If routing matches a razor component, the Blazor UI will be used. Otherwise, the request will be handled by the MVC framework.
![abp solution structure blazor server](../images/layered-project-dependencies-blazor-server.png)
#### .Blazor Project (WebAssembly)
The Blazor WebAssembly project is a single page application that runs on the browser. You'll see it as `.Blazor` project in the solution. It uses the `.HttpApi.Host` project to communicate with the backend. It can't be used without the backend application. It contains only **.razor** components. It's a pure client-side application. It doesn't have any server-side code. Everything in this layer will be for the client side.
![abp solution structure blazor wasm](../images/layered-project-dependencies-blazor-wasm.png)
### Angular UI
If you choose `Angular` as the UI framework (using the `-u angular` option), the solution is being separated into two folders:

2
docs/en/UI/Angular/Card-Component.md

@ -29,7 +29,7 @@ ABP Card Component is a part of the `ThemeSharedModule` module. If you've import
// my-feature.module.ts
import { ThemeSharedModule } from '@abp/ng.theme.shared';
import { CardDemoComponent } from './chart-demo.component';
import { CardDemoComponent } from './card-demo.component';
@NgModule({
imports: [

51
docs/en/UI/Angular/Checkbox-Component.md

@ -0,0 +1,51 @@
# Checkbox Component
The ABP Checkbox Component is a reusable form input component for the checkbox type.
# Inputs
- `label`
- `labelClass (default form-check-label)`
- `checkboxId`
- `checkboxReadonly`
- `checkboxReadonly (default form-check-input)`
- `checkboxStyle`
# Outputs
- `checkboxBlur`
- `checkboxFocus`
# Usage
The ABP Checkbox component is a part of the `ThemeSharedModule` module. If you've imported that module into your module, there's no need to import it again. If not, then first import it as shown below:
```ts
// my-feature.module.ts
import { ThemeSharedModule } from "@abp/ng.theme.shared";
import { CheckboxDemoComponent } from "./CheckboxDemoComponent.component";
@NgModule({
imports: [
ThemeSharedModule,
// ...
],
declarations: [CheckboxDemoComponent],
// ...
})
export class MyFeatureModule {}
```
Then, the `abp-checkbox` component can be used. See the example below:
```html
<div class="form-check">
<abp-checkbox label="Yes,I Agree" checkboxId="checkbox-input">
</abp-checkbox>
</div>
```
See the checkbox input result below:
![abp-checkbox](./images/form-checkbox.png)

49
docs/en/UI/Angular/FormInput-Component.md

@ -0,0 +1,49 @@
# Form Input Component
The ABP FormInput Component is a reusable form input component for the text type.
# Inputs
* `label`
* `labelClass (default form-label)`
* `inputPlaceholder`
* `inputReadonly`
* `inputClass (default form-control)`
# Outputs
* `formBlur`
* `formFocus`
# Usage
The ABP FormInput component is a part of the `ThemeSharedModule` module. If you've imported that module into your module, there's no need to import it again. If not, then first import it as shown below:
```ts
import { ThemeSharedModule } from "@abp/ng.theme.shared";
import { FormInputDemoComponent } from "./FomrInputDemoComponent.component";
@NgModule({
imports: [
ThemeSharedModule,
// ...
],
declarations: [FormInputDemoComponent],
})
export class MyFeatureModule {}
```
Then, the `abp-form-input` component can be used. See the example below:
```html
<div class="row">
<div class="col-4">
<abp-form-input
label="AbpAccount::UserNameOrEmailAddress"
inputId="login-input-user-name-or-email-address"
></abp-form-input>
</div>
</div>
```
See the form input result below:
![abp-form-input](./images/form-input.png)

BIN
docs/en/UI/Angular/images/form-checkbox.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
docs/en/UI/Angular/images/form-input.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

33
docs/en/UI/AspNetCore/Security-Headers.md

@ -19,7 +19,7 @@ ABP Framework allows you to add frequently used security headers into your appli
Configure<AbpSecurityHeadersOptions>(options =>
{
options.UseContentSecurityPolicyHeader = true; //false by default
options.ContentSecurityPolicyValue = "object-src 'none'; form-action 'self'; frame-ancestors 'none'";
options.ContentSecurityPolicyValue = "object-src 'none'; form-action 'self'; frame-ancestors 'none'"; //default value
//adding additional security headers
options.Headers["Referrer-Policy"] = "no-referrer";
@ -43,3 +43,34 @@ app.UseAbpSecurityHeaders();
After that, you have registered the `UseAbpSecurityHeaders` middleware into the request pipeline, the defined security headers will be shown in the response headers as in the figure below:
![](../../images/security-response-headers.png)
## Content Security Policy Script Nonce
Abp Framework provides a property to add a dynamic script-src nonce value to the Content-Security-Policy header. With this feature, it automatically adds a dynamic nonce value to the header side. And with the help of the script tag helper, it adds this [`script nonce`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/nonce) value to the script tags on your pages(The `ScriptNonceTagHelper` in the `Volo.Abp.AspNetCore.Mvc.UI.Bundling` namespace must be attached as a taghelper.).
> If you need to add the nonce script manually, you can use 'Html.GetScriptNonce()' to add the nonce value or 'Html.GetScriptNonceAttribute()' to add the nonce attribute value.
This feature is disabled by default. You can enable it by setting the `UseContentSecurityPolicyScriptNonce` property of the `AbpSecurityHeadersOptions` class to `true`.
### Ignore Script Nonce
You can ignore the script nonce for some pages or some selectors. You can use the `IgnoredScriptNoncePaths` and `IgnoredScriptNonceSelectors` properties of the `AbpSecurityHeadersOptions` class.
**Example:**
```csharp
Configure<AbpSecurityHeadersOptions>(options =>
{
//adding script-src nonce
options.UseContentSecurityPolicyScriptNonce = true; //false by default
//ignore script nonce source for these paths
options.IgnoredScriptNoncePaths.Add("/my-page");
//ignore script nonce by Elsa Workflows and other selectors
options.IgnoredScriptNonceSelectors.Add(context =>
{
var endpoint = context.GetEndpoint();
return Task.FromResult(endpoint?.Metadata.GetMetadata<PageRouteMetadata>()?.RouteTemplate == "/{YOURHOSTPAGE}");
});
});
```

4
docs/en/UI/AspNetCore/Tag-Helpers/Dropdowns.md

@ -70,8 +70,8 @@ Basic usage:
A value indicates which direction `abp-dropdown-menu` items will be aligned to. Should be one of the following values:
* `Left` (default value)
* `Right`
* `Start` (default value)
* `End`
### Additional content

8
docs/en/docs-nav.json

@ -417,6 +417,10 @@
"text": "GUID Generation",
"path": "Guid-Generation.md"
},
{
"text": "Image Manipulation",
"path": "Image-Manipulation.md"
},
{
"text": "JSON",
"path": "JSON.md"
@ -1351,6 +1355,10 @@
"text": "Deployment",
"path": "Deployment/Index.md",
"items": [
{
"text": "Configuring OpenIddict",
"path": "Deployment/Configuring-OpenIddict.md"
},
{
"text": "Configuring for Production",
"path": "Deployment/Configuring-Production.md"

BIN
docs/en/images/layered-project-dependencies-blazor-server.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

BIN
docs/en/images/layered-project-dependencies-blazor-wasm.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

BIN
docs/en/images/rn-options.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

2
docs/zh-Hans/CLI.md

@ -44,6 +44,7 @@ dotnet tool update -g Volo.Abp.Cli
* **`logout`**: 在你的计算机注销认证.
* **`bundle`**: 为 ABP Blazor 和 MAUI Blazor 项目生成引用的脚本和样式.
* **`install-libs`**: 为 MVC / Razor Pages 和 Blazor Server UI 类型安装NPM包.
* **`clear-download-cache`** 删除下载的模版缓存.
### help
@ -142,6 +143,7 @@ abp new Acme.BookStore
* `--create-solution-folder` 或者 `-csf`: 指定项目是在输出文件夹中的新文件夹中还是直接在输出文件夹中.
* `--connection-string` 或者 `-cs`: 重写所有 `appsettings.json` 文件的默认连接字符串. 默认连接字符串是 `Server=localhost;Database=MyProjectName;Trusted_Connection=True`. 默认的数据库提供程序是 `SQL Server`. 如果你使用EF Core但需要更改DBMS,可以按[这里所述](Entity-Framework-Core-Other-DBMS.md)进行更改(创建解决方案之后).
* `--local-framework-ref --abp-path`: 使用对项目的本地引用,而不是替换为NuGet包引用.
* `--skip-cache` or `-sc`: 从服务器下载最新的模版并更新本地模版缓存.
### update

2
docs/zh-Hans/Deployment/Clustered-Environment.md

@ -1,4 +1,4 @@
# 部署到集环境
# 部署到集环境
本文档介绍了在将应用程序部署到**多个应用程序实例同时运行**的集群环境中时应注意的内容, 并解释了如何在基于ABP的应用程序中处理这些内容.

2
docs/zh-Hans/Deployment/Index.md

@ -6,4 +6,4 @@
## 指南
* [部署到集环境](Clustered-Environment.md): 讲解了当你希望同时运行应用程序的多个实例时, 如何来配置应用程序.
* [部署到集环境](Clustered-Environment.md): 讲解了当你希望同时运行应用程序的多个实例时, 如何来配置应用程序.

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

@ -715,7 +715,7 @@
"path": "Deployment/Index.md",
"items": [
{
"text": "部署到集环境",
"text": "部署到集环境",
"path": "Deployment/Clustered-Environment.md"
}
]

70
framework/Volo.Abp.sln

@ -437,6 +437,26 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AspNetCore.Compone
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Ldap.Abstractions", "src\Volo.Abp.Ldap.Abstractions\Volo.Abp.Ldap.Abstractions.csproj", "{0F80E95C-41E6-4F23-94FF-FC9D0B8D5D71}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Ddd.Domain.Shared", "src\Volo.Abp.Ddd.Domain.Shared\Volo.Abp.Ddd.Domain.Shared.csproj", "{0858571B-CE73-4AD6-BD06-EC9F0714D8E9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.MultiTenancy.Abstractions", "src\Volo.Abp.MultiTenancy.Abstractions\Volo.Abp.MultiTenancy.Abstractions.csproj", "{86F3684C-A0A5-4943-8CFA-AE79E8E3E315}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Imaging.Abstractions", "src\Volo.Abp.Imaging.Abstractions\Volo.Abp.Imaging.Abstractions.csproj", "{32F3E84B-D02E-42BD-BC5C-0D211564EF30}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Imaging.AspNetCore", "src\Volo.Abp.Imaging.AspNetCore\Volo.Abp.Imaging.AspNetCore.csproj", "{78340A37-219E-4F2D-9AC6-40A7B467EEEC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Imaging.ImageSharp", "src\Volo.Abp.Imaging.ImageSharp\Volo.Abp.Imaging.ImageSharp.csproj", "{44467427-E0BE-492C-B9B4-82B362C183C3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Imaging.MagickNet", "src\Volo.Abp.Imaging.MagickNet\Volo.Abp.Imaging.MagickNet.csproj", "{F701EDA5-D7EA-4AA7-9C57-83ED50CE72EC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Imaging.Abstractions.Tests", "test\Volo.Abp.Imaging.Abstractions.Tests\Volo.Abp.Imaging.Abstractions.Tests.csproj", "{2BE6BDC7-A9A3-4E30-9099-A9EF4813F6FF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Imaging.ImageSharp.Tests", "test\Volo.Abp.Imaging.ImageSharp.Tests\Volo.Abp.Imaging.ImageSharp.Tests.csproj", "{1E161A34-10C1-46FA-9EFD-10DD0858A8F5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Imaging.MagickNet.Tests", "test\Volo.Abp.Imaging.MagickNet.Tests\Volo.Abp.Imaging.MagickNet.Tests.csproj", "{62B2B8C9-8F24-4D31-894F-C1F0728D32AB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Imaging.AspNetCore.Tests", "test\Volo.Abp.Imaging.AspNetCore.Tests\Volo.Abp.Imaging.AspNetCore.Tests.csproj", "{983B0136-384B-4439-B374-31111FFAA286}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -1303,6 +1323,46 @@ Global
{0F80E95C-41E6-4F23-94FF-FC9D0B8D5D71}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0F80E95C-41E6-4F23-94FF-FC9D0B8D5D71}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0F80E95C-41E6-4F23-94FF-FC9D0B8D5D71}.Release|Any CPU.Build.0 = Release|Any CPU
{0858571B-CE73-4AD6-BD06-EC9F0714D8E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0858571B-CE73-4AD6-BD06-EC9F0714D8E9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0858571B-CE73-4AD6-BD06-EC9F0714D8E9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0858571B-CE73-4AD6-BD06-EC9F0714D8E9}.Release|Any CPU.Build.0 = Release|Any CPU
{86F3684C-A0A5-4943-8CFA-AE79E8E3E315}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{86F3684C-A0A5-4943-8CFA-AE79E8E3E315}.Debug|Any CPU.Build.0 = Debug|Any CPU
{86F3684C-A0A5-4943-8CFA-AE79E8E3E315}.Release|Any CPU.ActiveCfg = Release|Any CPU
{86F3684C-A0A5-4943-8CFA-AE79E8E3E315}.Release|Any CPU.Build.0 = Release|Any CPU
{32F3E84B-D02E-42BD-BC5C-0D211564EF30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{32F3E84B-D02E-42BD-BC5C-0D211564EF30}.Debug|Any CPU.Build.0 = Debug|Any CPU
{32F3E84B-D02E-42BD-BC5C-0D211564EF30}.Release|Any CPU.ActiveCfg = Release|Any CPU
{32F3E84B-D02E-42BD-BC5C-0D211564EF30}.Release|Any CPU.Build.0 = Release|Any CPU
{78340A37-219E-4F2D-9AC6-40A7B467EEEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{78340A37-219E-4F2D-9AC6-40A7B467EEEC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{78340A37-219E-4F2D-9AC6-40A7B467EEEC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{78340A37-219E-4F2D-9AC6-40A7B467EEEC}.Release|Any CPU.Build.0 = Release|Any CPU
{44467427-E0BE-492C-B9B4-82B362C183C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{44467427-E0BE-492C-B9B4-82B362C183C3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{44467427-E0BE-492C-B9B4-82B362C183C3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{44467427-E0BE-492C-B9B4-82B362C183C3}.Release|Any CPU.Build.0 = Release|Any CPU
{F701EDA5-D7EA-4AA7-9C57-83ED50CE72EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F701EDA5-D7EA-4AA7-9C57-83ED50CE72EC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F701EDA5-D7EA-4AA7-9C57-83ED50CE72EC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F701EDA5-D7EA-4AA7-9C57-83ED50CE72EC}.Release|Any CPU.Build.0 = Release|Any CPU
{2BE6BDC7-A9A3-4E30-9099-A9EF4813F6FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2BE6BDC7-A9A3-4E30-9099-A9EF4813F6FF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2BE6BDC7-A9A3-4E30-9099-A9EF4813F6FF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2BE6BDC7-A9A3-4E30-9099-A9EF4813F6FF}.Release|Any CPU.Build.0 = Release|Any CPU
{1E161A34-10C1-46FA-9EFD-10DD0858A8F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1E161A34-10C1-46FA-9EFD-10DD0858A8F5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1E161A34-10C1-46FA-9EFD-10DD0858A8F5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1E161A34-10C1-46FA-9EFD-10DD0858A8F5}.Release|Any CPU.Build.0 = Release|Any CPU
{62B2B8C9-8F24-4D31-894F-C1F0728D32AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{62B2B8C9-8F24-4D31-894F-C1F0728D32AB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{62B2B8C9-8F24-4D31-894F-C1F0728D32AB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{62B2B8C9-8F24-4D31-894F-C1F0728D32AB}.Release|Any CPU.Build.0 = Release|Any CPU
{983B0136-384B-4439-B374-31111FFAA286}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{983B0136-384B-4439-B374-31111FFAA286}.Debug|Any CPU.Build.0 = Debug|Any CPU
{983B0136-384B-4439-B374-31111FFAA286}.Release|Any CPU.ActiveCfg = Release|Any CPU
{983B0136-384B-4439-B374-31111FFAA286}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -1523,6 +1583,16 @@ Global
{E9492F9F-47E0-45A6-A51D-9949FEAA8543} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{8764DFAF-D13D-449A-9A5E-5D7F0B2D7FEF} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{0F80E95C-41E6-4F23-94FF-FC9D0B8D5D71} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{0858571B-CE73-4AD6-BD06-EC9F0714D8E9} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{86F3684C-A0A5-4943-8CFA-AE79E8E3E315} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{32F3E84B-D02E-42BD-BC5C-0D211564EF30} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{44467427-E0BE-492C-B9B4-82B362C183C3} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{F701EDA5-D7EA-4AA7-9C57-83ED50CE72EC} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{78340A37-219E-4F2D-9AC6-40A7B467EEEC} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{2BE6BDC7-A9A3-4E30-9099-A9EF4813F6FF} = {447C8A77-E5F0-4538-8687-7383196D04EA}
{1E161A34-10C1-46FA-9EFD-10DD0858A8F5} = {447C8A77-E5F0-4538-8687-7383196D04EA}
{62B2B8C9-8F24-4D31-894F-C1F0728D32AB} = {447C8A77-E5F0-4538-8687-7383196D04EA}
{983B0136-384B-4439-B374-31111FFAA286} = {447C8A77-E5F0-4538-8687-7383196D04EA}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5}

4
framework/src/Volo.Abp.AspNetCore.Authentication.OpenIdConnect/Volo.Abp.AspNetCore.Authentication.OpenIdConnect.csproj

@ -8,10 +8,6 @@
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="$(MicrosoftAspNetCorePackageVersion)" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.AspNetCore.MultiTenancy\Volo.Abp.AspNetCore.MultiTenancy.csproj" />
<ProjectReference Include="..\Volo.Abp.AspNetCore.Authentication.OAuth\Volo.Abp.AspNetCore.Authentication.OAuth.csproj" />

78
framework/src/Volo.Abp.AspNetCore.Components.Server/Microsoft/AspNetCore/Authentication/Cookies/CookieAuthenticationOptionsExtensions.cs

@ -1,7 +1,9 @@
using System;
using System.Threading.Tasks;
using IdentityModel.Client;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Authentication.Cookies;
@ -16,41 +18,67 @@ public static class CookieAuthenticationOptionsExtensions
/// <returns></returns>
public static CookieAuthenticationOptions IntrospectAccessToken(this CookieAuthenticationOptions options, string oidcAuthenticationScheme = "oidc")
{
var originalHandler = options.Events.OnValidatePrincipal;
options.Events.OnValidatePrincipal = async principalContext =>
{
originalHandler?.Invoke(principalContext);
if (principalContext.Principal == null || principalContext.Principal.Identity == null || !principalContext.Principal.Identity.IsAuthenticated)
{
return;
}
var logger = principalContext.HttpContext.RequestServices.GetRequiredService<ILogger<CookieAuthenticationOptions>>();
if (principalContext.Principal != null && principalContext.Principal.Identity != null && principalContext.Principal.Identity.IsAuthenticated)
var accessToken = principalContext.Properties.GetTokenValue("access_token");
if (!accessToken.IsNullOrWhiteSpace())
{
var accessToken = principalContext.Properties.GetTokenValue("access_token");
if (!accessToken.IsNullOrWhiteSpace())
var openIdConnectOptions = await GetOpenIdConnectOptions(principalContext, oidcAuthenticationScheme);
var response = await openIdConnectOptions.Backchannel.IntrospectTokenAsync(new TokenIntrospectionRequest
{
Address = openIdConnectOptions.Configuration?.IntrospectionEndpoint ?? openIdConnectOptions.Authority.EnsureEndsWith('/') + "connect/introspect",
ClientId = openIdConnectOptions.ClientId,
ClientSecret = openIdConnectOptions.ClientSecret,
Token = accessToken
});
if (response.IsError)
{
var openIdConnectOptions = principalContext.HttpContext.RequestServices.GetRequiredService<IOptionsMonitor<OpenIdConnectOptions>>().Get(oidcAuthenticationScheme);
if (openIdConnectOptions.Configuration == null && openIdConnectOptions.ConfigurationManager != null)
{
openIdConnectOptions.Configuration = await openIdConnectOptions.ConfigurationManager.GetConfigurationAsync(principalContext.HttpContext.RequestAborted);
}
var response = await openIdConnectOptions.Backchannel.IntrospectTokenAsync(new TokenIntrospectionRequest
{
Address = openIdConnectOptions.Configuration?.IntrospectionEndpoint ?? openIdConnectOptions.Authority.EnsureEndsWith('/') + "connect/introspect",
ClientId = openIdConnectOptions.ClientId,
ClientSecret = openIdConnectOptions.ClientSecret,
Token = accessToken
});
if (response.IsActive)
{
return;
}
logger.LogError(response.Error);
await SignOutAsync(principalContext);
return;
}
principalContext.RejectPrincipal();
await principalContext.HttpContext.SignOutAsync(principalContext.Scheme.Name);
if (!response.IsActive)
{
logger.LogError("The access_token is not active.");
await SignOutAsync(principalContext);
return;
}
logger.LogInformation("The access_token is active.");
}
else
{
logger.LogError("The access_token is not found in the cookie properties, Please make sure SaveTokens of OpenIdConnectOptions is set as true.");
await SignOutAsync(principalContext);
}
};
return options;
}
private async static Task<OpenIdConnectOptions> GetOpenIdConnectOptions(CookieValidatePrincipalContext principalContext, string oidcAuthenticationScheme)
{
var openIdConnectOptions = principalContext.HttpContext.RequestServices.GetRequiredService<IOptionsMonitor<OpenIdConnectOptions>>().Get(oidcAuthenticationScheme);
if (openIdConnectOptions.Configuration == null && openIdConnectOptions.ConfigurationManager != null)
{
openIdConnectOptions.Configuration = await openIdConnectOptions.ConfigurationManager.GetConfigurationAsync(principalContext.HttpContext.RequestAborted);
}
return openIdConnectOptions;
}
private async static Task SignOutAsync(CookieValidatePrincipalContext principalContext)
{
principalContext.RejectPrincipal();
await principalContext.HttpContext.SignOutAsync(principalContext.Scheme.Name);
}
}

1
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo.Abp.AspNetCore.Components.WebAssembly.csproj

@ -23,6 +23,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="$(MicrosoftAspNetCorePackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="$(MicrosoftAspNetCorePackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="$(MicrosoftAspNetCorePackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="2.2.0" />
</ItemGroup>

2
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/AbpAspNetCoreComponentsWebAssemblyModule.cs

@ -19,7 +19,7 @@ namespace Volo.Abp.AspNetCore.Components.WebAssembly;
typeof(AbpAspNetCoreMvcClientCommonModule),
typeof(AbpUiModule),
typeof(AbpAspNetCoreComponentsWebModule)
)]
)]
public class AbpAspNetCoreComponentsWebAssemblyModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)

40
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/ClientProxyExceptionEventHandler.cs

@ -0,0 +1,40 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus;
using Volo.Abp.Http;
namespace Volo.Abp.AspNetCore.Components.WebAssembly;
public class ClientProxyExceptionEventHandler : ILocalEventHandler<ClientProxyExceptionEventData>, ITransientDependency
{
protected NavigationManager NavigationManager { get; }
protected IAccessTokenProvider AccessTokenProvider { get; }
public ClientProxyExceptionEventHandler(NavigationManager navigationManager, IAccessTokenProvider accessTokenProvider)
{
NavigationManager = navigationManager;
AccessTokenProvider = accessTokenProvider;
}
public virtual async Task HandleEventAsync(ClientProxyExceptionEventData eventData)
{
if (eventData.StatusCode == 401)
{
var result = await AccessTokenProvider.RequestAccessToken();
if (result.Status != AccessTokenResultStatus.Success)
{
NavigationManager.NavigateToLogout("authentication/logout");
return;
}
result.TryGetToken(out var token);
if (token != null && DateTimeOffset.Now >= token.Expires.AddMinutes(-5))
{
NavigationManager.NavigateToLogout("authentication/logout");
}
}
}
}

1
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/Extensibility/WebAssemblyLookupApiRequestService.cs

@ -3,7 +3,6 @@ using System.Globalization;
using System.Net.Http;
using System.Threading.Tasks;
using Castle.Components.DictionaryAdapter;
using Fody;
using Volo.Abp.AspNetCore.Components.Web.Extensibility;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Http.Client.Authentication;

1
framework/src/Volo.Abp.AspNetCore.Components/Volo.Abp.AspNetCore.Components.csproj

@ -18,6 +18,7 @@
<ProjectReference Include="..\Volo.Abp.Timing\Volo.Abp.Timing.csproj" />
<ProjectReference Include="..\Volo.Abp.ObjectMapping\Volo.Abp.ObjectMapping.csproj" />
<ProjectReference Include="..\Volo.Abp.Security\Volo.Abp.Security.csproj" />
<ProjectReference Include="..\Volo.Abp.MultiTenancy.Abstractions\Volo.Abp.MultiTenancy.Abstractions.csproj" />
</ItemGroup>
<ItemGroup>

4
framework/src/Volo.Abp.AspNetCore.Components/Volo/Abp/AspNetCore/Components/AbpAspNetCoreComponentsModule.cs

@ -4,6 +4,7 @@ using Volo.Abp.AspNetCore.Components.DependencyInjection;
using Volo.Abp.DynamicProxy;
using Volo.Abp.Localization;
using Volo.Abp.Modularity;
using Volo.Abp.MultiTenancy;
using Volo.Abp.ObjectMapping;
using Volo.Abp.Security;
using Volo.Abp.Timing;
@ -13,7 +14,8 @@ namespace Volo.Abp.AspNetCore.Components;
[DependsOn(
typeof(AbpObjectMappingModule),
typeof(AbpSecurityModule),
typeof(AbpTimingModule)
typeof(AbpTimingModule),
typeof(AbpMultiTenancyAbstractionsModule)
)]
public class AbpAspNetCoreComponentsModule : AbpModule
{

69
framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/AbpAspNetCoreMultiTenancyOptions.cs

@ -1,14 +1,23 @@
using System;
using System.Globalization;
using System.Net;
using System.Runtime.ExceptionServices;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Http.Json;
using Microsoft.AspNetCore.Internal;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.Net.Http.Headers;
using Volo.Abp.Http;
using Volo.Abp.Json;
using Volo.Abp.MultiTenancy;
namespace Volo.Abp.AspNetCore.MultiTenancy;
@ -58,14 +67,70 @@ public class AbpAspNetCoreMultiTenancyOptions
}
}
context.Response.Headers.Add("Abp-Tenant-Resolve-Error", HtmlEncoder.Default.Encode(exception.Message));
if (isCookieAuthentication && context.Request.Method.Equals("Get", StringComparison.OrdinalIgnoreCase) && !context.Request.IsAjax())
{
context.Response.Headers.Add("Abp-Tenant-Resolve-Error", HtmlEncoder.Default.Encode(exception.Message));
context.Response.Redirect(context.Request.GetEncodedUrl());
}
else if (context.Request.IsAjax())
{
var error = new RemoteServiceErrorResponse(new RemoteServiceErrorInfo(exception.Message, exception is BusinessException businessException ? businessException.Details : string.Empty));
var jsonSerializerOptions = context.RequestServices.GetRequiredService<IOptions<JsonOptions>>().Value.SerializerOptions;
ResponseContentTypeHelper.ResolveContentTypeAndEncoding(
null,
context.Response.ContentType,
(new MediaTypeHeaderValue("application/json")
{
Encoding = Encoding.UTF8
}.ToString(), Encoding.UTF8),
MediaType.GetEncoding,
out var resolvedContentType,
out var resolvedContentTypeEncoding);
context.Response.ContentType = resolvedContentType;
context.Response.StatusCode = (int)HttpStatusCode.NotFound;
var responseStream = context.Response.Body;
if (resolvedContentTypeEncoding.CodePage == Encoding.UTF8.CodePage)
{
try
{
await JsonSerializer.SerializeAsync(responseStream, error, error.GetType(), jsonSerializerOptions, context.RequestAborted);
await responseStream.FlushAsync(context.RequestAborted);
}
catch (OperationCanceledException) when (context.RequestAborted.IsCancellationRequested) { }
}
else
{
var transcodingStream = Encoding.CreateTranscodingStream(context.Response.Body, resolvedContentTypeEncoding, Encoding.UTF8, leaveOpen: true);
ExceptionDispatchInfo exceptionDispatchInfo = null;
try
{
await JsonSerializer.SerializeAsync(transcodingStream, error, error.GetType(), jsonSerializerOptions, context.RequestAborted);
await transcodingStream.FlushAsync(context.RequestAborted);
}
catch (OperationCanceledException) when (context.RequestAborted.IsCancellationRequested) { }
catch (Exception ex)
{
exceptionDispatchInfo = ExceptionDispatchInfo.Capture(ex);
}
finally
{
try
{
await transcodingStream.DisposeAsync();
}
catch when (exceptionDispatchInfo != null)
{
}
exceptionDispatchInfo?.Throw();
}
}
}
else
{
context.Response.Headers.Add("Abp-Tenant-Resolve-Error", HtmlEncoder.Default.Encode(exception.Message));
context.Response.StatusCode = (int)HttpStatusCode.NotFound;
context.Response.ContentType = "text/html";

22
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpDynamicformTagHelper.cs

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
@ -21,6 +22,8 @@ public class AbpDynamicFormTagHelper : AbpTagHelper<AbpDynamicFormTagHelper, Abp
public bool? RequiredSymbols { get; set; } = true;
#region MvcFormTagHelperAttiributes
private IDictionary<string, string> _routeValues;
private const string ActionAttributeName = "asp-action";
private const string AreaAttributeName = "asp-area";
@ -57,7 +60,22 @@ public class AbpDynamicFormTagHelper : AbpTagHelper<AbpDynamicFormTagHelper, Abp
public string Method { get; set; }
[HtmlAttributeName(RouteValuesDictionaryName, DictionaryAttributePrefix = RouteValuesPrefix)]
public IDictionary<string, string> RouteValues { get; set; }
public IDictionary<string, string> RouteValues
{
get
{
if (_routeValues == null)
{
_routeValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
return _routeValues;
}
set
{
_routeValues = value;
}
}
#endregion

14
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpDynamicformTagHelperService.cs

@ -5,6 +5,7 @@ using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Localization.Resources.AbpUi;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.TagHelpers;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
@ -316,6 +317,13 @@ public class AbpDynamicFormTagHelperService : AbpTagHelperService<AbpDynamicForm
{
return list;
}
if (IsFile(model.ModelType))
{
list.Add(ModelExplorerToModelExpressionConverter(model));
return list;
}
return model.Properties.Aggregate(list, ExploreModelsRecursively);
}
@ -368,6 +376,12 @@ public class AbpDynamicFormTagHelperService : AbpTagHelperService<AbpDynamicForm
{
return type == typeof(List<SelectListItem>) || type == typeof(IEnumerable<SelectListItem>);
}
protected virtual bool IsFile(Type type)
{
return typeof(IFormFile).IsAssignableFrom(type) ||
typeof(IEnumerable<IFormFile>).IsAssignableFrom(type);
}
protected virtual bool IsSelectGroup(TagHelperContext context, ModelExpression model)
{

7
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerBaseTagHelper.cs

@ -9,7 +9,7 @@ public abstract class
where TTagHelper : AbpDatePickerBaseTagHelper<TTagHelper>
{
private readonly IAbpDatePickerOptions _abpDatePickerOptionsImplementation;
private IAbpDatePickerOptions _abpDatePickerOptionsImplementation;
public string Label { get; set; }
@ -47,6 +47,11 @@ public abstract class
{
_abpDatePickerOptionsImplementation = new AbpDatePickerOptions();
}
public void SetDatePickerOptions(IAbpDatePickerOptions options)
{
_abpDatePickerOptionsImplementation = options;
}
public string PickerId {
get => _abpDatePickerOptionsImplementation.PickerId;

55
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerBaseTagHelperService.cs

@ -22,11 +22,42 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form.DatePicker;
public abstract class AbpDatePickerBaseTagHelperService<TTagHelper> : AbpTagHelperService<TTagHelper>
where TTagHelper : AbpDatePickerBaseTagHelper<TTagHelper>
{
protected readonly Dictionary<Type,Func<object,string>> SupportedInputTypes = new() {
{typeof(string), o => DateTime.Parse((string)o).ToString("O")},
{typeof(DateTime), o => ((DateTime) o).ToString("O")},
protected readonly Dictionary<Type, Func<object, string>> SupportedInputTypes = new()
{
{
typeof(string), o =>
{
if(o is string s && DateTime.TryParse(s, out var dt))
{
return dt.ToString("O");
}
return string.Empty;
}
},
{
typeof(DateTime), o =>
{
if(o is DateTime dt && dt != default)
{
return dt.ToString("O");
}
return string.Empty;
}
},
{typeof(DateTime?), o => ((DateTime?) o)?.ToString("O")},
{typeof(DateTimeOffset), o => ((DateTimeOffset) o).ToString("O")},
{
typeof(DateTimeOffset), o =>
{
if(o is DateTimeOffset dto && dto != default)
{
return dto.ToString("O");
}
return string.Empty;
}
},
{typeof(DateTimeOffset?), o => ((DateTimeOffset?) o)?.ToString("O")}
};
@ -84,6 +115,11 @@ public abstract class AbpDatePickerBaseTagHelperService<TTagHelper> : AbpTagHelp
AddReadOnlyAttribute(TagHelperOutput);
AddPlaceholderAttribute(TagHelperOutput);
AddInfoTextId(TagHelperOutput);
var optionsAttribute = GetAttributeAndModelExpression<DatePickerOptionsAttribute>(out var modelExpression);
if (optionsAttribute != null)
{
TagHelper.SetDatePickerOptions(optionsAttribute.GetDatePickerOptions(modelExpression.ModelExplorer));
}
// Open and close button
var openButtonContent = TagHelper.OpenButton
@ -426,15 +462,6 @@ public abstract class AbpDatePickerBaseTagHelperService<TTagHelper> : AbpTagHelp
attrList.Add(attr);
}
var optionsAttribute = GetAttributeAndModelExpression<DatePickerOptionsAttribute>(out var modelExpression);
if (optionsAttribute != null)
{
foreach (var attr in ConvertDatePickerOptionsToAttributeList(optionsAttribute.GetDatePickerOptions(modelExpression.ModelExplorer)))
{
attrList.Add(attr);
}
}
AddBaseTagAttributes(attrList);
return attrList;
@ -591,7 +618,7 @@ public abstract class AbpDatePickerBaseTagHelperService<TTagHelper> : AbpTagHelp
abpButtonTagHelper.ButtonType = AbpButtonType.Outline_Secondary;
abpButtonTagHelper.Icon = icon;
abpButtonTagHelper.Disabled = TagHelper.IsDisabled;
abpButtonTagHelper.Disabled = TagHelper.IsDisabled || GetAttribute<DisabledInput>() != null;
if (!visible)
{

9
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerTagHelperService.cs

@ -63,11 +63,14 @@ public class AbpDatePickerTagHelperService : AbpDatePickerBaseTagHelperService<A
protected override void AddBaseTagAttributes(TagHelperAttributeList attributes)
{
if (TagHelper.AspFor != null &&
TagHelper.AspFor.Model != null &&
if (TagHelper.AspFor?.Model != null &&
SupportedInputTypes.TryGetValue(TagHelper.AspFor.Metadata.ModelType, out var convertFunc))
{
attributes.Add("data-date", convertFunc(TagHelper.AspFor.Model));
var convert = convertFunc(TagHelper.AspFor.Model);
if(!convert.IsNullOrWhiteSpace())
{
attributes.Add("data-date", convert);
}
}
}

12
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperResourceService.cs

@ -3,14 +3,13 @@ using Microsoft.AspNetCore.Razor.TagHelpers;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Volo.Abp.AspNetCore.VirtualFileSystem;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling.TagHelpers;
@ -68,7 +67,9 @@ public abstract class AbpTagHelperResourceService : ITransientDependency
if (file == null || !file.Exists)
{
throw new AbpException($"Could not find the bundle file '{bundleFile}' for the bundle '{bundleName}'!");
Logger.LogError($"Could not find the bundle file '{bundleFile}' for the bundle '{bundleName}'!");
AddErrorScript(viewContext, tagHelper, context, output, bundleFile, bundleName);
continue;
}
if (file.Length > 0)
@ -87,6 +88,11 @@ public abstract class AbpTagHelperResourceService : ITransientDependency
protected abstract void AddHtmlTag(ViewContext viewContext, TagHelper tagHelper, TagHelperContext context, TagHelperOutput output, string file);
protected virtual void AddErrorScript(ViewContext viewContext, TagHelper tagHelper, TagHelperContext context, TagHelperOutput output, string file, string bundleName)
{
output.Content.AppendHtml($"<script>console.log(\"%cCould not find the bundle file '{file}' for the bundle '{bundleName}'!\", 'background: yellow; font-size:20px;');</script>{Environment.NewLine}");
}
protected virtual string GenerateBundleName(List<BundleTagHelperItem> bundleItems)
{
return bundleItems.JoinAsString("|").ToMd5();

5
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperScriptService.cs

@ -49,6 +49,9 @@ public class AbpTagHelperScriptService : AbpTagHelperResourceService
var deferText = (defer || Options.DeferScriptsByDefault || Options.DeferScripts.Any(x => file.StartsWith(x, StringComparison.OrdinalIgnoreCase)))
? "defer"
: string.Empty;
output.Content.AppendHtml($"<script {deferText} src=\"{viewContext.GetUrlHelper().Content(file.EnsureStartsWith('~'))}\"></script>{Environment.NewLine}");
var nonceText = (viewContext.HttpContext.Items.TryGetValue(AbpAspNetCoreConsts.ScriptNonceKey, out var nonce) && nonce is string nonceString && !string.IsNullOrEmpty(nonceString))
? $"nonce=\"{nonceString}\""
: string.Empty;
output.Content.AppendHtml($"<script {deferText} {nonceText} src=\"{viewContext.GetUrlHelper().Content(file.EnsureStartsWith('~'))}\"></script>{Environment.NewLine}");
}
}

10
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperStyleService.cs

@ -8,19 +8,23 @@ using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using Volo.Abp.AspNetCore.Security;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling.TagHelpers;
public class AbpTagHelperStyleService : AbpTagHelperResourceService
{
protected AbpSecurityHeadersOptions SecurityHeadersOptions;
public AbpTagHelperStyleService(
IBundleManager bundleManager,
IOptions<AbpBundlingOptions> options,
IWebHostEnvironment hostingEnvironment) : base(
IWebHostEnvironment hostingEnvironment,
IOptions<AbpSecurityHeadersOptions> securityHeadersOptions) : base(
bundleManager,
options,
hostingEnvironment)
{
SecurityHeadersOptions = securityHeadersOptions.Value;
}
protected override void CreateBundle(string bundleName, List<BundleTagHelperItem> bundleItems)
@ -48,7 +52,9 @@ public class AbpTagHelperStyleService : AbpTagHelperResourceService
if (preload || Options.PreloadStylesByDefault || Options.PreloadStyles.Any(x => file.StartsWith(x, StringComparison.OrdinalIgnoreCase)))
{
output.Content.AppendHtml($"<link rel=\"preload\" href=\"{viewContext.GetUrlHelper().Content(file.EnsureStartsWith('~'))}\" as=\"style\" onload=\"this.rel='stylesheet'\" />{Environment.NewLine}");
output.Content.AppendHtml(SecurityHeadersOptions.UseContentSecurityPolicyScriptNonce
? $"<link rel=\"preload\" href=\"{viewContext.GetUrlHelper().Content(file.EnsureStartsWith('~'))}\" as=\"style\" abp-csp-style />{Environment.NewLine}"
: $"<link rel=\"preload\" href=\"{viewContext.GetUrlHelper().Content(file.EnsureStartsWith('~'))}\" as=\"style\" onload=\"this.rel='stylesheet'\" />{Environment.NewLine}");
}
else
{

22
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/ScriptNonceTagHelper.cs

@ -0,0 +1,22 @@
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling.TagHelpers;
[HtmlTargetElement("script")]
[HtmlTargetElement("body")]
public class ScriptNonceTagHelper : AbpTagHelper
{
[HtmlAttributeNotBound]
[ViewContext]
public ViewContext ViewContext { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
if (ViewContext.HttpContext.Items.TryGetValue(AbpAspNetCoreConsts.ScriptNonceKey, out var nonce) && nonce is string nonceString && !string.IsNullOrEmpty(nonceString))
{
output.Attributes.Add("nonce", nonceString);
}
}
}

3
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalScriptContributor.cs

@ -35,6 +35,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Bundling;
)]
public class SharedThemeGlobalScriptContributor : BundleContributor
{
public override void ConfigureBundle(BundleConfigurationContext context)
{
context.Files.AddRange(new[]
@ -48,6 +49,6 @@ public class SharedThemeGlobalScriptContributor : BundleContributor
"/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js",
"/libs/abp/aspnetcore-mvc-ui-theme-shared/sweetalert2/abp-sweetalert2.js",
"/libs/abp/aspnetcore-mvc-ui-theme-shared/toastr/abp-toastr.js"
});
});
}
}

16
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/dom-event-handlers.js

@ -619,9 +619,7 @@
options.separator = separator;
}
if(options.autoUpdateInput){
fillInput($input, startDate, endDate, options);
}
fillInput($input, startDate, endDate, options);
$input.on('apply.daterangepicker', function (ev, picker) {
if (singleDatePicker) {
@ -643,8 +641,9 @@
var momentEndDate = getMoment(endDate, options);
if (momentStartDate.isValid()) {
picker.setStartDate(momentStartDate);
picker.setEndDate(momentEndDate);
}
if (momentEndDate.isValid()) {
if (momentEndDate.isValid() && !singleDatePicker) {
picker.setEndDate(momentEndDate);
}
});
@ -754,6 +753,10 @@
});
}
abp.dom.initializers.initializeAbpCspStyles = function ($abpCspStyles){
$abpCspStyles.attr("rel", "stylesheet");
}
abp.dom.onNodeAdded(function (args) {
abp.dom.initializers.initializeToolTips(args.$el.findWithSelf('[data-toggle="tooltip"]'));
abp.dom.initializers.initializePopovers(args.$el.findWithSelf('[data-toggle="popover"]'));
@ -761,6 +764,8 @@
abp.dom.initializers.initializeForms(args.$el.findWithSelf('form'), true);
abp.dom.initializers.initializeScript(args.$el);
abp.dom.initializers.initializeAutocompleteSelects(args.$el.findWithSelf('.auto-complete-select'));
abp.dom.initializers.initializeAbpCspStyles($("link[abp-csp-style]"));
abp.dom.initializers.initializeDateRangePickers(args.$el);
});
abp.dom.onNodeRemoved(function (args) {
@ -772,6 +777,7 @@
abp.event.on('abp.configurationInitialized', function () {
abp.libs.bootstrapDatepicker.normalizeLanguageConfig();
});
$(function () {
abp.dom.initializers.initializeToolTips($('[data-toggle="tooltip"]'));
@ -782,7 +788,7 @@
abp.dom.initializers.initializeForms($('form'));
abp.dom.initializers.initializeAutocompleteSelects($('.auto-complete-select'));
$('[data-auto-focus="true"]').first().findWithSelf('input,select').focus();
abp.dom.initializers.initializeAbpCspStyles($("link[abp-csp-style]"));
});
})(jQuery);

3
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs

@ -28,6 +28,7 @@ using Volo.Abp.AspNetCore.Mvc.ApiExploring;
using Volo.Abp.AspNetCore.Mvc.Conventions;
using Volo.Abp.AspNetCore.Mvc.DataAnnotations;
using Volo.Abp.AspNetCore.Mvc.DependencyInjection;
using Volo.Abp.AspNetCore.Mvc.Infrastructure;
using Volo.Abp.AspNetCore.Mvc.Json;
using Volo.Abp.AspNetCore.Mvc.Localization;
using Volo.Abp.AspNetCore.VirtualFileSystem;
@ -201,6 +202,8 @@ public class AbpAspNetCoreMvcModule : AbpModule
{
options.DisableModule("abp");
});
context.Services.Replace(ServiceDescriptor.Singleton<IHttpResponseStreamWriterFactory, AbpMemoryPoolHttpResponseStreamWriterFactory>());
}
public override void PostConfigureServices(ServiceConfigurationContext context)

3
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcOptions.cs

@ -12,6 +12,8 @@ public class AbpAspNetCoreMvcOptions
public HashSet<Type> IgnoredControllersOnModelExclusion { get; }
public HashSet<Type> ControllersToRemove { get; }
public bool AutoModelValidation { get; set; }
public bool EnableRazorRuntimeCompilationOnDevelopment { get; set; }
@ -22,6 +24,7 @@ public class AbpAspNetCoreMvcOptions
{
ConventionalControllers = new AbpConventionalControllerOptions();
IgnoredControllersOnModelExclusion = new HashSet<Type>();
ControllersToRemove = new HashSet<Type>();
AutoModelValidation = true;
EnableRazorRuntimeCompilationOnDevelopment = true;
ChangeControllerModelApiExplorerGroupName = true;

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

@ -11,6 +11,7 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Volo.Abp.Application.Services;
using Volo.Abp.AspNetCore.Controllers;
using Volo.Abp.DependencyInjection;
using Volo.Abp.GlobalFeatures;
using Volo.Abp.Http;
@ -80,6 +81,36 @@ public class AbpServiceConvention : IAbpServiceConvention, ITransientDependency
{
var controllerModelsToRemove = new List<ControllerModel>();
if (Options.ControllersToRemove.Any())
{
var removeControllerModels = GetControllers(application)
.Where(cm => Options.ControllersToRemove.Contains(cm.ControllerType))
.ToArray();
if (removeControllerModels.Any())
{
controllerModelsToRemove.AddRange(removeControllerModels);
Logger.LogInformation($"Removing the controller{(removeControllerModels.Length > 1 ? "s" : "")} {removeControllerModels.Select(c => c.ControllerType.AssemblyQualifiedName).JoinAsString(", ")} from the application model");
}
}
foreach (var controllerModel in GetControllers(application))
{
var replaceControllersAttr = ReflectionHelper.GetSingleAttributeOrDefault<ReplaceControllersAttribute>(controllerModel.ControllerType);
if (replaceControllersAttr != default)
{
var replaceControllerModels = GetControllers(application)
.Where(cm => replaceControllersAttr.ControllerTypes.Contains(cm.ControllerType))
.ToArray();
if (replaceControllerModels.Any())
{
controllerModelsToRemove.AddRange(replaceControllerModels);
Logger.LogInformation($"Removing the controller{(replaceControllerModels.Length > 1 ? "s" : "")} {replaceControllersAttr.ControllerTypes.Select(c => c.AssemblyQualifiedName).JoinAsString(", ")} from the application model since {(replaceControllerModels.Length > 1 ? "they are" : "it is")} replaced by the controller: {controllerModel.ControllerType.AssemblyQualifiedName}");
}
}
}
foreach (var controllerModel in GetControllers(application))
{
if (!controllerModel.ControllerType.IsDefined(typeof(ExposeServicesAttribute), false))
@ -99,8 +130,11 @@ public class AbpServiceConvention : IAbpServiceConvention, ITransientDependency
.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}");
if (exposedControllerModels.Any())
{
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;
}

40
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Infrastructure/AbpMemoryPoolHttpResponseStreamWriterFactory.cs

@ -0,0 +1,40 @@
using System;
using System.Buffers;
using System.IO;
using System.Text;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.WebUtilities;
namespace Volo.Abp.AspNetCore.Mvc.Infrastructure;
/// <summary>
/// https://github.com/dotnet/aspnetcore/issues/40928#issuecomment-1450063613
/// </summary>
public class AbpMemoryPoolHttpResponseStreamWriterFactory : IHttpResponseStreamWriterFactory
{
public const int DefaultBufferSize = 32 * 1024;
private readonly ArrayPool<byte> _bytePool;
private readonly ArrayPool<char> _charPool;
public AbpMemoryPoolHttpResponseStreamWriterFactory(ArrayPool<byte> bytePool, ArrayPool<char> charPool)
{
_bytePool = bytePool ?? throw new ArgumentNullException(nameof(bytePool));
_charPool = charPool ?? throw new ArgumentNullException(nameof(charPool));
}
public TextWriter CreateWriter(Stream stream, Encoding encoding)
{
if (stream == null)
{
throw new ArgumentNullException(nameof(stream));
}
if (encoding == null)
{
throw new ArgumentNullException(nameof(encoding));
}
return new HttpResponseStreamWriter(stream, encoding, DefaultBufferSize, _bytePool, _charPool);
}
}

78
framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Internal/ResponseContentTypeHelper.cs

@ -0,0 +1,78 @@
using System;
using System.Net.Mime;
using System.Text;
using Microsoft.AspNetCore.Http;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Internal;
/// <summary>
/// https://github.com/dotnet/aspnetcore/blob/release/7.0/src/Shared/ResponseContentTypeHelper.cs
/// </summary>
public static class ResponseContentTypeHelper
{
/// <summary>
/// Gets the content type and encoding that need to be used for the response.
/// The priority for selecting the content type is:
/// 1. ContentType property set on the action result
/// 2. <see cref="ContentType"/> property set on <see cref="HttpResponse"/>
/// 3. Default content type set on the action result
/// </summary>
/// <remarks>
/// The user supplied content type is not modified and is used as is. For example, if user
/// sets the content type to be "text/plain" without any encoding, then the default content type's
/// encoding is used to write the response and the ContentType header is set to be "text/plain" without any
/// "charset" information.
/// </remarks>
public static void ResolveContentTypeAndEncoding(
string? actionResultContentType,
string? httpResponseContentType,
(string defaultContentType, Encoding defaultEncoding) @default,
Func<string, Encoding> getEncoding,
out string resolvedContentType,
out Encoding resolvedContentTypeEncoding)
{
var (defaultContentType, defaultContentTypeEncoding) = @default;
// 1. User sets the ContentType property on the action result
if (actionResultContentType != null)
{
resolvedContentType = actionResultContentType;
var actionResultEncoding = getEncoding(actionResultContentType);
resolvedContentTypeEncoding = actionResultEncoding ?? defaultContentTypeEncoding;
return;
}
// 2. User sets the ContentType property on the http response directly
if (!string.IsNullOrEmpty(httpResponseContentType))
{
var mediaTypeEncoding = getEncoding(httpResponseContentType);
if (mediaTypeEncoding != null)
{
resolvedContentType = httpResponseContentType;
resolvedContentTypeEncoding = mediaTypeEncoding;
}
else
{
resolvedContentType = httpResponseContentType;
resolvedContentTypeEncoding = defaultContentTypeEncoding;
}
return;
}
// 3. Fall-back to the default content type
resolvedContentType = defaultContentType;
resolvedContentTypeEncoding = defaultContentTypeEncoding;
}
public static Encoding GetEncoding(string mediaType)
{
if (MediaTypeHeaderValue.TryParse(mediaType, out var parsed))
{
return parsed.Encoding;
}
return default;
}
}

100
framework/src/Volo.Abp.AspNetCore/Microsoft/Extensions/DependencyInjection/CookieAuthenticationOptionsExtensions.cs

@ -0,0 +1,100 @@
using System;
using System.Globalization;
using System.Threading.Tasks;
using IdentityModel.Client;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace Microsoft.Extensions.DependencyInjection;
public static class CookieAuthenticationOptionsExtensions
{
/// <summary>
/// Check the access_token is expired or inactive.
/// </summary>
public static CookieAuthenticationOptions CheckTokenExpiration(this CookieAuthenticationOptions options, string oidcAuthenticationScheme = "oidc", TimeSpan? advance = null, TimeSpan? validationInterval = null)
{
advance ??= TimeSpan.FromMinutes(3);
validationInterval ??= TimeSpan.FromMinutes(1);
options.Events.OnValidatePrincipal = async principalContext =>
{
if (principalContext.Principal == null || principalContext.Principal.Identity == null || !principalContext.Principal.Identity.IsAuthenticated)
{
return;
}
var logger = principalContext.HttpContext.RequestServices.GetRequiredService<ILogger<CookieAuthenticationOptions>>();
var tokenExpiresAt = principalContext.Properties.Items[".Token.expires_at"];
if (DateTimeOffset.TryParseExact(tokenExpiresAt, "o", null, DateTimeStyles.RoundtripKind, out var expiresAt) &&
expiresAt < DateTimeOffset.UtcNow.Subtract(advance.Value))
{
logger.LogInformation("The access_token is expired.");
await SignOutAsync(principalContext);
return;
}
if (principalContext.Properties.IssuedUtc != null && DateTimeOffset.UtcNow.Subtract(principalContext.Properties.IssuedUtc.Value) > validationInterval)
{
logger.LogInformation($"Check the access_token is active every {validationInterval.Value.TotalSeconds} seconds.");
var accessToken = principalContext.Properties.GetTokenValue("access_token");
if (!accessToken.IsNullOrWhiteSpace())
{
var openIdConnectOptions = await GetOpenIdConnectOptions(principalContext, oidcAuthenticationScheme);
var response = await openIdConnectOptions.Backchannel.IntrospectTokenAsync(new TokenIntrospectionRequest
{
Address = openIdConnectOptions.Configuration?.IntrospectionEndpoint ?? openIdConnectOptions.Authority.EnsureEndsWith('/') + "connect/introspect",
ClientId = openIdConnectOptions.ClientId,
ClientSecret = openIdConnectOptions.ClientSecret,
Token = accessToken
});
if (response.IsError)
{
logger.LogError(response.Error);
await SignOutAsync(principalContext);
return;
}
if (!response.IsActive)
{
logger.LogError("The access_token is not active.");
await SignOutAsync(principalContext);
return;
}
logger.LogInformation("The access_token is active.");
principalContext.ShouldRenew = true;
}
else
{
logger.LogError("The access_token is not found in the cookie properties, Please make sure SaveTokens of OpenIdConnectOptions is set as true.");
await SignOutAsync(principalContext);
}
}
};
return options;
}
private async static Task<OpenIdConnectOptions> GetOpenIdConnectOptions(CookieValidatePrincipalContext principalContext, string oidcAuthenticationScheme)
{
var openIdConnectOptions = principalContext.HttpContext.RequestServices.GetRequiredService<IOptionsMonitor<OpenIdConnectOptions>>().Get(oidcAuthenticationScheme);
if (openIdConnectOptions.Configuration == null && openIdConnectOptions.ConfigurationManager != null)
{
openIdConnectOptions.Configuration = await openIdConnectOptions.ConfigurationManager.GetConfigurationAsync(principalContext.HttpContext.RequestAborted);
}
return openIdConnectOptions;
}
private async static Task SignOutAsync(CookieValidatePrincipalContext principalContext)
{
principalContext.RejectPrincipal();
await principalContext.HttpContext.SignOutAsync(principalContext.Scheme.Name);
}
}

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

@ -26,6 +26,8 @@
<ProjectReference Include="..\Volo.Abp.Uow\Volo.Abp.Uow.csproj" />
<ProjectReference Include="..\Volo.Abp.Validation\Volo.Abp.Validation.csproj" />
<ProjectReference Include="..\Volo.Abp.VirtualFileSystem\Volo.Abp.VirtualFileSystem.csproj" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="$(MicrosoftAspNetCorePackageVersion)" />
<PackageReference Include="IdentityModel" Version="6.0.0" />
</ItemGroup>
</Project>

1
framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/AbpAspNetCoreConsts.cs

@ -4,4 +4,5 @@ public static class AbpAspNetCoreConsts
{
public const string DefaultApiPrefix = "api";
public const string DefaultIntegrationServiceApiPrefix = "integration-api";
public const string ScriptNonceKey = "ScriptNonce";
}

14
framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Controllers/ReplaceControllersAttribute.cs

@ -0,0 +1,14 @@
using System;
namespace Volo.Abp.AspNetCore.Controllers;
[AttributeUsage(AttributeTargets.Class)]
public class ReplaceControllersAttribute : Attribute
{
public Type[] ControllerTypes { get; }
public ReplaceControllersAttribute(params Type[] controllerTypes)
{
ControllerTypes = controllerTypes ?? Type.EmptyTypes;
}
}

23
framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/AbpSecurityHeaderNonceHelper.cs

@ -0,0 +1,23 @@
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.Rendering;
namespace Volo.Abp.AspNetCore.Security;
public static class AbpSecurityHeaderNonceHelper
{
public static string GetScriptNonce(this IHtmlHelper htmlHelper)
{
if (htmlHelper.ViewContext.HttpContext.Items.TryGetValue(AbpAspNetCoreConsts.ScriptNonceKey, out var nonce) && nonce is string nonceString && !string.IsNullOrEmpty(nonceString))
{
return nonceString;
}
return string.Empty;
}
public static IHtmlContent GetScriptNonceAttribute(this IHtmlHelper htmlHelper)
{
var nonce = htmlHelper.GetScriptNonce();
return nonce == string.Empty ? HtmlString.Empty : new HtmlString($"nonce=\"{nonce}\"");
}
}

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

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
@ -11,6 +12,8 @@ namespace Volo.Abp.AspNetCore.Security;
public class AbpSecurityHeadersMiddleware : IMiddleware, ITransientDependency
{
public IOptions<AbpSecurityHeadersOptions> Options { get; set; }
protected const string ScriptSrcKey = "script-src";
protected const string DefaultValue = "object-src 'none'; form-action 'self'; frame-ancestors 'none'";
public AbpSecurityHeadersMiddleware(IOptions<AbpSecurityHeadersOptions> options)
{
@ -28,21 +31,98 @@ public class AbpSecurityHeadersMiddleware : IMiddleware, ITransientDependency
/*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*/
AddHeader(context, "X-Frame-Options", "SAMEORIGIN");
if (Options.Value.UseContentSecurityPolicyHeader)
var requestAcceptTypeHtml = context.Request.Headers["Accept"].Any(x =>
x.Contains("text/html") || x.Contains("*/*") || x.Contains("application/xhtml+xml"));
if (!requestAcceptTypeHtml
|| !Options.Value.UseContentSecurityPolicyHeader
|| await AlwaysIgnoreContentTypes(context)
|| context.GetEndpoint() == null
|| Options.Value.IgnoredScriptNoncePaths.Any(x => context.Request.Path.StartsWithSegments(x.EnsureStartsWith('/'))))
{
AddOtherHeaders(context);
await next.Invoke(context);
return;
}
if (Options.Value.UseContentSecurityPolicyScriptNonce)
{
var randomValue = Guid.NewGuid().ToString("N");
context.Items.Add(AbpAspNetCoreConsts.ScriptNonceKey, randomValue);
}
context.Response.OnStarting(() =>
{
if (context.Response.Headers.ContainsKey("Content-Security-Policy"))
{
return Task.CompletedTask;
}
if (context.Response.ContentType?.StartsWith("text/html") != true)
{
return Task.CompletedTask;
}
if (context.Response.StatusCode is < 200 or > 299)
{
return Task.CompletedTask;
}
AddHeader(context, "Content-Security-Policy", BuildContentSecurityPolicyValue(context));
return Task.CompletedTask;
});
AddOtherHeaders(context);
await next.Invoke(context);
}
private async Task<bool> AlwaysIgnoreContentTypes(HttpContext context)
{
foreach (var selector in Options.Value.IgnoredScriptNonceSelectors)
{
AddHeader(context, "Content-Security-Policy",
Options.Value.ContentSecurityPolicyValue.IsNullOrEmpty()
? "object-src 'none'; form-action 'self'; frame-ancestors 'none'"
: Options.Value.ContentSecurityPolicyValue);
if(await selector(context))
{
return true;
}
}
return false;
}
private void AddOtherHeaders(HttpContext context)
{
foreach (var (key, value) in Options.Value.Headers)
{
AddHeader(context, key, value, true);
}
}
await next.Invoke(context);
protected virtual string BuildContentSecurityPolicyValue(HttpContext context)
{
var cspValue = Options.Value.ContentSecurityPolicyValue.IsNullOrWhiteSpace() ? DefaultValue : Options.Value.ContentSecurityPolicyValue;
if (!(Options.Value.UseContentSecurityPolicyScriptNonce &&
context.Items.TryGetValue(AbpAspNetCoreConsts.ScriptNonceKey, out var nonce) &&
nonce is string nonceValue && !string.IsNullOrEmpty(nonceValue)))
{
return cspValue;
}
var nonceStr = $" 'nonce-{nonceValue}'";
var scriptSrcValue = Options.Value.ContentSecurityPolicyValue.Split(';')
.FirstOrDefault(x => x.Trim().StartsWith(ScriptSrcKey))?.Trim();
if (scriptSrcValue.IsNullOrWhiteSpace())
{
return cspValue.EnsureEndsWith(';') + $" {ScriptSrcKey}{nonceStr};";
}
var newScriptSrcValue = scriptSrcValue + nonceStr;
return Options.Value.ContentSecurityPolicyValue.Replace(scriptSrcValue, newScriptSrcValue);
}
protected virtual void AddHeader(HttpContext context, string key, string value, bool overrideIfExists = false)
{
@ -51,7 +131,7 @@ public class AbpSecurityHeadersMiddleware : IMiddleware, ITransientDependency
context.Response.Headers[key] = value;
return;
}
context.Response.Headers.AddIfNotContains(new KeyValuePair<string, StringValues>(key, value));
}
}
}

13
framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/AbpSecurityHeadersOptions.cs

@ -1,17 +1,28 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
namespace Volo.Abp.AspNetCore.Security;
public class AbpSecurityHeadersOptions
{
public bool UseContentSecurityPolicyHeader { get; set; }
public bool UseContentSecurityPolicyScriptNonce { get; set; }
public string ContentSecurityPolicyValue { get; set; }
public Dictionary<string, string> Headers { get; }
public List<Func<HttpContext, Task<bool>>> IgnoredScriptNonceSelectors { get; }
public List<string> IgnoredScriptNoncePaths { get; }
public AbpSecurityHeadersOptions()
{
Headers = new Dictionary<string, string>();
IgnoredScriptNonceSelectors = new List<Func<HttpContext, Task<bool>>>();
IgnoredScriptNoncePaths = new List<string>();
}
}

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

@ -19,7 +19,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.MultiTenancy\Volo.Abp.MultiTenancy.csproj" />
<ProjectReference Include="..\Volo.Abp.MultiTenancy.Abstractions\Volo.Abp.MultiTenancy.Abstractions.csproj" />
</ItemGroup>
</Project>

2
framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/AbpAuthorizationAbstractionsModule.cs

@ -4,7 +4,7 @@ using Volo.Abp.MultiTenancy;
namespace Volo.Abp.Authorization;
[DependsOn(
typeof(AbpMultiTenancyModule)
typeof(AbpMultiTenancyAbstractionsModule)
)]
public class AbpAuthorizationAbstractionsModule : AbpModule
{

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

@ -20,11 +20,13 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.Authorization.Abstractions\Volo.Abp.Authorization.Abstractions.csproj" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.Localization\Volo.Abp.Localization.csproj" />
<ProjectReference Include="..\Volo.Abp.MultiTenancy\Volo.Abp.MultiTenancy.csproj" />
<ProjectReference Include="..\Volo.Abp.Security\Volo.Abp.Security.csproj" />
</ItemGroup>

4
framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AbpAuthorizationModule.cs

@ -8,6 +8,7 @@ using Volo.Abp.Authorization.Permissions;
using Volo.Abp.Localization;
using Volo.Abp.Localization.ExceptionHandling;
using Volo.Abp.Modularity;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Security;
using Volo.Abp.VirtualFileSystem;
@ -16,7 +17,8 @@ namespace Volo.Abp.Authorization;
[DependsOn(
typeof(AbpAuthorizationAbstractionsModule),
typeof(AbpSecurityModule),
typeof(AbpLocalizationModule)
typeof(AbpLocalizationModule),
typeof(AbpMultiTenancyModule)
)]
public class AbpAuthorizationModule : AbpModule
{

1
framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo.Abp.BackgroundJobs.Abstractions.csproj

@ -16,6 +16,7 @@
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.Json\Volo.Abp.Json.csproj" />
<ProjectReference Include="..\Volo.Abp.MultiTenancy.Abstractions\Volo.Abp.MultiTenancy.Abstractions.csproj" />
</ItemGroup>
</Project>

4
framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/AbpBackgroundJobsAbstractionsModule.cs

@ -3,12 +3,14 @@ using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Json;
using Volo.Abp.Modularity;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Reflection;
namespace Volo.Abp.BackgroundJobs;
[DependsOn(
typeof(AbpJsonModule)
typeof(AbpJsonModule),
typeof(AbpMultiTenancyAbstractionsModule)
)]
public class AbpBackgroundJobsAbstractionsModule : AbpModule
{

1
framework/src/Volo.Abp.BackgroundJobs/Volo.Abp.BackgroundJobs.csproj

@ -19,6 +19,7 @@
<ProjectReference Include="..\Volo.Abp.BackgroundWorkers\Volo.Abp.BackgroundWorkers.csproj" />
<ProjectReference Include="..\Volo.Abp.DistributedLocking.Abstractions\Volo.Abp.DistributedLocking.Abstractions.csproj" />
<ProjectReference Include="..\Volo.Abp.Guids\Volo.Abp.Guids.csproj" />
<ProjectReference Include="..\Volo.Abp.MultiTenancy\Volo.Abp.MultiTenancy.csproj" />
<ProjectReference Include="..\Volo.Abp.Timing\Volo.Abp.Timing.csproj" />
</ItemGroup>

4
framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/AbpBackgroundJobsModule.cs

@ -6,6 +6,7 @@ using Volo.Abp.Data;
using Volo.Abp.DistributedLocking;
using Volo.Abp.Guids;
using Volo.Abp.Modularity;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Threading;
using Volo.Abp.Timing;
@ -16,7 +17,8 @@ namespace Volo.Abp.BackgroundJobs;
typeof(AbpBackgroundWorkersModule),
typeof(AbpTimingModule),
typeof(AbpGuidsModule),
typeof(AbpDistributedLockingAbstractionsModule)
typeof(AbpDistributedLockingAbstractionsModule),
typeof(AbpMultiTenancyModule)
)]
public class AbpBackgroundJobsModule : AbpModule
{

21
framework/src/Volo.Abp.BlazoriseUI/Components/AbpExtensibleDataGrid.razor

@ -72,11 +72,22 @@
{
@if (column.Component != null)
{
<DataGridColumn TItem="TItem" Field="@typeof(TItem).GetProperties().First().Name" Caption="@column.Title">
<DisplayTemplate>
@RenderCustomTableColumnComponent(column.Component, context)
</DisplayTemplate>
</DataGridColumn>
@if (column.ValueConverter == null)
{
<DataGridColumn TItem="TItem" Field="@column.Data" Caption="@column.Title" Sortable="@column.Sortable" DisplayFormat="@column.DisplayFormat" DisplayFormatProvider="@column.DisplayFormatProvider" >
<DisplayTemplate>
@RenderCustomTableColumnComponent(column.Component, context)
</DisplayTemplate>
</DataGridColumn>
}
else
{
<DataGridColumn TItem="TItem" Field="@column.Data" Caption="@column.Title" Sortable="@column.Sortable">
<DisplayTemplate>
@RenderCustomTableColumnComponent(column.Component, context)
</DisplayTemplate>
</DataGridColumn>
}
}
else
{

8
framework/src/Volo.Abp.BlazoriseUI/Volo.Abp.BlazoriseUI.csproj

@ -14,10 +14,10 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Blazorise" Version="1.2.0" />
<PackageReference Include="Blazorise.DataGrid" Version="1.2.0" />
<PackageReference Include="Blazorise.Snackbar" Version="1.2.0" />
<PackageReference Include="Blazorise.Components" Version="1.2.0" />
<PackageReference Include="Blazorise" Version="1.2.3" />
<PackageReference Include="Blazorise.DataGrid" Version="1.2.3" />
<PackageReference Include="Blazorise.Snackbar" Version="1.2.3" />
<PackageReference Include="Blazorise.Components" Version="1.2.3" />
</ItemGroup>
</Project>

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

@ -5,6 +5,8 @@
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netstandard2.1;net7.0</TargetFrameworks>
<Nullable>enable</Nullable>
<WarningsAsErrors>Nullable</WarningsAsErrors>
<AssemblyName>Volo.Abp.Caching.StackExchangeRedis</AssemblyName>
<PackageId>Volo.Abp.Caching.StackExchangeRedis</PackageId>
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback>

2
framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo/Abp/Caching/StackExchangeRedis/AbpCachingStackExchangeRedisModule.cs

@ -16,7 +16,7 @@ public class AbpCachingStackExchangeRedisModule : AbpModule
var configuration = context.Services.GetConfiguration();
var redisEnabled = configuration["Redis:IsEnabled"];
if (redisEnabled.IsNullOrEmpty() || bool.Parse(redisEnabled))
if (string.IsNullOrEmpty(redisEnabled) || bool.Parse(redisEnabled))
{
context.Services.AddStackExchangeRedisCache(options =>
{

60
framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo/Abp/Caching/StackExchangeRedis/AbpRedisCache.cs

@ -28,8 +28,8 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems
private readonly static MethodInfo GetAbsoluteExpirationMethod;
private readonly static MethodInfo GetExpirationInSecondsMethod;
protected IDatabase RedisDatabase => GetRedisDatabase();
private IDatabase _redisDatabase;
protected IDatabase RedisDatabase => GetRedisDatabase()!;
private IDatabase? _redisDatabase;
protected string Instance { get; }
@ -37,28 +37,27 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems
{
var type = typeof(RedisCache);
RedisDatabaseField = Check.NotNull(type.GetField("_cache", BindingFlags.Instance | BindingFlags.NonPublic), nameof(RedisDatabaseField));
RedisDatabaseField = Check.NotNull(type.GetField("_cache", BindingFlags.Instance | BindingFlags.NonPublic), nameof(RedisDatabaseField))!;
SetScriptField = Check.NotNull(type.GetField("_setScript", BindingFlags.Instance | BindingFlags.NonPublic), nameof(SetScriptField));
SetScriptField = Check.NotNull(type.GetField("_setScript", BindingFlags.Instance | BindingFlags.NonPublic), nameof(SetScriptField))!;
ConnectMethod = Check.NotNull(type.GetMethod("Connect", BindingFlags.Instance | BindingFlags.NonPublic), nameof(ConnectMethod))!;
ConnectMethod = Check.NotNull(type.GetMethod("Connect", BindingFlags.Instance | BindingFlags.NonPublic), nameof(ConnectMethod));
ConnectAsyncMethod = Check.NotNull(type.GetMethod("ConnectAsync", BindingFlags.Instance | BindingFlags.NonPublic), nameof(ConnectAsyncMethod))!;
ConnectAsyncMethod = Check.NotNull(type.GetMethod("ConnectAsync", BindingFlags.Instance | BindingFlags.NonPublic), nameof(ConnectAsyncMethod));
MapMetadataMethod = Check.NotNull(type.GetMethod("MapMetadata", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Static), nameof(MapMetadataMethod))!;
MapMetadataMethod = Check.NotNull(type.GetMethod("MapMetadata", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Static), nameof(MapMetadataMethod));
GetAbsoluteExpirationMethod = Check.NotNull(type.GetMethod("GetAbsoluteExpiration", BindingFlags.Static | BindingFlags.NonPublic), nameof(GetAbsoluteExpirationMethod))!;
GetAbsoluteExpirationMethod = Check.NotNull(type.GetMethod("GetAbsoluteExpiration", BindingFlags.Static | BindingFlags.NonPublic), nameof(GetAbsoluteExpirationMethod));
GetExpirationInSecondsMethod = Check.NotNull(type.GetMethod("GetExpirationInSeconds", BindingFlags.Static | BindingFlags.NonPublic), nameof(GetExpirationInSecondsMethod))!;
GetExpirationInSecondsMethod = Check.NotNull(type.GetMethod("GetExpirationInSeconds", BindingFlags.Static | BindingFlags.NonPublic), nameof(GetExpirationInSecondsMethod));
AbsoluteExpirationKey = type.GetField("AbsoluteExpirationKey", BindingFlags.Static | BindingFlags.NonPublic)!.GetValue(null)!.ToString()!;
AbsoluteExpirationKey = type.GetField("AbsoluteExpirationKey", BindingFlags.Static | BindingFlags.NonPublic)?.GetValue(null).ToString();
SlidingExpirationKey = type.GetField("SlidingExpirationKey", BindingFlags.Static | BindingFlags.NonPublic)!.GetValue(null)!.ToString()!;
SlidingExpirationKey = type.GetField("SlidingExpirationKey", BindingFlags.Static | BindingFlags.NonPublic)?.GetValue(null).ToString();
DataKey = type.GetField("DataKey", BindingFlags.Static | BindingFlags.NonPublic)!.GetValue(null)!.ToString()!;
DataKey = type.GetField("DataKey", BindingFlags.Static | BindingFlags.NonPublic)?.GetValue(null).ToString();
// ReSharper disable once PossibleNullReferenceException
NotPresent = Check.NotNull(type.GetField("NotPresent", BindingFlags.Static | BindingFlags.NonPublic), nameof(NotPresent)).GetValue(null).To<int>();
NotPresent = type.GetField("NotPresent", BindingFlags.Static | BindingFlags.NonPublic)!.GetValue(null)!.To<int>();
}
public AbpRedisCache(IOptions<RedisCacheOptions> optionsAccessor)
@ -84,10 +83,10 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems
return;
}
await (Task)ConnectAsyncMethod.Invoke(this, new object[] { token });
await (Task)ConnectAsyncMethod.Invoke(this, new object[] { token })!;
}
public byte[][] GetMany(
public byte[]?[] GetMany(
IEnumerable<string> keys)
{
keys = Check.NotNull(keys, nameof(keys));
@ -95,7 +94,7 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems
return GetAndRefreshMany(keys, true);
}
public async Task<byte[][]> GetManyAsync(
public async Task<byte[]?[]> GetManyAsync(
IEnumerable<string> keys,
CancellationToken token = default)
{
@ -161,7 +160,7 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems
await RedisDatabase.KeyDeleteAsync(keys.Select(key => (RedisKey)(Instance + key)).ToArray());
}
protected virtual byte[][] GetAndRefreshMany(
protected virtual byte[]?[] GetAndRefreshMany(
IEnumerable<string> keys,
bool getData)
{
@ -186,7 +185,7 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems
return bytes;
}
protected virtual async Task<byte[][]> GetAndRefreshManyAsync(
protected virtual async Task<byte[]?[]> GetAndRefreshManyAsync(
IEnumerable<string> keys,
bool getData,
CancellationToken token = default)
@ -217,7 +216,7 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems
protected virtual Task[] PipelineRefreshManyAndOutData(
string[] keys,
RedisValue[][] results,
out byte[][] bytes)
out byte[]?[] bytes)
{
bytes = new byte[keys.Length][];
var tasks = new Task[keys.Length];
@ -226,7 +225,7 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems
{
if (results[i].Length >= 2)
{
MapMetadata(results[i], out DateTimeOffset? absExpr, out TimeSpan? sldExpr);
MapMetadata(results[i], out var absExpr, out var sldExpr);
if (sldExpr.HasValue)
{
@ -295,7 +294,7 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems
out DateTimeOffset? absoluteExpiration,
out TimeSpan? slidingExpiration)
{
var parameters = new object[] { results, null, null };
var parameters = new object?[] { results, null, null };
MapMetadataMethod.Invoke(this, parameters);
absoluteExpiration = (DateTimeOffset?)parameters[1];
@ -308,7 +307,7 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems
DistributedCacheEntryOptions options)
{
return (long?)GetExpirationInSecondsMethod.Invoke(null,
new object[] { creationTime, absoluteExpiration, options });
new object?[] { creationTime, absoluteExpiration, options });
}
protected virtual DateTimeOffset? GetAbsoluteExpiration(
@ -318,18 +317,13 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems
return (DateTimeOffset?)GetAbsoluteExpirationMethod.Invoke(null, new object[] { creationTime, options });
}
private IDatabase GetRedisDatabase()
private IDatabase? GetRedisDatabase()
{
if (_redisDatabase == null)
{
_redisDatabase = RedisDatabaseField.GetValue(this) as IDatabase;
}
return _redisDatabase;
return _redisDatabase ??= RedisDatabaseField.GetValue(this) as IDatabase;
}
private string GetSetScript()
{
return SetScriptField?.GetValue(this).ToString();
return SetScriptField.GetValue(this)!.ToString()!;
}
}

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

@ -5,6 +5,7 @@
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netstandard2.1;net7.0</TargetFrameworks>
<Nullable>enable</Nullable>
<AssemblyName>Volo.Abp.Caching</AssemblyName>
<PackageId>Volo.Abp.Caching</PackageId>
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback>

10
framework/src/Volo.Abp.Caching/Volo/Abp/Caching/AbpDistributedCacheOptions.cs

@ -25,26 +25,26 @@ public class AbpDistributedCacheOptions
/// List of all cache configurators.
/// (func argument:Name of cache)
/// </summary>
public List<Func<string, DistributedCacheEntryOptions>> CacheConfigurators { get; set; } //TODO: use a configurator interface instead?
public List<Func<string, DistributedCacheEntryOptions?>> CacheConfigurators { get; set; } //TODO: use a configurator interface instead?
public AbpDistributedCacheOptions()
{
CacheConfigurators = new List<Func<string, DistributedCacheEntryOptions>>();
CacheConfigurators = new List<Func<string, DistributedCacheEntryOptions?>>();
GlobalCacheEntryOptions = new DistributedCacheEntryOptions();
KeyPrefix = "";
}
public void ConfigureCache<TCacheItem>(DistributedCacheEntryOptions options)
public void ConfigureCache<TCacheItem>(DistributedCacheEntryOptions? options)
{
ConfigureCache(typeof(TCacheItem), options);
}
public void ConfigureCache(Type cacheItemType, DistributedCacheEntryOptions options)
public void ConfigureCache(Type cacheItemType, DistributedCacheEntryOptions? options)
{
ConfigureCache(CacheNameAttribute.GetCacheName(cacheItemType), options);
}
public void ConfigureCache(string cacheName, DistributedCacheEntryOptions options)
public void ConfigureCache(string cacheName, DistributedCacheEntryOptions? options)
{
CacheConfigurators.Add(name => cacheName != name ? null : options);
}

2
framework/src/Volo.Abp.Caching/Volo/Abp/Caching/CacheNameAttribute.cs

@ -33,6 +33,6 @@ public class CacheNameAttribute : Attribute
return cacheNameAttribute.Name;
}
return cacheItemType.FullName.RemovePostFix("CacheItem");
return cacheItemType.FullName.RemovePostFix("CacheItem")!;
}
}

131
framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs

@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
@ -31,62 +30,62 @@ public class DistributedCache<TCacheItem> :
InternalCache = internalCache;
}
public TCacheItem Get(string key, bool? hideErrors = null, bool considerUow = false)
public TCacheItem? Get(string key, bool? hideErrors = null, bool considerUow = false)
{
return InternalCache.Get(key, hideErrors, considerUow);
}
public KeyValuePair<string, TCacheItem>[] GetMany(IEnumerable<string> keys, bool? hideErrors = null, bool considerUow = false)
public KeyValuePair<string, TCacheItem?>[] GetMany(IEnumerable<string> keys, bool? hideErrors = null, bool considerUow = false)
{
return InternalCache.GetMany(keys, hideErrors, considerUow);
}
public Task<KeyValuePair<string, TCacheItem>[]> GetManyAsync(IEnumerable<string> keys, bool? hideErrors = null, bool considerUow = false, CancellationToken token = default)
public Task<KeyValuePair<string, TCacheItem?>[]> GetManyAsync(IEnumerable<string> keys, bool? hideErrors = null, bool considerUow = false, CancellationToken token = default)
{
return InternalCache.GetManyAsync(keys, hideErrors, considerUow, token);
}
public Task<TCacheItem> GetAsync(string key, bool? hideErrors = null, bool considerUow = false, CancellationToken token = default)
public Task<TCacheItem?> GetAsync(string key, bool? hideErrors = null, bool considerUow = false, CancellationToken token = default)
{
return InternalCache.GetAsync(key, hideErrors, considerUow, token);
}
public TCacheItem GetOrAdd(string key, Func<TCacheItem> factory, Func<DistributedCacheEntryOptions> optionsFactory = null, bool? hideErrors = null, bool considerUow = false)
public TCacheItem? GetOrAdd(string key, Func<TCacheItem> factory, Func<DistributedCacheEntryOptions>? optionsFactory = null, bool? hideErrors = null, bool considerUow = false)
{
return InternalCache.GetOrAdd(key, factory, optionsFactory, hideErrors, considerUow);
}
public Task<TCacheItem> GetOrAddAsync(string key, Func<Task<TCacheItem>> factory, Func<DistributedCacheEntryOptions> optionsFactory = null, bool? hideErrors = null, bool considerUow = false, CancellationToken token = default)
public Task<TCacheItem?> GetOrAddAsync(string key, Func<Task<TCacheItem>> factory, Func<DistributedCacheEntryOptions>? optionsFactory = null, bool? hideErrors = null, bool considerUow = false, CancellationToken token = default)
{
return InternalCache.GetOrAddAsync(key, factory, optionsFactory, hideErrors, considerUow, token);
}
public KeyValuePair<string, TCacheItem>[] GetOrAddMany(IEnumerable<string> keys, Func<IEnumerable<string>, List<KeyValuePair<string, TCacheItem>>> factory, Func<DistributedCacheEntryOptions> optionsFactory = null, bool? hideErrors = null, bool considerUow = false)
public KeyValuePair<string, TCacheItem?>[] GetOrAddMany(IEnumerable<string> keys, Func<IEnumerable<string>, List<KeyValuePair<string, TCacheItem>>> factory, Func<DistributedCacheEntryOptions>? optionsFactory = null, bool? hideErrors = null, bool considerUow = false)
{
return InternalCache.GetOrAddMany(keys, factory, optionsFactory, hideErrors, considerUow);
}
public Task<KeyValuePair<string, TCacheItem>[]> GetOrAddManyAsync(IEnumerable<string> keys, Func<IEnumerable<string>, Task<List<KeyValuePair<string, TCacheItem>>>> factory, Func<DistributedCacheEntryOptions> optionsFactory = null, bool? hideErrors = null, bool considerUow = false, CancellationToken token = default)
public Task<KeyValuePair<string, TCacheItem?>[]> GetOrAddManyAsync(IEnumerable<string> keys, Func<IEnumerable<string>, Task<List<KeyValuePair<string, TCacheItem>>>> factory, Func<DistributedCacheEntryOptions>? optionsFactory = null, bool? hideErrors = null, bool considerUow = false, CancellationToken token = default)
{
return InternalCache.GetOrAddManyAsync(keys, factory, optionsFactory, hideErrors, considerUow, token);
}
public void Set(string key, TCacheItem value, DistributedCacheEntryOptions options = null, bool? hideErrors = null, bool considerUow = false)
public void Set(string key, TCacheItem value, DistributedCacheEntryOptions? options = null, bool? hideErrors = null, bool considerUow = false)
{
InternalCache.Set(key, value, options, hideErrors, considerUow);
}
public Task SetAsync(string key, TCacheItem value, DistributedCacheEntryOptions options = null, bool? hideErrors = null, bool considerUow = false, CancellationToken token = default)
public Task SetAsync(string key, TCacheItem value, DistributedCacheEntryOptions? options = null, bool? hideErrors = null, bool considerUow = false, CancellationToken token = default)
{
return InternalCache.SetAsync(key, value, options, hideErrors, considerUow, token);
}
public void SetMany(IEnumerable<KeyValuePair<string, TCacheItem>> items, DistributedCacheEntryOptions options = null, bool? hideErrors = null, bool considerUow = false)
public void SetMany(IEnumerable<KeyValuePair<string, TCacheItem>> items, DistributedCacheEntryOptions? options = null, bool? hideErrors = null, bool considerUow = false)
{
InternalCache.SetMany(items, options, hideErrors, considerUow);
}
public Task SetManyAsync(IEnumerable<KeyValuePair<string, TCacheItem>> items, DistributedCacheEntryOptions options = null, bool? hideErrors = null, bool considerUow = false, CancellationToken token = default)
public Task SetManyAsync(IEnumerable<KeyValuePair<string, TCacheItem>> items, DistributedCacheEntryOptions? options = null, bool? hideErrors = null, bool considerUow = false, CancellationToken token = default)
{
return InternalCache.SetManyAsync(items, options, hideErrors, considerUow, token);
}
@ -141,12 +140,13 @@ public class DistributedCache<TCacheItem> :
/// <typeparam name="TCacheKey">The type of cache key being used.</typeparam>
public class DistributedCache<TCacheItem, TCacheKey> : IDistributedCache<TCacheItem, TCacheKey>
where TCacheItem : class
where TCacheKey : notnull
{
public const string UowCacheName = "AbpDistributedCache";
public ILogger<DistributedCache<TCacheItem, TCacheKey>> Logger { get; set; }
protected string CacheName { get; set; }
protected string CacheName { get; set; } = default!;
protected bool IgnoreMultiTenancy { get; set; }
@ -164,7 +164,7 @@ public class DistributedCache<TCacheItem, TCacheKey> : IDistributedCache<TCacheI
protected SemaphoreSlim SyncSemaphore { get; }
protected DistributedCacheEntryOptions DefaultCacheOptions;
protected DistributedCacheEntryOptions DefaultCacheOptions = default!;
private readonly AbpDistributedCacheOptions _distributedCacheOption;
@ -195,7 +195,7 @@ public class DistributedCache<TCacheItem, TCacheKey> : IDistributedCache<TCacheI
{
return KeyNormalizer.NormalizeKey(
new DistributedCacheKeyNormalizeArgs(
key.ToString(),
key.ToString()!,
CacheName,
IgnoreMultiTenancy
)
@ -234,7 +234,7 @@ public class DistributedCache<TCacheItem, TCacheKey> : IDistributedCache<TCacheI
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <param name="considerUow">This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.</param>
/// <returns>The cache item, or null.</returns>
public virtual TCacheItem Get(
public virtual TCacheItem? Get(
TCacheKey key,
bool? hideErrors = null,
bool considerUow = false)
@ -250,7 +250,7 @@ public class DistributedCache<TCacheItem, TCacheKey> : IDistributedCache<TCacheI
}
}
byte[] cachedBytes;
byte[]? cachedBytes;
try
{
@ -270,7 +270,7 @@ public class DistributedCache<TCacheItem, TCacheKey> : IDistributedCache<TCacheI
return ToCacheItem(cachedBytes);
}
public virtual KeyValuePair<TCacheKey, TCacheItem>[] GetMany(
public virtual KeyValuePair<TCacheKey, TCacheItem?>[] GetMany(
IEnumerable<TCacheKey> keys,
bool? hideErrors = null,
bool considerUow = false)
@ -288,7 +288,7 @@ public class DistributedCache<TCacheItem, TCacheKey> : IDistributedCache<TCacheI
}
var notCachedKeys = new List<TCacheKey>();
var cachedValues = new List<KeyValuePair<TCacheKey, TCacheItem>>();
var cachedValues = new List<KeyValuePair<TCacheKey, TCacheItem?>>();
if (ShouldConsiderUow(considerUow))
{
var uowCache = GetUnitOfWorkCache();
@ -297,7 +297,7 @@ public class DistributedCache<TCacheItem, TCacheKey> : IDistributedCache<TCacheI
var value = uowCache.GetOrDefault(key)?.GetUnRemovedValueOrNull();
if (value != null)
{
cachedValues.Add(new KeyValuePair<TCacheKey, TCacheItem>(key, value));
cachedValues.Add(new KeyValuePair<TCacheKey, TCacheItem?>(key, value));
}
}
@ -309,7 +309,7 @@ public class DistributedCache<TCacheItem, TCacheKey> : IDistributedCache<TCacheI
}
hideErrors = hideErrors ?? _distributedCacheOption.HideErrors;
byte[][] cachedBytes;
byte[]?[] cachedBytes;
var readKeys = notCachedKeys.Any() ? notCachedKeys.ToArray() : keyArray;
try
@ -330,7 +330,7 @@ public class DistributedCache<TCacheItem, TCacheKey> : IDistributedCache<TCacheI
return cachedValues.Concat(ToCacheItems(cachedBytes, readKeys)).ToArray();
}
protected virtual KeyValuePair<TCacheKey, TCacheItem>[] GetManyFallback(
protected virtual KeyValuePair<TCacheKey, TCacheItem?>[] GetManyFallback(
TCacheKey[] keys,
bool? hideErrors = null,
bool considerUow = false)
@ -340,7 +340,7 @@ public class DistributedCache<TCacheItem, TCacheKey> : IDistributedCache<TCacheI
try
{
return keys
.Select(key => new KeyValuePair<TCacheKey, TCacheItem>(
.Select(key => new KeyValuePair<TCacheKey, TCacheItem?>(
key,
Get(key, false, considerUow)
)
@ -358,7 +358,7 @@ public class DistributedCache<TCacheItem, TCacheKey> : IDistributedCache<TCacheI
}
}
public virtual async Task<KeyValuePair<TCacheKey, TCacheItem>[]> GetManyAsync(
public virtual async Task<KeyValuePair<TCacheKey, TCacheItem?>[]> GetManyAsync(
IEnumerable<TCacheKey> keys,
bool? hideErrors = null,
bool considerUow = false,
@ -378,7 +378,7 @@ public class DistributedCache<TCacheItem, TCacheKey> : IDistributedCache<TCacheI
}
var notCachedKeys = new List<TCacheKey>();
var cachedValues = new List<KeyValuePair<TCacheKey, TCacheItem>>();
var cachedValues = new List<KeyValuePair<TCacheKey, TCacheItem?>>();
if (ShouldConsiderUow(considerUow))
{
var uowCache = GetUnitOfWorkCache();
@ -387,7 +387,7 @@ public class DistributedCache<TCacheItem, TCacheKey> : IDistributedCache<TCacheI
var value = uowCache.GetOrDefault(key)?.GetUnRemovedValueOrNull();
if (value != null)
{
cachedValues.Add(new KeyValuePair<TCacheKey, TCacheItem>(key, value));
cachedValues.Add(new KeyValuePair<TCacheKey, TCacheItem?>(key, value));
}
}
@ -399,7 +399,7 @@ public class DistributedCache<TCacheItem, TCacheKey> : IDistributedCache<TCacheI
}
hideErrors = hideErrors ?? _distributedCacheOption.HideErrors;
byte[][] cachedBytes;
byte[]?[] cachedBytes;
var readKeys = notCachedKeys.Any() ? notCachedKeys.ToArray() : keyArray;
@ -424,7 +424,7 @@ public class DistributedCache<TCacheItem, TCacheKey> : IDistributedCache<TCacheI
return cachedValues.Concat(ToCacheItems(cachedBytes, readKeys)).ToArray();
}
protected virtual async Task<KeyValuePair<TCacheKey, TCacheItem>[]> GetManyFallbackAsync(
protected virtual async Task<KeyValuePair<TCacheKey, TCacheItem?>[]> GetManyFallbackAsync(
TCacheKey[] keys,
bool? hideErrors = null,
bool considerUow = false,
@ -434,11 +434,11 @@ public class DistributedCache<TCacheItem, TCacheKey> : IDistributedCache<TCacheI
try
{
var result = new List<KeyValuePair<TCacheKey, TCacheItem>>();
var result = new List<KeyValuePair<TCacheKey, TCacheItem?>>();
foreach (var key in keys)
{
result.Add(new KeyValuePair<TCacheKey, TCacheItem>(
result.Add(new KeyValuePair<TCacheKey, TCacheItem?>(
key,
await GetAsync(key, false, considerUow, token: token))
);
@ -466,7 +466,7 @@ public class DistributedCache<TCacheItem, TCacheKey> : IDistributedCache<TCacheI
/// <param name="considerUow">This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.</param>
/// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param>
/// <returns>The cache item, or null.</returns>
public virtual async Task<TCacheItem> GetAsync(
public virtual async Task<TCacheItem?> GetAsync(
TCacheKey key,
bool? hideErrors = null,
bool considerUow = false,
@ -483,7 +483,7 @@ public class DistributedCache<TCacheItem, TCacheKey> : IDistributedCache<TCacheI
}
}
byte[] cachedBytes;
byte[]? cachedBytes;
try
{
@ -521,10 +521,10 @@ public class DistributedCache<TCacheItem, TCacheKey> : IDistributedCache<TCacheI
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <param name="considerUow">This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.</param>
/// <returns>The cache item.</returns>
public virtual TCacheItem GetOrAdd(
public virtual TCacheItem? GetOrAdd(
TCacheKey key,
Func<TCacheItem> factory,
Func<DistributedCacheEntryOptions> optionsFactory = null,
Func<DistributedCacheEntryOptions>? optionsFactory = null,
bool? hideErrors = null,
bool considerUow = false)
{
@ -574,10 +574,10 @@ public class DistributedCache<TCacheItem, TCacheKey> : IDistributedCache<TCacheI
/// <param name="considerUow">This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.</param>
/// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param>
/// <returns>The cache item.</returns>
public virtual async Task<TCacheItem> GetOrAddAsync(
public virtual async Task<TCacheItem?> GetOrAddAsync(
TCacheKey key,
Func<Task<TCacheItem>> factory,
Func<DistributedCacheEntryOptions> optionsFactory = null,
Func<DistributedCacheEntryOptions>? optionsFactory = null,
bool? hideErrors = null,
bool considerUow = false,
CancellationToken token = default)
@ -618,15 +618,15 @@ public class DistributedCache<TCacheItem, TCacheKey> : IDistributedCache<TCacheI
return value;
}
public KeyValuePair<TCacheKey, TCacheItem>[] GetOrAddMany(
public KeyValuePair<TCacheKey, TCacheItem?>[] GetOrAddMany(
IEnumerable<TCacheKey> keys,
Func<IEnumerable<TCacheKey>, List<KeyValuePair<TCacheKey, TCacheItem>>> factory,
Func<DistributedCacheEntryOptions> optionsFactory = null,
Func<DistributedCacheEntryOptions>? optionsFactory = null,
bool? hideErrors = null,
bool considerUow = false)
{
KeyValuePair<TCacheKey, TCacheItem>[] result;
KeyValuePair<TCacheKey, TCacheItem?>[] result;
var keyArray = keys.ToArray();
var cacheSupportsMultipleItems = Cache as ICacheSupportsMultipleItems;
@ -641,7 +641,7 @@ public class DistributedCache<TCacheItem, TCacheKey> : IDistributedCache<TCacheI
else
{
var notCachedKeys = new List<TCacheKey>();
var cachedValues = new List<KeyValuePair<TCacheKey, TCacheItem>>();
var cachedValues = new List<KeyValuePair<TCacheKey, TCacheItem?>>();
if (ShouldConsiderUow(considerUow))
{
var uowCache = GetUnitOfWorkCache();
@ -650,7 +650,7 @@ public class DistributedCache<TCacheItem, TCacheKey> : IDistributedCache<TCacheI
var value = uowCache.GetOrDefault(key)?.GetUnRemovedValueOrNull();
if (value != null)
{
cachedValues.Add(new KeyValuePair<TCacheKey, TCacheItem>(key, value));
cachedValues.Add(new KeyValuePair<TCacheKey, TCacheItem?>(key, value));
}
}
@ -662,7 +662,7 @@ public class DistributedCache<TCacheItem, TCacheKey> : IDistributedCache<TCacheI
}
hideErrors = hideErrors ?? _distributedCacheOption.HideErrors;
byte[][] cachedBytes;
byte[]?[] cachedBytes;
var readKeys = notCachedKeys.Any() ? notCachedKeys.ToArray() : keyArray;
try
@ -685,7 +685,7 @@ public class DistributedCache<TCacheItem, TCacheKey> : IDistributedCache<TCacheI
if (result.All(x => x.Value != null))
{
return result;
return result!;
}
var missingKeys = new List<TCacheKey>();
@ -708,22 +708,22 @@ public class DistributedCache<TCacheItem, TCacheKey> : IDistributedCache<TCacheI
foreach (var index in missingValuesIndex)
{
result[index] = valueQueue.Dequeue();
result[index] = valueQueue.Dequeue()!;
}
return result;
}
public async Task<KeyValuePair<TCacheKey, TCacheItem>[]> GetOrAddManyAsync(
public async Task<KeyValuePair<TCacheKey, TCacheItem?>[]> GetOrAddManyAsync(
IEnumerable<TCacheKey> keys,
Func<IEnumerable<TCacheKey>, Task<List<KeyValuePair<TCacheKey, TCacheItem>>>> factory,
Func<DistributedCacheEntryOptions> optionsFactory = null,
Func<DistributedCacheEntryOptions>? optionsFactory = null,
bool? hideErrors = null,
bool considerUow = false,
CancellationToken token = default)
{
KeyValuePair<TCacheKey, TCacheItem>[] result;
KeyValuePair<TCacheKey, TCacheItem?>[] result;
var keyArray = keys.ToArray();
var cacheSupportsMultipleItems = Cache as ICacheSupportsMultipleItems;
@ -737,7 +737,7 @@ public class DistributedCache<TCacheItem, TCacheKey> : IDistributedCache<TCacheI
else
{
var notCachedKeys = new List<TCacheKey>();
var cachedValues = new List<KeyValuePair<TCacheKey, TCacheItem>>();
var cachedValues = new List<KeyValuePair<TCacheKey, TCacheItem?>>();
if (ShouldConsiderUow(considerUow))
{
var uowCache = GetUnitOfWorkCache();
@ -746,7 +746,7 @@ public class DistributedCache<TCacheItem, TCacheKey> : IDistributedCache<TCacheI
var value = uowCache.GetOrDefault(key)?.GetUnRemovedValueOrNull();
if (value != null)
{
cachedValues.Add(new KeyValuePair<TCacheKey, TCacheItem>(key, value));
cachedValues.Add(new KeyValuePair<TCacheKey, TCacheItem?>(key, value));
}
}
@ -758,7 +758,7 @@ public class DistributedCache<TCacheItem, TCacheKey> : IDistributedCache<TCacheI
}
hideErrors = hideErrors ?? _distributedCacheOption.HideErrors;
byte[][] cachedBytes;
byte[]?[] cachedBytes;
var readKeys = notCachedKeys.Any() ? notCachedKeys.ToArray() : keyArray;
try
@ -804,7 +804,7 @@ public class DistributedCache<TCacheItem, TCacheKey> : IDistributedCache<TCacheI
foreach (var index in missingValuesIndex)
{
result[index] = valueQueue.Dequeue();
result[index] = valueQueue.Dequeue()!;
}
return result;
@ -821,7 +821,7 @@ public class DistributedCache<TCacheItem, TCacheKey> : IDistributedCache<TCacheI
public virtual void Set(
TCacheKey key,
TCacheItem value,
DistributedCacheEntryOptions options = null,
DistributedCacheEntryOptions? options = null,
bool? hideErrors = null,
bool considerUow = false)
{
@ -886,7 +886,7 @@ public class DistributedCache<TCacheItem, TCacheKey> : IDistributedCache<TCacheI
public virtual async Task SetAsync(
TCacheKey key,
TCacheItem value,
DistributedCacheEntryOptions options = null,
DistributedCacheEntryOptions? options = null,
bool? hideErrors = null,
bool considerUow = false,
CancellationToken token = default)
@ -939,7 +939,7 @@ public class DistributedCache<TCacheItem, TCacheKey> : IDistributedCache<TCacheI
public void SetMany(
IEnumerable<KeyValuePair<TCacheKey, TCacheItem>> items,
DistributedCacheEntryOptions options = null,
DistributedCacheEntryOptions? options = null,
bool? hideErrors = null,
bool considerUow = false)
{
@ -1012,7 +1012,7 @@ public class DistributedCache<TCacheItem, TCacheKey> : IDistributedCache<TCacheI
protected virtual void SetManyFallback(
KeyValuePair<TCacheKey, TCacheItem>[] items,
DistributedCacheEntryOptions options = null,
DistributedCacheEntryOptions? options = null,
bool? hideErrors = null,
bool considerUow = false)
{
@ -1045,7 +1045,7 @@ public class DistributedCache<TCacheItem, TCacheKey> : IDistributedCache<TCacheI
public virtual async Task SetManyAsync(
IEnumerable<KeyValuePair<TCacheKey, TCacheItem>> items,
DistributedCacheEntryOptions options = null,
DistributedCacheEntryOptions? options = null,
bool? hideErrors = null,
bool considerUow = false,
CancellationToken token = default)
@ -1117,7 +1117,7 @@ public class DistributedCache<TCacheItem, TCacheKey> : IDistributedCache<TCacheI
protected virtual async Task SetManyFallbackAsync(
KeyValuePair<TCacheKey, TCacheItem>[] items,
DistributedCacheEntryOptions options = null,
DistributedCacheEntryOptions? options = null,
bool? hideErrors = null,
bool considerUow = false,
CancellationToken token = default)
@ -1516,19 +1516,19 @@ public class DistributedCache<TCacheItem, TCacheKey> : IDistributedCache<TCacheI
}
}
protected virtual KeyValuePair<TCacheKey, TCacheItem>[] ToCacheItems(byte[][] itemBytes, TCacheKey[] itemKeys)
protected virtual KeyValuePair<TCacheKey, TCacheItem?>[] ToCacheItems(byte[]?[] itemBytes, TCacheKey[] itemKeys)
{
if (itemBytes.Length != itemKeys.Length)
{
throw new AbpException("count of the item bytes should be same with the count of the given keys");
}
var result = new List<KeyValuePair<TCacheKey, TCacheItem>>();
var result = new List<KeyValuePair<TCacheKey, TCacheItem?>>();
for (int i = 0; i < itemKeys.Length; i++)
for (var i = 0; i < itemKeys.Length; i++)
{
result.Add(
new KeyValuePair<TCacheKey, TCacheItem>(
new KeyValuePair<TCacheKey, TCacheItem?>(
itemKeys[i],
ToCacheItem(itemBytes[i])
)
@ -1538,8 +1538,7 @@ public class DistributedCache<TCacheItem, TCacheKey> : IDistributedCache<TCacheI
return result.ToArray();
}
[CanBeNull]
protected virtual TCacheItem ToCacheItem([CanBeNull] byte[] bytes)
protected virtual TCacheItem? ToCacheItem(byte[]? bytes)
{
if (bytes == null)
{
@ -1560,10 +1559,10 @@ public class DistributedCache<TCacheItem, TCacheKey> : IDistributedCache<TCacheI
).ToArray();
}
private static KeyValuePair<TCacheKey, TCacheItem>[] ToCacheItemsWithDefaultValues(TCacheKey[] keys)
private static KeyValuePair<TCacheKey, TCacheItem?>[] ToCacheItemsWithDefaultValues(TCacheKey[] keys)
{
return keys
.Select(key => new KeyValuePair<TCacheKey, TCacheItem>(key, default))
.Select(key => new KeyValuePair<TCacheKey, TCacheItem?>(key, default))
.ToArray();
}

4
framework/src/Volo.Abp.Caching/Volo/Abp/Caching/ICacheSupportsMultipleItems.cs

@ -7,11 +7,11 @@ namespace Volo.Abp.Caching;
public interface ICacheSupportsMultipleItems
{
byte[][] GetMany(
byte[]?[] GetMany(
IEnumerable<string> keys
);
Task<byte[][]> GetManyAsync(
Task<byte[]?[]> GetManyAsync(
IEnumerable<string> keys,
CancellationToken token = default
);

32
framework/src/Volo.Abp.Caching/Volo/Abp/Caching/IDistributedCache.cs

@ -33,7 +33,7 @@ public interface IDistributedCache<TCacheItem, TCacheKey>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <param name="considerUow">This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.</param>
/// <returns>The cache item, or null.</returns>
TCacheItem Get(
TCacheItem? Get(
TCacheKey key,
bool? hideErrors = null,
bool considerUow = false
@ -50,7 +50,7 @@ public interface IDistributedCache<TCacheItem, TCacheKey>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <param name="considerUow">This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.</param>
/// <returns>List of cache items.</returns>
KeyValuePair<TCacheKey, TCacheItem>[] GetMany(
KeyValuePair<TCacheKey, TCacheItem?>[] GetMany(
IEnumerable<TCacheKey> keys,
bool? hideErrors = null,
bool considerUow = false
@ -69,7 +69,7 @@ public interface IDistributedCache<TCacheItem, TCacheKey>
/// <param name="considerUow">This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.</param>
/// /// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param>
/// <returns>List of cache items.</returns>
Task<KeyValuePair<TCacheKey, TCacheItem>[]> GetManyAsync(
Task<KeyValuePair<TCacheKey, TCacheItem?>[]> GetManyAsync(
IEnumerable<TCacheKey> keys,
bool? hideErrors = null,
bool considerUow = false,
@ -84,7 +84,7 @@ public interface IDistributedCache<TCacheItem, TCacheKey>
/// <param name="considerUow">This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.</param>
/// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param>
/// <returns>The cache item, or null.</returns>
Task<TCacheItem> GetAsync(
Task<TCacheItem?> GetAsync(
[NotNull] TCacheKey key,
bool? hideErrors = null,
bool considerUow = false,
@ -101,10 +101,10 @@ public interface IDistributedCache<TCacheItem, TCacheKey>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <param name="considerUow">This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.</param>
/// <returns>The cache item.</returns>
TCacheItem GetOrAdd(
TCacheItem? GetOrAdd(
TCacheKey key,
Func<TCacheItem> factory,
Func<DistributedCacheEntryOptions> optionsFactory = null,
Func<DistributedCacheEntryOptions>? optionsFactory = null,
bool? hideErrors = null,
bool considerUow = false
);
@ -120,10 +120,10 @@ public interface IDistributedCache<TCacheItem, TCacheKey>
/// <param name="considerUow">This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.</param>
/// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param>
/// <returns>The cache item.</returns>
Task<TCacheItem> GetOrAddAsync(
Task<TCacheItem?> GetOrAddAsync(
[NotNull] TCacheKey key,
Func<Task<TCacheItem>> factory,
Func<DistributedCacheEntryOptions> optionsFactory = null,
Func<DistributedCacheEntryOptions>? optionsFactory = null,
bool? hideErrors = null,
bool considerUow = false,
CancellationToken token = default
@ -139,10 +139,10 @@ public interface IDistributedCache<TCacheItem, TCacheKey>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <param name="considerUow">This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.</param>
/// <returns>The cache items.</returns>
KeyValuePair<TCacheKey, TCacheItem>[] GetOrAddMany(
KeyValuePair<TCacheKey, TCacheItem?>[] GetOrAddMany(
IEnumerable<TCacheKey> keys,
Func<IEnumerable<TCacheKey>, List<KeyValuePair<TCacheKey, TCacheItem>>> factory,
Func<DistributedCacheEntryOptions> optionsFactory = null,
Func<DistributedCacheEntryOptions>? optionsFactory = null,
bool? hideErrors = null,
bool considerUow = false
);
@ -158,10 +158,10 @@ public interface IDistributedCache<TCacheItem, TCacheKey>
/// <param name="considerUow">This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.</param>
/// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param>
/// <returns>The cache items.</returns>
Task<KeyValuePair<TCacheKey, TCacheItem>[]> GetOrAddManyAsync(
Task<KeyValuePair<TCacheKey, TCacheItem?>[]> GetOrAddManyAsync(
IEnumerable<TCacheKey> keys,
Func<IEnumerable<TCacheKey>, Task<List<KeyValuePair<TCacheKey, TCacheItem>>>> factory,
Func<DistributedCacheEntryOptions> optionsFactory = null,
Func<DistributedCacheEntryOptions>? optionsFactory = null,
bool? hideErrors = null,
bool considerUow = false,
CancellationToken token = default
@ -178,7 +178,7 @@ public interface IDistributedCache<TCacheItem, TCacheKey>
void Set(
TCacheKey key,
TCacheItem value,
DistributedCacheEntryOptions options = null,
DistributedCacheEntryOptions? options = null,
bool? hideErrors = null,
bool considerUow = false
);
@ -196,7 +196,7 @@ public interface IDistributedCache<TCacheItem, TCacheKey>
Task SetAsync(
[NotNull] TCacheKey key,
[NotNull] TCacheItem value,
[CanBeNull] DistributedCacheEntryOptions options = null,
DistributedCacheEntryOptions? options = null,
bool? hideErrors = null,
bool considerUow = false,
CancellationToken token = default
@ -212,7 +212,7 @@ public interface IDistributedCache<TCacheItem, TCacheKey>
/// <param name="considerUow">This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.</param>
void SetMany(
IEnumerable<KeyValuePair<TCacheKey, TCacheItem>> items,
DistributedCacheEntryOptions options = null,
DistributedCacheEntryOptions? options = null,
bool? hideErrors = null,
bool considerUow = false
);
@ -229,7 +229,7 @@ public interface IDistributedCache<TCacheItem, TCacheKey>
/// <returns>The <see cref="T:System.Threading.Tasks.Task" /> indicating that the operation is asynchronous.</returns>
Task SetManyAsync(
IEnumerable<KeyValuePair<TCacheKey, TCacheItem>> items,
DistributedCacheEntryOptions options = null,
DistributedCacheEntryOptions? options = null,
bool? hideErrors = null,
bool considerUow = false,
CancellationToken token = default

2
framework/src/Volo.Abp.Caching/Volo/Abp/Caching/UnitOfWorkCacheItem.cs

@ -8,7 +8,7 @@ public class UnitOfWorkCacheItem<TValue>
{
public bool IsRemoved { get; set; }
public TValue Value { get; set; }
public TValue? Value { get; set; }
public UnitOfWorkCacheItem()
{

2
framework/src/Volo.Abp.Caching/Volo/Abp/Caching/UnitOfWorkCacheItemExtensions.cs

@ -2,7 +2,7 @@
public static class UnitOfWorkCacheItemExtensions
{
public static TValue GetUnRemovedValueOrNull<TValue>(this UnitOfWorkCacheItem<TValue> item)
public static TValue? GetUnRemovedValueOrNull<TValue>(this UnitOfWorkCacheItem<TValue>? item)
where TValue : class
{
return item != null && !item.IsRemoved ? item.Value : null;

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

@ -64,7 +64,8 @@ public class AbpCliCoreModule : AbpModule
options.Commands[InstallLibsCommand.Name] = typeof(InstallLibsCommand);
options.Commands[CleanCommand.Name] = typeof(CleanCommand);
options.Commands[CliCommand.Name] = typeof(CliCommand);
options.Commands[ClearDownloadCacheCommand.Name] = typeof(ClearDownloadCacheCommand);
options.DisabledModulesToAddToSolution.Add("Volo.Abp.LeptonXTheme.Pro");
options.DisabledModulesToAddToSolution.Add("Volo.Abp.LeptonXTheme.Lite");
});

59
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ClearDownloadCacheCommand.cs

@ -0,0 +1,59 @@
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Volo.Abp.Cli.Args;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.Cli.Commands;
public class ClearDownloadCacheCommand : IConsoleCommand, ITransientDependency
{
public const string Name = "clear-download-cache";
public ILogger<ClearDownloadCacheCommand> Logger { get; set; }
public ClearDownloadCacheCommand()
{
Logger = NullLogger<ClearDownloadCacheCommand>.Instance;
}
public async Task ExecuteAsync(CommandLineArgs commandLineArgs)
{
Logger.LogInformation("Clearing the templates download cache...");
foreach (var cacheFile in Directory.GetFiles(CliPaths.TemplateCache, "*.zip"))
{
Logger.LogInformation($"Deleting {cacheFile}");
try
{
File.Delete(cacheFile);
}
catch (Exception e)
{
Logger.LogError(e, $"Could not delete {cacheFile}");
}
}
Logger.LogInformation("Done.");
await Task.CompletedTask;
}
public string GetUsageInfo()
{
var sb = new StringBuilder();
sb.AppendLine("");
sb.AppendLine("Usage:");
sb.AppendLine(" abp clear-download-cache");
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 "Clears the templates download cache.";
}
}

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

@ -24,40 +24,40 @@ namespace Volo.Abp.Cli.Commands;
public class NewCommand : ProjectCreationCommandBase, IConsoleCommand, ITransientDependency
{
public const string Name = "new";
protected TemplateProjectBuilder TemplateProjectBuilder { get; }
public ITemplateInfoProvider TemplateInfoProvider { get; }
public NewCommand(
ConnectionStringProvider connectionStringProvider,
ConnectionStringProvider connectionStringProvider,
SolutionPackageVersionFinder solutionPackageVersionFinder,
ICmdHelper cmdHelper,
IInstallLibsService installLibsService,
IInstallLibsService installLibsService,
CliService cliService,
AngularPwaSupportAdder angularPwaSupportAdder,
AngularPwaSupportAdder angularPwaSupportAdder,
InitialMigrationCreator initialMigrationCreator,
ThemePackageAdder themePackageAdder,
ILocalEventBus eventBus,
ThemePackageAdder themePackageAdder,
ILocalEventBus eventBus,
IBundlingService bundlingService,
ITemplateInfoProvider templateInfoProvider,
ITemplateInfoProvider templateInfoProvider,
TemplateProjectBuilder templateProjectBuilder,
AngularThemeConfigurer angularThemeConfigurer) :
base(connectionStringProvider,
solutionPackageVersionFinder,
cmdHelper,
installLibsService,
cliService,
solutionPackageVersionFinder,
cmdHelper,
installLibsService,
cliService,
angularPwaSupportAdder,
initialMigrationCreator,
themePackageAdder,
eventBus,
themePackageAdder,
eventBus,
bundlingService,
angularThemeConfigurer)
{
TemplateInfoProvider = templateInfoProvider;
TemplateProjectBuilder = templateProjectBuilder;
}
public async Task ExecuteAsync(CommandLineArgs commandLineArgs)
{
var projectName = NamespaceHelper.NormalizeNamespace(commandLineArgs.Target);
@ -100,20 +100,20 @@ public class NewCommand : ProjectCreationCommandBase, IConsoleCommand, ITransien
ConfigureNpmPackagesForTheme(projectArgs);
await RunGraphBuildForMicroserviceServiceTemplate(projectArgs);
await CreateInitialMigrationsAsync(projectArgs);
var skipInstallLibs = commandLineArgs.Options.ContainsKey(Options.SkipInstallingLibs.Long) || commandLineArgs.Options.ContainsKey(Options.SkipInstallingLibs.Short);
if (!skipInstallLibs)
{
await RunInstallLibsForWebTemplateAsync(projectArgs);
ConfigureAngularJsonForThemeSelection(projectArgs);
}
var skipBundling = commandLineArgs.Options.ContainsKey(Options.SkipBundling.Long) || commandLineArgs.Options.ContainsKey(Options.SkipBundling.Short);
if (!skipBundling)
{
await RunBundleForBlazorWasmOrMauiBlazorTemplateAsync(projectArgs);
}
await ConfigurePwaSupportForAngular(projectArgs);
OpenRelatedWebPage(projectArgs, template, isTiered, commandLineArgs);
@ -149,6 +149,7 @@ public class NewCommand : ProjectCreationCommandBase, IConsoleCommand, ITransien
sb.AppendLine("--local-framework-ref --abp-path <your-local-abp-repo-path> (keeps local references to projects instead of replacing with NuGet package references)");
sb.AppendLine("-sib|--skip-installing-libs (Doesn't run `abp install-libs` command after project creation)");
sb.AppendLine("-sb|--skip-bundling (Doesn't run `abp bundle` command after Blazor Wasm project creation)");
sb.AppendLine("-sc|--skip-cache (Always download the latest from our server and refresh their templates folder cache)");
sb.AppendLine("");
sb.AppendLine("Examples:");
sb.AppendLine("");

38
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ProjectCreationCommandBase.cs

@ -39,7 +39,7 @@ public abstract class ProjectCreationCommandBase
public ILogger<NewCommand> Logger { get; set; }
public ThemePackageAdder ThemePackageAdder { get; }
public AngularThemeConfigurer AngularThemeConfigurer { get; }
public ProjectCreationCommandBase(
@ -52,7 +52,7 @@ public abstract class ProjectCreationCommandBase
InitialMigrationCreator initialMigrationCreator,
ThemePackageAdder themePackageAdder,
ILocalEventBus eventBus,
IBundlingService bundlingService,
IBundlingService bundlingService,
AngularThemeConfigurer angularThemeConfigurer)
{
_bundlingService = bundlingService;
@ -221,10 +221,12 @@ public abstract class ProjectCreationCommandBase
}
commandLineArgs.Options.Add(CliConsts.Command, commandLineArgs.Command);
var theme = uiFramework == UiFramework.None ? (Theme?)null : GetThemeByTemplateOrNull(commandLineArgs, template);
var themeStyle = theme.HasValue ? GetThemeStyleOrNull(commandLineArgs, theme.Value) : (ThemeStyle?)null;
var skipCache = commandLineArgs.Options.ContainsKey(Options.SkipCache.Long) || commandLineArgs.Options.ContainsKey(Options.SkipCache.Short);
return new ProjectBuildArgs(
solutionName,
template,
@ -242,7 +244,8 @@ public abstract class ProjectCreationCommandBase
connectionString,
pwa,
theme,
themeStyle
themeStyle,
skipCache
);
}
@ -417,7 +420,7 @@ public abstract class ProjectCreationCommandBase
protected async Task RunBundleForBlazorWasmOrMauiBlazorTemplateAsync(ProjectBuildArgs projectArgs)
{
if ((AppTemplateBase.IsAppTemplate(projectArgs.TemplateName) || AppNoLayersTemplateBase.IsAppNoLayersTemplate(projectArgs.TemplateName))
if ((AppTemplateBase.IsAppTemplate(projectArgs.TemplateName) || AppNoLayersTemplateBase.IsAppNoLayersTemplate(projectArgs.TemplateName))
&& projectArgs.UiFramework is UiFramework.Blazor or UiFramework.MauiBlazor)
{
var isWebassembly = projectArgs.UiFramework == UiFramework.Blazor;
@ -432,7 +435,7 @@ public abstract class ProjectCreationCommandBase
var directory = Path.GetDirectoryName(
Directory.GetFiles(projectArgs.OutputFolder, isWebassembly? "*.Blazor.csproj" :"*.MauiBlazor.csproj", SearchOption.AllDirectories).First()
);
await _bundlingService.BundleAsync(directory, true, projectType: isWebassembly ? BundlingConsts.WebAssembly : BundlingConsts.MauiBlazor);
}
}
@ -584,10 +587,10 @@ public abstract class ProjectCreationCommandBase
{
// null or "leptonx-lite" => Theme.LeptonXLite,
"basic" => Theme.Basic,
_ => Theme.LeptonXLite
_ => Theme.LeptonXLite
};
}
Theme GetAppProTheme()
{
return theme switch
@ -600,16 +603,16 @@ public abstract class ProjectCreationCommandBase
}
}
protected virtual ThemeStyle? GetThemeStyleOrNull(CommandLineArgs commandLineArgs, Theme theme)
protected virtual ThemeStyle? GetThemeStyleOrNull(CommandLineArgs commandLineArgs, Theme theme)
{
if(theme != Theme.LeptonX)
if(theme != Theme.LeptonX)
{
return null;
}
var themeStyle = commandLineArgs.Options.GetOrNull(Options.ThemeStyle.Long)?.ToLower();
return themeStyle switch
return themeStyle switch
{
"system" or null => ThemeStyle.System,
"dim" => ThemeStyle.Dim,
@ -660,7 +663,7 @@ public abstract class ProjectCreationCommandBase
ThemePackageAdder.AddAngularPackage(projectArgs.OutputFolder, "@abp/ng.theme.basic", projectArgs.Version);
}
}
private void ConfigureNpmPackagesForLeptonTheme(ProjectBuildArgs projectArgs)
{
if (projectArgs.UiFramework is not UiFramework.None or UiFramework.Angular)
@ -686,7 +689,7 @@ public abstract class ProjectCreationCommandBase
{
return;
}
if (projectArgs.Theme.HasValue && projectArgs.UiFramework == UiFramework.Angular)
{
var angularFolderPath = projectArgs.TemplateName == MicroserviceProTemplate.TemplateName
@ -790,6 +793,13 @@ public abstract class ProjectCreationCommandBase
public const string Long = "skip-bundling";
}
public static class SkipCache
{
public const string Short = "sc";
public const string Long = "skip-cache";
}
public static class Tiered
{
public const string Long = "tiered";

2
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/SourceCodeDownloadService.cs

@ -33,7 +33,7 @@ public class SourceCodeDownloadService : ITransientDependency
public async Task DownloadModuleAsync(string moduleName, string outputFolder, string version, string gitHubAbpLocalRepositoryPath, string gitHubVoloLocalRepositoryPath, AbpCommandLineOptions options)
{
Logger.LogInformation($"Downloading source code of {moduleName} (v{version ?? "Latest"})");
Logger.LogInformation($"Downloading source code of {moduleName} ({(version != null ? "v" + version : "Latest")})");
Logger.LogInformation("Output folder: " + outputFolder);
var result = await ModuleProjectBuilder.BuildAsync(

12
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs

@ -59,7 +59,8 @@ public class AbpIoSourceCodeStore : ISourceCodeStore, ITransientDependency
string type,
string version = null,
string templateSource = null,
bool includePreReleases = false)
bool includePreReleases = false,
bool skipCache = false)
{
DirectoryHelper.CreateIfNotExists(CliPaths.TemplateCache);
var latestVersion = version ?? await GetLatestSourceCodeVersionAsync(name, type, null, includePreReleases);
@ -111,7 +112,7 @@ public class AbpIoSourceCodeStore : ISourceCodeStore, ITransientDependency
}
#endif
if (Options.CacheTemplates && File.Exists(localCacheFile) && templateSource.IsNullOrWhiteSpace())
if (Options.CacheTemplates && !skipCache && File.Exists(localCacheFile) && templateSource.IsNullOrWhiteSpace())
{
Logger.LogInformation("Using cached " + type + ": " + name + ", version: " + version);
return new TemplateFile(File.ReadAllBytes(localCacheFile), version, latestVersion, nugetVersion);
@ -132,6 +133,7 @@ public class AbpIoSourceCodeStore : ISourceCodeStore, ITransientDependency
if (Options.CacheTemplates && templateSource.IsNullOrWhiteSpace())
{
File.Delete(localCacheFile);
File.WriteAllBytes(localCacheFile, fileContent);
}
@ -218,7 +220,7 @@ public class AbpIoSourceCodeStore : ISourceCodeStore, ITransientDependency
var result = await response.Content.ReadAsStringAsync();
var versions = JsonSerializer.Deserialize<GithubReleaseVersions>(result);
return templateName.Contains("LeptonX") ?
return templateName.Contains("LeptonX") ?
versions.LeptonXVersions.Any(v => v.Name == version) :
versions.FrameworkAndCommercialVersions.Any(v => v.Name == version);
}
@ -335,7 +337,7 @@ public class AbpIoSourceCodeStore : ISourceCodeStore, ITransientDependency
public class GithubReleaseVersions
{
public List<GithubRelease> FrameworkAndCommercialVersions { get; set; }
public List<GithubRelease> LeptonXVersions { get; set; }
}
}
}

724
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ChangeThemeStep.cs

@ -10,6 +10,11 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps;
public class ChangeThemeStep : ProjectBuildPipelineStep
{
private const string Basic = "Basic";
private const string LeptonXLite = "LeptonXLite";
private const string LeptonX = "LeptonX";
private const string Lepton = "Lepton";
public override void Execute(ProjectBuildContext context)
{
if (!context.BuildArgs.Theme.HasValue)
@ -28,50 +33,65 @@ public class ChangeThemeStep : ProjectBuildPipelineStep
}
}
protected void ChangeToBasicTheme(ProjectBuildContext context)
protected virtual void ChangeToBasicTheme(ProjectBuildContext context)
{
var defaultThemeName = context.BuildArgs.TemplateName is AppTemplate.TemplateName or AppNoLayersTemplate.TemplateName
? "LeptonXLite"
: "LeptonX";
#region MVC Projects
? LeptonXLite : LeptonX;
ChangeThemeToBasicForMvcProjects(context, defaultThemeName);
ChangeThemeToBasicForBlazorProjects(context, defaultThemeName);
ChangeThemeToBasicForBlazorServerProjects(context, defaultThemeName);
ChangeThemeForAngularProjects(context, defaultThemeName, Basic, GetAngularPackageName(context.BuildArgs.Theme!.Value), GetAngularPackageName(Theme.Basic));
}
#endregion
protected virtual void ChangeToLeptonTheme(ProjectBuildContext context)
{
//common
RenameFolders(context, oldFolderName: LeptonX , newFolderName: Lepton);
AddLeptonThemeManagementReferenceToProjects(context);
#region MyCompanyName.MyProjectName.Blazor
ChangeThemeToLeptonForMvcProjects(context);
ChangeThemeToLeptonForBlazorProjects(context);
ChangeThemeToLeptonForBlazorServerProjects(context);
ChangeThemeForAngularProjects(context, oldThemeName: LeptonX, Lepton, GetAngularPackageName(Theme.LeptonX), GetAngularPackageName(Theme.Lepton));
ConfigureLeptonManagementPackagesForNoLayersMvc(context, "/MyCompanyName.MyProjectName.Mvc/MyCompanyName.MyProjectName.csproj", new[] { "Web", "HttpApi", "Application" });
ChangeThemeToLeptonForNoLayersBlazorServerProjects(context);
ChangeThemeToLeptonForMauiBlazorProjects(context);
}
private static string GetAngularPackageName(Theme theme)
{
return theme switch
{
Theme.LeptonX => "@volosoft/abp.ng.theme.lepton-x",
Theme.LeptonXLite => "@abp/ng.theme.lepton-x",
Theme.Basic => "@abp/ng.theme.basic",
_ => string.Empty
};
}
private static void ChangeThemeToBasicForBlazorProjects(ProjectBuildContext context, string defaultThemeName)
{
ReplacePackageReferenceWithProjectReference(
context,
"/MyCompanyName.MyProjectName.Blazor/MyCompanyName.MyProjectName.Blazor.csproj",
$"Volo.Abp.AspNetCore.Components.WebAssembly.{defaultThemeName}Theme",
@"..\..\..\..\..\modules\basic-theme\src\Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme\Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.csproj"
);
ChangeNamespaceAndKeyword(
context,
"/MyCompanyName.MyProjectName.Blazor/MyProjectNameBlazorModule.cs",
$"Volo.Abp.AspNetCore.Components.WebAssembly.{defaultThemeName}Theme",
"Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme",
$"AbpAspNetCoreComponentsWebAssembly{defaultThemeName}ThemeModule",
"AbpAspNetCoreComponentsWebAssemblyBasicThemeModule"
);
ChangeNamespaceAndKeyword(
ReplaceAllKeywords(
context,
"/MyCompanyName.MyProjectName.Blazor/MyProjectNameBlazorModule.cs",
$"Volo.Abp.AspNetCore.Components.Web.{defaultThemeName}Theme.Themes.{defaultThemeName}",
"Volo.Abp.AspNetCore.Components.Web.BasicTheme.Themes.Basic",
$"AbpAspNetCoreComponentsWebAssembly{defaultThemeName}ThemeModule",
"AbpAspNetCoreComponentsWebAssemblyBasicThemeModule"
$"{defaultThemeName}Theme.Components",
"BasicTheme.Themes.Basic"
);
ChangeNamespace(
ReplaceAllKeywords(
context,
"/MyCompanyName.MyProjectName.Blazor/MyProjectNameBlazorModule.cs",
$"Volo.Abp.AspNetCore.Components.Web.{defaultThemeName}Theme.Components",
"Volo.Abp.AspNetCore.Components.Web.BasicTheme.Themes.Basic"
defaultThemeName,
Basic
);
ReplacePackageReferenceWithProjectReference(
@ -81,209 +101,94 @@ public class ChangeThemeStep : ProjectBuildPipelineStep
@"..\..\..\..\..\modules\basic-theme\src\Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic\Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.csproj"
);
ChangeNamespaceAndKeyword(
context,
"/MyCompanyName.MyProjectName.Host/MyProjectNameHostModule.cs",
$"Volo.Abp.AspNetCore.Mvc.UI.Theme.{defaultThemeName}.Bundling",
"Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Bundling",
$"{defaultThemeName}ThemeBundles.Styles.Global",
"BasicThemeBundles.Styles.Global"
);
ChangeNamespaceAndKeyword(
ReplaceAllKeywords(
context,
"/MyCompanyName.MyProjectName.Host/MyProjectNameHostModule.cs",
$"Volo.Abp.AspNetCore.Mvc.UI.Theme.{defaultThemeName}",
"Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic",
$"AbpAspNetCoreMvcUi{defaultThemeName}ThemeModule",
"AbpAspNetCoreMvcUiBasicThemeModule"
defaultThemeName,
Basic
);
}
#endregion
#region Blazor.Server Projects
ChangeThemeToBasicForBlazorProjects(context, defaultThemeName);
#endregion
#region Angular
var angularPackageName = context.BuildArgs.TemplateName is AppTemplate.TemplateName or AppNoLayersTemplate.TemplateName
? "@abp/ng.theme.lepton-x"
: "@volosoft/abp.ng.theme.lepton-x";
private static void ChangeThemeForAngularProjects(ProjectBuildContext context, string oldThemeName, string newThemeName, string oldPackageName, string newPackageName)
{
if (context.BuildArgs.UiFramework != UiFramework.Angular)
{
return;
}
ReplaceImportPackage(
context,
"/angular/src/app/app.module.ts",
angularPackageName,
"@abp/ng.theme.basic"
oldPackageName,
newPackageName
);
RemoveLinesByStatement(
context,
"/angular/src/app/app.module.ts",
"SideMenuLayoutModule"
);
ReplaceMethodNames(
ReplaceAllKeywords(
context,
"/angular/src/app/app.module.ts",
"ThemeLeptonXModule",
"ThemeBasicModule"
);
RemoveLinesByStatement(
context,
"/angular/angular.json",
"node_modules/bootstrap-icons/font/bootstrap-icons.css"
$"Theme{oldThemeName}Module",
$"Theme{newThemeName}Module"
);
if(defaultThemeName == "LeptonX")
if (oldThemeName != LeptonX)
{
ReplaceMethodNames(
context,
"/angular/src/app/app.module.ts",
"HttpErrorComponent, ",
""
);
ChangeModuleImportBetweenStatements(
context,
"/angular/src/app/app.module.ts",
"ThemeSharedModule.forRoot",
"AccountAdminConfigModule.forRoot",
"ThemeSharedModule.forRoot(),"
);
return;
}
#endregion
ReplaceAllKeywords(
context,
"/angular/src/app/app.module.ts",
"HttpErrorComponent, ",
""
);
ChangeModuleImportBetweenStatements(
context,
"/angular/src/app/app.module.ts",
"ThemeSharedModule.forRoot",
"AccountAdminConfigModule.forRoot",
"ThemeSharedModule.forRoot(),"
);
}
protected void ChangeToLeptonTheme(ProjectBuildContext context)
private static void ChangeThemeToLeptonForBlazorProjects(ProjectBuildContext context)
{
#region Common
RenameLeptonXFolders(context, folderName: "Lepton");
AddLeptonThemeManagementReferenceToProjects(context);
#endregion
#region MVC Projects
ChangeThemeToLeptonForMvcProjects(context);
#endregion
#region MyCompanyName.MyProjectName.Blazor
ReplacePackageReferenceWithProjectReference(
context,
"/MyCompanyName.MyProjectName.Blazor/MyCompanyName.MyProjectName.Blazor.csproj",
"Volo.Abp.AspNetCore.Components.WebAssembly.LeptonXTheme",
@"..\..\..\..\..\lepton-theme\src\Volo.Abp.AspNetCore.Components.WebAssembly.LeptonTheme\Volo.Abp.AspNetCore.Components.WebAssembly.LeptonTheme.csproj"
);
ChangeNamespaceAndKeyword(
ReplaceAllKeywords(
context,
"/MyCompanyName.MyProjectName.Blazor/MyProjectNameBlazorModule.cs",
"Volo.Abp.AspNetCore.Components.Web.LeptonXTheme.Components",
"Volo.Abp.AspNetCore.Components.Web.LeptonTheme.Components",
"AbpAspNetCoreComponentsWebAssemblyLeptonXThemeModule",
"AbpAspNetCoreComponentsWebAssemblyLeptonThemeModule"
LeptonX,
Lepton
);
ChangeNamespace(
context,
"/MyCompanyName.MyProjectName.Blazor/MyProjectNameBlazorModule.cs",
"Volo.Abp.AspNetCore.Components.WebAssembly.LeptonXTheme",
"Volo.Abp.AspNetCore.Components.WebAssembly.LeptonTheme"
);
ReplacePackageReferenceWithProjectReference(
context,
"/MyCompanyName.MyProjectName.Host/MyCompanyName.MyProjectName.Host.csproj",
"Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonX",
@"..\..\..\..\..\lepton-theme\src\Volo.Abp.AspNetCore.Mvc.UI.Theme.Lepton\Volo.Abp.AspNetCore.Mvc.UI.Theme.Lepton.csproj"
);
ChangeNamespaceAndKeyword(
context,
"/MyCompanyName.MyProjectName.Host/MyProjectNameHostModule.cs",
"Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonX.Bundling",
"Volo.Abp.AspNetCore.Mvc.UI.Theme.Lepton.Bundling",
"LeptonXThemeBundles.Styles.Global",
"LeptonThemeBundles.Styles.Global"
);
ChangeNamespaceAndKeyword(
ReplaceAllKeywords(
context,
"/MyCompanyName.MyProjectName.Host/MyProjectNameHostModule.cs",
"Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonX",
"Volo.Abp.AspNetCore.Mvc.UI.Theme.Lepton",
"AbpAspNetCoreMvcUiLeptonXThemeModule",
"AbpAspNetCoreMvcUiLeptonThemeModule"
);
#endregion
#region MyCompanyName.MyProjectName.Blazor.Server && MyCompanyName.MyProjectName.Blazor.Server.Tiered
ChangeThemeToLeptonForBlazorServerProjects(context);
#endregion
#region Angular
ReplaceImportPackage(
context,
"/angular/src/app/app.module.ts",
"@volosoft/abp.ng.theme.lepton-x",
"@volo/abp.ng.theme.lepton"
);
RemoveLinesByStatement(
context,
"/angular/src/app/app.module.ts",
"SideMenuLayoutModule"
);
ReplaceMethodNames(
context,
"/angular/src/app/app.module.ts",
"ThemeLeptonXModule",
"ThemeLeptonModule"
);
RemoveLinesByStatement(
context,
"/angular/angular.json",
"node_modules/bootstrap-icons/font/bootstrap-icons.css"
LeptonX,
Lepton
);
#endregion
#region MyCompanyName.MyProjectName.Mvc && MyCompanyName.MyProjectName.Mvc.Mongo
var projectNames = new[] {"Web", "HttpApi", "Application"};
ConfigureLeptonManagementPackagesForNoLayersMvc(context, @"/MyCompanyName.MyProjectName.Mvc/MyCompanyName.MyProjectName.csproj", projectNames);
#endregion
#region MyCompanyName.MyProjectName.Blazor.Server && MyCompanyName.MyProjectName.Blazor.Server.Mongo - (app-nolayers)
ChangeThemeToLeptonForNoLayersBlazorServerProjects(context);
#endregion
#region MyCompanyName.MyProjectName.MauiBlazor
ChangeThemeToLeptonForMauiBlazorProjects(context);
#endregion
}
private void ConfigureLeptonManagementPackagesForNoLayersMvc(ProjectBuildContext context, string targetProjectPath, string[] projectNames)
private static void ConfigureLeptonManagementPackagesForNoLayersMvc(ProjectBuildContext context, string targetProjectPath, IEnumerable<string> projectNames)
{
var file = context.Files.FirstOrDefault(f => !f.Name.Contains("Test") && f.Name.Contains(targetProjectPath) && f.Name.Contains(".csproj"));
if (file == null)
@ -304,7 +209,7 @@ public class ChangeThemeStep : ProjectBuildPipelineStep
}
}
private void ChangeThemeToLeptonForMvcProjects(ProjectBuildContext context)
private static void ChangeThemeToLeptonForMvcProjects(ProjectBuildContext context)
{
var projectNames = new[]
{
@ -333,23 +238,12 @@ public class ChangeThemeStep : ProjectBuildPipelineStep
"Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonX",
@"..\..\..\..\..\lepton-theme\src\Volo.Abp.AspNetCore.Mvc.UI.Theme.Lepton\Volo.Abp.AspNetCore.Mvc.UI.Theme.Lepton.csproj"
);
ChangeNamespaceAndKeyword(
context,
moduleFile.Name,
"Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonX.Bundling",
"Volo.Abp.AspNetCore.Mvc.UI.Theme.Lepton.Bundling",
"LeptonXThemeBundles.Styles.Global",
"LeptonThemeBundles.Styles.Global"
);
ChangeNamespaceAndKeyword(
ReplaceAllKeywords(
context,
moduleFile.Name,
"Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonX",
"Volo.Abp.AspNetCore.Mvc.UI.Theme.Lepton",
"AbpAspNetCoreMvcUiLeptonXThemeModule",
"AbpAspNetCoreMvcUiLeptonThemeModule"
LeptonX,
Lepton
);
RemoveLinesByStatement(
@ -360,7 +254,7 @@ public class ChangeThemeStep : ProjectBuildPipelineStep
}
}
private void AddLeptonThemeManagementReferenceToProjects(ProjectBuildContext context)
private static void AddLeptonThemeManagementReferenceToProjects(ProjectBuildContext context)
{
var projects = new Dictionary<string, string>
{
@ -396,7 +290,7 @@ public class ChangeThemeStep : ProjectBuildPipelineStep
}
}
private void AddUiProjectToProjects(Dictionary<string, string> projects, ProjectBuildContext context)
private static void AddUiProjectToProjects(Dictionary<string, string> projects, ProjectBuildContext context)
{
if (projects.IsNullOrEmpty())
{
@ -418,7 +312,7 @@ public class ChangeThemeStep : ProjectBuildPipelineStep
}
}
private void AddLeptonThemeManagementReference(ProjectBuildContext context, KeyValuePair<string, string> projectInfo)
private static void AddLeptonThemeManagementReference(ProjectBuildContext context, KeyValuePair<string, string> projectInfo)
{
var reference = $@"..\..\..\..\..\lepton-theme\src\Volo.Abp.LeptonTheme.Management.{projectInfo.Key}\Volo.Abp.LeptonTheme.Management.{projectInfo.Key}.csproj";
var projectFile = context.Files.FirstOrDefault(f => !f.Name.Contains("Test") && f.Name.Contains(projectInfo.Value) && f.Name.Contains(".csproj"));
@ -439,7 +333,7 @@ public class ChangeThemeStep : ProjectBuildPipelineStep
underManagementFolder: projectInfo.Key != "HttpApi");
}
private void AddModuleDependency(FileEntry moduleFile, string projectName, string dependency, bool underManagementFolder = true)
private static void AddModuleDependency(FileEntry moduleFile, string projectName, string dependency, bool underManagementFolder = true)
{
var projectNames = new[] { "Blazor", "Blazor.Server", "Blazor.WebAssembly" };
@ -460,11 +354,7 @@ public class ChangeThemeStep : ProjectBuildPipelineStep
moduleFile.SetLines(lines);
}
protected void ReplacePackageReferenceWithProjectReference(
ProjectBuildContext context,
string targetProjectFilePath,
string packageReference,
string projectReference)
private static void ReplacePackageReferenceWithProjectReference(ProjectBuildContext context, string targetProjectFilePath, string packageReference, string projectReference)
{
var file = context.Files.FirstOrDefault(x => x.Name.Contains(targetProjectFilePath));
if (file == null)
@ -485,97 +375,7 @@ public class ChangeThemeStep : ProjectBuildPipelineStep
file.SetLines(lines);
}
protected void ChangeNamespaceAndKeyword(
ProjectBuildContext context,
string targetModuleFilePath,
string oldNamespace,
string newNamespace,
string oldKeyword,
string newKeyword)
{
var file = context.Files.FirstOrDefault(x => x.Name.Contains(targetModuleFilePath));
if (file == null)
{
return;
}
file.NormalizeLineEndings();
var lines = file.GetLines();
for (var i = 0; i < lines.Length; i++)
{
if (lines[i].Contains($"using {oldNamespace}"))
{
lines[i] = lines[i].Replace($"using {oldNamespace}", $"using {newNamespace}");
}
else if (lines[i].Contains(oldKeyword))
{
lines[i] = lines[i].Replace(oldKeyword, newKeyword);
}
}
file.SetLines(lines);
}
protected void ChangeNamespace(
ProjectBuildContext context,
string targetModuleFilePath,
string oldNamespace,
string newNamespace)
{
var file = context.Files.FirstOrDefault(x => x.Name.Contains(targetModuleFilePath));
if (file == null)
{
return;
}
file.NormalizeLineEndings();
var lines = file.GetLines();
for (var i = 0; i < lines.Length; i++)
{
if (lines[i].Contains($"using {oldNamespace}"))
{
lines[i] = lines[i].Replace($"using {oldNamespace}", $"using {newNamespace}");
}
}
file.SetLines(lines);
}
protected void ChangeKeyword(
ProjectBuildContext context,
string targetModuleFilePath,
string oldKeyword,
string newKeyword)
{
var file = context.Files.FirstOrDefault(x => x.Name.Contains(targetModuleFilePath));
if (file == null)
{
return;
}
file.NormalizeLineEndings();
var lines = file.GetLines();
for (var i = 0; i < lines.Length; i++)
{
if (lines[i].Contains(oldKeyword))
{
lines[i] = lines[i].Replace(oldKeyword, newKeyword);
}
}
file.SetLines(lines);
}
protected void ReplaceImportPackage(
ProjectBuildContext context,
string filePath,
string oldImportPackage,
string newImportPackage)
private static void ReplaceImportPackage(ProjectBuildContext context, string filePath, string oldImportPackage, string newImportPackage)
{
var file = context.Files.FirstOrDefault(x => x.Name.Contains(filePath));
if (file == null)
@ -584,6 +384,7 @@ public class ChangeThemeStep : ProjectBuildPipelineStep
}
file.NormalizeLineEndings();
var lines = file.GetLines();
var lineIndex = lines.FindIndex(line => line.Contains($"from '{oldImportPackage}'"));
if (lineIndex == -1)
@ -595,10 +396,7 @@ public class ChangeThemeStep : ProjectBuildPipelineStep
file.SetLines(lines);
}
protected void RemoveLinesByStatement(
ProjectBuildContext context,
string filePath,
string statement)
private static void RemoveLinesByStatement(ProjectBuildContext context, string filePath, string statement)
{
var file = context.Files.FirstOrDefault(x => x.Name.Contains(filePath));
if (file == null)
@ -620,12 +418,7 @@ public class ChangeThemeStep : ProjectBuildPipelineStep
file.SetLines(lines.Where(x => x != null));
}
private void ChangeModuleImportBetweenStatements(
ProjectBuildContext context,
string filePath,
string firstStatement,
string lastStatement,
string newStatement)
private static void ChangeModuleImportBetweenStatements(ProjectBuildContext context, string filePath, string firstStatement, string lastStatement, string newStatement)
{
var file = context.Files.FirstOrDefault(x => x.Name.Contains(filePath));
if (file == null)
@ -651,36 +444,9 @@ public class ChangeThemeStep : ProjectBuildPipelineStep
lines[i] = null;
}
file.SetLines(lines.Where(x => x != null));
}
protected void ReplaceMethodNames(
ProjectBuildContext context,
string filePath,
string oldMethodName,
string newMethodName)
{
var file = context.Files.FirstOrDefault(x => x.Name.Contains(filePath));
if (file == null)
{
return;
}
file.NormalizeLineEndings();
var lines = file.GetLines();
for (var i = 0; i < lines.Length; i++)
{
if (lines[i].Contains(oldMethodName))
{
lines[i] = lines[i].Replace(oldMethodName, newMethodName);
}
}
file.SetLines(lines);
}
private static void AddProjectReference(FileEntry file, string reference)
{
if (!file.Name.Contains(".csproj"))
@ -746,7 +512,7 @@ public class ChangeThemeStep : ProjectBuildPipelineStep
splittedProjectFileName = splittedProjectFileName.Take(splittedProjectFileName.Length - 1).ToArray();
var fileName = splittedProjectFileName?.Last();
var fileName = splittedProjectFileName.Last();
if (fileName == null)
{
return null;
@ -765,16 +531,40 @@ public class ChangeThemeStep : ProjectBuildPipelineStep
return moduleName.Replace(".", "");
}
private static void RenameLeptonXFolders(ProjectBuildContext context, string folderName)
private static void RenameFolders(ProjectBuildContext context, string oldFolderName, string newFolderName)
{
var leptonXFiles = context.Files.Where(x => x.Name.Contains("LeptonX") && x.IsDirectory);
foreach (var file in leptonXFiles)
foreach (var file in context.Files.Where(x => x.Name.Contains(oldFolderName) && x.IsDirectory))
{
new MoveFolderStep(file.Name, file.Name.Replace("LeptonX", folderName)).Execute(context);
new MoveFolderStep(file.Name, file.Name.Replace(oldFolderName, newFolderName)).Execute(context);
}
}
private void ChangeThemeToBasicForMvcProjects(ProjectBuildContext context, string defaultThemeName)
private static void ReplaceAllKeywords(ProjectBuildContext context, string targetModuleFilePath, string oldKeyword, string newKeyword)
{
var file = context.Files.FirstOrDefault(x => x.Name.Contains(targetModuleFilePath));
if (file == null)
{
return;
}
file.NormalizeLineEndings();
var lines = file.GetLines();
for (var i = 0; i < lines.Length; i++)
{
if (!lines[i].Contains(oldKeyword))
{
continue;;
}
lines[i] = lines[i].Replace(oldKeyword, newKeyword);
}
file.SetLines(lines);
}
private static void ChangeThemeToBasicForMvcProjects(ProjectBuildContext context, string defaultThemeName)
{
var projects = new Dictionary<string, string>
{
@ -794,28 +584,17 @@ public class ChangeThemeStep : ProjectBuildPipelineStep
$"Volo.Abp.AspNetCore.Mvc.UI.Theme.{defaultThemeName}",
@"..\..\..\..\..\modules\basic-theme\src\Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic\Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.csproj"
);
ChangeNamespaceAndKeyword(
ReplaceAllKeywords(
context,
$"/MyCompanyName.MyProjectName{project.Key}/{project.Value}.cs",
$"Volo.Abp.AspNetCore.Mvc.UI.Theme.{defaultThemeName}",
"Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic",
$"AbpAspNetCoreMvcUi{defaultThemeName}ThemeModule",
"AbpAspNetCoreMvcUiBasicThemeModule"
);
ChangeNamespaceAndKeyword(
context,
$"/MyCompanyName.MyProjectName{project.Key}/{project.Value}.cs",
$"Volo.Abp.AspNetCore.Mvc.UI.Theme.{defaultThemeName}.Bundling",
"Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Bundling",
$"{defaultThemeName}ThemeBundles.Styles.Global",
"BasicThemeBundles.Styles.Global"
defaultThemeName,
Basic
);
}
}
private void ChangeThemeToBasicForBlazorProjects(ProjectBuildContext context, string defaultThemeName)
private static void ChangeThemeToBasicForBlazorServerProjects(ProjectBuildContext context, string defaultThemeName)
{
var projects = new Dictionary<string, string>
{
@ -840,73 +619,34 @@ public class ChangeThemeStep : ProjectBuildPipelineStep
$"Volo.Abp.AspNetCore.Mvc.UI.Theme.{defaultThemeName}",
@"..\..\..\..\..\modules\basic-theme\src\Volo.Abp.AspNetCore.Components.Server.BasicTheme\Volo.Abp.AspNetCore.Components.Server.BasicTheme.csproj"
);
ChangeNamespaceAndKeyword(
context,
$"/MyCompanyName.MyProjectName.{project.Key}/{project.Value}.cs",
$"Volo.Abp.AspNetCore.Components.Server.{defaultThemeName}Theme",
"Volo.Abp.AspNetCore.Components.Server.BasicTheme",
$"AbpAspNetCoreComponentsServer{defaultThemeName}ThemeModule",
"AbpAspNetCoreComponentsServerBasicThemeModule"
);
ChangeNamespaceAndKeyword(
context,
$"/MyCompanyName.MyProjectName.{project.Key}/{project.Value}.cs",
$"Volo.Abp.AspNetCore.Mvc.UI.Theme.{defaultThemeName}",
"Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic",
$"AbpAspNetCoreMvcUi{defaultThemeName}ThemeModule",
"AbpAspNetCoreMvcUiBasicThemeModule"
);
ChangeNamespaceAndKeyword(
context,
$"/MyCompanyName.MyProjectName.{project.Key}/{project.Value}.cs",
$"Volo.Abp.AspNetCore.Components.Server.{defaultThemeName}Theme.Bundling",
"Volo.Abp.AspNetCore.Components.Server.BasicTheme.Bundling",
$"{defaultThemeName}ThemeBundles.Styles.Global",
"BasicThemeBundles.Styles.Global"
);
ChangeNamespaceAndKeyword(
context,
$"/MyCompanyName.MyProjectName.{project.Key}/{project.Value}.cs",
$"Volo.Abp.AspNetCore.Mvc.UI.Theme.{defaultThemeName}.Bundling",
"Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Bundling",
$"Blazor{defaultThemeName}ThemeBundles.Styles.Global",
"BlazorBasicThemeBundles.Styles.Global"
);
ChangeNamespaceAndKeyword(
ReplaceAllKeywords(
context,
$"/MyCompanyName.MyProjectName.{project.Key}/Pages/_Host.cshtml",
$"Volo.Abp.AspNetCore.Components.Web.{defaultThemeName}Theme.Components",
"Volo.Abp.AspNetCore.Components.Web.BasicTheme.Themes.Basic",
$"Blazor{defaultThemeName}ThemeBundles.Styles.Global",
"BlazorBasicThemeBundles.Styles.Global"
$"{defaultThemeName}Theme.Components",
Basic
);
ChangeNamespaceAndKeyword(
ReplaceAllKeywords(
context,
$"/MyCompanyName.MyProjectName.{project.Key}/Pages/_Host.cshtml",
$"Volo.Abp.AspNetCore.Components.Server.{defaultThemeName}Theme.Bundling",
"Volo.Abp.AspNetCore.Components.Server.BasicTheme.Bundling",
$"Blazor{defaultThemeName}ThemeBundles.Scripts.Global",
"BlazorBasicThemeBundles.Scripts.Global"
$"/MyCompanyName.MyProjectName.{project.Key}/{project.Value}.cs",
defaultThemeName,
Basic
);
ChangeNamespace(
ReplaceAllKeywords(
context,
$"/MyCompanyName.MyProjectName.{project.Key}/Pages/_Host.cshtml",
$"Volo.Abp.AspNetCore.Components.Web.{defaultThemeName}Theme.Themes.{defaultThemeName}",
"Volo.Abp.AspNetCore.Components.Web.BasicTheme.Themes.Basic"
defaultThemeName,
Basic
);
}
}
private void ChangeThemeToLeptonForBlazorServerProjects(ProjectBuildContext context)
private static void ChangeThemeToLeptonForBlazorServerProjects(ProjectBuildContext context)
{
var projectNames = new[] {"Blazor", "Blazor.Server.Tiered"};
var projectNames = new[] { "Blazor", "Blazor.Server.Tiered" };
foreach (var projectName in projectNames)
{
ReplacePackageReferenceWithProjectReference(
@ -922,59 +662,19 @@ public class ChangeThemeStep : ProjectBuildPipelineStep
"Volo.Abp.AspNetCore.Components.Server.LeptonXTheme",
@"..\..\..\..\..\lepton-theme\src\Volo.Abp.AspNetCore.Components.Server.LeptonTheme\Volo.Abp.AspNetCore.Components.Server.LeptonTheme.csproj"
);
ChangeNamespaceAndKeyword(
context,
$"/MyCompanyName.MyProjectName.{projectName}/MyProjectNameBlazorModule.cs",
"Volo.Abp.AspNetCore.Components.Server.LeptonXTheme",
"Volo.Abp.AspNetCore.Components.Server.LeptonTheme",
"AbpAspNetCoreComponentsServerLeptonXThemeModule",
"AbpAspNetCoreComponentsServerLeptonThemeModule"
);
ChangeNamespaceAndKeyword(
context,
$"/MyCompanyName.MyProjectName.{projectName}/MyProjectNameBlazorModule.cs",
"Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonX",
"Volo.Abp.AspNetCore.Mvc.UI.Theme.Lepton",
"AbpAspNetCoreMvcUiLeptonXThemeModule",
"AbpAspNetCoreMvcUiLeptonThemeModule"
);
ChangeNamespaceAndKeyword(
context,
$"/MyCompanyName.MyProjectName.{projectName}/MyProjectNameBlazorModule.cs",
"Volo.Abp.AspNetCore.Components.Server.LeptonXTheme.Bundling",
"Volo.Abp.AspNetCore.Components.Server.LeptonTheme.Bundling",
"LeptonXThemeBundles.Styles.Global",
"LeptonThemeBundles.Styles.Global"
);
ChangeNamespaceAndKeyword(
ReplaceAllKeywords(
context,
$"/MyCompanyName.MyProjectName.{projectName}/MyProjectNameBlazorModule.cs",
"Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonX.Bundling",
"Volo.Abp.AspNetCore.Mvc.UI.Theme.Lepton.Bundling",
"BlazorLeptonXThemeBundles.Styles.Global",
"BlazorLeptonThemeBundles.Styles.Global"
);
ChangeNamespaceAndKeyword(
context,
$"/MyCompanyName.MyProjectName.{projectName}/Pages/_Host.cshtml",
"Volo.Abp.AspNetCore.Components.Web.LeptonXTheme.Components",
"Volo.Abp.AspNetCore.Components.Web.LeptonTheme.Components",
"BlazorLeptonXThemeBundles.Styles.Global",
"BlazorLeptonThemeBundles.Styles.Global"
LeptonX,
Lepton
);
ChangeNamespaceAndKeyword(
ReplaceAllKeywords(
context,
$"/MyCompanyName.MyProjectName.{projectName}/Pages/_Host.cshtml",
"Volo.Abp.AspNetCore.Components.Server.LeptonXTheme.Bundling",
"Volo.Abp.AspNetCore.Components.Server.LeptonTheme.Bundling",
"BlazorLeptonXThemeBundles.Scripts.Global",
"BlazorLeptonThemeBundles.Scripts.Global"
LeptonX,
Lepton
);
RemoveLinesByStatement(
@ -985,11 +685,11 @@ public class ChangeThemeStep : ProjectBuildPipelineStep
}
}
private void ChangeThemeToLeptonForNoLayersBlazorServerProjects(ProjectBuildContext context)
private static void ChangeThemeToLeptonForNoLayersBlazorServerProjects(ProjectBuildContext context)
{
var blazorServerProjects = new[] { "Blazor.Server", "HttpApi", "Application" };
var projectNames = new[] { "Blazor.Server", "Blazor.Server.Mongo" };
foreach (var projectName in projectNames)
{
ReplacePackageReferenceWithProjectReference(
@ -1006,67 +706,29 @@ public class ChangeThemeStep : ProjectBuildPipelineStep
@"..\..\..\..\lepton-theme\src\Volo.Abp.AspNetCore.Components.Server.LeptonTheme\Volo.Abp.AspNetCore.Components.Server.LeptonTheme.csproj"
);
ConfigureLeptonManagementPackagesForNoLayersMvc(context,
$@"/MyCompanyName.MyProjectName.{projectName}/MyCompanyName.MyProjectName.{projectName}.csproj",
blazorServerProjects);
ChangeNamespaceAndKeyword(
context,
$"/MyCompanyName.MyProjectName.{projectName}/MyProjectNameModule.cs",
"Volo.Abp.AspNetCore.Components.Server.LeptonXTheme",
"Volo.Abp.AspNetCore.Components.Server.LeptonTheme",
"AbpAspNetCoreComponentsServerLeptonXThemeModule",
"AbpAspNetCoreComponentsServerLeptonThemeModule"
);
ChangeNamespaceAndKeyword(
context,
$"/MyCompanyName.MyProjectName.{projectName}/MyProjectNameModule.cs",
"Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonX",
"Volo.Abp.AspNetCore.Mvc.UI.Theme.Lepton",
"AbpAspNetCoreMvcUiLeptonXThemeModule",
"AbpAspNetCoreMvcUiLeptonThemeModule"
);
ChangeNamespaceAndKeyword(
ConfigureLeptonManagementPackagesForNoLayersMvc(
context,
$"/MyCompanyName.MyProjectName.{projectName}/MyProjectNameModule.cs",
"Volo.Abp.AspNetCore.Components.Server.LeptonXTheme.Bundling",
"Volo.Abp.AspNetCore.Components.Server.LeptonTheme.Bundling",
"LeptonXThemeBundles.Styles.Global",
"LeptonThemeBundles.Styles.Global"
$@"/MyCompanyName.MyProjectName.{projectName}/MyCompanyName.MyProjectName.{projectName}.csproj",
blazorServerProjects
);
ChangeNamespaceAndKeyword(
ReplaceAllKeywords(
context,
$"/MyCompanyName.MyProjectName.{projectName}/MyProjectNameModule.cs",
"Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonX.Bundling",
"Volo.Abp.AspNetCore.Mvc.UI.Theme.Lepton.Bundling",
"BlazorLeptonXThemeBundles.Styles.Global",
"BlazorLeptonThemeBundles.Styles.Global"
);
ChangeNamespaceAndKeyword(
context,
$"/MyCompanyName.MyProjectName.{projectName}/Pages/_Host.cshtml",
"Volo.Abp.AspNetCore.Components.Web.LeptonXTheme.Components",
"Volo.Abp.AspNetCore.Components.Web.LeptonTheme.Components",
"BlazorLeptonXThemeBundles.Styles.Global",
"BlazorLeptonThemeBundles.Styles.Global"
LeptonX,
Lepton
);
ChangeNamespaceAndKeyword(
ReplaceAllKeywords(
context,
$"/MyCompanyName.MyProjectName.{projectName}/Pages/_Host.cshtml",
"Volo.Abp.AspNetCore.Components.Server.LeptonXTheme.Bundling",
"Volo.Abp.AspNetCore.Components.Server.LeptonTheme.Bundling",
"BlazorLeptonXThemeBundles.Scripts.Global",
"BlazorLeptonThemeBundles.Scripts.Global"
LeptonX,
Lepton
);
}
}
private void ChangeThemeToLeptonForMauiBlazorProjects(ProjectBuildContext context)
private static void ChangeThemeToLeptonForMauiBlazorProjects(ProjectBuildContext context)
{
ReplacePackageReferenceWithProjectReference(
context,
@ -1074,44 +736,42 @@ public class ChangeThemeStep : ProjectBuildPipelineStep
"Volo.Abp.AspNetCore.Components.MauiBlazor.LeptonXTheme",
@"..\..\..\..\..\lepton-theme\src\Volo.Abp.AspNetCore.Components.MauiBlazor.LeptonTheme\Volo.Abp.AspNetCore.Components.MauiBlazor.LeptonTheme.csproj"
);
ChangeNamespaceAndKeyword(
context,
"/MyCompanyName.MyProjectName.MauiBlazor/MyProjectNameMauiBlazorModule.cs",
"Volo.Abp.AspNetCore.Components.MauiBlazor.LeptonXTheme",
"Volo.Abp.AspNetCore.Components.MauiBlazor.LeptonTheme",
"AbpAspNetCoreComponentsMauiBlazorLeptonXThemeModule",
"AbpAspNetCoreComponentsMauiBlazorLeptonThemeModule"
);
ChangeKeyword(
ReplaceAllKeywords(
context,
"/MyCompanyName.MyProjectName.MauiBlazor/MainPage.xaml",
"clr-namespace:Volo.Abp.AspNetCore.Components.Web.LeptonXTheme.Components;assembly=Volo.Abp.AspNetCore.Components.Web.LeptonXTheme",
"clr-namespace:Volo.Abp.AspNetCore.Components.Web.LeptonTheme.Components;assembly=Volo.Abp.AspNetCore.Components.Web.LeptonTheme");
ChangeKeyword(
ReplaceAllKeywords(
context,
"/MyCompanyName.MyProjectName.MauiBlazor/MainPage.xaml",
"leptonXTheme",
"leptonTheme");
ChangeKeyword(
ReplaceAllKeywords(
context,
"/MyCompanyName.MyProjectName.MauiBlazor/Pages/Account/Login.razor",
"Volo.Abp.AspNetCore.Components.MauiBlazor.LeptonXTheme.Components.AccountLayout",
"Volo.Abp.AspNetCore.Components.MauiBlazor.LeptonTheme.Components.AccountLayout");
ChangeKeyword(
ReplaceAllKeywords(
context,
"/MyCompanyName.MyProjectName.MauiBlazor/Pages/Account/RedirectToLogout.razor",
"LeptonXResource",
"LeptonThemeManagementResource");
ChangeKeyword(
ReplaceAllKeywords(
context,
"/MyCompanyName.MyProjectName.MauiBlazor/Pages/Account/RedirectToLogout.razor",
"Volo.Abp.LeptonX.Shared.Localization",
"Volo.Abp.LeptonTheme.Management.Localization");
ReplaceAllKeywords(
context,
"/MyCompanyName.MyProjectName.MauiBlazor/MyProjectNameMauiBlazorModule.cs",
LeptonX,
Lepton
);
}
}
}

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

@ -19,7 +19,8 @@ public class TemplateCodeDeleteStep : ProjectBuildPipelineStep
file.Name.EndsWith(".ps1") ||
file.Name.EndsWith(".html") ||
file.Name.EndsWith(".ts") ||
file.Name.EndsWith(".css"))
file.Name.EndsWith(".css") ||
file.Name.Contains("Dockerfile"))
{
file.RemoveTemplateCode(context.Symbols);
file.RemoveTemplateCodeMarkers();

3
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ISourceCodeStore.cs

@ -10,6 +10,7 @@ public interface ISourceCodeStore
string type,
[CanBeNull] string version = null,
[CanBeNull] string templateSource = null,
bool includePreReleases = false
bool includePreReleases = false,
bool skipCache = false
);
}

6
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ProjectBuildArgs.cs

@ -46,6 +46,8 @@ public class ProjectBuildArgs
public ThemeStyle? ThemeStyle { get; set; }
public bool SkipCache { get; set; }
[NotNull]
public Dictionary<string, string> ExtraProperties { get; set; }
@ -66,7 +68,8 @@ public class ProjectBuildArgs
[CanBeNull] string connectionString = null,
bool pwa = false,
Theme? theme = null,
ThemeStyle? themeStyle = null)
ThemeStyle? themeStyle = null,
bool skipCache = false)
{
SolutionName = Check.NotNull(solutionName, nameof(solutionName));
TemplateName = templateName;
@ -85,5 +88,6 @@ public class ProjectBuildArgs
Pwa = pwa;
Theme = theme;
ThemeStyle = themeStyle;
SkipCache = skipCache;
}
}

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

Loading…
Cancel
Save