You have the right idea, and your approach is valid, but people tend to separate the intent of a request for a specific invoice out from a request for a list of invoices.
I tend to build a service such that my DTOs follow a C-R-U-D-L operations format i.e. Create Read Update Delete List type DTO:
So when I have a ReadInvoiceRequest
rather than a GetInvoiceRequest
I know will always return a single InvoiceDto
object and requires the InvoiceId
parameter:
[Route("/invoices/{InvoiceId}", "GET")]
[Route("/account/{AccountId}/invoices/{InvoiceId}","GET")]
public ReadInvoiceRequest : IReturn<InvoiceDto>
{
public int InvoiceId { get; set; } // InvoiceId is always expected
public int? AccountId { get; set; }
public DateTime? From { get; set; }
}
And the routes specifically for listing, will always return a list of InvoiceDto
:
[Route("/invoices", "GET")]
[Route("/account/{AccountId}/invoices","GET")]
public ListInvoicesRequest : IReturn<List<InvoiceDto>>
{
public int? AccountId { get; set; }
public DateTime? From { get; set; }
}
This prevents ambiguity over how many results would have been returned if an InvoiceId
was specified and your return type was a list of results.
It's also makes it clear to the consumer that the optional parameters in the request DTOs will be treated as filters.
Your action methods in your Service would then be similar to this:
public class MyInvoiceService : Service
{
// CREATE
public int Post(CreateInvoiceRequest request)
{
// Return the InvoiceId of the created record
}
// READ
public InvoiceDto Get(ReadInvoiceRequest request)
{
var invoice = ... // request.InvoiceId; (filter by AccountId & From if set)
return invoice;
}
// UPDATE
public void Post(UpdateInvoiceRequest request)
{
// Handle update
// I don't return anything, only throw exceptions if update fails
// Success is indicated by 200 status
}
// DELETE
public void Delete(DeleteInvoiceRequest request)
{
// Handle delete
// I don't return anything, only throw exceptions if delete fails
// Success is indicated by 200 status
}
// LIST
public List<InvoiceDto> Get(ListInvoicesRequest request)
{
var invoices = ... // (filter by AccountId & From if set)
return invoices;
}
}
From a service consumers point of view, I believe there is less uncertainty about the response it will get from the separated DTOs.