I'm testing .net7 MVC. I have 2 controllers. 1st - default, created during installation and 2nd, copied from the 1st and modified. 1st controller works as expected, 2nd - does not. I've broken up my head dealing with this silly case.
Attaching the code
Route https://localhost:44425/account/123 - DOES NOT WORK (Return 404)
Route https://localhost:44425/weatherforecast/123 - WORKS
program.cs
using POCs.Services.Impelemtation;
using POCs.Services.Interfaces;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
builder.Services.AddScoped<IServiceManager, ServiceManager>();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
app.MapFallbackToFile("index.html");
app.Run();
working controller
using Microsoft.AspNetCore.Mvc;
namespace CloudServiceWebAPI.Controllers
{
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet("123")]
public IEnumerable<WeatherForecast> Get1()
{
return Enumerable.Range(1, 2).Select(index => new WeatherForecast
{
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
}
}
not working controller
using CloudServiceWebAPI.DTOs.AccountController;
using Microsoft.AspNetCore.Mvc;
using POCs.POCs.DTOs;
using POCs.Services.Interfaces;
namespace CloudServiceWebAPI.Controllers
{
[ApiController]
[Route("[controller]")]
public class AccountController : ControllerBase
{
private readonly ILogger<AccountController> _logger;
private readonly IServiceManager _serviceManager;
public AccountController(ILogger<AccountController> logger, IServiceManager serviceManager)
{
_logger = logger;
_serviceManager = serviceManager;
}
[HttpGet("123")]
public GetAccountsResponse GetAccounts()
{
GetAccountsResponse response = new GetAccountsResponse();
response.Success = true;
response.Message = string.Empty;
GetAccountsDTO getAccountsDTO = new GetAccountsDTO();
response.Accounts = this._serviceManager.AccountService.GetAccounts(getAccountsDTO);
return response;
}
}
}
I've tried to change program.cs in many ways, but came a conclusion that it's not the case.
Tried to play with names of controllers and with routing policies. Looks like I'm missing something pretty simple. Will be glad for any help
Oh. I've found an answer.
Into the latest template for .net 7 webAPI there is a separate script for run redirection from https to http request.
And there is a list of allowed routes.
file is called "proxy.conf.js".
Hope my experience will help somebody.
Related
I am using dotnet 6 Web API. I have a consumer that can only use POST or GET. So I need to redirect their POST request to my PUT endpoint. I am trying to implement HttpMethodOverrideMiddleware but can't get it to work.
Here is my program.cs:
namespace middlewareTesting
{
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
app.UseHttpMethodOverride();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
}
}
}
And here is my controller:
using Microsoft.AspNetCore.Mvc;
namespace middlewareTesting.Controllers
{
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet(Name = "GetWeatherForecast")]
public IActionResult Get()
{
return Ok("GET WAS HIT");
}
[HttpPost(Name = "PostWeatherForecast")]
public IActionResult Post()
{
return Ok("POST WAS HIT");
}
[HttpPut(Name = "PutWeatherForecast")]
public IActionResult Put()
{
return Ok("PUT WAS HIT");
}
}
}
When I use Postman and specify a header with a key of X-HTTP-Method-Override and a value of PUT, it doesn't hit the PUT method. It hits the Post method.
However, if I set a breakpoint and inspect the Request object it looks like it changed the method to PUT.
{Microsoft.AspNetCore.Http.DefaultHttpRequest}
Body: {Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpRequestStream}
BodyReader: {Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpRequestPipeReader}
ContentLength: 147
ContentType: "application/json"
Cookies: {Microsoft.AspNetCore.Http.RequestCookieCollection}
Form: '((Microsoft.AspNetCore.Http.DefaultHttpRequest)Request).Form' threw an exception of type 'System.InvalidOperationException'
HasFormContentType: false
Headers: {Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpRequestHeaders}
Host: {localhost:7188}
HttpContext: {Microsoft.AspNetCore.Http.DefaultHttpContext}
IsHttps: true
Method: "PUT"
Path: {/WeatherForecast}
PathBase: {}
Protocol: "HTTP/1.1"
Query: {Microsoft.AspNetCore.Http.QueryCollection}
QueryString: {}
RouteValues: {Microsoft.AspNetCore.Routing.RouteValueDictionary}
Scheme: "https"
Is this behavior expected?
Do I need to do anything in my POST method?
Do I have to do anything else to get this to work?
Can't explain why but adding UseRouting after the UseHttpMethodOverride made it work for me:
app.UseHttpMethodOverride();
app.UseRouting();
I built the React template with dotnet new react -o my-new-app
Program.cs:
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.MapControllers();
app.MapFallbackToFile("index.html");;
app.Run();
The controller that I added.
using Microsoft.AspNetCore.Mvc;
namespace my_new_app.Controllers;
[ApiController]
[Route("[controller]")]
public class DifferentController : ControllerBase
{
private readonly ILogger<DifferentController> _logger;
public DifferentController(ILogger<DifferentController> logger)
{
_logger = logger;
}
[HttpGet]
public IEnumerable<string> Get()
{
return Enumerable.Range(1, 5).Select(_ => "hello world")
.ToArray();
}
}
My front-end request:
async populateWeatherData() {
const differentResponse = await fetch('different');
const differentData = await differentResponse.text();
const response = await fetch('weatherforecast');
const data = await response.json();
this.setState({ forecasts: data, loading: false });
}
differentData will always print out as the index.html. Why is it doing this? I understand that the index.html is the fallback; I understand that the server is trying to serve the front-end assets and be the api at the same time, but why would fetch work for weatherdata and not different? I have tried possible combination of mapcontrollers, mapendpoints, mapdefaultcontrollerroute, addcontrollerswithviews, etc. that I could find online, so I feel that there is something fundamental I'm missing.
Figured it out. The API is not the same port as the SPA, so whenever I was trying to fetch data the server was sending the only thing that it could. Now, when I add different to the context in setupProxy.js like so...
const { createProxyMiddleware } = require('http-proxy-middleware'); const { env } = require('process');
const target = env.ASPNETCORE_HTTPS_PORT ? `https://localhost:${env.ASPNETCORE_HTTPS_PORT}` : env.ASPNETCORE_URLS ? env.ASPNETCORE_URLS.split(';')[0] : 'http://localhost:37776';
const context = [ "/weatherforecast", "/different", ];
module.exports = function(app) { const appProxy = createProxyMiddleware(context, {
target: target,
secure: false,
headers: {
Connection: 'Keep-Alive'
} });
app.use(appProxy); };
...the fetch returns the correct data.
I confirmed by issuing a get request on a different port.
I am getting this error every time I build my application:
fail: Microsoft.Identity.Web.TokenAcquisition[0]
False MSAL 4.49.1.0 MSAL.NetCore .NET 7.0.0 Microsoft Windows 10.0.19043 Exception type: Microsoft.Identity.Client.MsalUiRequiredException
, ErrorCode: user_null
HTTP StatusCode 0
CorrelationId
at Microsoft.Identity.Client.Internal.Requests.Silent.SilentRequest.ExecuteAsync(CancellationToken cancellationToken)
at Microsoft.Identity.Client.Internal.Requests.Silent.SilentRequest.ExecuteAsync(CancellationToken cancellationToken)
at Microsoft.Identity.Client.Internal.Requests.RequestBase.RunAsync(CancellationToken cancellationToken)
I am building upon this project: https://github.com/Azure-Samples/active-directory-aspnetcore-webapp-openidconnect-v2/tree/master/2-WebApp-graph-user/2-1-Call-MSGraph
Other than the error, the app is working fine except for another error previously mentioned (token not automatically refreshing after about an hour) here: Microsoft graph token refresh azure app service.
However, this problem seemed to be a bug with .net 7.
My Startup class:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Get the scopes from the configuration (appsettings.json)
var initialScopes = Configuration.GetValue<string>("DownstreamApi:Scopes")?.Split(' ');
// Add sign-in with Microsoft
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd"))
// Add the possibility of acquiring a token to call a protected web API
.EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
// Enables controllers and pages to get GraphServiceClient by dependency injection
// And use an in memory token cache
.AddMicrosoftGraph(Configuration.GetSection("DownstreamApi"))
.AddDistributedTokenCaches();
// Register AadService and PbiEmbedService for dependency injection
services.AddScoped(typeof(AadService))
.AddScoped(typeof(PbiEmbedService))
.AddScoped(typeof(PowerBiServiceApi));
services.AddControllersWithViews(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
// Enables a UI and controller for sign in and sign out.
services.AddRazorPages()
.AddMicrosoftIdentityUI();
// Session/cookie variables etc
services.AddDistributedMemoryCache();
services.AddSession();
// Loading appsettings.json in C# Model classes
services.Configure<AzureAd>(Configuration.GetSection("AzureAd"))
.Configure<PowerBI>(Configuration.GetSection("PowerBI"));
// Add the UI support to handle claims challenges
services.AddServerSideBlazor()
.AddMicrosoftIdentityConsentHandler();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseSession();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapRazorPages();
});
}
}
Controller class:
[Authorize]
public class HomeController : Controller
{
public IConfiguration Configuration { get; }
private readonly GraphServiceClient _graphServiceClient;
public HomeController(IConfiguration configuration,
GraphServiceClient graphServiceClient)
{
Configuration = configuration;
_graphServiceClient = graphServiceClient;
}
[AuthorizeForScopes(ScopeKeySection = "DownstreamApi:Scopes")]
public Task<IActionResult> Index()
{
var graphUser = _graphServiceClient.Me.Request().GetAsync();
var datasource = Configuration["AzureDB:Datasource"];
var username = Configuration["AzureDB:Username"];
var password = Configuration["AzureDB:Password"];
var database = Configuration["AzureDB:Database"];
var user = new Userclass(graphUser.Result, datasource, username, password, database);
Console.WriteLine(user.ProjectName);
if (!user.ProjectName.IsNullOrEmpty())
{
HttpContext.Session.SetString("MyProject", user.ProjectName);
}else{
/*HttpContext.Session.SetString("MyProject", "No Project");*/
}
/*var user = _graphServiceClient.Me.Request().GetAsync();
Console.WriteLine(user.Result.DisplayName);*/
return Task.FromResult<IActionResult>(View());
}
[AuthorizeForScopes(ScopeKeySection = "DownstreamApi:Scopes")]
public async Task<IActionResult> Profile()
{
var user = await _graphServiceClient.Me.Request().GetAsync();
return View(user);
}
The error message indicating the issue happened in TokenAcquisition, but the sample link you shared doesn't have the method which used TokenAcquisition. So I'm not sure where and how the issue happened. I noticed that you used .net 7, so I also created a new .net 7 MVC application then follow the sample link to test and it worked. Here's what I did in my side.
First, adding _LoginPartial.cshtml in Shared folder, the content is completely copied from the sample, then adding this partial view into _layout.cshtml. Then adding configuration in appsettings.json. I set https://localhost/signin-oidc in Azure portal web app platform.
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "tenant_id",
"TenantId": "tenant_id",
"ClientId": "azure_ad_app_id",
"ClientSecret": "azure_ad_app_client_secret",
//"ClientCertificates": [
//],
// the following is required to handle Continuous Access Evaluation challenges
//"ClientCapabilities": [ "cp1" ],
"CallbackPath": "/signin-oidc"
},
"DownstreamApi": {
"BaseUrl": "https://graph.microsoft.com/v1.0",
"Scopes": "user.read"
}
Then modifying Program.cs, also adding app.UseAuthentication();.
using Microsoft.Extensions.Configuration;
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.UI;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(builder.Configuration)
.EnableTokenAcquisitionToCallDownstreamApi(builder.Configuration.GetValue<string>("DownstreamApi:Scopes")?.Split(' '))
.AddMicrosoftGraph(builder.Configuration.GetSection("DownstreamApi"))
.AddInMemoryTokenCaches();
// Add services to the container.
builder.Services.AddControllersWithViews(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
}).AddMicrosoftIdentityUI();
Then in my Controller, adding [Authorize] attribute for the controller, and inject graphsdk,
private readonly GraphServiceClient _graphServiceClient;
public HomeController(GraphServiceClient graphServiceClient)
{
_graphServiceClient = graphServiceClient;
}
public async Task<IActionResult> IndexAsync()
{
var user = await _graphServiceClient.Me.Request().GetAsync();
return View();
}
Packages I installed:
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="7.0.2" />
<PackageReference Include="Microsoft.Identity.Web.MicrosoftGraph" Version="1.25.10" />
<PackageReference Include="Microsoft.Identity.Web.UI" Version="1.25.10" />
By the way, I also test the TokenAcquisition in my Controller. It became like below. It still worked.
private readonly GraphServiceClient _graphServiceClient;
private readonly ITokenAcquisition _tokenAcquisition;
public HomeController(GraphServiceClient graphServiceClient, ITokenAcquisition tokenAcquisition)
{
_graphServiceClient = graphServiceClient;
_tokenAcquisition = tokenAcquisition;
}
[AuthorizeForScopes(ScopeKeySection = "user.read")]
public async Task<IActionResult> IndexAsync()
{
var user = await _graphServiceClient.Me.Request().GetAsync();
string[] scopes = new string[] { "user.read" };
string accessToken = await _tokenAcquisition.GetAccessTokenForUserAsync(scopes);
return View();
}
But if I removed [AuthorizeForScopes(ScopeKeySection = "user.read")] for the action method, I will meet error like this, this is due to the sign in operation happened after the Index action method without the attribute.
How to force Blazor Web API so it to map as it was in some previous .NET Framework?
If I have this UserController:
[Route("api/[controller]")]
[ApiController]
public class UserController : Controller
{
private readonly IHttpContextAccessor httpContextAccessor;
private readonly IConfiguration configuration;
public UserController(IHttpContextAccessor httpContextAccessor)
{
this.httpContextAccessor = httpContextAccessor;
}
[HttpGet]
[Authorize(AuthenticationSchemes = NegotiateDefaults.AuthenticationScheme)]
[Route("GetUserName")]
public String GetUserName()
{
return httpContextAccessor!.HttpContext!.User?.Identity?.Name?;
}
}
Then what should I do to be able to delete
[Route("api/[controller]")] and [Route("GetUserName")]
and still be able to use same route? .../user/getusername
i want to be able to add new controllers / methods without having to specyfy any [Route("xxx")]. want it wo towk using default controller / action name for each own
I thought that
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
});
will do it - but it is not... It does not change anything in that case...
Thanks and regards!
Right, I see, you need to add MVC middleware service then, see below
builder.Services.AddMvc(opt =>
{
opt.EnableEndpointRouting = false;
});
...
app.UseMvcWithDefaultRoute();
Full example
Controller
public class UserController : Controller
{
private readonly IHttpContextAccessor httpContextAccessor;
public UserController(IHttpContextAccessor httpContextAccessor)
{
this.httpContextAccessor = httpContextAccessor;
}
public String? GetUserName()
{
return httpContextAccessor!.HttpContext!.User?.Identity?.Name;
}
}
Program.cs
using Microsoft.AspNetCore.Authentication.Negotiate;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddAuthentication(NegotiateDefaults.AuthenticationScheme)
.AddNegotiate();
builder.Services.AddAuthorization(options =>
{
// By default, all incoming requests will be authorized according to the default policy.
options.FallbackPolicy = options.DefaultPolicy;
});
builder.Services.AddHttpContextAccessor();
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddSingleton<WeatherForecastService>();
builder.Services.AddMvc(opt =>
{
opt.EnableEndpointRouting = false;
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseMvcWithDefaultRoute();
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");
app.Run();
I am trying to set up a API for my MVC web app that will have a lot of routes but much of the same part for each one. Basically a CRUD for each area. I am also setting it up to be version-able. I have set up two controllers each with a simple action to get started and receive a conflict right off the bat. The error I get is
I am after these urls
https://foo.bar/aim/v1/contacts/delete/11111
https://foo.bar/aim/v1/locations/delete/11111
and so on
The MVC will let you have a
https://foo.bar/home/index/
https://foo.bar/contacts/index/
and so on
So I am looking for a away around needlessly naming things like contacts_delete and locations_delete producing URLs like
https://foo.bar/aim/v1/contacts/contacts_delete/11111
https://foo.bar/aim/v1/locations/locations_delete/11111
and so on
(which is in the observation of the root issue provided in the comments and below anwsers like Route names must be unique. You cannot have two different routes named delete. Use deleteLocation and deleteContact instead.)
I may as well just do https://foo.bar/aim/v1/contacts_delete/11111 but that seems so senseless to me. If the MVC can do it, i have to believe that there is a way to make this happen.
The error I get is:
Attribute routes with the same name 'delete' must have the same template:
Action: 'rest.fais.foo.edu.Controllers.aimContactsController.delete (rest.fais.foo.edu)' - Template: 'aim/v1/contacts/delete/{id}'
Action: 'rest.fais.foo.edu.Controllers.aimLocationsController.delete (rest.fais.foo.edu)' - Template: 'aim/v1/locations/delete/{id}'
For the controllers I have set up
[EnableCors("SubDomains")]
[ResponseCache(NoStore = true, Duration = 0)]
[Produces("application/json")]
[Route("aim/v1/contacts/[action]")]
[ProducesResponseType(typeof(errorJson), 500)]
public class aimContactsController : Controller
{
private readonly IHostingEnvironment _appEnvironment;
private readonly AimDbContext _aim_context;
private readonly UserManager<ApplicationUser> _userManager;
public aimContactsController(IHostingEnvironment appEnvironment,
AimDbContext aim_context,
UserManager<ApplicationUser> userManager)
{
_appEnvironment = appEnvironment;
_userManager = userManager;
_aim_context = aim_context;
}
[HttpPost("{id}", Name = "delete")]
public IActionResult delete(string id)
{
return Json(new
{
results = "deleted"
});
}
}
[EnableCors("SubDomains")]
[ResponseCache(NoStore = true, Duration = 0)]
[Produces("application/json")]
[Route("aim/v1/locations/[action]")]
[ProducesResponseType(typeof(errorJson), 500)]
public class aimLocationsController : Controller
{
private readonly IHostingEnvironment _appEnvironment;
private readonly AimDbContext _aim_context;
private readonly UserManager<ApplicationUser> _userManager;
public aimLocationsController(IHostingEnvironment appEnvironment,
AimDbContext aim_context,
UserManager<ApplicationUser> userManager)
{
_appEnvironment = appEnvironment;
_userManager = userManager;
_aim_context = aim_context;
}
[HttpPost("{id}", Name = "delete")]
public IActionResult delete(string id)
{
return Json(new
{
results = "deleted"
});
}
}
For the Startup.cs I have
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
//RolesData.SeedRoles(app.ApplicationServices).Wait();
app.UseApplicationInsightsRequestTelemetry();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
app.UseBrowserLink();
}
else
{
//app.UseExceptionHandler("/Home/Error");
}
app.UseIdentity();
app.UseDefaultFiles();
app.UseStaticFiles();
//app.UseResponseCompression();
// Add external authentication middleware below. To configure them please see http://go.microsoft.com/fwlink/?LinkID=532715
app.UseSession();
// custom Authentication Middleware
app.UseWhen(x => (x.Request.Path.StartsWithSegments("/aim_write", StringComparison.OrdinalIgnoreCase) || x.Request.Path.StartsWithSegments("/rms", StringComparison.OrdinalIgnoreCase)),
builder =>
{
builder.UseMiddleware<AuthenticationMiddleware>();
});
// Enable middleware to serve generated Swagger as a JSON endpoint.
app.UseSwagger();
// Enable middleware to serve swagger-ui (HTML, JS, CSS etc.), specifying the Swagger JSON endpoint.
app.UseSwaggerUI(c =>
{
c.RoutePrefix = "docs";
//c.SwaggerEndpoint("/docs/v1/wsu_restful.json", "v1.0.0");swagger
c.SwaggerEndpoint("/swagger/v1/swagger.json", "v1.0.0");
});
app.UseMvc(routes =>
{
routes.MapRoute(
name: "javascript",
template: "javascript/{action}.js",
defaults: new { controller = "mainline" });
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
routes.MapRoute(
name: "AIMApi",
template: "aim/v1/{action}/{id?}",
defaults: new { controller = "aim" });
routes.MapRoute(
name: "AIMContactsApi",
template: "aim/v1/contacts/{action}/{id?}",
defaults: new { controller = "aimContactsController" }
);
routes.MapRoute(
name: "AIMLocationsApi",
template: "aim/v1/locations/{action}/{id?}",
defaults: new { controller = "aimLocationsController" }
);
routes.MapRoute(
name: "RMSApi",
template: "{controller=rms}/v1/{action}/{id?}");
});
}
}
What I can't seem to google out the answer how to work around this. I want to fix the immediate issue, but any suggests are welcomed.
Both your routes are named the same, this cannot work in ASP.NET Core MVC.
I'm not talking about the methods naming, but about routes naming. You called both your routes with the same identifier Name = "delete" inside the HttpPost attribute. Route names in MVC uniquely identifies a route template.
From what I can see you do not really need to identify your routes, but only to distinguish different URIs. For this reason you may freely remove the Name property of HttpPost attribute on your action methods. This should be enough for ASP.NET Core router to match your action methods.
If you, instead, what to revert using only attribute routing you better change your controller to the following:
// other code omitted for clarity
[Route("aim/v1/contacts/")]
public class aimContactsController : Controller
{
[HttpPost("delete/{id}")]
public IActionResult delete(string id)
{
// omitted ...
}
}
Another possible solution is:
// other solution
[Route("aim/v1/contacts/[Action]")]
public class aimContactsController : Controller
{
[HttpPost("{id}")]
public IActionResult delete(string id)
{
// omitted ...
}
}
Example with Action name dynamically:
[Route("api/{lang}/[controller]/[Action]")]
[ApiController]
public class SpatiaController : ControllerBase
{
private readonly ISpatialService _service;
private readonly ILogger<SpatialController> _logger;
public SpatialUnitsIGMEController(ILogger<SpatialController> logger, ISpatialService service)
{
_service = service;
_logger = logger;
}
[HttpGet]
public async Task<ActionResult<IQueryable<ItemDto>>> Get50k(string lang)
{
var result = await _service.GetAll50k(lang);
return Ok(result);
}
[HttpGet("{name}")]
public async Task<ActionResult<ItemDto>> Get50k(string lang, string name)
{
var result = await _service.Get50k(lang, name);
return Ok(result);
}
}
To call these endpoints the following calls would be used:
https://example.domain.com/api/en/spatial/get50k --> we get all the data in English
https://example.domain.com/api/en/spatial/get50k/madrid --> we obtain the data of madrid in english
https://example.domain.com/api/es/spatial/get50k/madrid --> we obtain the data of madrid in spanish
(using the action and language in the route)