.net core API optional route parameters - .net-core

I have a .net core api with swagger. No I want to add a Filter-Class including optional filter-parameters.
[HttpGet("", Name ="get-index")]
[ProducesResponseType(typeof(IEnumerable<MyModelGet>), (int)HttpStatusCode.OK)]
[ProducesResponseType(typeof(void), (int)HttpStatusCode.NoContent)]
public IActionResult GetIndex([FromRoute] MyFilter? filter){
...
}
The properties of the filter-class are optional/nullabel:
public class MyFilter {
public int? size{
get; set;
} = null;
...
}
But in Swagger all Properties are required:
Is there any way (e.g. a Annotation) to make this fields optional?

Replacing the [FromRoute] by [FromQuery] solved my issue.

just Add '?' in HttpGet , like This :
[HttpGet("", Name ="get-index?")]

Related

Swagger UI doesn't render body parameter field for my complex type parameter in GET action of my Controller

I have an ASP.NET Web API 2 project to which I have added Swagger - Swashbuckle v5.6.0. Everything works fine. Swagger UI renders test endpoints for my API as expected.
I added a new Controller to my API. There is a GET action with a complex type parameter. For complex types, Web API tries to read the value from the message body. This is the default behaviour.
Here is my GET action:
[HttpGet]
[Route("search")]
[ResponseType(typeof(List<SearchModel>))]
public IHttpActionResult Search(SearchModel searchOptions)
{
//....
return Ok();
}
And her is my complex type:
public class SearchModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
[DataType(DataType.EmailAddress)]
[EmailAddress]
public string Email { get; set; }
public string AddressLine1 { get; set; }
public string City { get; set; }
public string Telephone { get; set; }
public string MobilePhone { get; set; }
}
The problem:
But Swagger UI doesn't render body parameter field for my complex type in the GET action. For POST and PUT actions Swagger UI renders body parameter fields as expected but not for the complex type in my GET action.
As can be seen in the screenshot Swagger UI renders query parameters fields for attributes in my complex type instead of rendering a body parameter field for my type as it does in the case of POST and PUT.
My GET action is working fine when testing from Postman and filling the json in the body of the request. By setting breakpoint in the action inside Visual Studio I can see the values are bound to my object in the action parameter.
I have tried to decorate the parameter in my action with [FromBody] (which is the default for complex type) but same result.
Is this a bug in Swagger? Or am I missing something?
Sadly, you can't do what you want with Swagger. You can't send a request model in an HTTP GET method. You can however change the swagger UI to look like this:
but you won't be able to receive the model in your controller.
This is a known issue within the Swagger developers and it was discussed in 2016 and the final decision is that swagger won't support a request body in an HTTP GET method. Here is the link to the already closed issue.
You have three options here:
Leave the method as it is, and test it in Postman, but not in Swagger.
Follow the below steps to achieve the picture above, but please note, that it will only fix the UI part and you will always end up with null SearchModel in the controller when you press Try it out! in swagger.
Make it a [HttpPost method instead of [HttpGet].
How to make swagger UI display GET method with request body:
First, create one Attribute class:
public class ModelInBodyAttribute : Attribute
{
public ModelInBodyAttribute(string modelName, string description, bool isRequired)
{
this.ModelName = modelName;
this.Description = description;
this.IsRequired = IsRequired;
}
public string ModelName { get; set; }
public bool IsRequired { get; set; }
public string Description { get; set; }
}
Then you can decorate your method in the controller:
[ModelInBody(modelName: nameof(SearchModel), description: "My model description", isRequired: true)]
[HttpGet]
[Route("search")]
[ResponseType(typeof(List<SearchModel>))]
public IHttpActionResult Search(SearchModel searchOptions)
{
//....
return Ok(new List<SearchModel>());
}
After that create IOperationFilter class (ModelInBodyOperationFilter):
public class ModelInBodyOperationFilter : IOperationFilter
{
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
{
var attribute = apiDescription.GetControllerAndActionAttributes<ModelInBodyAttribute>().FirstOrDefault();
if (attribute == null)
{
return;
}
operation.parameters.Clear();
operation.parameters.Add(new Parameter
{
name = attribute.ModelName,
description = attribute.Description,
#in = "body",
required = attribute.IsRequired,
schema = new Schema { #ref = $"#/definitions/{attribute.ModelName}" }
});
}
}
Lastly, don't forget to register the IOperationFilter in SwaggerConfig:
c.OperationFilter<ModelInBodyOperationFilter>();
When you send the request via swagger, you will notice that the Curl part is absolutely correct, but still, in your controller there is nothing.
There are endless discussions on whether you should have a PAYLOAD "Body content" in a GET request. As you mentioned it's supported by HTTP but you will find in the internet that many people suggest not to do it. I guess that swagger team also expect you not to use it.

Swagger different classes in different namespaces with same name don't work

I got (more than) two Api POST endpoints. Each one needs a json as parameter. But when I use the same class name Payload in two endpoint argument classes, Swagger does not work. When I change one of it e.g. from Payload to Payload1 than it works.
Of course I set the right namespaces into the wrapper classes so it finds it Payload. But I would love to use the same name "Payload" each time. How can I use the same class name Payload?
I can keep the json name "Payload" at both cases and just set different names for the property ("Payload1", "Payload2"). It works. But would be nice to have same property names too.,
Endpoint A
[HttpPost()]
public async Task PostPerson([FromBody]JsonWrapperA jsonWrapperA)
namespace myProject.A
{
public class JsonWrapperA
{
[JsonProperty("name", Required = Required.AllowNull)]
public string Name { get; set; }
[JsonProperty("payload", Required = Required.AllowNull)]
public Payload Payload { get; set; }
}
public class Payload
{
[JsonProperty("value", Required = Required.AllowNull)]
public double Value { get; set; }
}
}
Endpoint B
[HttpPost()]
public async Task PostCompagn([FromBody]JsonWrapperB jsonWrapperB)
namespace myProject.B
{
public class JsonWrapperB
{
[JsonProperty("compagny", Required = Required.AllowNull)]
public string Compagny { get; set; }
[JsonProperty("payload", Required = Required.AllowNull)]
public Payload Payload { get; set; }
}
public class Payload
{
[JsonProperty("age", Required = Required.AllowNull)]
public double Age{ get; set; }
}
}
By default swagger will attempt to build its Schema Ids for objects that are return types or parameter types for your APIs endpoints, and it will display these objects in the "Models" section of the documentation. It will build these schema Ids based on the class names of the objects.
When you try to have two or more classes named the same, even though they are in different namespaces, then you will get the conflicting schemaIds error:
InvalidOperationException: Conflicting schemaIds: Identical schemaIds detected for types NamespaceOne.MyClass and NamespaceTwo.MyClass. See config settings - "CustomSchemaIds" for a workaround
This means Swagger needs to be configured to change the way it generates its SchemaIds. You can simply tell swagger to use an objects fully qualified name which will include namespaces in the schemaIds. You can do this in your Startup.cs file in the ConfigureServices method like this:
//add using statement for Swagger in Startup.cs
using Swashbuckle.AspNetCore.Swagger;
...
public void ConfigureServices(IServiceCollection services)
{
services.AddSwaggerGen(config =>
{
//some swagger configuration code.
//use fully qualified object names
config.CustomSchemaIds(x => x.FullName);
}
}
Using Swashbuckle.AspNetCore Version 5.5.1 i've had the same issue so i solved it using JustSomeDude answer, but afterwards all entities were shown with the full name so i needed a way to show only the name. This is what i did:
options.CustomSchemaIds(x => x.FullName); // Enables to support different classes with the same name using the full name with namespace
options.SchemaFilter<NamespaceSchemaFilter>(); // Makes the namespaces hidden for the schemas
Using this filter class:
public class NamespaceSchemaFilter : ISchemaFilter
{
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
if (schema is null)
{
throw new System.ArgumentNullException(nameof(schema));
}
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
schema.Title = context.Type.Name; // To replace the full name with namespace with the class name only
}
}

ASP.Net OData with string keys

I am trying to use ASP.Net OData v4 (e.g. ODataController) to allow access where the key is a string. 95% of the examples out there use an integer as a key and the couple of posts I've found that discuss the steps to use a string as the key aren't working for me.
In all cases, I am trying to access my resource with the following URL:
/api/ContactTypes('Agency')
Optimistically, I started with just changing the type of the key from int to key:
public SingleResult<ContactType> Get([FromODataUri] string key)
But I get a 404 response. Changing the URL to an integer, /api/ContactTypes(1) does "work" in that it routes to the correct method and that the key is a string type, but obviously, that doesn't help me. This is the scenario described in this post: How to get ASP.Net Web API and OData to bind a string value as a key? except that that post implies that accessing the URL the way I am should work (and also is for OData v3).
After further searching, I found this article: https://blogs.msdn.microsoft.com/davidhardin/2014/12/17/web-api-odata-v4-lessons-learned/ which basically says that you have to decorate the Get method with an explicit routing:
[ODataRoute("({key})")]
public SingleResult<ContactType> Get([FromODataUri] string key)
If I do that alone, though, I get "The path template '({key})' on the action 'Get' in controller 'ContactTypes' is not a valid OData path template. Empty segment encountered in request URL. Please make sure that a valid request URL is specified."
The comments in this post (https://damienbod.com/2014/06/16/web-api-and-odata-v4-crud-and-actions-part-3/) suggest that I need to decorate the Controller with an ODataRoutePrefix:
[ODataRoutePrefix("ContactTypes")]
public class ContactTypesController : ODataController
That seems counter-intuitive since I do not have anything ASP.Net should be confusing. My controller name is already following convention and I have no Web API controllers that could be confusing it.
Regardless, it does seem to "fix" the issue in that the error goes away, but then I am right back at square one (e.g. only integer values can be passed in the URL).
What am I missing?
Full controller code:
[Authorize]
[ODataRoutePrefix("ContactTypes")]
public class ContactTypesController : ODataController
{
PolicyContext _Db;
public ContactTypesController(PolicyContext db)
{
if (db == null)
throw new ArgumentNullException("db");
this._Db = db;
}
public ContactTypesController() : this(new PolicyContext())
{
}
protected override void Dispose(bool disposing)
{
_Db.Dispose();
base.Dispose(disposing);
}
[EnableQuery]
[ODataRoute()]
public IQueryable<ContactType> Get(ODataQueryOptions options)
{
return _Db.ContactType;
}
[EnableQuery]
[ODataRoute("({key})")]
public SingleResult<ContactType> Get([FromODataUri] string key)
{
IQueryable<ContactType> result = _Db.ContactType.Where(p => p.ContactTypeKey == key);
return SingleResult.Create(result);
}
Full WebApiConfig:
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
builder.EntitySet<ContactType>("ContactTypes");
config.MapODataServiceRoute(
routeName: "ODataRoute",
routePrefix: "api",
model: builder.GetEdmModel()
);
}
1.If in your EdmModel, the string property is key, then no ODataRoute is need, for example:
public class Product
{
public string Id { get; set; }
public string Name { get; set; }
public double Price { get; set; }
}
ConventionModelBuilder will use property named "Id" as the key, or you should specify it's a key like:
public class Product
{
[Key]
public string StringKey { get; set; }
public string Name { get; set; }
public double Price { get; set; }
}
Then the call like localhost\api\Products('test') should just go to
public SingleResult<Product> GetProduct([FromODataUri]string key)
2.If you already have a int as a key, but you want use string as another key, then you should try this feature: http://odata.github.io/WebApi/#04-17-Alternate-Key , and you can call like:
localhost\api\Products(StringKey='test')

Odata v3 Web Api navigation with composite key

I have a Web Api using Odata v3, with some entities a composite key, like this one:
public class AerodromoAdministracaoData
{
[Key]
[Column("idAerodromo", Order = 0)]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public short IdAerodromo { get; set; }
[Key]
[Column("data", Order = 1, TypeName = "date")]
public DateTime Data { get; set; }
public virtual Aerodromo Aerodromo { get; set; }
}
I followed this msdn article and created a NavigationRoutingConvention. The application handles composite keys fine now. However, navigation Links like this one don't work:
http://localhost/WebApiV3/AerodromoAdministracaoData%28idAerodromo=1,data=%272014-10-24%27%29/Aerodromo
I keep getting a "No HTTP resource was found that matches the request" as if the method was not implemented in the controller. By the way, this is the controller method:
[EnableQuery]
public Aerodromo GetAerodromo([FromODataUri] short idAerodromo, [FromODataUri] DateTime data)
{
AerodromoAdministracaoData result = Store.AerodromoAdministracaoData.Find(idAerodromo, data);
if (result == null)
{
throw new HttpResponseException(new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.NotFound));
}
return result.Aerodromo;
}
I found this question talking about exactly the same problem, but I haven't figured out how Nikon handled the issue.
Eduardo
From MSDN article Support Composite Key in ASP.NET Web API OData
public class CompositeKeyRoutingConvention : EntityRoutingConvention
{
....
}
The above routing convention can cover the following Uri templates:
~/entityset/key
~/entityset/key/cast
But, it can't cover ~/entityset/key/navigation
The fix is simple, just derived from NavigationRouteConvention as below
public class CompositeKeyRoutingConvention : NavigationRoutingConvention
{
...
}
Below is the debug information:
Please make sure: if you want support both Uris:
/AerodromoAdministracaoData%28idAerodromo=1,data=%272014-10-24%27%29
/AerodromoAdministracaoData%28idAerodromo=1,data=%272014-10-24%27%29/Aerodromo
You must have two custom routing conventions, one derived from EntityRoutingConvention, the other one derived from NavigationRoutingConvention.
Hope it can help. Thanks.

Json object using WebAPI controller

I am using webapi controller.I have to pass an JSON object from Java applet to web api controller. How to write a web api controller method to accept the JSON object
public test GetPo(int id)
{
}
ASP.NET Web API uses JSON format as default format:
JSON formatting is provided by the JsonMediaTypeFormatter class. By default, JsonMediaTypeFormatter uses the Json.NET library to perform serialization. Json.NET is a third-party open source project.
what you just do is to defined your model object which map with json:
public class Model
{
public int Property1 { get; set; }
public string Property2 { get; set; }
// More properties
}
And your POST method:
public void Post(Model model)
{}

Resources