I have two methods like this
public class ProductController : ApiController
{
public Product GetProductById(int id)
{
var product = ... //get product
return product;
}
public Product GetProduct(int id)
{
var product = ... //get product
return product;
}
}
When I call url: GET http://localhost/api/product/1 . I want the first method is invoked, not the second method.
How can I do that ?
You need unique URIs. You can modify your route to get this:
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Now you can access your API like this:
http://localhost/api/product/GetProductById/1
http://localhost/api/product/GetProduct/1
I've written a little introduction to ASP.NET Web API which shows some of the differences to WCF Web API.
You can also add a default action, e.g. the one the lists all products so you can do something like this:
http://localhost/api/product/ // returns the list without specifying the method
and the other one is invoked this way
http://localhost/api/product/byid/1 // returns the list without specifying the method
What I do is having a ProductsController and a ProductController.
ProductsController is responsible for operations on Collections of T (get all) and ProductController is responsible for operations on T (like getting a specific one).
Related
Upgrading to asp.net core 2.2 in my hobby project there is a new routing system I want to migrate to. Previously I implemented a custom IRouter to be able to set the controller for the request dynamically. The incoming request path can be anything. I match the request against a database table containing slugs and it looks up the a matching data container class type for the resolved slug. After that I resolve a controller type that can handle the request and set the RouteData values to the current HttpContext and passing it along to the default implementation for IRouter and everything works ok.
Custom implementaion of IRouter:
public async Task RouteAsync(RouteContext context)
{
var requestPath = context.HttpContext.Request.Path.Value;
var page = _pIndex.GetPage(requestPath);
if (page != null)
{
var controllerType = _controllerResolver.GetController(page.PageType);
if (controllerType != null)
{
var oldRouteData = context.RouteData;
var newRouteData = new RouteData(oldRouteData);
newRouteData.Values["pageType"] = page.PageType;
newRouteData.Values["controller"] = controllerType.Name.Replace("Controller", "");
newRouteData.Values["action"] = "Index";
context.RouteData = newRouteData;
await _defaultRouter.RouteAsync(context);
}
}
}
A controller to handle a specific page type.
public class SomePageController : PageController<PageData>
{
public ActionResult Index(PageData currentPage)
{
return View("Index", currentPage);
}
}
However I got stuck when I'm trying to figure out how I can solve it using the new system. I'm not sure where I'm suppose to extend it for this behavior. I don't want to turn off the endpoint routing feature because I see an opportunity to learn something. I would aso appreciate a code sample if possible.
In ASP.NET 3.0 there is an new dynamic controller routing system. You can implement DynamicRouteValueTransformer.
Documentation is on the way, look at the github issue
Can we use dashes (-) in the Route template in ASP.NET Core?
// GET: api/customers/5/orders?active-orders=true
[Route("customers/{customer-id}/orders")]
public IActionResult GetCustomerOrders(int customerId, bool activeOrders)
{
.
.
.
}
(The above code doesn't work)
The route parameters usually directly map to the action's variable name, so [Route("customers/{customerId}/orders")] should work since that's the name of your variable (int customerId).
You don't need dashes there, the part within the curly braces {} will never appear as a part of the generated url, it will always be replaced by the content you pass from browser or the variables you pass to the url generator.
customers/{customerId}/orders will always be customers/1/orders when customerId is set to 1, so there's no point trying to force it to {customer-id}.
However, you can try public
[Route("customers/{customer-id}/orders")]
IActionResult GetCustomerOrders([FromRoute(Name = "customer-id")]int customerId, bool activeOrders)
to bind the customerId from a unconventional route name, if you wish. But I'd strongly advise against it, as it just adds unnecessary code which has absolutely zero-effect on your generated urls.
The above generates (and parses) the exactly same url as
[Route("customers/{customerId}/orders")]
IActionResult GetCustomerOrders(int customerId, bool activeOrders)
and is much more readable code.
For the query part, as you figured it out in the comments, it makes sense to add the dashes via [FromQuery(Name = "active-orders")] bool activeOrders, since that really affects the generated url.
New in ASP.NET Core 2.2
In ASP.NET Core 2.2 you'll get a new option to 'slugify' your routes (only supported when using the new Route Dispatcher instead of the default Mvc Router).
A route of blog\{article:slugify} will (when used with Url.Action(new { article = "MyTestArticle" })) generate blog\my-test-article as url.
Can also be used in default routes:
routes.MapRoute(
name: "default",
template: "{controller=Home:slugify}/{action=Index:slugify}/{id?}");
For further details see the ASP.NET Core 2.2-preview 3 annoucement.
Just expanding on Tseng answer to the question. for ASP NET CORE to use "slugify" transformer you need to register it first like so:
public class SlugifyParameterTransformer : IOutboundParameterTransformer
{
public string TransformOutbound(object value)
{
if (value == null) { return null; }
return Regex.Replace(value.ToString(),
"([a-z])([A-Z])",
"$1-$2",
RegexOptions.CultureInvariant,
TimeSpan.FromMilliseconds(100)).ToLowerInvariant();
}
}
and then in Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddRouting(options =>
{
options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer);
});
}
Code from Microsoft
I have an action in my ASP.Net Core WebAPI Controller which takes one parameter. I'm trying to configure it to be able to call it in following forms:
api/{controller}/{action}/{id}
api/{controller}/{action}?id={id}
I can't seem to get the routing right, as I can only make one form to be recognized. The (simplified) action signature looks like this: public ActionResult<string> Get(Guid id). These are the routes I've tried:
[HttpGet("Get")] -- mapped to api/MyController/Get?id=...
[HttpGet("Get/{id}")] -- mapped to api/MyController/Get/...
both of them -- mapped to api/MyController/Get/...
How can I configure my action to be called using both URL forms?
if you want to use route templates
you can provide one in Startup.cs Configure Method Like This:
app.UseMvc(o =>
{
o.MapRoute("main", "{controller}/{action}/{id?}");
});
now you can use both of request addresses.
If you want to use the attribute routing you can use the same way:
[HttpGet("Get/{id?}")]
public async ValueTask<IActionResult> Get(
Guid id)
{
return Ok(id);
}
Make the parameter optional
[Route("api/MyController")]
public class MyController: Controller {
//GET api/MyController/Get
//GET api/MyController/Get/{285A477F-22A7-4691-AA51-08247FB93F7E}
//GET api/MyController/Get?id={285A477F-22A7-4691-AA51-08247FB93F7E}
[HttpGet("Get/{id:guid?}"
public ActionResult<string> Get(Guid? id) {
if(id == null)
return BadRequest();
//...
}
}
This however means that you would need to do some validation of the parameter in the action to account for the fact that it can be passed in as null because of the action being able to accept api/MyController/Get on its own.
Reference Routing to controller actions in ASP.NET Core
I have an OData (v3) Web API 2 project that is a wrapper to another wcf web service. The intended client for this odata connection is SharePoint 2013. I am creating CRUD operations within this wrapper and noticed that when sharepoint is asked to delete something it send a request in this format: /Entity(Identity=XX) instead of it's normal /Entity(XX) that i have working normally. I need to be able to handle that request without breaking the other one. Here is my code:
public IHttpActionResult GetSchool([FromODataUri] int key, ODataQueryOptions<School> queryOptions)
{
// validate the query.
try
{
queryOptions.Validate(_validationSettings);
}
catch (ODataException ex)
{
return BadRequest(ex.Message);
}
SchoolDataSource data = new SchoolDataSource();
var result = data.GetByID(key);
return Ok<School>(result);
//return StatusCode(HttpStatusCode.NotImplemented);
}
This works fine for a request for /Schools(1), but not for /Schools(ID=1). i have tried adding:
[Route("Schools(ID={key}")]
And this makes the /Schools(ID=1) route work, but breaks pretty much everything else (406 Errors). i tried adding the above attribute and
[Route("Schools({key})")]to see if i can get them both working, but it doesn't function correctly either. I am very new to this, and was hoping to at least get some direction. Here is my WebApiConfig:
config.MapHttpAttributeRoutes();
config.EnableQuerySupport();
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
// Web API configuration and services
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<School>("Schools");
builder.DataServiceVersion = new Version("2.0");
config.Routes.MapODataRoute("odata", null, builder.GetEdmModel());
// Web API routes
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Errors i get:
406 if i have the route attribute set. 500 if i dont have the route attribute set. it seems as though my service has no idea how to handle the parameter unless i specify it, but if i do, all calls get 406 errors.
may not be the best approach, but made it work with this class:
public class SharePointRoutingConvention : EntitySetRoutingConvention
{
public override string SelectAction(ODataPath odataPath, HttpControllerContext context,
ILookup<string, HttpActionDescriptor> actionMap)
{
//Gets the entity type
IEdmEntityType entityType = odataPath.EdmType as IEdmEntityType;
//makes sure the format is correct
if (odataPath.PathTemplate == "~/entityset/key")
{
//parses out the path segment (Identity=X)
KeyValuePathSegment segment = odataPath.Segments[1] as KeyValuePathSegment;
//Gets the verb from the request header
string actionName = context.Request.Method.ToString();
// Add keys to route data, so they will bind to action parameters.
KeyValuePathSegment keyValueSegment = odataPath.Segments[1] as KeyValuePathSegment;
//Checks to see if the "Identity=" part is in the url
if (keyValueSegment.Value.Contains("Identity="))
{
//removes the extra text
context.RouteData.Values[ODataRouteConstants.Key] = keyValueSegment.Value.Replace("Identity=", "");
}
else
{
//parses it normally
context.RouteData.Values[ODataRouteConstants.Key] = keyValueSegment.Value;
}
//returns the verb
return actionName;
}
// Not a match.
return null;
}
}
and make the change to the webapiconfig:
var conventions = ODataRoutingConventions.CreateDefault();
//adding the custom odata routing convention
conventions.Insert(0, new SharePointRoutingConvention());
config.Routes.MapODataRoute(
routeName: "odata",
routePrefix: null,//this is so that you can type the base url and get metadata back (http://localhost/)
model: builder.GetEdmModel(),
pathHandler: new DefaultODataPathHandler(),
routingConventions: conventions //this assigns the conventions to the route
);
how I can implement default product in each category in mvc4 using routing?
You'll want to set up your routes like this:
routes.MapRoute("Category Route",
"categories/{category}/{product}",
new {
Controller = "Categories",
Action = "ShowCategory",
Category = "Books",
Product = ""
});
Then, your controller action method could look like this:
public CategoriesController : Controller {
public ActionResult ShowCategory(String category, String product) {
// rest of code goes here
}
}
Then inside your action you can design your repository to check String.IsNullOrEmpty(product) and determine the default one based on a database flag or however you want to indicate that.