Use a viewmodel with web api action - asp.net

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

Related

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).

FromUri binding when querystring is empty

If we have the following controller action in Web API
public async Task<HttpResponseMessage> GetRoutes(
[FromUri] MapExtentQuery extent,
[FromUri] PagingQuery paging)
{
...
}
with
class MapExtentQuery {
public int X { get;set; }
public int Y { get; set; }
}
class PagingQuery {
public int Skip { get; set; }
public int Top { get; set; }
}
and we make a GET request to /routes both parameters (extent and paging) will be null.
If the request contains at least one querystring parameter, for instance
/routes?x=45
then both complex parameters will get initialized, so in the case of the 2nd route
extent.X = 45
extent.Y = 0
paging != null (but Skip and Top will be 0 of course).
Why does the [FromUri] binder work this way? It makes little or no sense.
I would understand if it initialized only the parameter that contains a property that matched at least one of the querystring values.
The problem is, this behaviour requires us to check when parameters are null (which happens only in the case that no querystring parameter was provided) and then initiliaze them ourselves.
Because obviously those complex params might have constructors which would set some property values to default.
It is because when you do not have query string and the parameters have FromUri attribute, the parser for the query string does not run and all values are not binded - you receive null.
If you have query string, the binder runs and instantiates all FromUri parameters and primitive types with their default values and then tries to fill in the properties from the query string values. This leaves you with two instantiated parameters but only one having the populated value because that is all the query string has.
As for why it works this way - most likely performance - this way they will not need to find which parameters they need to instantiate - they instantiate all of them and then find the properties. It is faster and performance is critical in the action and model binding.

Proper way to send json object to HttpGet endpoint in WebAPI 2

I am developing web api as an facade which will encapsulated request to underlying systems.
So, lets assume I have cars endpoint:
api/v1/cars
Now I want my api to get parameters which will determine calls to underlying systems.
Like:
{
provider: 'service_1'.
access_token: 'token_2',
info: 'some_info'
},
{
provider: 'service_2'.
access_token: 'token_2',
info: 'some_info'
}
Besides that api will take standard parameters like startdate, enddate, offset and others.
public async Task<Result<Cars>> Get([FromUri] RequestParams requestParams);
public class RequestParams
{
public RequestParams()
{
Limit = 50;
Offset = 0;
StartDate = DateTime.Now;
EndDate = DateTime.Now;
}
public string UserId { get; set; }
public int Limit { get; set; }
public int Offset { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
}
It's easy to map standard params from uri, but I do know how to properly pass json collection. Any ideas?
By definition, a GET request doesn't have payload (i.e. data in the body). So, the only way to pass data to a GET request is in the url, i.e. using route data or query string parameters.
If you need to pass a JSON object you need to use a different request method, usualy POST. This includes passing collections.
If you use POST, Web API will automatically load the parameter object with the JSON posted object. If the paramerter is a collection (for example a List<T>) it will also be correctly populated.
There is only one thing that you must take into account: the method can only have one parameter loaded from the body. I.e. you cannot receive several parameters in a Web API action from the body. (You can, however, have data coming from the URL and data coming from the body).
So, you need to change your request and your method to use POST or any other method with payload. Besides, you must choose one of thesse two options:
create a class that includes all the standard parameters, and the collection, and use it as parameter, and post al lthe data in a single object, with the same structure.
pass the standard parameters in the query string, or using route data, and the collection as JSON. In this case, yourmethod must have several parameters: onw for the collection posted as JSON, and one for each other parameters postes in the query string or route data
Posting a collection in the querystring, as proposed in kapsi's answer, is not possible, unless you make some kind of serialization of the parameter on the client side and deserialization when receiving it on the server side. That's overkill, just use POST or any other method with body, as explained above.
If you for example use jQuery, you can use the ajax method to address this:
$.ajax({
url: "./",
type: "GET",
data: {
UserId: 1,
Limit: 2,
Offset: 2,
StartDate: "02/15/2015",
EndDate: "05/15/2015"
}
});
jQuery takes action and following GET is made:
?UserId=1&Limit=2&Offset=2&StartDate=02%2F15%2F2015&EndDate=05%2F15%2F2015&_=1423137376902

RedirectAttributes changes object id between controllers

I am using Spring MVC and Hibernate in my project. Also I have 2 controllers UserController and BookController where BookController redirects to the users page and I am passing a Book object in addition.
I've found I can do this with RedirectAttributes but the problem is that the id of the passed Book object is changed during this transition to user.id.
BookController.java
public class BookController {
#RequestMapping("/users/{user_id}/books/edit/{book_id}")
public String editBook(#PathVariable("user_id") int user_id, #PathVariable("book_id") int book_id, final RedirectAttributes redirectAttrs){
bookDetail = this.bookService.getBookById(book_id)
redirectAttrs.addFlashAttribute("bookDetail", bookDetail);
System.out.println(bookDetail);
return "redirect:/users/"+user_id;
}
}
Prints: id=8, title=Motylek, description=Some description, user_id=2.
UserController.java
public class UserController {
#RequestMapping("/users/{id}")
public String detailUser(#ModelAttribute("bookDetail") Book bookDetail, #PathVariable("id") int id, Model model){
User u = this.userService.getUserById(id);
model.addAttribute("user", u);
model.addAttribute("bookDetail", bookDetail);
System.out.println(bookDetail);
return "user";
}
}
Prints: id=2, title=Motylek, description=Some description, user_id=2.
Do you have and idea why this happens or is it a bug? Thanks.
I'm going to assume that your Book class has a property called id, ie. a getter or setter called getId() and setId(..).
When Spring parses the request URL, it stores path segments as declared in the corresponding #RequestMapping. So for
/your-app/users/2
and
#RequestMapping("/users/{id}")
It will store
id=2
as a request parameter.
Spring will then proceed to generate an argument for
#ModelAttribute("bookDetail") Book bookDetail
It will check the various request, session, servlet attributes for an entry with the name bookDetail. (If it doesn't find one, it will create one and add it to the request attributes.) In your case, it will have found the object in the HttpSession. It will then bind any request parameters to matching object properties. Since the parameter above is called id, it will be bound to the Book property id.
You should be good by changing
#RequestMapping("/users/{id}")
to
#RequestMapping("/users/{user_id}")
along with the corresponding #PathVariable.

ASP.NET MVC - Posting a form with custom fields of different data types

In my ASP.NET MVC 2 web application, I allow users to create custom input fields of different data types to extend our basic input form. While tricky, building the input form from a collection of custom fields is straight-forward enough.
However, I'm now to the point where I want to handle the posting of this form and I'm not certain what the best way to handle this would be. Normally, we'd use strongly-typed input models that get bound from the various statically-typed inputs available on the form. However, I'm at a loss for how to do this with a variable number of input fields that represent different data types.
A representative input form might look something like:
My date field: [ date time input
control ]
My text field: [ text input
field ]
My file field: [ file upload
control ]
My number field: [ numerical input control ]
My text field 2: [text input field ]
etc...
Ideas I've thought about are:
Sending everything as strings (except for the file inputs, which would need to be handled specially).
Using a model with an "object" property and attempting to bind to that (if this is even possible).
Sending a json request to my controller with the data encoded properly and attempting to parse that.
Manually processing the form collection in my controller post action - certainly an option, but I'd love to avoid this.
Has anyone tackled an issue like this before? If so, how did you solve it?
Update:
My "base" form is handled on another input area all together, so a solution doesn't need to account for any sort of inheritence magic for this. I'm just interested in handling the custom fields on this interface, not my "base" ones.
Update 2:
Thank you to ARM and smartcaveman; both of you provided good guidance for how this could be done. I will update this question with my final solution once its been implemented.
This is how I would begin to approach the issue. A custom model binder would be pretty easy to build based on the FormKey property (which could be determined by the index and/or label, depending).
public class CustomFormModel
{
public string FormId { get; set; }
public string Label { get; set; }
public CustomFieldModel[] Fields { get; set; }
}
public class CustomFieldModel
{
public DataType DateType { get; set; } // System.ComponentModel.DataAnnotations
public string FormKey { get; set; }
public string Label { get; set; }
public object Value { get; set; }
}
public class CustomFieldModel<T> : CustomFieldModel
{
public new T Value { get; set; }
}
Also, I noticed one of the comments below had a filtered model binder system. Jimmy Bogard from Automapper made a really helpful post about this method at http://www.lostechies.com/blogs/jimmy_bogard/archive/2009/03/17/a-better-model-binder.aspx , and later revised in, http://www.lostechies.com/blogs/jimmy_bogard/archive/2009/11/19/a-better-model-binder-addendum.aspx . It has been very helpful for me in building custom model binders.
Update
I realized that I misinterpreted the question, and that he was specifically asking how to handle posting of the form "with a variable number of input fields that represent different data types". I think the best way to do this is to use a structure similar to above but leverage the Composite Pattern. Basically, you will need to create an interface like IFormComponent and implement it for each datatype that would be represented. I wrote and commented an example interface to help explain how this would be accomplished:
public interface IFormComponent
{
// the id on the html form field. In the case of a composite Id, that doesn't have a corresponding
// field you should still use something consistent, since it will be helpful for model binding
// (For example, a CompositeDateField appearing as the third field in the form should have an id
// something like "frmId_3_date" and its child fields would be "frmId_3_date_day", "frmId_3_date_month",
// and "frmId_3_date_year".
string FieldId { get; }
// the human readable field label
string Label { get; }
// some functionality may require knowledge of the
// Parent component. For example, a DayField with a value of "30"
// would need to ask its Parent, a CompositeDateField
// for its MonthField's value in order to validate
// that the month is not "February"
IFormComponent Parent { get; }
// Gets any child components or null if the
// component is a leaf component (has no children).
IList<IFormComponent> GetChildren();
// For leaf components, this method should accept the AttemptedValue from the value provider
// during Model Binding, and create the appropriate value.
// For composites, the input should be delimited in someway, and this method should parse the
// string to create the child components.
void BindTo(string value);
// This method should parse the Children or Underlying value to the
// default used by your business models. (e.g. a CompositeDateField would
// return a DateTime. You can get type safety by creating a FormComponent<TValue>
// which would help to avoid issues in binding.
object GetValue();
// This method would render the field to the http response stream.
// This makes it easy to render the forms simply by looping through
// the array. Implementations could extend this for using an injected
// formatting
void Render(TextWriter writer);
}
I am assuming that the custom forms can be accessed via some sort of id which can be contained as a form parameter. With that assumption, the model binder and provider could look something like this.
public interface IForm : IFormComponent
{
Guid FormId { get; }
void Add(IFormComponent component);
}
public interface IFormRepository
{
IForm GetForm(Guid id);
}
public class CustomFormModelBinder : IModelBinder
{
private readonly IFormRepository _repository;
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
ValueProviderResult result;
if(bindingContext.ValueProvider.TryGetValue("_customFormId", out result))
{
var form = _repository.GetForm(new Guid(result.AttemptedValue));
var fields = form.GetChildren();
// loop through the fields and bind their values
return form;
}
throw new Exception("Form ID not found.");
}
}
Obviously, all the code here is just to get the point across, and would need to be completed and cleaned up for actual use. Also, even if completed this would only bind to an implementation of the IForm interface, not a strongly typed business object. (It wouldn't be a huge step to convert it to a dictionary and build a strongly typed proxy using the Castle DictionaryAdapter, but since your users are dynamically creating the forms on the site, there is probably no strongly typed model in your solution and this is irrelevant). Hope this helps more.
Take a peek at what I did here: MVC2 Action to handle multiple models and see if can get you on the right track.
If you use a FormCollection as one of your parameters to your action, you can then go thru that form collection looking for bits of data here or there in order to bind those values to whatever an then save the data. You are most likely going to need to take advantage of both strategy and command patterns to get this to work.
Best of luck, feel free to ask follow-up questions.
Edit:
Your method which does the work should look something like this:
private/public void SaveCustomFields(var formId, FormCollection collection) //var as I don't know what type you are using to Id the form.
{
var binders = this.binders.select(b => b.CanHandle(collection)); //I used IOC to get my list of IBinder objects
// Method 1:
binders.ForEach(b => b.Save(formId, collection)); //This is the execution implementation.
// Method 2:
var commands = binders.Select(b => b.Command(formId, collection));
commands.ForEach(c => c.Execute());
}
public DateBinder : IBinder //Example binder
{
public bool CanHandle(FormCollection collection)
{
return (null != collection["MyDateField"]); //Whatever the name of this field is.
}
//Method 1
public void Save(var formId, FormCollection collection)
{
var value = DateTime.Parse(collection["MyDateField"]);
this.someLogic.Save(formId, value); //Save the value with the formId, or however you wish to save it.
}
//Method 2
public Command Command(var formId, FormCollection collection)
{
//I haven't done command pattern before so I'm not sure exactly what to do here.
//Sorry that I can't help further than that.
}
}
I would think one of the best options is to create a custom model binder, which makes it possible to have custom logic behind the scenes and still very customizable code behind.
Maybe these articles can help you:
http://www.gregshackles.com/2010/03/templated-helpers-and-custom-model-binders-in-asp-net-mvc-2/
http://www.singingeels.com/Articles/Model_Binders_in_ASPNET_MVC.aspx
More specifically I would probably take as the controller argument a custom class with all "base" properties included. The class could then for example include a dictionary linking the name of each field to either just an object or an interface which you implement once for each data-type making it simple to process the data later.
/Victor

Resources