3

I'm new to MVC Web Api but I have some experience working with ServiceStack framework. Some of the web api examples look a lot like RPC with more action methods and less parameters. In fact most examples seem to limit the request parameter to an id. I would like to create some reporting services using request/response because the request gets quite complex with all the reporting criteria.

Here's my simplified request / response types:

public class PendingRequest
{
    public string Id { get; set; }
    public int AccountId { get; set; }
    public DateTime? FromDate { get; set; }
    public DateTime? ToDate { get; set; }
}

public class PendingResponse
{
    public string Id { get; set; }
    public IEnumerable<Pending> Data { get; set; }
}

And my outline reports controller:

public class ReportsController : ApiController
{
    public async Task<PendingResponse> GetPending(PendingRequest request)
    {
        return new PendingResponse
        {
            Id = request.Id,
            // Data = await Repo.GetPending()
        };
    }

    public async Task<ShippedResponse> GetShipped(ShippedRequest request)
    {
        return new ShippedResponse
        {
            Id = request.Id,
            // Data = await Repo.GetShipped()
        };
    }

    public async Task<ProductsResponse> GetProducts(ProductsRequest request)
    {
        return new ProductsResponse
        {
            Id = request.Id,
            // Data = await Repo.GetProducts()
        };
    }
}

And my routing and config for a self-hosting app:

class Program
{
    static void Main()
    {
        var config = new HttpSelfHostConfiguration("http://localhost:8080");

        config.Routes.MapHttpRoute(
            name: "Reports",
            routeTemplate: "api/reports/{action}",
            defaults: new
                {
                    controller = "Reports"
                });

        using (var server = new HttpSelfHostServer(config))
        {
            server.OpenAsync().Wait();
            Console.WriteLine("Press Enter to quit.");
            Console.ReadLine();
        }
    }
}

So with this routing I intend the action to be the name of the report. Note i've not included the request parameter here. Not sure if I should or not.

The problem is actually on the client side. With the HttpClient the GetAsync method does not allow me to include the JSON request object. I see an extension method PostAsJsonAsync but surely this shouldn't be a POST? Without a GetAsJsonAsync or similar then I can't see a way of including a request with a GET?

To be honest I prefer ServiceStack but there appears to be no async support yet. I need to develop some high performance services which are I/O intensive and I want to reduce the blocking as much as possible.

UPDATE:

It seems Web Api will perform the model binding if, rather than using the request body, I include the model's parameters as part of the query string. In order for this to work, the Get method needs to prefix the model type with the [FromUri] attribute, which informs the model binder that the model should be constructed from the request query string. Messy, but works.

[ActionName("Pending")]
public async Task<PendingResponse> GetPending([FromUri] PendingRequest request)
{
    return new PendingResponse
    {
        Id = request.Id,
        // query = ...build from request params
        // Data = await Repo.GetPending(query)
    };
}

And now on the client side I perform the following:

var result = await _httpClient.GetAsync("api/reports/pending?Id=123&AccountId=456");
result.EnsureSuccessStatusCode();
var response = await result.Content.ReadAsAsync<PendingResponse>();

A call to the service results in the GetPending method being called with a copy of the PendingRequest object materialised from the supplied query string parameters.

Neil Dobson
  • 1,189
  • 1
  • 11
  • 23

2 Answers2

3

The fundamental problem here is you are trying to provide a request body to HTTP GET, which is not allowed. Well, you can still go ahead and format a GET with request body and submit it but that is not according to the spec. Strictly speaking, HTTP spec does not forbid GET requests from having a body but the response for a GET request must not change based on the request body, which basically means you cannot use search criteria in the GET request body. You can use the URI path and query string to specify the search criteria and if you want to bind them into a complex type parameter, you will need to use [FromUri] like this: public async Task<PendingResponse> GetPending([FromUri]PendingRequest request).

Badri
  • 18,439
  • 4
  • 58
  • 58
  • This is interesting. If the statement "...but the response for a GET request must not change based on the request body" is true then I can't use GET because a request to /api/reports/pending even without any criteria is likely to yield different results over time. Perhaps then really we are talking making a POST. I should consider this as a creation as in CreatePendingReport(Request request) or something... – Neil Dobson Aug 29 '13 at 23:34
  • Response changing over time is completely different from using request body to send out different responses. May be I was not clear. Check this out - http://stackoverflow.com/questions/978061/http-get-with-request-body. – Badri Aug 30 '13 at 02:07
  • Thanks Badri. The initial answer is correct and i've updated my post with an example client call. Now I just need a good object->query string mapper. – Neil Dobson Aug 30 '13 at 04:39
0

Bit of a guess, but what about just URL encoding the JSON request object and putting it in the query string segment of the GET URI?

Also, you might need [FromUri] attribute on your request method parameter in the action as it is a complex type. I may have missed something, but this article on parameter binding doesn't mention anything about GET being a special case for complex types, hence I think they will always try and read from the POST body.

Josh Gallagher
  • 4,707
  • 2
  • 28
  • 53