|
After Width: | Height: | Size: 268 KiB |
|
After Width: | Height: | Size: 323 KiB |
|
After Width: | Height: | Size: 137 KiB |
|
After Width: | Height: | Size: 111 KiB |
|
After Width: | Height: | Size: 104 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 254 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 252 KiB |
@ -0,0 +1,20 @@ |
|||
# 2024 First Community Event. |
|||
|
|||
The first .NET community event in 2024 was successfully held in Shenzhen on January 14, 2024. |
|||
|
|||
This event is co-organized by **Microsoft MVP China Team**, **Microsoft Reactor**, **China .NET Community** and **Shenzhen .NET Club**. |
|||
|
|||
**ABP.IO** continues to strongly support the community, and we have prepared exquisite gifts for participants. |
|||
|
|||
The event includes four wonderful technical lectures to reveal big data and AI's potential opportunities and innovations. It is a transfer of knowledge and a platform for communication and cooperation among technology enthusiasts. |
|||
 |
|||
 |
|||
 |
|||
 |
|||
 |
|||
 |
|||
 |
|||
 |
|||
 |
|||
|
|||
**See you at the next community event!** |
|||
@ -0,0 +1,173 @@ |
|||
 |
|||
|
|||
# ABP Now Supports .NET 8 |
|||
|
|||
Recently we have published ABP v8.0. With this version [the ABP Framework](https://github.com/abpframework/abp/blob/dev/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyCompanyName.MyProjectName.Web.csproj#L6) and ABP Commercial both supports for .NET 8, aligning itself with the latest enhancements and new features of the ASP.NET's new version 8. |
|||
|
|||
Here's the [related PR](https://commercial.abp.io/releases/pr/15676) for this upgrade. This update ensures that developers using ABP can leverage the new features and improvements of .NET 8, enhancing the capability and performance of their applications. With .NET 8 support, ABP 8.0 likely offers improved performance, enhanced security, and greater efficiency, making it a robust choice for building modern web applications. |
|||
Here's the summary of .NET 8 features and enhancements: |
|||
|
|||
## What's new in .NET 8 |
|||
|
|||
### .NET Aspire |
|||
|
|||
[.NET Aspire](https://learn.microsoft.com/en-us/dotnet/aspire/) is a tool to observe and manage distributed web applications. It's still preview version. You can manage your containers, executables, logs, traces and metrics of your running web application. For more information see this article https://devblogs.microsoft.com/dotnet/introducing-dotnet-aspire-simplifying-cloud-native-development-with-dotnet-8/ |
|||
|
|||
### Serialization |
|||
|
|||
12 features have been implemented for [System.Text.Json](https://learn.microsoft.com/en-us/dotnet/api/system.text.json) serialization and deserialization library with .NET 8. See these enhancements |
|||
* [Built-in support for additional types](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8#built-in-support-for-additional-types) |
|||
* [Source generator](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8#source-generator) |
|||
* [Interface hierarchies](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8#interface-hierarchies) |
|||
* [Naming policies](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8#naming-policies) |
|||
* [Read-only properties](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8#read-only-properties) |
|||
* [Disable reflection-based default](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8#disable-reflection-based-default) |
|||
* [New JsonNode API methods](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8#new-jsonnode-api-methods) |
|||
* [Non-public members](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8#non-public-members) |
|||
* [Streaming deserialization APIs](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8#streaming-deserialization-apis) |
|||
* [WithAddedModifier extension method](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8#withaddedmodifier-extension-method) |
|||
* [New JsonContent.Create overloads](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8#new-jsoncontentcreate-overloads) |
|||
* [Freeze a JsonSerializerOptions instance](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8#freeze-a-jsonserializeroptions-instance) |
|||
|
|||
### Time abstraction |
|||
|
|||
You may mock time in test scenarios with the new [TimeProvider class](https://learn.microsoft.com/en-us/dotnet/api/system.timeprovider?view=net-8.0) and [ITimer interface](https://learn.microsoft.com/en-us/dotnet/api/system.threading.itimer?view=net-8.0), which add time abstraction functionality. |
|||
|
|||
### UTF8 improvements |
|||
|
|||
The new [IUtf8SpanFormattable](https://learn.microsoft.com/en-us/dotnet/api/system.iutf8spanformattable) interface targets `UTF8` and `Span<byte>` instead of `UTF16` and `Span<char>`. Also the new [Utf8.TryWrite](https://learn.microsoft.com/en-us/dotnet/api/system.text.unicode.utf8.trywrite) methods provide a `UTF8` based counterpart to the existing [MemoryExtensions.TryWrite](https://learn.microsoft.com/en-us/dotnet/api/system.memoryextensions.trywrite) methods, which are UTF16-based. |
|||
|
|||
### Data validation |
|||
|
|||
There are new validation attributes for cloud-native services. The new properties were added to the [RangeAttribute](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.rangeattribute) and [RequiredAttribute](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.requiredattribute) types. See the list below for the new validation attributes: |
|||
|
|||
| New Data Attribute | Description | |
|||
| ------------------------------------------------------------ | ------------------------------------------------------------ | |
|||
| [MinimumIsExclusive](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.rangeattribute.minimumisexclusive#system-componentmodel-dataannotations-rangeattribute-minimumisexclusive), [MaximumIsExclusive](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.rangeattribute.maximumisexclusive#system-componentmodel-dataannotations-rangeattribute-maximumisexclusive) | Checks whether bounds are included in the allowable range | |
|||
| [Length](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.lengthattribute) | Checks both lower and upper bounds for strings or collections. For example, `[Length(5, 10)]` requires at least 5 elements and at most 10 elements in a collection | |
|||
| [Base64String](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.base64stringattribute) | Checks that a string is a valid Base64 format | |
|||
| [AllowedValues](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.allowedvaluesattribute), [DeniedValues](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.deniedvaluesattribute) | Checks if values are from the allowed or disallowed list eg: `[AllowedValues("man", "woman", "child")]` | |
|||
|
|||
|
|||
|
|||
### Randomness methods |
|||
|
|||
The [System.Random](https://learn.microsoft.com/en-us/dotnet/api/system.random) and [System.Security.Cryptography.RandomNumberGenerator](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.randomnumbergenerator) types present 2 new approaches to handling randomness. |
|||
|
|||
### Performance-focused types |
|||
|
|||
* The new [System.Collections.Frozen](https://learn.microsoft.com/en-us/dotnet/api/system.collections.frozen) namespace includes the collection types [FrozenDictionary](https://learn.microsoft.com/en-us/dotnet/api/system.collections.frozen.frozendictionary-2) and [FrozenSet](https://learn.microsoft.com/en-us/dotnet/api/system.collections.frozen.frozenset-1). |
|||
* Methods like [MemoryExtensions.IndexOfAny](https://learn.microsoft.com/en-us/dotnet/api/system.memoryextensions.indexofany) look for the first occurrence of *any value in the passed collection*. |
|||
* The new [System.Text.CompositeFormat](https://learn.microsoft.com/en-us/dotnet/api/system.text.compositeformat) type is useful for optimizing format strings that aren't known at compile time (for example, if the format string is loaded from a resource file). |
|||
* New [System.IO.Hashing.XxHash3](https://learn.microsoft.com/en-us/dotnet/api/system.io.hashing.xxhash3) and [System.IO.Hashing.XxHash128](https://learn.microsoft.com/en-us/dotnet/api/system.io.hashing.xxhash128) types provide implementations of the fast XXH3 and XXH128 hash algorithms. |
|||
|
|||
### System.Numerics and System.Runtime.Intrinsics |
|||
|
|||
The [System.Numerics](https://learn.microsoft.com/en-us/dotnet/api/system.numerics) and [System.Runtime.Intrinsics](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.intrinsics) namespaces introduces some hardware improvements with .NET 8. The following structs reimplemented for running with the best performance on ARM64. |
|||
|
|||
[Vector256](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.intrinsics.vector256-1), [Matrix3x2](https://learn.microsoft.com/en-us/dotnet/api/system.numerics.matrix3x2), and [Matrix4x4](https://learn.microsoft.com/en-us/dotnet/api/system.numerics.matrix4x4) have improved |
|||
|
|||
### Metrics |
|||
|
|||
New APIs let you attach key-value pair tags to [Meter](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.metrics.meter) and [Instrument](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.metrics.instrument) objects when you create them. Aggregators of published metric measurements can use the tags to differentiate the aggregated values. |
|||
|
|||
### Cryptography |
|||
|
|||
[SHA-3](https://en.wikipedia.org/wiki/SHA-3) (Secure Hash Algorithm 3) is the latest member of the Secure Hash Algorithm family of standards. With .NET 8 SHA-3 is being supported. |
|||
|
|||
### HTTPS proxy support |
|||
|
|||
Before [HttpClient](https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclient) allows hackers to make [man-in-the-middle attack](https://en.wikipedia.org/wiki/Man-in-the-middle_attack) with .NET 8, it creates an encrypted channel between the client and the proxy so all requests can be handled with full privacy. |
|||
|
|||
### Stream-based ZipFile methods |
|||
|
|||
The new [ZipFile.ExtractToDirectory](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression.zipfile.extracttodirectory) provides a stream containing a zipped file and extract its contents into the filesystem. |
|||
|
|||
### Hosted lifecycle services |
|||
|
|||
Hosted services now have more options for execution during the application lifecycle. [IHostedService](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.ihostedservice) provided `StartAsync` and `StopAsync`, and now [IHostedLifecycleService](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.ihostedlifecycleservice) provides these additional methods: |
|||
|
|||
- [StartingAsync(CancellationToken)](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.ihostedlifecycleservice.startingasync#microsoft-extensions-hosting-ihostedlifecycleservice-startingasync(system-threading-cancellationtoken)) |
|||
- [StartedAsync(CancellationToken)](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.ihostedlifecycleservice.startedasync#microsoft-extensions-hosting-ihostedlifecycleservice-startedasync(system-threading-cancellationtoken)) |
|||
- [StoppingAsync(CancellationToken)](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.ihostedlifecycleservice.stoppingasync#microsoft-extensions-hosting-ihostedlifecycleservice-stoppingasync(system-threading-cancellationtoken)) |
|||
- [StoppedAsync(CancellationToken)](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.ihostedlifecycleservice.stoppedasync#microsoft-extensions-hosting-ihostedlifecycleservice-stoppedasync(system-threading-cancellationtoken)) |
|||
|
|||
### Native AOT support |
|||
|
|||
The option to [publish as Native AOT](https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/) was first introduced in .NET 7. Publishing an app with Native AOT creates a fully self-contained version of your app that doesn't need a runtime—everything is included in a single file. .NET 8 brings the following improvements to Native AOT publishing: |
|||
|
|||
| Operating system | .NET 7 | .NET 8 | |
|||
| :-------------------------------------- | :------ | :------ | |
|||
| Linux x64 (with `-p:StripSymbols=true`) | 3.76 MB | 1.84 MB | |
|||
| Windows x64 | 2.85 MB | 1.77 MB | |
|||
|
|||
### Target iOS-like platforms with Native AOT |
|||
|
|||
.NET 8 starts the work to enable Native AOT support for iOS-like platforms. You can now build and run .NET iOS and .NET MAUI applications with Native AOT on the following platforms: |
|||
|
|||
- `ios` |
|||
- `iossimulator` |
|||
- `maccatalyst` |
|||
- `tvos` |
|||
- `tvossimulator` |
|||
|
|||
### Performance improvements |
|||
|
|||
.NET 8 includes improvements to code generation and just-in time (JIT) compilation: |
|||
|
|||
- Arm64 performance improvements |
|||
- SIMD improvements |
|||
- Support for AVX-512 ISA extensions (see [Vector512 and AVX-512](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8#vector512-and-avx-512)) |
|||
- Cloud-native improvements |
|||
- JIT throughput improvements |
|||
- Loop and general optimizations |
|||
- Optimized access for fields marked with [ThreadStaticAttribute](https://learn.microsoft.com/en-us/dotnet/api/system.threadstaticattribute) |
|||
- Consecutive register allocation. Arm64 has two instructions for table vector lookup, which require that all entities in their tuple operands are present in consecutive registers. |
|||
- JIT/NativeAOT can now unroll and auto-vectorize some memory operations with SIMD, such as comparison, copying, and zeroing, if it can determine their sizes at compile time. |
|||
|
|||
|
|||
|
|||
### .NET SDK |
|||
|
|||
This section contains the following subtopics: |
|||
|
|||
- [CLI-based project evaluation](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8#cli-based-project-evaluation) |
|||
- [Terminal build output](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8#terminal-build-output) |
|||
- [Simplified output paths](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8#simplified-output-paths) |
|||
- ['dotnet workload clean' command](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8#dotnet-workload-clean-command) |
|||
- ['dotnet publish' and 'dotnet pack' assets](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8#dotnet-publish-and-dotnet-pack-assets) |
|||
- [Template engine](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8#template-engine) |
|||
- [Source Link](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8#source-link) |
|||
- [Source-build SDK](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8#source-build-sdk) |
|||
|
|||
### ASP.NET composite images |
|||
|
|||
As part of an effort to improve containerization performance, new ASP.NET Docker images are available that have a composite version of the runtime. This composite is built by compiling multiple MSIL assemblies into a single ready-to-run (R2R) output binary. Because these assemblies are embedded into a single image, jitting takes less time, and the startup performance of apps improves. The other big advantage of the composite over the regular ASP.NET image is that the composite images have a smaller size on disk. |
|||
|
|||
### Source-generated COM interop |
|||
|
|||
.NET 8 includes a new source generator that supports interoperating with COM interfaces. |
|||
|
|||
### Minimum support baselines for Linux |
|||
|
|||
The minimum support baselines for Linux have been updated for .NET 8. .NET is built targeting Ubuntu 16.04, for all architectures. |
|||
|
|||
### AOT compilation for Android apps |
|||
|
|||
To decrease app size, .NET and .NET MAUI apps that target Android use *profiled* ahead-of-time (AOT) compilation mode when they're built in Release mode. Profiled AOT compilation affects fewer methods than regular AOT compilation. .NET 8 introduces the `<AndroidStripILAfterAOT>` property that lets you opt-in to further AOT compilation for Android apps to decrease app size even more. |
|||
|
|||
### Code analysis |
|||
|
|||
.NET 8 includes several new code analyzers and fixers to help verify that you're using .NET library APIs correctly and efficiently. The following table summarizes the new analyzers. |
|||
|
|||
### NuGet |
|||
|
|||
Starting in .NET 8, NuGet verifies signed packages on Linux by default. NuGet continues to verify signed packages on Windows as well. |
|||
|
|||
### C# Hot Reload supports modifying generics |
|||
|
|||
Starting in .NET 8, C# Hot Reload [supports modifying generic types and generic methods](https://devblogs.microsoft.com/dotnet/hot-reload-generics/). When you debug console, desktop, mobile, or WebAssembly applications with Visual Studio, you can apply changes to generic classes and generic methods in C# code or Razor pages. For more information, see the [full list of edits supported by Roslyn](https://github.com/dotnet/roslyn/blob/main/docs/wiki/EnC-Supported-Edits.md) |
|||
|
|||
*References:* |
|||
|
|||
* https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8 |
|||
|
After Width: | Height: | Size: 168 KiB |
@ -0,0 +1,198 @@ |
|||
# ABP Now Supports Keyed Services! |
|||
|
|||
In this post, I describe the new **"keyed service"** support for the dependency injection container, which came with .NET 8.0. Then, I'll show you an example of usage within the ABP Framework. |
|||
|
|||
## What Are Keyed Services? |
|||
|
|||
ASP.NET Core ships with a built-in dependency injection container, which is a pretty basic DI container that supports minimal features a dependency injection container is supposed to have. For that reason, most of the .NET users use third-party containers like [Autofac](https://autofac.org/), or Ninject. |
|||
|
|||
> ABP Framework uses Autofac by default in startup templates, and it supports more features. For example, the built-in DI container does not natively support property injection but Autofac does. |
|||
|
|||
[**Keyed dependency injection (DI) services**](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-8.0#keyed-services) were added to the built-in DI container as a new feature with .NET 8.0. This is an important feature, which allows for registering and retrieving DI services using keys/names. |
|||
|
|||
Prior to this version, you could register multiple services as follows: |
|||
|
|||
```csharp |
|||
var builder = WebApplication.CreateBuilder(args); |
|||
|
|||
//service registrations |
|||
builder.Services.AddTransient<IMyService, MyServiceOne>(); |
|||
builder.Services.AddTransient<IMyService, MyServiceTwo>(); |
|||
builder.Services.AddTransient<IMyService, MyServiceThree>(); |
|||
``` |
|||
|
|||
But without keyed services, you could only retrieve all of the services like this: |
|||
|
|||
```csharp |
|||
public class BookService(IEnumerable<IMyService> services) {} |
|||
``` |
|||
|
|||
Or the last registered one (`MyServiceThree` in our case): |
|||
|
|||
```csharp |
|||
public class BookService(IMyService service) {} |
|||
``` |
|||
|
|||
There was not a simple way to retrieve a specific implementation type directly. With keyed services, now we can register services with a key and then resolve them by the key anywhere we want and retrieve the only one that we want to use. |
|||
|
|||
## ABP Framework Now Supports Keyed Services! |
|||
|
|||
Autofac was already supporting the named/keyed DI services for a long time and with v9.0.0, they made it compatible with the `Microsoft.Extensions.DependencyInjection` package (including the keyed service support). |
|||
|
|||
After the v9.0.0 was released for Autofac, then the ABP Framework Core team immediately updated the `Autofac.Extensions.DependencyInjection` package to `v9.0.0`, made the related changes in its own Autofac package and included in the v8.0.2 release. |
|||
|
|||
You can see the related changes in the following PRs: |
|||
|
|||
* https://github.com/abpframework/abp/pull/18775 |
|||
* https://github.com/abpframework/abp/pull/18777 |
|||
|
|||
## Using Keyed Services with ABP Framework |
|||
|
|||
After the quick background of the process and the feature itself, now let's see the keyed services in action. |
|||
|
|||
First, update the ABP CLI to v8.0.2 with the following command (if your CLI version is newer, no need to apply this): |
|||
|
|||
```bash |
|||
dotnet tool update -g Volo.Abp.Cli --version 8.0.2 |
|||
``` |
|||
|
|||
Then, we can create an application template (single-layer) with the following command (MVC as the UI option and EF Core as the DB provider): |
|||
|
|||
```bash |
|||
abp new KeyedServiceDemo -t app-nolayers -csf --version 8.0.2 |
|||
``` |
|||
|
|||
After the application is created, we can open it in an IDE and start developing... |
|||
|
|||
**Example**: |
|||
|
|||
Let's assume that we have an interface called `IMyService`, which has three implementation types `MyServiceOne`, `MyServiceTwo` and `MyServiceThree`: |
|||
|
|||
```csharp |
|||
public interface IMyService |
|||
{ |
|||
string GetMessage(); |
|||
} |
|||
|
|||
public class MyServiceOne : IMyService |
|||
{ |
|||
public string GetMessage() => "MyServiceOne"; |
|||
} |
|||
|
|||
public class MyServiceTwo : IMyService |
|||
{ |
|||
public string GetMessage() => "MyServiceTwo"; |
|||
} |
|||
|
|||
public class MyServiceThree : IMyService |
|||
{ |
|||
public string GetMessage() => "MyServiceThree"; |
|||
} |
|||
``` |
|||
|
|||
After creating our services, now we can register them in our module class. So, open the module class and add the following lines in the `ConfigureServices` method: |
|||
|
|||
```csharp |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
//code abbreviation for clarity... |
|||
|
|||
context.Services.AddKeyedTransient<IMyService, MyServiceOne>("myserviceone"); |
|||
context.Services.AddKeyedTransient<IMyService, MyServiceTwo>("myservicetwo"); |
|||
context.Services.AddKeyedTransient<IMyService, MyServiceThree>("myservicethree"); |
|||
} |
|||
``` |
|||
|
|||
* To register a keyed service, you can use one of the `AddKeyedTransient`, `AddKeyedScoped`, or `AddKeyedSingleton` overloads, and provide a key for the registration. |
|||
* In the example above, I used `AddKeyedTransient` overload, and registered all of these services as in the `Transient` lifetime. |
|||
|
|||
Then, when you want to retrieve a keyed service, you can use the `[FromKeyedServices(object key)]` attribute: |
|||
|
|||
```csharp |
|||
public class NotificationService([FromKeyedServices("myserviceone")] IMyService myServiceOne) |
|||
{ |
|||
public string Notify() => myServiceOne.GetMessage(); |
|||
} |
|||
``` |
|||
|
|||
With this kind of use, you can be certain that not the latest registered service is resolved and instead the keyed service will be resolved, and then you can use it. |
|||
|
|||
Also, you can use the `FromKeyedServicesAttribute` with minimal APIs as follows: |
|||
|
|||
```csharp |
|||
public override void OnApplicationInitialization(ApplicationInitializationContext context) |
|||
{ |
|||
//code abbreviation for clarity... |
|||
|
|||
app.UseConfiguredEndpoints(endpoints => |
|||
{ |
|||
endpoints.MapGet("/my-service", ([FromKeyedServices("myserviceone")] IMyService myservice) => myservice.GetMessage()); |
|||
}); |
|||
} |
|||
``` |
|||
|
|||
That's how it's easy to register multiple services in a keyed fashion and resolve them by keys! |
|||
|
|||
## Advanced |
|||
|
|||
In this section, I want to briefly mention some advanced topics (relatively 😀), such as what happens if I register more than one service with the same key, or is there a way to get the keyed services from the `ServiceProvider` etc. |
|||
|
|||
### Registering Multiple Services with the Same Key |
|||
|
|||
Let's assume that you mistakenly registered multiple services with the same key: |
|||
|
|||
```csharp |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
//code abbreviation for clarity... |
|||
|
|||
context.Services.AddKeyedTransient<IMyService, MyServiceOne>("myserviceone"); |
|||
context.Services.AddKeyedTransient<IMyService, MyServiceTwo>("myserviceone"); |
|||
} |
|||
``` |
|||
|
|||
In that case, when you try to resolve the dependency with the key, then the last registered one will be used without having a problem: |
|||
|
|||
```csharp |
|||
//it will resolve the `MyServiceTwo` service! |
|||
endpoints.MapGet("/my-service", ([FromKeyedServices("myserviceone")] IMyService myservice) => myservice.GetMessage()); |
|||
``` |
|||
|
|||
Also, since you used the same key for multiple services, then you can inject the `IEnumerable<IMyService>` to get all services for a certain key and use them: |
|||
|
|||
```csharp |
|||
//it will resolve both `MyServiceOne` and `MyServiceTwo` services! |
|||
endpoints.MapGet("/my-service", ([FromKeyedServices("myserviceone")] IEnumerable<IMyService> myservices) => |
|||
{ |
|||
var sb = new StringBuilder(); |
|||
foreach (var myService in myservices) |
|||
{ |
|||
sb.AppendLine(myService.GetMessage()); |
|||
} |
|||
|
|||
return sb.ToString(); |
|||
}); |
|||
``` |
|||
|
|||
### Resolving Keyed Services from ServiceProvider & LazyServiceProvider |
|||
|
|||
You can resolve keyed services by using one of the extension methods (`.GetKeyedServices`, `.GetKeyedService<>`, `.GetRequiredKeyedService<>`, ...): |
|||
|
|||
```csharp |
|||
//uses the `MyServiceTwo` service! |
|||
var myService = ServiceProvider.GetRequiredKeyedService<IMyService>("myserviceone"); |
|||
``` |
|||
|
|||
On the other hand, resolving keyed services from `LazyServiceProvider` is not supported in v8.0.2, but there is a PR, which you can find at https://github.com/abpframework/abp/pull/18792, it will fix this problem and it will be included in the next version. |
|||
|
|||
### Automatically Registering Keyed Services |
|||
|
|||
Currently, if you want to register a keyed service, you need to do it manually as we see in the previous sections by using one of the overloads (`.AddKeyedTransient`, `.AddKeyedScoped` and `.AddKeyedSingleton`). |
|||
|
|||
It would be good if we could make this process automatically and not need to manually register services, and for that purpose, I have [created an issue](https://github.com/abpframework/abp/issues/18794) that aims to introduce an attribute, which allows us to automatically register multiple services as keyed services. |
|||
|
|||
You can [follow the issue](https://github.com/abpframework/abp/issues/18794) if you are considering using keyed services in your application and don't want to register them manually. |
|||
|
|||
## Summary |
|||
|
|||
In this post, I described the new **"keyed service"** support added to the built-in dependency injection container that was released in .NET 8, and wanted to announce it's being supported from v8.0.2 for ABP Framework. It's a really good addition to the built-in dependency injection container and can be pretty useful for certain use-cases. |
|||
|
After Width: | Height: | Size: 387 KiB |
@ -0,0 +1,31 @@ |
|||
using System; |
|||
|
|||
namespace Microsoft.Extensions.DependencyInjection; |
|||
|
|||
/// <summary>
|
|||
/// Extensions for working with <see cref="ServiceDescriptor"/>.
|
|||
/// </summary>
|
|||
public static class ServiceDescriptorExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Normalizes the implementation instance data between keyed and not keyed services.
|
|||
/// </summary>
|
|||
/// <param name="descriptor">
|
|||
/// The <see cref="ServiceDescriptor"/> to normalize.
|
|||
/// </param>
|
|||
/// <returns>
|
|||
/// The appropriate implementation instance from the service descriptor.
|
|||
/// </returns>
|
|||
public static object? NormalizedImplementationInstance(this ServiceDescriptor descriptor) => descriptor.IsKeyedService ? descriptor.KeyedImplementationInstance : descriptor.ImplementationInstance; |
|||
|
|||
/// <summary>
|
|||
/// Normalizes the implementation type data between keyed and not keyed services.
|
|||
/// </summary>
|
|||
/// <param name="descriptor">
|
|||
/// The <see cref="ServiceDescriptor"/> to normalize.
|
|||
/// </param>
|
|||
/// <returns>
|
|||
/// The appropriate implementation type from the service descriptor.
|
|||
/// </returns>
|
|||
public static Type? NormalizedImplementationType(this ServiceDescriptor descriptor) => descriptor.IsKeyedService ? descriptor.KeyedImplementationType : descriptor.ImplementationType; |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
using System; |
|||
using Volo.Abp; |
|||
|
|||
namespace Microsoft.Extensions.DependencyInjection; |
|||
|
|||
public static class ServiceProviderKeyedServiceExtensions |
|||
{ |
|||
public static object? GetKeyedService(this IServiceProvider provider, Type serviceType, object? serviceKey) |
|||
{ |
|||
Check.NotNull(provider, nameof(provider)); |
|||
|
|||
if (provider is IKeyedServiceProvider keyedServiceProvider) |
|||
{ |
|||
return keyedServiceProvider.GetKeyedService(serviceType, serviceKey); |
|||
} |
|||
|
|||
throw new InvalidOperationException("This service provider doesn't support keyed services."); |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
using System; |
|||
|
|||
namespace Volo.Abp.DependencyInjection; |
|||
|
|||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] |
|||
public class ExposeKeyedServiceAttribute<TServiceType> : Attribute, IExposedKeyedServiceTypesProvider |
|||
where TServiceType : class |
|||
{ |
|||
public ServiceIdentifier ServiceIdentifier { get; } |
|||
|
|||
public ExposeKeyedServiceAttribute(object serviceKey) |
|||
{ |
|||
if (serviceKey == null) |
|||
{ |
|||
throw new AbpException($"{nameof(serviceKey)} can not be null! Use {nameof(ExposeServicesAttribute)} instead."); |
|||
} |
|||
|
|||
ServiceIdentifier = new ServiceIdentifier(serviceKey, typeof(TServiceType)); |
|||
} |
|||
|
|||
public ServiceIdentifier[] GetExposedServiceTypes(Type targetType) |
|||
{ |
|||
return new[] { ServiceIdentifier }; |
|||
} |
|||
} |
|||
@ -1,14 +1,15 @@ |
|||
using System; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.DependencyInjection; |
|||
|
|||
public interface ICachedServiceProviderBase : IServiceProvider |
|||
public interface ICachedServiceProviderBase : IKeyedServiceProvider |
|||
{ |
|||
T GetService<T>(T defaultValue); |
|||
|
|||
|
|||
object GetService(Type serviceType, object defaultValue); |
|||
|
|||
T GetService<T>(Func<IServiceProvider, object> factory); |
|||
|
|||
object GetService(Type serviceType, Func<IServiceProvider, object> factory); |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,8 @@ |
|||
using System; |
|||
|
|||
namespace Volo.Abp.DependencyInjection; |
|||
|
|||
public interface IExposedKeyedServiceTypesProvider |
|||
{ |
|||
ServiceIdentifier[] GetExposedServiceTypes(Type targetType); |
|||
} |
|||
@ -0,0 +1,54 @@ |
|||
using System; |
|||
|
|||
namespace Volo.Abp.DependencyInjection; |
|||
|
|||
/// <summary>
|
|||
/// https://github.com/dotnet/runtime/blob/release/8.0/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ServiceIdentifier.cs#L9
|
|||
/// </summary>
|
|||
public readonly struct ServiceIdentifier : IEquatable<ServiceIdentifier> |
|||
{ |
|||
public object? ServiceKey { get; } |
|||
|
|||
public Type ServiceType { get; } |
|||
|
|||
public ServiceIdentifier(Type serviceType) |
|||
{ |
|||
ServiceType = serviceType; |
|||
} |
|||
|
|||
public ServiceIdentifier(object? serviceKey, Type serviceType) |
|||
{ |
|||
ServiceKey = serviceKey; |
|||
ServiceType = serviceType; |
|||
} |
|||
|
|||
public bool Equals(ServiceIdentifier other) |
|||
{ |
|||
if (ServiceKey == null && other.ServiceKey == null) |
|||
{ |
|||
return ServiceType == other.ServiceType; |
|||
} |
|||
else if (ServiceKey != null && other.ServiceKey != null) |
|||
{ |
|||
return ServiceType == other.ServiceType && ServiceKey.Equals(other.ServiceKey); |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
public override bool Equals(object? obj) |
|||
{ |
|||
return obj is ServiceIdentifier && Equals((ServiceIdentifier)obj); |
|||
} |
|||
|
|||
public override int GetHashCode() |
|||
{ |
|||
if (ServiceKey == null) |
|||
{ |
|||
return ServiceType.GetHashCode(); |
|||
} |
|||
unchecked |
|||
{ |
|||
return (ServiceType.GetHashCode() * 397) ^ ServiceKey.GetHashCode(); |
|||
} |
|||
} |
|||
} |
|||