Search code examples
restodataasp.net-web-apiasp.net-web-api2

Multiple POST methods OData Web API. Should/can it happen?


This question is perhaps an idealogical one...

I have a requirement to create multiple records from an application calling an OData Web API through a service proxy.

At the moment, I'm doing this by invoking the post method multiple times (through a service proxy) and then calling save changes to batch the requests. Eg.

    container.AddToColours(new Colour { Id = 1, Name = "Green" });
    container.AddToColours(new Colour { Id = 2, Name = "Orange" });
    container.SaveChanges(SaveChangesOptions.Batch);

The downside of this is that in my web api controller the post method is called twice and so generates two insert statements.

Now, if in my client did this

    container.AddToColours(new List<Colour>() { new Colour { Id = 1, Name = "Green" }, new Colour { Id = 2, Name = "Orange" }});
    container.SaveChanges(SaveChangesOptions.Batch);

Then only a single insert statement would be created that inserts both records at once.

The questions are;

Is this possible? (I'd probably end up having two POST methods - one for a single colour another to accept a list of colours).

Is this possible through a service proxy?

Does this break every/any rule in the OData/REST book?

Is this ethical?

Is this naughty?

Edit

Having revisited this I've been able to create a custom batch handler as suggested below. I'm hoping that this helps others. It saves all changes made in a batch in a single transaction so if one part of the batch fails then all parts fail. But, importantly, attempts are made to save for all operations in the batch regardless of previous successes or failures so error messages can be returned for operation.

I used the sample here as a basis:

http://aspnet.codeplex.com/SourceControl/latest#Samples/WebApi/OData/v3/ODataEFBatchSample/

But made the following changes:

Changed EntityFrameworkBatchHandler.ExecuteChangeSet method to:

    private async Task ExecuteChangeSet(ChangeSetRequestItem changeSet, IList<ODataBatchResponseItem> responses, CancellationToken cancellation)
    {
        using (var scope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions() { IsolationLevel = IsolationLevel.ReadUncommitted }, TransactionScopeAsyncFlowOption.Enabled))
        {
            responses.Add((ChangeSetResponseItem) await new ChangeSetBatchRequestItem(changeSet).SendRequestAsync(Invoker, cancellation));

            if (responses.All(x => (x as ChangeSetResponseItem).Responses.All(y => y.IsSuccessStatusCode)))
            { 
                scope.Complete();
            }
        }
    }

I believe that this means that it is not tied to EF at all. I removed HttpRequestMessageExtensions.cs and created a new class, ChangeSetBatchRequestItem which is exactly the same as System.Web.OData.Batch.ChangeSetRequestItem except for the following method:

    public override async Task<ODataBatchResponseItem> SendRequestAsync(HttpMessageInvoker invoker, CancellationToken cancellationToken)
    {
        if (invoker == null)
        {
            throw new ArgumentNullException("invoker");
        }

        Dictionary<string, string> contentIdToLocationMapping = new Dictionary<string, string>();
        List<HttpResponseMessage> responses = new List<HttpResponseMessage>();

        try
        {
            foreach (HttpRequestMessage request in Requests)
            {
                responses.Add(await SendMessageAsync(invoker, request, cancellationToken, contentIdToLocationMapping));
            }
        }
        catch
        {
            DisposeResponses(responses);
            throw;
        }

        return new ChangeSetResponseItem(responses);
    }

And that's the lot.


Solution

  • Thanks for the answer, and here my answer to you question.

    1. POST more than one entity in a request body(if it is not a batch request) is not a conventional OData request.
    2. The Post method in the controller being called twice does not mean that the client sends two POST requests. Actually the clients issues a batch request, which contains the 2 POST as its sub-reqeusts. And the batch handler dispatchers each sub-request to the Web API pipeline, then WebAPI composes all the sub-response into one and send it back the requester. For more please refer https://aspnetwebstack.codeplex.com/wikipage?title=Web%20API%20Request%20Batching