在控制器中重复授权代码的最佳实践

时间:2011-04-09 15:38:54

标签: c# asp.net-mvc-3

我有一个控制器,它有方法GetSignatories(),AddMe(),RemoveMe(),AddUser(),RemoveUser()以及更多内容我必须验证每次合同是否存在,如果用户如果所请求的版本存在,则可以访问合同。我想知道我应该把这段代码放在哪里?在服务中还是在我的控制器的其他方法中提取?我的问题是我soometime返回Unathorized或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);
    }

2 个答案:

答案 0 :(得分:2)

确实你有相当重复的代码。有很多方法可以重构此代码,其中一种方法包括为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;
    }
}

Contract中使用Application_Start模型注册此模型活页夹后,您的控制器就会变为:

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);
    }
}

我们成功put it on a diet

答案 1 :(得分:2)

一种可能的解决方案是创建一个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());
}