Настройте Autofac для реализации строгого соблюдения скалярной мощности в стиле MEF.C#

Место общения программистов C#
Ответить
Anonymous
 Настройте Autofac для реализации строгого соблюдения скалярной мощности в стиле MEF.

Сообщение Anonymous »

Предположим, у нас есть интерфейс ISomeFeature, и у него есть две реализации, зарегистрированные в области одного и того же приложения — SomeFeatureX и SomeFeatureY.
Когда интерфейс разрешается как скалярный без какого-либо ключа или имени, возвращается реализация, зарегистрированная последней. Когда приложение содержит множество проектов, каждый из которых имеет свой собственный модуль регистрации, неочевидно, когда появляется вторая реализация и «затмевает» исходную реализацию.
Недавно мы перенесли наше монолитное приложение из 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.Value код считает, что это нарушение скалярной мощности, что в данном случае неверно. Непонятно, как это решить.
Что мы сделали
Как видите, мы предприняли попытку реализовать это сами. Но у нас есть проблема со сценарием 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
Поэтому Lazy проходит проверку, поскольку промежуточное программное обеспечение способно распознать, что оно разрешено как часть IEnumerable – нет проблем.
Однако при разрешении оно спотыкается. TestModels.StrictScalarCardinality.IDeviceState - в этот момент теряется вся связь с внедрением коллекции:
Изображение

Я открыл соответствующий запрос функции, но он закрыт как WontFix и хотя причины имеют смысл, мне все равно интересно чтобы узнать, возможно ли это реализовать.

Подробнее здесь: https://stackoverflow.com/questions/798 ... t-a-la-mef
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «C#»