diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json index b363220bb4..d228bed40c 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json +++ b/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", diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json index 9efe75ad7e..bddb51b98b 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json +++ b/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 ABP framework. ABP Commercial is being developed and supported by the same team behind the ABP framework.", "WhatAreDifferencesThanABPFrameworkExplanation": "

ABP framework 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.

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

", "VisitTheFrameworkVSCommercialDocument": "Visit the following link for more information {1} ", @@ -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 blazorise.com/support/login.", "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 blazorise.com/support/user/manage/license.", - "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" } } diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json index 5fa2120cce..e6b3cf46f7 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json +++ b/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 \"{0}\"", "SeeMoreVideos": "See more videos", "DiscordPageTitle": "ABP Discord Community", - "ViewVideo": "View Video" + "ViewVideo": "View Video", + "AbpCommunityTitleContent": "ABP Community - Open Source ABP Framework" } } diff --git a/configureawait.props b/configureawait.props index 92f22f85fb..93d9dfa6a4 100644 --- a/configureawait.props +++ b/configureawait.props @@ -1,9 +1,9 @@ - + All runtime; build; native; contentfiles; analyzers - \ No newline at end of file + diff --git a/docs/en/CLI.md b/docs/en/CLI.md index 22be84f72c..a65f29a324 100644 --- a/docs/en/CLI.md +++ b/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`. diff --git a/docs/en/Image-Manipulation.md b/docs/en/Image-Manipulation.md new file mode 100644 index 0000000000..91f7c11c1e --- /dev/null +++ b/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> ResizeAsync( + Stream stream, + ImageResizeArgs resizeArgs, + string mimeType = null, + CancellationToken cancellationToken = default + ); + + /* Works with a byte array that contains an image file */ + Task> 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> CompressAsync( + Stream stream, + string mimeType = null, + CancellationToken cancellationToken = default + ); + + /* Works with a byte array that contains an image file */ + Task> 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(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` 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 Upload(IFormFile file) +{ + //... +} +``` + +### ResizeImageAttribute + +The `ResizeImageAttribute` is used to resize the image before requesting the action. `IFormFile`, `IRemoteStreamContent`, `Stream` and `IEnumrable` 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 Upload(IFormFile file) +{ + //... +} +``` \ No newline at end of file diff --git a/docs/en/KB/Windows-Path-Too-Long-Fix.md b/docs/en/KB/Windows-Path-Too-Long-Fix.md index e463288239..6a822c1402 100644 --- a/docs/en/KB/Windows-Path-Too-Long-Fix.md +++ b/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 \ No newline at end of file +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 +``` diff --git a/docs/en/Startup-Templates/Application.md b/docs/en/Startup-Templates/Application.md index 98ccdd10d2..0c5e9c4c38 100644 --- a/docs/en/Startup-Templates/Application.md +++ b/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: diff --git a/docs/en/UI/AspNetCore/Security-Headers.md b/docs/en/UI/AspNetCore/Security-Headers.md index 8322644b1d..b02e9691b0 100644 --- a/docs/en/UI/AspNetCore/Security-Headers.md +++ b/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(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(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()?.RouteTemplate == "/{YOURHOSTPAGE}"); + }); +}); +``` diff --git a/docs/en/UI/AspNetCore/Tag-Helpers/Dropdowns.md b/docs/en/UI/AspNetCore/Tag-Helpers/Dropdowns.md index 1c062b5f73..75d57479b5 100644 --- a/docs/en/UI/AspNetCore/Tag-Helpers/Dropdowns.md +++ b/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 diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index ad2bbc652b..c6c07d9a7c 100644 --- a/docs/en/docs-nav.json +++ b/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" diff --git a/docs/en/images/layered-project-dependencies-blazor-server.png b/docs/en/images/layered-project-dependencies-blazor-server.png new file mode 100644 index 0000000000..951363bdf1 Binary files /dev/null and b/docs/en/images/layered-project-dependencies-blazor-server.png differ diff --git a/docs/en/images/layered-project-dependencies-blazor-wasm.png b/docs/en/images/layered-project-dependencies-blazor-wasm.png new file mode 100644 index 0000000000..bd214ef762 Binary files /dev/null and b/docs/en/images/layered-project-dependencies-blazor-wasm.png differ diff --git a/docs/zh-Hans/CLI.md b/docs/zh-Hans/CLI.md index 4e778e1b31..ef412b7348 100644 --- a/docs/zh-Hans/CLI.md +++ b/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 diff --git a/docs/zh-Hans/Deployment/Clustered-Environment.md b/docs/zh-Hans/Deployment/Clustered-Environment.md index 34547709d1..a32b9b6ceb 100644 --- a/docs/zh-Hans/Deployment/Clustered-Environment.md +++ b/docs/zh-Hans/Deployment/Clustered-Environment.md @@ -1,4 +1,4 @@ -# 部署到群集环境 +# 部署到集群环境 本文档介绍了在将应用程序部署到**多个应用程序实例同时运行**的集群环境中时应注意的内容, 并解释了如何在基于ABP的应用程序中处理这些内容. diff --git a/docs/zh-Hans/docs-nav.json b/docs/zh-Hans/docs-nav.json index be30753fb3..1234b381a8 100644 --- a/docs/zh-Hans/docs-nav.json +++ b/docs/zh-Hans/docs-nav.json @@ -715,7 +715,7 @@ "path": "Deployment/Index.md", "items": [ { - "text": "部署到群集环境", + "text": "部署到集群环境", "path": "Deployment/Clustered-Environment.md" } ] diff --git a/framework/Volo.Abp.sln b/framework/Volo.Abp.sln index fc6755c18a..a475aa8aa5 100644 --- a/framework/Volo.Abp.sln +++ b/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} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/Extensibility/WebAssemblyLookupApiRequestService.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/Extensibility/WebAssemblyLookupApiRequestService.cs index b8eadacdd6..01378f5ddd 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/Extensibility/WebAssemblyLookupApiRequestService.cs +++ b/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; diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpDynamicformTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpDynamicformTagHelperService.cs index 1379b63d7c..b9a72fac0f 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpDynamicformTagHelperService.cs +++ b/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) || type == typeof(IEnumerable); } + + protected virtual bool IsFile(Type type) + { + return typeof(IFormFile).IsAssignableFrom(type) || + typeof(IEnumerable).IsAssignableFrom(type); + } protected virtual bool IsSelectGroup(TagHelperContext context, ModelExpression model) { diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerBaseTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerBaseTagHelperService.cs index 6e5f0ad2f7..92bb48b974 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerBaseTagHelperService.cs +++ b/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 : AbpTagHelperService where TTagHelper : AbpDatePickerBaseTagHelper { - protected readonly Dictionary> SupportedInputTypes = new() { - {typeof(string), o => DateTime.Parse((string)o).ToString("O")}, - {typeof(DateTime), o => ((DateTime) o).ToString("O")}, + protected readonly Dictionary> 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")} }; diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerTagHelperService.cs index e5eae85c53..727ab1a239 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerTagHelperService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerTagHelperService.cs @@ -63,11 +63,14 @@ public class AbpDatePickerTagHelperService : AbpDatePickerBaseTagHelperService file.StartsWith(x, StringComparison.OrdinalIgnoreCase))) ? "defer" : string.Empty; - output.Content.AppendHtml($"{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($"{Environment.NewLine}"); } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperStyleService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperStyleService.cs index 5869699f1e..0f68433f4c 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperStyleService.cs +++ b/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 options, - IWebHostEnvironment hostingEnvironment) : base( + IWebHostEnvironment hostingEnvironment, + IOptions securityHeadersOptions) : base( bundleManager, options, hostingEnvironment) { + SecurityHeadersOptions = securityHeadersOptions.Value; } protected override void CreateBundle(string bundleName, List 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($"{Environment.NewLine}"); + output.Content.AppendHtml(SecurityHeadersOptions.UseContentSecurityPolicyScriptNonce + ? $"{Environment.NewLine}" + : $"{Environment.NewLine}"); } else { diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/ScriptNonceTagHelper.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/ScriptNonceTagHelper.cs new file mode 100644 index 0000000000..6c29c81073 --- /dev/null +++ b/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); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalScriptContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalScriptContributor.cs index 2127b3f43a..2ac299959a 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalScriptContributor.cs +++ b/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" - }); + }); } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/dom-event-handlers.js b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/dom-event-handlers.js index 6501c23a14..b30e816c1d 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/dom-event-handlers.js +++ b/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); diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs index 0a12d0332a..e22fef4236 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs +++ b/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()); } public override void PostConfigureServices(ServiceConfigurationContext context) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcOptions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcOptions.cs index 364bb76c1f..1cd4b88fb8 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcOptions.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcOptions.cs @@ -12,6 +12,8 @@ public class AbpAspNetCoreMvcOptions public HashSet IgnoredControllersOnModelExclusion { get; } + public HashSet 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(); + ControllersToRemove = new HashSet(); AutoModelValidation = true; EnableRazorRuntimeCompilationOnDevelopment = true; ChangeControllerModelApiExplorerGroupName = true; diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs index 9a78479b9c..933f325975 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs +++ b/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(); + 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(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; } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Infrastructure/AbpMemoryPoolHttpResponseStreamWriterFactory.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Infrastructure/AbpMemoryPoolHttpResponseStreamWriterFactory.cs new file mode 100644 index 0000000000..78e13fc73e --- /dev/null +++ b/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; + +/// +/// https://github.com/dotnet/aspnetcore/issues/40928#issuecomment-1450063613 +/// +public class AbpMemoryPoolHttpResponseStreamWriterFactory : IHttpResponseStreamWriterFactory +{ + public const int DefaultBufferSize = 32 * 1024; + + private readonly ArrayPool _bytePool; + private readonly ArrayPool _charPool; + + public AbpMemoryPoolHttpResponseStreamWriterFactory(ArrayPool bytePool, ArrayPool 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); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/AbpAspNetCoreConsts.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/AbpAspNetCoreConsts.cs index dff987b6b3..134889324e 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/AbpAspNetCoreConsts.cs +++ b/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"; } \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Controllers/ReplaceControllersAttribute.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Controllers/ReplaceControllersAttribute.cs new file mode 100644 index 0000000000..4a7e7d6e4f --- /dev/null +++ b/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; + } +} diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/AbpSecurityHeaderNonceHelper.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/AbpSecurityHeaderNonceHelper.cs new file mode 100644 index 0000000000..64db12f3a7 --- /dev/null +++ b/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}\""); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/AbpSecurityHeadersMiddleware.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/AbpSecurityHeadersMiddleware.cs index 29e05b9bdc..c9b0738619 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/AbpSecurityHeadersMiddleware.cs +++ b/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 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 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 ,