How do I handle path/route parameters in Swashbuckle? - c#

I have a controller where I get the ID from the route.
[HttpGet]
[Route("{vehicleId}")]
public InfoDto GetInfo([FromUri] VehicleDetailsRequest request)
{
return ...;
}
The VehicleDetailsRequest object looks like this (the Validator is from FluentValidation):
[Validator(typeof(VehicleDetailsRequestValidator))]
public class VehicleDetailsRequest
{
public int VehicleId { get; set; }
public string Lang { get; set; }
}
I can query this action as I expect with http://localhost/controller/123?lang=sv but my swagger documentation looks like this:
How can I get Swashbuckle/Swagger to only show me one vehicleId but still keep the FluentValidation?
I'm using Swashbuckle 5.6 and .Net Framework 4.6.2.

In my opinion, there is something wrong with action method signature.
The parameter from the URL should be present in the action method parameters.
Also, if you want only lang in query string, then it should also be present as a parameter in the action.
Please note that if you specify object in the action parameters, all its properties would be forming the query string.
So, your action method should like below:
[HttpGet]
[Route("{vehicleId}")]
public InfoDto GetInfo(int vehicleId, [FromUri] string lang)
{
return ...;
}
This should help you to resolve the issue.

Related

ASP.NET Core Web API : route by query parameter

I am coming from a heavy Java/Spring background and trying to transition some knowledge over to ASP.NET Core 6.
In Spring, on a RestController, I am able to route the request based on the presence of a query parameter.
So a HttpRequest with the uri: /students?firstName=Kevin can be routed to a different controller method than a HttpRequest with the uri: /students.
In ASP.NET Core 6, I am unable to determine if the equivalent is possible after working through some examples and reading the documentation for Web API.
Here is what I am trying to achieve, is this possible using two methods and routing configuration that will discern which controller method to invoke based on the query parameter?
[ApiController]
[Route("Students")]
public class StudentHomeProfileController : ControllerBase
{
[HttpGet] //Route here when no parameters provided
public async Task<ActionResult<IEnumerable<Student>>> GetStudentAsync()
{
/* Code omitted */
}
[HttpGet] //Route here when firstName query param provided
public async Task<ActionResult<IEnumerable<Student>>> SearchStudentAsync([FromQuery] string firstName)
{
/* Code omitted */
}
}
While filtering by query parameters does not come with ASP.NET Core out of the box, it's not too hard to supply this functionality on your own.
When it comes to extensibility, ASP.NET has some superpowers, one of them is IActionConstraint, which
Supports conditional logic to determine whether or not an associated action is valid to be selected for the given request. (Source)
Creating an annotation to filter for query parameters is as easy as
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class QueryParameterConstraintAttribute : Attribute, IActionConstraint
{
private readonly string _parameterName;
public QueryParameterConstraintAttribute(string parameterName)
{
this._parameterName = parameterName;
}
public bool Accept(ActionConstraintContext context)
{
return context.RouteContext.HttpContext.Request.Query.Keys.Contains(this._parameterName);
}
public int Order { get; }
}
All that's left is annotating your controller method with that constraint
[HttpGet] //Route here when firstName query param provided
[QueryParameterConstraint("firstName")]
public async Task<ActionResult<IEnumerable<Student>>> SearchStudentAsync([FromQuery] string firstName)
{
/* Code omitted */
}
In a quick test I was able to confirm that it seems to work as intended, even if you add multiple of those attributes for different query parameters (if all conditions match, the route is called).
(Please note, this was tested with .NET Core 2.1. Anyway, it shuold be pretty much the same with .NET 6)
I think you are looking for something like this, you need to specify the parameter in the "HttpGet" attribute
https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/routing?view=aspnetcore-6.0#attribute-routing-with-http-verb-attributes
[Route("api/[controller]")]
[ApiController]
public class Test2Controller : ControllerBase
{
[HttpGet] // GET /api/test2
public IActionResult ListProducts()
{
return ControllerContext.MyDisplayRouteInfo();
}
[HttpGet("{id}")] // GET /api/test2/xyz
public IActionResult GetProduct(string id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
[HttpGet("int/{id:int}")] // GET /api/test2/int/3
public IActionResult GetIntProduct(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
[HttpGet("int2/{id}")] // GET /api/test2/int2/3
public IActionResult GetInt2Product(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
You are trying to differentiate API calls using query params. this is not the way to do this. if you want to separate the calls you should probably use path params instead.
Read more about Routing in ASP.NET Core - https://learn.microsoft.com/en-us/aspnet/core/fundamentals/routing?view=aspnetcore-6.0

Model value redirected to another action does not pass all values

I have an ASP.Net Core application which needs passing of a model from one action to another.
These are models :
public class ClassA
{
public string Id{get;set;}
public string Name {get;set;}
public StudentMarks Marks {get;set;}
}
public class StudentMarks
{
public int Marks {get;set;}
public string Grade {get;set;}
}
And the post Controller:
[HttpPost]
public ActionResult TestAction1(ClassA model)
{
return RedirectToAction("TestAction2", model);
}
public ActionResult TestAction2(ClassA model)
{
}
In TestAction 1 while debugging, i see that Id, Name and marks have value.
I am getting the value for Id in TestAction2 same as that in TestAction1. However the value of complex object Marks is not obtained in the TestAction2 action method.
What are my other options?
You cannot redirect with a model. A redirect is simply an empty response with a 301, 302, or 307 status code, and a Location response header. That Location header contains the the URL you'd like to redirect the client to.
The client then must make a new request to that URL in the header, if it so chooses. Browsers will do this automatically, but not all HTTP clients will. Importantly, this new request is made via a GET, and GET requests do not have bodies. (Technically, the HTTP spec allows for it, but no browser or HTTP client out there actually supports that.)
It's unclear what your ultimate goal is here, but if you need to persist data temporarily between requests (such as a redirect), then you should serialize that data into a TempData key.
You can use TempData to pass model data to a redirect request in Asp.Net Core In Asp.Net core, you cannot pass complex types in TempData. You can pass simple types like string, int, Guid etc. If you want to pass a complex type object via TempData, you have can serialize your object to a string and pass that. I have made a simple test application that will suffice to your needs:
Controller:
public ActionResult TestAction1(ClassA model)
{
model.Id = "1";
model.Name = "test";
model.Marks.Grade = "A";
model.Marks.Marks = 100;
var complexObj = JsonConvert.SerializeObject(model);
TempData["newuser"] = complexObj;
return RedirectToAction("TestAction2");
}
public ActionResult TestAction2()
{
if (TempData["newuser"] is string complexObj )
{
var getModel= JsonConvert.DeserializeObject<ClassA>(complexObj);
}
return View();
}
Model:
public class ClassA
{
public ClassA()
{
Marks = new StudentMarks();
}
public string Id { get; set; }
public string Name { get; set; }
public StudentMarks Marks { get; set; }
}
public class StudentMarks
{
public int Marks { get; set; }
public string Grade { get; set; }
}
If you want to persist your TempData values for more requests you can use Peek and Keep functions. This answer can give more insight on these functions.
I think you're getting model and routeValues mixed up. The overload of RedirectToAction that you're calling (takes a string and an object) expects a routeValues argument, not a model argument. https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.controllerbase.redirecttoaction?view=aspnetcore-2.2
TestAction1 is called via Post, but TestAction2 is called via Get. You need to work out a URL that will let you call TestAction2 the way you want (independently of the RedirectToAction in TestAction1). I'm guessing this will involve setting up a custom route. Once you have a URL that will let you call TestAction2 the way you want, you can specify the route values to form that URL in the RedirectToAction in TestAction1.
I think the problem is that you shuold use:
return RedirectToAction("TestAction2", model);
(you did this without return)

ASP.Net Core Web API Controller action with many parameters

I have ASP.Net Core Web API Controller's method that returns List<Car> based on query parameters.
[HttpPost("cars")]
public async Task<List<Car>> GetCars([FromBody] CarParams par)
{
//...
}
Parameters are grouped in record type CarParams. There are about 20 parameters:
public class CarParams
{
public string EngineType { get; set; }
public int WheelsCount { get; set; }
/// ... 20 params
public bool IsTruck { get; set; }
}
I need to change this method from POST to GET, because I want to use a server-side caching for such requests.
Should I create a controller's method with 20 params?
[HttpGet("cars")]
public async Task<List<Car>> GetCars(string engineType,
int wheelsCount,
/*...20 params?...*/
bool isTruck)
{
//...
}
If this is the case: Is there a way to automatically generate such a complex URL for a client-side app?
You can keep the model. Update the action's model binder so that it knows where to look for the model data.
Use [FromQuery] to specify the exact binding source you want to apply.
[HttpGet("cars")]
[Produces(typeof(List<Car>))]
public async Task<IActionResult> GetCars([FromQuery] CarParams parameters) {
//...
return Ok(data);
}
Reference Model Binding in ASP.NET Core
Just change [FromBody] attribute with [FromUrl]

ASP.NET MVC required parameters in controller method

I was wondering if it's possible to have something like:
[HttpGet]
public JsonResult AddTextFile(string path)
{
if(string.IsNullOrEmpty(path))
{
// return error
}
}
But in the case where I might have a lot of parameters in my controller method I don't want to use string.IsNullOrEmpty() for each one. I know that I could use view-models with a [Required] field indicator and that will allow me to use ModelState, but because these are all API endpoints, I'm requiring information through get parameters.
Is there an elegant way of requiring controller method parameters, so that if any of them are not set it would return a generic response message?
Use a complex object as the parameter:
[HttpGet]
public JsonResult AddTextFile(MyObject obj) {
if(!ModelState.IsValid) {
// return error
}
}
public class MyObject {
[Required]
public string Path { get; set; }
}
The properties of MyObject will be taken from the query parameters, like: /addtextfile?path=blah
And the model validation will apply.

How to validate GET url parameters through ModelState with data annotation

I have a Web API project... I would like to respect the REST principles, so I should have just a GET method and just a POST method...
I have to do a search, so i think this matches the GET method, because after the search I obtain the result and I show it in the page... If I do not find anything I must create the object... this action is a POST...
Now I have a problem... I must validate the filters of the search, because filters are a tax code and a alpha-numeric code (6 chars)... I have already done a client side validation. Now I should do a Server Side validation.
Untill now, we have used data annotation to validate the request, but this is a GET... so my method has this signature:
[HttpGet]
public IHttpActionResult GetActivationStatus(string taxCode, string requestCode)
{
if (ModelState.IsValid)
{
...
}
}
But how can I validate my ModelState with Data Annotation?
Thank you
Create your own model...
public class YourModel
{
[//DataAnnotation ...]
public string taxCode { get; set; }
[//DataAnnotation ...]
public string requestCode { get; set; }
}
And change your signature of your server side controller:
[HttpGet]
public IHttpActionResult GetActivationStatus([FromUri] YourModel yourmodel)
{
if (ModelState.IsValid)
{
...
}
}
If your client side code already worked you don't have to change it... Please, note that the properties of your Model are the same of the parameter you are passing now (string taxCode, string requestCode)... and they are case sensitive...
EDIT:
I mean that you can call your controller in this way:
http://localhost/api/values/?taxCode=xxxx&requestCode=yyyy

Categories