Swagger not handling .net core optional parameter correctly - swagger

I've got a Web API controller method that has an optional parameter like so:
[HttpGet]
[Route("getOrder/{price?}")]
public async Task<IEnumerable<Product>> Get(int? price = null)
{
}
However, when running the API, the Swagger UI shows the parameter as required and I can't call the method without the parameter. Why is this and how can I resolve it so that it's handle as an optional parameter?

An alternative to #CodingMytra's suggestion if you don't want to modify any SwaggerGen configurations could be to break the GET Web API controller into two distinct methods: one where the price is required in the route and another where it is an optional query parameter
As an example:
// endpoint #1 with required route param
[HttpGet]
[Route("Products/getOrder/{price}")]
public async Task<IEnumerable<Product>> Get1(int price)
{
//...
}
// endpoint #2 with optional params passed in via query string
[HttpGet]
[Route("Products/getOrder")]
public async Task<IEnumerable<Product>> Get2([FromQuery]int price)
{
//...
}
Which will produce the following Swagger documentation:
Products/getOrder/{price} and Products/getOrder.
The benefit of using query params being that you can reduce or expand search criteria in the future without changing the endpoint route.

Related

ASP.NET - Different behavior of nullable parameter binding in Web API and MVC controller actions

Recently I realized that there is a difference between nullable type parameter binding by Web API and MVC.
The following URI GET http://localhost/api/test does not invoke
the below Web API action method but GET http://localhost/api/test/1 does.
public class TestController : ApiController {
public string Get(int? id) {
return "sampleApp";
}
}
and returns the below error
<Error>
<Message>
No HTTP resource was found that matches the request URI 'http://localhost/api/test'.
</Message>
<MessageDetail>
No action was found on the controller 'Test' that matches the request.
</MessageDetail>
</Error>
But both the GET http://localhost/test/Get (with id = null) and GET http://localhost/test/Get/1 (with id = 1) does invoke the below MVC action method and displays the view.
public class TestController : Controller {
public ActionResult Get(int? id) {
ViewBag.Message = "Your app page.";
return View();
}
}
I have explored on MVC routing and learned that a nullable type parameter will be treated as an optional parameter in MVC and hence it will not be considered in the binding process.
But the same does not holds true for Web API which in turn needs an explicit setup for optional parameters.
Is there any reason for this different behavior between Web API and MVC for nullable type parameter binding.

OData Web API routing

I have a web API exposing ODATA from a SQL stored proc. I want to use a url like /odata/firmhierarchy(225) to pass 225 into a param for the stored proc. It just tells me that it can't find a matching resource. It hits the controller, just skips the method. Thoughts?
In webapiconfig
private static IEdmModel GenerateEdmModel()
{
var builder = new ODataConventionModelBuilder();
builder.EntitySet<Employee>("Employees");
builder.EntitySet<Employee>("FirmHierarchy");
return builder.GetEdmModel();
}
Context:
public virtual ObjectResult<Employee> sp_EmployeeHierarchy(Nullable<int> managerEmpID)
{
var managerEmpIDParameter = managerEmpID.HasValue ?
new SqlParameter("ManagerEmpID", managerEmpID) :
new SqlParameter("ManagerEmpID", 0);
return ((IObjectContextAdapter)this).ObjectContext.ExecuteStoreQuery<Employee>("sp_EmployeeHierarchy #ManagerEmpID", managerEmpIDParameter);
}
Only method in controller:
[Queryable]
public IQueryable<Employee> GetFirmHierarchy()
{
return db.sp_EmployeeHierarchy(225).AsQueryable();
//return SingleResult.Create(db.Employees.Where(employee => employee.EmpId == key));
}
This should work:
1.Write another method in your controller:
[EnableQuery]
public IQueryable<Employee> Get([FromODataUri] int key)
{
return db.sp_EmployeeHierarchy(key).AsQueryable();
}
Please note that [EnableQuery] is an attribute introduced in Web API for OData V4. If you are still using Web API for OData V1-3, use [Queryable] still.
2.Then you can send the request
GET /odata/firmhierarchy(225)
and get the employees.
I was able to make ODATA work for a table, when auto-generated from entity framework. However, that generation process didn't want to work for a complex type returned by a Table Valued Function (similar scenario to a SP), because it didn't seem to understand where the key was.
What I found was that I could however make it work. First, I check out this article. He sets things up a bit more manually, where his Get on a companyProcessingController ends up routing for id 3 as "http://localhost:10020/odata/companyProcessing(3)" .
This surprised me. My other generated classes set up the pattern that SomeEntity became SomeEntityController, with methods like GetSomeEntities, and a routing that seemed to me to match the method but dropping the word get. Therefore, dropping the entity name from the Get method name seemed different, but it worked. Proving that the path is actually matching the controller name, not the method name.
In this Case you configure the routing using the data type you're querying for, and the beginning of the controller name. Then the actual path utilizes the beginning of the controller name as well.
And then all of this just brings us essentially to the other posted solution, assuming your controller name is firmhierarchyController
So, now, making sense of this... Try going to http://localhost:55063/odata/$metadata , where your port may differ. You'll notice that ODATA exposes a DataType, which is accessed via a DataSet. When a client tries to query into ODATA, they are trying to query against the DataSet, getting items of the DataType.
The DataSet matching the controller name (less Controller), and the Get methods can indeed just be Get without further extension of the name - and otherwise in this scenario was giving me problems.

Order property in System.Web.Http.AuthorizeAttribute

If I have multiple authorization attributes on an action, my understanding is that for System.Web.Mvc.AuthorizeAttribute I can specify the Order property like:
[CustomAuth(Order=2)]
[CustomAuth(Order=1)]
public ActionResult Get() { }
But this doesn't exist in the authorize attribute in the Web API. How do I order the execution of the attributes in the Web API?
Also, does the attribute at the class level always take precedence over the attribute that decorates the action?
I can answer one of your questions.
Also, does the attribute at the class level always take precedence
over the attribute that decorates the action?
ApiController.ExecuteAsync() runs the list of filters gotten from HttpActionDescriptor.GetFilterPipeline(). Here is the comment given for GetFilterPipeline().
///Returns the filters for the given configuration and action. The filter
///collection is ordered according to the FilterScope (in order from
///least specific to most specific: First, Global, Controller, Action)
So, the gloabl filters run first, followed by controller level and then action level filters.
As far as your other question on how to order, I don't have a clear answer though. I understand the filters (attributes) are retrieved using Type.GetCustomAttributes(). This method does not guarantee any order but it usually returns in the reverse order. For example, if you have an action method like this,
[CustomAuth(Name="1")]
[CustomAuth(Name="2")]
public HttpResponseMessage Get()
{
}
the filter with Name="2" comes first in the list followed by "1" in the list returned by typeof(YourApiController).GetCustomAttributes(). If I were you, I'll not make any assumptions about this order. I'd much rather have one Authorization filter at the action method level and run the logic in the order I want it.
Anyways, if you add two global authz filters like
config.Filters.Add(new CustomAuth() { Name = "g1" });
config.Filters.Add(new CustomAuth() { Name = "g2" });
and have a controller like
[CustomAuth(Name="c1")]
[CustomAuth(Name="c2")]
public class ValuesController : ApiController
{
[CustomAuth(Name="1")]
[CustomAuth(Name="2")]
public HttpResponseMessage Get()
{
}
}
the filters are run in this order: g1, g2, c2, c1, 2, and 1.

How can I overload ASP.NET MVC Actions based on the accepted HTTP verbs?

Wanted to use the same URL for a GET/PUT/DELETE/POST for a REST based API, but when the only thing different about the Actions is which HTTP verbs it accepts, it considers them to be duplicate!
"Type already defines a member called 'Index' with the same parameter types."
To which I said, so what? This one only accepts GET, this one only accepts POST... should be able to be co-exist right?
How?
That's not ASP.NET MVC limitation or whatever. It's .NET and how classes work: no matter how hard you try, you cannot have two methods with the same name on the same class which take the same parameters. You could cheat using the [ActionName] attribute:
[HttpGet]
[ActionName("Foo")]
public ActionResult GetMe()
{
...
}
[HttpPut]
[ActionName("Foo")]
public ActionResult PutMe()
{
...
}
[HttpDelete]
[ActionName("Foo")]
public ActionResult DeleteMe()
{
...
}
[HttpPost]
[ActionName("Foo")]
public ActionResult PostMe()
{
...
}
Of course in a real RESTFul application the different verbs would take different parameters as well, so you will seldom have such situations.
You may take a look at SimplyRestful for some ideas about how your routes could be organized.
While ASP.NET MVC will allow you to have two actions with the same name, .NET won't allow you to have two methods with the same signature - i.e. the same name and parameters.
You will need to name the methods differently use the ActionName attribute to tell ASP.NET MVC that they're actually the same action.
That said, if you're talking about a GET and a POST, this problem will likely go away, as the POST action will take more parameters than the GET and therefore be distinguishable.
So, you need either:
[HttpGet]
public ActionResult ActionName() {...}
[HttpPost, ActionName("ActionName")]
public ActionResult ActionNamePost() {...}
Or:
[HttpGet]
public ActionResult ActionName() {...}
[HttpPost]
public ActionResult ActionName(string aParameter) {...}
Another option is to have a single method that accepts all and distinguishes between HttpMethod and calls the appropriate code from there. E.g.
string httpMethod = Request.HttpMethod.ToUpperInvariant();
switch (httpMethod)
{
case "GET":
return GetResponse();
case "POST":
return PostResponse();
default:
throw new ApplicationException(string.Format("Unsupported HttpMethod {0}.", httpMethod));
}
As a workaround you can add to one of the methods an extra argument with a default value, just to bypass the limitation and be able to build.
Of course take in mind that this is not the most recommended way of doing things, and also you will have to make clear in your code (by the parameter name or via comments) that this is an extra argument just to allow it to build, and of course make sure that you have decorated your attributes correctly.

How to route legacy QueryString parameters in ASP.Net MVC 3?

I am using a third party service that does an async callback to a URL I provide to them.
So I tell them to use http://www.mysite.com/Status/Incoming.
This must obviously map to an Incoming() method on my StatusController.
However, what I don't have control over is the format of the parameters they call my URL with.
E.g. They will do a callback such as: http://www.mysite.com/Status/Incoming?param1=val1&param2=val2&param3=val3
I want to map this to the parameters of my action method: Incoming(string param1, string param2, int param3)
How do I do this?
I have found a lot of stuff about custom routing, but nothing about legacy QueryString parameters.
There is no such thing as legacy query string parameters. There are query string parameters and they are part of the HTTP specification. And assuming that the http://www.mysite.com/Status/Incoming?param1=val1&param2=val2&param3=val3 url is called you don't need any route to make it map to the following action (the default route will do just fine):
public ActionResult Incoming(string param1, string param2, string param3)
{
...
}
The default model will take care of binding those values.
Why not use a catch all?
routes.MapRoute(
"Incoming",
"Status/Incoming/{*path}", // URL with parameters
new { controller = "Status", action = "Incoming"}
);
then in your controller,
public ActionResult Incoming(string path){
// the next line would probably be better off in a model binder, but this works:
var dictionary = path
.Substring(path.IndexOf("?")+1)
.Split("&")
.Select(x =>
{
var kvArray = x.Split("=");
return new KeyValuePair<string, string>(kvArray[0], kvArray[1]);
})
.ToDictionary(x=>x.Key,x=>x.Value);
return Incoming(dictionary);
}
public ActionResult Incoming(Dictionary<string,string> dictionary){
//do stuff
}
All that being said, I think using the Request.QueryString is probably a better approach. As long as you are using MVC, it is accessible from your controller. However, if you can guarantee that the correct parameters will be passed then Darin's approach is going to be the best choice.
When I've had to deal with this before now, I just use the "legacy" call of Request.QueryString. It still works, even if it isn't very graceful.

Resources