I am coding a C# web api 2 webservice and would like some help to get a single item from a request to a webservice.
Here is the web service controller class code:
[RoutePrefix("api")]
public class ItemsWebApiController : ApiController
Here is the web service function:
// GET: api/Getitem/1
[Route("Getitem")]
[System.Web.Http.HttpGet]
[ResponseType(typeof(Item))]
public async Task<IHttpActionResult> GetItem(int id)
{
Item item = await db.items.FindAsync(id);
if (item == null)
{
return NotFound();
}
return Ok(item);
}
Here is the uri to the IIS website:
http://localhost/thephase
Here is the uri that I am accessing:
http://localhost/thephase/api/Getitem/1
Here is the error that is being displayed in the browser:
{"Message":"No HTTP resource was found that matches the request URI
'http://localhost/thephase/api/GetItem/1'.","MessageDetail":"No type
was found that matches the controller named 'GetItem'."}
Here is the WebApiConfig code:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
var appXmlType = config.Formatters.XmlFormatter.SupportedMediaTypes.FirstOrDefault(t => t.MediaType == "application/xml");
config.Formatters.XmlFormatter.SupportedMediaTypes.Remove(appXmlType);
}
}
The error states that the controller is named 'GetItem', and this is incorrect. Hence I am thinking that the problem is in the WebApiConfig route code.
If I remove the int id from the function, then the function is called correctly.
Here is the same function with no parameter:
// GET: api/Getitemnoparameter
[Route("Getitemnoparameter")]
[System.Web.Http.HttpGet]
[ResponseType(typeof(Item))]
public async Task<IHttpActionResult> GetItem()
{
Item item = await db.items.FindAsync(1);
if (item == null)
{
return NotFound();
}
return Ok(item);
}
The following uri accesses the function correctly:
http://localhost/thephase/api/Getitemnoparameter
So the problem has something to do with the int parameter.
Can someone please help me to get access the GetItem function with a parameter?
Because you are using Attribute Routing you also need to specify the parameter for it to work.
Check this tutorial out for a better understanding.
Route Prefixes
[Route("Getitem/{id:int}")]
The Id is an int. int is a value type. Value types cannot be null. You set the parameter to RouteParameter.Optional, but if it's optional, it must be able to be assigned null.
Solution: Use a nullable int
public async Task<IHttpActionResult> GetItem(int? id)
Related
I have implemented a generic controller using c# and webApi 2.0
http://localhost:4200/api/projects
will correctly call GetAllItems() and return the expected results
http://localhost:4200/api/projects/1
does not call GetItemById( )
Instead, it calls GetAllItems( )
prior to building the generic, i built a concrete controller for projects.
Its a cut/paste and it calls the correct methods.
My thinking is that my route is wrong on the generic, or should be different because it is a generic, but I can not seem to come up w/ the correct syntax.
Why is the generic not calling the correct method when the url includes a trailing integer?
Things i tried w.o success
Reordering the methods
Enhancing the verb to be System.Web.Http.HttpGet
Combining GET and ROUTE into 1 tag, comma separated
Specifying [FromUri] on the itemId parameter in the function signature
Commenting out the GetAllItems() --> The requested resource does not support http method 'GET' (This has to be the big clue, but for the life of me...)
Here is an abbreviated listing of the generic template
[RoutePrefix("api/{contoller}")]
[EnableCors(origins: "*", headers: "*", methods: "*")]
public class baseController<T, T_Q > : ApiController
where T:pgBaseClass, new()
where T_Q : sbQuery<T> , new()
{
[HttpGet]
[Route("")]
public CustomJsonStringResult GetAllItems()
{
T_Q q = new T_Q();
List<T> l = q.Items();
string json = q.ListToJSON(l);
return JSONStringResultExtension.JSONString(this, json, HttpStatusCode.OK);
}
[HttpGet]
[Route("{itemId:int}")]
public IHttpActionResult GetItemById(int itemId)
{
T_Q q = new T_Q();
T p = q.GetById(itemId);
if (p == null)
{
return JSONStringResultExtension.JSONString(this, "Item not Found", HttpStatusCode.NotFound);
}
else
{
return JSONStringResultExtension.JSONString(this, p.JSON, HttpStatusCode.OK);
}
}
}
Here is the definition for the projects controller using the generic
public class ProjectsController : baseController<pgProject,pgProjectQuery>
{
}
Here is an abbreviated listing of the non generic controller that works as expected.
(I am excluding one or the other to get the project to compile and run...)
[RoutePrefix("api/projects")]
[EnableCors(origins: "*", headers: "*", methods: "*")]
public class ProjectController : ApiController
{
//[HttpGet]
[Route("")]
public CustomJsonStringResult GetAllItems()
{
pgProjectQuery ag = new pgProjectQuery();
ag.SortExpression = " [Name] asc ";
List<pgProject> l = ag.Items();
string json = ag.ListToJSON(l);
return JSONStringResultExtension.JSONString(this, json, HttpStatusCode.OK);
}
[HttpGet]
[Route("{itemId:int}")]
public IHttpActionResult GetItemById(int itemId)
{
pgProjectQuery q = new pgProjectQuery();
pgProject p = q.GetById(itemId);
if (p == null)
{
return JSONStringResultExtension.JSONString(this, "Item not Found", HttpStatusCode.NotFound);
}
else
{
return JSONStringResultExtension.JSONString(this, p.JSON, HttpStatusCode.OK);
}
}
}
I had this configured in webApiConfig.js
here i have the sole route defined w/ an optional parameter called id.
I changed the parameter name in my GetItemById() method and this works.
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
config.EnableCors();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
[System.Web.Http.HttpGet]
[Route("{id:int}")]
public CustomJsonStringResult GetItemById([FromUri] int id){...}
What I can not explain is why the non generic controller worked as expected but the generic-based controller does not.
I do believe things are probably 'more correct' now, just wish I had a factual answer.
I also ran into the same problem with my patch method. Renamed the parameter to id and it worked.
Hope this helps someone down the line.
I am trying to just do a simple file upload API using Web API.
Here is the Controller:
[RoutePrefix("api/resize")]
public class ResizeController : ApiController
{
[HttpPost, Route("api/resize/preserveAspectRatio")]
public async Task<IHttpActionResult> resizePreserveAspectRatio()
{
if (!Request.Content.IsMimeMultipartContent())
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
int maxWidth = 100;
int maxHeight = 100;
var provider = new MultipartMemoryStreamProvider();
await Request.Content.ReadAsMultipartAsync(provider);
foreach (var file in provider.Contents)
{
var filename = file.Headers.ContentDisposition.FileName.Trim('\"');
var buffer = await file.ReadAsByteArrayAsync();
//Do whatever you want with filename and its binaray data.
}
return Ok();
}
}
This is my WebApiConfig:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
When I POST a file with PostMan, here is the error I get:
{
"Message": "No HTTP resource was found that matches the request URI 'http://localhost:26303/api/resize/preserveAspectRatio'.",
"MessageDetail": "No type was found that matches the controller named 'resize'."
}
This is not a dupe - was not able to find another article that addresses this specific combination.
This as you would expect is a routing issue. The comments have already identified that you have conflicts with your route and route prefix attributes resulting in the following route
api/resize/api/resize/preserveAspectRatio
being mapped to your action.
To get the desired route, you can either remove the prefix from the controller itself.
//removed prefix
public class ResizeController : ApiController {
//Matches POST api/resize/preserveAspectRatio
[HttpPost, Route("api/resize/preserveAspectRatio")]
public async Task<IHttpActionResult> resizePreserveAspectRatio() {
//...removed for brevity
}
}
Or Remove it from the route on the method
[RoutePrefix("api/resize")]
public class ResizeController : ApiController {
//Matches POST api/resize/preserveAspectRatio
[HttpPost, Route("preserveAspectRatio")]
public async Task<IHttpActionResult> resizePreserveAspectRatio() {
//...removed for brevity
}
}
Or override the route prefix by using tilde (~) on the method attribute
[RoutePrefix("api/resize")]
public class ResizeController : ApiController {
//Matches POST api/resize/preserveAspectRatio
[HttpPost, Route("~/api/resize/preserveAspectRatio")]
public async Task<IHttpActionResult> resizePreserveAspectRatio() {
//...removed for brevity
}
}
Reference Attribute Routing in ASP.NET Web API 2
I'm using a custom HTTP controller selector to version my API.
config.Services.Replace(typeof(IHttpControllerSelector), new NamespaceSelector(config));
Below is my controller with actions:
[RoutePrefix("api/v1/messages")]
public class MessagesController : ApiController
{
[Route("unreadall")] // api/v1/messages/unreadall
public IEnumerable<long> UnreadAll()
{
// Return value;
}
[Route("{type}/unreadall")] // api/v1/messages/{type}/unreadall
public IEnumerable<long> UnreadAll(string type)
{
// Return value;
}
[Route("unreadnext")] // api/v1/messages/unreadnext
public long UnreadNext()
{
// Return value;
}
[Route("{type/}unreadnext")] // api/v1/messages/{type}/unreadnext
public long UnreadNext(string type)
{
// Return value;
}
[Route("{id:long}/markasread")] // api/v1/messages/123/markasread
[HttpPut]
public string MarkAsRead(long id)
{
// Return value;
}
[Route("{id:long}")] // Default Action
public string Get(long id) // api/v1/messages/123
{
// Return value;
}
[Route("")] // Default Action
[HttpPost]
public long Post(string message) // api/v1/messages
{
// Return value;
}
}
Below is my route config:
config.Routes.MapHttpRoute(
name: "DefaultApi1",
routeTemplate: "api/{version}/{controller}/{id}/{action}"
);
config.Routes.MapHttpRoute(
name: "DefaultApi2",
routeTemplate: "api/{version}/{controller}/{action}"
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{version}/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
When I test my routes, the following work.
/api/v1/messages/unreadall
/api/v1/messages/unreadnext
/api/v1/messages/123/markasread
But the below routes, also point to the same actions.
/api/v1/messages/type/unreadall
/api/v1/messages/type/unreadnext
And I get errors for the rest of my routes.
/api/v1/messages/123
Error:
{
"Message": "No HTTP resource was found that matches the request URI 'http://localhost:59411/api/v1/messages/123'.",
"MessageDetail": "No action was found on the controller 'MessagesController' that matches the name '123'."
}
POST: /api/v1/messages
Error:
{
"Message": "The requested resource does not support http method 'POST'."
}
Can someone please tell what I'm doing wrong with my route configuration ? or can someone please post working route configuration for my scenarios above ?
Appreciate your help !
Cheers,
What you are getting it's the expected behavior: the routes defined in route config works, while the attribute routes does not.
This happens because request.GetRouteData() is not taking into account attribute routes. This makes sense of course because there is no specific controller that a route points to as attributed routes are related to methods, not controllers.
When you use attribute routing, all the route attributes get added to a common route without a name. This is a special route that is an instance of an internal class called RouteCollectionRoute. This route has a collection of sub-routes that you can query for that includes all the attribute routes. But if you just want the selected route for your call, you can simple ask for it using the RouteData.Values:
var routeData = request.GetRouteData();
var subroutes = (IEnumerable<IHttpRouteData>)routeData.Values["MS_SubRoutes"];
var route = subroutes.First().Route;
Source: http://wildermuth.com/2013/11/12/Web_API_2_s_Attribute_Routing_Looking_Deeper
I'm setting up backend for my Windows Phone 8.1 App. I'm using ASP.net WebApi to create RESTful api for accessing data from DB, which is set up on Windows Azure.
This is how my routes looks:
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "DefaultNamedApi",
routeTemplate: "api/{controller}/{name}",
defaults: new { name = RouteParameter.Optional }
);
What i'm trying to achieve is accesing data using not an integer - I want to access data using a string.
This is code from my WebApi controller:
private SmokopediaContext db = new SmokopediaContext();
// GET api/Images
public IQueryable<ImageModel> GetImageModels()
{
return db.ImageModels;
}
// GET api/Images/5
[ResponseType(typeof(ImageModel))]
public IHttpActionResult GetImageModel(int id)
{
ImageModel imagemodel = db.ImageModels.Find(id);
if (imagemodel == null)
{
return NotFound();
}
return Ok(imagemodel);
}
public IHttpActionResult GetImageModel(string name)
{
ImageModel imagemodel = db.ImageModels.Find(name);
if(imagemodel == null)
{
return NotFound();
}
return Ok(imagemodel);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
db.Dispose();
}
base.Dispose(disposing);
}
private bool ImageModelExists(int id)
{
return db.ImageModels.Count(e => e.ID == id) > 0;
}
Most important code is an overload to GetImageModel with string parameter.
Server is returning error which says that parameter of url is incorrect:
<Error><Message>The request is invalid.</Message><MessageDetail>The parameters dictionary contains a null entry for parameter 'id' of non-nullable type 'System.Int32' for method 'System.Web.Http.IHttpActionResult GetImageModel(Int32)' in 'Smokopedia.Controllers.DragonController'. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter.</MessageDetail></Error>
What should I correct in my route?
There is no difference in URI template terms between api/{controller}/{id} and api/{controller}/{name}; the arbitrary names you assign to the parameters can't be used in resolving the route.
Take a look at Overload web api action method based on parameter type for an example of how to "overload" routes based on parameter types.
Use Attribute Routing:
[Route("api/Images/{id:int}")]
public IHttpActionResult GetImageModel(int id){ do something}
[Route("api/Images/{id}")]
public IHttpActionResult GetImageModel(string id) {do something}
Your url/query is important here.
You are probably using:
http://yourapp.address/<Controller>/<name>
That will not work in this registration. Your routing matches that address to first matching route. Ant that is DefaultApi. You must use
http://yourapp.address/<Controller>?name=<name>
or change Your routing.
I am using the default routing setup in WebApiConfig (MVC 4) but for some reason I am getting unexpected results.
If I call /api/devices/get/ it hits the Get function but the Id is "get" rather than 1. If I call /api/devices/get/1 I get a 404. I also want to be able to support multiple parameters i.e.
public Device[] Get(int? page, int? pageSize) // for multiple devices
The route
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional });
}
And my API:
public class DevicesController : ApiController
{
EClient client = new EClient();
// GET api/devices/5
public Device Get(string id)
{
return client.GetDeviceBySerial(id);
}
}
id in the controller parameter should be integer:
public Device Get(int id)
{
return client.GetDeviceBySerial(id);
}
if you need to pass in string, or other prams, just use quesry string:
public Device Get(int id, string pageSize)
{
return client.GetDeviceBySerial(id);
}
the above can be called as:
/api/devices/1
or
/api/devices/?id=1&pageSize=10
Note: you do not need to specify method name. Web API will judge that on the basis of HTTP Verb used. If its a GET request, it will use the Get method, if its a POST request, then it will use Post method ... and so on.
You can change the above behavior, but I guess you mentioned that you want to keep usign the default Routing ... so I am not covering that.