Jeer
@Jeer
уверенный пользователь

Какие статьи почитать (или видео), чтобы разобраться полностью в теме про роутинг в .net?

Привет,
Хочу разобраться с роутингом (маршрутизацией) в .net приложениях
Недавно появились minimal APIs и там роутинг строится так:
app.MapGet("/", () => "Hello World!");

Ранее в .net framework была маршрутизация на основе соглашений (conventional), мы добавляли контроллеры (или контроллеры с представлениями), подключали роутинг и прописывали маппинги всё в том же стартапе:
builder.Services.AddControllersWithViews();

<...>

app.UseRouting();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

// или
// app.MapDefaultControllerRoute();


Затем, в .net core стали переходить к роутингу на атрибутах, при этом можно было сохранить роутинг на основе соглашений, типа такого:
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

<...>

app.UseMvc(routes =>
{
    routes.MapRoute(
        name: "default",
        template: "{controller}/{action=Index}/{id?}");
});


С обычным api должно быть что-то вроде такого:
services.AddControllers();

<...>

app.MapControllers();


Ну и стали ставить атрибуты на каждый контроллер и каждый action, что-то вроде такого:
[Route("api/[controller]")]
[ApiController]
public class Test2Controller : ControllerBase
{
    [HttpGet]   // GET /api/test2
    public IActionResult ListProducts()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [HttpGet("{id}")]   // GET /api/test2/xyz
    public IActionResult GetProduct(string id)
    {
       return ControllerContext.MyDisplayRouteInfo(id);
    }

    [HttpGet("int/{id:int}")] // GET /api/test2/int/3
    public IActionResult GetIntProduct(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [HttpGet("int2/{id}")]  // GET /api/test2/int2/3
    public IActionResult GetInt2Product(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}


Вопрос, исторически из-за чего произошел такой переход, насколько понимаю, сейчас стандартом идет роутинг через атрибуты, как-то я упустил статьи по этому поводу.
Так же интересуют другие способы, хочу разобраться в вопросе, так сказать "раз и навсегда".

Я увидел в чужих проектах, например, можно выставить какие-то эндпоинты наружу вот такими способами:
app
            .UseHealthChecks("/health/ready", new HealthCheckOptions
            {
                Predicate = check => check.Tags.Contains("ready"),
            })
            .UseHealthChecks("/health/live", new HealthCheckOptions
            {
                Predicate = _ => false,
            });

        app.UseEndpoints(
            c => c.MapControllers()
        );

        // да тот же сваггер добавляет свой эндпоинт
        app.UseSwaggerUI();


Еще можно навешивать свои какие-то методы прям на эндпоинты, типа такого:
public const string StopConsumerPath = "/kafka/stopconsumer";
    public const string StartConsumerPath = "/kafka/startconsumer";
    public const string RewindOffsetPath = "/kafka/rewindoffset";

    public static void MapKafkaConsumersTools(this IEndpointRouteBuilder endpoints)
    {
        endpoints.MapEndpoint<KafkaConsumersStopRequest>(
            StopConsumerPath,
            (request, rewindService, ct) => rewindService.StopConsumeFor(request.Topic, ct));
        endpoints.MapEndpoint<KafkaConsumersStartRequest>(
            StartConsumerPath,
            (request, rewindService, ct) => rewindService.StartConsumerFor(request.Topic, ct));
        endpoints.MapEndpoint<KafkaConsumersRewindRequest>(
            RewindOffsetPath,
            (request, rewindService, ct) =>
            {
                rewindService.RewindTopic(request.Topic, request.RewindTo, request.PartitionsCount, ct);
                return Task.CompletedTask;
            });
    }

private static void MapEndpoint<TRequest>(
        this IEndpointRouteBuilder endpoints,
        string path,
        Func<TRequest, RewindService, CancellationToken, Task> execute)
    {
        endpoints.MapPost(path, async context =>
        {
            TRequest request;
            var logger = context.RequestServices.GetService<ILogger<RewindService>>();
            var ct = context.RequestAborted;
            try
            {
                request = await ParseRequest<TRequest>(context);
            }
            catch (Exception e)
            {
                logger?.LogError(e, "Error handling request {Path}", path);
                await SetErrorResponse(context.Response, HttpStatusCode.BadRequest, e, ct);
                return;
            }
            try
            {
                var rewindService = context.RequestServices.GetRequiredService<RewindService>();
                await execute(request, rewindService, ct);
                SetSuccessResponse(context.Response);
            }
            catch (Exception e)
            {
                logger?.LogError(e, "Error handling request {Path}", path);
                await SetErrorResponse(context.Response, HttpStatusCode.InternalServerError, e, ct);
            }
        });
    }


Хотя в статьях про роутинг об этом ни слова. Подскажите, что почитать или посмотреть по данному вопросу, чтобы вот всю тему разобрать?
  • Вопрос задан
  • 184 просмотра
Решения вопроса 1
Ну варианта три:
1. Конвенции
2. Атрибуты
3. MapGet / MapPost итд

Это если не считать всякие сторонние роутеры типа F# Giraffe

Атрибуты были и в .net framework.
А от Convention based все уходят, тк:
1. Не позволяет делать нормальный rest-style роутинг. Да и в принципе маршруты с ним получаются не очень красивые, и не очень предсказуемые.
2. Легко может поломаться

private static void MapEndpoint(

Последнее - это частный случай 3го варианта. Ещё он есть в формате отдельной библиотеки - Carter
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы