如何在 odata 控制器中实现与常规 PUT 相同的 POST 以更新实体

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

鉴于 ODataController PUT method 的以下典型实现,我如何使完全相同的方法也可用作 POST?

我正在开发一个 OData 端点,它将从我无法控制的外部系统调用。该系统似乎通过发送带有 uri 密钥的 POST 而不是使用 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);
}

我的第一个猜测是用 [AcceptVerbs("PUT", "POST")] 注释该方法,以使与 POST 完全相同的方法实现可用,但这并没有工作。可能是 ODataConventionModelBuilder 默认设置不知道这个...

理想情况下,我想保留基于标准的 PUT 和用于插入的常规 POST,但添加一个与 put 相同但仅在动词上不同的特殊 post。

谢谢

我的方法是在方法中加入更多逻辑,使用 update.Id 检查并查看数据库中是否已存在记录,然后检查数据是否为空。

public async Task<IHttpActionResult> Put([FromODataUri] int key, Product update)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    //might have to get rid of this condition for the sake of new entries
    //if (key != update.Id)
    //{
        //return BadRequest();
    //}

    try
    {
        //not sure what the name of your table is so I'm going to call it ProductTable
        var foo = db.ProductTable.Where(p => p.Id == update.Id).FirstOrDefault();
        if(foo == null)
        {
            db.Entry(update).State = EntityState.Added;
            await db.SaveChangesAsync();
            return StatusCode(HttpStatusCode.Accepted);
        }
        else
        {
            db.Entry(update).State = EntityState.Modified;
            await db.SaveChangesAsync();
            return Updated(update);
        }
    }
    catch (DbUpdateConcurrencyException ex)
    {
        if (!ProductExists(key))
        {
            return NotFound();
        }
        else
       {
            throw new DbUpdateConcurrencyException(ex.Message);
       }
    }
}

编辑 刚刚注意到 ProductExists 方法......我会把它从 catch 块中取出并扔到 try

//for Post, pass in a 0 for key's argument
public async Task<IHttpActionResult> Put([FromODataUri] int key, Product update)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    //might have to get rid of this condition for the sake of new entries
    //if (key != update.Id)
    //{
        //return BadRequest();
    //}

    try
    {
        if (!ProductExists(key))
        {
            db.Entry(update).State = EntityState.Added;
            await db.SaveChangesAsync();
            return StatusCode(HttpStatusCode.Accepted);
        }
        else
        {
            db.Entry(update).State = EntityState.Modified;
            await db.SaveChangesAsync();
            return Updated(update);
        }
    }
    catch (DbUpdateConcurrencyException ex)
    {
        throw new DbUpdateConcurrencyException(ex.Message);
    }
}

在外部数据 Source/External 对象的 odata 端点实现上发现一些不太明显的 documentation on salesforce.com 之后,我发现 salesforce.com 试图调用 POST对于外部对象的更新语义,还添加了 X-HTTP-METHOD 集作为 PATCH。

因此,解决方案是实施以下 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);
        }
    }

并在WebApiConfig.Register(HttpConfiguration config)中这样注册:

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

现在,针对外部对象的 salesforce 更新操作的 non-odata 兼容 POST 将委托给我最初发布的 PUT 方法的标准兼容 odata 实现(在 ODataController 中)。

我希望这对以后的人有所帮助...