Browse Source

Merge branch 'dev' into Volo.Abp.MultiTenancy.Abstractions

pull/16584/head
maliming 3 years ago
parent
commit
512fe5d057
No known key found for this signature in database GPG Key ID: A646B9CB645ECEA4
  1. 2
      abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json
  2. 5
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json
  3. 5
      abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json
  4. 4
      configureawait.props
  5. 5
      docs/en/CLI.md
  6. 307
      docs/en/Image-Manipulation.md
  7. 33
      docs/en/KB/Windows-Path-Too-Long-Fix.md
  8. 13
      docs/en/Startup-Templates/Application.md
  9. 33
      docs/en/UI/AspNetCore/Security-Headers.md
  10. 4
      docs/en/UI/AspNetCore/Tag-Helpers/Dropdowns.md
  11. 8
      docs/en/docs-nav.json
  12. BIN
      docs/en/images/layered-project-dependencies-blazor-server.png
  13. BIN
      docs/en/images/layered-project-dependencies-blazor-wasm.png
  14. 2
      docs/zh-Hans/CLI.md
  15. 2
      docs/zh-Hans/Deployment/Clustered-Environment.md
  16. 2
      docs/zh-Hans/docs-nav.json
  17. 56
      framework/Volo.Abp.sln
  18. 1
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/Extensibility/WebAssemblyLookupApiRequestService.cs
  19. 14
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpDynamicformTagHelperService.cs
  20. 39
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerBaseTagHelperService.cs
  21. 9
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerTagHelperService.cs
  22. 5
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperScriptService.cs
  23. 10
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperStyleService.cs
  24. 22
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/ScriptNonceTagHelper.cs
  25. 3
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalScriptContributor.cs
  26. 10
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/dom-event-handlers.js
  27. 3
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs
  28. 3
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcOptions.cs
  29. 38
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs
  30. 40
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Infrastructure/AbpMemoryPoolHttpResponseStreamWriterFactory.cs
  31. 1
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/AbpAspNetCoreConsts.cs
  32. 14
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Controllers/ReplaceControllersAttribute.cs
  33. 23
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/AbpSecurityHeaderNonceHelper.cs
  34. 96
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/AbpSecurityHeadersMiddleware.cs
  35. 13
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/AbpSecurityHeadersOptions.cs
  36. 21
      framework/src/Volo.Abp.BlazoriseUI/Components/AbpExtensibleDataGrid.razor
  37. 8
      framework/src/Volo.Abp.BlazoriseUI/Volo.Abp.BlazoriseUI.csproj
  38. 3
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs
  39. 59
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ClearDownloadCacheCommand.cs
  40. 37
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs
  41. 38
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ProjectCreationCommandBase.cs
  42. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/SourceCodeDownloadService.cs
  43. 12
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs
  44. 3
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ISourceCodeStore.cs
  45. 6
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ProjectBuildArgs.cs
  46. 63
      framework/src/Volo.Abp.Core/System/IO/AbpStreamExtensions.cs
  47. 0
      framework/src/Volo.Abp.Core/Volo/Abp/Http/MimeTypes.cs
  48. 76
      framework/src/Volo.Abp.Core/Volo/Abp/Threading/SemaphoreSlimExtensions.cs
  49. 6
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/EfCoreTransactionApi.cs
  50. 3
      framework/src/Volo.Abp.Imaging.Abstractions/FodyWeavers.xml
  51. 30
      framework/src/Volo.Abp.Imaging.Abstractions/FodyWeavers.xsd
  52. 3
      framework/src/Volo.Abp.Imaging.Abstractions/Volo.Abp.Imaging.Abstractions.abppkg.json
  53. 20
      framework/src/Volo.Abp.Imaging.Abstractions/Volo.Abp.Imaging.Abstractions.csproj
  54. 9
      framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/AbpImagingAbstractionsModule.cs
  55. 21
      framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/IImageCompressor.cs
  56. 18
      framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/IImageCompressorContributor.cs
  57. 23
      framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/IImageResizer.cs
  58. 21
      framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/IImageResizerContributor.cs
  59. 8
      framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/ImageCompressResult.cs
  60. 66
      framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/ImageCompressor.cs
  61. 13
      framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/ImageProcessResult.cs
  62. 8
      framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/ImageProcessState.cs
  63. 49
      framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/ImageResizeArgs.cs
  64. 13
      framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/ImageResizeMode.cs
  65. 6
      framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/ImageResizeOptions.cs
  66. 8
      framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/ImageResizeResult.cs
  67. 87
      framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/ImageResizer.cs
  68. 3
      framework/src/Volo.Abp.Imaging.AspNetCore/FodyWeavers.xml
  69. 30
      framework/src/Volo.Abp.Imaging.AspNetCore/FodyWeavers.xsd
  70. 3
      framework/src/Volo.Abp.Imaging.AspNetCore/Volo.Abp.Imaging.AspNetCore.abppkg.json
  71. 23
      framework/src/Volo.Abp.Imaging.AspNetCore/Volo.Abp.Imaging.AspNetCore.csproj
  72. 9
      framework/src/Volo.Abp.Imaging.AspNetCore/Volo/Abp/Imaging/AbpImagingAspNetCoreModule.cs
  73. 104
      framework/src/Volo.Abp.Imaging.AspNetCore/Volo/Abp/Imaging/CompressImageAttribute.cs
  74. 117
      framework/src/Volo.Abp.Imaging.AspNetCore/Volo/Abp/Imaging/ResizeImageAttribute.cs
  75. 3
      framework/src/Volo.Abp.Imaging.ImageSharp/FodyWeavers.xml
  76. 30
      framework/src/Volo.Abp.Imaging.ImageSharp/FodyWeavers.xsd
  77. 24
      framework/src/Volo.Abp.Imaging.ImageSharp/Volo.Abp.Imaging.ImageSharp.csproj
  78. 3
      framework/src/Volo.Abp.Imaging.ImageSharp/Volo.Abp.Imaging.ImageSharp.json
  79. 8
      framework/src/Volo.Abp.Imaging.ImageSharp/Volo/Abp/Imaging/AbpImagingImageSharpModule.cs
  80. 32
      framework/src/Volo.Abp.Imaging.ImageSharp/Volo/Abp/Imaging/ImageSharpCompressOptions.cs
  81. 124
      framework/src/Volo.Abp.Imaging.ImageSharp/Volo/Abp/Imaging/ImageSharpImageCompressorContributor.cs
  82. 124
      framework/src/Volo.Abp.Imaging.ImageSharp/Volo/Abp/Imaging/ImageSharpImageResizerContributor.cs
  83. 3
      framework/src/Volo.Abp.Imaging.MagickNet/FodyWeavers.xml
  84. 30
      framework/src/Volo.Abp.Imaging.MagickNet/FodyWeavers.xsd
  85. 3
      framework/src/Volo.Abp.Imaging.MagickNet/Volo.Abp.Imaging.MagickNet.abppkg.json
  86. 24
      framework/src/Volo.Abp.Imaging.MagickNet/Volo.Abp.Imaging.MagickNet.csproj
  87. 8
      framework/src/Volo.Abp.Imaging.MagickNet/Volo/Abp/Imaging/AbpImagingMagickNetModule.cs
  88. 103
      framework/src/Volo.Abp.Imaging.MagickNet/Volo/Abp/Imaging/MagickImageCompressorContributor.cs
  89. 325
      framework/src/Volo.Abp.Imaging.MagickNet/Volo/Abp/Imaging/MagickImageResizerContributor.cs
  90. 8
      framework/src/Volo.Abp.Imaging.MagickNet/Volo/Abp/Imaging/MagickNetCompressOptions.cs
  91. 4
      framework/src/Volo.Abp.MongoDB/Volo/Abp/Uow/MongoDB/MongoDbTransactionApi.cs
  92. 2
      framework/src/Volo.Abp.Uow/Volo/Abp/Uow/ISupportsRollback.cs
  93. 3
      framework/src/Volo.Abp.Uow/Volo/Abp/Uow/ITransactionApi.cs
  94. 6
      framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWork.cs
  95. 50
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Controllers/ReplaceBuiltInController.cs
  96. 32
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Controllers/ReplaceBuiltInController_Tests.cs
  97. 1
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Security/Headers/SecurityHeadersTestController_Tests.cs
  98. 28
      framework/test/Volo.Abp.Core.Tests/Volo/Abp/Threading/SemaphoreSlimExtensions_Tests.cs
  99. 29
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Uow/UnitOfWork_CancellationToken_Tests.cs
  100. 3
      framework/test/Volo.Abp.Imaging.Abstractions.Tests/Volo.Abp.Imaging.Abstractions.Tests.abppkg.json

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",

5
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>",
@ -841,6 +841,7 @@
"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."
"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"
}
}

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>

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`.

307
docs/en/Image-Manipulation.md

@ -0,0 +1,307 @@
# 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
It is suggested to use the [ABP CLI](CLI.md) to install this package.
### 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
}
```
### 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. If you want to use that library as image process provider, add the `Volo.Abp.Imaging.MagickNet` NuGet package to your project, then 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. If you want to use that library as image process provider, add the `Volo.Abp.Imaging.ImageSharp` NuGet package to your project, then 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.
To use the ASP.NET Core integration, add the `Volo.Abp.Imaging.AspNetCore` NuGet package to your project and add the `AbpImagingAspNetCoreModule` module to your module'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
```

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:

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

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/docs-nav.json

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

56
framework/Volo.Abp.sln

@ -441,6 +441,22 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Ddd.Domain.Shared"
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
@ -1315,6 +1331,38 @@ Global
{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
@ -1537,6 +1585,14 @@ Global
{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}

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;

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

39
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")}
};

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);
}
}
}

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"
});
});
}
}

10
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) {
@ -755,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"]'));
@ -762,6 +764,7 @@
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);
});
@ -785,6 +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);
}
}

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>();
}
}

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>

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; }
}
}
}

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;
}
}

63
framework/src/Volo.Abp.Core/System/IO/AbpStreamExtensions.cs

@ -7,28 +7,28 @@ public static class AbpStreamExtensions
{
public static byte[] GetAllBytes(this Stream stream)
{
using (var memoryStream = new MemoryStream())
if (stream is MemoryStream memoryStream)
{
if (stream.CanSeek)
{
stream.Position = 0;
}
stream.CopyTo(memoryStream);
return memoryStream.ToArray();
}
using (var ms = stream.CreateMemoryStream())
{
return ms.ToArray();
}
}
public static async Task<byte[]> GetAllBytesAsync(this Stream stream, CancellationToken cancellationToken = default)
{
using (var memoryStream = new MemoryStream())
if (stream is MemoryStream memoryStream)
{
if (stream.CanSeek)
{
stream.Position = 0;
}
await stream.CopyToAsync(memoryStream, cancellationToken);
return memoryStream.ToArray();
}
using (var ms = await stream.CreateMemoryStreamAsync(cancellationToken))
{
return ms.ToArray();
}
}
public static Task CopyToAsync(this Stream stream, Stream destination, CancellationToken cancellationToken)
@ -37,10 +37,49 @@ public static class AbpStreamExtensions
{
stream.Position = 0;
}
return stream.CopyToAsync(
destination,
81920, //this is already the default value, but needed to set to be able to pass the cancellationToken
cancellationToken
);
}
public async static Task<MemoryStream> CreateMemoryStreamAsync(this Stream stream, CancellationToken cancellationToken = default)
{
if (stream.CanSeek)
{
stream.Position = 0;
}
var memoryStream = new MemoryStream();
await stream.CopyToAsync(memoryStream, cancellationToken);
if (stream.CanSeek)
{
stream.Position = 0;
}
memoryStream.Position = 0;
return memoryStream;
}
public static MemoryStream CreateMemoryStream(this Stream stream)
{
if (stream.CanSeek)
{
stream.Position = 0;
}
var memoryStream = new MemoryStream();
stream.CopyTo(memoryStream);
if (stream.CanSeek)
{
stream.Position = 0;
}
memoryStream.Position = 0;
return memoryStream;
}
}

0
framework/src/Volo.Abp.Http/Volo/Abp/Http/MimeTypes.cs → framework/src/Volo.Abp.Core/Volo/Abp/Http/MimeTypes.cs

76
framework/src/Volo.Abp.Core/Volo/Abp/Threading/SemaphoreSlimExtensions.cs

@ -6,40 +6,56 @@ namespace Volo.Abp.Threading;
public static class SemaphoreSlimExtensions
{
public static async Task<IDisposable> LockAsync(this SemaphoreSlim semaphoreSlim)
public async static Task<IDisposable> LockAsync(this SemaphoreSlim semaphoreSlim)
{
await semaphoreSlim.WaitAsync();
return GetDispose(semaphoreSlim);
}
public static async Task<IDisposable> LockAsync(this SemaphoreSlim semaphoreSlim, CancellationToken cancellationToken)
public async static Task<IDisposable> LockAsync(this SemaphoreSlim semaphoreSlim, CancellationToken cancellationToken)
{
await semaphoreSlim.WaitAsync(cancellationToken);
return GetDispose(semaphoreSlim);
}
public static async Task<IDisposable> LockAsync(this SemaphoreSlim semaphoreSlim, int millisecondsTimeout)
public async static Task<IDisposable> LockAsync(this SemaphoreSlim semaphoreSlim, int millisecondsTimeout)
{
await semaphoreSlim.WaitAsync(millisecondsTimeout);
return GetDispose(semaphoreSlim);
if (await semaphoreSlim.WaitAsync(millisecondsTimeout))
{
return GetDispose(semaphoreSlim);
}
throw new TimeoutException();
}
public static async Task<IDisposable> LockAsync(this SemaphoreSlim semaphoreSlim, int millisecondsTimeout, CancellationToken cancellationToken)
public async static Task<IDisposable> LockAsync(this SemaphoreSlim semaphoreSlim, int millisecondsTimeout, CancellationToken cancellationToken)
{
await semaphoreSlim.WaitAsync(millisecondsTimeout, cancellationToken);
return GetDispose(semaphoreSlim);
if (await semaphoreSlim.WaitAsync(millisecondsTimeout, cancellationToken))
{
return GetDispose(semaphoreSlim);
}
throw new TimeoutException();
}
public static async Task<IDisposable> LockAsync(this SemaphoreSlim semaphoreSlim, TimeSpan timeout)
public async static Task<IDisposable> LockAsync(this SemaphoreSlim semaphoreSlim, TimeSpan timeout)
{
await semaphoreSlim.WaitAsync(timeout);
return GetDispose(semaphoreSlim);
if (await semaphoreSlim.WaitAsync(timeout))
{
return GetDispose(semaphoreSlim);
}
throw new TimeoutException();
}
public static async Task<IDisposable> LockAsync(this SemaphoreSlim semaphoreSlim, TimeSpan timeout, CancellationToken cancellationToken)
public async static Task<IDisposable> LockAsync(this SemaphoreSlim semaphoreSlim, TimeSpan timeout, CancellationToken cancellationToken)
{
await semaphoreSlim.WaitAsync(timeout, cancellationToken);
return GetDispose(semaphoreSlim);
if (await semaphoreSlim.WaitAsync(timeout, cancellationToken))
{
return GetDispose(semaphoreSlim);
}
throw new TimeoutException();
}
public static IDisposable Lock(this SemaphoreSlim semaphoreSlim)
@ -56,26 +72,42 @@ public static class SemaphoreSlimExtensions
public static IDisposable Lock(this SemaphoreSlim semaphoreSlim, int millisecondsTimeout)
{
semaphoreSlim.Wait(millisecondsTimeout);
return GetDispose(semaphoreSlim);
if (semaphoreSlim.Wait(millisecondsTimeout))
{
return GetDispose(semaphoreSlim);
}
throw new TimeoutException();
}
public static IDisposable Lock(this SemaphoreSlim semaphoreSlim, int millisecondsTimeout, CancellationToken cancellationToken)
{
semaphoreSlim.Wait(millisecondsTimeout, cancellationToken);
return GetDispose(semaphoreSlim);
if (semaphoreSlim.Wait(millisecondsTimeout, cancellationToken))
{
return GetDispose(semaphoreSlim);
}
throw new TimeoutException();
}
public static IDisposable Lock(this SemaphoreSlim semaphoreSlim, TimeSpan timeout)
{
semaphoreSlim.Wait(timeout);
return GetDispose(semaphoreSlim);
if (semaphoreSlim.Wait(timeout))
{
return GetDispose(semaphoreSlim);
}
throw new TimeoutException();
}
public static IDisposable Lock(this SemaphoreSlim semaphoreSlim, TimeSpan timeout, CancellationToken cancellationToken)
{
semaphoreSlim.Wait(timeout, cancellationToken);
return GetDispose(semaphoreSlim);
if (semaphoreSlim.Wait(timeout, cancellationToken))
{
return GetDispose(semaphoreSlim);
}
throw new TimeoutException();
}
private static IDisposable GetDispose(this SemaphoreSlim semaphoreSlim)

6
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/EfCoreTransactionApi.cs

@ -28,7 +28,7 @@ public class EfCoreTransactionApi : ITransactionApi, ISupportsRollback
AttendedDbContexts = new List<IEfCoreDbContext>();
}
public async Task CommitAsync()
public async Task CommitAsync(CancellationToken cancellationToken = default)
{
foreach (var dbContext in AttendedDbContexts)
{
@ -38,10 +38,10 @@ public class EfCoreTransactionApi : ITransactionApi, ISupportsRollback
continue; //Relational databases use the shared transaction if they are using the same connection
}
await dbContext.Database.CommitTransactionAsync(CancellationTokenProvider.Token);
await dbContext.Database.CommitTransactionAsync(CancellationTokenProvider.FallbackToProvider(cancellationToken));
}
await DbContextTransaction.CommitAsync(CancellationTokenProvider.Token);
await DbContextTransaction.CommitAsync(CancellationTokenProvider.FallbackToProvider(cancellationToken));
}
public void Dispose()

3
framework/src/Volo.Abp.Imaging.Abstractions/FodyWeavers.xml

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait ContinueOnCapturedContext="false" />
</Weavers>

30
framework/src/Volo.Abp.Imaging.Abstractions/FodyWeavers.xsd

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" />
</xs:complexType>
</xs:element>
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

3
framework/src/Volo.Abp.Imaging.Abstractions/Volo.Abp.Imaging.Abstractions.abppkg.json

@ -0,0 +1,3 @@
{
"role": "lib.framework"
}

20
framework/src/Volo.Abp.Imaging.Abstractions/Volo.Abp.Imaging.Abstractions.csproj

@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\configureawait.props" />
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netstandard2.1;net7.0</TargetFrameworks>
<PackageId>Volo.Abp.Imaging.Abstractions</PackageId>
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.Threading\Volo.Abp.Threading.csproj" />
</ItemGroup>
</Project>

9
framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/AbpImagingAbstractionsModule.cs

@ -0,0 +1,9 @@
using Volo.Abp.Modularity;
using Volo.Abp.Threading;
namespace Volo.Abp.Imaging;
[DependsOn(typeof(AbpThreadingModule))]
public class AbpImagingAbstractionsModule : AbpModule
{
}

21
framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/IImageCompressor.cs

@ -0,0 +1,21 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
namespace Volo.Abp.Imaging;
public interface IImageCompressor
{
Task<ImageCompressResult<Stream>> CompressAsync(
Stream stream,
[CanBeNull] string mimeType = null,
CancellationToken cancellationToken = default
);
Task<ImageCompressResult<byte[]>> CompressAsync(
byte[] bytes,
[CanBeNull] string mimeType = null,
CancellationToken cancellationToken = default
);
}

18
framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/IImageCompressorContributor.cs

@ -0,0 +1,18 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
namespace Volo.Abp.Imaging;
public interface IImageCompressorContributor
{
Task<ImageCompressResult<Stream>> TryCompressAsync(
Stream stream,
[CanBeNull] string mimeType = null,
CancellationToken cancellationToken = default);
Task<ImageCompressResult<byte[]>> TryCompressAsync(
byte[] bytes,
[CanBeNull] string mimeType = null,
CancellationToken cancellationToken = default);
}

23
framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/IImageResizer.cs

@ -0,0 +1,23 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
namespace Volo.Abp.Imaging;
public interface IImageResizer
{
Task<ImageResizeResult<Stream>> ResizeAsync(
Stream stream,
ImageResizeArgs resizeArgs,
[CanBeNull] string mimeType = null,
CancellationToken cancellationToken = default
);
Task<ImageResizeResult<byte[]>> ResizeAsync(
byte[] bytes,
ImageResizeArgs resizeArgs,
[CanBeNull] string mimeType = null,
CancellationToken cancellationToken = default
);
}

21
framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/IImageResizerContributor.cs

@ -0,0 +1,21 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
namespace Volo.Abp.Imaging;
public interface IImageResizerContributor
{
Task<ImageResizeResult<Stream>> TryResizeAsync(
Stream stream,
ImageResizeArgs resizeArgs,
[CanBeNull] string mimeType = null,
CancellationToken cancellationToken = default);
Task<ImageResizeResult<byte[]>> TryResizeAsync(
byte[] bytes,
ImageResizeArgs resizeArgs,
[CanBeNull] string mimeType = null,
CancellationToken cancellationToken = default);
}

8
framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/ImageCompressResult.cs

@ -0,0 +1,8 @@
namespace Volo.Abp.Imaging;
public class ImageCompressResult<T> : ImageProcessResult<T>
{
public ImageCompressResult(T result, ImageProcessState state) : base(result, state)
{
}
}

66
framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/ImageCompressor.cs

@ -0,0 +1,66 @@
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Threading;
namespace Volo.Abp.Imaging;
public class ImageCompressor : IImageCompressor, ITransientDependency
{
protected IEnumerable<IImageCompressorContributor> ImageCompressorContributors { get; }
protected ICancellationTokenProvider CancellationTokenProvider { get; }
public ImageCompressor(IEnumerable<IImageCompressorContributor> imageCompressorContributors, ICancellationTokenProvider cancellationTokenProvider)
{
ImageCompressorContributors = imageCompressorContributors;
CancellationTokenProvider = cancellationTokenProvider;
}
public virtual async Task<ImageCompressResult<Stream>> CompressAsync(
[NotNull] Stream stream,
[CanBeNull] string mimeType = null,
CancellationToken cancellationToken = default)
{
Check.NotNull(stream, nameof(stream));
foreach (var imageCompressorContributor in ImageCompressorContributors)
{
var result = await imageCompressorContributor.TryCompressAsync(stream, mimeType, CancellationTokenProvider.FallbackToProvider(cancellationToken));
if (result.State == ImageProcessState.Unsupported)
{
continue;
}
return result;
}
return new ImageCompressResult<Stream>(stream, ImageProcessState.Unsupported);
}
public virtual async Task<ImageCompressResult<byte[]>> CompressAsync(
[NotNull] byte[] bytes,
[CanBeNull] string mimeType = null,
CancellationToken cancellationToken = default)
{
Check.NotNull(bytes, nameof(bytes));
foreach (var imageCompressorContributor in ImageCompressorContributors)
{
var result = await imageCompressorContributor.TryCompressAsync(bytes, mimeType, CancellationTokenProvider.FallbackToProvider(cancellationToken));
if (result.State == ImageProcessState.Unsupported)
{
continue;
}
return result;
}
return new ImageCompressResult<byte[]>(bytes, ImageProcessState.Unsupported);
}
}

13
framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/ImageProcessResult.cs

@ -0,0 +1,13 @@
namespace Volo.Abp.Imaging;
public abstract class ImageProcessResult<T>
{
public T Result { get; }
public ImageProcessState State { get; }
protected ImageProcessResult(T result, ImageProcessState state)
{
Result = result;
State = state;
}
}

8
framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/ImageProcessState.cs

@ -0,0 +1,8 @@
namespace Volo.Abp.Imaging;
public enum ImageProcessState : byte
{
Done = 1,
Canceled = 2,
Unsupported = 3,
}

49
framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/ImageResizeArgs.cs

@ -0,0 +1,49 @@
using System;
namespace Volo.Abp.Imaging;
public class ImageResizeArgs
{
private int _width;
public int Width
{
get => _width;
set
{
if (value < 0)
{
throw new ArgumentException("Width cannot be negative!", nameof(value));
}
_width = value;
}
}
private int _height;
public int Height
{
get => _height;
set
{
if (value < 0)
{
throw new ArgumentException("Height cannot be negative!", nameof(value));
}
_height = value;
}
}
public ImageResizeMode Mode { get; set; } = ImageResizeMode.Default;
public ImageResizeArgs(int? width = null, int? height = null, ImageResizeMode? mode = null)
{
if (mode.HasValue)
{
Mode = mode.Value;
}
Width = width ?? 0;
Height = height ?? 0;
}
}

13
framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/ImageResizeMode.cs

@ -0,0 +1,13 @@
namespace Volo.Abp.Imaging;
public enum ImageResizeMode : byte
{
None = 0,
Stretch = 1,
BoxPad = 2,
Min = 3,
Max = 4,
Crop = 5,
Pad = 6,
Default = 7
}

6
framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/ImageResizeOptions.cs

@ -0,0 +1,6 @@
namespace Volo.Abp.Imaging;
public class ImageResizeOptions
{
public ImageResizeMode DefaultResizeMode { get; set; } = ImageResizeMode.None;
}

8
framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/ImageResizeResult.cs

@ -0,0 +1,8 @@
namespace Volo.Abp.Imaging;
public class ImageResizeResult<T> : ImageProcessResult<T>
{
public ImageResizeResult(T result, ImageProcessState state) : base(result, state)
{
}
}

87
framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/ImageResizer.cs

@ -0,0 +1,87 @@
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Threading;
namespace Volo.Abp.Imaging;
public class ImageResizer : IImageResizer, ITransientDependency
{
protected IEnumerable<IImageResizerContributor> ImageResizerContributors { get; }
protected ImageResizeOptions ImageResizeOptions { get; }
protected ICancellationTokenProvider CancellationTokenProvider { get; }
public ImageResizer(
IEnumerable<IImageResizerContributor> imageResizerContributors,
IOptions<ImageResizeOptions> imageResizeOptions,
ICancellationTokenProvider cancellationTokenProvider)
{
ImageResizerContributors = imageResizerContributors;
CancellationTokenProvider = cancellationTokenProvider;
ImageResizeOptions = imageResizeOptions.Value;
}
public virtual async Task<ImageResizeResult<Stream>> ResizeAsync(
[NotNull] Stream stream,
ImageResizeArgs resizeArgs,
[CanBeNull] string mimeType = null,
CancellationToken cancellationToken = default)
{
Check.NotNull(stream, nameof(stream));
ChangeDefaultResizeMode(resizeArgs);
foreach (var imageResizerContributor in ImageResizerContributors)
{
var result = await imageResizerContributor.TryResizeAsync(stream, resizeArgs, mimeType, CancellationTokenProvider.FallbackToProvider(cancellationToken));
if (result.State == ImageProcessState.Unsupported)
{
continue;
}
return result;
}
return new ImageResizeResult<Stream>(stream, ImageProcessState.Unsupported);
}
public virtual async Task<ImageResizeResult<byte[]>> ResizeAsync(
[NotNull] byte[] bytes,
ImageResizeArgs resizeArgs,
[CanBeNull] string mimeType = null,
CancellationToken cancellationToken = default)
{
Check.NotNull(bytes, nameof(bytes));
ChangeDefaultResizeMode(resizeArgs);
foreach (var imageResizerContributor in ImageResizerContributors)
{
var result = await imageResizerContributor.TryResizeAsync(bytes, resizeArgs, mimeType, CancellationTokenProvider.FallbackToProvider(cancellationToken));
if (result.State == ImageProcessState.Unsupported)
{
continue;
}
return result;
}
return new ImageResizeResult<byte[]>(bytes, ImageProcessState.Unsupported);
}
protected virtual void ChangeDefaultResizeMode(ImageResizeArgs resizeArgs)
{
if (resizeArgs.Mode == ImageResizeMode.Default)
{
resizeArgs.Mode = ImageResizeOptions.DefaultResizeMode;
}
}
}

3
framework/src/Volo.Abp.Imaging.AspNetCore/FodyWeavers.xml

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait ContinueOnCapturedContext="false" />
</Weavers>

30
framework/src/Volo.Abp.Imaging.AspNetCore/FodyWeavers.xsd

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" />
</xs:complexType>
</xs:element>
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

3
framework/src/Volo.Abp.Imaging.AspNetCore/Volo.Abp.Imaging.AspNetCore.abppkg.json

@ -0,0 +1,3 @@
{
"role": "lib.framework"
}

23
framework/src/Volo.Abp.Imaging.AspNetCore/Volo.Abp.Imaging.AspNetCore.csproj

@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Import Project="..\..\..\configureawait.props" />
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<PackageId>Volo.Abp.Imaging.AspNetCore</PackageId>
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<IsPackable>true</IsPackable>
<OutputType>Library</OutputType>
<NoDefaultLaunchSettingsFile>true</NoDefaultLaunchSettingsFile>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.Imaging.Abstractions\Volo.Abp.Imaging.Abstractions.csproj" />
</ItemGroup>
</Project>

9
framework/src/Volo.Abp.Imaging.AspNetCore/Volo/Abp/Imaging/AbpImagingAspNetCoreModule.cs

@ -0,0 +1,9 @@
using Volo.Abp.Modularity;
namespace Volo.Abp.Imaging;
[DependsOn(typeof(AbpImagingAbstractionsModule))]
public class AbpImagingAspNetCoreModule : AbpModule
{
}

104
framework/src/Volo.Abp.Imaging.AspNetCore/Volo/Abp/Imaging/CompressImageAttribute.cs

@ -0,0 +1,104 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Content;
namespace Volo.Abp.Imaging;
public class CompressImageAttribute : ActionFilterAttribute
{
public string[] Parameters { get; }
public CompressImageAttribute(params string[] parameters)
{
Parameters = parameters;
}
public async override Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
var parameters = Parameters.Any()
? context.ActionArguments.Where(x => Parameters.Contains(x.Key)).ToArray()
: context.ActionArguments.ToArray();
var imageCompressor = context.HttpContext.RequestServices.GetRequiredService<IImageCompressor>();
foreach (var (key, value) in parameters)
{
object compressedValue = value switch {
IFormFile file => await CompressImageAsync(file, imageCompressor),
IRemoteStreamContent remoteStreamContent => await CompressImageAsync(remoteStreamContent, imageCompressor),
Stream stream => await CompressImageAsync(stream, imageCompressor),
IEnumerable<byte> bytes => await CompressImageAsync(bytes.ToArray(), imageCompressor),
_ => null
};
if (compressedValue != null)
{
context.ActionArguments[key] = compressedValue;
}
}
await next();
}
protected virtual async Task<IFormFile> CompressImageAsync(IFormFile file, IImageCompressor imageCompressor)
{
if(file.Headers == null || file.ContentType == null || !file.ContentType.StartsWith("image/"))
{
return file;
}
var result = await imageCompressor.CompressAsync(file.OpenReadStream(), file.ContentType);
if (result.State != ImageProcessState.Done)
{
return file;
}
return new FormFile(result.Result, 0, result.Result.Length, file.Name, file.FileName) {
Headers = file.Headers,
};
}
protected virtual async Task<IRemoteStreamContent> CompressImageAsync(IRemoteStreamContent remoteStreamContent, IImageCompressor imageCompressor)
{
if(remoteStreamContent.ContentType == null || !remoteStreamContent.ContentType.StartsWith("image/"))
{
return remoteStreamContent;
}
var result = await imageCompressor.CompressAsync(remoteStreamContent.GetStream(), remoteStreamContent.ContentType);
if (result.State != ImageProcessState.Done)
{
return remoteStreamContent;
}
var fileName = remoteStreamContent.FileName;
var contentType = remoteStreamContent.ContentType;
remoteStreamContent.Dispose();
return new RemoteStreamContent(result.Result, fileName, contentType);
}
protected virtual async Task<Stream> CompressImageAsync(Stream stream, IImageCompressor imageCompressor)
{
var result = await imageCompressor.CompressAsync(stream);
if (result.State != ImageProcessState.Done)
{
return stream;
}
await stream.DisposeAsync();
return result.Result;
}
protected virtual async Task<byte[]> CompressImageAsync(byte[] bytes, IImageCompressor imageCompressor)
{
return (await imageCompressor.CompressAsync(bytes)).Result;
}
}

117
framework/src/Volo.Abp.Imaging.AspNetCore/Volo/Abp/Imaging/ResizeImageAttribute.cs

@ -0,0 +1,117 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Content;
namespace Volo.Abp.Imaging;
public class ResizeImageAttribute : ActionFilterAttribute
{
public int? Width { get; }
public int? Height { get; }
public ImageResizeMode Mode { get; set; }
public string[] Parameters { get; }
public ResizeImageAttribute(int width, int height, params string[] parameters)
{
Width = width;
Height = height;
Parameters = parameters;
}
public ResizeImageAttribute(int size, params string[] parameters)
{
Width = size;
Height = size;
Parameters = parameters;
}
public async override Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
var parameters = Parameters.Any()
? context.ActionArguments.Where(x => Parameters.Contains(x.Key)).ToArray()
: context.ActionArguments.ToArray();
var imageResizer = context.HttpContext.RequestServices.GetRequiredService<IImageResizer>();
foreach (var (key, value) in parameters)
{
object resizedValue = value switch {
IFormFile file => await ResizeImageAsync(file, imageResizer),
IRemoteStreamContent remoteStreamContent => await ResizeImageAsync(remoteStreamContent, imageResizer),
Stream stream => await ResizeImageAsync(stream, imageResizer),
IEnumerable<byte> bytes => await ResizeImageAsync(bytes.ToArray(), imageResizer),
_ => null
};
if (resizedValue != null)
{
context.ActionArguments[key] = resizedValue;
}
}
await next();
}
protected virtual async Task<IFormFile> ResizeImageAsync(IFormFile file, IImageResizer imageResizer)
{
if(file.Headers == null || file.ContentType == null || !file.ContentType.StartsWith("image/"))
{
return file;
}
var result = await imageResizer.ResizeAsync(file.OpenReadStream(), new ImageResizeArgs(Width, Height, Mode), file.ContentType);
if (result.State != ImageProcessState.Done)
{
return file;
}
return new FormFile(result.Result, 0, result.Result.Length, file.Name, file.FileName) {
Headers = file.Headers
};
}
protected virtual async Task<IRemoteStreamContent> ResizeImageAsync(IRemoteStreamContent remoteStreamContent, IImageResizer imageResizer)
{
if(remoteStreamContent.ContentType == null || !remoteStreamContent.ContentType.StartsWith("image/"))
{
return remoteStreamContent;
}
var result = await imageResizer.ResizeAsync(remoteStreamContent.GetStream(), new ImageResizeArgs(Width, Height, Mode), remoteStreamContent.ContentType);
if (result.State != ImageProcessState.Done)
{
return remoteStreamContent;
}
var fileName = remoteStreamContent.FileName;
var contentType = remoteStreamContent.ContentType;
remoteStreamContent.Dispose();
return new RemoteStreamContent(result.Result, fileName, contentType);
}
protected virtual async Task<Stream> ResizeImageAsync(Stream stream, IImageResizer imageResizer)
{
var result = await imageResizer.ResizeAsync(stream, new ImageResizeArgs(Width, Height, Mode));
if (result.State != ImageProcessState.Done)
{
return stream;
}
await stream.DisposeAsync();
return result.Result;
}
protected virtual async Task<byte[]> ResizeImageAsync(byte[] bytes, IImageResizer imageResizer)
{
return (await imageResizer.ResizeAsync(bytes, new ImageResizeArgs(Width, Height, Mode))).Result;
}
}

3
framework/src/Volo.Abp.Imaging.ImageSharp/FodyWeavers.xml

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait ContinueOnCapturedContext="false" />
</Weavers>

30
framework/src/Volo.Abp.Imaging.ImageSharp/FodyWeavers.xsd

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" />
</xs:complexType>
</xs:element>
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

24
framework/src/Volo.Abp.Imaging.ImageSharp/Volo.Abp.Imaging.ImageSharp.csproj

@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\configureawait.props" />
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFrameworks>netstandard2.0;</TargetFrameworks>
<PackageId>Volo.Abp.Imaging.ImageSharp</PackageId>
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.Imaging.Abstractions\Volo.Abp.Imaging.Abstractions.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.4" />
</ItemGroup>
</Project>

3
framework/src/Volo.Abp.Imaging.ImageSharp/Volo.Abp.Imaging.ImageSharp.json

@ -0,0 +1,3 @@
{
"role": "lib.framework"
}

8
framework/src/Volo.Abp.Imaging.ImageSharp/Volo/Abp/Imaging/AbpImagingImageSharpModule.cs

@ -0,0 +1,8 @@
using Volo.Abp.Modularity;
namespace Volo.Abp.Imaging;
[DependsOn(typeof(AbpImagingAbstractionsModule))]
public class AbpImagingImageSharpModule : AbpModule
{
}

32
framework/src/Volo.Abp.Imaging.ImageSharp/Volo/Abp/Imaging/ImageSharpCompressOptions.cs

@ -0,0 +1,32 @@
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Formats.Webp;
namespace Volo.Abp.Imaging;
public class ImageSharpCompressOptions
{
public IImageEncoder JpegEncoder { get; set; }
public IImageEncoder PngEncoder { get; set; }
public IImageEncoder WebpEncoder { get; set; }
public int DefaultQuality { get; set; } = 75;
public ImageSharpCompressOptions()
{
JpegEncoder = new JpegEncoder {
Quality = DefaultQuality
};
PngEncoder = new PngEncoder {
CompressionLevel = PngCompressionLevel.BestCompression,
IgnoreMetadata = true
};
WebpEncoder = new WebpEncoder {
Quality = DefaultQuality
};
}
}

124
framework/src/Volo.Abp.Imaging.ImageSharp/Volo/Abp/Imaging/ImageSharpImageCompressorContributor.cs

@ -0,0 +1,124 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.Options;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Formats.Webp;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Http;
namespace Volo.Abp.Imaging;
public class ImageSharpImageCompressorContributor : IImageCompressorContributor, ITransientDependency
{
protected ImageSharpCompressOptions Options { get; }
public ImageSharpImageCompressorContributor(IOptions<ImageSharpCompressOptions> options)
{
Options = options.Value;
}
public virtual async Task<ImageCompressResult<Stream>> TryCompressAsync(
Stream stream,
[CanBeNull] string mimeType = null,
CancellationToken cancellationToken = default)
{
if (!string.IsNullOrWhiteSpace(mimeType) && !CanCompress(mimeType))
{
return new ImageCompressResult<Stream>(stream, ImageProcessState.Unsupported);
}
var (image, format) = await Image.LoadWithFormatAsync(stream, cancellationToken);
if (!CanCompress(format.DefaultMimeType))
{
return new ImageCompressResult<Stream>(stream, ImageProcessState.Unsupported);
}
var memoryStream = await GetStreamFromImageAsync(image, format, cancellationToken);
if (memoryStream.Length < stream.Length)
{
return new ImageCompressResult<Stream>(memoryStream, ImageProcessState.Done);
}
memoryStream.Dispose();
return new ImageCompressResult<Stream>(stream, ImageProcessState.Canceled);
}
public virtual async Task<ImageCompressResult<byte[]>> TryCompressAsync(
byte[] bytes,
[CanBeNull] string mimeType = null,
CancellationToken cancellationToken = default)
{
if (!string.IsNullOrWhiteSpace(mimeType) && !CanCompress(mimeType))
{
return new ImageCompressResult<byte[]>(bytes, ImageProcessState.Unsupported);
}
using var ms = new MemoryStream(bytes);
var result = await TryCompressAsync(ms, mimeType, cancellationToken);
if (result.State != ImageProcessState.Done)
{
return new ImageCompressResult<byte[]>(bytes, result.State);
}
var newBytes = await result.Result.GetAllBytesAsync(cancellationToken);
result.Result.Dispose();
return new ImageCompressResult<byte[]>(newBytes, result.State);
}
protected virtual bool CanCompress(string mimeType)
{
return mimeType switch {
MimeTypes.Image.Jpeg => true,
MimeTypes.Image.Png => true,
MimeTypes.Image.Webp => true,
_ => false
};
}
protected virtual async Task<Stream> GetStreamFromImageAsync(
Image image,
IImageFormat format,
CancellationToken cancellationToken = default)
{
var memoryStream = new MemoryStream();
try
{
await image.SaveAsync(memoryStream, GetEncoder(format), cancellationToken: cancellationToken);
memoryStream.Position = 0;
return memoryStream;
}
catch
{
memoryStream.Dispose();
throw;
}
}
protected virtual IImageEncoder GetEncoder(IImageFormat format)
{
switch (format.DefaultMimeType)
{
case MimeTypes.Image.Jpeg:
return Options.JpegEncoder ?? new JpegEncoder();
case MimeTypes.Image.Png:
return Options.PngEncoder ?? new PngEncoder();
case MimeTypes.Image.Webp:
return Options.WebpEncoder ?? new WebpEncoder();
default:
throw new NotSupportedException($"No encoder available for the given format: {format.Name}");
}
}
}

124
framework/src/Volo.Abp.Imaging.ImageSharp/Volo/Abp/Imaging/ImageSharpImageResizerContributor.cs

@ -0,0 +1,124 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Processing;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Http;
namespace Volo.Abp.Imaging;
public class ImageSharpImageResizerContributor : IImageResizerContributor, ITransientDependency
{
public virtual async Task<ImageResizeResult<Stream>> TryResizeAsync(
Stream stream,
ImageResizeArgs resizeArgs,
[CanBeNull] string mimeType = null,
CancellationToken cancellationToken = default)
{
if (!string.IsNullOrWhiteSpace(mimeType) && !CanResize(mimeType))
{
return new ImageResizeResult<Stream>(stream, ImageProcessState.Unsupported);
}
var (image, format) = await Image.LoadWithFormatAsync(stream, cancellationToken);
if (!CanResize(format.DefaultMimeType))
{
return new ImageResizeResult<Stream>(stream, ImageProcessState.Unsupported);
}
if (ResizeModeMap.TryGetValue(resizeArgs.Mode, out var resizeMode))
{
image.Mutate(x => x.Resize(new ResizeOptions { Size = GetSize(resizeArgs), Mode = resizeMode }));
}
else
{
throw new NotSupportedException("Resize mode " + resizeArgs.Mode + "is not supported!");
}
var memoryStream = new MemoryStream();
try
{
await image.SaveAsync(memoryStream, format, cancellationToken: cancellationToken);
memoryStream.Position = 0;
return new ImageResizeResult<Stream>(memoryStream, ImageProcessState.Done);
}
catch
{
memoryStream.Dispose();
throw;
}
}
public virtual async Task<ImageResizeResult<byte[]>> TryResizeAsync(
byte[] bytes,
ImageResizeArgs resizeArgs,
[CanBeNull] string mimeType = null,
CancellationToken cancellationToken = default)
{
if (!string.IsNullOrWhiteSpace(mimeType) && !CanResize(mimeType))
{
return new ImageResizeResult<byte[]>(bytes, ImageProcessState.Unsupported);
}
using var ms = new MemoryStream(bytes);
var result = await TryResizeAsync(ms, resizeArgs, mimeType, cancellationToken);
if (result.State != ImageProcessState.Done)
{
return new ImageResizeResult<byte[]>(bytes, result.State);
}
var newBytes = await result.Result.GetAllBytesAsync(cancellationToken);
result.Result.Dispose();
return new ImageResizeResult<byte[]>(newBytes, result.State);
}
protected virtual bool CanResize(string mimeType)
{
return mimeType switch {
MimeTypes.Image.Jpeg => true,
MimeTypes.Image.Png => true,
MimeTypes.Image.Gif => true,
MimeTypes.Image.Bmp => true,
MimeTypes.Image.Tiff => true,
MimeTypes.Image.Webp => true,
_ => false
};
}
protected Dictionary<ImageResizeMode, ResizeMode> ResizeModeMap = new() {
{ ImageResizeMode.None, default },
{ ImageResizeMode.Stretch, ResizeMode.Stretch },
{ ImageResizeMode.BoxPad, ResizeMode.BoxPad },
{ ImageResizeMode.Min, ResizeMode.Min },
{ ImageResizeMode.Max, ResizeMode.Max },
{ ImageResizeMode.Crop, ResizeMode.Crop },
{ ImageResizeMode.Pad, ResizeMode.Pad }
};
private static Size GetSize(ImageResizeArgs resizeArgs)
{
var size = new Size();
if (resizeArgs.Width > 0)
{
size.Width = resizeArgs.Width;
}
if (resizeArgs.Height > 0)
{
size.Height = resizeArgs.Height;
}
return size;
}
}

3
framework/src/Volo.Abp.Imaging.MagickNet/FodyWeavers.xml

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait ContinueOnCapturedContext="false" />
</Weavers>

30
framework/src/Volo.Abp.Imaging.MagickNet/FodyWeavers.xsd

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" />
</xs:complexType>
</xs:element>
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

3
framework/src/Volo.Abp.Imaging.MagickNet/Volo.Abp.Imaging.MagickNet.abppkg.json

@ -0,0 +1,3 @@
{
"role": "lib.framework"
}

24
framework/src/Volo.Abp.Imaging.MagickNet/Volo.Abp.Imaging.MagickNet.csproj

@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\configureawait.props" />
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netstandard2.1;net7.0</TargetFrameworks>
<PackageId>Volo.Abp.Imaging.MagicNet</PackageId>
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.Imaging.Abstractions\Volo.Abp.Imaging.Abstractions.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Magick.NET-Q16-AnyCPU" Version="13.1.0" />
</ItemGroup>
</Project>

8
framework/src/Volo.Abp.Imaging.MagickNet/Volo/Abp/Imaging/AbpImagingMagickNetModule.cs

@ -0,0 +1,8 @@
using Volo.Abp.Modularity;
namespace Volo.Abp.Imaging;
[DependsOn(typeof(AbpImagingAbstractionsModule))]
public class AbpImagingMagickNetModule : AbpModule
{
}

103
framework/src/Volo.Abp.Imaging.MagickNet/Volo/Abp/Imaging/MagickImageCompressorContributor.cs

@ -0,0 +1,103 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using ImageMagick;
using JetBrains.Annotations;
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Http;
namespace Volo.Abp.Imaging;
public class MagickImageCompressorContributor : IImageCompressorContributor, ITransientDependency
{
protected MagickNetCompressOptions Options { get; }
protected readonly ImageOptimizer Optimizer;
public MagickImageCompressorContributor(IOptions<MagickNetCompressOptions> options)
{
Options = options.Value;
Optimizer = new ImageOptimizer
{
OptimalCompression = Options.OptimalCompression,
IgnoreUnsupportedFormats = Options.IgnoreUnsupportedFormats
};
}
public virtual async Task<ImageCompressResult<Stream>> TryCompressAsync(
Stream stream,
[CanBeNull] string mimeType = null,
CancellationToken cancellationToken = default)
{
if (!string.IsNullOrWhiteSpace(mimeType) && !CanCompress(mimeType))
{
return new ImageCompressResult<Stream>(stream, ImageProcessState.Unsupported);
}
var memoryStream = await stream.CreateMemoryStreamAsync(cancellationToken: cancellationToken);
try
{
if (!Optimizer.IsSupported(memoryStream))
{
return new ImageCompressResult<Stream>(stream, ImageProcessState.Unsupported);
}
if (Compress(memoryStream))
{
return new ImageCompressResult<Stream>(memoryStream, ImageProcessState.Done);
}
memoryStream.Dispose();
return new ImageCompressResult<Stream>(stream, ImageProcessState.Canceled);
}
catch
{
memoryStream.Dispose();
throw;
}
}
public virtual async Task<ImageCompressResult<byte[]>> TryCompressAsync(
byte[] bytes,
[CanBeNull] string mimeType = null,
CancellationToken cancellationToken = default)
{
if (!string.IsNullOrWhiteSpace(mimeType) && !CanCompress(mimeType))
{
return new ImageCompressResult<byte[]>(bytes, ImageProcessState.Unsupported);
}
using var memoryStream = new MemoryStream(bytes);
var result = await TryCompressAsync(memoryStream, mimeType, cancellationToken);
if (result.State != ImageProcessState.Done)
{
return new ImageCompressResult<byte[]>(bytes, result.State);
}
var newBytes = await result.Result.GetAllBytesAsync(cancellationToken);
result.Result.Dispose();
return new ImageCompressResult<byte[]>(newBytes, result.State);
}
protected virtual bool CanCompress(string mimeType)
{
return mimeType switch {
MimeTypes.Image.Jpeg => true,
MimeTypes.Image.Png => true,
MimeTypes.Image.Gif => true,
_ => false
};
}
protected virtual bool Compress(Stream stream)
{
return Options.Lossless ? Optimizer.LosslessCompress(stream) : Optimizer.Compress(stream);
}
}

325
framework/src/Volo.Abp.Imaging.MagickNet/Volo/Abp/Imaging/MagickImageResizerContributor.cs

@ -0,0 +1,325 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using ImageMagick;
using JetBrains.Annotations;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Http;
namespace Volo.Abp.Imaging;
public class MagickImageResizerContributor : IImageResizerContributor, ITransientDependency
{
private const int Min = 1;
public virtual async Task<ImageResizeResult<Stream>> TryResizeAsync(
Stream stream,
ImageResizeArgs resizeArgs,
[CanBeNull] string mimeType = null,
CancellationToken cancellationToken = default)
{
if (!mimeType.IsNullOrWhiteSpace() && !CanResize(mimeType))
{
return new ImageResizeResult<Stream>(stream, ImageProcessState.Unsupported);
}
var memoryStream = await stream.CreateMemoryStreamAsync(cancellationToken: cancellationToken);
try
{
using var image = new MagickImage(memoryStream);
if (mimeType.IsNullOrWhiteSpace() && !CanResize(image.FormatInfo?.MimeType))
{
return new ImageResizeResult<Stream>(stream, ImageProcessState.Unsupported);
}
Resize(image, resizeArgs);
memoryStream.Position = 0;
await image.WriteAsync(memoryStream, cancellationToken);
memoryStream.SetLength(memoryStream.Position);
memoryStream.Position = 0;
return new ImageResizeResult<Stream>(memoryStream, ImageProcessState.Done);
}
catch
{
memoryStream.Dispose();
throw;
}
}
public virtual Task<ImageResizeResult<byte[]>> TryResizeAsync(
byte[] bytes,
ImageResizeArgs resizeArgs,
[CanBeNull] string mimeType = null,
CancellationToken cancellationToken = default)
{
if (!mimeType.IsNullOrWhiteSpace() && !CanResize(mimeType))
{
return Task.FromResult(new ImageResizeResult<byte[]>(bytes, ImageProcessState.Unsupported));
}
using var image = new MagickImage(bytes);
if (mimeType.IsNullOrWhiteSpace() && !CanResize(image.FormatInfo?.MimeType))
{
return Task.FromResult(new ImageResizeResult<byte[]>(bytes, ImageProcessState.Unsupported));
}
Resize(image, resizeArgs);
return Task.FromResult(new ImageResizeResult<byte[]>(image.ToByteArray(), ImageProcessState.Done));
}
protected virtual bool CanResize(string mimeType)
{
return mimeType switch {
MimeTypes.Image.Jpeg => true,
MimeTypes.Image.Png => true,
MimeTypes.Image.Gif => true,
MimeTypes.Image.Bmp => true,
MimeTypes.Image.Tiff => true,
MimeTypes.Image.Webp => true,
_ => false
};
}
protected virtual void Resize(MagickImage image, ImageResizeArgs resizeArgs)
{
ApplyResizeMode(image, resizeArgs);
}
protected virtual void ApplyResizeMode(MagickImage image, ImageResizeArgs resizeArgs)
{
switch (resizeArgs.Mode)
{
case ImageResizeMode.None:
ResizeModeNone(image, resizeArgs);
break;
case ImageResizeMode.Stretch:
ResizeStretch(image, resizeArgs);
break;
case ImageResizeMode.Pad:
ResizePad(image, resizeArgs);
break;
case ImageResizeMode.BoxPad:
ResizeBoxPad(image, resizeArgs);
break;
case ImageResizeMode.Max:
ResizeMax(image, resizeArgs);
break;
case ImageResizeMode.Min:
ResizeMin(image, resizeArgs);
break;
case ImageResizeMode.Crop:
ResizeCrop(image, resizeArgs);
break;
default:
throw new NotSupportedException("Resize mode " + resizeArgs.Mode + "is not supported!");
}
}
protected virtual int GetTargetHeight(ImageResizeArgs resizeArgs, int min, int sourceWidth, int sourceHeight)
{
if (resizeArgs.Height == 0 && resizeArgs.Width > 0)
{
return Math.Max(min, (int)Math.Round(sourceHeight * resizeArgs.Width / (float)sourceWidth));
}
return resizeArgs.Height;
}
protected virtual int GetTargetWidth(ImageResizeArgs resizeArgs, int min, int sourceWidth, int sourceHeight)
{
if (resizeArgs.Width == 0 && resizeArgs.Height > 0)
{
return Math.Max(min, (int)Math.Round(sourceWidth * resizeArgs.Height / (float)sourceHeight));
}
return resizeArgs.Width;
}
protected virtual void ResizeModeNone(IMagickImage image, ImageResizeArgs resizeArgs)
{
var sourceWidth = image.Width;
var sourceHeight = image.Height;
image.Resize(
GetTargetWidth(resizeArgs, Min, sourceWidth, sourceHeight),
GetTargetHeight(resizeArgs, Min, sourceWidth, sourceHeight)
);
}
protected virtual void ResizeStretch(IMagickImage image, ImageResizeArgs resizeArgs)
{
var sourceWidth = image.Width;
var sourceHeight = image.Height;
image.Resize(
new MagickGeometry(
GetTargetWidth(resizeArgs, Min, sourceWidth, sourceHeight),
GetTargetHeight(resizeArgs, Min, sourceWidth, sourceHeight)) { IgnoreAspectRatio = true });
}
protected virtual void ResizePad(MagickImage image, ImageResizeArgs resizeArgs)
{
var sourceWidth = image.Width;
var sourceHeight = image.Height;
var targetWidth = GetTargetWidth(resizeArgs, Min, sourceWidth, sourceHeight);
var targetHeight = GetTargetHeight(resizeArgs, Min, sourceWidth, sourceHeight);
var percentHeight = CalculatePercent(sourceHeight, targetHeight);
var percentWidth = CalculatePercent(sourceWidth, targetWidth);
var newWidth = targetWidth;
var newHeight = targetHeight;
if (percentHeight < percentWidth)
{
newWidth = (int)Math.Round(sourceWidth * percentHeight);
}
else
{
newHeight = (int)Math.Round(sourceHeight * percentWidth);
}
image.Resize(newWidth, newHeight);
image.Extent(targetWidth, targetHeight, Gravity.Center, MagickColors.Transparent);
}
protected virtual void ResizeBoxPad(MagickImage image, ImageResizeArgs resizeArgs)
{
var sourceWidth = image.Width;
var sourceHeight = image.Height;
var targetWidth = GetTargetWidth(resizeArgs, Min, sourceWidth, sourceHeight);
var targetHeight = GetTargetHeight(resizeArgs, Min, sourceWidth, sourceHeight);
var percentHeight = CalculatePercent(sourceHeight, targetHeight);
var percentWidth = CalculatePercent(sourceWidth, targetWidth);
var newWidth = targetWidth;
var newHeight = targetHeight;
var boxPadWidth = targetWidth > 0 ? targetWidth : (int)Math.Round(sourceWidth * percentHeight);
var boxPadHeight = targetHeight > 0 ? targetHeight : (int)Math.Round(sourceHeight * percentWidth);
if (sourceWidth < boxPadWidth && sourceHeight < boxPadHeight)
{
newWidth = boxPadWidth;
newHeight = boxPadHeight;
}
image.Resize(newWidth, newHeight);
image.Extent(targetWidth, targetHeight, Gravity.Center, MagickColors.Transparent);
}
protected virtual void ResizeMax(IMagickImage image, ImageResizeArgs resizeArgs)
{
var sourceWidth = image.Width;
var sourceHeight = image.Height;
var imageRatio = CalculateRatio(sourceWidth, sourceHeight);
var targetWidth = GetTargetWidth(resizeArgs, Min, sourceWidth, sourceHeight);
var targetHeight = GetTargetHeight(resizeArgs, Min, sourceWidth, sourceHeight);
var ratio = CalculateRatio(targetWidth, targetHeight);
var percentHeight = CalculatePercent(sourceHeight, targetHeight);
var percentWidth = CalculatePercent(sourceWidth, targetWidth);
if (imageRatio < ratio)
{
targetHeight = (int)(sourceHeight * percentWidth);
}
else
{
targetWidth = (int)(sourceWidth * percentHeight);
}
image.Resize(targetWidth, targetHeight);
}
protected virtual void ResizeMin(MagickImage image, ImageResizeArgs resizeArgs)
{
var sourceWidth = image.Width;
var sourceHeight = image.Height;
var imageRatio = CalculateRatio(sourceWidth, sourceHeight);
var targetWidth = GetTargetWidth(resizeArgs, Min, sourceWidth, sourceHeight);
var targetHeight = GetTargetHeight(resizeArgs, Min, sourceWidth, sourceHeight);
var percentWidth = CalculatePercent(sourceWidth, targetWidth);
if (targetWidth > sourceWidth || targetHeight > sourceHeight)
{
targetWidth = sourceWidth;
targetHeight = sourceHeight;
}
else
{
var widthDiff = sourceWidth - targetWidth;
var heightDiff = sourceHeight - targetHeight;
if (widthDiff > heightDiff)
{
targetWidth = (int)Math.Round(targetHeight / imageRatio);
}
else if (widthDiff < heightDiff)
{
targetHeight = (int)Math.Round(targetWidth * imageRatio);
}
else
{
if (targetHeight > targetWidth)
{
targetWidth = (int)Math.Round(sourceHeight * percentWidth);
}
else
{
targetHeight = (int)Math.Round(sourceHeight * percentWidth);
}
}
}
image.Resize(targetWidth, targetHeight);
}
protected virtual void ResizeCrop(MagickImage image, ImageResizeArgs resizeArgs)
{
var sourceWidth = image.Width;
var sourceHeight = image.Height;
var targetWidth = GetTargetWidth(resizeArgs, Min, sourceWidth, sourceHeight);
var targetHeight = GetTargetHeight(resizeArgs, Min, sourceWidth, sourceHeight);
image.Extent(
targetWidth,
targetHeight,
Gravity.Center,
MagickColors.Transparent);
image.Crop(
new MagickGeometry(
targetWidth,
targetHeight) { IgnoreAspectRatio = true },
Gravity.Center);
}
protected virtual float CalculatePercent(int imageHeightOrWidth, int heightOrWidth)
{
return heightOrWidth / (float)imageHeightOrWidth;
}
protected virtual float CalculateRatio(int width, int height)
{
return height / (float)width;
}
}

8
framework/src/Volo.Abp.Imaging.MagickNet/Volo/Abp/Imaging/MagickNetCompressOptions.cs

@ -0,0 +1,8 @@
namespace Volo.Abp.Imaging;
public class MagickNetCompressOptions
{
public bool OptimalCompression { get; set; }
public bool IgnoreUnsupportedFormats { get; set; }
public bool Lossless { get; set; }
}

4
framework/src/Volo.Abp.MongoDB/Volo/Abp/Uow/MongoDB/MongoDbTransactionApi.cs

@ -19,9 +19,9 @@ public class MongoDbTransactionApi : ITransactionApi, ISupportsRollback
CancellationTokenProvider = cancellationTokenProvider;
}
public async Task CommitAsync()
public async Task CommitAsync(CancellationToken cancellationToken)
{
await SessionHandle.CommitTransactionAsync(CancellationTokenProvider.Token);
await SessionHandle.CommitTransactionAsync(CancellationTokenProvider.FallbackToProvider(cancellationToken));
}
public void Dispose()

2
framework/src/Volo.Abp.Uow/Volo/Abp/Uow/ISupportsRollback.cs

@ -5,5 +5,5 @@ namespace Volo.Abp.Uow;
public interface ISupportsRollback
{
Task RollbackAsync(CancellationToken cancellationToken);
Task RollbackAsync(CancellationToken cancellationToken = default);
}

3
framework/src/Volo.Abp.Uow/Volo/Abp/Uow/ITransactionApi.cs

@ -1,9 +1,10 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Volo.Abp.Uow;
public interface ITransactionApi : IDisposable
{
Task CommitAsync();
Task CommitAsync(CancellationToken cancellationToken = default);
}

6
framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWork.cs

@ -158,7 +158,7 @@ public class UnitOfWork : IUnitOfWork, ITransientDependency
await SaveChangesAsync(cancellationToken);
}
await CommitTransactionsAsync();
await CommitTransactionsAsync(cancellationToken);
IsCompleted = true;
await OnCompletedAsync();
}
@ -363,11 +363,11 @@ public class UnitOfWork : IUnitOfWork, ITransientDependency
}
}
protected virtual async Task CommitTransactionsAsync()
protected virtual async Task CommitTransactionsAsync(CancellationToken cancellationToken)
{
foreach (var transaction in GetAllActiveTransactionApis())
{
await transaction.CommitAsync();
await transaction.CommitAsync(cancellationToken);
}
}

50
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Controllers/ReplaceBuiltInController.cs

@ -0,0 +1,50 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Controllers;
using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
namespace Volo.Abp.AspNetCore.Mvc.Controllers;
[Area("abp")]
[RemoteService(Name = "abp")]
[ReplaceControllers(typeof(AbpApplicationConfigurationController), typeof(AbpApplicationLocalizationController))]
public class ReplaceBuiltInController : AbpController
{
[HttpGet("api/abp/application-configuration")]
public virtual Task<MyApplicationConfigurationDto> GetAsync(MyApplicationConfigurationRequestOptions options)
{
return Task.FromResult(new MyApplicationConfigurationDto()
{
Random = options.Random
});
}
[HttpGet("api/abp/application-localization")]
public virtual Task<MyApplicationLocalizationDto> GetAsync(MyApplicationLocalizationRequestDto input)
{
return Task.FromResult(new MyApplicationLocalizationDto()
{
Random = input.Random
});
}
}
public class MyApplicationConfigurationRequestOptions : ApplicationConfigurationRequestOptions
{
public string Random { get; set; }
}
public class MyApplicationConfigurationDto : ApplicationConfigurationDto
{
public string Random { get; set; }
}
public class MyApplicationLocalizationRequestDto : ApplicationLocalizationRequestDto
{
public string Random { get; set; }
}
public class MyApplicationLocalizationDto : ApplicationLocalizationDto
{
public string Random { get; set; }
}

32
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Controllers/ReplaceBuiltInController_Tests.cs

@ -0,0 +1,32 @@
using System;
using System.Net;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Shouldly;
using Volo.Abp.AspNetCore.Mvc.Localization;
using Xunit;
namespace Volo.Abp.AspNetCore.Mvc.Controllers;
public class ReplaceBuiltInController_Tests : AspNetCoreMvcTestBase
{
protected override void ConfigureServices(HostBuilderContext context, IServiceCollection services)
{
services.Configure<AbpAspNetCoreMvcOptions>(options =>
{
options.ControllersToRemove.Add(typeof(AbpLanguagesController));
});
}
[Fact]
public async Task Test()
{
var random = Guid.NewGuid().ToString("N");
(await GetResponseAsObjectAsync<MyApplicationConfigurationDto>("api/abp/application-configuration?random=" + random)).Random.ShouldBe(random);
(await GetResponseAsObjectAsync<MyApplicationLocalizationDto>("api/abp/application-localization?CultureName=en&random=" + random)).Random.ShouldBe(random);
(await GetResponseAsync("Abp/Languages/Switch", HttpStatusCode.NotFound)).StatusCode.ShouldBe(HttpStatusCode.NotFound);
}
}

1
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Security/Headers/SecurityHeadersTestController_Tests.cs

@ -29,7 +29,6 @@ public class SecurityHeadersTestController_Tests : AspNetCoreMvcTestBase
responseMessage.Headers.ShouldContain(x => x.Key == "X-XSS-Protection" & x.Value.First().ToString() == "1; mode=block");
responseMessage.Headers.ShouldContain(x => x.Key == "X-Frame-Options" & x.Value.First().ToString() == "SAMEORIGIN");
responseMessage.Headers.ShouldContain(x => x.Key == "X-Content-Type-Options" & x.Value.First().ToString() == "nosniff");
responseMessage.Headers.ShouldContain(x => x.Key == "Content-Security-Policy" & x.Value.First().ToString() == "object-src 'none'; form-action 'self'; frame-ancestors 'none'");
}
[Fact]

28
framework/test/Volo.Abp.Core.Tests/Volo/Abp/Threading/SemaphoreSlimExtensions_Tests.cs

@ -0,0 +1,28 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Shouldly;
using Xunit;
namespace Volo.Abp.Threading;
public class SemaphoreSlimExtensions_Tests
{
[Fact]
public async Task LockAsync_Test()
{
var semaphore = new SemaphoreSlim(0, 1);
await Assert.ThrowsAsync<TimeoutException>(async () =>
{
await semaphore.LockAsync(10);
});
semaphore = new SemaphoreSlim(1, 1);
using (await semaphore.LockAsync())
{
semaphore.CurrentCount.ShouldBe(0);
}
semaphore.CurrentCount.ShouldBe(1);
}
}

29
framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Uow/UnitOfWork_CancellationToken_Tests.cs

@ -0,0 +1,29 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.TestApp.Domain;
using Volo.Abp.Uow;
using Xunit;
namespace Volo.Abp.EntityFrameworkCore.Uow;
public class UnitOfWork_CancellationToken_Tests : EntityFrameworkCoreTestBase
{
[Fact]
public async Task Should_Cancel_Test()
{
using (var uow = GetRequiredService<IUnitOfWorkManager>().Begin(isTransactional: true))
{
await Assert.ThrowsAsync<TaskCanceledException>(async () =>
{
var cst = new CancellationTokenSource();
cst.Cancel();
await GetRequiredService<IBasicRepository<Person, Guid>>().InsertAsync(new Person(Guid.NewGuid(), "Adam", 42));
await uow.CompleteAsync(cst.Token);
});
}
}
}

3
framework/test/Volo.Abp.Imaging.Abstractions.Tests/Volo.Abp.Imaging.Abstractions.Tests.abppkg.json

@ -0,0 +1,3 @@
{
"role": "lib.test"
}

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

Loading…
Cancel
Save