Route attribute with wildcard on the left - asp.net-web-api-routing

I want to be able to automatically match routes that may or may not have a prefix on the left of the incoming request's route.
For example, for this controller
[MyRoutePrefix("api/hello-world")]
public class MyController
{
[Route("")]
public IHttpActionResult Get(){ return OK(); }
}
I'd like the following requests to be matched up with MyController:
http://example.com/api/hello-world
http://example.com/us/api/hello-world
http://example.com/any-word/api/hello-world
Thanks.

Related

HttpGetAttribute doesn't work in core web api

Well known situation. I need two endpoints
GetAll -> api/brands
GetById -> api/brands/1
[ApiController]
[Route("api/[controller]")]
public class BrandsController : ControllerBase
{
private readonly BrandRepository repository;
public BrandsController(BrandRepository repository)
{
this.repository = repository;
}
[HttpGet("{id:int}")]
public async Task<ActionResult> GetById(int id)
{
var brand = await repository.FindAsync(id);
if (brand == null)
{
return NotFound();
}
return Ok(brand);
}
[HttpGet("")]
public ActionResult<IEnumerable<Brand>> GetAll()
{
var brands = repository.GetAll().ToList();
return Ok(brands);
}}
So, I always get into GetAll()
Any ideas? Help, please :)
Is it a correct namespace?
using Microsoft.AspNetCore.Mvc;
for
[HttpGet]
Startup.cs
namespace BackOffice
{
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)
{
services.AddControllers();
services.AddDbContext<ApplicationDbContext>(
options =>
options.UseMySql(Configuration.GetConnectionString("local")));
services.AddTransient<BrandRepository, BrandRepository>();
}
// 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();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(
endpoints =>
{
endpoints.MapControllers();
});
app.UseCors();
}
}
}
dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd
dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd
Change your the attribute on your GetAll action to simply [HttpGet] and then change the attribute on your GetById action to [HttpGet("{id}")] .
You can use a constraint to id if need be but in your case I don't see any need for it. Generally you can use constraints when you have multiple actions on the same route but with different parameter types. For example, "api/brands/1" to get by integer ID and then maybe you have another action that is mapped to "api/brands/gucci" that will search for the brand by string name. Then you can use the {id:int} and {id:string} constraints in your route template to define which action to invoke.
Also make sure you use IActionResult when declaring the action return types. You don't want to use the concrete ActionResult type. Code samples below.
For the GetById action :
[HttpGet("{id}")]
public async Task<IActionResult> GetById(int id)
{
var brand = await repository.FindAsync(id);
if (brand == null)
{
return NotFound();
}
return Ok(brand);
}
For your GetAll action :
[HttpGet]
public IActionResult<IEnumerable<Brand>> GetAll()
{
var brands = repository.GetAll().ToList();
return Ok(brands);
}
This will tell the routing middleware which action to invoke. For actions that you want mapped to the base controller route (i.e. "api/brands"), just use the attribute without an overload. Such as [HttpGet], [HttpPost], [HttpDelete]. For the actions that have a route parameter then you can use [HttpGet("{id}")] and so forth depending on the HTTP method. Don't worry about defining the type of the parameter in the attribute route template. You define the parameter in your action's parameters. For instance:
[HttpGet("{id}")]
public async Task<IActionResult> GetById(int id)
{
// Code here
return Ok();
}
If you want to map a route to something like "api/brands/designers/2" then you would use a template like [HttpGet("designers/{id}")] to do so. Don't put a "/" before the designers.
Edit : Forgot to mention, make sure your Startup.cs is properly configured for Web API routing. You can read the specifics on the ASP.NET Core 3.1 docs for what all the different options do. If you used the Web API template then it's probably fine but it's worth double checking as improperly configured endpoint routing can cause issues. Make sure you have the following in your Configure method in Startup.cs.
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
Make sure that app.UseRouting(); is called before app.UseEndpoints();

Web Api Get Overload Throws Multiple actions were found that match the request

I have an api controller:
[RoutePrefix("api/users")]
[Authorize]
public class UsersController : ApiController
that has two Get methods:
[HttpGet]
[Route("")]
public async Task<HttpResponseMessage> Get(ODataQueryOptions<ApplicationUser> options)
{
return Request.CreateResponse(HttpStatusCode.OK, new List<ApplicationUser>());
}
[HttpGet]
[Route("")]
public async Task<HttpResponseMessage> Get()
{
return Request.CreateResponse(HttpStatusCode.OK, new List<ApplicationUser>());
}
Calling http://mysite/api/users?$filter=FirstName eq 'George'
or
Calling http://mysite/api/users
causes the exception Multiple actions were found that match the request.
Commenting out either method will cause the other to work.
Any help would be appreciated.
All the Web API routing is about converting url into controller/action. And its mapping must be unambiguous.
In case that we would have only the first actionGet(ODataQueryOptions<ApplicationUser> options) it would match both urls below:
http://mysite/api/users?$filter=FirstName+eq+'George'
http://mysite/api/users
The first url will be converted into call Get(someODataValue), the second could be Get(null)
The same could be applied to second method Get() without params, because both urls will be converted into parameterless call Get() (OData part will be skipped)
So the solution usually should be in two methods, which are really unique by params. E.g. one is object/refence the second is valueType/int
[HttpGet]
public async Task<HttpResponseMessage> Get(ODataQueryOptions<ApplicationUser> options)
{ ... }
[HttpGet]
public async Task<HttpResponseMessage> Get(int id)
{ ... }

Attribute routing in MVC 5 and optional defaults

Traditional routing defaults meant we were able to access these URLs and always end up on the same action:
/
/Home
/Home/Index
But today we would be writing something in these lines:
[RoutePrefix("Home")]
[Route("{action=Index}")]
public class HomeController
{
public ActionResult Index() {}
public ActionResult ...
}
But this routing definition is by no means the same.
/ (fails)
/Home (works)
/Home/Index (works)
So if we then change upper code to
[RoutePrefix("Home")]
[Route("{action=Index}")]
public class HomeController
{
[Route("~/")]
public ActionResult Index() {}
public ActionResult ...
}
But then we turn the processing upside down:
/ (works)
/Home (fails)
/Home/Index (fails)
We could make declarative code more verbose and make it work as the old-fashioned routing mechanism by:
[RoutePrefix("Home")]
[Route("{action=Index}")]
public class HomeController
{
[Route("~/")]
[Route("~/Home")]
[Route("~/Home/Index")]
public ActionResult Index() {}
public ActionResult ...
}
This works with all three different routes.
Question
This issue is of course bound to the very application default action that defaults controller and action. It's just that I wonder whether this is the only way of doing it? Is there any less verbose code way of getting it to work as expected?
Yeah, right..what you have is the way to do here...
I modified the code a bit here:
[RoutePrefix("Home")]
[Route("{action}")]
public class HomeController
{
[Route("~/")] // GET /
[Route] // GET /Home
[Route("Index")] // GET /Home/Index
public ActionResult Index() {}
public ActionResult ...
}
Some details:
1. Your first case is not exactly the same as conventional routing as in this case you have a literal segment Home which is not similar to the conventional routing optional of {controller}/{action}/{id} and controller = Home, action=Index,id=optional.
2. Your second case is expected as by design if a Route attribute is used on action the attributes on Controller do not take effect.
right now for SEO you should use canonical url meaning single url something like this
public class HomeController
{
[Route("~/")] // GET /
public ActionResult Index() {}
public ActionResult ...
}
so home controller is accessible at root only

How to access multiple resources in a single request : Jersey Rest

I am trying to a find a good design for the following scenario.
I have a POST rest service which will be given an array of services as data. And which should in turn be calling them one by one to aggregate results on the server and send them back to the client.
#Path("/resource1")
#Path("/resource2")
#Path("/collection")
Post data to /collection
{["serviceName": "resource1", "data":"test1"], ["serviceName":"resource2","data":"test2"]}
The reason i need the resource1 and resource2 are, because those services can be called standalone also. I want to reuse the same setup if possible.
Is there any way to do this.
I am using jersey with spring.
Not sure what these resources have in common. If the post method has the same signature for all of them, you could have an abstract class or interface they implement defining the post method and can try using ResourceContext.matchResource to do this. E.g. something like this:
public abstract class AbstractResource {
public abstract String post(Object data);
}
#Path("/resource1")
public class Resource1 extends AbstractResource {
#POST
public String post(String data) {
// do something
}
}
#Path("/collection")
public class CollectionResource {
#Context
private ResourceContext rc;
#POST
#Consumes("application/json")
public String post(List<PostRequest> postRequests) {
StringBuilder result = new StringBuilder();
for (PostRequest pr : postRequests) {
// should wrap this in try-catch
AbstractResource ar = rc.matchResource(pr.resource,
AbstractResource.class);
sb.append(ar.post(pr.data));
}
return result.toString();
}
}
#XmlRootElement
public class PostRequest {
public String resource;
public String data;
}
Hopefully you got the idea and will be able to play with it and tweak it to fit your needs.

ASP.NET MVC 2 A problem with OnActionExecuting method

I have a controller called "SomeController". I want to check if the user is logged in or if has persissions to execute any action in that controller. To do so, I read that article http://blog.wekeroad.com/blog/aspnet-mvc-securing-your-controller-actions/ and I've written my own class (a test):
public class BaseFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
FormsAuthentication.RedirectToLoginPage();
}
//here will be checking the user permissions if he's logged in
}
}
[BaseFilter]
public class SomeController : BaseController
{
...
}
but as You can understand it makes an infinitive loop when I want to run any action from that controller. So, how to cope with that ?
You can apply the action filter on the relevant methods instead of at the class level.
Personally I would name this something like Authorize and then apply it to the controller methods that require authorization.
[Authorize]
public ActionResult Index()
{
// Do stuff
}