为 Web 实现 DRY 原则的设计模式 API
Design pattern to implement DRY principle for Web API
我的控制器中有以下两种操作方法。两者都采用相同的参数并对模型进行相同的验证。它仅在调用服务方法的一行上有所不同。
有没有更好的方法来重构这段代码?
[HttpPost]
public async Task<IActionResult> Search([FromBody]AggregateSearchCriteria criteria)
{
if (criteria == null || !criteria.Aggregates.Any())
{
return BadRequest();
}
var providers = Request.Headers["providers"];
if (providers.Equals(StringValues.Empty))
return BadRequest();
criteria.Providers = providers.ToString().Split(',').ToList();
ModelState.Clear();
TryValidateModel(criteria);
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var result = await _searchService.Search(criteria);
return Ok(result);
}
[HttpPost("rulebreak")]
public async Task<IActionResult> SearchRuleBreak([FromBody]AggregateSearchCriteria criteria)
{
if (criteria == null || !criteria.Aggregates.Any())
{
return BadRequest();
}
var providers = Request.Headers["providers"];
if (providers.Equals(StringValues.Empty))
return BadRequest();
criteria.Providers = providers.ToString().Split(',').ToList();
ModelState.Clear();
TryValidateModel(criteria);
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var result = await _searchService.SearchRuleBreak(criteria);
return Ok(result);
}
您可以为模型 AggregateSearchCriteria 实现 IValidatableObject 并将所有验证逻辑移入其中。对于提供者,您可以将其添加到您的模型中并编写一个自定义数据绑定器,它将绑定来自 headers 的值,您也可以编写一个逗号数组绑定器,它将您的值拆分为一个数组。
public class AggregateSearchCriteria : IValidatableObject
{
[FromHeader]
public IList<string> Providers { get; set; } = new List<string>();
public IList<string> Aggregates { get; set; } = new List<string>();
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var result = new List<ValidationResult>();
if (!Providers.Any())
{
result.Add(new ValidationResult("No Providers", new[] { nameof(AggregateSearchCriteria.Providers) }));
}
if (!Aggregates.Any())
{
result.Add(new ValidationResult("No Aggregates", new[] { nameof(AggregateSearchCriteria.Aggregates) }));
}
return result;
}
}
[HttpPost]
public async Task<IActionResult> Search([FromBody]AggregateSearchCriteria criteria)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var result = await _searchService.Search(criteria);
return Ok(result);
}
这样的事情可能是一个开始。
[HttpPost]
public async Task<IActionResult> Search([FromBody]AggregateSearchCriteria criteria)
{
return await Common(criteria, c => _searchService.Search(c));
}
public async Task<IActionResult> SearchRuleBreak([FromBody]AggregateSearchCriteria criteria)
{
return await Common(criteria, c => _searchService.SearchRuleBreak(c));
}
private async Task<IActionResult> Common(AggregateSearchCriteria criteria, Func<List<string>, Task<???>> action)
{
if (criteria == null || !criteria.Aggregates.Any())
{
return BadRequest();
}
var providers = Request.Headers["providers"];
if (providers.Equals(StringValues.Empty))
return BadRequest();
criteria.Providers = providers.ToString().Split(',').ToList();
ModelState.Clear();
TryValidateModel(criteria);
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var result = await action.Invoke(criteria);
return Ok(result);
}
模板模式是解决这种情况的方法。虽然与验证无关。而且您还必须处理控制器的依赖性。请注意,如果不进行一些更改,下面的代码将不会自动运行。
public abstract class BaseSearch
{
public Task<IActionResult> Apply(AggregateSearchCriteria criteria)
{
if (criteria == null || !criteria.Aggregates.Any())
{
return BadRequest();
}
var providers = Request.Headers["providers"];
if (providers.Equals(StringValues.Empty))
return BadRequest();
criteria.Providers = providers.ToString().Split(',').ToList();
ModelState.Clear();
TryValidateModel(criteria);
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var result = await ServiceCall(criteria);
return Ok(result);
}
protected abstract async IActionResult ServiceCall(AggregateSearchCriteria criteria);
}
public class Search : BaseSearch
{
protected async Task<IActionResult> ServiceCall(AggregateSearchCriteria criteria)
{
return await _searchService.Search(criteria);
}
}
public class SearchRuleBreak : BaseSearch
{
protected async Task<IActionResult> ServiceCall(AggregateSearchCriteria criteria)
{
return await _searchService.SearchRuleBreak(criteria);
}
}
然后在调用时:
[HttpPost]
public async Task<IActionResult> Search([FromBody]AggregateSearchCriteria criteria)
{
return await new Search().Apply(criteria);
}
[HttpPost("rulebreak")]
public async Task<IActionResult> SearchRuleBreak([FromBody]AggregateSearchCriteria criteria)
{
return await new SearchRuleBreak().Apply(criteria);
}
注意: 随着当今语言的功能越来越强大,"send function as parameter" 方法也是一种有效的方法,正如@stuartd 所建议的那样。
我的控制器中有以下两种操作方法。两者都采用相同的参数并对模型进行相同的验证。它仅在调用服务方法的一行上有所不同。
有没有更好的方法来重构这段代码?
[HttpPost]
public async Task<IActionResult> Search([FromBody]AggregateSearchCriteria criteria)
{
if (criteria == null || !criteria.Aggregates.Any())
{
return BadRequest();
}
var providers = Request.Headers["providers"];
if (providers.Equals(StringValues.Empty))
return BadRequest();
criteria.Providers = providers.ToString().Split(',').ToList();
ModelState.Clear();
TryValidateModel(criteria);
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var result = await _searchService.Search(criteria);
return Ok(result);
}
[HttpPost("rulebreak")]
public async Task<IActionResult> SearchRuleBreak([FromBody]AggregateSearchCriteria criteria)
{
if (criteria == null || !criteria.Aggregates.Any())
{
return BadRequest();
}
var providers = Request.Headers["providers"];
if (providers.Equals(StringValues.Empty))
return BadRequest();
criteria.Providers = providers.ToString().Split(',').ToList();
ModelState.Clear();
TryValidateModel(criteria);
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var result = await _searchService.SearchRuleBreak(criteria);
return Ok(result);
}
您可以为模型 AggregateSearchCriteria 实现 IValidatableObject 并将所有验证逻辑移入其中。对于提供者,您可以将其添加到您的模型中并编写一个自定义数据绑定器,它将绑定来自 headers 的值,您也可以编写一个逗号数组绑定器,它将您的值拆分为一个数组。
public class AggregateSearchCriteria : IValidatableObject
{
[FromHeader]
public IList<string> Providers { get; set; } = new List<string>();
public IList<string> Aggregates { get; set; } = new List<string>();
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var result = new List<ValidationResult>();
if (!Providers.Any())
{
result.Add(new ValidationResult("No Providers", new[] { nameof(AggregateSearchCriteria.Providers) }));
}
if (!Aggregates.Any())
{
result.Add(new ValidationResult("No Aggregates", new[] { nameof(AggregateSearchCriteria.Aggregates) }));
}
return result;
}
}
[HttpPost]
public async Task<IActionResult> Search([FromBody]AggregateSearchCriteria criteria)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var result = await _searchService.Search(criteria);
return Ok(result);
}
这样的事情可能是一个开始。
[HttpPost]
public async Task<IActionResult> Search([FromBody]AggregateSearchCriteria criteria)
{
return await Common(criteria, c => _searchService.Search(c));
}
public async Task<IActionResult> SearchRuleBreak([FromBody]AggregateSearchCriteria criteria)
{
return await Common(criteria, c => _searchService.SearchRuleBreak(c));
}
private async Task<IActionResult> Common(AggregateSearchCriteria criteria, Func<List<string>, Task<???>> action)
{
if (criteria == null || !criteria.Aggregates.Any())
{
return BadRequest();
}
var providers = Request.Headers["providers"];
if (providers.Equals(StringValues.Empty))
return BadRequest();
criteria.Providers = providers.ToString().Split(',').ToList();
ModelState.Clear();
TryValidateModel(criteria);
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var result = await action.Invoke(criteria);
return Ok(result);
}
模板模式是解决这种情况的方法。虽然与验证无关。而且您还必须处理控制器的依赖性。请注意,如果不进行一些更改,下面的代码将不会自动运行。
public abstract class BaseSearch
{
public Task<IActionResult> Apply(AggregateSearchCriteria criteria)
{
if (criteria == null || !criteria.Aggregates.Any())
{
return BadRequest();
}
var providers = Request.Headers["providers"];
if (providers.Equals(StringValues.Empty))
return BadRequest();
criteria.Providers = providers.ToString().Split(',').ToList();
ModelState.Clear();
TryValidateModel(criteria);
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var result = await ServiceCall(criteria);
return Ok(result);
}
protected abstract async IActionResult ServiceCall(AggregateSearchCriteria criteria);
}
public class Search : BaseSearch
{
protected async Task<IActionResult> ServiceCall(AggregateSearchCriteria criteria)
{
return await _searchService.Search(criteria);
}
}
public class SearchRuleBreak : BaseSearch
{
protected async Task<IActionResult> ServiceCall(AggregateSearchCriteria criteria)
{
return await _searchService.SearchRuleBreak(criteria);
}
}
然后在调用时:
[HttpPost]
public async Task<IActionResult> Search([FromBody]AggregateSearchCriteria criteria)
{
return await new Search().Apply(criteria);
}
[HttpPost("rulebreak")]
public async Task<IActionResult> SearchRuleBreak([FromBody]AggregateSearchCriteria criteria)
{
return await new SearchRuleBreak().Apply(criteria);
}
注意: 随着当今语言的功能越来越强大,"send function as parameter" 方法也是一种有效的方法,正如@stuartd 所建议的那样。