MVC WebApi advanced custom routing - c#

I'm struggling with this for some time now. I've searched all over the internet, didn't find any solution.
I'd like to create a webapi project with somewhat custom routing. Using VS 2019, project is of type ASP.NET WebApi on .NET Core 2.2. Routing should be like this:
Basic application must reside on url similar to "https://my.server.com/myapi". URLs which will be called are in form "https://my.server.com/myapi/{InstanceName}/{CommandName}?{customParams}"
I have one controller defined in my project and I would like to redirect all requests to that controller, where instanceName could be parameter of all the methods contained in a controller, so I would get a value for that parameter. CommandName is basicly the same as "action" RouteData by MVC principles. As you can see there is no controller specified, since all is handled by one controller.
So far I've tried setup routing like this:
Startup.cs
app.UseMvc(routes =>
{
routes.MapRoute(
name: "MyRoute",
template: "{instance}/{action}",
defaults: new { controller = "MyController" });
});
}
MyController.cs
[Route("/")]
[ApiController]
public class MyController : ControllerBase
{
[HttpGet("{instance}/info")]
public JsonResult Info(string instance, InfoCommand model)
{
// Just return serialized model for now.
var result = new JsonResult(model);
return result;
}
}
But this does not work. I get 415 response from (I think) web server when I call for example
https://my.server.com/myapi/MYINSTANCE/info?param1=value1&param2=value2
While debugging from VS this URL looks like this:
https://localhost:12345/MYINSTANCE/info?param1=value1&param2=value2
but I think it shouldn't matter for routing.
In best case scenario (putting [Route("{instance}")] above controller and [HttpGet("info")] above Info method) I get 404 response, which is also what I do not want.
I've even tried creating my own ControllerFactory, but that didn't work either (changing controller inside ControllerFactory's create method and adding another parameter to RouteData).
How to setup routing like that? Is it even possible? I would still like to use all other MVC features (model binding, proper routing, auth features, etc.), it's just this routing I cannot figure it out.

Your attempt resulting a in 415 Unsupported Media Type error was your best one.
You were only missing the FromQuery as shown below.
The error indicates that the complex type InfoCommand could not be resolved.
You must specify that it must be parsed from the querystring.
Note that the route defined via MapRoute doesn't have effect, since you are using attribute-based routing; it's only one or the other.
[Route("/")]
[ApiController]
public class MyController : ControllerBase
{
[HttpGet("{instance}/info")]
public JsonResult Info(string instance, [FromQuery] InfoCommand model)
{
var result = new JsonResult(model);
return result;
}
}
public class InfoCommand
{
public InfoCommand()
{}
public string Param1 { get; set; }
public string Param2 { get; set; }
}

Related

I can't access methods in net core MVC controllers

EDIT: If I create an empty ASP.NET CORE WEB APP MVC, I can make it working. I am having problem when I am using MVC with Angular. There might be a problem with SPA proxy as well.
EDIT 2: I found a report https://github.com/dotnet/aspnetcore/issues/38354
I am still trying but no chance.
I can not access my public methods in controller classes. This is my controller:
[Route("authentication")]
public class AuthenticationController : Controller
{
[HttpGet("example")]
public IActionResult Example()
{
return Ok("This is the Welcome action method...");
}
}
And also I tried this attribute as well:
[Route("[controller]")]
public class AuthenticationController : Controller
when I try to navigate to localhost:PORT/authentication/example I am getting 404. I am not using API. I am trying to build a web application with .net core MVC and angular. So I will be just sending GET or POST requests to controllers.
This is my program.cs file
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
var app = builder.Build();
app.UseStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
app.Run();
I strongly believe that something is wrong in my program.cs. But I couldn't figure it out.
FIX:
After trying out a few days, I finally found the answer. I had to add my new route into 'proxy' variable in proxy.conf.js file.
const PROXY_CONFIG = [
{
context: [
"/weatherforecast",
"/authentication"
],
target: target,
secure: false,
headers: {
Connection: 'Keep-Alive'
}}
]
you can try this for example, it will work for localhost:PORT/authentication/example
[Route("[controller]/[action]")]
public class AuthenticationController : Controller
{
public IActionResult Example()
{
return Ok("This is the Welcome action method...");
}
}
//or
public class AuthenticationController : Controller
{
[HttpGet("~/Authentication/Example")]
public IActionResult Example()
{
return Ok("This is the Welcome action method...");
}
}
but since you are using a Controller as a base class, not an ApiController for example, everything should be working even if you remove all attribute routing at all.
You need to decorate your controller with method / routing attributes
Try:
[Route("api/[controller]")]
public class AuthenticationController : Controller
{
[HttpGet("example")]
public IActionResult Example()
{
return Ok("This is the Welcome action method...");
}
}
This will create a get endpoint which can be called at api/authentication/example
Returning a 200 status with the text in the body.
The convention is that if Your memers start with an action verb, it can find out automatically, like
public string GetExample()
However you do not want to return raw string, you always want to return an action result, because you want wrapping with explicit HttpStatus response codes, so
public IActionResult<string> GetExample()
Now many of us a bias towards the works by magic because of prefix and like to be more explicit, not only because the attribute notation allows more control, but also for consistency. Because nearly almost always, at least one action method of the controller actually requires that fine grain.
[HttpGet("example")]
public IActionResult<string> Example()
Then often for instance there is an id and you can go
[HttpGet("example/id?")]
public IActionResult<string> Example([FromRoute] string id)
if you want to not have it go through all the places it might be getting your variables from for instance, there are many choices available

ASP.NET Core routing return 404

I am creating a web api using ASP.NET Core 3.1 and am trying to route URL to controllers. So far I have a basic controller like this:
[Route("abc")]
[ApiController]
public class ABCController : ControllerBase
{
// GET: abc/1234
[HttpGet("{id}")]
public async Task<ActionResult<string>> GetABCService(long id)
{
...
}
}
Which correctly route me to the page when I type in http://myurl/abc/1234. The next thing I controller I wanted to wire is like this:
[Route("xxx")]
[ApiController]
public class XXXController : ControllerBase
{
// GET: abc/1234/XXX
[HttpGet("{id}")]
public async Task<ActionResult<string>> GetXXXService(long id)
{
...
}
}
Somehow it keeps giving me 404 when I type in http://myurl/abc/1234/xxx. I made the first one works by setting my endpoint like this :
app.UseEndpoints(endpoints =>{
endpoints.MapControllerRoute(
"abc",
"abc/{id}",
new {controller = "ABCController", action = "GetABCService"});
//My current endpoint mapping for the second controller:
endpoints.MapControllerRoute(
"xxx",
"abc/{id}/xxx",
new {controller = "XXXController", action = "GetXXXStatus" });
}
I could not figure out why I would get 404 with http://myurl/abc/1234/xxx. Any insight?
You want to say XXXController to route 'abc' first by [Route("abc")]
[Route("abc")]
[ApiController]
public class XXXController : ControllerBase
{
[HttpGet("{id}/xxx")]
public ActionResult<string> GetXXXService(long id)
{
return "ActionResult";
}
}
When you use attribute routing, e.g. with [Route] or [HttpGet(…)], then convention-based routing is ignored. So the route templates you define with MapControllerRoute are not taken into account when generating the routes for your API controller. In addition, using the [ApiController] attribute actually enables certain API-related conventions. And one of those convention is that you may only use attribute routing for your API controllers.
So if you only have API controllers in your project, then you can leave out the MapControllerRoute calls. Instead, you will have to make sure that your attribute routing is correct.
In your case, if you want the route abc/1234/XXX to work, then you will have to use a route of abc/{id}/XXX.

Asp.net core mvc optional parameter for Index

im currently trying to create a Controller in ASP.net core mvc with an optional parameter "id".
Im fairly new to asp.net, I've tried to check other posts but nothing solved my problem.
public class TestController : Controller
{
private readonly ITestRepository _TestRepository;
public TestController(ITestRepository TestRepository)
{
_TestRepository = TestRepository;
}
public async Task<IActionResult> Index(string id)
{
if (string.IsNullOrEmpty(id))
{
return View("Search");
}
var lieferscheinInfo = await _TestRepository.GetTestdata(Convert.ToInt64(id));
if (lieferscheinInfo == null)
{
// return err view
throw new Exception("error todo");
}
return View(lieferscheinInfo);
}
}
I want to open the site like this "localhost:6002/Test" or "localhost:6002/Test/123750349" e.g, the parameter can be an int as well, i've tried both(string and int) but it doesnt work.
Either the site returns 404 (for both cases, with and without an parameter) or the parameter gets ignored and is always null.
I've tried to add [Route("{id?}")] on the Index but it did not change anything.
Greetings
your code should work just fine. string parameters accepts null by default so you dont need to specify can you check how the routing is set up in your startup.cs file.
You can add routing through attribute for example the following :
[Route("Test")]
[Route("Test/Index")]
[Route("Test/Index/{id?}")]
Will allow you to access the Action using :
/test/?id=sasasa
test/Index/?id=sasasa
test/Index
check Routing Documentation at Microsoft site :
https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/routing?view=aspnetcore-3.1
In your project's Startup class make sure you are using the right MapControllerRoute
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Test}/{action=Index}/{id?}");
});

Mvc routing. How can i set a default parameter in .net core 3.0?

My controller is
public partial class GridController : Controller
{
[Route("/grid/{name}")]
public IActionResult Index(string name)
{
}
}
Routing is setup correctly since if i visit /grid/something i get http ok.
But how can i set a default parameter in startup.cs?
I tried the following but on page load, i get http 404 error
endpoints.MapControllerRoute(
"default",
"{controller=Grid}/{action=Index}/{name}");
Okay. Ended up with a workaround
endpoints.MapControllerRoute(
"default",
"{controller=Grid}/{action=Index}");
and by adding an extra action overload.
public IActionResult Index()
{
return Index("something");
}
it simply works
[Route("/grid/{name}")]
public IActionResult Index(string name )
{
}
Use attribute routing at Controller level, So that it will be applicable to all the action method under that controller.
In your route template. The name parameter is not optional.
So, use {name?}.
First of all, you did not specify the name parameter in your endpoint configuration. So it should have been {controller=Grid}/{action=Index}/{name=something}. But if there are overloads, this does not seem to work anyway. I wonder if this is a bug or is it intended by design. It seems that if there is are multiple overloads of the same action, execution gets directed to the parameterless one.
Here's another workaround with the possibility to use parameters. I tried this with .NET Core 2.2. Create a new action with the same parameters, but without RouteAttribute. From there you can do a redirect to the necessary action. Here's an example where I use TestIndex for the default route.
[Route("/[controller]/{name}/{age}")]
public IActionResult Index(string name, int age)
{
// implementation
return Ok($"{name} is {age} years old.");
}
public IActionResult Index()
{
return Index("Mr. It", 7);
}
public IActionResult TestIndex(string name, int age)
{
return RedirectToAction(nameof(Index), new { name, age });
}
Then for default route use the new method, e.g. {controller=Grid}/{action=TestIndex}/{name=That}/{age=42}.
If you use this for testing, the new action could be omitted from production by adding a TypeFilterAttribute. See this answer for more details.

Mixing WebAPI and MVC AttributeRouting with Route Prefix?

I'm pretty new to setting up routes and routing in MVC. At my last job we used attribute routing for our WebAPI stuff, so I'm pretty familiar with that (RoutePrefix, Route, and HttpGet/HttpPost attributes, etc). And I can get my current project to work just fine with attributes.
So now what I want to do is "prefix" all of the webApi routes with "api". So instead of going to mysite/test/hello, I want to go to mysite/api/test/hello.
This is what I have, using only attribute routing, and it works just fine:
[RoutePrefix("Test")]
public class TestController : ApiController
{ ....
[HttpPost]
[Route("{message}")]
public HttpResponse EchoBack(string message)
{
// return message ... in this case, "hello"
}
}
Now, I know I can change the RoutePrefix to "api/Test" (which works 100%), but I don't want to change all my controllers, I would rather be able to perform this by changing the values passed in to config.Routes.MapHttpRoute in WebApiConfig.
Is this possible?
What you describe can be done in attribute routing by using what is referred to as a global route prefix.
Referencing this article Global route prefixes with attribute routing in ASP.NET Web API
Create a DirectRouteProvider
public class CentralizedPrefixProvider : DefaultDirectRouteProvider {
private readonly string _centralizedPrefix;
public CentralizedPrefixProvider(string centralizedPrefix) {
_centralizedPrefix = centralizedPrefix;
}
protected override string GetRoutePrefix(HttpControllerDescriptor controllerDescriptor) {
var existingPrefix = base.GetRoutePrefix(controllerDescriptor);
if (existingPrefix == null) return _centralizedPrefix;
return string.Format("{0}/{1}", _centralizedPrefix, existingPrefix);
}
}
To quote article.
The CentralizedPrefixProvider shown above, takes a prefix that is
globally prepended to every route. If a particular controller has it’s
own route prefix (obtained via the base.GetRoutePrefix method
invocation), then the centralized prefix is simply prepended to that
one too.
Configure it in the WebAPiConfig like this
config.MapHttpAttributeRoutes(new CentralizedPrefixProvider("api"));
So now for example if you have a controller like this
[RoutePrefix("Test")]
public class TestController : ApiController {
[HttpPost]
[Route("{message}")]
public IHttpActionResult EchoBack(string message) { ... }
}
The above action will be accessed via
<<host>>/api/Test/{message}
where api will be prepended to the controller actions route.

Categories