0

Я пишу простое приложение «Todo» с использованием ASP.NET WebApi 2 и Entity Framework 6.1.0-alpha1. Моя цель - ограничить доступ, каждый пользователь должен только просматривать/редактировать свои собственные Todos.Реализация проверки подлинности Entity Framework/WebApi 2

Пример:

// 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 на текущий идентификатор пользователя. Однако у меня проблема с обновлением.

Я попытался это:

// 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, но не смог найти рабочий синтаксис.

Как решить эту проблему? Любая лучшая практика для подобных ситуаций?

ответ

2

Когда вы вызываете FindAsync, возвращаемый экземпляр объекта уже привязан к контексту. Таким образом, нет никаких оснований для _db.Entry(todo).State = EntityState.Modified;

Update

Я думаю, что я вижу, что вы пытаетесь сделать здесь. Попробуйте вместо этого:

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, и поскольку контекст еще не отслеживает другой объект с тем же идентификатором, вы больше не должны получать это исключение.

В качестве вторичного примечания, поскольку объект 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); 
} 
+0

Если упущено '_db.Entry (todo)', мне нужно вручную изменить свойства 'оригинала', правильно? Большое преимущество '_db.Entry (todo)' заключается в том, что мне не нужно вручную обновлять свойства. Или я что-то не понимаю? –

+0

Большое спасибо, я попробую это. Кроме того, в отношении вашей второй заметки: метод (и его подпись) был создан подкладкой Visual Studio. Любые идеи, почему он создал отдельный идентификатор? –

+0

Скажите, что ваша сущность имела идентификатор TodoId вместо Id. Вот почему строительные леса создают дополнительный параметр. – danludwig

Смежные вопросы