Routing: How to allow Path parameters and Query parameters at the same time? - asp.net-core-mvc

For my ASP.NET Core 6.0 MVC web application, I need both:
http://example.com/users/7 and
http://example.com/users?userid=7
My current controller looks like this:
[HttpGet("users/{userId}")]
public IActionResult GetUser(int userId)
{ ... }
The first call works, the second returns a 404.
I wonder why... and what do I need to do to fix this (allow both calls)?

userId section is required so the second Url returned 404
You could try add ? to set Userid section nullable as below:
[Route("Users/{UserId?}")]
public IActionResult GetUser(int UserId)
{
return Ok();
}
Result:

Related

Invoking the web api get method with multiple parameters returns 404 not found error

I have written the following HttpGet method and calling it with 2 parameters. When I try invoking it via postman , I get 404
not found error. Not sure what the problem is in my call
[HttpGet]
[AllowAnonymous]
[Route("unique-email/{clientCompanyId:int}/{email}")]
public IActionResult UniqueEmail( int clientCompanyId, string email )
{
_identityService.CheckUniqueEmail(clientCompanyId, email );
return Ok();
}
I tried the following ways to invoke it
http://localhost:57973/unique-email?clientCompanyId=29&email=test#test.co.uk
http://localhost:57973/unique-email?clientCompanyId=29&email="test#test.co.uk"
http://localhost:57973/unique-email?clientCompanyId=29&email='test#test.co.uk'
Code
public bool CheckUniqueEmail(int clientCompanyId, string email)
{
return _userUow.UniqueEmail(clientCompanyId, email);
}
public bool UniqueEmail(int clientCompanyId, string email)
{
bool anyMatching = ClientCompanyContactRepository.Get()
.Any(x => x.Email == email && x.ClientCompanyId == clientCompanyId);
return !anyMatching;
}
First, check if you have a Route attribute on controller or set any url prefix like api somewhere.
Your urls doesn't meets the requirement path
/unique-email?clientCompanyId=29&email=test#test.co.uk
because route attribute is
[Route("unique-email/{clientCompanyId:int}/{email}")]
it means that url should be
/unique-email/29/test#test.co.uk
If you need to pass parameters as a query string then use [FromQuery] attribute
The route you use in the controller is incorrect with the route you specify in the postman.
your Route
[Route("unique-email/{clientCompanyId:int}/{email}")]
And Uses
http://localhost:57973/unique-email?clientCompanyId=29&email=test#test.co.uk
http://localhost:57973/unique-email?clientCompanyId=29&email="test#test.co.uk"
http://localhost:57973/unique-email?clientCompanyId=29&email='test#test.co.uk'
With this route you used in the code you can use it in the postman
http://localhost:57973/unique-email/29/test#test.co.uk
But if you want to route your route as you did in the postman you first built, make a change to your code.
[HttpGet]
[AllowAnonymous]
[Route("unique-email")]
public IActionResult UniqueEmail([FromQuery] int clientCompanyId, [FromQuery] string email )
{
_identityService.CheckUniqueEmail(clientCompanyId, email );
return Ok();
}
And Postman
http://localhost:57973/unique-email?clientCompanyId=29&email="test#test.co.uk"

ASP.NET5 MVC6 routing

I have the following route defined in Startup.cs:
app.UseMvc(routes =>
{
routes.MapRoute(
name: "api",
template: "api/{controller}/{action}/{id?}"
);
});
And the following controller:
public class BookmarksController : Controller
{
[HttpGet]
public string GetAll()
{
return "GetAll Action";
}
[HttpGet("{id}")]
public string Get(int id)
{
return "Get action";
}
}
Can someone explain please why I can invoke GetAll Action by api/bookmarks/getall, but can not invoke Get by api/bookmarks/get/3 ?
This is by design. If you have controllers/actions which are decorated with attribute routes, any request which matches the conventional routes (the ones defined in your Startup.cs) cannot find/reach those controllers/actions. That is the reason GetAll can be invoked by using a conventional route where as you should be able to reach the Get(int id) by doing the url like /10 (of course, you migt want to modify this template :-))
Oh! I think I understand.
To invoke a action method of the Web API it is not necessary to include method name in the URL.
GetAll() can be invoked via api/bookmarks/ instead if api/bookmarks/getall
and Get(int id) can be invoked via api/bookmarks/3 instead of api/bookmarks/get/3

Prepend a custom parameter before {controller} in ASP.NET WebApi route

I want to create a route template for Owin WebApi like this:
cfg.Routes.MapHttpRoute(
"API Default", "{myparam}/{controller}/{action}",
new { id = RouteParameter.Optional });
Because I have controllers defined that need a parameter before the controller selection.
I have tried to remove the parameter and set it into RoutePrefixAttribute on controller but it doesn't work.
{controller} must be the first dynamic parameter of the route?
I would use some form of attribute based routing to go to different controllers based on {myparam}.
First controller:
[Route("param1/customer/{id}")]
public IEnumerable<Order> GetOrdersByCustomer(int id) { ... }
Second controller:
[Route("param2/customer/{id}")]
public IEnumerable<Order> GetOrdersByCustomer(int id) { ... }
More information can be found here: Attribute Based WebAPI Routing
Delete RoutePrefix attribute and set the first parameter dynamic in your action Route attribute like the example below:
[HttpGet, Route("{myparam}/books/{bookId:int:min(1)}")]
public HttpResponseMessage Get(string myparam, int bookId)
{
...
}

Controller not filtering data in Breeze query in DotNetNuke Module

I am trying to include the basic Breeze sample in a DotNetNuke module (it works fine in a standalone WebAPI project). To simplify things I remove the client and will just refer to the URL JSON calls I make in the Chrome browser.
I can see my metadata and a full list of items, eg:
http://www.dnndev.me/DesktopModules/framework/api/breeze/dare/metadata
http://www.dnndev.me/DesktopModules/framework/api/breeze/dare/todos
however, when I try to filter the list from the URL, it always returns the full list, e.g.
http://www.dnndev.me/DesktopModules/framework/api/breeze/dare/todos?=DareId%20eq%204
I think it is something to do with the way I have declared the MapHTTRoute. The problem is that DotNetNuke modules do not have a Global.ascx. I have copied the BreezeWebApiconfig.cs file into my App_Start folder and this does fire when I debug, however DotNetNuke uses mechanism for registering routes:
using DotNetNuke.Web.Api;
namespace SmartThinker.Modules.Framework
{
public class RouteMapper : IServiceRouteMapper
{
public void RegisterRoutes(IMapRoute mapRouteManager)
{
mapRouteManager.MapHttpRoute("framework", "BreezeApi", "breeze/{controller}/{action}", new[] { "SmartThinker.Modules.Framework.Controllers" });
}
}
}
I have read up on http://www.breezejs.com/documentation/web-api-controller#note01 and http://www.breezejs.com/documentation/web-api-routing but it seems that it's something to do with the way DNN registers the routes. Is there anyway to do this without using BreezeWebApiConfig.cs?
My controller code has the BreezeController attribute. (When I do connect the sample client to it I do get a list of items - it just does not filter, so I think it is something to with the OData Action filters. How can I debug where the problem is?
Update 1)
Here is the metadata:
http://www.ftter.com/desktopmodules/framework/api/dare/metadata
The GetUsers method:
http://www.ftter.com/desktopmodules/framework/api/dare/getusers
and the GetUsers method trying to filter by UserID (which doesn't work, which is the issue)
http://www.ftter.com/desktopmodules/framework/api/dare/getusers?=UserID%20eq%204
http://www.ftter.com/desktopmodules/framework/api/dare/GetUsersWithoutCors?=UserID%20eq%204 (this returns IQueryable)
Here is the controller:
[BreezeController]
public class DareController : DnnApiController
{
private readonly EFContextProvider<FrameworkContext> contextProvider = new EFContextProvider<FrameworkContext>();
[AllowAnonymous]
[HttpGet]
public HttpResponseMessage Metadata()
{
var response = Request.CreateResponse(HttpStatusCode.OK, contextProvider.Metadata());
return GetResponseWithCorsHeader(response);
}
[AllowAnonymous]
[HttpGet]
public HttpResponseMessage GetUsers()
{
var userInfoController = new UserInfoController();
var response = Request.CreateResponse(HttpStatusCode.OK, userInfoController.GetUsers());
return GetResponseWithCorsHeader(response);
}
[AllowAnonymous]
[HttpGet]
public IQueryable<User> GetUsersWithoutCors()
{
return contextProvider.Context.Users;
}
}
The routing is not really a Breeze issue. How your server routes requests to your controller is up to you. What we do out-of-the-box is just one way among innumerable many.
You have the [BreezeController] attribute on your controller yes? Can you put a sample endpoint up where we could hit it. Might get some clues from that. Also post the controller. A tiny example should do ... something returning metadata and one method returning IQueryable.
Update 25 Jun 2013
I think you've discovered a bug in the way our [BreezeController] discovers methods returning IQueryable<T>.
The [BreezeController] attribute scans your Web API controller methods and (in effect) applies the [BreezeQueryable] attribute to methods returning IQueryable<T>.
[BreezeQueryable] is an extension of the Web API's [Queryable] that adds support for $select, $expand, and nested $orderby ... all missing from the current [Queryable].
I see now that your GetUsers() method returns HttpResponseMessage rather than IQueryable<User>. Let's assume that the userInfoController.GetUsers() method inside your method returns IQueryable<User>. Otherwise, the OData query parameters will not apply and we'll have to take this in a different direction. Moving along ...
I checked with v.1.3.6 of the Breeze.WebApi.dll and it does not detect that the HttpResponseMessage is wrapping IQueryable<T>. Therefore, it does not apply the client's OData query criteria (or any other OData modifiers for that matter). This shortcoming (in my opinion) is a bug. The following should be equivalent implementations:
[HttpGet]
public IQueryable<TodoItem> Todos() {
return _repository.Todos;
}
[HttpGet]
public HttpResponseMessage TodosWrapped()
{
return Request.CreateResponse(HttpStatusCode.OK, _repository.Todos);
}
The second, "wrapped" method does not respect the OData query parameters.
Fortunately, there is a workaround until we get this fixed. Just add the [BreezeQueryable] attribute explicitly ... as in:
[HttpGet]
[BreezeQueryable]
public HttpResponseMessage TodosWrapped()
{
return Request.CreateResponse(HttpStatusCode.OK, _repository.Todos);
}
I confirmed that this approach does work.
Thanks for finding this.
Use OData query syntax
A colleague also noticed that your query URL does not use the OData query syntax. You wrote:
... /todos?=DareId%20eq%204
when it should be
... /todos/?$filter=DareId%20eq%204
Make sure you use ?$filter=

How do I bind a URL parameter with a period in it to an MVC action parameter?

A web browser is calling my action with the following URL;
Request URL:http://localhost:4000/MyController/UrlCheck?Menu.Url=sometext
My action is as follows;
public JsonResult UrlCheck(string Url)
{
return Json("Url is " + Url , JsonRequestBehavior.AllowGet);
}
But the Url parameter never gets bound, I've tried the following to no avail;
public JsonResult UrlCheck([Bind(Prefix="Menu")] string Url)
The URL is generated by MVC itself as part of an Ajax post and is tied to a property of a complex object, hence the 'Menu.Url' bit. It won't be easy to change the name of the URL parameter.
Have also tried Menu_Url as a parmeter name. The action is executed so the routing should be working fine.
I haven't come across binding get parameters like that, but I would try binding to a simple viewmodel that is named Menu and has a property called Url.
E.g.
Your Viewmodel
public class SimpleViewModel
{
public string Url { get; set; }
}
Your Action
public JsonResult UrlCheck(SimpleViewModel Menu)
{
return Json("Url is " + Menu.Url, JsonRequestBehavior.AllowGet);
}

Resources