Когда интерфейс разрешается как скалярный без какого-либо ключа или имени, возвращается реализация, зарегистрированная последней. Когда приложение содержит множество проектов, каждый из которых имеет свой собственный модуль регистрации, неочевидно, когда появляется вторая реализация и «затмевает» исходную реализацию.
Недавно мы перенесли наше монолитное приложение из MEF в Autofac, и нам очень не хватает функции безопасности MEF, когда в этом сценарии во время выполнения возникает исключение несовпадения мощности.
Желаемое решение
Какое-то рода промежуточного программного обеспечения конвейера Autofac.
Мы попробовали следующее (сырая версия, на данный момент никаких попыток оптимизировать производительность не предпринимается):
Код: Выделить всё
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Autofac;
using Autofac.Core;
using Autofac.Core.Registration;
using Autofac.Core.Resolving.Pipeline;
using Autofac.Features.Metadata;
using Autofac.Features.OwnedInstances;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace xyz.DependencyInjection;
internal sealed class StrictScalarCardinalityEnforcer : IServiceMiddlewareSource
{
private ILogger m_logger;
private bool? m_disabled;
private bool m_internalDependenciesResolved;
private static readonly string[] s_whitelistedServices =
[
"Microsoft.AspNetCore.Hosting.Server.IServer",
"Microsoft.Extensions.Hosting.IHostedService",
"Microsoft.AspNetCore.Mvc.ApiExplorer.IApiDescriptionProvider"
];
public void ProvideMiddleware(Service service, IComponentRegistryServices availableServices, IResolvePipelineBuilder pipelineBuilder)
{
if (service is not IServiceWithType serviceWithType ||
Array.IndexOf(s_whitelistedServices, serviceWithType.ServiceType.FullName) >= 0)
{
return;
}
pipelineBuilder.Use(PipelinePhase.ResolveRequestStart, (ctx, next) =>
{
if (!m_internalDependenciesResolved)
{
m_internalDependenciesResolved = true;
m_disabled = ctx.ResolveOptional()?.Value.DisableStrictScalarCardinality;
if (m_disabled != true)
{
m_logger = ctx.ResolveOptional()?.CreateLogger();
}
}
if (m_disabled == true)
{
next(ctx);
return;
}
var serviceType = ((IServiceWithType)ctx.Service).ServiceType;
if (m_logger?.IsEnabled(LogLevel.Debug) == true)
{
m_logger?.LogDebug("--> {service}", GetTypeName(serviceType));
}
if (IsPartOfCollectionResolution(ctx, serviceType))
{
next(ctx);
return;
}
var moreRegistrations = YieldRegistrations(ctx, ctx.Service).Skip(1);
if (moreRegistrations.Any())
{
var registrations = YieldRegistrations(ctx, ctx.Service).ToList();
var implementationNames = string.Join(", ", registrations.Select(r => GetImplementationName(r.Registration)));
var serviceName = GetTypeName(serviceType);
var msg = $"Service {serviceName} has {registrations.Count} implementations registered " +
$"({implementationNames}), but it is being resolved as a single instance. If a single instance " +
$"is needed - use keyed services. If multiple instances are needed - use collection injection.";
throw new DependencyResolutionException(msg);
}
next(ctx);
});
}
private string GetImplementationName(IComponentRegistration registration)
{
if (registration is ComponentRegistration componentRegistration)
{
registration = componentRegistration.Target;
}
return GetTypeName(registration.Activator.LimitType);
}
private string GetTypeName(Type type)
{
if (type.IsGenericType || type.IsArray)
{
return GetTypeName(type, new StringBuilder(100)).ToString();
}
return type.FullName;
}
private StringBuilder GetTypeName(Type type, StringBuilder sb)
{
if (TryUnwrapSpecialType(type, out var unwrapped))
{
sb.Append(type.Name);
sb.Length -= 2;
GetTypeName(unwrapped, sb.Append("");
}
else if (type.IsGenericType)
{
if (!type.Namespace.StartsWith("System.Collections"))
{
sb.Append(type.Namespace).Append('.');
}
sb.Append(type.Name);
while (sb[^1] != '`')
{
--sb.Length;
}
--sb.Length;
string delim = "");
}
else if (type.IsArray)
{
GetTypeName(type.GetElementType(), sb);
sb.Append("[]");
}
else
{
sb.Append(type.FullName);
}
return sb;
}
private static IEnumerable YieldRegistrations(ResolveRequestContext ctx, Service service) =>
ctx.ComponentRegistry.ServiceRegistrationsFor(service);
private static bool TryUnwrapSpecialType(Type type, out Type unwrapped)
{
if (type.IsGenericType)
{
var def = type.GetGenericTypeDefinition();
if (def == typeof(Lazy) ||
def == typeof(Func) ||
def == typeof(Meta) ||
def == typeof(Owned))
{
unwrapped = type.GetGenericArguments()[0];
return true;
}
}
unwrapped = type;
return false;
}
private bool IsPartOfCollectionResolution(ResolveRequestContext ctx, Type type) =>
ctx.Operation.InitiatingRequest is ResolveRequest rr && IsElementTypeOfCollectionService(rr.Service, type) ||
ctx.Operation.InProgressRequests.Any(rrCtx => !ReferenceEquals(rrCtx, ctx) && IsElementTypeOfCollectionService(rrCtx.Service, type));
private bool IsElementTypeOfCollectionService(Service service, Type type)
{
if (service is not IServiceWithType serviceWithType || serviceWithType.ServiceType == type)
{
return false;
}
if (m_logger?.IsEnabled(LogLevel.Debug) == true)
{
m_logger?.LogDebug("Is {service} a collection of {type} ?", GetTypeName(serviceWithType.ServiceType), GetTypeName(type));
}
return serviceWithType.ServiceType.IsInjectableCollectionType(out var elementType) &&
(type == elementType || TryUnwrapSpecialType(elementType, out var unwrapped) && unwrapped == type);
}
}
Что мы сделали
Как видите, мы предприняли попытку реализовать это сами. Но у нас есть проблема со сценарием IEnumerable. Каждый из элементов Lazy уже привязан к определенной реализации. Но при обращении к свойству Lazy.Value — мы теряемся. Ссылки на исходный запрос разрешения нет.
Когда я запускаю модульный тест, я получаю следующий вывод журнала:
Код: Выделить всё
--> TestModels.StrictScalarCardinality.DeviceWithCollectionOfLazy
--> IEnumerable
Is TestModels.StrictScalarCardinality.DeviceWithCollectionOfLazy a collection of IEnumerable ?
--> Lazy
Is TestModels.StrictScalarCardinality.DeviceWithCollectionOfLazy a collection of Lazy ?
Is IEnumerable a collection of Lazy ?
--> Autofac.IComponentContext
Is TestModels.StrictScalarCardinality.DeviceWithCollectionOfLazy a collection of Autofac.IComponentContext ?
Is Lazy a collection of Autofac.IComponentContext ?
Is IEnumerable a collection of Autofac.IComponentContext ?
Is TestModels.StrictScalarCardinality.DeviceWithCollectionOfLazy a collection of Autofac.IComponentContext ?
--> Lazy
Is TestModels.StrictScalarCardinality.DeviceWithCollectionOfLazy a collection of Lazy ?
Is IEnumerable a collection of Lazy ?
--> Autofac.IComponentContext
Is TestModels.StrictScalarCardinality.DeviceWithCollectionOfLazy a collection of Autofac.IComponentContext ?
Is Lazy a collection of Autofac.IComponentContext ?
Is IEnumerable a collection of Autofac.IComponentContext ?
Is TestModels.StrictScalarCardinality.DeviceWithCollectionOfLazy a collection of Autofac.IComponentContext ?
--> Lazy
Is TestModels.StrictScalarCardinality.DeviceWithCollectionOfLazy a collection of Lazy ?
Is IEnumerable a collection of Lazy ?
--> Autofac.IComponentContext
Is TestModels.StrictScalarCardinality.DeviceWithCollectionOfLazy a collection of Autofac.IComponentContext ?
Is Lazy a collection of Autofac.IComponentContext ?
Is IEnumerable a collection of Autofac.IComponentContext ?
Is TestModels.StrictScalarCardinality.DeviceWithCollectionOfLazy a collection of Autofac.IComponentContext ?
--> TestModels.StrictScalarCardinality.IDeviceState
Однако при разрешении оно спотыкается. TestModels.StrictScalarCardinality.IDeviceState - в этот момент теряется вся связь с внедрением коллекции:

Я открыл соответствующий запрос функции, но он закрыт как WontFix и хотя причины имеют смысл, мне все равно интересно чтобы узнать, возможно ли это реализовать.
Подробнее здесь: https://stackoverflow.com/questions/798 ... t-a-la-mef
Мобильная версия