在控制器中重复授权代码的最佳实践
本文关键字:最佳 代码 授权 控制器 | 更新日期: 2023-09-27 17:49:38
我有一个控制器,该控制器具有GetSignatories(), AddMe(), RemoveMe(), AddUser(), RemoveUser()等方法,因此我必须每次验证合同是否存在,用户是否有权访问合同以及所请求的版本是否存在。我想知道我应该把这段代码放在哪里?在服务中还是在控制器的其他方法中提取?我的问题是,我有时返回ununauthorized或NotFound,不知道什么是最好的方法。
这是我的控制器:
public partial class ContractController : Controller
{
private ISession _session;
private IAuthenticationService _authService;
public V1Controller(ISession session, IAuthenticationService authService)
{
_session = session;
_authService = authService;
}
[HttpPost]
[Authorize(Roles = "User")]
[ValidateAntiForgeryToken]
public virtual ActionResult GetSignatories(string contractId, int version)
{
//NOTE Should be extracted
var contract = _session.Single<Contract>(contractId);
if (contract == null) return HttpNotFound();
var user = _authService.LoggedUser();
if (contract.CreatedBy == null || !contract.CreatedBy.Id.HasValue || contract.CreatedBy.Id.Value != user.Id) return new HttpUnauthorizedResult();
if (contract.Versions.Count < version) return HttpNotFound();
/*NOTE END Should be extracted */
return Json(contract.CurrentVersion.Tokens.Select(x => x.User).ToList());
}
[HttpPost]
[Authorize(Roles = "User")]
[ValidateAntiForgeryToken]
public virtual ActionResult AddMe(string contractId, int version)
{
//NOTE Should be extracted
var contract = _session.Single<Contract>(contractId);
if (contract == null) return HttpNotFound();
var user = _authService.LoggedUser();
if (contract.CreatedBy == null || !contract.CreatedBy.Id.HasValue || contract.CreatedBy.Id.Value != user.Id) return new HttpUnauthorizedResult();
if (contract.Versions.Count < version) return HttpNotFound();
/*NOTE END Should be extracted */
return AddUserToContract(contract, new UserSummary(user));
}
[HttpPost]
[Authorize(Roles = "User")]
[ValidateAntiForgeryToken]
public virtual ActionResult RemoveMe(string contractId, int version)
{
//NOTE Should be extracted
var contract = _session.Single<Contract>(contractId);
if (contract == null) return HttpNotFound();
var user = _authService.LoggedUser();
if (contract.CreatedBy == null || !contract.CreatedBy.Id.HasValue || contract.CreatedBy.Id.Value != user.Id) return new HttpUnauthorizedResult();
if (contract.Versions.Count < version) return HttpNotFound();
/*NOTE END Should be extracted */
return RemoveUserFromContract(contract, new UserSummary(user));
}
[HttpPost]
[Authorize(Roles = "User")]
[ValidateAntiForgeryToken]
public virtual ActionResult AddUser(string contractId, int version, UserSummary userSummary)
{
//NOTE Should be extracted
var contract = _session.Single<Contract>(contractId);
if (contract == null) return HttpNotFound();
var user = _authService.LoggedUser();
if (contract.CreatedBy == null || !contract.CreatedBy.Id.HasValue || contract.CreatedBy.Id.Value != user.Id) return new HttpUnauthorizedResult();
if (contract.Versions.Count < version) return HttpNotFound();
/*NOTE END Should be extracted */
return AddUserToContract(contract, userSummary);
}
[HttpPost]
[Authorize(Roles = "User")]
[ValidateAntiForgeryToken]
public virtual ActionResult RemoveUser(string contractId, int version, UserSummary userSummary)
{
//NOTE Should be extracted
var contract = _session.Single<Contract>(contractId);
if (contract == null) return HttpNotFound();
var user = _authService.LoggedUser();
if (contract.CreatedBy == null || !contract.CreatedBy.Id.HasValue || contract.CreatedBy.Id.Value != user.Id) return new HttpUnauthorizedResult();
if (contract.Versions.Count < version) return HttpNotFound();
/*NOTE END Should be extracted */
return RemoveUserFromContract(contract, userSummary);
}
}
对于那些可能正在寻找如何在全局中注册模型绑定器的人:
public static void RegisterModelBinders()
{
var session = (ISession)DependencyResolver.Current.GetService(typeof(ISession));
var authService = (IAuthenticationService)DependencyResolver.Current.GetService(typeof(IAuthenticationService));
System.Web.Mvc.ModelBinders.Binders[typeof (Contract)] = new ContractModelBinder(session, authService);
}
确实有相当重复的代码。重构这段代码的方法有很多,其中之一就是为Contract
模型编写一个自定义模型绑定器:
public class ContractModelBinder : DefaultModelBinder
{
private readonly ISession _session;
private readonly IAuthenticationService _authService;
public ContractModelBinder(ISession session, IAuthenticationService authService)
{
_session = session;
_authService = authService;
}
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
string contractId = null;
int version = 0;
var contractIdValue = bindingContext.ValueProvider.GetValue("contractId");
var versionValue = bindingContext.ValueProvider.GetValue("version");
if (versionValue != null)
{
version = int.Parse(versionValue.AttemptedValue);
}
if (contractIdValue != null)
{
contractId = contractIdValue.AttemptedValue;
}
var contract = _session.Single<Contract>(contractId);
if (contract == null)
{
throw new HttpException(404, "Not found");
}
var user = _authService.LoggedUser();
if (contract.CreatedBy == null ||
!contract.CreatedBy.Id.HasValue ||
contract.CreatedBy.Id.Value != user.Id
)
{
throw new HttpException(401, "Unauthorized");
}
if (contract.Versions.Count < version)
{
throw new HttpException(404, "Not found");
}
return contract;
}
}
一旦你在Application_Start
中用Contract
模型注册了这个模型绑定器,你的控制器就变成了:
public class ContractController : Controller
{
[HttpPost]
[Authorize(Roles = "User")]
[ValidateAntiForgeryToken]
public ActionResult GetSignatories(Contract contract)
{
return Json(contract.CurrentVersion.Tokens.Select(x => x.User).ToList());
}
[HttpPost]
[Authorize(Roles = "User")]
[ValidateAntiForgeryToken]
public ActionResult AddMe(Contract contract)
{
var user = contract.CreatedBy;
return AddUserToContract(contract, new UserSummary(user));
}
[HttpPost]
[Authorize(Roles = "User")]
[ValidateAntiForgeryToken]
public ActionResult RemoveMe(Contract contract)
{
var user = contract.CreatedBy;
return RemoveUserFromContract(contract, new UserSummary(user));
}
[HttpPost]
[Authorize(Roles = "User")]
[ValidateAntiForgeryToken]
public ActionResult AddUser(Contract contract, UserSummary userSummary)
{
return AddUserToContract(contract, userSummary);
}
[HttpPost]
[Authorize(Roles = "User")]
[ValidateAntiForgeryToken]
public ActionResult RemoveUser(Contract contract, UserSummary userSummary)
{
return RemoveUserFromContract(contract, userSummary);
}
}
我们成功地让它节食。
一个可能的解决方案是创建一个IContractService
接口,该接口具有两个方法,一个用于获取合同,另一个用于验证合同:
public IContractService
{
Contract GetContract(int id);
ValidationResult ValidateContract(Contract contract);
}
ValidationResult
可以是一个枚举,只是为了通知方法的调用者如何继续:
public enum ValidationResult
{
Valid,
Unauthorized,
NotFound
}
IContractService
的可能实现:
public class ContractService : IContractService
{
private readonly ISession _session;
private readonly IAuthenticationService _authService;
// Use Dependency Injection for this!
public ContractService(ISession session, IAuthenticationService authService)
{
_session = session;
_authService = authService;
}
public Contract GetContract(int id)
{
var contract = _session.Single<Contract>(contractId);
// hanlde somwhere else whether it's null or not
return contract;
}
public ValidationResult ValidateContract(Contract contract)
{
var user = _authService.LoggedUser();
if (contract.CreatedBy == null || !contract.CreatedBy.Id.HasValue ||
contract.CreatedBy.Id.Value != user.Id)
return ValidationResult.Unauthorized;
if (contract.Versions.Count < version)
return ValidationResult.NotFound;
return ValidationResult.Valid;
}
}
然后在你的控制器中,你仍然可以返回各种HttpNotFound
等到视图:
[HttpPost, Authorize(Roles = "User"), ValidateAntiForgeryToken]
public virtual ActionResult GetSignatories(string contractId, int version)
{
//NOTE Should be extracted
var contract = _contractService.GetContract(contractId);
if (contract == null)
return HttpNotFound();
var result = _contractService.ValidateContract(contract);
if (result == ValidationResult.Unauthorized)
return new HttpUnauthorizedResult();
if (result == ValidationResult.NotFound)
return HttpNotFound();
return Json(contract.CurrentVersion.Tokens.Select(x => x.User).ToList());
}