Как создать пользовательский фильтр результатов для добавления заголовка пагинации в ответ от контроллера webapi - PullRequest
1 голос
/ 05 июня 2019

У меня есть следующий сценарий:

Я хочу создать пользовательский фильтр результатов, который позволяет мне добавлять заголовок разбиения на страницы в ответ, используя дочерний класс ResultFilterAttribute .Чтобы создать метаданные пагинации, используемые в заголовке, я создал вспомогательный сервис пагинации, который вводится в класс контроллера и нуждается в объекте PagingModel для генерации метаданных.

Чтобы добавить метаданные разбиения на страницы к ответу в фильтре, мне понадобится доступ как к pagingMetadata, так и к фактическому значению (entity, dto или whataver), которое я хочу вернуть из контроллера.Чтобы можно было передать оба объекта в фильтр результатов, я использовал кортеж.

Дело в том, что я хочу сделать этот фильтр своего рода универсальным, и я пытаюсь привести фактическое значение (сущность,dto ...) поступающий от контроллера к объекту , и это вызывает у меня исключение , говорящее, что приведение невозможно,

Как я мог выполнить приведение?или, может быть, я должен использовать другой подход?

Я пытался использовать paginationHelperService непосредственно в фильтре результатов вместо того, чтобы использовать его в контроллере, но невозможно передать экземпляр службы из контроллера, так какклассы атрибутов не позволяют это.

Я также пытался использовать дженерики, думаю, это был бы самый простой способ добиться кастинга.Если бы я мог просто сделать атрибут универсальным, я мог бы привести объект к его фактическому типу, но, к сожалению, это невозможно, поскольку это класс атрибута.


// Controller
[HttpGet]
[AddPaginationHeader]
public async Task<IActionResult> Get([FromQuery]PagingModel pagingModel, 
    [FromHeader(Name = "Accept")]string mediaType) {
    var pagedCollection = repository.GetPage(pagingModel);
    PaginationMetadata paginationMetadata = paginationHelperService.GetPagingMetadata(pagingModel);
    if (mediaType == "mycustommediatype") {
        var shapedCollection = ShapeCollectionOfData(pagedCollection);
        return Ok((shapedCollection, pagingModel));
    } else {
        return Ok((pagedCollection, pagingModel));
    }
}

// Custom Result Filter
public override void OnResultExecuting(ResultExecutingContext context) {
    var result = context.Result as ObjectResult;
    if (result?.Value != null && result?.StatusCode >= 200 &&
        result?.StatusCode < 300) {
        (object value, PaginationMetadata paginationMetadata) = ((object, PaginationMetadata))result.Value; // Casting
        string paginationMetadataString = (context.HttpContext.Request.Headers["Accept"] == "mycustommediatype")
            ? JsonConvert.SerializeObject(paginationMetadata.FullMetadata)
            : JsonConvert.SerializeObject(pagingMetadata.GenericMetadata);
        context.HttpContext.Response.Headers.Add("X-Pagination", paging);
        context.Result.Value = value;
    }
}

1 Ответ

2 голосов
/ 05 июня 2019

Я пытался использовать paginationHelperService непосредственно в фильтре результатов вместо его использования в контроллере, но невозможно передать экземпляр службы из контроллера, так как классы атрибутов не позволяютit.

Хотя вы не можете внедрить службу в Attribute, вы можете использовать атрибут ServiceFilter(typeof(Your_Filter_Type)), чтобы включить любой фильтр, чтобы вы моглиВнедрить услуги, как вам нравится.

Например, создайте фильтр результатов AddPaginationHeader (который не является Attribute):

public class AddPaginationHeader : IResultFilter
{
    private readonly IRepository repository;

    // inject services
    public AddPaginationHeader(IRepository repository, ... other services)
    {
        this.repository = repository;
    }

    public void IResultFilter.OnResultExecuting(ResultExecutingContext context)
    {
        ...
    }

    public void IResultFilter.OnResultExecuted(ResultExecutedContext context) { ... }

}

И не забудьте зарегистрировать этот сервис в Startup.cs:

services.AddScoped<AddPaginationHeader>();

Наконец, вы можете включить этот фильтр с помощью [ServiceFilterAttribute]:

[HttpGet]
<b>[ServiceFilter(typeof(AddPaginationHeader))]</b>
public async Task Get([FromQuery]PagingModel pagingModel, [FromHeader(Name = "Accept")]string mediaType) 
{
    ....
}

Я также пытался использовать дженерики, я думаю, что это будет самый простойспособ добиться кастинга.Если бы я мог сделать атрибут универсальным, я мог бы привести объект к его фактическому типу, но, к сожалению, это невозможно, поскольку это класс атрибута .

Тот же трюк может быть выполнен и для универсальных типов.,Например, измените указанный выше фильтр AddPaginationHeader на class AddPaginationHeader<TResult> : IResultFilter, и вы можете включить этот фильтр следующим образом:

[ServiceFilter(typeof(AddPaginationHeader<(object,PaginationMetadata)>))]

Вы можете расширить универсальный TResult, как хотите.Единственная хитрость - добавить фильтр по ServiceFilter.

...