We have a Desktop Client and a REST API. For several POST REST calls, we want to pass a correlation GUID from the Client to the API that is used to correlate log entries related to the same user interaction.
This parameter is optional and must be backwards compatible, not failing if it is omitted. What options are there to pass this in while keeping code clean?
I'm concerned that if I put it:
In the header, it means receiving a HttpRequestMessage and manually extracting it, the URL parameters and the body content in each API call.
In the URL, it may not hit the correct route if the parameter is missing (eg. backward compatibility.)
In the body, it pollutes the model as the parameter is related to the system rather than the data.
For example, an existing method is:
[HttpPost]
public HttpResponseMessage SaveData([FromBody]DataModel model)
{
...
}
I feel like I've missed something. Where is the best place for it?
It's strongly depends on your api action either is Get or Post, if you using Get action you can put your parameter in header and also you can use default value for your parameter in route configuration like this:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { **id = RouteParameter.Optional** }
);
If you are using Post action you can easily use default value in method prototype :
Example :
[HttpPost]
public object DoSomething(int logEntryIndex=0)
{
return "Something";
}
But I'm strongly recommend you always use an object as your parameter. Then it's so easy to add any property [as a new parameter] to class and you never would had these kind of problems
[HttpPost]
public object DoSomething(MyParam parameter)
{
return "Something";
}
public class MyParam
{
// prop1
// prop2
}
You can pass the correlation GUID from URL as optional parameter so that it will be backward compatible, clean and will not pollute your data models. For example:
[HttpPost]
[Route("api/Contact/{correlationId:Guid?}")]
public HttpResponseMessage SaveData([FromBody]DataModel model, [FromUri] Guid? correlationId = null)
{
//code to handle request
}
From MSDN:
You can make a URI parameter optional by adding a question mark to the
route parameter. If a route parameter is optional, you must define a
default value for the method parameter.
More information here
Hope it helps.
Related
I am using WebAPI 2 with EF and scaffolding webapi controllers from visual studio.
Each controller is created with 4 default verbs (GET,PUT,DELETE,POST) and 5 actions. while there are two versions of GET action.
IQueryable<entity> GetEntities ()
Task<IHttpActionResult> GetEntity(GUID key) // default is int id but I changed to guid.
I am using attribute routing and route prefix for the controller. just some fancy keywords for better management of url. [RoutePrefix("api/v3/Company")]
Problem :
Ideally when a wrong parameter is sent in url, it should return error, but it is not raising error, instead it fall back to the action without parameter.while if I send a wrong GUID, it shows error.
Like if I call :
http://localhost:8080/api/v3/Company/1f7dc74f-af14-428d-aa31-147628e965b2
it shows the right result.
when I call :
http://localhost:8080/api/v3/Company/1f7dc74f-af14-428d-aa31-147628e96500 (wrong key)
it set back to GetEntity() function and shows all records
when I call:
http://localhost:8080/api/v3/Company/1 (not a GUID length parameter)
it do the same and shows all records.
I am using attribute [Route("{id:guid}")]
Really appreciate if I can get some guidance on this!
It is most likely that the route is defaulting back to the convention-based mapping.
You need to explicitly make apply the route attribute on actions to let the routing know that it is the default route got GET
[RoutePrefix("api/v3/Company")]
public class CompanyController : ApiController {
//GET api/v3/Company
[HttpGet]
[Route("")] //Default Get
public IQueryable GetEntities() { ... }
//GET api/v3/Company/1f7dc74f-af14-428d-aa31-147628e965b2
[HttpGet]
[Route("{id:guid}")] // ALSO NOTE THAT THE PARAMETER NAMES HAVE TO MATCH
public Task<IHttpActionResult> GetEntity(Guid id) { ... }
//...other code removed for brevity
}
Make sure that attribute routing is enabled in the web api config
config.MapHttpAttributeRoutes();
I'm trying to create a REST api based on the Asp.Net MVC4 web api framework. The GET requests are working just fine. All other verbs are getting ignored by the server.
It just says the following:
"No action was found on the controller 'Track' that matches the request."
Though the example error is from the Track controller all other controllers have the same problem.
This is the method on the controller Track I'm trying to call:
[HttpPost]
public Object Index(string token, string juke, string track)
{
}
I'v tried it using a JSON object like so:
{ "track": "0000", "juke": "0000" }
And I tried to use the "normal" way:
track=0000&juke=0000
The '0000' in the examples above are stand-ins for the real id's.
To be sure I'm also posting the Register() from WebApiConfig.cs:
public static void Register(HttpConfiguration config)
{
config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));
config.Routes.MapHttpRoute(
name: "DefaultIndexBackEnd",
routeTemplate: "back-end/{controller}/{token}",
defaults: new { action = "Index", token = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "DefaultBackEnd",
routeTemplate: "back-end/{controller}/{action}/{token}",
defaults: new { token = RouteParameter.Optional }
);
}
Try this in your TrackController. Because you are using multiple parameters, they must be declared as optional.
[HttpPost]
public Object Index(string token="", string juke="", string track="")
{
}
You can make parameter token mandatory because token is declared as optional in routing configuration DefaultIndexBackEnd. I think using [FromBody] attribute is a good idea when there is more than one parameter for POST actions.
First, it's important to understand how a typical REST Web API is supposed to work. Typically, it uses different HTTP verbs (GET, POST, PUT, DELETE) for specific actions.
GET: get an entity (or a collection) from the server
POST: create a new entity
PUT: update an existing entity
DELETE: delete an existing entity
So when I see [HttpPost] on your Index action, it seems to me that the REST API pattern is broken.
Instead, in your controller you should have a Entity Get(int id) action (to get data) and a void Post(Entity entity) action to create new records.
No need to decorate your actions with HttpGet or HttpPost, the MVC Api framework will route the request to your action based on their names.
You can take a look at an example here.
After several hours of trying and researching articles I finally found an article that precisely described my problem! If you're having the same problems, check out the article.
The problem was that I had to use the [FromBody] attribute on one parameter of the of the action. After moving juke and track into a model it finally worked as I had hoped.
Thanks everyone for the help, you all set me on the right track!
I want to implement multiple Get Methods, for Ex:
Get(int id,User userObj) and Get(int storeId,User userObj)
Is it possible to implement like this, I don't want to change action method name as in that case I need to type action name in URL.
I am thinking of hitting the action methods through this sample format '//localhost:2342/'
which does not contains action method name.
Basically you cannot do that, and the reason is that both methods have same name and exactly the same signature (same parameter number and types) and this will not compile with C#, because C# doesn't allow that.
Now, with Web API, if you have two methods with the same action like your example (both GET), and with the same signature (int, User), when you try to hit one of them from the client side (like from Javascript) the ASp.NET will try to match the passed parameters type to the methods (actions) and since both have the exact signature it will fail and raise exception about ambiguity.
So, you either add the ActionName attribute to your methods to differentiate between them, or you use the Route Attribute and give your methods a different routes.
Hope that helps.
You need to add action name to the route template to implement multiple GET methods in ASP.Net Web API controller.
WebApiConfig:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new {id = RouteParameter.Optional }
);
Controller:
public class TestController : ApiController
{
public DataSet GetStudentDetails(int iStudID)
{
}
[HttpGet]
public DataSet TeacherDetails(int iTeachID)
{
}
}
Note: The action/method name should startwith 'Get', orelse you need to specify [HttpGet] above the action/method
My controller currently looks like:
[Jsonp filter]
public class ProductController : Controller
{
public Json GetProduct(string id)
{
Product x;
//code
return Json(x, JsonRequestBehavior.AllowGet);
}
}
I am able to get a product doing this:
api/product/getproduct/5
But, I want to be able to access it like this:
api/product/5
What change do I need to make to do this?
EDIT: I am actually using Jsonp because I need to call this API from a different domain and get a json object back. Would this be possible using ApiController? Otherwise is there a way to do this without switching to ApiController?
You will have to edit your webapiconfig (located in the App_Start folder).
You will need to add something like this before any other route (to make sure it is caught first):
// Map Http Route takes in 3 parameters below
// param 1 is the name of the route.. This has nothing to do with class names or method names
// param 2 is the route itself. Route parameters are denoted in curly braces {likethis}
// param 3 sets up defaults
config.Routes.MapHttpRoute("GetProductApi", "api/product/{id}",
new {
controller = "Product", // the name of the controller class (without the Controller suffix)
action = "GetProduct", // the name of your method
id = RouteParameter.Optional
});
Also, your code for your controller looks like it isn't an API controller. Regardless, this is a routing problem. You can add a route configuration in your regular route config if you 100% need to.
Your code above is not a WebApi Controller it is an MVC Controller. Your class needs to inherits from ApiController instead like:
public class ProductController : ApiController{
...
Regarding your method I am not sure why you used Json as returned type since it is part of the MediaFormatter configuration to define the returned format, it should not be defined at method/function level.
It looks like the correct method declaration will be something like:
public Product GetProduct(string id)
{
Product x; //probably you want initialize it like new Product();
return x;
}
Update JsonP
WebApi works based on MediaFormatters as explained earlier. In order to use JsonP you need to use the proper media formatter there are several out there but how about:
http://www.nuget.org/packages/WebApi.JsonP
If you wish to read more about JsonP formatters for WebApi here is a SO Post about this:
.net 4.5 ASP.Net web API JSONP support
Jsonp in WebApi
Your ProductController should derive from ApiController, instead of Controller.
Building on #Dalorzo's answer, if/when you can convert to an APIController, and if you can use WebAPI 2, you can use decorator attributes on your methods that will alter the routes and even the HTTP verbs to use for the method... which is really nice because everything you need to know about that API call is right there at the function signature. It's quite robust and intuitive, and I highly recommend it.
I have an architecture where I have numerous objects I can configure. Examples of URLs I want are as follows:
/configuration/building/add
/configuration/building/edit/1
/configuration/group/add
/configuration/group/edit/1
I have a Configuration controller but how do I intercept or deal with building/add and building/edit/1 etc... If it were AddBuilding I could simply add an AddBuilding() function, and similarily how do I get it to work for configuration/building/edit/
Here's what you can do for the first one - open up the Global.asax.cs file of your site and put this in RegisterRoutes before the standard MVC catch-all route (the one that uses the route "{controller}/{action}/{id}"):
routes.MapRoute("AddBuilding", "configuration/building/add",
new { controller = "Configuration", action = "AddBuilding" });
The others will be the same, but different names (first parameter) and action, whislt the edit routes but would include an {id} route placeholder and route parameter (but not optional - unlike the MVC default route):
routes.MapRoute("EditBuilding", "configuration/building/edit/{id}",
new { controller = "Configuration", action = "EditBuilding" });
By leaving the id off the route defaults we make it required. I'm assuming this, because I'm guessing the Url /Building/Edit doesn't logically map to anything.
As a side node - including verbs in your urls isn't really in keeping with REST methodology, however you're not the first to do it by a long way (I include myself in that too). That said - trying to keep to it usually makes your life a lot easier, as you'll find your controllers will be cleaner, as will your route table, and your site's URL space will be a lot smaller and more obviously hierarchical. This last point is - handy for zooming around the site at dev time, but more importantly it's crucial for SEO.
So (I've commented this code heavily, hopefully enough to provide some nuggets of knowledge!):
public class ConfigurationController{
////HTTP GET /Buildings
/// DISPLAYS BUILDINGS
public ActionResult Buildings(){
//get model and return view that shows all buildings with perhaps a
//partial in that for creating a new one (or you can use another action)
//The HTML form on that view will POST to the URL handled by the method below.
}
////HTTP POST /Buildings
/// CREATES A NEW BUILDING
//use ActionName here to make this and the method above accessible through
//the same URL
[ActionName("Buildings")]
[HttpPost]
public ActionResult CreateBuilding(BuildingModel model){
//validate the model, create the object and return the same
//view as the Buildings() method above (after re-loading all the
//buildings. Or, you can issue a redirect, effectively transferring
//control back to the method above.
}
////HTTP GET /Configuration/Building/id
///DISPLAYS A BUILDING
public ActionResult Building(int id){
//get building and return view, which also contains Edit functionality
}
////HTTP POST /Configuration/Building/id
///EDITS A BUILDING
[HttpPost]
public ActionResult Building(int id, BuildingModel model){
//very similar to the CreateBuilding method - and again you might
//either simply return a building view at the end, or redirect
//to the method above.
//Note that we don't need [ActionName] here because this and the
//get method can have the same method names, because they are overloads
//i.e. since they have different method signatures we can call them the same
//thing in code.
}
}
I've left off the group stuff to keep it short, and hopefully you'll be able to see how to do it from there.
With this in place, we only need at most two routes in Global.asax.cs - although I think the order will be important:
//handles both GET and POST to this URL, i.e. our view & edit operations
routes.MapRoute("IndividualBuilding", "/configuration/buildings/{id}",
new { controller = "Configuration", action = "Building" });
routes.MapRoute("Buildings", "/configuration/buildings",
new { controller = "Configuration", action = "Buildings" });
Now we are using the HTTP verbs to signify what we intend to do with a particular request, and our URLs have become more 'logical'.
Another refactor
If you want to be 'clever' you can lump both buildings and groups under two routes
//handles both GET and POST to this URL, i.e. our view & edit operations
routes.MapRoute("Individual", "/configuration/{controller}/{id}",
new { controller = "Configuration", action = "List" });
//again, handles GET and POST
routes.MapRoute("Collection", "/configuration/{controller}",
new { controller = "Configuration", action = "Single" });
Now you do both buildings and groups controllers as I showed above, but replace Buildings (remembering the ActionName attribute on the second method) with List and Building with Single.
One final thing to consider is that because of the default MVC route:
routes.MapRoute("Default", "{controller}/{action}/{id}",
new { controller = "Default", action="Home", id = UrlParameter.Optional });
Both of your two controllers can still be routed via /Buildings/Single/1 or /Groups for example. This is a minor issue (dupe content isn't great SEO) but it can be something that people can use to sniff your site.
If you absolutely want to prevent this other url format; you can take out the default route, meaning you'd have to explicitly route other stuff that might already work (not a great issue).
Or you can use a little trick that will make it far harder: use explicit [ActionName] attributes with characters in the route name that won't be allowed through IIS - e.g. ":Single" or ":List", and then adjust our two routes from a couple of code blocks back accordingly.
So firstly you can create a controller action called AddBuilding() as you have hinted.
Then in your Global.asax file in the RegisterRoutes method you can add a route like so:
routes.MapRoute(
"AddBuilding", // Route name
"configuration/building/add", // URL with parameters
new { controller = "Configuration", action = "AddBuilding" }
);
You should not though that you will likely still be able to access the page using "/configuration/addbuilding" because of your default route mapping.
You edit one will be similar expect you will want to map the ID value for this:
routes.MapRoute(
"EditBuilding", // Route name
"configuration/building/edit/{id}", // URL with parameters
new { controller = "Configuration", action = "AddBuilding", id = UrlParameter.Optional }
);
I think you will need to add this code with the default MapRoute setup to ensure that one does not take priority
Another approach would be to create a Configuration MVC area, and then have a building and group controller in that Area.
You can do that by Attribute Routing in ASP.NET MVC 5. Something like following;
// eg: /reviews
[Route(“reviews”)]
public ActionResult Index() { … }
// eg: /reviews/5
[Route(“reviews/{reviewId}”)]
public ActionResult Show(int reviewId) { … }
// eg: /reviews/5/edit
[Route(“reviews/{reviewId}/edit”)]
public ActionResult Edit(int reviewId) { … }
You can add multiple route for the same controller as well. For details please check here