上下文:我被要求维护的AspNetCore控制器包含类似于以下内容的方法:
// Get api/Foo/ABCXXX/item/12345
[HttpGet("{accountId}/item/{itemNumber}")]
public async Task<ActionResult<ItemViewModel>> GetFoo([FromRoute] string accountId, [FromRoute] int itemNumber)
{
if (string.IsNullOrWhiteSpace(accountId))
{
return BadRequest("accountId must be provided");
}
if (itemNumber < 0)
{
return BadRequest("itemNumber must be positive");
}
if (!await CanAccessAccountAsync(accountId))
{
return Forbid();
}
// Returns null if account or item not found
var result = _fooService.GetItem(accountId, itemNumber);
if (result == null)
{
return NotFound();
}
return result;
}
// GET api/Foo/ABCXXX
[HttpGet("{accountId}")]
public async Task<ActionResult<IEnumerable<ItemViewModel>>> GetFoos([FromRoute] string accountId)
{
if (string.IsNullOrWhiteSpace(accountId))
{
return BadRequest("accountId must be provided");
}
if (!await CanAccessAccountAsync(accountId))
{
return Forbid();
}
// Returns null if account not found
var results = _fooService.GetItems(accountId);
if (results == null)
{
return NotFound();
}
return Ok(results);
}
Run Code Online (Sandbox Code Playgroud)
您可能会假设有两种以上的方法,它们的组成部分非常相似。
看着这段代码会使我发痒-似乎有很多重复,但是重复的部分由于包含return语句而无法提取到自己的方法中。
对我来说,这些早期退出是异常而不是返回值,这是有意义的。说,为了论证,我定义了一个包装包裹的异常IActionResult:
internal class ActionResultException : Exception
{
public ActionResultException(IActionResult actionResult)
{
ActionResult = actionResult;
}
public IActionResult ActionResult { get; }
}
Run Code Online (Sandbox Code Playgroud)
然后,我可以提取一些特定的验证:
private void CheckAccountId(string accountId)
{
if (string.IsNullOrWhiteSpace(accountId))
{
throw new ActionResultException(BadRequest("accountId must be provided"));
}
}
private async Task CheckAccountIdAccessAsync(string accountId)
{
if (!await CanAccessAccountAsync(accountId))
{
throw new ActionResultException(Forbid());
}
}
private void CheckItemNumber(int itemNumber)
{
if (itemNumber < 0)
{
throw new ActionResultException(BadRequest("itemNumber must be positive"));
}
}
Run Code Online (Sandbox Code Playgroud)
并重写控制器以使用它们:
// Get api/Foo/ABCXXX/item/12345
[HttpGet("{accountId}/item/{itemNumber}")]
public async Task<IActionResult> GetFoo([FromRoute] string accountId, [FromRoute] int itemNumber)
{
try
{
CheckAccountId(accountId);
CheckItemNumber(itemNumber);
await CheckAccountIdAccessAsync(accountId);
// Returns null if account or item not found
var result = _fooService.GetItem(accountId, itemNumber);
if (result == null)
{
return NotFound();
}
return Ok(result);
}
catch (ActionResultException e)
{
return e.ActionResult;
}
}
// GET api/Foo/ABCXXX
[HttpGet("{accountId}")]
public async Task<IActionResult> GetFoos([FromRoute] string accountId)
{
try
{
CheckAccountId(accountId);
await CheckAccountIdAccessAsync(accountId);
// Returns null if account not found
var results = _fooService.GetItems(accountId);
if (results == null)
{
return NotFound();
}
return Ok(results);
}
catch (ActionResultException e)
{
return e.ActionResult;
}
}
Run Code Online (Sandbox Code Playgroud)
为了使其正常工作,我必须将控制器主体包装在中,try以解开异常的动作结果。
我还必须将返回类型还原为IActionResult,这是我可能不愿意这样做的原因。我能想到的解决该问题的唯一方法就是对异常和catches 进行更具体的说明,但这似乎只是将WET-ness从验证代码转移到了代码catch块。
// Exceptions
internal class AccessDeniedException : Exception { ... }
internal class BadParameterException : Exception { ... }
// Controller
private void CheckAccountId(string accountId)
{
if (string.IsNullOrWhiteSpace(accountId))
{
throw new BadParameterException("accountId must be provided");
}
}
private async Task CheckAccountIdAccessAsync(string accountId)
{
if (!await CanAccessAccountAsync(accountId))
{
throw new AccessDeniedException();
}
}
private void CheckItemNumber(int itemNumber)
{
if (itemNumber < 0)
{
throw new BadParameterException("itemNumber must be positive");
}
}
// Get api/Foo/ABCXXX/item/12345
[HttpGet("{accountId}/item/{itemNumber}")]
public async Task<IActionResult> GetFoo([FromRoute] string accountId, [FromRoute] int itemNumber)
{
try
{
...
}
catch (AccessDeniedException)
{
return Forbid();
}
catch(BadParameterException e)
{
return BadRequest(e.Message);
}
}
// GET api/Foo/ABCXXX
[HttpGet("{accountId}")]
public async Task<IActionResult> GetFoos([FromRoute] string accountId)
{
try
{
...
}
catch (AccessDeniedException)
{
return Forbid();
}
catch (BadParameterException e)
{
return BadRequest(e.Message);
}
}
Run Code Online (Sandbox Code Playgroud)
您可以做一些简单的事情。在这一点上不需要太过分。
首先,检查是否accountId为null或空白是完全多余的。这是路线的一部分;如果没有东西卡在那儿,那您就不会到达这里。
其次,您可以在适当情况下明智地使用路由约束。例如,对于您itemNumber的积极态度:
[HttpGet("{accountId}/item/{itemNumber:min(0)}")]
Run Code Online (Sandbox Code Playgroud)
虽然,老实说,我不确定类似的东西/XXXX/item/-1是否一开始就可以工作。无论如何,指定最小值将覆盖您。
第三,您的CanAccessAccount支票实际上应该通过内置于ASP.NET Core 的基于资源的授权来处理。
总而言之,如果您使用已经可用的功能,那么实际上,您实际上不需要做很多额外的验证,而不必寻找某种“分解”的方法。
| 归档时间: |
|
| 查看次数: |
61 次 |
| 最近记录: |