Specifying a format specifier in ASP.Net route parameter handling? - asp.net

Building an ASP.Net Core 5.0 service. In order to make this service mirror our other APIs I want to:
Have a route that supports the following URIs
// returns a single item
/api/Items/{id}
// returns multiple items
/api/Items/?date={date}
In the above, {id} could be an int, or a guid, and date is in the format YYYYMMDD
Couple
[ApiController]
[Route("Items")]
public class ItemController: ControllerBase
{
[HttpGet("{id}")]
public Item Get(string id)
{
}
[HttpGet()]
public IEnumerable<Item> Get([FromQuery] DateTime date)
{
return new List<Item> { new Item() };
}
}
When I run this, which I'm debugging using the Swagger functionality, it appears that I get two methods:
/Items/{id}
/Items/?date={date}
So far so good, except, that I want to support date strings of YYYYMMDD, but the default date converter does not support that.
One kludge is to make this a string and convert in the function(boo).
Ideally, I'd like to register an additional Date parser for all endpoints.
Ideas?

Related

asp.net swagger specify object type

I have the following class
public class UpdateUserRequest
{
public string Id {get;set;}
public string FirstName {get;set;}
public string LastName {get;set;}
public int Age {get;set;}
}
And I have the following endpoint:
[HttpPut]
[Route("/[controller]/update/user/{userId}")]
public IActionResult Update(UpdateUserRequest update, string userId)
{
// code logic to update a user
// etc...
}
If I run my project with that code swagger generates the proper documentation. The problem is that I want to modify my endpoint and instead of taking a UpdateUserRequest update I will like to take a object update. In other words this is how I would like my endpoint to look like:
[HttpPut]
[Route("/[controller]/update/user/{userId}")]
public IActionResult Update(object update, string userId)
{
// code logic to update a user
// etc...
}
The reason why I want to accept an object instead of an UpdateUserRequest is because I will like to enable my API to only update the properties that are sent through the request. In other words if the user sends:
{ "FirstName":"Tono" }
Then I will only update the property FirstName. without making the other properties null!
How can I tell swagger to generate documentation as if the method where to take UpdateUserRequest parameter when in fact it is accepting an object? When I place an object as a parameter swagger displays this:
How can I have it display json with the format of a UpdateUserRequest?
One solution I have found is to do something like this:
app.Use(async (context, next) =>
{
// if its an update?
if (context.Request.Method == "PUT")
{
// enable buffering in order to see raw object
context.Request.EnableBuffering();
using MemoryStream memoryStream = new MemoryStream();
// save body to memory
await context.Request.Body.CopyToAsync(memoryStream);
// convert it to json
var rawJson = System.Text.Encoding.UTF8.GetString(memoryStream.ToArray());
// save raw json on current context.
context.Items.Add("rawJson", rawJson);
// later that json could be from current context
// reset the position
context.Request.Body.Position = 0;
}
await next();
);
But this is kind of an ugly hack when I just need a different swagger documentation.

NET Core: Filtering with HttpGet

I would like to filter a list of vehicles, by their makeId using httpGet. The URL I would expect to use is:
https://localhost:5001/api/vehicle?makeId=2
Below, I will define the DTO and controller methods I used for this task:
FilterDto
public class FilterDTO
{
public int? MakeId { get; set; }
}
Below are the 2 HTTPGet methods in my controller class. I expect the first method to be called.
[HttpGet]
public async Task<IEnumerable<VehicleDTO>> Get(FilterDTO filterDto)
{
var filter = _mapper.Map<Filter>(filterDto);
var vehicles = await _vehicleRepository.GetAll(filter);
return _mapper.Map<IEnumerable<VehicleDTO>>(vehicles);
}
[HttpGet("{id}")]
public async Task<ActionResult<VehicleDTO>> Get(long id)
{
var vehicle = await _vehicleRepository.GetWithRelated(id);
if (vehicle == default)
{
return BadRequest("Vehicle not found");
}
var result = _mapper.Map<VehicleDTO>(vehicle);
return Ok(result);
}
With the above code, when I call the URL above, in Postman I get a 400 Error, saying "The input does not contain any JSON tokens. Expected the input to start with a valid JSON token, when isFinalBlock is true. Path: $ | LineNumber: 0 | BytePositionInLine: 0."
I get the same result for https://localhost:5001/api/vehicle
If I change the first Get method like below, I am able to get a response:
[HttpGet]
public async Task<IEnumerable<VehicleDTO>> Get(int? makeId)
{
var filter = new Filter { MakeId = makeId};
var vehicles = await _vehicleRepository.GetAll(filter);
return _mapper.Map<IEnumerable<VehicleDTO>>(vehicles);
}
After this (lengthy) introduction, my questions are:
Why does HttpGet support 'int?' but not the data transfer object 'FilterDto'?
Should I be using a different verb instead of HttpGet?
I might have to filter in the future for some other types (say customerId). Is there any way I can change the method to support custom objects, like FilterDto, ideally without changing the verb?
Change your code as follow:
[HttpGet]
public async Task<IEnumerable<VehicleDTO>> Get([FromQuery] FilterDTO filterDto)
{
var filter = _mapper.Map<Filter>(filterDto);
var vehicles = await _vehicleRepository.GetAll(filter);
return _mapper.Map<IEnumerable<VehicleDTO>>(vehicles);
}
and call it like:
baseUrl/Controller/Get?MarkId=1
Take a look at the docs.
Basically the primitive types are supported, but the controller has no idea how to convert your web request data into C# object. You need to explicitly tell it how you want this custom object to be created out of web request.
You may have in mind that HttpGet methods are only able to receive primitiveTypes (string, int, short, datetime -using a specific format-) because the arguments are being sent through query string, for example:
myAddres.com/api/mymethod?id=5&filter1=value1&filter2=value2
Having this consideration in mind you'll notice there's no way to send any object because you need to use a json or another notation, remember querystring has a limit and because of that is better using "argument=value" notation.
On the other hand PUT and POST are able to send their data through a "body" property where you may use a json notation and this way you may create almost any object on your Backend side.
If you need to use an object as an argument it is a better idea using POST or PUT (better POST than PUT).

MediaFormatter or ModelBinder for web api PUT method

I have a PUT method in web api which accepts a JSON data and a route data as follows.
[Route("api/v1/Orders/{orderId}/active")]
public HttpResponseMessage Put(Guid? orderId,List<ActiveRequest> activeRequests)
{
}
public class ActiveRequest
{
public int Id { get; set; }
public bool IsActive { get; set; }
}
Now is it possible to simplify the method signature as:
[Route("api/v1/Orders/{orderId}/active")]
public HttpResponseMessage Put(ActiveRequestModel model)
{
}
public class ActiveRequestModel
{
public Guid OrderId { get; set; }
public List<ActiveRequest> ActiveRequests {get; set;}
}
I tried writing a custom ModelBinder by implementing the System.Web.Http.ModelBinding.IModelBinder interface but could'nt find a way to read the JSON data that is coming inside the Request object.
I doubt that is there a way by which I can bind my model with data coming from three different places i.e. from route data, json & form.
You cannot simplify the parameter as described.
Unlike MVC model binding, beacuse of how the Web API formatter works, in Web API you only can have a single parameter that is deserialized from the payload, and a number of simple type parameters coming from route parameters or url query string. The reason is that the creation of the parameter coming from the payload is done in a single pass deserialization of the payload.
So, for your example you need the two parameters in your original version, i.e.:
public HttpResponseMessage Put(Guid? orderId, List<ActiveRequest> activeRequests)
If you want to use the ActiveRequestModel you need to include a payload which has exactly the same structure, so you should include the orderId in the payload, because it will not be recovered from the url (even if the name matches).
Please, read this article which explains how parameter binding works in Web API:
Parameter Binding in ASP.NET Web API
If you read it thoroughly you'll see that you can create and register your own model binder to make it work the same way that an MVC controller, but I think it's not worth the effort (so I include it only in this last paragraph), and it's not the standard way of working.

Use a viewmodel with web api action

I just read this post by Dave Ward (http://encosia.com/using-jquery-to-post-frombody-parameters-to-web-api/), and I'm trying to throw together a simple web api controller that will accept a viewmodel, and something just isn't clicking for me.
I want my viewmodel to be an object with a couple DateTime properties:
public class DateRange
{
public DateTime Start { get; set; }
public DateTime End { get; set; }
}
Without changing anything in the stock web api project, I edit my values controller to this:
public IEnumerable<float> Get()
{
DateRange range = new DateRange()
{
Start = DateTime.Now.AddDays(-1),
End = DateTime.Now
};
return Repo.Get(range);
}
// GET api/values/5
public IEnumerable<float> Get(DateRange id)
{
return Repo.Get(range);
}
However, when I try to use this controller, I get this error:
Multiple actions were found that match the request:
System.Collections.Generic.IEnumerable1[System.Single] Get() on type FEPIWebService.Controllers.ValuesController
System.Collections.Generic.IEnumerable1[System.Single] Get(FEPIWebService.Models.DateRange) on type FEPIWebService.Controllers.ValuesController
This message appears when I hit
/api/values
or
/api/values?start=01/01/2013&end=02/02/2013
How can I solve the ambiguity between the first and second get actions?
For further credit, if I had this action
public void Post(DateRange value)
{
}
how could I post the Start and End properties to that object using jQuery so that modelbinding would build up the DateRange parameter?
Thanks!
Chris
The answer is in detail described here: Routing and Action Selection. The Extract
With that background, here is the action selection algorithm.
Create a list of all actions on the controller that match the HTTP request method.
If the route dictionary has an "action" entry, remove actions whose name does not match this value.
Try to match action parameters to the URI, as follows:
For each action, get a list of the parameters that are a simple type, where the binding gets the parameter from the URI. Exclude
optional parameters.
From this list, try to find a match for each parameter name, either in the route dictionary or in the URI query string. Matches are
case insensitive and do not depend on the parameter order.
Select an action where every parameter in the list has a match in the URI.
If more that one action meets these criteria, pick the one with the most parameter matches.
4.Ignore actions with the [NonAction] attribute.
Other words, The ID parameter you are using, is not SimpleType, so it does not help to decide which of your Get methods to use. Usually the Id is integer or guid..., then both methods could live side by side
If both of them would return IList<float>, solution could be to omit one of them:
public IEnumerable<float> Get([FromUri]DateRange id)
{
range = range ?? new DateRange()
{
Start = DateTime.Now.AddDays(-1),
End = DateTime.Now
};
return Repo.Get(range);
}
And now both will work
/api/values
or
/api/values?Start=2011-01-01&End=2014-01-01

Multiple controllers, one view, and one model ASP.NET MVC 3

I want to have one model & view that is served by multiple controllers in my ASP.NET MVC 3 app.
I'm implementing a system that interacts with the users' online calendar and I support Exchange, Google, Hotmail, Yahoo, Apple, ect... Each of these has wildly different implementations of calendar APIs, but I can abstract that away with my own model. I'm thinking that by implementing the polymorphism at the controller level I will be able to deal cleanly with the different APIs and authentication issues.
I have a nice clean model and view and I've implemented two controllers so far that prove I can read/query/write/update to both Exchange and Google: ExchangeController.cs and GoogleController.cs.
I have /Views/Calendar which contains my view code. I also have /Models/CalendarModel.cs that includes my model.
I want the test for which calendar system the user is using to happen in my ControllerFactory. I've implemented it like this:
public class CustomControllerFactory : DefaultControllerFactory
{
protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
if (controllerType == typeof(CalendarController))
{
if(MvcApplication.IsExchange) // hack for now
return new ExchangeController();
else
return new GoogleController();
}
return base.GetControllerInstance(requestContext, controllerType);
}
}
and in my Application_Start:
ControllerBuilder.Current.SetControllerFactory(new CustomControllerFactory());
This works. If I got to http://.../Calendar this factory code works and the correct controller is created!
This worked beautifully and I did it without really understanding what I was doing. Now i think I got it but I want to make sure I'm not missing something. I really spent time searching for something like this and didn't find anything.
One thing that concerns me is that I figured I'd be able to have an inheritance relationship between CalendarController and ExchangeController/GoogleController like this:
public class ExchangeController : CalendarController
{
But if I do that I get:
The current request for action 'Index' on controller type 'GoogleController' is ambiguous between the following action methods:
System.Web.Mvc.ViewResult Index(System.DateTime, System.DateTime) on type Controllers.GoogleController
System.Web.Mvc.ActionResult Index() on type Controllers.CalendarController
Which bums me out because I wanted to put some common functionality on the base and now I guess I'll have to use another way.
Is this the right way to do have multiple controllers for one view/model? What else am I going to have to consider?
EDIT: More details on my impl
Based on the responses below (thanks!) I think I need to show some more code to make sure you guys see what I'm trying to do. My model is really just a data model. It starts with this:
/// <summary>
/// Represents a user's calendar across a date range.
/// </summary>
public class Calendar
{
private List<Appointment> appointments = null;
/// <summary>
/// Date of the start of the calendar.
/// </summary>
public DateTime StartDate { get; set; }
/// <summary>
/// Date of the end of the calendar
/// </summary>
public DateTime EndDate { get; set; }
/// <summary>
/// List of all appointments on the calendar
/// </summary>
public List<Appointment> Appointments
{
get
{
if (appointments == null)
appointments = new List<Appointment>();
return appointments;
}
set { }
}
}
Then my controller has the following methods:
public class ExchangeController : Controller
{
//
// GET: /Exchange/
public ViewResult Index(DateTime startDate, DateTime endDate)
{
// Exchange specific gunk. The MvcApplication._service thing is a temporary hack
CalendarFolder calendar = (CalendarFolder)Folder.Bind(MvcApplication._service, WellKnownFolderName.Calendar);
Models.Calendar cal = new Models.Calendar();
cal.StartDate = startDate;
cal.EndDate = endDate;
// Copy the data from the exchange object to the model
foreach (Microsoft.Exchange.WebServices.Data.Appointment exAppt in findResults.Items)
{
Microsoft.Exchange.WebServices.Data.Appointment a = Microsoft.Exchange.WebServices.Data.Appointment.Bind(MvcApplication._service, exAppt.Id);
Models.Appointment appt = new Models.Appointment();
appt.End = a.End;
appt.Id = a.Id.ToString();
...
}
return View(cal);
}
//
// GET: /Exchange/Details/5
public ViewResult Details(string id)
{
...
Models.Appointment appt = new Models.Appointment();
...
return View(appt);
}
//
// GET: /Exchange/Edit/5
public ActionResult Edit(string id)
{
return Details(id);
}
//
// POST: /Exchange/Edit/5
[HttpPost]
public ActionResult Edit(MileLogr.Models.Appointment appointment)
{
if (ModelState.IsValid)
{
Microsoft.Exchange.WebServices.Data.Appointment a = Microsoft.Exchange.WebServices.Data.Appointment.Bind(MvcApplication._service, new ItemId(appointment.Id));
// copy stuff from the model (appointment)
// to the service (a)
a.Subject = appointment.Subject
...
a.Update(ConflictResolutionMode.AlwaysOverwrite, SendInvitationsOrCancellationsMode.SendToNone);
return RedirectToAction("Index");
}
return View(appointment);
}
//
// GET: /Exchange/Delete/5
public ActionResult Delete(string id)
{
return Details(id);
}
//
// POST: /Exchange/Delete/5
[HttpPost, ActionName("Delete")]
public ActionResult DeleteConfirmed(string id)
{
Microsoft.Exchange.WebServices.Data.Appointment a = Microsoft.Exchange.WebServices.Data.Appointment.Bind(MvcApplication._service, new ItemId(id));
a.Delete(DeleteMode.MoveToDeletedItems);
return RedirectToAction("Index");
}
So it's basically the typical CRUD stuff. I've provided the sample from the ExchangeCalendar.cs version. The GoogleCalendar.cs is obviously similar in implementation.
My model (Calendar) and the related classes (e.g. Appointment) are what get passed from controller to view. I don't want my view to see details of what underlying online service is being used. I do not understand how implementing the Calendar class with an interface (or abstract base class) will give me the polymorphism I am looking for.
SOMEWHERE I have to pick which implementation to use based on the user.
I can either do this:
In my model. I don't want to do this because then my model gets all crufty with service specific code.
In the controller. E.g. start each controller method with something that redirects to the right implementation
Below the controller. E.g. as I'm suggesting above with a new controller factory.
The responses below mention "service layer". I think this is, perhaps, where I'm off the rails. If you look at the way MVC is done normally with a database, the dbContext represents the "service layer", right? So maybe what you guys are suggesting is a 4th place where I can do the indirection? For example Edit above would go something like this:
private CalendarService svc = new CalendarService( e.g. Exchange or Google );
//
// POST: /Calendar/Edit/5
[HttpPost]
public ActionResult Edit(MileLogr.Models.Appointment appointment)
{
if (ModelState.IsValid)
{
svc.Update(appointment);
return RedirectToAction("Index");
}
return View(appointment);
}
Is this the right way to do it?
Sorry this has become so long-winded, but it's the only way I know how to get enough context across...
END EDIT
I wouldn't do it this way. As Jonas points out, controllers should be very simple and are intended to coordinate various "services" which are used to respond to the request. Are the flows of requests really all that different from calendar to calendar? Or is the data calls needed to grab that data different.
One way to do this would be to factor your calendars behind a common calendar interface (or abstract base class), and then accept the calendar into the controller via a constructor parameter.
public interface ICalendar {
// All your calendar methods
}
public abstract class Calendar {
}
public class GoogleCalendar : Calendar {}
public class ExchangeCalendar : Calendar {}
Then within your CalendarController,
public class CalendarController {
public CalendarController(ICalendar calendar) {}
}
This won't work by default, unless you register a dependency resolver. One quick way to do that is to use NuGet to install a package that sets one up. For example:
Install-Package Ninject.Mvc3
I think this would be a better architecture. But suppose you disagree, let me answer your original question.
The reason you get the ambiguous exception is you have two public Index methods that are not distinguished by an attribute that indicates one should respond to GETs and one to POSTs. All public methods of a controller are action methods.
If the CalendarController isn't meant to be instantiated directly (i.e. it'll always be inherited), then I would make the Index method on that class protected virtual and then override it in the derived class.
If the CalendarController is meant to be instantiated on its own, and the other derived classes are merely "flavors" of it, then you need to make the Index method public virtual and then have each of the derived classes override the Index method. If they don't override it, they're adding another Index method (C# rules, not ours) and you need to distinguish them for MVC's sake.
I think you're on a dangerous path here. A controller should generally be as simple as possible, and only contain the "glue" between e.g. your service layer and the models/views. By moving your general calendar abstractions and vendor specific implementations out of the controllers, you get rid of the coupling between your routes and the calendar implementation.
Edit: I would implement the polymorphism in the service layer instead, and have a factory class in the service layer check your user database for the current user's vendor and instantiate the corresponding implementation of a CalendarService class. This should eliminate the need for checking the calendar vendor in the controller, keeping it simple.
What I mean by coupling to the routes is that your custom URLs is what is currently causing you problems AFAICT. By going with a single controller and moving the complexity to the service layer, you can probably just use the default routes of MVC.
As the other answers suggest, you really should refactor your code so as to not require the multiple controllers in the first place.
However, you can still have your controllers inherit from a base class controller - you simply need to make sure that when you register the routes in the Global.asax.cs, you use the overload that specifies which namespace to find the controllers and action methods for a given route
e.g.
routes.MapRoute(null, "{controller}/{action}", new[] { "Namespace.Of.Controllers.To.USe" });

Resources