В моем контроллере есть два следующих метода действий. Оба берут один и тот же параметр и проводят одинаковую проверку модели. он отличается только в одной строке, где он вызывает метод службы.
Есть ли лучший способ реорганизовать этот код?
[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);
}





Вы можете реализовать IValidatableObject для своей модели AggregateSearchCriteria и переместить в него всю логику проверки. Для поставщиков вы можете добавить его в свою модель и написать настраиваемый связыватель данных, который будет связывать значения из заголовков, также вы можете написать связыватель массива coma, который разделит ваши значения в массив.
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);
}
См. docs.microsoft.com/en-us/aspnet/web-api/overview/…
Шаблонный шаблон - лучший вариант в подобных ситуациях. Однако не имеет отношения к валидации. И вы также должны позаботиться о зависимости контроллера. Обратите внимание, что приведенный ниже код не будет работать автоматически без некоторых изменений.
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);
}
Примечание: По мере того, как сегодня языки становятся все более и более функциональными, подход «отправить функцию как параметр» также является допустимым, как предлагает @stuartd.
Спасибо. Я узнал шаблон шаблона из вашего примера. Да, пойдет с решением @stuartd.
Прости еще раз. может это быть модульное тестирование, какое-то издевательство и прочая ерунда?