mirror of https://github.com/abpframework/abp.git
507 changed files with 11250 additions and 4427 deletions
@ -0,0 +1,94 @@ |
|||
# Contributor Covenant Code of Conduct |
|||
|
|||
## Preamble |
|||
|
|||
The ABP Framework was created to implement common generic web application features in a common code base. This approach helps developers to decouple line of business application features from their custom business logic. Besides, the ABP Framework helps create MVPs and kick-start your project as quickly as possible. All the contributors try to save valuable time by automating repetitive tasks at the framework level. This code of conduct has been adopted by many [other open source communities](http://contributor-covenant.org/adopters/) and we feel it expresses our values well. |
|||
|
|||
|
|||
|
|||
## Our Pledge |
|||
|
|||
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. |
|||
|
|||
|
|||
|
|||
## Our Standards |
|||
|
|||
Examples of behavior that contributes to a positive environment for our community include: |
|||
|
|||
- Demonstrating empathy and kindness toward other people |
|||
- Being respectful of differing opinions, viewpoints, and experiences |
|||
- Giving and gracefully accepting constructive feedback |
|||
- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience |
|||
- Focusing on what is best not just for us as individuals but for the overall community |
|||
|
|||
Examples of unacceptable behavior include: |
|||
|
|||
- The use of sexualized language or imagery, and sexual attention or advances of any kind |
|||
- Trolling, insulting or derogatory comments, and personal or political attacks |
|||
- Public or private harassment |
|||
- Publishing others’ private information, such as a physical or email address, without their explicit permission |
|||
- Other conduct which could reasonably be considered inappropriate in a professional setting |
|||
|
|||
|
|||
|
|||
## Enforcement Responsibilities |
|||
|
|||
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. |
|||
|
|||
Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned with this Code of Conduct and will communicate reasons for moderation decisions when appropriate. |
|||
|
|||
|
|||
|
|||
## Scope |
|||
|
|||
This Code of Conduct applies within all community spaces and when an individual officially represents the community in public spaces. Examples of representing our community include: |
|||
|
|||
- Using an official e-mail address. |
|||
- Posting via an official social media account. |
|||
- Acting as an appointed representative at an online or offline event. |
|||
|
|||
|
|||
|
|||
## Enforcement |
|||
|
|||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [info@abp.io](mailto:info@abp.io). All complaints will be reviewed and investigated promptly and fairly. All community leaders must to respect the privacy and security of the reporter of any incident. |
|||
|
|||
|
|||
|
|||
## Enforcement Guidelines |
|||
|
|||
Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: |
|||
|
|||
### 1. Correction |
|||
|
|||
**Community Impact:** Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. |
|||
|
|||
**Consequence:** A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. |
|||
|
|||
### 2. Warning |
|||
|
|||
**Community Impact:** A violation through a single incident or series of actions. |
|||
|
|||
**Consequence:** A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period. This includes avoiding interactions in community spaces and external channels like social media. Violating these terms may lead to a temporary or permanent ban. |
|||
|
|||
### 3. Temporary Ban |
|||
|
|||
**Community Impact:** A serious violation of community standards, including sustained inappropriate behavior. |
|||
|
|||
**Consequence:** A temporary ban from any interaction or public communication with the community for a specified period. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. |
|||
|
|||
### 4. Permanent Ban |
|||
|
|||
**Community Impact:** Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. |
|||
|
|||
**Consequence:** A permanent ban from any sort of public interaction within the community. |
|||
|
|||
|
|||
|
|||
## Attribution |
|||
|
|||
This document is adapted from the [Contributor Covenant](https://www.contributor-covenant.org/), version 2.0, available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. |
|||
Community Impact Guidelines were inspired by [Mozilla’s code of conduct enforcement ladder](https://github.com/mozilla/diversity). |
|||
For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq. |
|||
Translations are available at https://www.contributor-covenant.org/translations. |
|||
@ -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> |
|||
|
|||
@ -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) |
|||
{ |
|||
//... |
|||
} |
|||
``` |
|||
@ -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 |
|||
``` |
|||
|
|||
@ -0,0 +1,51 @@ |
|||
# Checkbox Component |
|||
|
|||
The ABP Checkbox Component is a reusable form input component for the checkbox type. |
|||
|
|||
# Inputs |
|||
|
|||
- `label` |
|||
- `labelClass (default form-check-label)` |
|||
- `checkboxId` |
|||
- `checkboxReadonly` |
|||
- `checkboxReadonly (default form-check-input)` |
|||
- `checkboxStyle` |
|||
|
|||
# Outputs |
|||
|
|||
- `checkboxBlur` |
|||
- `checkboxFocus` |
|||
|
|||
# Usage |
|||
|
|||
The ABP Checkbox component is a part of the `ThemeSharedModule` module. If you've imported that module into your module, there's no need to import it again. If not, then first import it as shown below: |
|||
|
|||
```ts |
|||
// my-feature.module.ts |
|||
|
|||
import { ThemeSharedModule } from "@abp/ng.theme.shared"; |
|||
import { CheckboxDemoComponent } from "./CheckboxDemoComponent.component"; |
|||
|
|||
@NgModule({ |
|||
imports: [ |
|||
ThemeSharedModule, |
|||
// ... |
|||
], |
|||
declarations: [CheckboxDemoComponent], |
|||
// ... |
|||
}) |
|||
export class MyFeatureModule {} |
|||
``` |
|||
|
|||
Then, the `abp-checkbox` component can be used. See the example below: |
|||
|
|||
```html |
|||
<div class="form-check"> |
|||
<abp-checkbox label="Yes,I Agree" checkboxId="checkbox-input"> |
|||
</abp-checkbox> |
|||
</div> |
|||
``` |
|||
|
|||
See the checkbox input result below: |
|||
|
|||
 |
|||
@ -0,0 +1,49 @@ |
|||
# Form Input Component |
|||
|
|||
The ABP FormInput Component is a reusable form input component for the text type. |
|||
|
|||
# Inputs |
|||
* `label` |
|||
* `labelClass (default form-label)` |
|||
* `inputPlaceholder` |
|||
* `inputReadonly` |
|||
* `inputClass (default form-control)` |
|||
|
|||
# Outputs |
|||
* `formBlur` |
|||
* `formFocus` |
|||
|
|||
# Usage |
|||
|
|||
The ABP FormInput component is a part of the `ThemeSharedModule` module. If you've imported that module into your module, there's no need to import it again. If not, then first import it as shown below: |
|||
|
|||
```ts |
|||
import { ThemeSharedModule } from "@abp/ng.theme.shared"; |
|||
import { FormInputDemoComponent } from "./FomrInputDemoComponent.component"; |
|||
|
|||
@NgModule({ |
|||
imports: [ |
|||
ThemeSharedModule, |
|||
// ... |
|||
], |
|||
declarations: [FormInputDemoComponent], |
|||
}) |
|||
export class MyFeatureModule {} |
|||
``` |
|||
|
|||
Then, the `abp-form-input` component can be used. See the example below: |
|||
|
|||
```html |
|||
<div class="row"> |
|||
<div class="col-4"> |
|||
<abp-form-input |
|||
label="AbpAccount::UserNameOrEmailAddress" |
|||
inputId="login-input-user-name-or-email-address" |
|||
></abp-form-input> |
|||
</div> |
|||
</div> |
|||
``` |
|||
|
|||
See the form input result below: |
|||
|
|||
 |
|||
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 107 KiB |
|
After Width: | Height: | Size: 112 KiB |
|
After Width: | Height: | Size: 25 KiB |
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -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}\""); |
|||
} |
|||
} |
|||
@ -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>(); |
|||
} |
|||
} |
|||
|
|||
@ -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."; |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
using System.Linq; |
|||
using System.Runtime.InteropServices; |
|||
using Volo.Abp.Cli.ProjectBuilding.Building; |
|||
using Volo.Abp.Cli.ProjectBuilding.Files; |
|||
|
|||
namespace Volo.Abp.Cli.ProjectBuilding.Templates.Microservice; |
|||
|
|||
public class UpdateDockerImagesStep : ProjectBuildPipelineStep |
|||
{ |
|||
private readonly string _ymlFilePath; |
|||
|
|||
public UpdateDockerImagesStep(string ymlFilePath) |
|||
{ |
|||
_ymlFilePath = ymlFilePath; |
|||
} |
|||
|
|||
public override void Execute(ProjectBuildContext context) |
|||
{ |
|||
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && RuntimeInformation.OSArchitecture == Architecture.Arm64) |
|||
{ |
|||
var file = context.Files.FirstOrDefault(f => f.Name == _ymlFilePath); |
|||
file?.ReplaceText("mcr.microsoft.com/mssql/server:2019-latest", "mcr.microsoft.com/azure-sql-edge"); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
using System; |
|||
|
|||
namespace Volo.Abp.EventBus.Local; |
|||
|
|||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] |
|||
public class LocalEventHandlerOrderAttribute : Attribute |
|||
{ |
|||
/// <summary>
|
|||
/// Handlers execute in ascending numeric value of the Order property.
|
|||
/// </summary>
|
|||
public int Order { get; set; } |
|||
|
|||
public LocalEventHandlerOrderAttribute(int order) |
|||
{ |
|||
Order = order; |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -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> |
|||
@ -0,0 +1,3 @@ |
|||
{ |
|||
"role": "lib.framework" |
|||
} |
|||
@ -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> |
|||
@ -0,0 +1,9 @@ |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.Threading; |
|||
|
|||
namespace Volo.Abp.Imaging; |
|||
|
|||
[DependsOn(typeof(AbpThreadingModule))] |
|||
public class AbpImagingAbstractionsModule : AbpModule |
|||
{ |
|||
} |
|||
@ -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 |
|||
); |
|||
} |
|||
@ -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); |
|||
} |
|||
@ -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 |
|||
); |
|||
} |
|||
@ -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); |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
namespace Volo.Abp.Imaging; |
|||
|
|||
public class ImageCompressResult<T> : ImageProcessResult<T> |
|||
{ |
|||
public ImageCompressResult(T result, ImageProcessState state) : base(result, state) |
|||
{ |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
namespace Volo.Abp.Imaging; |
|||
|
|||
public enum ImageProcessState : byte |
|||
{ |
|||
Done = 1, |
|||
Canceled = 2, |
|||
Unsupported = 3, |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -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 |
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
namespace Volo.Abp.Imaging; |
|||
|
|||
public class ImageResizeOptions |
|||
{ |
|||
public ImageResizeMode DefaultResizeMode { get; set; } = ImageResizeMode.None; |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
namespace Volo.Abp.Imaging; |
|||
|
|||
public class ImageResizeResult<T> : ImageProcessResult<T> |
|||
{ |
|||
public ImageResizeResult(T result, ImageProcessState state) : base(result, state) |
|||
{ |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -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> |
|||
@ -0,0 +1,3 @@ |
|||
{ |
|||
"role": "lib.framework" |
|||
} |
|||
@ -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> |
|||
@ -0,0 +1,9 @@ |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace Volo.Abp.Imaging; |
|||
|
|||
[DependsOn(typeof(AbpImagingAbstractionsModule))] |
|||
public class AbpImagingAspNetCoreModule : AbpModule |
|||
{ |
|||
|
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -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> |
|||
@ -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> |
|||
@ -0,0 +1,3 @@ |
|||
{ |
|||
"role": "lib.framework" |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace Volo.Abp.Imaging; |
|||
|
|||
[DependsOn(typeof(AbpImagingAbstractionsModule))] |
|||
public class AbpImagingImageSharpModule : AbpModule |
|||
{ |
|||
} |
|||
@ -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 |
|||
}; |
|||
} |
|||
} |
|||
@ -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}"); |
|||
} |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue