Добавьте заголовок для операций с использованием данной службы в контракт OpenAPI

#c# #asp.net-core #.net-core #swashbuckle

Вопрос:

А ASP.NET Основное приложение имеет две службы :

 public interface IPrimaryService { }
public class PrimaryService : IPrimaryService { }

public interface ISecondaryService { }
public class SecondaryService : ISecondaryService
{
    public SecondaryService(IPrimaryService service) {}
}
 

Который используется в контроллере :

 [Route("foo")]
public class FooController : ControllerBase
{
    [HttpGet("primary")]
    public IActionResult Primary([FromServices] IPrimaryService primary) => Ok();
    [HttpGet("secondary")]
    public IActionResult Secondary([FromServices] ISecondaryService secondary) => Ok();
}
 

Для работы основной службы требуется определенный заголовок, затем в контракте Open API должна быть указана эта информация.

Я пытаюсь написать пользовательский IOperationFilter интерфейс для автоматического определения всех операций, которые нуждаются IPrimaryService в этом, и добавить определенный заголовок в контракт Open API :

 public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();
    services.AddTransient<IPrimaryService, PrimaryService>();
    services.AddTransient<ISecondaryService, SecondaryService>();
    services.AddSwaggerGen(c =>
    {
        c.SwaggerDoc("v1", new OpenApiInfo { Title = "WebApplication1", Version = "v1" });
        c.OperationFilter<AddPrimaryHeadersOperationFilter>();
    });
}
 

Это моя попытка :

 public class AddPrimaryHeadersOperationFilter : IOperationFilter
{
    IServiceProvider _provider;
    public AddPrimaryHeadersOperationFilter(IServiceProvider provider)
    {
        _provider = provider;
    }

    public void Apply(OpenApiOperation operation, OperationFilterContext context)
    {
        if (NeedPrimaryService(context.MethodInfo))
        {
            if (operation.Parameters == null)
                operation.Parameters = new List<OpenApiParameter>();
            operation.Parameters.Add(new OpenApiParameter {
                Name = "Primary-Id",
                In = ParameterLocation.Header,
                Required = false,
                Schema = new OpenApiSchema { Type = "int" }
            });
        }
    }

    private bool NeedPrimaryService(MethodInfo action)
    {
        foreach (var prm in action.GetParameters())
        {
            if (NeedPrimaryService(prm.ParameterType))
            {
                return true;
            }
        }
        return false;
    }

    private bool NeedPrimaryService(Type service)
    {
        if (service == typeof(IPrimaryService))
        {
            return true;
        }
        //Try to get the implementation TYPE of the service
        object implementation = _provider... 
        return false;
    }
}
 

Это работает для обнаружения действия, в которое IPrimaryService вводится непосредственно, но это не работает, если IPrimaryService вводится через другую службу (в примере ISecondaryService ).

Наивно я думаю, что мне нужно пройти через дерево зависимостей, но я не знаю, как это сделать.

Любая идея приветствуется.

PS : Область действия PrimaryService ограничена, потому что ей нужен заголовок запроса.

PS : Я знаю, что это не определяет, получена ли услуга заводским методом, но в моем случае это приемлемо.

PS : Это также контроллер, но я удалил эту часть, чтобы сократить вопрос. Idem, чтобы проверить, имеет ли параметр действия атрибут FromServices .

Ответ №1:

На private bool NeedPrimaryService(Type service) , вы можете использовать что-то вроде этого.

 var constructorInfos = service.GetConstructors(BindingFlags.Public);
foreach (var item in constructorInfos)
{
    var paramsInfo = item.GetParameters();
    foreach (var singleParam in paramsInfo)
    {
        if (singleParam.ParameterType == typeof(IPrimaryService))
        {
            return true; // or do some other useful things...
        }
    }
}
 

Я просто слепо набираю их, чтобы описать идею… оптимизируйте, чтобы соответствовать вашему варианту использования, пожалуйста 😀

Обновить

Позвольте мне сделать это немного более ясным… в своем private bool NeedPrimaryService(Type service) методе вы оцениваете все параметры от того, что было введено в конечную точку действия (через цикл от NeedPrimaryService ).

итак… это должно обнаружить службу:

 // This method is responsible for evaluating all params for each Action endpoint
private bool NeedPrimaryService(Type service)
    {
        if (service == typeof(ISecondaryService))
        {
            return true;
        }
        // I know this only detect one level depth of ISecondaryService, write a recursive somewhere and use it as needed
        if(service.IsInterface)
        {
                var properties = test.GetProperties();
                foreach (var item in properties)
                {
                    if(item.GetType() == typeof(ISecondaryService)
                    {
                        return true;
                    }
                }
        }
        else // Check service.IsClass make sure the rest params would be struct or primitive data... but feel free with those type checking later, I'm aiming just express the idea
        {
                var constructorInfos = service.GetConstructors(BindingFlags.Public);
                foreach (var item in constructorInfos)
                {
                    var paramsInfo = item.GetParameters();
                    foreach (var singleParam in paramsInfo)
                    {
                        if (singleParam.ParameterType == typeof(ISecondaryService))
                        {
                            return true; // or do some other useful things...
                        }
                    }
                }
        }
        return false;
    }
 

Комментарии:

1. Это не работает, если service это ISecondaryService связано с тем, что в интерфейсе нет конструктора. Проблема заключается в том, чтобы извлечь реализацию службы из введенного интерфейса.

2. @vernou Я обновил ответ, чтобы немного прояснить решение, сделайте рекурсивную оценку настолько глубокой, насколько вы хотите… пришло время разбить эту принятую кнопку, чувак