#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 Я обновил ответ, чтобы немного прояснить решение, сделайте рекурсивную оценку настолько глубокой, насколько вы хотите… пришло время разбить эту принятую кнопку, чувак