A model class userQuery that I have written isn't showing up in Swagger UI. I have referenced it in my controller file so I expected it to show up in the Swagger UI. I use SwashBuckle. What am I missing here?
The controller file having an endpoint:
using Project.Models;
namespace Project.Controllers
{
[HttpGet]
[Authorize(Policy = "Read-Run")]
[Route("byRoute/{element}")]
[Produces(typeof(EntityResult<EntityResponse>))]
public async Task<IActionResult> ListEntities([FromQuery] userQuery entityMatch, string element)
{
return Ok((await _entityService.ListEntities(entityMatch, element)));
}
}
Model class:
using System;
namespace Project.Models
{
public class UserQuery
{
public int Id { get; set; }
public DateTime? DateCreated { get; set; }
public DateTime? DateUpdated { get; set; }
}
}
It seems you can't bind complex objects to query string parameters in Swashbuckle (or OpenAPI): check this question.
You can use three different parameters: Id, DateCreated, and DateUpdated in your controller and use the [FromQuery] attribute for each of them.
Your endpoint will look like this:
public async Task<IActionResult> ListEntities(
[FromRoute] string element,
[FromQuery] int id,
[FromQuery] DateTime? dateCreated,
[FromQuery] DateTime? DateUpdated
)
Or you can pass the object in the body of the request and use the [FromBody] attribute. But usually, you shouldn't use the body for a GET request.
Related
I have a class for holding both raw and encrypted values from query parameters:
public class SID
{
[FromQuery(Name = "sidInt")]
public int RawValue { get; set; }
[FromQuery(Name = "sid")]
public string EncryptedValue { get; set; }
public static implicit operator int(SID model)
{
return model.RawValue;
}
}
Having this I can successfully use it as a parameter for get requests:
/// GET: /{controller}/index?sid=my-encrypted-string
[HttpGet]
public IActionResult Index(SID id){
int resourceId = id;
//rest of the action code
}
Only encrypted value is provided in query string, raw integer value is automatically decrypted and added to the query string in my custom middleware. Notice that thanks to named [FromQuery] attributes I can use any parameter name in the action.
So far so good. But now I want to use the same SID class as a property in model for post request:
public class MyPostModel {
[Required]
[FromQuery]
public SID Id { get; set; }
public string Name { get; set; } // posted in body
// rest of the properties
}
/// POST: /{controller}/index?sid=my-encrypted-string + other fields in the body
[HttpPost]
public IActionResult Index(MyPostModel model){
int resourceId = model.Id;//model.Id is null here
//rest of the action code
}
But unfortunately I cannot make it working, Id in the model is not bound. I feel like I'm missing something obvious here. Is it possible? Do I need a custom model binder for this(I hope not, I want to use SID class as a property for several models and for different actions)?
Of course I can add sid parameter to the post action and reassign it in the action body explicitly but it looks wrong and too verbose:
[HttpPost]
public IActionResult Index(SID id, MyPostModel model){
model.Id = id;//id is correctly populated but model.Id is not
//rest of the action code
}
I think that your problem is that parameter is called "sid" and property in MyPostModel is "Id". Aside from that, I don't see any reason why it would not work. Can you change your MyPostModel:
public class MyPostModel {
[Required]
[FromQuery(Name = "sid")]
public SID Id { get; set; }
public string Name { get; set; } // posted in body
// rest of the properties
}
I am building an API to generate Invoice Number.
I have the following InvItem class
public class InvItem
{
public string ItemCode;
public double Quantity;
public double SaleValue;
}
I have the following Controller method
[Route("api/InvoiceMaster/GetInvoiceNum")]
[HttpGet]
public IActionResult GetInvoiceNum(
string xDateTime,
string BuyerName,
double TotalBillAmount,
InvItem[] items
)
{
...
var invItems = new List<InvItem>();
invItems.AddRange(items);
...
return Ok();
}
Invoice can have one or more items. Now I want to call that method from postman (or any other application) using GET request.
I have already built that method using POST request and reading parameters from request body. But the requirement here is STRICTLY Get Request.
I have tried the following url but cannot get the value of items in controller's action method 'GetInvoiceNum'
https://localhost:44365/api/InvoiceMaster/GetInvoiceNum?xDateTime=2020-01-01 12:00:00&BuyerName=elon&TotalBillAmount=1519&items[0].ItemCode=001897&items[0].Quantity=1&items[0].SaleValue=19&items[1].ItemCode=002899&items[1].Quantity=1&items[1].SaleValue=1500
How can I pass this array of objects to api?
You have to add from [FromQuery]
public IActionResult GetInvoiceNum(
[FromQuery] string xDateTime,
[FromQuery] string BuyerName,
[FromQuery] double TotalBillAmount,
[FromQuery] InvItem[] items
)
and convert fields to properties by adding getters/setters
public class InvItem
{
public string ItemCode { get; set; }
public double Quantity { get; set; }
public double SaleValue { get; set; }
}
I have this controller and action method:
[ApiController]
[Route("api/[controller]")]
public class AppointmentController : ControllerBase
{
[Route("{provider}/AvailableSlots")]
[HttpGet]
public Task<AvailableSlotsResponse> GetAvailableSlots(Request<AvailableSlotsRequest> request)
{
return null;
}
}
Here's the model:
public class Request<T> where T : class
{
[FromRoute]
public string Provider { get; set; }
[FromQuery(Name = "")]
public T Model { get; set; }
}
public class AvailableSlotsRequest
{
//[FromQuery(Name = "Location")] //Would prefer not to have to use this
public string Location { get; set; }
}
I need to use Location as the query param name in the URL in order to hit the endpoint, as expected.
eg. http://localhost/api/Appointment/Company/AvailableSlots?Location=SYD
However, when I view the Swagger page, the parameter is called Model.Location which is confusing for consumers of my API:
I can use [FromQuery(Name = "Location")] to force Swagger to display Location, however this feels very redundant and duplicates the property name.
Here is my Swagger set up in ConfigureServices():
services.AddSwaggerDocument(document =>
{
document.PostProcess = d =>
{
d.Info.Version = Configuration["APIVersion"];
d.Info.Title = $"{Configuration["ApplicationName"]} {Configuration["DomainName"]} API";
};
});
How can I make Swagger display Location instead of Model.Location, without having to duplicate the word "Location" in the [FromQuery] attribute?
Add to the controller parameter the attribute [FromRoute]:
public Task<AvailableSlotsResponse> GetAvailableSlots([FromRoute]Request<AvailableSlotsRequest> request)
Remove the attribute FromQuery in the Model property and uncomment the attribute FromQuery from de Location Property.
Unfortunately I had to use [FromQuery(Name = "<PropertyName>")].
However I found a better way:
[ApiController]
[Route("api/[controller]")]
public class AppointmentController : ControllerBase
{
[Route("{provider}/AvailableSlots")]
[HttpGet]
public Task<AvailableSlotsResponse> GetAvailableSlots(AvailableSlotsRequest request)
{
return null;
}
}
public class Request
{
[FromRoute]
public string ProviderName { get; set; }
}
public class AvailableSlotsRequest : Request
{
[FromQuery]
public string Location { get; set; }
}
This also means the model can use any attribute, compared to my first attempt where the T Model was decorated with [FromQuery]
I am trying to use [FromQuery] in my web api and I am not sure how to use it.
Here's the GetAllBooks() method in the controller:
[HttpGet]
[Route("api/v1/ShelfID/{shelfID}/BookCollection")]
public async Task<IActionResult> GetAllBooks(string shelfID, [FromQuery] Book bookinfo)
{
//do something
}
Here's the Book model class:
public class Book
{
public string ID{ get; set; }
public string Name{ get; set; }
public string Author { get; set; }
public string PublishDate { get; set; }
}
I am confused about whether it is the correct way to use [FromQuery]. I thought the URL is
https://localhost:xxxxx/api/v1/ShelfID/{shelfID}/BookCollection/IActionResult?ID="123"&Name="HarryPotter"
But the break point is not hitting my controller method, so I was thinking maybe the URL is not correct. Any suggestions? Thanks!
The name of the method and return type are completely ignored when you define your route explicitly via attributes like that. IActionResult shouldn't be there.
The correct URL would be: https://localhost:xxxxx/api/v1/ShelfID/{shelfID}/BookCollection?ID="123"&Name="HarryPotter"
Furthermore, query string binding only works out of the box for primitive types (string, int, etc). To bind a class to a query string you'd need a custom modelbinder, which is quite involved.
It would be better to just explicitly declare the properties you want to pass in:
public async Task<IActionResult> GetAllBooks(string shelfID,
[FromQuery] string ID,
[FromQuery] string Name)
I'm using the below model to bind the query string with my controller using [FromUri]
namespace MyApi.Models
{
public class Product
{
public int Id { get; set; }
public decimal Price { get; set; }
}
}
The controller action method code is
public ActionResult ActionMethod1( [FromUri] Product product)
The URL incoming is http://myapplication:2020/ActionMethod1/?Id=1&Cost=20
Now, internally the Cost in the query param should be mapped to Price in the Product class.
How can I do that ? I know I can use custom model binding but instead something like,
Is there any attribute that I can apply to the above class? Something like,
[BindingName("Cost")]
public decimal Price {get;set}