I have a .NET 6 ASP.net Core web application where I want to configure all calls with a particular path prefix to map to a specific controller.
What I want is for all calls that are of the form http://myhost/ipc/some_action to invoke the action some_action of the controller LocalIpcController. Here's how I setup my route in Startup class:
//Configure routing
app.UseEndpoints(endpoints =>
{
//Local IPC endpoint
endpoints.MapControllerRoute(
name: "Ipc",
pattern: "ipc/{action}",
defaults: new { controller = "LocalIpc" }
);
}
However, it is not working. Specifically:
If I make a call to http://myhost/ipc/some_action I get a 404 error
If I make a call to http://myhost/some_action it works correctly
So it looks like the /ipc prefix in the path is completely ignored. Why is this happening?
PS: I know I can also use the [Route] attribute on the controller to do this, but I want to why it isn't working via MapControllerRoute() and what I am doing wrong.
I tested it out and it is working for me. Here is the pipeline configuration and the test controller:
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
// Don't forget to use the UseRouting middleware.
app.UseRouting();
app.UseHttpsRedirection();
app.UseAuthorization();
app.UseEndpoints( endpoints => {
//Local IPC endpoint
endpoints.MapControllerRoute(
name: "Ipc",
pattern: "ipc/{action}",
defaults: new { controller = "LocalIpc" }
);
});
Note: do not forget to include the app.UseRouting();
public class LocalIpcController : ControllerBase
{
public LocalIpcController()
{
}
[HttpGet]
public IActionResult Get()
{
return Content("I'm here.");
}
}
Also I would like to add, that defining the URL routes in endpoints middleware options is not in my opinion the best approach from readability perspective and I would use it only in some edge cases. It is much more clear to specify it in Route attribute (or you can even put it into HTTP method attribute constructor e.g. [HttpGet("ipc/some_action")]).
It turns out, I didn't understand properly how the routing attributes work in ASP.net.
In my controller, the action methods were marked like this:
[HttpGet("some_action")]
public IActionResult MyMethod()
{
}
I was under the impression that "some_action" would be appended to the path indicated in the configuration phase, to obtain "ipc/some_action", but apparently that is not the case, the route specified in the attribute seems to override the one specified in the MapControllerRoute() method.
I created an ASP .NET Core 3.1 website which uses MVC (at least to the best of my understanding). Apart from the login page I use cookie auth to restrict access. One of the controllers has got an action decorated with the [HttpPost] and [AllowAnonymous] attributes.
When running the website on my computer everything works just fine, but when I deploy it to my prod server (Server 2016 + IIS) and post to the action using Postman I get redirected to the login page and the HTML of the login page is returned.
The rest of the website, login etc works just fine. It's only the API I'm having problems with.
I also tried creating a brand new controller using the template from Visual Studio, but the result is the same.
Removing the [Authorize] attribute from the controller also did not change the situation.
Would anybody know why that might be?
Let me know if there is any information missing you might need.
My controller (simplified)
[Authorize]
public class ApplicationController : Controller
{
...
public async Task<IActionResult> Index([FromQuery] string searchTerms, [FromQuery] bool updateDb)
{
...
return View(vm);
}
[AllowAnonymous]
[HttpPost]
public string PostAction([FromBody] object postBody)
{
//Process data (JSON)
return "SomeProcessedDataString";
}
...
}
Cookie auth stuff in startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication("myCookieScheme")
.AddCookie("myCookieScheme", (CookieAuthenticationOptions config) =>
{
config.Cookie.Name = "myLoginCookie";
config.LoginPath = "/Home/Login";
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
app.UseAuthentication();
app.UseAuthorization();
...
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapControllers();
});
Thank you.
I managed to fix the problem myself.
Due to file system permission settings on the prod server the POST action was erroring out, which then caused ASP.NET core to redirect to the login page.
Hope this helps somebody else in the future.
I have an ASP.NET Core 3.1 app. This app has an "Admin" area. I've put this area in it's own library to use in other apps. In my other app, I can currently access the "Admin" area by visiting https://{domain}/_admin. I want to reuse/share this library with other apps in my company. Now, other devs in my company have built other apps. So, I expect the _admin path may cause conflicts. For that reason, I want to make that part of the route configurable. Currently, I have the following
appsettings.json
{
"Miscellaneous": {
"AdminRoot":"_admin"
}
}
Startup.cs
...
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
app.UseEndpoints(endpoints =>
{
var adminRoot = Configuration["Miscellaneous:AdminRoot"];
adminRoot = String.IsNullOrEmpty(adminRoot) ? "_admin" : adminRoot;
endpoints.MapAreaControllerRoute(
name: "Admin",
areaName: "Admin",
pattern: (adminRoot + "/{controller=Home}/{action=Index}/{id?}")
);
}
...
}
...
AdminController.cs
[Area("Admin")]
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
[Route("~/api/user-permissions")]
public IActionResult LoadUserPermissions()
{
var permissions = GetPermissions();
return Json(permissions);
}
}
This approach uses [convention-based routing][1]. At the same time, my Admin tool also has it's own REST API. For that reason, I want to mix convention-based routing with [attribute routing][2]. Basically, I want to have routes like /_admin/api/user-permissions. As this route demonstrates, the _admin creates a challenge if I add a Route attribute in my API since it's a configured value.
I thought if I used the ~ it would be relative to the area. However, I get a 404 when I try this approach. Is there a way to dynamically set parts of a route? For example, I would love to be able to either:
Use an approach relative to the area or
Use an attribute like [Route("{Configuration["Miscellaneous:AdminRoot"]}/api/user-permissions")]
I'm not finding a way to accomplish what I'm trying to do. Is there a way to do this? Please note, my Admin area is much more complex. I've tried to isolate this question to my actual problem. In addition, I'm looking to have a configurable approach for the sake of learning. For that reason, I'm not interested in solutions like changing "_admin" to a random value like a GUID or something. Thank you for your help!
I have been reviewing this for several hours. Essentially, since the one parameter is pulled from configuration, the routing requires convention based routing. However, some names, like those URL-friendly names that include dashes are invalid method names in C#. The way around this is use the following:
Rely on convention-based routing
Use attributes other than Route in the controllers. On actions, use the ActionName attribute.
What am I missing that I'm greeted with a 404 for this controller? I really don't want to use attribute-based routing. I also don't want action to be part of any URIs.
I'm using Visual Studio 2017 and .Net Core 1.1.
TestController.cs
using System;
using Microsoft.AspNetCore.Mvc;
namespace Foo.Controllers
{
public class TestController : Controller
{
public long Get() => DateTimeOffset.Now.ToUnixTimeSeconds();
}
}
Note that this works with a [Route("api/Test")] attribute. But I don't want to use attribute-based routing. And as soon as I take that attribute off, I get 404s.
Startup.cs
namespace Foo
{
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "api/{controller}/{id?}"
);
});
}
}
}
Note there's also some stuff in here for Autofac/DI, but I took that out to remove the distraction.
Debug output from a request
Application Insights Telemetry (unconfigured): {"name":"Microsoft.ApplicationInsights.Dev.Message","time":"2017-03-10T14:18:01.3308908Z","tags":{"ai.internal.sdkVersion":"aspnet5c:2.0.0","ai.operation.id":"0HL37O0HBESDL","ai.application.ver":"1.0.0.0"},"data":{"baseType":"MessageData","baseData":{"ver":2,"message":"Request starting HTTP/1.1 GET http://localhost:50129/api/test","severityLevel":"Information","properties":{"DeveloperMode":"true","Host":"localhost:50129","AspNetCoreEnvironment":"Development","CategoryName":"Microsoft.AspNetCore.Hosting.Internal.WebHost","Path":"/api/test","Protocol":"HTTP/1.1","Method":"GET","Scheme":"http"}}}}
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/1.1 GET http://localhost:50129/api/test
Application Insights Telemetry (unconfigured): {"name":"Microsoft.ApplicationInsights.Dev.Message","time":"2017-03-10T14:18:01.3633954Z","tags":{"ai.cloud.roleInstance":"Desktop","ai.internal.sdkVersion":"aspnet5c:2.0.0","ai.location.ip":"::1","ai.operation.id":"0HL37O0HBESDM","ai.application.ver":"1.0.0.0","ai.internal.nodeName":"Desktop","ai.operation.name":"GET /api/test"},"data":{"baseType":"MessageData","baseData":{"ver":2,"message":"Request successfully matched the route with name 'default' and template 'api/{controller}/{id?}'.","severityLevel":"Verbose","properties":{"DeveloperMode":"true","AspNetCoreEnvironment":"Development","CategoryName":"Microsoft.AspNetCore.Routing.RouteBase","RouteName":"default","{OriginalFormat}":"Request successfully matched the route with name '{RouteName}' and template '{RouteTemplate}'.","RouteTemplate":"api/{controller}/{id?}"}}}}
Application Insights Telemetry (unconfigured): {"name":"Microsoft.ApplicationInsights.Dev.Message","time":"2017-03-10T14:18:01.3663952Z","tags":{"ai.cloud.roleInstance":"Desktop","ai.internal.sdkVersion":"aspnet5c:2.0.0","ai.location.ip":"::1","ai.operation.id":"0HL37O0HBESDM","ai.application.ver":"1.0.0.0","ai.internal.nodeName":"Desktop","ai.operation.name":"GET /api/test"},"data":{"baseType":"MessageData","baseData":{"ver":2,"message":"No actions matched the current request","severityLevel":"Verbose","properties":{"DeveloperMode":"true","AspNetCoreEnvironment":"Development","CategoryName":"Microsoft.AspNetCore.Mvc.Internal.MvcRouteHandler","{OriginalFormat}":"No actions matched the current request"}}}}
Application Insights Telemetry (unconfigured): {"name":"Microsoft.ApplicationInsights.Dev.Message","time":"2017-03-10T14:18:01.3693962Z","tags":{"ai.cloud.roleInstance":"Desktop","ai.internal.sdkVersion":"aspnet5c:2.0.0","ai.location.ip":"::1","ai.operation.id":"0HL37O0HBESDM","ai.application.ver":"1.0.0.0","ai.internal.nodeName":"Desktop","ai.operation.name":"GET /api/test"},"data":{"baseType":"MessageData","baseData":{"ver":2,"message":"Request did not match any routes.","severityLevel":"Verbose","properties":{"DeveloperMode":"true","AspNetCoreEnvironment":"Development","CategoryName":"Microsoft.AspNetCore.Builder.RouterMiddleware","{OriginalFormat}":"Request did not match any routes."}}}}
Application Insights Telemetry (unconfigured): {"name":"Microsoft.ApplicationInsights.Dev.Message","time":"2017-03-10T14:18:01.3753962Z","tags":{"ai.cloud.roleInstance":"Desktop","ai.internal.sdkVersion":"aspnet5c:2.0.0","ai.location.ip":"::1","ai.operation.id":"0HL37O0HBESDM","ai.application.ver":"1.0.0.0","ai.internal.nodeName":"Desktop","ai.operation.name":"GET /api/test"},"data":{"baseType":"MessageData","baseData":{"ver":2,"message":"Connection id \"0HL37O0H95P8K\" completed keep alive response.","severityLevel":"Verbose","properties":{"DeveloperMode":"true","ConnectionId":"0HL37O0H95P8K","AspNetCoreEnvironment":"Development","CategoryName":"Microsoft.AspNetCore.Server.Kestrel","{OriginalFormat}":"Connection id \"{ConnectionId}\" completed keep alive response."}}}}
Application Insights Telemetry (unconfigured): {"name":"Microsoft.ApplicationInsights.Dev.Message","time":"2017-03-10T14:18:01.3878990Z","tags":{"ai.cloud.roleInstance":"Desktop","ai.internal.sdkVersion":"aspnet5c:2.0.0","ai.location.ip":"::1","ai.operation.id":"0HL37O0HBESDM","ai.application.ver":"1.0.0.0","ai.internal.nodeName":"Desktop","ai.operation.name":"GET /api/test"},"data":{"baseType":"MessageData","baseData":{"ver":2,"message":"Request finished in 54.7982ms 404","severityLevel":"Information","properties":{"DeveloperMode":"true","ElapsedMilliseconds":"54.7982","AspNetCoreEnvironment":"Development","CategoryName":"Microsoft.AspNetCore.Hosting.Internal.WebHost","StatusCode":"404"}}}}
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request finished in 54.7982ms 404
This is not works, as mapping to action method is not defined. AFAIK, you may achieve the WebApi REST like routing ONLY using the attribute routing and you may define it on controller level:
[Route("api/[controller]")]
public class TestController : Controller
{
[HttpGet]
public long Get() => DateTimeOffset.Now.ToUnixTimeSeconds();
}
Update: have found this github issue Web API not working with convention based routin and:
for ASP.NET Core MVC we decided to adopt MVC 5.x's conventional routing approach and not Web API 2.x's approach. With the conventional routing approach, the route must specify both a controller and an action.
You may change route template to
template: "api/{controller}/{action}/{id?}"
But in this case, your URL will be /api/test/get.
Update 2 (based on the guide): you can include the NuGet package for Microsoft.AspNetCore.Mvc.WebApiCompatShim and still use ApiController. The code is on GitHub if you are curious as to what it does. Then you can define the WebApi routing:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseMvc(routes =>
{
routes.MapWebApiRoute("DefaultApi", "api/{controller}/{id?}");
});
}
I have a working webpage (front-end strictly) and I was curious of I could add a WebApi without leaving VS Code. So I created a directory called webapi in the root of my project and added a file demo.cs containing the following.
using Microsoft.AspNetCore.Mvc;
namespace WebApi
{
[Route("api/[controller]")]
public class Demo : Controller
{
[HttpGet] public string Ping() { return "yo!"; }
}
}
After some googlearching for references, I made sure that my project.json contains the following.
"Microsoft.AspNetCore.Server.Kestrel": "1.0.1",
"Microsoft.AspNetCore.StaticFiles": "1.0.0",
"Microsoft.AspNetCore.Mvc.Core": "1.0.1",
I also edited the configuration methods like this.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvcCore();
services.AddDirectoryBrowser();
}
public void Configure(IApplicationBuilder app,
IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole();
if (env.IsDevelopment())
app.UseDeveloperExceptionPage();
app.UseMvc();
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseFileServer(true);
app.UseMvcWithDefaultRoute();
}
Now, as I execute the project with dotnet run, I get no errors, the page functions but I can't seem to access the text I meant to expose. The extra problem's that I'm not sure if I've got the WebApi running but using the wrong URL (I went localhost:port with /api/donkey) or if it's not running at all.
How can I verify that it's up? What would be the address? Is there something else I'm missing in the setup?
The guides tell partially different things, which I guess depends on the rapid evolution of NET.Core. Not sure how to proceed.
Using documentation from
Routing to Controller Actions
To make attribute routing less repetitive, route attributes on the
controller are combined with route attributes on the individual
actions. Any route templates defined on the controller are prepended
to route templates on the actions. Placing a route attribute on the
controller makes all actions in the controller use attribute routing.
using Microsoft.AspNetCore.Mvc;
namespace WebApi {
[Route("api/[controller]")]
public class Demo : Controller {
[HttpGet] // Matches 'GET /api/Demo'
public string Ping() {
return "yo!";
}
}
}
the start up should have this in the configure services
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services) {
// Add framework services.
services.AddMvc();
//...other code
}