Search code examples
asp.net-web-api2odata

How to implement an identical POST like the regular PUT in an odata controller to update an entity


Given the following typical implementation of an ODataController's PUT method, how would I make the exact same method ALSO be available as a POST?

I am developing an OData end-point that will be called from an external system that I have no control over. It appears that that system implements the Update semantics (to tell my system to update an entity) wrongly by sending a POST with a uri key instead of using a PUT.

public async Task<IHttpActionResult> Put([FromODataUri] int key, Product update)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }
    if (key != update.Id)
    {
        return BadRequest();
    }
    db.Entry(update).State = EntityState.Modified;
    try
    {
        await db.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!ProductExists(key))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }
    return Updated(update);
}

My first guess was to annotate the method with [AcceptVerbs("PUT", "POST")] to make the same exact method implementation be available as a POST, but that doesn't work. It's probably that the ODataConventionModelBuilder default setup doesn't know about this...

Ideally I'd like to keep the standards based PUT and the regular POST for inserts, but add a special post that is identical to the put but differs only in the verb.

Thanks


Solution

  • After finding some not so evident documentation on salesforce.com on odata endpoint implementation for External Data Source/External Objects, it became evident to me that salesforce.com tries to call a POST for Update semantics on the external object but also adds the X-HTTP-METHOD set as PATCH.

    So, the solution was to implement the following class:

       public class MethodOverrideHandler : DelegatingHandler
        {
            readonly string[] _methods = { "DELETE", "HEAD", "PUT", "PATCH", "MERGE" };
            const string _header1 = "X-HTTP-Method-Override";
            const string _header2 = "X-HTTP-Method";//salesforce special behavior???
    
            protected override Task<HttpResponseMessage> SendAsync(
                HttpRequestMessage request, CancellationToken cancellationToken)
            {
                // Check for HTTP POST with the X-HTTP-Method-Override header.
                if (request.Method == HttpMethod.Post && request.Headers.Contains(_header1))
                {
                    // Check if the header value is in our methods list.
                    var method = request.Headers.GetValues(_header1).FirstOrDefault();
                    if (_methods.Contains(method, StringComparer.InvariantCultureIgnoreCase))
                    {
                        // Change the request method.
                        request.Method = new HttpMethod(method);
                    }
                }
                else  if (request.Method == HttpMethod.Post && request.Headers.Contains(_header2))
                {
                    // Check if the header value is in our methods list.
                    var method = request.Headers.GetValues(_header2).FirstOrDefault();
                    if (_methods.Contains(method, StringComparer.InvariantCultureIgnoreCase))
                    {
                        // Change the request method.
                        request.Method = new HttpMethod(method);
                    }
                }
                return base.SendAsync(request, cancellationToken);
            }
        }
    

    and register it in WebApiConfig.Register(HttpConfiguration config) as such:

    config.MessageHandlers.Add(new MethodOverrideHandler());
    

    Now, the non-odata compliant POST for salesforce update operations on the External Object will get delegated to the standards compliant odata implementation (in the ODataController) of PUT method I originally posted.

    I hope that this helps someone in the future...