How do I set a prefix in my ASP.NET Core 7 Web API for all endpoints?

471 views Asked by At

I am retrieving a string from my appsettings.json:

{
  "ConnectionStrings": {
    //several strings
  },
  "BaseRoute": {
    "Base": "/myapi/"
  }
}

And I have class:

public class BaseRoute
{
    public string Base { get; set; }
}

In my Program.cs I have configured:

var builder = WebApplication.CreateBuilder(args);
// ...
builder.Services.Configure<BaseRoute>(builder.Configuration.GetSection("BaseRoute"));
var baseroute = builder.Configuration.GetSection("BaseRoute").Get<BaseRoute>();
builder.Services.AddSingleton(resolver => resolver.GetRequiredService<IOptions<BaseRoute>>().Value);
// ...
var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseMiddleware<JwtRefresher>();
app.UseHttpsRedirection();

app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();
app.UsePathBase(new PathString(baseroute.Base));
app.UseRouting();
app.Run();

But no prefix is set in my Swagger endpoints when I run the api. How should I solve this?

I even tried placing the app.UsePathBase(new PathString("/api")); higher up:

var app = builder.Build();
app.UsePathBase(new PathString(baseroute.Base));
app.UseRouting();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseMiddleware<JwtRefresher>();
app.UseHttpsRedirection();

app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();

app.Run();

Or replaced the variable in the PathString argument with a fixed string:

var app = builder.Build();
app.UsePathBase(new PathString("/api"));
app.UseRouting();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseMiddleware<JwtRefresher>();
app.UseHttpsRedirection();

app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();

app.Run();

But I still won't get the desired result.

What am I doing wrong?

2

There are 2 answers

5
Qiang Fu On BEST ANSWER

Configre swagger like below:

app.UsePathBase(new PathString("/api"));
app.UseRouting();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    var basePath = "/api";
    app.UseSwagger(c =>
    {
        c.RouteTemplate = "swagger/{documentName}/swagger.json";
        c.PreSerializeFilters.Add((swaggerDoc, httpReq) =>
        {
            swaggerDoc.Servers = new List<OpenApiServer> { new OpenApiServer { Url = $"{httpReq.Scheme}://{httpReq.Host.Value}{basePath}" } };
        });
    });
    app.UseSwaggerUI();
}

Test
enter image description here

A workaround to change base path:
Delete app.UsePathBase(You could also delete app.UseRouting when you don't use usepathbase), Replace app.MapControllers with following

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

(Just note that {id?} is just a pattern, you could use it for any parameter other than "id");
In this way, you needn't to change swagger prefix.
enter image description here

0
Pieter Dreissen On

I changed my appsettings.json to:

{
  "ConnectionStrings": {
    //several strings
  },
  "BaseRoute": {
    "Base": "/myapi/",
    "BlockRoutingWithoutBase": true
  }
}

And the class BaseRoute:

public class BaseRoute
{
    public string Base { get; set; }
    public bool BlockRoutingWithoutBase { get; set; }
}

And then in program.cs:

var builder = WebApplication.CreateBuilder(args);
// ...
builder.Services.Configure<BaseRoute>(builder.Configuration.GetSection("BaseRoute"));
var baseroute = builder.Configuration.GetSection("BaseRoute").Get<BaseRoute>();
builder.Services.AddSingleton(resolver => resolver.GetRequiredService<IOptions<BaseRoute>>().Value);
// ...
var app = builder.Build();
if (baseroute.BlockRoutingWithoutBase)
{
    app.Use(async (context, next) =>
    {
        if (context.Request.Path.StartsWithSegments("/" + baseroute.Base.Split('/')[1]) || context.Request.Path.StartsWithSegments("/swagger"))
        {
            await next();
        }
        else
        {
            context.Response.StatusCode = 404;
            await context.Response.WriteAsync("Not Found");
        }
    });
}
app.UsePathBase(new PathString(baseroute.Base));
app.UseRouting();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger(c =>
    {
        c.RouteTemplate = "swagger/{documentName}/swagger.json";
        c.PreSerializeFilters.Add((swaggerDoc, httpReq) =>
        {
            swaggerDoc.Servers = new List<OpenApiServer> { new OpenApiServer { Url = $"{httpReq.Scheme}://{httpReq.Host.Value}{baseroute.Base}" } };
        });
    });
    app.UseSwaggerUI();
}

app.UseMiddleware<JwtRefresher>();
app.UseHttpsRedirection();

app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();
app.Run();

So I used the answer of @Qiang Fu to set the Swagger endpoints and put the app.UsePathBase() on top. Then both endpoints with and without prefix were available, so I added a filter before app.UsePathBase() is hit so only requests with intended prefix will continue in the pipeline.