实体框架/ WebApi 2权限检查实现

时间:2014-01-24 23:55:23

标签: c# entity-framework asp.net-web-api entity-framework-6 asp.net-web-api2

我正在使用ASP.NET WebApi 2和Entity Framework 6.1.0-alpha1编写一个简单的“Todo App”。我的目标是限制访问,每个用户只能查看/编辑自己的待办事项。

示例:

    // GET api/Todo/5
    [ResponseType(typeof(Todo))]
    public async Task<IHttpActionResult> GetTodo(int id)
    {
        var todo = await _db.Todos.FindAsync(id);

        if (todo == null)
        {
            return NotFound();
        }

        if (todo.CreatorId != _currentUser.Id)
        {
            return StatusCode(HttpStatusCode.Forbidden); 
        }

        return Ok(todo);
    }

没关系。为删除添加了类似的检查,并在创建时将CreatorId设置为当前用户的ID。但是,我有更新问题。

我试过了:

    // PUT api/Todo/5
    public async Task<IHttpActionResult> PutTodo(int id, Todo todo)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        if (id != todo.Id)
        {
            return BadRequest();
        }


        // --- No exception if I remove this block - BEGIN ---
        var original = await _db.Todos.FindAsync(id);

        if (original.CreatorId != _currentUser.Id || original.CreatorId != todo.CreatorId)
        {
            return StatusCode(HttpStatusCode.Forbidden);
        }
        // --- No exception if I remove this block - END ---

        _db.Entry(todo).State = EntityState.Modified; // Exception thrown here

        try
        {
            await _db.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!TodoExists(id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }

        return StatusCode(HttpStatusCode.NoContent);
    }

但是,它会在标记的行上抛出System.InvalidOperationException

  

System.InvalidOperationException

     

附加“ModernWeb.Domain.Models.Todo”类型的实体失败   因为同一类型的另一个实体已经具有相同的主要实体   核心价值。使用“附加”方法或设置时可能会发生这种情况   如果有任何实体,则实体的状态为“未更改”或“已修改”   图表具有冲突的键值。这可能是因为一些   实体是新的,尚未收到数据库生成的密钥   值。在这种情况下,使用“添加”方法或“已添加”实体状态   跟踪图形,然后将非新实体的状态设置为   “视情况而定”或“修改”。

如果我使用FindByAsync()删除块,它将不会抛出异常。

我也尝试使用_db.Entry(todo).OriginalValue,但找不到有效的语法。

我该如何克服这个问题?这种情况的最佳做法是什么?

1 个答案:

答案 0 :(得分:2)

当您调用FindAsync时,返回的实体实例已附加到上下文。所以没有理由_db.Entry(todo).State = EntityState.Modified;

<强>更新

我想我知道你在这里想做什么。试试这个:

var original = await _db.Todos.AsNoTracking()
    .SingleOrDefaultAsync(x => x.Id == id);

if (original.CreatorId != _currentUser.Id || original.CreatorId != todo.CreatorId)
{
    return StatusCode(HttpStatusCode.Forbidden);
}
// --- No exception if I remove this block - END ---

_db.Entry(todo).State = EntityState.Modified;

当您调用.AsNoTracking().SingleOrDefaultAsync而不是FindAsync时,返回的original实体将不会附加到上下文。然后,您可以将传递给控制器​​操作的那个设置为Modified,并且由于上下文尚未跟踪具有相同ID的其他实体,因此您不应再获得该异常。

作为次要说明,由于传入您的参数的Todo实体已经具有Id属性,因此不应该将其作为控制器操作中的单独参数传递。你应该能够做到这一点:

public async Task<IHttpActionResult> PutTodo(Todo todo)
{
    if (!ModelState.IsValid || todo == null)
    {
        return BadRequest(ModelState);
    }

    var original = await _db.Todos.AsNoTracking()
        .SingleOrDefaultAsync(x => x.Id == todo.Id);

    if (original == null) return NotFound();

    if (original.CreatorId != _currentUser.Id || original.CreatorId != todo.CreatorId)
    {
        return StatusCode(HttpStatusCode.Forbidden);
    }

    _db.Entry(todo).State = EntityState.Modified; // Exception thrown here

    try
    {
        await _db.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!TodoExists(todo.Id))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }

    return StatusCode(HttpStatusCode.NoContent);
}