Преамбула: это немного философский вопрос. Я больше ищу «правильный» способ сделать это, а не «способ» сделать это.
Давайте представим, что у меня есть несколько продуктов и приложение ASP.NET MVC, выполняющее CRUD для этих продуктов: -
mysite.example/products/1
mysite.example/products/1/edit
Я использую шаблон репозитория, поэтому не имеет значения, откуда берутся эти продукты: -
public interface IProductRepository
{
IEnumberable<Product> GetProducts();
....
}
Кроме того, мой репозиторий описывает список пользователей и продукты, для которых они являются менеджерами (многие-многие между пользователями и продуктами). В другом месте приложения Super-Admin выполняет CRUD для пользователей и управляет отношениями между пользователями и продуктами, которыми им разрешено управлять.
Любой может просматривать любой продукт, но только пользователи, которые определены как «администраторы» для определенного продукта, могут вызывать, например, Действие Изменить.
Как должен Я пойду о реализации этого в ASP.NET MVC? Если я что-то пропустил, я не могу использовать встроенный атрибут авторизации ASP.NET, так как сначала мне понадобится отдельная роль для каждого продукта, а во-вторых, я не буду знать, какую роль проверять, пока не укажу извлек мой продукт из хранилища.
Очевидно, что вы можете обобщить этот сценарий для большинства сценариев управления контентом - например, Пользователи могут редактировать только свои собственные сообщения на форуме. Пользователи StackOverflow могут редактировать только свои вопросы - если у них нет 2000 или более представителей ...
Самое простое решение, например, было бы что-то вроде: -
public class ProductsController
{
public ActionResult Edit(int id)
{
Product p = ProductRepository.GetProductById(id);
User u = UserService.GetUser(); // Gets the currently logged in user
if (ProductAdminService.UserIsAdminForProduct(u, p))
{
return View(p);
}
else
{
return RedirectToAction("AccessDenied");
}
}
}
Мои проблемы:
- Часть этого кода необходимо будет повторить - представьте, что есть несколько операций (Update, Delete, SetStock, Order, CreateOffer) в зависимости от отношения User-Products. Вам придется скопировать и вставить несколько раз.
- Это не очень проверяемое - вы должны смоделировать по моему количеству четыре объекта для каждого теста.
- Не похоже, что «работа» контроллера заключается в проверке, разрешено ли пользователю выполнять действие. Я бы предпочел более подключаемое (например, AOP через атрибуты) решение. Однако означает ли это, что вам придется ВЫБРАТЬ продукт дважды (один раз в AuthorizationFilter и снова в Controller)?
- Было бы лучше вернуть 403, если пользователю не разрешено делать этот запрос? Если так, как бы я поступил так?
Вероятно, я буду держать это в курсе, так как сам получаю идеи, но я очень хочу услышать ваши!
Заранее спасибо!
Редактировать
Просто чтобы добавить немного деталей здесь. Проблема, с которой я столкнулся, заключается в том, что я хочу, чтобы бизнес-правило «Только пользователи с разрешениями могли редактировать продукты» содержалось в одном и только одном месте. Я чувствую, что тот же код, который определяет, может ли пользователь получить или POST для действия Edit, также должен отвечать за определение, следует ли отображать ссылку «Edit» в представлениях Index или Details. Может быть, это невозможно / не возможно, но я чувствую, что это должно быть ...
Редактировать 2
Начиная награду за это. Я получил несколько хороших и полезных ответов, но ничего, что я чувствую себя комфортно, «принимая». Имейте в виду, что я ищу хороший чистый метод, чтобы сохранить бизнес-логику, которая определяет, будет ли ссылка «Редактировать» в представлении индекса отображаться в том же месте, которое определяет, будет ли запрос на Продукты / Редактировать / 1 разрешено или нет. Я хотел бы свести загрязнение в моем методе действия к абсолютному минимуму. В идеале я ищу решение на основе атрибутов, но согласен, что это невозможно.