diff --git a/Directory.Packages.props b/Directory.Packages.props index 67c100c20b..a6d8cfdc4f 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -140,6 +140,9 @@ + + + diff --git a/docs/en/Community-Articles/2023-10-23-NET-8-Feature-containers/POST.md b/docs/en/Community-Articles/2023-10-23-NET-8-Feature-containers/POST.md index dc0f96e726..73bea079a1 100644 --- a/docs/en/Community-Articles/2023-10-23-NET-8-Feature-containers/POST.md +++ b/docs/en/Community-Articles/2023-10-23-NET-8-Feature-containers/POST.md @@ -17,8 +17,8 @@ It's important to note that this change only affects the default port used when If you want your application to continue using port 80, you can still specify it during the application launch or configure it in the application settings. -* Recommended: Explicitly set the `ASPNETCORE_HTTP_PORTS`, `ASPNETCORE_HTTPS_PORTS``, and `ASPNETCORE_URLS` environment variables to the desired port. Example: `docker run --rm -it -p 9999:80 -e ASPNETCORE_HTTP_PORTS=80 `` -* Update existing commands and configuration that rely on the expected default port of port 80 to reference port 8080 instead. Example: `docker run --rm -it -p 9999:8080 `` +* Recommended: Explicitly set the `ASPNETCORE_HTTP_PORTS`, `ASPNETCORE_HTTPS_PORTS`, and `ASPNETCORE_URLS` environment variables to the desired port. Example: `docker run --rm -it -p 9999:80 -e ASPNETCORE_HTTP_PORTS=80 ` +* Update existing commands and configuration that rely on the expected default port of port 80 to reference port 8080 instead. Example: `docker run --rm -it -p 9999:8080 ` > The `dockerfile` of ABP templates has been updated to use port `80`. diff --git a/docs/en/Community-Articles/2023-11-05-EF-Core-8-Complex-Types/POST.MD b/docs/en/Community-Articles/2023-11-05-EF-Core-8-Complex-Types/POST.MD index 06289387e5..f11dec060d 100644 --- a/docs/en/Community-Articles/2023-11-05-EF-Core-8-Complex-Types/POST.MD +++ b/docs/en/Community-Articles/2023-11-05-EF-Core-8-Complex-Types/POST.MD @@ -105,7 +105,7 @@ And update the database: dotnet ef database update ```` -If you check the fields of the `Customers` table in your dayabase, you will see the following fields: +If you check the fields of the `Customers` table in your database, you will see the following fields: * `Id` * `Name` @@ -180,7 +180,7 @@ For more details and examples, see the Microsoft's document in the *References* You can find the sample project here: -https://github.com/hikalkan/samples/tree/master/EfCoreComplexTypeDemo +[https://github.com/hikalkan/samples/tree/master/EfCoreComplexTypeDemo](https://github.com/hikalkan/samples/tree/master/EfCoreComplexTypeDemo) ## References diff --git a/docs/en/Community-Articles/2023-11-06-Blazor-Fullstack-Web-Ui/Post.md b/docs/en/Community-Articles/2023-11-06-Blazor-Fullstack-Web-Ui/Post.md index a346b6f084..c3203d481d 100644 --- a/docs/en/Community-Articles/2023-11-06-Blazor-Fullstack-Web-Ui/Post.md +++ b/docs/en/Community-Articles/2023-11-06-Blazor-Fullstack-Web-Ui/Post.md @@ -1,6 +1,9 @@ # Blazor's History and Full-stack Web UI -Blazor is a web framework that allows developers to build interactive web applications using .NET instead of JavaScript. The first version of Blazor was **released on May 14, 2020**. Since its initial release, Blazor has evolved with the new versions. Until now, six different versions have been declared. Sometimes, it can be not very clear to see the differences between these approaches. First, let's try to understand these. +![Cover Image](cover-image.png) + + +Blazor is a web framework that allows developers to build interactive web applications using .NET instead of JavaScript. The first version of Blazor was released on May 14, 2020. Since its initial release, Blazor has evolved with the new versions. Until now, six different versions have been declared. Sometimes, it can be not very clear to see the differences between these approaches. First, let's try to understand these. * **Blazor-Server**: >> *Loads fast at first* >> In this version, heavy things are done in the server. Browsers are thin clients and download a small page for the first load. The page updates are done via SignalR connection. This was released with .NET Core 3. * **Blazor WebAssembly (WASM):** >> *Loads slow at first* >> In this version, some binary files are being downloaded to the browser. This approach takes longer initialization time than the "Server" approach. The hard work is done on the browser. @@ -40,7 +43,7 @@ You can switch between two rendering modes and even mix them on the same page. W - ## How it works? +## How it works? ### Rendering on Server @@ -50,25 +53,27 @@ You can add `WebComponentRenderMode.Server` to your Blazor components so that th -And sure you can add `WebComponentRenderMode.Server` to your page level, and the complete page will be rendered as a server component. All inputs on this page can work as an interactive server component like SPA mode. +And sure, you can add `WebComponentRenderMode.Server` to your page level, and the complete page will be rendered as a server component. All inputs on this page can work as an interactive server component like SPA mode. ![image-20231106172638604](image-20231106172638604.png) -### Rendering on client +### Rendering on Client You can switch to WebAssembly mode by writing `WebComponentRenderMode.WebAssembly` attribute to your page. By doing so, the whole page should run interactively using WebAssembly. This time there's no server connection anymore because it loads the binaries (WebAssembly runtimes) at the page load. ![image-20231106173021958](image-20231106173021958.png) -## How it works? + +## Enabling the Blazor Fullstack UI? To enable Blazor Full-stack Web UI, you need to write `net8.0;net7.0-browser` into the `TargetFrameworks` area of your `csproj` file. These two keywords change your app like this; `net8.0` framework renders on the server, and `net7.0-browser` framework renders on the browser. ![image-20231106173411309](image-20231106173411309.png) -## Let the System decide on WebAssembly or Server approach + +## Let the System Decide WebAssembly or Server Approach You can let the system decide whether it uses `WebAssembly` or `Server`. This can be done with the `Auto` mode of the `WebComponentRenderMode`. In this case, it will not load binary files (WebAssembly files) for the initial page that has `WebComponentRenderMode.Server` attribute, but whenever the user navigates to a page that has `WebComponentRenderMode.WebAssembly`, it will download the runtimes. This will allow us to load the initial page very fast, and when we need interactivity, we can switch to `WebAssembly` and wait for the binaries to download. But this download will be done one time because it will be cached. @@ -80,10 +85,10 @@ You can let the system decide whether it uses `WebAssembly` or `Server`. This ca I summarized the new generation Blazor in a very simple way. This architecture will be useful to everyone who uses Blazor. - +--- *Resources:* -* You can check out Dan Roth's GitHub issue 👉 [github.com/dotnet/aspnetcore/issues/46636](https://github.com/dotnet/aspnetcore/issues/46636). +* You can check Dan Roth's GitHub issue 👉 [github.com/dotnet/aspnetcore/issues/46636](https://github.com/dotnet/aspnetcore/issues/46636). * Steven Sanderson's YouTube video is very good for understanding these concepts 👉 [Blazor United Prototype Video](https://youtu.be/48G_CEGXZZM). diff --git a/docs/en/Community-Articles/2023-11-06-Blazor-Fullstack-Web-Ui/cover-image.png b/docs/en/Community-Articles/2023-11-06-Blazor-Fullstack-Web-Ui/cover-image.png new file mode 100644 index 0000000000..b3f1d6920e Binary files /dev/null and b/docs/en/Community-Articles/2023-11-06-Blazor-Fullstack-Web-Ui/cover-image.png differ diff --git a/docs/en/Community-Articles/2023-11-06-EF-Core_Hierarchy-Id/POST.md b/docs/en/Community-Articles/2023-11-06-EF-Core_Hierarchy-Id/POST.md index 9255c39a5a..a3c143a9c8 100644 --- a/docs/en/Community-Articles/2023-11-06-EF-Core_Hierarchy-Id/POST.md +++ b/docs/en/Community-Articles/2023-11-06-EF-Core_Hierarchy-Id/POST.md @@ -76,4 +76,4 @@ await context.SaveChangesAsync(); For more information about hierarchy id, see the following resource: -https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew#hierarchyid-in-net-and-ef-core +- [https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew#hierarchyid-in-net-and-ef-core](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew#hierarchyid-in-net-and-ef-core) diff --git a/docs/en/Community-Articles/2023-11-97-AOT-Compilation/Post.md b/docs/en/Community-Articles/2023-11-97-AOT-Compilation/Post.md index 2b994b59c3..b39a2af339 100644 --- a/docs/en/Community-Articles/2023-11-97-AOT-Compilation/Post.md +++ b/docs/en/Community-Articles/2023-11-97-AOT-Compilation/Post.md @@ -39,7 +39,7 @@ I have created a simple console application to test the Native AOT Compilation. | | Size | Speed | -| --- | --- | --- | +| --- | --- | --- | | .NET 8
_(Self-Contained, Single File)_ | 65938 kb | 00.0051806 ~5ms | | .NET 7 AOT (default) | 4452 kb | 00.0029823 ~2ms | | .NET 8 AOT (default) | 1242 kb | 00.0028638 ~2ms | @@ -65,11 +65,8 @@ Always consider the specific needs and constraints of your project before decidi Native AOT Compilation is a great feature that improves the performance of .NET applications. It's still in early-stages and not all libraries support it yet. But it's a great beginning for the future of .NET 🚀 - ## Links - Native AOT deployment overview - .NET | Microsoft Learn. https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/. -- - Optimize AOT deployments https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/optimizing -- - What's new in .NET 8 | Microsoft Learn. https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8. diff --git a/framework/Volo.Abp.sln b/framework/Volo.Abp.sln index cd003b7f07..42339a60ac 100644 --- a/framework/Volo.Abp.sln +++ b/framework/Volo.Abp.sln @@ -459,6 +459,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Imaging.AspNetCore EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Maui.Client", "src\Volo.Abp.Maui.Client\Volo.Abp.Maui.Client.csproj", "{F19A6E0C-F719-4ED9-A024-14E4B8D40883}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Imaging.SkiaSharp", "src\Volo.Abp.Imaging.SkiaSharp\Volo.Abp.Imaging.SkiaSharp.csproj", "{198683D0-7DC6-40F2-B81B-8E446E70A9DE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Imaging.SkiaSharp.Tests", "test\Volo.Abp.Imaging.SkiaSharp.Tests\Volo.Abp.Imaging.SkiaSharp.Tests.csproj", "{DFAF8763-D1D6-4EB4-B459-20E31007FE2F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1369,6 +1373,14 @@ Global {F19A6E0C-F719-4ED9-A024-14E4B8D40883}.Debug|Any CPU.Build.0 = Debug|Any CPU {F19A6E0C-F719-4ED9-A024-14E4B8D40883}.Release|Any CPU.ActiveCfg = Release|Any CPU {F19A6E0C-F719-4ED9-A024-14E4B8D40883}.Release|Any CPU.Build.0 = Release|Any CPU + {198683D0-7DC6-40F2-B81B-8E446E70A9DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {198683D0-7DC6-40F2-B81B-8E446E70A9DE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {198683D0-7DC6-40F2-B81B-8E446E70A9DE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {198683D0-7DC6-40F2-B81B-8E446E70A9DE}.Release|Any CPU.Build.0 = Release|Any CPU + {DFAF8763-D1D6-4EB4-B459-20E31007FE2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DFAF8763-D1D6-4EB4-B459-20E31007FE2F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DFAF8763-D1D6-4EB4-B459-20E31007FE2F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DFAF8763-D1D6-4EB4-B459-20E31007FE2F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1600,6 +1612,8 @@ Global {62B2B8C9-8F24-4D31-894F-C1F0728D32AB} = {447C8A77-E5F0-4538-8687-7383196D04EA} {983B0136-384B-4439-B374-31111FFAA286} = {447C8A77-E5F0-4538-8687-7383196D04EA} {F19A6E0C-F719-4ED9-A024-14E4B8D40883} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} + {198683D0-7DC6-40F2-B81B-8E446E70A9DE} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} + {DFAF8763-D1D6-4EB4-B459-20E31007FE2F} = {447C8A77-E5F0-4538-8687-7383196D04EA} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleFile.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleFile.cs index fbd6c3eaa1..53fceef714 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleFile.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleFile.cs @@ -2,11 +2,11 @@ using System; namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling; -public class BundleFile +public class BundleFile : IEquatable, IComparable { - public string FileName { get; set; } + public string FileName { get; } - public bool IsExternalFile { get; set; } + public bool IsExternalFile { get; } public BundleFile(string fileName) { @@ -28,4 +28,59 @@ public class BundleFile { return new BundleFile(fileName); } + + public bool Equals(BundleFile? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return FileName == other.FileName; + } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != this.GetType()) + { + return false; + } + + return Equals((BundleFile)obj); + } + + public override int GetHashCode() + { + return FileName.GetHashCode(); + } + + public int CompareTo(BundleFile? other) + { + if (ReferenceEquals(this, other)) + { + return 0; + } + + if (ReferenceEquals(null, other)) + { + return 1; + } + + return string.Compare(FileName, other.FileName, StringComparison.Ordinal); + } } diff --git a/framework/src/Volo.Abp.Imaging.SkiaSharp/FodyWeavers.xml b/framework/src/Volo.Abp.Imaging.SkiaSharp/FodyWeavers.xml new file mode 100644 index 0000000000..1715698ccd --- /dev/null +++ b/framework/src/Volo.Abp.Imaging.SkiaSharp/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.Imaging.SkiaSharp/FodyWeavers.xsd b/framework/src/Volo.Abp.Imaging.SkiaSharp/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/framework/src/Volo.Abp.Imaging.SkiaSharp/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.Imaging.SkiaSharp/Volo.Abp.Imaging.SkiaSharp.csproj b/framework/src/Volo.Abp.Imaging.SkiaSharp/Volo.Abp.Imaging.SkiaSharp.csproj new file mode 100644 index 0000000000..857cb0f92f --- /dev/null +++ b/framework/src/Volo.Abp.Imaging.SkiaSharp/Volo.Abp.Imaging.SkiaSharp.csproj @@ -0,0 +1,28 @@ + + + + + + + netstandard2.0;netstandard2.1;net8.0 + enable + Nullable + Volo.Abp.Imaging.SkiaSharp + $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + false + false + false + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.Imaging.SkiaSharp/Volo/Abp/Imaging/AbpImagingSkiaSharpModule.cs b/framework/src/Volo.Abp.Imaging.SkiaSharp/Volo/Abp/Imaging/AbpImagingSkiaSharpModule.cs new file mode 100644 index 0000000000..79fc4314aa --- /dev/null +++ b/framework/src/Volo.Abp.Imaging.SkiaSharp/Volo/Abp/Imaging/AbpImagingSkiaSharpModule.cs @@ -0,0 +1,8 @@ +using Volo.Abp.Modularity; + +namespace Volo.Abp.Imaging; + +[DependsOn(typeof(AbpImagingAbstractionsModule))] +public class AbpImagingSkiaSharpModule : AbpModule +{ +} diff --git a/framework/src/Volo.Abp.Imaging.SkiaSharp/Volo/Abp/Imaging/SkiaSharpImageResizerContributor.cs b/framework/src/Volo.Abp.Imaging.SkiaSharp/Volo/Abp/Imaging/SkiaSharpImageResizerContributor.cs new file mode 100644 index 0000000000..760acd0916 --- /dev/null +++ b/framework/src/Volo.Abp.Imaging.SkiaSharp/Volo/Abp/Imaging/SkiaSharpImageResizerContributor.cs @@ -0,0 +1,98 @@ +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using SkiaSharp; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Http; + +namespace Volo.Abp.Imaging; + +public class SkiaSharpImageResizerContributor : IImageResizerContributor, ITransientDependency +{ + protected SkiaSharpResizerOptions Options { get; } + + public SkiaSharpImageResizerContributor(IOptions options) + { + Options = options.Value; + } + + public virtual async Task> TryResizeAsync(byte[] bytes, ImageResizeArgs resizeArgs, string? mimeType = null, CancellationToken cancellationToken = default) + { + if (!mimeType.IsNullOrWhiteSpace() && !CanResize(mimeType)) + { + return new ImageResizeResult(bytes, ImageProcessState.Unsupported); + } + + using (var memoryStream = new MemoryStream(bytes)) + { + var result = await TryResizeAsync(memoryStream, resizeArgs, mimeType, cancellationToken); + + if (result.State != ImageProcessState.Done) + { + return new ImageResizeResult(bytes, result.State); + } + + var newBytes = await result.Result.GetAllBytesAsync(cancellationToken); + + result.Result.Dispose(); + + return new ImageResizeResult(newBytes, result.State); + } + } + + public virtual async Task> TryResizeAsync(Stream stream, ImageResizeArgs resizeArgs, string? mimeType = null, CancellationToken cancellationToken = default) + { + if (!mimeType.IsNullOrWhiteSpace() && !CanResize(mimeType)) + { + return new ImageResizeResult(stream, ImageProcessState.Unsupported); + } + + var (memoryBitmapStream, memorySkCodecStream) = await CreateMemoryStream(stream); + + using (var original = SKBitmap.Decode(memoryBitmapStream)) + { + using (var resized = original.Resize(new SKImageInfo(resizeArgs.Width, resizeArgs.Height), Options.SKFilterQuality)) + { + using (var image = SKImage.FromBitmap(resized)) + { + using (var codec = SKCodec.Create(memorySkCodecStream)) + { + var memoryStream = new MemoryStream(); + image.Encode(codec.EncodedFormat, Options.Quality).SaveTo(memoryStream); + return new ImageResizeResult(memoryStream, ImageProcessState.Done); + } + } + } + } + } + + protected virtual async Task<(MemoryStream, MemoryStream)> CreateMemoryStream(Stream stream) + { + var streamPosition = stream.Position; + + var memoryBitmapStream = new MemoryStream(); + var memorySkCodecStream = new MemoryStream(); + + await stream.CopyToAsync(memoryBitmapStream); + stream.Position = streamPosition; + await stream.CopyToAsync(memorySkCodecStream); + stream.Position = streamPosition; + + memoryBitmapStream.Position = 0; + memorySkCodecStream.Position = 0; + + return (memoryBitmapStream, memorySkCodecStream); + } + + protected virtual bool CanResize(string? mimeType) + { + return mimeType switch { + MimeTypes.Image.Jpeg => true, + MimeTypes.Image.Png => true, + MimeTypes.Image.Webp => true, + _ => false + }; + } +} diff --git a/framework/src/Volo.Abp.Imaging.SkiaSharp/Volo/Abp/Imaging/SkiaSharpResizerOptions.cs b/framework/src/Volo.Abp.Imaging.SkiaSharp/Volo/Abp/Imaging/SkiaSharpResizerOptions.cs new file mode 100644 index 0000000000..6bc220feb1 --- /dev/null +++ b/framework/src/Volo.Abp.Imaging.SkiaSharp/Volo/Abp/Imaging/SkiaSharpResizerOptions.cs @@ -0,0 +1,16 @@ +using SkiaSharp; + +namespace Volo.Abp.Imaging; + +public class SkiaSharpResizerOptions +{ + public SKFilterQuality SKFilterQuality { get; set; } + + public int Quality { get; set; } + + public SkiaSharpResizerOptions() + { + SKFilterQuality = SKFilterQuality.None; + Quality = 75; + } +} diff --git a/framework/test/Volo.Abp.Imaging.SkiaSharp.Tests/Volo.Abp.Imaging.SkiaSharp.Tests.csproj b/framework/test/Volo.Abp.Imaging.SkiaSharp.Tests/Volo.Abp.Imaging.SkiaSharp.Tests.csproj new file mode 100644 index 0000000000..3272fc325a --- /dev/null +++ b/framework/test/Volo.Abp.Imaging.SkiaSharp.Tests/Volo.Abp.Imaging.SkiaSharp.Tests.csproj @@ -0,0 +1,19 @@ + + + + + + net8.0 + + + + + + + + + + + + + diff --git a/framework/test/Volo.Abp.Imaging.SkiaSharp.Tests/Volo/Abp/Imaging/AbpImagingMagickNetTestModule.cs b/framework/test/Volo.Abp.Imaging.SkiaSharp.Tests/Volo/Abp/Imaging/AbpImagingMagickNetTestModule.cs new file mode 100644 index 0000000000..7fce00d3ac --- /dev/null +++ b/framework/test/Volo.Abp.Imaging.SkiaSharp.Tests/Volo/Abp/Imaging/AbpImagingMagickNetTestModule.cs @@ -0,0 +1,14 @@ +using Volo.Abp.Autofac; +using Volo.Abp.Modularity; + +namespace Volo.Abp.Imaging; + +[DependsOn( + typeof(AbpAutofacModule), + typeof(AbpImagingSkiaSharpModule), + typeof(AbpTestBaseModule) +)] +public class AbpImagingSkiaSharpTestModule : AbpModule +{ + +} diff --git a/framework/test/Volo.Abp.Imaging.SkiaSharp.Tests/Volo/Abp/Imaging/AbpImagingSkiaSharpTestModule.cs b/framework/test/Volo.Abp.Imaging.SkiaSharp.Tests/Volo/Abp/Imaging/AbpImagingSkiaSharpTestModule.cs new file mode 100644 index 0000000000..953e8c4b3a --- /dev/null +++ b/framework/test/Volo.Abp.Imaging.SkiaSharp.Tests/Volo/Abp/Imaging/AbpImagingSkiaSharpTestModule.cs @@ -0,0 +1,11 @@ +using Volo.Abp.Testing; + +namespace Volo.Abp.Imaging; + +public abstract class AbpImagingSkiaSharpTestBase : AbpIntegratedTest +{ + protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) + { + options.UseAutofac(); + } +} diff --git a/framework/test/Volo.Abp.Imaging.SkiaSharp.Tests/Volo/Abp/Imaging/SkiaSharpImageResizerTests.cs b/framework/test/Volo.Abp.Imaging.SkiaSharp.Tests/Volo/Abp/Imaging/SkiaSharpImageResizerTests.cs new file mode 100644 index 0000000000..460c83bf5b --- /dev/null +++ b/framework/test/Volo.Abp.Imaging.SkiaSharp.Tests/Volo/Abp/Imaging/SkiaSharpImageResizerTests.cs @@ -0,0 +1,75 @@ +using System.IO; +using System.Threading.Tasks; +using Shouldly; +using Xunit; + +namespace Volo.Abp.Imaging; + +public class SkiaSharpImageResizerTests : AbpImagingSkiaSharpTestBase +{ + public IImageResizer ImageResizer { get; } + + public SkiaSharpImageResizerTests() + { + ImageResizer = GetRequiredService(); + } + + [Fact] + public async Task Should_Resize_Jpg() + { + await using var jpegImage = ImageFileHelper.GetJpgTestFileStream(); + var resizedImage = await ImageResizer.ResizeAsync(jpegImage, new ImageResizeArgs(100, 100)); + + resizedImage.ShouldNotBeNull(); + resizedImage.State.ShouldBe(ImageProcessState.Done); + resizedImage.Result.Length.ShouldBeLessThan(jpegImage.Length); + + resizedImage.Result.Dispose(); + } + + [Fact] + public async Task Should_Resize_Png() + { + await using var pngImage = ImageFileHelper.GetPngTestFileStream(); + var resizedImage = await ImageResizer.ResizeAsync(pngImage, new ImageResizeArgs(100, 100)); + + resizedImage.ShouldNotBeNull(); + resizedImage.State.ShouldBe(ImageProcessState.Done); + resizedImage.Result.Length.ShouldBeLessThan(pngImage.Length); + + resizedImage.Result.Dispose(); + } + + [Fact] + public async Task Should_Resize_Webp() + { + await using var webpImage = ImageFileHelper.GetWebpTestFileStream(); + var resizedImage = await ImageResizer.ResizeAsync(webpImage, new ImageResizeArgs(100, 100)); + + resizedImage.ShouldNotBeNull(); + resizedImage.State.ShouldBe(ImageProcessState.Done); + resizedImage.Result.Length.ShouldBeLessThan(webpImage.Length); + + resizedImage.Result.Dispose(); + } + + [Fact] + public async Task Should_Resize_Stream_And_Byte_Array_The_Same() + { + await using var jpegImage = ImageFileHelper.GetJpgTestFileStream(); + var resizedImage1 = await ImageResizer.ResizeAsync(jpegImage, new ImageResizeArgs(100, 100)); + var resizedImage2 = await ImageResizer.ResizeAsync(await jpegImage.GetAllBytesAsync(), new ImageResizeArgs(100, 100)); + + resizedImage1.ShouldNotBeNull(); + resizedImage1.State.ShouldBe(ImageProcessState.Done); + resizedImage1.Result.Length.ShouldBeLessThan(jpegImage.Length); + + resizedImage2.ShouldNotBeNull(); + resizedImage2.State.ShouldBe(ImageProcessState.Done); + resizedImage2.Result.LongLength.ShouldBeLessThan(jpegImage.Length); + + resizedImage1.Result.Length.ShouldBe(resizedImage2.Result.LongLength); + + resizedImage1.Result.Dispose(); + } +} diff --git a/npm/ng-packs/.gitignore b/npm/ng-packs/.gitignore index f15a862742..9d756a8b67 100644 --- a/npm/ng-packs/.gitignore +++ b/npm/ng-packs/.gitignore @@ -57,4 +57,4 @@ Thumbs.db .angular -.nx/cache \ No newline at end of file +.nx/ diff --git a/npm/ng-packs/packages/components/extensible/src/lib/components/date-time-picker/extensible-date-time-picker.component.ts b/npm/ng-packs/packages/components/extensible/src/lib/components/date-time-picker/extensible-date-time-picker.component.ts index 55b73581c9..e6f7881ca2 100644 --- a/npm/ng-packs/packages/components/extensible/src/lib/components/date-time-picker/extensible-date-time-picker.component.ts +++ b/npm/ng-packs/packages/components/extensible/src/lib/components/date-time-picker/extensible-date-time-picker.component.ts @@ -7,19 +7,24 @@ import { SkipSelf, ViewChild, } from '@angular/core'; -import { ControlContainer } from '@angular/forms'; +import { ControlContainer, ReactiveFormsModule } from '@angular/forms'; import { NgbDateAdapter, + NgbDatepickerModule, NgbInputDatepicker, NgbTimeAdapter, NgbTimepicker, + NgbTimepickerModule, } from '@ng-bootstrap/ng-bootstrap'; import { FormProp } from '../../models/form-props'; import { selfFactory } from '../../utils/factory.util'; import { DateTimeAdapter } from '@abp/ng.theme.shared'; +import { CommonModule } from '@angular/common'; @Component({ exportAs: 'abpExtensibleDateTimePicker', + standalone: true, + imports: [CommonModule, NgbDatepickerModule, ReactiveFormsModule, NgbTimepickerModule], selector: 'abp-extensible-date-time-picker', template: ` - + diff --git a/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-form/extensible-form-prop.component.ts b/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-form/extensible-form-prop.component.ts index 2689e09a33..ca32fd3e7a 100644 --- a/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-form/extensible-form-prop.component.ts +++ b/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-form/extensible-form-prop.component.ts @@ -1,11 +1,12 @@ import { EXTENSIONS_FORM_PROP, EXTENSIONS_FORM_PROP_DATA } from './../../tokens/extensions.token'; -import { ABP, AbpValidators, ConfigStateService, TrackByService } from '@abp/ng.core'; +import { ABP, CoreModule, TrackByService } from '@abp/ng.core'; import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, + inject, Injector, Input, OnChanges, @@ -17,26 +18,53 @@ import { import { ControlContainer, FormGroupDirective, - UntypedFormGroup, + ReactiveFormsModule, ValidatorFn, - Validators, } from '@angular/forms'; -import { NgbDateAdapter, NgbTimeAdapter } from '@ng-bootstrap/ng-bootstrap'; +import { + NgbDateAdapter, + NgbDatepickerModule, + NgbTimeAdapter, + NgbTimepickerModule, + NgbTypeaheadModule, +} from '@ng-bootstrap/ng-bootstrap'; import { Observable, of } from 'rxjs'; import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators'; -import { DateAdapter, TimeAdapter } from '@abp/ng.theme.shared'; +import { + DateAdapter, + DisabledDirective, + PasswordComponent, + TimeAdapter, +} from '@abp/ng.theme.shared'; import { EXTRA_PROPERTIES_KEY } from '../../constants/extra-properties'; -import { ePropType } from '../../enums/props.enum'; import { FormProp } from '../../models/form-props'; import { PropData } from '../../models/props'; import { selfFactory } from '../../utils/factory.util'; import { addTypeaheadTextSuffix } from '../../utils/typeahead.util'; -import { eThemeSharedComponents } from "../../enums/components"; +import { eThemeSharedComponents } from '../../enums/components'; +import { ExtensibleDateTimePickerComponent } from '../date-time-picker/extensible-date-time-picker.component'; +import { NgxValidateCoreModule } from '@ngx-validate/core'; +import { ExtensibleFormPropService } from '../../services/extensible-form-prop.service'; +import {CreateInjectorPipe} from "../../pipes/create-injector.pipe"; @Component({ selector: 'abp-extensible-form-prop', templateUrl: './extensible-form-prop.component.html', + standalone: true, + imports: [ + CoreModule, + ExtensibleDateTimePickerComponent, + NgbDatepickerModule, + NgbTimepickerModule, + ReactiveFormsModule, + DisabledDirective, + NgxValidateCoreModule, + NgbTypeaheadModule, + PasswordComponent, + CreateInjectorPipe + ], changeDetection: ChangeDetectionStrategy.OnPush, + providers: [ExtensibleFormPropService], viewProviders: [ { provide: ControlContainer, @@ -48,32 +76,27 @@ import { eThemeSharedComponents } from "../../enums/components"; ], }) export class ExtensibleFormPropComponent implements OnChanges, AfterViewInit { - @Input() data!: PropData; + protected service = inject(ExtensibleFormPropService); + public readonly cdRef = inject(ChangeDetectorRef); + public readonly track = inject(TrackByService); + #groupDirective = inject(FormGroupDirective); + private injector = inject(Injector); + private readonly form = this.#groupDirective.form; + @Input() data!: PropData; @Input() prop!: FormProp; - @Input() first?: boolean; - @ViewChild('field') private fieldRef!: ElementRef; - public injectorForCustomComponent?: Injector; - + injectorForCustomComponent?: Injector; asterisk = ''; - containerClassName = 'mb-2'; - options$: Observable[]> = of([]); - validators: ValidatorFn[] = []; - readonly!: boolean; - typeaheadModel: any; - passwordKey = eThemeSharedComponents.PasswordComponent; - private readonly form: UntypedFormGroup; - disabledFn = (data: PropData) => false; get disabled() { @@ -100,27 +123,13 @@ export class ExtensibleFormPropComponent implements OnChanges, AfterViewInit { typeaheadFormatter = (option: ABP.Option) => option.key; - get meridian() { - return ( - this.configState.getDeep('localization.currentCulture.dateTimeFormat.shortTimePattern') || '' - ).includes('tt'); - } + meridian$ = this.service.meridian$; get isInvalid() { const control = this.form.get(this.prop.name); return control?.touched && control.invalid; } - constructor( - public readonly cdRef: ChangeDetectorRef, - public readonly track: TrackByService, - protected configState: ConfigStateService, - groupDirective: FormGroupDirective, - private injector: Injector, - ) { - this.form = groupDirective.form; - } - private getTypeaheadControls() { const { name } = this.prop; const extraPropName = `${EXTRA_PROPERTIES_KEY}.${name}`; @@ -132,7 +141,7 @@ export class ExtensibleFormPropComponent implements OnChanges, AfterViewInit { } private setAsterisk() { - this.asterisk = this.validators.some(isRequired) ? '*' : ''; + this.asterisk = this.service.calcAsterisks(this.validators); } ngAfterViewInit() { @@ -143,51 +152,11 @@ export class ExtensibleFormPropComponent implements OnChanges, AfterViewInit { } getComponent(prop: FormProp): string { - if (prop.template) { - return 'template'; - } - switch (prop.type) { - case ePropType.Boolean: - return 'checkbox'; - case ePropType.Date: - return 'date'; - case ePropType.DateTime: - return 'dateTime'; - case ePropType.Hidden: - return 'hidden'; - case ePropType.MultiSelect: - return 'multiselect'; - case ePropType.Text: - return 'textarea'; - case ePropType.Time: - return 'time'; - case ePropType.Typeahead: - return 'typeahead'; - case ePropType.PasswordInputGroup: - return 'passwordinputgroup'; - default: - return prop.options ? 'select' : 'input'; - } + return this.service.getComponent(prop); } getType(prop: FormProp): string { - switch (prop.type) { - case ePropType.Date: - case ePropType.String: - return 'text'; - case ePropType.Boolean: - return 'checkbox'; - case ePropType.Number: - return 'number'; - case ePropType.Email: - return 'email'; - case ePropType.Password: - return 'password'; - case ePropType.PasswordInputGroup: - return 'passwordinputgroup'; - default: - return 'hidden'; - } + return this.service.getType(prop); } ngOnChanges({ prop, data }: SimpleChanges) { @@ -229,11 +198,3 @@ export class ExtensibleFormPropComponent implements OnChanges, AfterViewInit { this.typeaheadModel = { key: keyControl.value, value: valueControl.value }; } } - -function isRequired(validator: ValidatorFn) { - return ( - validator === Validators.required || - validator === AbpValidators.required || - validator.name === 'required' - ); -} diff --git a/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-form/extensible-form.component.ts b/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-form/extensible-form.component.ts index bd6f23591f..c7436d3374 100644 --- a/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-form/extensible-form.component.ts +++ b/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-form/extensible-form.component.ts @@ -1,73 +1,76 @@ -import { TrackByService } from '@abp/ng.core'; +import {TrackByService} from '@abp/ng.core'; import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - Inject, - Input, - Optional, - QueryList, - SkipSelf, - ViewChildren, + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, inject, + Input, + Optional, + QueryList, + SkipSelf, + ViewChildren, } from '@angular/core'; -import { ControlContainer, UntypedFormGroup } from '@angular/forms'; -import { EXTRA_PROPERTIES_KEY } from '../../constants/extra-properties'; -import { FormPropList, GroupedFormPropList } from '../../models/form-props'; -import { ExtensionsService } from '../../services/extensions.service'; -import { EXTENSIONS_IDENTIFIER } from '../../tokens/extensions.token'; -import { selfFactory } from '../../utils/factory.util'; -import { ExtensibleFormPropComponent } from './extensible-form-prop.component'; +import {ControlContainer, ReactiveFormsModule, UntypedFormGroup} from '@angular/forms'; +import {EXTRA_PROPERTIES_KEY} from '../../constants/extra-properties'; +import {FormPropList, GroupedFormPropList} from '../../models/form-props'; +import {ExtensionsService} from '../../services/extensions.service'; +import {EXTENSIONS_IDENTIFIER} from '../../tokens/extensions.token'; +import {selfFactory} from '../../utils/factory.util'; +import {ExtensibleFormPropComponent} from './extensible-form-prop.component'; +import {CommonModule} from "@angular/common"; +import {PropDataDirective} from "../../directives/prop-data.directive"; @Component({ - exportAs: 'abpExtensibleForm', - selector: 'abp-extensible-form', - templateUrl: './extensible-form.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, - viewProviders: [ - { - provide: ControlContainer, - useFactory: selfFactory, - deps: [[new Optional(), new SkipSelf(), ControlContainer]], - }, - ], + exportAs: 'abpExtensibleForm', + selector: 'abp-extensible-form', + templateUrl: './extensible-form.component.html', + standalone:true, + imports:[CommonModule, PropDataDirective,ReactiveFormsModule,ExtensibleFormPropComponent], + changeDetection: ChangeDetectionStrategy.OnPush, + viewProviders: [ + { + provide: ControlContainer, + useFactory: selfFactory, + deps: [[new Optional(), new SkipSelf(), ControlContainer]], + }, + ], }) export class ExtensibleFormComponent { - @ViewChildren(ExtensibleFormPropComponent) - formProps!: QueryList; - @Input() - set selectedRecord(record: R) { - const type = !record || JSON.stringify(record) === '{}' ? 'create' : 'edit'; - const propList = this.extensions[`${type}FormProps`].get(this.identifier).props; - this.groupedPropList = this.createGroupedList(propList); - this.record = record; - } + @ViewChildren(ExtensibleFormPropComponent) + formProps!: QueryList; - extraPropertiesKey = EXTRA_PROPERTIES_KEY; - groupedPropList!: GroupedFormPropList; - record!: R; + @Input() + set selectedRecord(record: R) { + const type = !record || JSON.stringify(record) === '{}' ? 'create' : 'edit'; + const propList = this.extensions[`${type}FormProps`].get(this.identifier).props; + this.groupedPropList = this.createGroupedList(propList); + this.record = record; + } - createGroupedList(propList: FormPropList) { - const groupedFormPropList = new GroupedFormPropList(); - propList.forEach(item => { - groupedFormPropList.addItem(item.value); - }); - return groupedFormPropList; - } + extraPropertiesKey = EXTRA_PROPERTIES_KEY; + groupedPropList!: GroupedFormPropList; + record!: R; - get form(): UntypedFormGroup { - return (this.container ? this.container.control : { controls: {} }) as UntypedFormGroup; - } + public readonly cdRef = inject(ChangeDetectorRef) + public readonly track = inject(TrackByService) + private container = inject(ControlContainer) + private extensions = inject(ExtensionsService); + private identifier = inject(EXTENSIONS_IDENTIFIER) - get extraProperties(): UntypedFormGroup { - return (this.form.controls.extraProperties || { controls: {} }) as UntypedFormGroup; - } + createGroupedList(propList: FormPropList) { + const groupedFormPropList = new GroupedFormPropList(); + propList.forEach(item => { + groupedFormPropList.addItem(item.value); + }); + return groupedFormPropList; + } + + get form(): UntypedFormGroup { + return (this.container ? this.container.control : {controls: {}}) as UntypedFormGroup; + } + + get extraProperties(): UntypedFormGroup { + return (this.form.controls.extraProperties || {controls: {}}) as UntypedFormGroup; + } - constructor( - public readonly cdRef: ChangeDetectorRef, - public readonly track: TrackByService, - private container: ControlContainer, - private extensions: ExtensionsService, - @Inject(EXTENSIONS_IDENTIFIER) private identifier: string, - ) {} } diff --git a/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table.component.ts b/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table.component.ts index af146e2367..697f8d36bd 100644 --- a/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table.component.ts +++ b/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table.component.ts @@ -1,17 +1,19 @@ import { ABP, ConfigStateService, + CoreModule, getShortDateFormat, getShortDateShortTimeFormat, getShortTimeFormat, ListService, PermissionService, } from '@abp/ng.core'; -import { formatDate } from '@angular/common'; +import { CommonModule, formatDate } from '@angular/common'; import { ChangeDetectionStrategy, Component, EventEmitter, + inject, Inject, Injector, Input, @@ -31,16 +33,28 @@ import { PropData } from '../../models/props'; import { ExtensionsService } from '../../services/extensions.service'; import { ENTITY_PROP_TYPE_CLASSES, - EntityPropTypeClass, EXTENSIONS_IDENTIFIER, PROP_DATA_STREAM, } from '../../tokens/extensions.token'; +import { NgxDatatableModule } from '@swimlane/ngx-datatable'; +import { GridActionsComponent } from '../grid-actions/grid-actions.component'; +import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap'; +import { NgxDatatableDefaultDirective, NgxDatatableListDirective } from '@abp/ng.theme.shared'; const DEFAULT_ACTIONS_COLUMN_WIDTH = 150; - @Component({ +@Component({ exportAs: 'abpExtensibleTable', selector: 'abp-extensible-table', + standalone: true, + imports: [ + CoreModule, + NgxDatatableModule, + GridActionsComponent, + NgbTooltip, + NgxDatatableDefaultDirective, + NgxDatatableListDirective, + ], templateUrl: './extensible-table.component.html', changeDetection: ChangeDetectionStrategy.OnPush, }) @@ -50,6 +64,7 @@ export class ExtensibleTableComponent implements OnChanges { set actionsText(value: string) { this._actionsText = value; } + get actionsText(): string { return this._actionsText ?? (this.actionList.length > 1 ? 'AbpUi::Actions' : ''); } @@ -57,19 +72,17 @@ export class ExtensibleTableComponent implements OnChanges { @Input() data!: R[]; @Input() list!: ListService; @Input() recordsTotal!: number; + @Input() set actionsColumnWidth(width: number) { this.setColumnWidths(width ? Number(width) : undefined); } + @Input() actionsTemplate?: TemplateRef; @Output() tableActivate = new EventEmitter(); - getInjected: typeof this.injector.get - hasAtLeastOnePermittedAction: boolean; - entityPropTypeClasses: EntityPropTypeClass; - readonly columnWidths!: number[]; readonly propList: EntityPropList; @@ -78,20 +91,20 @@ export class ExtensibleTableComponent implements OnChanges { readonly trackByFn: TrackByFunction> = (_, item) => item.name; - constructor( - @Inject(LOCALE_ID) private locale: string, - private config: ConfigStateService, - private injector: Injector, - ) { - this.entityPropTypeClasses = injector.get(ENTITY_PROP_TYPE_CLASSES); - this.getInjected = injector.get.bind(injector); - const extensions = injector.get(ExtensionsService); - const name = injector.get(EXTENSIONS_IDENTIFIER); + locale = inject(LOCALE_ID); + private config = inject(ConfigStateService); + entityPropTypeClasses = inject(ENTITY_PROP_TYPE_CLASSES); + #injector = inject(Injector); + getInjected = this.#injector.get.bind(this.#injector); + + constructor() { + const extensions = this.#injector.get(ExtensionsService); + const name = this.#injector.get(EXTENSIONS_IDENTIFIER); this.propList = extensions.entityProps.get(name).props; this.actionList = extensions['entityActions'].get(name) .actions as unknown as EntityActionList; - const permissionService = injector.get(PermissionService); + const permissionService = this.#injector.get(PermissionService); this.hasAtLeastOnePermittedAction = permissionService.filterItemsByPolicy( this.actionList.toArray().map(action => ({ requiredPolicy: action.permission })), @@ -149,7 +162,7 @@ export class ExtensibleTableComponent implements OnChanges { if (!data?.currentValue) return; if (data.currentValue.length < 1) { - this.list.totalCount = this.recordsTotal + this.list.totalCount = this.recordsTotal; } this.data = data.currentValue.map((record: any, index: number) => { @@ -170,7 +183,7 @@ export class ExtensibleTableComponent implements OnChanges { useValue: value, }, ], - parent: this.injector, + parent: this.#injector, }); record[propKey].component = prop.value.component; } diff --git a/npm/ng-packs/packages/components/extensible/src/lib/components/grid-actions/grid-actions.component.ts b/npm/ng-packs/packages/components/extensible/src/lib/components/grid-actions/grid-actions.component.ts index 22b5419b55..ebccd6e163 100644 --- a/npm/ng-packs/packages/components/extensible/src/lib/components/grid-actions/grid-actions.component.ts +++ b/npm/ng-packs/packages/components/extensible/src/lib/components/grid-actions/grid-actions.component.ts @@ -8,9 +8,14 @@ import { import { EntityAction, EntityActionList } from '../../models/entity-actions'; import { EXTENSIONS_ACTION_TYPE } from '../../tokens/extensions.token'; import { AbstractActionsComponent } from '../abstract-actions/abstract-actions.component'; +import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; +import { CoreModule } from '@abp/ng.core'; +import {EllipsisDirective} from "@abp/ng.theme.shared"; @Component({ exportAs: 'abpGridActions', + standalone: true, + imports: [ CoreModule, NgbDropdownModule, EllipsisDirective], selector: 'abp-grid-actions', templateUrl: './grid-actions.component.html', providers: [ diff --git a/npm/ng-packs/packages/components/extensible/src/lib/components/page-toolbar/page-toolbar.component.ts b/npm/ng-packs/packages/components/extensible/src/lib/components/page-toolbar/page-toolbar.component.ts index afe421554b..85685cdee6 100644 --- a/npm/ng-packs/packages/components/extensible/src/lib/components/page-toolbar/page-toolbar.component.ts +++ b/npm/ng-packs/packages/components/extensible/src/lib/components/page-toolbar/page-toolbar.component.ts @@ -8,10 +8,14 @@ import { } from '../../models/toolbar-actions'; import { EXTENSIONS_ACTION_TYPE } from '../../tokens/extensions.token'; import { AbstractActionsComponent } from '../abstract-actions/abstract-actions.component'; +import { CreateInjectorPipe } from '../../pipes/create-injector.pipe'; +import { CoreModule } from '@abp/ng.core'; @Component({ exportAs: 'abpPageToolbar', selector: 'abp-page-toolbar', + standalone: true, + imports: [ CoreModule, CreateInjectorPipe], templateUrl: './page-toolbar.component.html', providers: [ { @@ -31,6 +35,7 @@ export class PageToolbarComponent readonly trackByFn: TrackByFunction> = (_, item) => item.action || item.component; + constructor(public readonly injector: Injector) { super(injector); } diff --git a/npm/ng-packs/packages/components/extensible/src/lib/directives/prop-data.directive.ts b/npm/ng-packs/packages/components/extensible/src/lib/directives/prop-data.directive.ts index 2bba45ff70..52ccd286ad 100644 --- a/npm/ng-packs/packages/components/extensible/src/lib/directives/prop-data.directive.ts +++ b/npm/ng-packs/packages/components/extensible/src/lib/directives/prop-data.directive.ts @@ -13,6 +13,7 @@ import { PropData, PropList } from '../models/props'; @Directive({ exportAs: 'abpPropData', selector: '[abpPropData]', + standalone: true, }) export class PropDataDirective> extends PropData> diff --git a/npm/ng-packs/packages/components/extensible/src/lib/extensible.module.ts b/npm/ng-packs/packages/components/extensible/src/lib/extensible.module.ts index 334909a7e2..99bc25b6f4 100644 --- a/npm/ng-packs/packages/components/extensible/src/lib/extensible.module.ts +++ b/npm/ng-packs/packages/components/extensible/src/lib/extensible.module.ts @@ -15,23 +15,25 @@ import { ExtensibleTableComponent, GridActionsComponent, PageToolbarComponent, - ExtensibleDateTimePickerComponent + ExtensibleDateTimePickerComponent, } from './components'; import { PropDataDirective } from './directives/prop-data.directive'; import { CreateInjectorPipe } from './pipes/create-injector.pipe'; import { DisabledDirective } from '@abp/ng.theme.shared'; +const importWithExport = [ + DisabledDirective, + ExtensibleDateTimePickerComponent, + ExtensibleFormPropComponent, + GridActionsComponent, + PropDataDirective, + PageToolbarComponent, + CreateInjectorPipe, + ExtensibleFormComponent, + ExtensibleTableComponent, +]; @NgModule({ - declarations: [ - ExtensibleDateTimePickerComponent, - PageToolbarComponent, - GridActionsComponent, - ExtensibleFormPropComponent, - ExtensibleFormComponent, - ExtensibleTableComponent, - PropDataDirective, - CreateInjectorPipe, - ], + declarations: [], imports: [ CoreModule, ThemeSharedModule, @@ -41,15 +43,8 @@ import { DisabledDirective } from '@abp/ng.theme.shared'; NgbTimepickerModule, NgbTypeaheadModule, NgbTooltipModule, - DisabledDirective, - ], - exports: [ - PageToolbarComponent, - GridActionsComponent, - ExtensibleFormComponent, - ExtensibleTableComponent, - PropDataDirective, - CreateInjectorPipe, + ...importWithExport, ], + exports: [...importWithExport], }) export class ExtensibleModule {} diff --git a/npm/ng-packs/packages/components/extensible/src/lib/pipes/create-injector.pipe.ts b/npm/ng-packs/packages/components/extensible/src/lib/pipes/create-injector.pipe.ts index 3536fa4dad..4b410fcea9 100644 --- a/npm/ng-packs/packages/components/extensible/src/lib/pipes/create-injector.pipe.ts +++ b/npm/ng-packs/packages/components/extensible/src/lib/pipes/create-injector.pipe.ts @@ -12,6 +12,7 @@ import { EXTENSIONS_ACTION_CALLBACK, EXTENSIONS_ACTION_DATA } from '../tokens/ex @Pipe({ name: 'createInjector', + standalone: true, }) export class CreateInjectorPipe implements PipeTransform { public transform( diff --git a/npm/ng-packs/packages/components/extensible/src/lib/services/extensible-form-prop.service.ts b/npm/ng-packs/packages/components/extensible/src/lib/services/extensible-form-prop.service.ts new file mode 100644 index 0000000000..bf6de7ed05 --- /dev/null +++ b/npm/ng-packs/packages/components/extensible/src/lib/services/extensible-form-prop.service.ts @@ -0,0 +1,77 @@ +import { inject, Injectable } from '@angular/core'; + import { ValidatorFn, Validators } from '@angular/forms'; +import { AbpValidators, ConfigStateService } from '@abp/ng.core'; +import { map } from 'rxjs/operators'; +import { FormProp } from '../models/form-props'; +import { ePropType } from '../enums/props.enum'; + +@Injectable() +export class ExtensibleFormPropService { + readonly #configStateService = inject(ConfigStateService); + + meridian$ = this.#configStateService + .getDeep$('localization.currentCulture.dateTimeFormat.shortTimePattern') + .pipe(map((shortTimePattern: string | undefined) => (shortTimePattern || '').includes('tt'))); + + isRequired(validator: ValidatorFn) { + return ( + validator === Validators.required || + validator === AbpValidators.required || + validator.name === 'required' + ); + } + + getComponent(prop: FormProp) { + if (prop.template) { + return 'template'; + } + switch (prop.type) { + case ePropType.Boolean: + return 'checkbox'; + case ePropType.Date: + return 'date'; + case ePropType.DateTime: + return 'dateTime'; + case ePropType.Hidden: + return 'hidden'; + case ePropType.MultiSelect: + return 'multiselect'; + case ePropType.Text: + return 'textarea'; + case ePropType.Time: + return 'time'; + case ePropType.Typeahead: + return 'typeahead'; + case ePropType.PasswordInputGroup: + return 'passwordinputgroup'; + default: + return prop.options ? 'select' : 'input'; + } + } + + getType(prop: FormProp) { + switch (prop.type) { + case ePropType.Date: + case ePropType.String: + return 'text'; + case ePropType.Boolean: + return 'checkbox'; + case ePropType.Number: + return 'number'; + case ePropType.Email: + return 'email'; + case ePropType.Password: + return 'password'; + case ePropType.PasswordInputGroup: + return 'passwordinputgroup'; + default: + return 'hidden'; + } + } + + calcAsterisks(validators: ValidatorFn[]) { + if (!validators) return ''; + const required = validators.find(v => this.isRequired(v)); + return required ? '*' : ''; + } +} diff --git a/npm/ng-packs/packages/components/page/src/page.module.ts b/npm/ng-packs/packages/components/page/src/page.module.ts index 5bb27fbc66..77ffca0951 100644 --- a/npm/ng-packs/packages/components/page/src/page.module.ts +++ b/npm/ng-packs/packages/components/page/src/page.module.ts @@ -1,6 +1,5 @@ import { CoreModule } from '@abp/ng.core'; import { ThemeSharedModule } from '@abp/ng.theme.shared'; - import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { PagePartDirective } from './page-part.directive'; import { @@ -9,7 +8,7 @@ import { PageToolbarContainerComponent, } from './page-parts.component'; import { PageComponent } from './page.component'; -import {ExtensibleModule} from "@abp/ng.components/extensible"; +import {PageToolbarComponent} from "@abp/ng.components/extensible"; const exportedDeclarations = [ PageComponent, @@ -21,7 +20,7 @@ const exportedDeclarations = [ @NgModule({ declarations: [...exportedDeclarations], - imports: [CommonModule, CoreModule, ThemeSharedModule, ExtensibleModule], + imports: [CoreModule, ThemeSharedModule, PageToolbarComponent], exports: [...exportedDeclarations], }) export class PageModule {} diff --git a/npm/ng-packs/packages/core/src/lib/abstracts/ng-model.component.ts b/npm/ng-packs/packages/core/src/lib/abstracts/ng-model.component.ts index 0f0eaa3722..8cabb30b0d 100644 --- a/npm/ng-packs/packages/core/src/lib/abstracts/ng-model.component.ts +++ b/npm/ng-packs/packages/core/src/lib/abstracts/ng-model.component.ts @@ -1,11 +1,11 @@ -import { ChangeDetectorRef, Component, Injector, Input } from '@angular/core'; +import { ChangeDetectorRef, Component, inject, Input } from '@angular/core'; import { ControlValueAccessor } from '@angular/forms'; // Not an abstract class on purpose. Do not change! @Component({ template: '' }) export class AbstractNgModelComponent implements ControlValueAccessor { protected _value!: T; - protected cdRef: ChangeDetectorRef; + protected cdRef = inject(ChangeDetectorRef); onChange?: (value: T) => void; onTouched?: () => void; @@ -39,10 +39,6 @@ export class AbstractNgModelComponent implements ControlValueAcc return this._value; } - constructor(public injector: Injector) { - this.cdRef = injector.get(ChangeDetectorRef); - } - notifyValueChange(): void { if (this.onChange) { this.onChange(this.value); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/checkbox/checkbox.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/checkbox/checkbox.component.ts index ee2dd5aeac..89766ce992 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/checkbox/checkbox.component.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/checkbox/checkbox.component.ts @@ -43,8 +43,4 @@ export class FormCheckboxComponent extends AbstractNgModelComponent { @Input() checkboxReadonly = false; @Output() checkboxBlur = new EventEmitter(); @Output() checkboxFocus = new EventEmitter(); - - constructor(injector: Injector) { - super(injector); - } } diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/form-input/form-input.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/form-input/form-input.component.ts index 8aa68a2047..d759163a0d 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/form-input/form-input.component.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/form-input/form-input.component.ts @@ -45,8 +45,4 @@ export class FormInputComponent extends AbstractNgModelComponent { @Input() inputClass = 'form-control'; @Output() formBlur = new EventEmitter(); @Output() formFocus = new EventEmitter(); - - constructor(injector: Injector) { - super(injector); - } } diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/password/password.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/password/password.component.ts index 22fcd6e4f5..4567ef45d6 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/password/password.component.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/password/password.component.ts @@ -1,9 +1,12 @@ -import { Component, forwardRef, Injector, Input } from '@angular/core'; +import { Component, forwardRef, Input } from '@angular/core'; import { AbstractNgModelComponent } from '@abp/ng.core'; -import { NG_VALUE_ACCESSOR } from '@angular/forms'; +import { FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { CommonModule } from '@angular/common'; @Component({ selector: 'abp-password', + standalone: true, + imports: [CommonModule, FormsModule], templateUrl: `./password.component.html`, providers: [ { @@ -18,10 +21,6 @@ export class PasswordComponent extends AbstractNgModelComponent { @Input() formControlName!: string; fieldTextType?: boolean; - constructor(injector: Injector) { - super(injector); - } - toggleFieldTextType() { this.fieldTextType = !this.fieldTextType; } diff --git a/npm/ng-packs/packages/theme-shared/src/lib/directives/ellipsis.directive.ts b/npm/ng-packs/packages/theme-shared/src/lib/directives/ellipsis.directive.ts index d7bfc999ff..2cad957942 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/directives/ellipsis.directive.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/directives/ellipsis.directive.ts @@ -10,6 +10,7 @@ import { @Directive({ selector: '[abpEllipsis]', + standalone:true }) export class EllipsisDirective implements AfterViewInit { @Input('abpEllipsis') @@ -44,9 +45,3 @@ export class EllipsisDirective implements AfterViewInit { this.cdRef.detectChanges(); } } - -@NgModule({ - exports: [EllipsisDirective], - declarations: [EllipsisDirective], -}) -export class EllipsisModule {} diff --git a/npm/ng-packs/packages/theme-shared/src/lib/directives/ngx-datatable-default.directive.ts b/npm/ng-packs/packages/theme-shared/src/lib/directives/ngx-datatable-default.directive.ts index b41be45343..eb781666c1 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/directives/ngx-datatable-default.directive.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/directives/ngx-datatable-default.directive.ts @@ -7,6 +7,7 @@ import { debounceTime } from 'rxjs/operators'; @Directive({ // eslint-disable-next-line @angular-eslint/directive-selector selector: 'ngx-datatable[default]', + standalone:true, exportAs: 'ngxDatatableDefault', }) export class NgxDatatableDefaultDirective implements AfterViewInit, OnDestroy { diff --git a/npm/ng-packs/packages/theme-shared/src/lib/directives/ngx-datatable-list.directive.ts b/npm/ng-packs/packages/theme-shared/src/lib/directives/ngx-datatable-list.directive.ts index a179669473..9ce5d1d860 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/directives/ngx-datatable-list.directive.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/directives/ngx-datatable-list.directive.ts @@ -21,6 +21,7 @@ import { @Directive({ // eslint-disable-next-line @angular-eslint/directive-selector selector: 'ngx-datatable[list]', + standalone: true, exportAs: 'ngxDatatableList', }) export class NgxDatatableListDirective implements OnChanges, OnDestroy, OnInit { diff --git a/npm/ng-packs/packages/theme-shared/src/lib/theme-shared.module.ts b/npm/ng-packs/packages/theme-shared/src/lib/theme-shared.module.ts index 71c081c9b2..44fbc85c0f 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/theme-shared.module.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/theme-shared.module.ts @@ -22,7 +22,7 @@ import { ModalComponent } from './components/modal/modal.component'; import { ToastContainerComponent } from './components/toast-container/toast-container.component'; import { ToastComponent } from './components/toast/toast.component'; import { DEFAULT_VALIDATION_BLUEPRINTS } from './constants/validation'; -import { EllipsisModule } from './directives/ellipsis.directive'; +import { EllipsisDirective } from './directives/ellipsis.directive'; import { LoadingDirective } from './directives/loading.directive'; import { NgxDatatableDefaultDirective } from './directives/ngx-datatable-default.directive'; import { NgxDatatableListDirective } from './directives/ngx-datatable-list.directive'; @@ -52,9 +52,6 @@ const declarationsWithExports = [ ModalComponent, ToastComponent, ToastContainerComponent, - PasswordComponent, - NgxDatatableDefaultDirective, - NgxDatatableListDirective, LoadingDirective, ModalCloseDirective, AbpVisibleDirective, @@ -68,14 +65,16 @@ const declarationsWithExports = [ NgxDatatableModule, NgxValidateCoreModule, NgbPaginationModule, - EllipsisModule, + EllipsisDirective, CardModule, + PasswordComponent, + NgxDatatableDefaultDirective, + NgxDatatableListDirective, DisabledDirective ], declarations: [...declarationsWithExports, HttpErrorWrapperComponent], exports: [ NgxDatatableModule, - EllipsisModule, NgxValidateCoreModule, CardModule, DisabledDirective, diff --git a/nupkg/common.ps1 b/nupkg/common.ps1 index 96d61dc74d..78de2aef44 100644 --- a/nupkg/common.ps1 +++ b/nupkg/common.ps1 @@ -204,6 +204,7 @@ $projects = ( "framework/src/Volo.Abp.Imaging.AspNetCore", "framework/src/Volo.Abp.Imaging.ImageSharp", "framework/src/Volo.Abp.Imaging.MagickNet", + "framework/src/Volo.Abp.Imaging.SkiaSharp", "framework/src/Volo.Abp.Json", "framework/src/Volo.Abp.Json.Abstractions", "framework/src/Volo.Abp.Json.Newtonsoft", diff --git a/templates/NuGet.Config b/templates/NuGet.Config index 554c2f634b..25cf8cc86f 100644 --- a/templates/NuGet.Config +++ b/templates/NuGet.Config @@ -2,5 +2,6 @@ + - \ No newline at end of file + diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/MyCompanyName.MyProjectName.Blazor.Server.Mongo.csproj b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/MyCompanyName.MyProjectName.Blazor.Server.Mongo.csproj index 5d17e0ba7c..8458da78e2 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/MyCompanyName.MyProjectName.Blazor.Server.Mongo.csproj +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/MyCompanyName.MyProjectName.Blazor.Server.Mongo.csproj @@ -22,8 +22,8 @@ - - + + diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj index e3b54d23e0..fc58cb48a9 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj @@ -22,8 +22,8 @@ - - + + diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Client/MyCompanyName.MyProjectName.Blazor.WebAssembly.Client.csproj b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Client/MyCompanyName.MyProjectName.Blazor.WebAssembly.Client.csproj index dc984ee426..78cefa8230 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Client/MyCompanyName.MyProjectName.Blazor.WebAssembly.Client.csproj +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Client/MyCompanyName.MyProjectName.Blazor.WebAssembly.Client.csproj @@ -21,7 +21,7 @@ - + diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server.Mongo/MyCompanyName.MyProjectName.Blazor.WebAssembly.Server.Mongo.csproj b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server.Mongo/MyCompanyName.MyProjectName.Blazor.WebAssembly.Server.Mongo.csproj index 102dd98170..85262239d9 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server.Mongo/MyCompanyName.MyProjectName.Blazor.WebAssembly.Server.Mongo.csproj +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server.Mongo/MyCompanyName.MyProjectName.Blazor.WebAssembly.Server.Mongo.csproj @@ -74,7 +74,7 @@ - + diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/MyCompanyName.MyProjectName.Blazor.WebAssembly.Server.csproj b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/MyCompanyName.MyProjectName.Blazor.WebAssembly.Server.csproj index 5c39ce6677..06e58454e4 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/MyCompanyName.MyProjectName.Blazor.WebAssembly.Server.csproj +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/MyCompanyName.MyProjectName.Blazor.WebAssembly.Server.csproj @@ -75,7 +75,7 @@ - + diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host.Mongo/MyCompanyName.MyProjectName.Host.Mongo.csproj b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host.Mongo/MyCompanyName.MyProjectName.Host.Mongo.csproj index 96d14d106b..2376ece313 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host.Mongo/MyCompanyName.MyProjectName.Host.Mongo.csproj +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host.Mongo/MyCompanyName.MyProjectName.Host.Mongo.csproj @@ -70,7 +70,7 @@ - + diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/MyCompanyName.MyProjectName.Host.csproj b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/MyCompanyName.MyProjectName.Host.csproj index 933b134e88..b54fc9be23 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/MyCompanyName.MyProjectName.Host.csproj +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/MyCompanyName.MyProjectName.Host.csproj @@ -71,7 +71,7 @@ - + diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc.Mongo/MyCompanyName.MyProjectName.Mvc.Mongo.csproj b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc.Mongo/MyCompanyName.MyProjectName.Mvc.Mongo.csproj index c89281a1b3..4ffd445303 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc.Mongo/MyCompanyName.MyProjectName.Mvc.Mongo.csproj +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc.Mongo/MyCompanyName.MyProjectName.Mvc.Mongo.csproj @@ -18,7 +18,7 @@ - + diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/MyCompanyName.MyProjectName.Mvc.csproj b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/MyCompanyName.MyProjectName.Mvc.csproj index 2b5d5f43cb..e0e3e7c0ee 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/MyCompanyName.MyProjectName.Mvc.csproj +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/MyCompanyName.MyProjectName.Mvc.csproj @@ -18,7 +18,7 @@ - + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.AuthServer/MyCompanyName.MyProjectName.AuthServer.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.AuthServer/MyCompanyName.MyProjectName.AuthServer.csproj index 4b4cde037a..980c8f4289 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.AuthServer/MyCompanyName.MyProjectName.AuthServer.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.AuthServer/MyCompanyName.MyProjectName.AuthServer.csproj @@ -51,7 +51,7 @@ - + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/MyCompanyName.MyProjectName.Blazor.Server.Tiered.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/MyCompanyName.MyProjectName.Blazor.Server.Tiered.csproj index 3cd676ba96..e0b60ea62a 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/MyCompanyName.MyProjectName.Blazor.Server.Tiered.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/MyCompanyName.MyProjectName.Blazor.Server.Tiered.csproj @@ -30,8 +30,8 @@ - - + + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj index 3575382c4c..875dcef886 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj @@ -28,8 +28,8 @@ - - + + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyCompanyName.MyProjectName.Blazor.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyCompanyName.MyProjectName.Blazor.csproj index 5ec1d4d889..9babd3b127 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyCompanyName.MyProjectName.Blazor.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyCompanyName.MyProjectName.Blazor.csproj @@ -24,7 +24,7 @@ - + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyCompanyName.MyProjectName.HttpApi.HostWithIds.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyCompanyName.MyProjectName.HttpApi.HostWithIds.csproj index c590ef232c..3a738e0f76 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyCompanyName.MyProjectName.HttpApi.HostWithIds.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyCompanyName.MyProjectName.HttpApi.HostWithIds.csproj @@ -26,7 +26,7 @@ - + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyCompanyName.MyProjectName.Web.Host.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyCompanyName.MyProjectName.Web.Host.csproj index b59c0bcf08..4bc03be393 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyCompanyName.MyProjectName.Web.Host.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyCompanyName.MyProjectName.Web.Host.csproj @@ -28,7 +28,7 @@ - + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyCompanyName.MyProjectName.Web.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyCompanyName.MyProjectName.Web.csproj index dd0e555b9e..495aa54824 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyCompanyName.MyProjectName.Web.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyCompanyName.MyProjectName.Web.csproj @@ -49,7 +49,7 @@ - +