12

I've been assigned to develop the WebAPI controller for an application (something I had never worked with before). Everything went fine, had some basic requests like GetAllUsers(int id) just for testing reasons - the configuration itself is fine.

Now here's the issue. I have a method GetAllItems(Carrier carrier) where Carrier is a class with a number of different parameters. As we already have a few Carrier instances in the database for testing purposes, what I've tried was querying the database, selecting the instance of Carrier based on the ID (GUID) attribute, but to no result.

Is there a way to test GET requests when the input parameter is an object, rather than a single value (such as int ID, for example) manually, with a test method or a test input parameter of some sort ?

EDIT.: Thanks everyone for the feedback, the solution to my issue was actually much easier to fix than I had expected. I would absolutely love to upvote all of you, although unfortunately my reputation is too low to do so (I'm new to stackoverflow), so I'll have to get back to doing so at some point in the near future. Cheers :)

EvilBeer
  • 1,964
  • 1
  • 15
  • 37
  • 1
    Is the object from the parameter non-null? This may be a problem with serialization. Where does that object come from, how are its properties sent in the GET request? – Slavo Aug 29 '13 at 14:31
  • Please provide more information about what Carrier class is and what is expected result of GetAllItems. It looks like it should return some kind of carrier details record... – Pavel Kutakov Aug 29 '13 at 14:37
  • Yes, it can be done but you probably should read the following posts before deciding to do this: http://stackoverflow.com/questions/11091160/rest-api-get-request-with-body and http://stackoverflow.com/questions/978061/http-get-with-request-body – David Tansey Aug 29 '13 at 14:47

2 Answers2

20

As far as i understand your question, you want to be able to pass the Carrier's properties directly in the URL rather than in your request body.

ex:

[GET] http://localhost/entities?id=000000000000000

You controller method is this one

GetAllItems(Carrier carrier)

Carrier has a Id (Guid) property :

class Carrier {
    public Guid Id { get; set; }
    public string Name { get; set; }
}

Carrier is a complex object in term of WebApi model binding.

Default behavior for model binding is :

By default, Web API uses the following rules to bind parameters: If the parameter is a “simple” type, Web API tries to get the value from the URI. Simple types include the .NET primitive types (int, bool, double, and so forth), plus TimeSpan, DateTime, Guid, decimal, and string, plus any type with a type converter that can convert from a string. (More about type converters later.) For complex types, Web API tries to read the value from the message body, using a media-type formatter.

see: http://www.asp.net/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api

Expecting a model binding with a complex object in the URL is not the WebApi default behavior.

If you want your controller method to model-bind a complex object from the URL you have to tell it.

GetAllItems([FromUri] Carrier carrier)

With the FromUri binding indicator, you can use the complex model binding from the URL

Now you can even add more properties mapping in the URL :

[GET] http://localhost/entities?id=000000000000000&name=ABC

GetAllItems will received a Carrier object populated with : carrier.Id = 0000-00000000000-000; carrier.Name = "ABC"

A. M.
  • 1,515
  • 9
  • 14
  • can I have a method like `Get(int id)` and another like `Get([FromUri]Carrier c)` ..., I m getting 'too many matching `get` error ' ? – eRaisedToX Aug 25 '17 at 06:49
4

You have a routing problem here along with several misconceptions.

The default route for WebApi is:

routes.MapHttpRoute(
            name: "Default",
            routeTemplate: "{controller}/{action}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );

This along with certain Conventions:

  • GetX maps GET methods.
  • InsertX maps POST methods.
  • UpdateX maps PUT methods.
  • DeleteX maps DELETE methods.

When your naming convention is not in alignment with the WepApi conventions then you would need to specify the method, action name, etc.

Same happens with your routes. If you have no other route defined, then only the actions following the convention AND the default route will get bounded.

For instance:

public IEnumerable<Carrier> GetAll(){
   //this will get called when using the route: /api/carriers/
}

public IEnumerable<Carrier> Get(string id){
   //this will be called when using the route: /api/carriers/1
   //where 1 is the carrier id
}

Will work in the CarrierController since they both are aligned with the conventions and the route.

Now, if you need a method that return ALL the items for a carrier you will need this method:

[ActionName("getItems")]
public IEnumerable<Item> GetAllItems(string id){
   //where id is the carrierid       
   var carrierId = id;
   //because you are specifying the ActionName to getItems this will match the following route: 
   // /api/carriers/getItems/1
}

Another option is to create a ItemsController, and add an action that return a list of items based on the carrierId, this is probably better, conceptually, but the routing principle is the same.

Raciel R.
  • 2,096
  • 19
  • 27