Ответ
Есть несколько паттернов для расширения и переиспользования функциональности контроллеров в ASP.NET Core, каждый решает разные задачи:
1. Наследование от базового контроллера: Идеально для общей логики, которая должна быть доступна во множестве контроллеров (например, логирование, общие методы-хелперы).
// Базовый контроллер с общей функциональностью
[ApiController]
[Route("api/[controller]")]
public abstract class ApiBaseController : ControllerBase
{
// Общий метод для успешного ответа с данными
protected IActionResult ApiOk<T>(T data) => Ok(new ApiResponse<T> { Success = true, Data = data });
// Общий метод для обработки ошибок валидации ModelState
protected IActionResult ApiValidationError()
=> BadRequest(new ApiResponse { Success = false, Errors = ModelState.Values.SelectMany(v => v.Errors) });
// Общее свойство (например, идентификатор текущего пользователя)
protected Guid CurrentUserId => User.FindFirstValue(ClaimTypes.NameIdentifier);
}
// Контроллер, использующий базовый
public class ProductsController : ApiBaseController
{
[HttpGet]
public IActionResult Get()
{
// Используем метод из базового класса
return ApiOk(_repository.GetAllProducts());
}
}
2. Фильтры (Filters): Лучший способ для сквозной функциональности (cross-cutting concerns), которая должна выполняться до или после действия метода: авторизация, валидация, кэширование, логирование.
// Кастомный фильтр для логирования длительных запросов
public class LogExecutionTimeAttribute : ActionFilterAttribute
{
private Stopwatch _stopwatch;
public override void OnActionExecuting(ActionExecutingContext context)
{
_stopwatch = Stopwatch.StartNew();
}
public override void OnActionExecuted(ActionExecutedContext context)
{
_stopwatch.Stop();
var elapsedMs = _stopwatch.ElapsedMilliseconds;
if (elapsedMs > 500)
{
var logger = context.HttpContext.RequestServices.GetService<ILogger<LogExecutionTimeAttribute>>();
logger.LogWarning("Длительный запрос {ActionName}: {ElapsedMs}ms", context.ActionDescriptor.DisplayName, elapsedMs);
}
}
}
// Применение фильтра на уровне контроллера или метода
[LogExecutionTime]
[ApiController]
public class SlowController : ControllerBase { /* ... */ }
3. Методы расширения (Extension Methods) для ControllerBase:
Удобны для добавления небольших, часто используемых утилитарных методов.
public static class ControllerExtensions
{
public static string GetClientIpAddress(this ControllerBase controller)
{
return controller.HttpContext.Connection.RemoteIpAddress?.ToString();
}
public static IActionResult RedirectToLocal(this ControllerBase controller, string returnUrl)
{
if (Url.IsLocalUrl(returnUrl))
return controller.Redirect(returnUrl);
else
return controller.RedirectToAction("Index", "Home");
}
}
// Использование внутри действия: var ip = this.GetClientIpAddress();
4. Внедрение зависимостей (Dependency Injection): Самый чистый способ для предоставления контроллеру общих сервисов. Логика инкапсулируется в сервисе, который затем внедряется в конструктор любого контроллера.
// Сервис с общей бизнес-логикой
public interface IUserContextService
{
Guid GetCurrentUserId();
}
// Контроллер, использующий этот сервис
public class ProfileController : ControllerBase
{
private readonly IUserContextService _userContext;
public ProfileController(IUserContextService userContext) // DI
{
_userContext = userContext;
}
[HttpGet("me")]
public IActionResult GetMyProfile()
{
var userId = _userContext.GetCurrentUserId(); // Используем общий сервис
// ...
}
}
Выбор подхода:
- Общая логика ответов/хелперы -> Наследование.
- Сквозная функциональность (логи, проверки) -> Фильтры.
- Небольшие утилиты -> Методы расширения.
- Сложная бизнес-логика или доступ к данным -> Внедрение сервисов (DI).