I want to make the "middle" route parameter optional in Azure functions. Ex:
public static HttpResponseMessage MyFunction([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "SomeRoute/{MyOptionalRoute=int?}/AnotherRoute")]HttpRequestMessage req, TraceWriter log,
int MyOptionalRoute = 0)
{
//some magic
}
This method works if i give MyOptionalValue a value. For example : /SomeRoute/123/AnotherRoute
But returns a 404 if i dont: Ex: /SomeRoute/AnotherRoute
Do anyone know if there is a way to get around this so that i dont have to create two seperate functions? I have been looking around and all i see is people using the optional route parameter as the last parameter in the sequence. Maybe i just dont know what keywords to google or is it just not possible?
Appreciate all help i can get.
Thanks.
As you have found, Azure function doesn't support optional “middle” route parameter yet. Only consecutive optional parameter like SomeRoute/{MyOptionalRoute:int?}/{AnotherRoute:int?} works.
Back to the point, find a workaround with proxy for function, see whether it meets your requirement.
Add a proxies.json to your function project, change file property copy to output directory to copy if newer.
See content below, I use 0 as the reserved number as the alternative of null value. Proxy directs http://localhost/api/SomeRoute/AnotherRoute to the real url http://localhost/api/SomeRoute/0/AnotherRoute, which matches the pattern of SomeRoute/{MyOptionalRoute:int}/AnotherRoute.
{
"$schema": "http://json.schemastore.org/proxies",
"proxies": {
"proxy1": {
"matchCondition": {
"methods": [ "GET" ],
"route": "/api/SomeRoute/AnotherRoute"
},
"backendUri": "http://localhost/api/SomeRoute/0/AnotherRoute"
}
}
}
Related
When I use HttpGet(...), intellisense tells me that besides the first argument, i.e. pattern, I also have name and order. While the latter is obvious to me, I got a bit uncertain on what the parameter name had as its purpose.
Heading over to the docs, I see that the constructor of HttpGet only declares a single parameter. That confused me and I suspect that I'm missing something or using the Framework's version instead of Core, or something.
As I can see the biggest advantage of the Name property of the HttpMethodAttribute (which is the base class of HttpGetAttribute) is that you can distinguish method overloads:
[HttpGet(Name="ById"]
public IActionResult GetBy(int id)
{
}
[HttpGet(Name="ByExternalId"]
public IActionResult GetBy(Guid id)
{
}
So, this can contribute in the route selection.
EDIT: I've revised answer
The above sample code would result in an AmbiguousMatchException, where it is stating the same Template has been registered for different action.
I had put together another sample and used the following RouteDebugger to get insight. In the Configure method I've called the app.UseRouteDebugger() method to be able to see the registered routes in json format under the /route-debugger url.
[Route("api/[controller]")]
[ApiController]
public class TestController : ControllerBase
{
[HttpGet()]
public IActionResult GetA(string a)
{
return Ok(nameof(GetA));
}
[HttpGet(template: "GetB")]
public IActionResult GetB(string b)
{
return Ok(nameof(GetB));
}
[HttpGet(template: "GetC", Name= "GetD")]
public IActionResult GetD(string d, string e)
{
return CreatedAtRoute(routeName:"GetC", routeValues: new { c = "v"}, value: null);
}
[HttpGet(template: "GetC/{c}", Name = "GetC")]
public IActionResult GetC(string c)
{
return Ok(nameof(GetC));
}
}
The route table would look like this:
[
{
"Action":"GetA",
"Controller":"Test",
"Name":null,
"Template":"api/Test",
"Contraint":[{}]
},
{
"Action":"GetB",
"Controller":"Test",
"Name":null,
"Template":"api/Test/GetB",
"Contraint":[{}]
},
{
"Action":"GetD",
"Controller":"Test",
"Name":"GetD",
"Template":"api/Test/GetC",
"Contraint":[{}]
},
{
"Action":"GetC",
"Controller":"Test",
"Name":"GetC",
"Template":"api/Test/GetC/{c}",
"Contraint":[{}]
}
]
As you seen the following happened:
GetA method
It is exposed under the controller route because not Template has been specified.
The route itself does not have a Name so you can't refer to this route via its name inside ActionLink or CreatedAtRoute, etc.
GetB method
It is exposed under the api/test/getb, because the controller's and the action's Template properties are combined.
The route itself does not have a Name so you can't refer to this route via its name inside ActionLink or CreatedAtRoute, etc.
GetC method
It is exposed under the api/test/getc/{c}, because the controller's and the action's Template properties are combined. The c parameter can accept any value. If it is not provided then GetD will be called, because that was registered first.
The route has a Name (GetC) so you can refer to this route via its name inside ActionLink or CreatedAtRoute, etc. Like as we did it inside GetD
GetD method
It is exposed under the api/test/getc, because the controller's and the action's Template properties are combined. Because it was registered prior GetC method's route that's why it will called if no further path is provided.
The route has a Name (GetD). In the CreatedAtRoute we are referring to GetC via its name not through its route. If we are replacing the Name to GetC then it would throw the following exception at runtime:
InvalidOperationException: The following errors occurred with
attribute routing information:
Error 1: Attribute routes with the same name 'GetC' must have the same
template: Action: 'Controllers.TestController.GetD ()' - Template:
'api/Test/GetC' Action: 'Controllers.TestController.GetC ()' -
Template: 'api/Test/GetC/{c}
Summary
If you register two routes with the same Template it will throw an AmbiguousMatchException at runtime when you make a call against that route. (So, other routes will work perfectly fine)
If you register two routes with the same Name it will throw an InvalidOperationException at runtime when you make any call. (So, other routes will not work)
Route's Name provides the ability to refer them easily without knowing the exact Template. This separation allows us to change the Template without affecting referring links.
Name is the route name, as distinct from the template. See the docs page.
The docs state:
Route names can be used to generate a URL based on a specific route. Route names:
Have no impact on the URL matching behavior of routing.
Are only used for URL generation.
Route names must be unique application-wide.
You can use the name to generate URLs for named routes using IUrlHelper. For example, if you named a route "Konrad", you could generate a link like so:
string url = urlHelper.Link("Konrad", new { id = 5, query = "test" });
Where id and query are the route parameters.
By the way, the issue you had with the documentation is that HttpGet is an Attribute. The attribute syntax allows you to specify values for the attributes' properties by name after any positional constructor values.
Considering the following attribute, you can see that the constructor accepts int a, but there's also a string propery: B.
public class TestAttribute : System.Attribute
{
public TestAttribute(int a)
{
}
public string B {get;set;}
}
To use such an attribute, you could apply it in the following ways:
[TestAttribute(5)] // B is null
[TestAttribute(5, B = "hello")] // B is "hello"
or simply as Test:
[Test(5)] // B is null
[Test(5, B = "hello")] // B is "hello"
It says they can be used to generate a URL based on a specific route. I don't think I understand what it means (despite understanding the words as such). Please elaborate
Here are my two cents:
When designing a proper API, the level 3 of maturity model talks about HATEOAS constraint (Hypermedia As Transfer Engine Of Application State). More information about it can be found at : Maturity models of Rest API
In order to traverse through a resource and to know the actions that are available with that resource we generate links for the resource.
The generation of links is done with the help of URLHelper library, which takes in a name parameter to define the link. The name we associate with in the URLHelper library, is the name defined for HTTP GET/POST/PUT etc action using the attribute Name parameter.
In simple terms, it is an identifier for your Route.
P.S
A complete working example of a web api with HATEOAS can be found on github:
Web Api with HATEOAS example
After some searching, managed to find at least somewhat clear simple example with explanation, see section 5.5.2 here: https://livebook.manning.com/book/asp-net-core-in-action/chapter-5/254
The linked section contains two pieces of code, from which it is obvious, besides others already have mentioned:
You can have route template with parameters, and even provide template within Http verb template [Http...], but...
If needed to generate links to be returned to the client, to avoid some problems, it is easier to use the route name as explained here: https://csharp-video-tutorials.blogspot.com/2017/02/generating-links-using-route-names-in.html
About "URL generation", instead of hard-coded routes for certain API resources on the client side (e.g., details for the user with certain id value), Controller can send URL for each user, so the client doesn't need to have any logic about creating those URLs. Also, what about multimedia resources (images, videos, etc.), or any resources that have dynamically created URLs for protection purposes? Client side shouldn't know anything about how to construct those URLs, just obtain them from the Controller. In that way, it is not needed to have all URLs (or routes) predefined on both client and server side.
I think this and this could help.
Route name
The following code defines a route name of Products_List:
[ApiController]
public class Products2ApiController : ControllerBase
{
[HttpGet("/products2/{id}", Name = "Products_List")]
public IActionResult GetProduct(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
Route names can be used to generate a URL based on a specific route. Route names:
Have no impact on the URL matching behavior of routing.
Are only used for URL generation.
Route names must be unique application-wide.
Contrast the preceding code with the conventional default route, which defines the id parameter as optional ({id?}). The ability to precisely specify APIs has advantages, such as allowing /products and /products/5 to be dispatched to different actions.
In asp.net core source code I've found only this usage
[HttpGet("{id}", Name = "FindPetById")]
[ProducesResponseType(typeof(Pet), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<Pet>> FindById(int id)
{
var pet = await DbContext.Pets
.Include(p => p.Category)
.Include(p => p.Images)
.Include(p => p.Tags)
.FirstOrDefaultAsync(p => p.Id == id);
if (pet == null)
{
return new NotFoundResult();
}
return pet;
}
I have an Azure Function and I want to set a custom HTTP endpoint. Following the answer to this SO question, I ended up with something like this:
[FunctionName("DoSomething")]
public static async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "v1/tenants/{tenantId}/locations/{locationId}/products?manufacturer={manufacturer}")]
HttpRequest request, ILogger logger, string tenantId, string locationId, string manufacturer)
{
//
}
However, the route is not accepted by the Webjob:
"v1/tenants/{tenantId}/locations/{locationId}/products?manufacturer={manufacturer}"
The reason is because of the question mark '?':
An error occurred while creating the route with name 'DoSomething' and
template
'api/v1/tenants/{tenantId}/locations/{locationId}/products?manufacturer={manufacturer}'.
The literal section 'products?manufacturer=' is invalid. Literal sections
cannot contain the '?' character. Parameter name: routeTemplate The
literal section 'products?manufacturer=' is invalid. Literal sections
cannot contain the '?' character.
Question
How can I specify a query parameter in a custom HTTP endpoint of my Azure Function?
I am afraid it's not possible to put query parameter in Route.
Microsoft.AspNetCore.Routing: The literal section 'products?manufacturer=' is invalid. Literal sections cannot contain the '?' character.
It's a built-in restriction of ASP.NET Routing, which is used by Azure Function to build route for Http trigger.
allow me to get the value as one of the Run's method parameters instead of poking at the HttpRequest instance
If it's the reason why you want to put query parameter in route, I would suggest you add IDictionary<string, string> query in method signature and use query["manufacturer"] to access the parameter in function code. But honestly it's almost the same as request.Query["manufacturer"].
Or you may have to follow the recommendation, transform the query parameter to route like products/{productId}.
Here's an example of my function that uses query parameters:
public static async Task<HttpResponseMessage> Run( HttpRequestMessage req, TraceWriter log, ExecutionContext context )
{
string data = await req.Content.ReadAsStringAsync();
dynamic parsed = JsonConvert.DeserializeObject(data);
if (parsed == null)
{
parsed = req.GetQueryNameValuePairs().ToDictionary(kv => kv.Key, kv=> kv.Value, StringComparer.OrdinalIgnoreCase);
}
xxx
}
and then you only have to specify request parameter name to be req, i think. if you want to go the route crowcoder proposed you just specify your path in settings (integrate tag):
As you can see I dont have a route defined and it just works. I suspect you dont need to define query parameters in your route.
I've got an ASP.NET Web API project that I'm working on. I've got an APIController called SpellbookController that has basic CRUD operations to an EntityFramework repository.
However, when I try to add a method that takes a parameter that's not an id, I can't seem to get it to route correctly.
Here's what it looks like:
// GET: api/Spellbooks/user#email.com
[ResponseType(typeof(List<Spellbook>))]
[Route("api/Spellbooks/{username}")]
public IHttpActionResult GetSpellbook(string username)
{
List<Spellbook> spellbooks = db.Spellbooks.Where(x=>x.Username == username).ToList();
if (spellbooks == null)
{
return NotFound();
}
return Ok(spellbooks);
}
So I'm trying to hit http://localhost:xxxx/api/Spellbooks/emailaddress,
but I get a 404 every time.
It's definitely an APIController, and I have config.MapHttpAttributeRoutes(); turned on.
What am I missing?
Where is your username parameter?
Your call should looks like this-
http://localhost:xxxx/api/Spellbooks/emailaddress/David
Update
Try to declare the parameter as string {username:string}
Update 2
The problem is with your '.' as you can see in this link.
You can send the email without the point in the parameter and then replace ite back to you regular mode or you can use all the good advice that the link provide.
If you step through it, do you get a value when looking at username? Or is it always null?
If your route is not working, a simple way to debug the route is to make a request to http://localhost:xxxx/api/Spellbooks/?emailaddress=thisemailaddress and see if you can get a response. In fact, from a REST standard, it can be argues that it's a cleaner route, since you will be returning a collection of elements, rather than a single object.
I have seen this post: MVC Handler for an unknown number of optional parameters but it's for MVC and doesn't seem to work for me as I get an error:
A path segment that contains more than one section, such as a literal section or a parameter, cannot contain a catch-all parameter.
I want to be able to have an indeterminate amount of params in a Url, I have the following route:
RouteCollection.MapPageRoute("ManyParam", "{*params}.html", "~/Default.aspx");
This also seems to trigger the error message above.
How can I set up a route to have an unknown number of parameters in web forms (not MVC).
I am trying to achieve the following urls:
www.example.com/some-thing.html
www.example.com/some-thing/else.html
www.example.com/and/some-thing/else.html
www.example.com/1/2/3/4/5/6.html
EDIT
It seems to work when I use the following:
RouteCollection.MapPageRoute("ManyParam", "{*params}", "~/Default.aspx");
The problem is with this is that it doesn't allow the .html at the end.
Untested route below - the wildcard one have to be absolutely last portion of Url. So to force ".html" at the end you need to use constraint (5th argument).
routes.MapPageRoute(
"ManyParam",
"{*path}",
"~/Default.aspx",
false,
new RouteValueDictionary(),
new RouteValueDictionary { { "path", #".*\.html" } }
);
I'm using a simple route as
routes.MapRoute(
"Default2", // Route name
"{cliurl}/{id}", // URL with parameters
new { cliurl = "none", controller = "ABook", action = "Index", id = "none" } // Parameter defaults
);
routes.MapRoute(
"Default", // Route name
"{cliurl}/{controller}/{action}/{id}", // URL with parameters
new { cliurl = "none", controller = "ABook", action = "Index", id = "none" } // Parameter defaults
);
and when I debug the website (VS2010 SP1), I have a breakpoint in my ABook Controller, inside the Index action method witch contains only:
//
// GET: /ABook/
public ActionResult Index()
{
if (currentClient == null)
return RedirectToAction("Empty");
return View();
}
//
// GET: /Empty/
public ActionResult Empty()
{
return View();
}
The thing is that, when I insert this in the browser:
http://localhost:14951/client_name/hashed_id
I get 3 breaks in that breakpoint.
How can I see what in the world is going on? why 3 times when I just requested 1, what is exactly the browser requesting?
I can only get the Route Parameters and I do get the first correct, but 2nd and 3rd are using the default values, and I tried to navigate through the RequestContext and I can't see anything useful :(
Just want to know if there is a way to really see what's been requested.
I ended up using Glimpse
http://getglimpse.com/
http://www.balexandre.com/temp/2011-05-28_1854.png
If you have breakpoint inside controller you can use watch where you can simply create new watch. Type in Request and search it...
In every Controller there exists a property called Request. It is actually defined in System.Web.Mvc.Controller which is the superclass of all controllers. The property returns the acutal Request object as HttpRequestBase and exposes fields like InputStream, Headers, HttpMethod so on and so forth.
As for why you are hitting the index method 3 times, I'm sure that other requests made by the browser, say for example for images and javascript and other existing files, also are handled by your route defined. In short your route defenition is too generic and handles unexpected requests. You can correct this by using Route.IgnoreRoute("Path/to/Existing/Files") or by making your route more specific by adding RouteConstraints. Leave a comment if you want to know how to do that.
You can use fiddler to see what the browser requests or you could try the routdebugger download from Nuget.
I know others have sort-of made a stab at this... they are correct:
Use the Request object to find out what is being requested. It's probably something incorrectly being handled by your controller. Shovel some output while debugging from Request in that method, such as the raw url. That will likely answer the question.
As a suggestion, why not hook up the BeginRequest event handler for the application which will allow you to see every request coming through. There is also the HttpContext.Current.Request.Url object which can be inspected
// Global.asax
public MvcApplication()
{
BeginRequest += new EventHandler(MvcApplication_BeginRequest);
}
void MvcApplication_BeginRequest(object sender, EventArgs e)
{
Debug.WriteLine("[Start] Requested Url: " + HttpContext.Current.Request.RawUrl);
}