ServiceRequest<T>
IServiceProvider.GetService(Type) requests are forwarded as ServiceRequest<T> requests.
MyClass instance = (MyClass)service.GetService(typeof(MyClass))!;
ServiceRequest<T> are received and processed by ServiceRequestHandler (included in ServiceHandlers). ServiceRequest<T> is the request on the consumer side.
MyClass instance = service.GetRequired<ServiceRequest<MyClass>, MyClass>(default);
ServiceRequestT.Create(type) constructs a ServiceRequest<T> for a specific run-time type.
// Create request
IServiceRequest request = ServiceRequestT.Create(typeof(MyClass));
// Get service
MyClass instance = service.GetRequired<IServiceRequest, MyClass>(request);
IServiceProducerRequest<T>
IServiceProducerRequest<T> is the interface for the three producer side requests.
IServiceProducerRequest<T> ├── SingletonServiceRequest<T> ├── ScopedServiceRequest<T> └── TransientServiceRequest<T>
ServiceRequestHandler forwards ServiceRequest<T> as three sub-requests: SingletonServiceRequest<T>, ScopedServiceRequest<T> and TransientServiceRequest<T>.
Handler serves one of these request types and appends value in IServiceList<T>. The selected request type affects the dispose and cache policy of the result.
/// <summary>Creates lists with huge initial capacity.</summary>
public class LargeListBuilder : IHandlerBase
{
/// <summary>Constructs <![CDATA[List<>]]> instances</summary>
ConstructorT<int, IList> listConstructor = new(typeof(List<>));
/// <summary>Serve <![CDATA[ServiceRequest<List<>>]]></summary>
public void Handle<T>(IQuery<TransientServiceRequest<List<T>>, IServiceList<List<T>>> query)
{
// Unexpected status
if (query.Response.Status.ExcludeFlags() > EntryStatus.Value) return;
// Get response container
IServiceList<List<T>>? response = query.Response.Value<IServiceList<List<T>>>() ?? query.Response.PassValue<IServiceList<List<T>>>(new ServiceList<List<T>>());
// Create List<T> with Capacity = 1_000_000
IList largeList = listConstructor.Create(typeof(T), 1_000_000);
// Append to result
response.Add(largeList, new IServiceList.Line(Order: 0L, ToDispose: false));
}
}
// Create service collection
IServiceCollection serviceCollection = new ServiceCollection()
.AddHandler(new LargeListBuilder())
.AddHandlers(ServiceHandlers.Instance.ServiceRequestHandler)
.AddCachePolicy(CachePolicies.Default);
// Create service
using var service = serviceCollection.BuildAvalancheServiceProvider();
// Create list with Capacity = 1_000_000
List<int> instance = service.GetRequiredService<List<int>>();
Handlers participate in service query by modifiying IServiceList<T>.
/// <summary>Participate in <![CDATA[IEnumerable<string>]]> service request</summary>
public class StringsService : IHandlerBase
{
/// <summary></summary>
[Order(1000)] // After ServiceDescriptor (0)
public void Handle(IQuery<SingletonServiceRequest<string>, IServiceList<string>> query)
{
// Unexpected status
if (query.Response.Status.ExcludeFlags() > EntryStatus.Value) return;
// Get response container
IServiceList<string>? list = query.Response.Value<IServiceList<string>>() ?? query.Response.PassValue<IServiceList<string>>(new ServiceList<string>());
// String to add to service enumeration
string str = "Participated in service request (first line)";
// Append as first result value (Order = long.MinValue)
list.Add(str, new IServiceList.Line(Order: long.MinValue, ToDispose: false));
// String to add to service enumeration
str = "Participated in service request (last line)";
// Append as last result value (Order = long.MaxValue)
list.Add(str, new IServiceList.Line(Order: long.MaxValue, ToDispose: false));
}
}
// Create service collection
IServiceCollection serviceCollection = new ServiceCollection()
.AddHandlers(ServiceHandlers.Instance.ServiceRequestHandler)
.AddSingleton<string>("Hello")
.AddSingleton<string>("World")
.AddHandler(new StringsService());
// Create service provider
using var service = serviceCollection.BuildAvalancheServiceProvider();
// Request service IEnumerable<string>
IEnumerable<string> strings = service.GetRequiredService<IEnumerable<string>>();
// Print four strings
foreach (var str in strings) Console.WriteLine(str);
// "Participated in service request (first line)"
// "Hello"
// "World"
// "Participated in service request(last line)"
IServiceList<T>
/// <summary>
/// List that has elements and associated sort order.
///
/// Elements and order values are in separate sub-lists, so that a reference to element and order lists can be taken distictively.
///
/// List not maintained in sorted order, but must be put into order with explicit <see cref="Sort"/> call.
///
/// List has mutability state, and throws <see cref="InvalidOperationException"/> if is modified after put into read-only state.
/// </summary>
public interface IServiceList : IReadOnly, IDisposable
{
/// <summary>Get element type</summary>
Type ElementType { get; }
/// <summary>Get element at index.</summary>
object this[int index] { get; }
/// <summary>Elements as <![CDATA[IList<T>]]></summary>
object Elements { get; set; }
/// <summary>Orders, each line corresponds to line in <see cref="Elements"/></summary>
IList<Line> Lines { get; set; }
/// <summary>Get number of lines</summary>
int Count { get; }
/// <summary>Sort <see cref="Elements"/> by <see cref="Lines"/> values in ascending order. Must be in mutable-state.</summary>
void Sort();
/// <summary>Add <paramref name="element"/> with associated order value <paramref name="order"/>.</summary>
void Add(object element, Line line);
/// <summary>object that can be used to synchronize the access.</summary>
object SyncRoot { get; }
/// <summary>Create T[]</summary>
Array ToArray();
/// <summary>Add all elements and lines from <paramref name="listFrom"/>.</summary>
void AddAll(IServiceList listFrom);
/// <summary>Service meta info</summary>
public record struct Line(long Order, bool ToDispose);
}
/// <summary>
/// List that has elements and associated sort order.
///
/// Elements and order values are in separate sub-lists, so that a reference to element and order lists can be taken distictively.
///
/// List not maintained in sorted order, but must be put into order with explicit <see cref="IServiceList.Sort"/> call.
///
/// List has mutability state, and throws <see cref="InvalidOperationException"/> if is modified after put into read-only state.
/// </summary>
public interface IServiceList<T> : IServiceList
{
/// <summary></summary>
new IList<T> Elements { get; set; }
/// <summary>Add <paramref name="element"/> with associated order value <paramref name="order"/>.</summary>
void Add(T element, Line line);
}
Decorating a service
Handler can decorate a service result that has already been constructed by other handlers.
public class LoggerDecorator : IHandlerBase
{
/// <summary>Decorate loggers in every producer request</summary>
[Order(9_000_000_000_000_000_000L)]
public void Handle<R, T>(IQuery<R, IServiceList<ILogger<T>>> query) where R : IServiceProducerRequest<ILogger<T>>
{
// No loggers
if (query.Response.Value() is not IServiceList<ILogger<T>> list || list.Count == 0) return;
// Decorate each logger
for (int i = 0; i < list.Count; i++)
{
// Get logger
ILogger<T> logger = list.Elements[i];
// Decorate logger
ILogger<T> loggerDecoration = new LoggerDecoration<T>(logger);
// Assign decoartion
list.Elements[i] = loggerDecoration;
}
}
}
// Create service collection
IServiceCollection serviceCollection = new ServiceCollection();
// Add logging
InitializeLogging(serviceCollection);
// Add services
serviceCollection
.AddHandler(new LoggerDecorator())
.AddHandlers(ServiceHandlers.Instance.ServiceRequestHandler)
.AddAvalancheServiceQueryLogger()
.AddTransient<MyClassWithLogger>();
// Create service
using var service = serviceCollection.BuildAvalancheServiceProvider(cachePolicy: CachePolicies.Default);
// Get myclass
MyClassWithLogger myClass = service.GetRequiredService<MyClassWithLogger>();
MyClassWithLogger
/// <summary></summary>
public class MyClassWithLogger
{
/// <summary></summary>
public ILogger<MyClassWithLogger> logger;
/// <summary></summary>
public MyClassWithLogger(ILogger<MyClassWithLogger> logger)
{
this.logger = logger;
this.logger.LogInformation("Hello world");
}
}
LoggerDecoration
public class LoggerDecoration<T> : ILogger<T>
{
/// <summary></summary>
ILogger<T> source;
/// <summary></summary>
public LoggerDecoration(ILogger<T> source)
{
this.source = source;
}
/// <summary></summary>
public IDisposable BeginScope<TState>(TState state) => source.BeginScope<TState>(state);
/// <summary></summary>
public bool IsEnabled(LogLevel logLevel) => source.IsEnabled(logLevel);
/// <summary></summary>
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
//
string msg = formatter(state, exception);
// Debug print
Console.WriteLine($"LoggerDecoration Here: {msg}");
//
source.Log<TState>(logLevel, eventId, state, exception, formatter);
}
}
InitializeLogging
static IServiceCollection InitializeLogging(IServiceCollection serviceCollection)
{
// Initial configuration
MemoryConfiguration memConfig = new MemoryConfiguration()
.Set("Logging:LogLevel:Default", "Debug")
.Set("Serilog:WriteTo:0:Name", "Console")
.Set("Serilog:WriteTo:0:Args:OutputTemplate", "[{EventId:x8} {Level:u1}] {Message:lj}{NewLine}{Exception}")
.Set("Serilog:WriteTo:0:Args:RestrictedToMinimumLevel", "Verbose")
.Set("Serilog:WriteTo:0:Args:Theme", "Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Code, Serilog.Sinks.Console");
// Read configuration
IConfigurationRoot configuration = new ConfigurationBuilder()
.Add(memConfig)
.Build();
// Logging
serviceCollection.AddLogging(loggingBuilder =>
loggingBuilder
#if DEBUG
.SetMinimumLevel(LogLevel.Trace)
#else
.SetMinimumLevel(LogLevel.Information)
#endif
.AddSerilog(SwitchableLogger.Instance, true)
.AddSerilogConfigurationLoader(configuration, SwitchableLogger.Instance,
c => new Serilog.LoggerConfiguration()
#if DEBUG
.MinimumLevel.Verbose()
#else
.MinimumLevel.Information()
#endif
.Enrich.With(new EventIdEnricher())
.ReadFrom.Configuration(configuration)
.CreateLogger())
);
//
return serviceCollection;
}
MemoryConfiguration.cs
using System;
using System.Collections;
using System.Collections.Generic;
using Microsoft.Extensions.Configuration;
namespace docs
{
/// <summary>Memory configuration</summary>
public class MemoryConfiguration : ConfigurationProvider, IEnumerable<KeyValuePair<string, string>>, IConfigurationSource
{
/// <summary>Expose inner configuration data</summary>
public new IDictionary<String, String> Data => base.Data;
/// <summary>Configuration data</summary>
public string this[string key] { get => base.Data[key]; set => base.Data[key] = value; }
/// <summary>Create memory configuration</summary>
public MemoryConfiguration() : base() { }
/// <summary>Assign <paramref name="value"/> to <paramref name="key"/></summary>
public new MemoryConfiguration Set(string key, string value) { base.Data[key] = value; return this; }
/// <summary>Build configuration provider.</summary>
public IConfigurationProvider Build(IConfigurationBuilder builder) => this;
/// <summary>Enumerate</summary>
public IEnumerator<KeyValuePair<string, string>> GetEnumerator() => Data.GetEnumerator();
/// <summary>Enumerate</summary>
IEnumerator IEnumerable.GetEnumerator() => Data.GetEnumerator();
}
}
EventIdEnricher.cs
using System.Collections.Generic;
using System.Globalization;
using Serilog.Core;
using Serilog.Events;
namespace docs
{
/// <summary>Serilog enricher that reduces "EventId" to its "id" field.</summary>
public class EventIdEnricher : ILogEventEnricher
{
/// <summary>Reduce to EventId.id</summary>
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
{
// Get properties
if (!logEvent.Properties.TryGetValue("EventId", out LogEventPropertyValue? eventIdStructure)) return;
// Not structure field
if (eventIdStructure is not StructureValue structureValue || structureValue.Properties == null) return;
// Get list
IReadOnlyList<LogEventProperty> list = structureValue.Properties;
// Process each
for (int i = 0; i < list.Count; i++)
{
// Get property
LogEventProperty logEventProperty = list[i];
// Not id
if (logEventProperty.Name != "Id") continue;
// New property
LogEventProperty eventId = propertyFactory.CreateProperty("EventId", logEventProperty.Value);
// Add as new key
logEvent.AddOrUpdateProperty(eventId);
// Print as hex
string hex = logEventProperty.Value.ToString("X8", CultureInfo.InvariantCulture);
// New property
LogEventProperty eventIdHex = propertyFactory.CreateProperty("EventIdHex", hex);
// Add as new key
logEvent.AddOrUpdateProperty(eventIdHex);
// Completed
return;
}
}
}
}
Full Example
Full example
using System;
using System.Collections;
using System.Collections.Generic;
using Avalanche.Service;
using Avalanche.Utilities;
using docs;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Serilog;
using static System.Console;
public class di_servicerequest
{
// <InitializeLogging>
static IServiceCollection InitializeLogging(IServiceCollection serviceCollection)
{
// Initial configuration
MemoryConfiguration memConfig = new MemoryConfiguration()
.Set("Logging:LogLevel:Default", "Debug")
.Set("Serilog:WriteTo:0:Name", "Console")
.Set("Serilog:WriteTo:0:Args:OutputTemplate", "[{EventId:x8} {Level:u1}] {Message:lj}{NewLine}{Exception}")
.Set("Serilog:WriteTo:0:Args:RestrictedToMinimumLevel", "Verbose")
.Set("Serilog:WriteTo:0:Args:Theme", "Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Code, Serilog.Sinks.Console");
// Read configuration
IConfigurationRoot configuration = new ConfigurationBuilder()
.Add(memConfig)
.Build();
// Logging
serviceCollection.AddLogging(loggingBuilder =>
loggingBuilder
#if DEBUG
.SetMinimumLevel(LogLevel.Trace)
#else
.SetMinimumLevel(LogLevel.Information)
#endif
.AddSerilog(SwitchableLogger.Instance, true)
.AddSerilogConfigurationLoader(configuration, SwitchableLogger.Instance,
c => new Serilog.LoggerConfiguration()
#if DEBUG
.MinimumLevel.Verbose()
#else
.MinimumLevel.Information()
#endif
.Enrich.With(new EventIdEnricher())
.ReadFrom.Configuration(configuration)
.CreateLogger())
);
//
return serviceCollection;
}
// </InitializeLogging>
// <MyClassWithLogger>
/// <summary></summary>
public class MyClassWithLogger
{
/// <summary></summary>
public ILogger<MyClassWithLogger> logger;
/// <summary></summary>
public MyClassWithLogger(ILogger<MyClassWithLogger> logger)
{
this.logger = logger;
this.logger.LogInformation("Hello world");
}
}
// </MyClassWithLogger>
// <LoggerDecorator>
public class LoggerDecorator : IHandlerBase
{
/// <summary>Decorate loggers in every producer request</summary>
[Order(9_000_000_000_000_000_000L)]
public void Handle<R, T>(IQuery<R, IServiceList<ILogger<T>>> query) where R : IServiceProducerRequest<ILogger<T>>
{
// No loggers
if (query.Response.Value() is not IServiceList<ILogger<T>> list || list.Count == 0) return;
// Decorate each logger
for (int i = 0; i < list.Count; i++)
{
// Get logger
ILogger<T> logger = list.Elements[i];
// Decorate logger
ILogger<T> loggerDecoration = new LoggerDecoration<T>(logger);
// Assign decoartion
list.Elements[i] = loggerDecoration;
}
}
}
// </LoggerDecorator>
// <LoggerDecoration>
public class LoggerDecoration<T> : ILogger<T>
{
/// <summary></summary>
ILogger<T> source;
/// <summary></summary>
public LoggerDecoration(ILogger<T> source)
{
this.source = source;
}
/// <summary></summary>
public IDisposable BeginScope<TState>(TState state) => source.BeginScope<TState>(state);
/// <summary></summary>
public bool IsEnabled(LogLevel logLevel) => source.IsEnabled(logLevel);
/// <summary></summary>
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
//
string msg = formatter(state, exception);
// Debug print
Console.WriteLine($"LoggerDecoration Here: {msg}");
//
source.Log<TState>(logLevel, eventId, state, exception, formatter);
}
}
// </LoggerDecoration>
public static void Run()
{
{
// Create service collection
IServiceCollection serviceCollection = new ServiceCollection()
.AddHandlers(ServiceHandlers.Instance.ServiceRequestHandler)
.AddCachePolicy(CachePolicies.Default)
.AddTransient<MyClass>();
// Create service
using IServiceDisposable service = serviceCollection.BuildAvalancheServiceProvider();
// Get instance
// <01>
MyClass instance = service.GetRequired<ServiceRequest<MyClass>, MyClass>(default);
// </01>
WriteLine(instance);
}
{
// Create service collection
IServiceCollection serviceCollection = new ServiceCollection()
.AddHandlers(ServiceHandlers.Instance.ServiceRequestHandler)
.AddCachePolicy(CachePolicies.Default)
.AddTransient<MyClass>();
// Create service
using IServiceDisposable service = serviceCollection.BuildAvalancheServiceProvider();
// <02>
// Create request
IServiceRequest request = ServiceRequestT.Create(typeof(MyClass));
// Get service
MyClass instance = service.GetRequired<IServiceRequest, MyClass>(request);
// </02>
WriteLine(instance);
}
{
// Create service collection
IServiceCollection serviceCollection = new ServiceCollection()
.AddHandlers(ServiceHandlers.Instance.ServiceRequestHandler)
.AddCachePolicy(CachePolicies.Default)
.AddTransient<MyClass>();
// Create service
using IServiceDisposable service = serviceCollection.BuildAvalancheServiceProvider();
// Get instance
// <03>
MyClass instance = (MyClass)service.GetService(typeof(MyClass))!;
// </03>
WriteLine(instance);
}
{
// Create service collection
IServiceCollection serviceCollection = new ServiceCollection()
.AddHandlers(ServiceHandlers.Instance.ServiceRequestHandler)
.AddCachePolicy(CachePolicies.Default)
.AddTransient<MyClass>();
// Create service
using IServiceDisposable service = serviceCollection.BuildAvalancheServiceProvider();
// <04>
// Create request
IServiceRequest request = ServiceRequestT.Create(typeof(MyClass));
// Get instance
MyClass instance = service.GetRequired<IServiceRequest, MyClass>(request);
// </04>
WriteLine(instance);
}
{
// <11>
// Create service collection
IServiceCollection serviceCollection = new ServiceCollection();
// Add logging
InitializeLogging(serviceCollection);
// Add services
serviceCollection
.AddHandler(new LoggerDecorator())
.AddHandlers(ServiceHandlers.Instance.ServiceRequestHandler)
.AddAvalancheServiceQueryLogger()
.AddTransient<MyClassWithLogger>();
// Create service
using var service = serviceCollection.BuildAvalancheServiceProvider(cachePolicy: CachePolicies.Default);
// Get myclass
MyClassWithLogger myClass = service.GetRequiredService<MyClassWithLogger>();
// </11>
//
WriteLine(service);
WriteLine(myClass.logger.GetType());
}
{
// <31>
// Create service collection
IServiceCollection serviceCollection = new ServiceCollection()
.AddHandler(new LargeListBuilder())
.AddHandlers(ServiceHandlers.Instance.ServiceRequestHandler)
.AddCachePolicy(CachePolicies.Default);
// Create service
using var service = serviceCollection.BuildAvalancheServiceProvider();
// Create list with Capacity = 1_000_000
List<int> instance = service.GetRequiredService<List<int>>();
// </31>
}
{
// <41>
// Create service collection
IServiceCollection serviceCollection = new ServiceCollection()
.AddHandlers(ServiceHandlers.Instance.ServiceRequestHandler)
.AddSingleton<string>("Hello")
.AddSingleton<string>("World")
.AddHandler(new StringsService());
// Create service provider
using var service = serviceCollection.BuildAvalancheServiceProvider();
// Request service IEnumerable<string>
IEnumerable<string> strings = service.GetRequiredService<IEnumerable<string>>();
// Print four strings
foreach (var str in strings) Console.WriteLine(str);
// "Participated in service request (first line)"
// "Hello"
// "World"
// "Participated in service request(last line)"
// </41>
}
}
// <MyClass>
/// <summary></summary>
public class MyClass
{
/// <summary></summary>
public MyClass()
{
}
}
// </MyClass>
// <LargeListBuilder>
/// <summary>Creates lists with huge initial capacity.</summary>
public class LargeListBuilder : IHandlerBase
{
/// <summary>Constructs <![CDATA[List<>]]> instances</summary>
ConstructorT<int, IList> listConstructor = new(typeof(List<>));
/// <summary>Serve <![CDATA[ServiceRequest<List<>>]]></summary>
public void Handle<T>(IQuery<TransientServiceRequest<List<T>>, IServiceList<List<T>>> query)
{
// Unexpected status
if (query.Response.Status.ExcludeFlags() > EntryStatus.Value) return;
// Get response container
IServiceList<List<T>>? response = query.Response.Value<IServiceList<List<T>>>() ?? query.Response.PassValue<IServiceList<List<T>>>(new ServiceList<List<T>>());
// Create List<T> with Capacity = 1_000_000
IList largeList = listConstructor.Create(typeof(T), 1_000_000);
// Append to result
response.Add(largeList, new IServiceList.Line(Order: 0L, ToDispose: false));
}
}
// </LargeListBuilder>
// <42>
/// <summary>Participate in <![CDATA[IEnumerable<string>]]> service request</summary>
public class StringsService : IHandlerBase
{
/// <summary></summary>
[Order(1000)] // After ServiceDescriptor (0)
public void Handle(IQuery<SingletonServiceRequest<string>, IServiceList<string>> query)
{
// Unexpected status
if (query.Response.Status.ExcludeFlags() > EntryStatus.Value) return;
// Get response container
IServiceList<string>? list = query.Response.Value<IServiceList<string>>() ?? query.Response.PassValue<IServiceList<string>>(new ServiceList<string>());
// String to add to service enumeration
string str = "Participated in service request (first line)";
// Append as first result value (Order = long.MinValue)
list.Add(str, new IServiceList.Line(Order: long.MinValue, ToDispose: false));
// String to add to service enumeration
str = "Participated in service request (last line)";
// Append as last result value (Order = long.MaxValue)
list.Add(str, new IServiceList.Line(Order: long.MaxValue, ToDispose: false));
}
}
// </42>
}