在ServiceStack

时间:2018-02-28 12:46:46

标签: servicestack

背景

我正在使用ServiceStack使用第三方WebApi。许多端点根据通用模式对结果进行分页。

示例JSON:

{
    "count": 23,
    "pageSize": 10,
    "pageNumber": 1,
    "_embedded": {
        "people": [
            {
                "id": 1,
                "name": "Jean-Luc Picard"
            },
            {
                "id": 2,
                "name": "William T. Riker"
            },
            [...]
        ]
    }
}

实施

由于每个分页请求和响应DTO都具有公共属性,因此我创建了抽象类来保持这些DTO干燥。

public abstract class PaginatedRequest<TResponseDto, TEmbeddedResponseDto> : IReturn<TResponseDto>
    where TResponseDto : PaginatedResponse<TEmbeddedResponseDto>
{
    public int PageSize { get; set; }
    public int PageNumber { get; set; }

    public List<TEmbeddedResponseDto> Paginate()
    {
        var list = new List<TEmbeddedResponseDto>();

        // Page through results, and add to list until all have been received.

        return list;
    }
}

public abstract class PaginatedResponse<T>
{
    public int Count { get; set; }
    public int PageSize { get; set; }
    public int PageNumber { get; set; }

    public Dictionary<string, List<T>> _embedded { get; set; }
}

这些抽象类分别由请求和响应DTO继承。

public class PersonDto
{
    public int ID { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

public class PeopleDto : PaginatedResponse<PersonDto> { }

[Route("/people/{id}")]
public class GetPerson : IReturn<PersonDto>
{
    public int ID { get; set; }
}

[Route("/people")]
public class GetPeople : PaginatedRequest<PeopleDto, PersonDto> { }

然后可以通过创建请求DTO并调用Paginate函数来调用它。

public List<PersonDto> GetPeople() => new GetPeople().Paginate();

问题

有没有更好的方法来实现此功能,同时保持DRY?这个实现是否有任何特定的缺点,我没有考虑过?我知道Inheritance in DTOs is Considered Harmful,但我认为abstract基类可以避免这些特殊问题。

更新#1

在提交之后,我认为将分页从抽象类中移出可能会减轻预期的问题,即DTO应该是免费实现的。

这可以作为我的类库中的方法实现,也可以作为JsonServiceClient上的扩展方法(未显示)实现。

public List<TEmbeddedResponseDto> GetPaginated<TResponseDto, TEmbeddedResponseDto>(PaginatedRequest<TResponseDto, TEmbeddedResponseDto> request)
    where TResponseDto : PaginatedResponse<TEmbeddedResponseDto>
{
    var list = new List<TEmbeddedResponseDto>();

    // Page through results, and add to list until all have been received.

    return list;
}

public List<PersonDto> GetPeople() => GetPaginated(new GetPeople());

1 个答案:

答案 0 :(得分:1)

您是否看过AutoQuery,为人们创建AutoQuery服务的整个Request DTO是:

[Route("/people")]
public class QueryPeople : QueryDb<Person> {}

AutoQuery将自动为其提供Service实现,以创建一个允许您查询和翻页Person表的服务。

您可以分享:

/people?take=100          # first 100 results
/people?skip=100&take=100 # next 100 results

请参阅Include Total,了解如何始终包含总计或将其包含在?Include=Total的特设请求中。

您还可以使用AutoQuery UI获取AutoQuery服务的即时用户界面,该界面可让您浏览结果并向您显示所使用的AutoQuery网址。

实时AutoQuery查看器示例

如果您想手动执行此操作,请考虑使用相同的QueryResponse DTO all AutoQuery Services use。然后用OrmLite来实现它,看起来像:

public object Any(SearchPeople request)
{
    var q = Db.From<Person>();
    if (request.Age != null) //Query Example
        q.Where(x => x.Age == request.Age.Value);
    if (request.Skip != null)
        q.Skip(request.Skip.Value);
    if (request.Take != null)
        q.Take(request.Take.Value);

    return new QueryResponse<Person>
    {
        Offset = request.Skip ?? 0,
        Results = db.Select<Person>(q),
        Total = db.Count(Q),
    }
}

另请阅读designing message-based APIs,其中建议使用一致的命名法并为合并者查询保留Get*服务,例如通过主键提取并使用Search*Find*使用其他参数过滤结果的服务;