Catch 404 errors in Asp.net Web API - asp.net

I am trying to catch 404 errors which are returned by the Asp.net Web API server.
However, Application_Error from inside Global.asax is not catching them.
Is there a way to handle these errors?

You might want to take a look at Handling HTTP 404 Error in ASP.NET Web API which has a step by step example

I know this is old, but I was also just looking for this, and found a very easy way that seems to work, so thought I'd add incase this can help someone else.
The solution I found, that works for me, is here. Also, this can be mixed with attribute routing (which I use).
So, in my (Owin) Startup class I just add something like..
public void Configuration(IAppBuilder app)
{
HttpConfiguration httpConfig = new HttpConfiguration();
//.. other config
app.UseWebApi(httpConfig);
//...
// The I added this to the end as suggested in the linked post
httpConfig.Routes.MapHttpRoute(
name: "ResourceNotFound",
routeTemplate: "{*uri}",
defaults: new { controller = "Default", uri = RouteParameter.Optional });
// ...
}
// Add the controller and any verbs we want to trap
public class DefaultController : ApiController
{
public IHttpActionResult Get(string uri)
{
return this.NotFound();
}
public HttpResponseMessage Post(string uri)
{
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.NotFound, "I am not found");
return response;
}
}
Above you can then return any error object (in this example I am just returning a string "I am not found" for my POST.
I tried the xxyyzz (no named controller prefix) as suggested by #Catalin and this worked as well.

Related

ASP.NET ApiController inside a webform can't reach methods

I can't reach any methods from my ApiController in anyway, the routing does appear if i try to reach it by a browser but no methods are shown.
My Controller:
namespace AgroRiego.Controllers
{
public class datacontrol : ApiController
{
[HttpGet, Route("api/get")]
public string Get([FromUri]string user, string pass)
{
string check = SQL.Reader("SELECT * FROM users WHERE username='" + user + "' AND password='" + pass + "'");
if (String.IsNullOrWhiteSpace(check))
{
return "error en credenciales";
}
DataTable horarios = SQL.table_read("SELECT * FROM horario_riego");
string json = Utils.ConvertDataTabletoJSON(horarios);
return json;
}
[HttpPost, Route("api/post")]
public void Post([FromBody]string value)
{
string i = value;
}
}
}
my global asax:
namespace AgroRiego
{
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
GlobalConfiguration.Configure(WebApiConfig.Register);
}
}
}
and my webapiconfig:
namespace AgroRiego
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// ConfiguraciĆ³n y servicios de API web
// Rutas de API web
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
}
i have more webforms inside the project (originally it was just html pages with serverside code, but i need to add a couple methods to retrieve and send data, help much appreciated!
EDIT1: i managed to reach HTTP 200 changing the URL but i can't reach the methods anyway (in debug mode it does not stop on the breakpoints) how can i route correctly the Api (so it is not Login.aspx) and how do i fix the methods reaching?
EDIT2: i read in documentation that i need this line in global:
RouteConfig.RegisterRoutes(RouteTable.Routes);
but im not using MVC does that matter? i tried reaching the routes with a brand new MVC Web Api and it yields "No Response"
use a routerprefix with your controller. So you access the URL as
http://localhost/routerprefix/router
HttpClient class can be use to send and receive HTTP requests and responses. Since you are trying to consume a WebApi from a aspx page, better way is to create a HttpClient instance
Below is a very simple implementation. Please check this url for further information
MSDN sample
HttpClient client = new HttpClient();
HttpResponseMessage response = await client.GetAsync("http://localhost:49342/api/get");
if (response.IsSuccessStatusCode)
{
product = await response.Content.ReadAsAsync();
}
By the look of your set up, it seems correct
you have got:
config.MapHttpAttributeRoutes(); - setup the attribute route
config.Routes.MapHttpRoute( - setup the default route
GlobalConfiguration.Configure(WebApiConfig.Register); - to register at startup
so it should work.
I think the problem you are having is the way you are calling it
WebAPI routing work a little different to MVC
for example:
In you get method, the route is set as below
[HttpGet, Route("api/get")]
so you should call it {host}/api/get using a GET http method
in the screen shot, you are calling using {host}/api/get/Get - which would not have work, because no route would match
Same for your POST method
So give it another try and you should be able to reach it
The URL to add in the rest testing tool is
http://localhost:49342/api/get
Method type is GET
If you are calling this web api from aspx page use the httpClient class.

Web API routing for non-REST services

I'm designing a webservice which has nothing to do with REST. It backs up a single-page application and currently must implement three simple methods:
public class ImportController : ApiController
{
[HttpPost]
public string[] Parse(string source) { ... }
[HttpPost]
public ConvertResponse Convert(ConvertRequest request) { ... }
[HttpGet]
public object GetHeaders() { ... }
}
It worked pretty well when I was using Controller, except for one thing: I needed to convert all returned JSON data to camelCase. I found a pretty reasonable solution on the web which used CamelCasePropertyNamesContractResolver, but it was only applicable to WebApi controllers since MVC controllers that return JsonResult always use JavascriptSerializer and ignore this configuration.
When I switched the base class to ApiController, the routing broke: GetHeaders works, but other methods return a 404 error!
My route configuration is as follows:
routes.MapHttpRoute(
name: "ImportParse",
routeTemplate: "import/{action}",
defaults: new { controller = "Import" }
);
The successful request (AngularJS):
var baseUrl = 'http://localhost:3821/';
$http.get(baseUrl + 'import/getHeaders').success( ... );
The unsuccessful request:
$http.post(baseUrl + 'import/parse', { source: 'test' }).success( ... );
Error:
message: "No HTTP resource was found that matches the request URI 'http://localhost:3821/import/parse'."
messageDetail: "No action was found on the controller 'Import' that matches the request."
How do I define the correct routing rules for those methods?
Most probably Web Api is looking for a Parse action that supports HttpPost and has object parameter. Because you post object, but not a string, that is why you get 404.
To solve this problem try to send :
$http.post(baseUrl + 'import/parse', 'test').success( ... );

How can I MapHttpRoute a POST to a custom action using the WebApi?

I'm trying to figure out the madness behind the Web API routing.
When I try to post data like this:
curl -v -d "test" http://localhost:8088/services/SendData
I get a 404, and the following error message:
{"Message":"No HTTP resource was found that matches the request URI 'http://localhost:8088/services/SendData'.","MessageDetail":"No action was found on the controller 'Test' that matches the request."}
Here is the code for my test server.
public class TestController : ApiController
{
[HttpPost]
public void SendData(string data)
{
Console.WriteLine(data);
}
}
class Program
{
static void Main(string[] args)
{
var config = new HttpSelfHostConfiguration("http://localhost:8088");
config.Routes.MapHttpRoute(
name: "API Default",
routeTemplate:"services/SendData",
defaults: new { controller = "Test", action = "SendData"},
constraints: null);
using (var server = new HttpSelfHostServer(config))
{
server.OpenAsync().Wait();
Console.WriteLine("Press Enter to quit.");
Console.ReadLine();
}
}
}
More generally, why has the ASP.NET team decided to make the MapHttpRoute method so confusing. Why does it take two anonymous objects....how is anyone supposed to know what properties these objects actually need?
MSDN gives no help: http://msdn.microsoft.com/en-us/library/hh835483(v=vs.108).aspx
All the pain of a dynamically typed language without any of the benefit if you ask me...
Agree with you, it's a hell of a madness, you need to specify that the data parameter should be bound from the POST payload, since the Web API automatically assumes that it should be part of the query string (because it is a simple type):
public void SendData([FromBody] string data)
And to make the madness even worse you need to prepend the POST payload with = (yeah, that's not a typo, it's the equal sign):
curl -v -d "=test" http://localhost:8088/services/SendData
You could read more about the madness in this article.
Or stop the madness and try ServiceStack.
Use this signature and it will work every time.
public class TestController : ApiController
{
[HttpPost]
[ActionName("SendData")]
public HttpResponseMessage SendData(HttpRequestMessage request)
{
var data = request.Content.ReadAsStringAsync().Result;
Console.WriteLine(data);
}
}
Try with the following change,
public class TestController : ApiController
{
[HttpPost]
[ActionName("SendData")]
public void SendData(string data)
{
Console.WriteLine(data);
}
}
The ActionName attribute might fix the issue. Otherwise, you can also the name convention "Post"
public void Post(string data)
{
Console.WriteLine(data);
}
And send an Http Post directly to "services" without SendData.

WebAPI service design

I'm pretty comfortable with how Asp.NET MVC controllers worked when designing services.
However the new WebAPI controllers. how am I supposed to design my services here?
Lets say we have 3 different ways to list e.g. Users.
Get 10 latest , Get all, Get inactive or whatever.
none of these might need parameters. so how would you solve this in WebAPI
IEnumerable<User> Get10Latest()
IEnumerable<User> GetAll()
IEnumerable<User> GetInactive()
That won't work since they have the same param signature.
So what is the correct way to design this here?
You can support multiple methods in one controller for a single HTTP method by using the action parameter.
E.g.
public class UsersController : ApiController
{
[ActionName("All")]
public HttpResponseMessage GetAll()
{
return new HttpResponseMessage();
}
[ActionName("MostIQ")]
public HttpResponseMessage GetMostIQ()
{
return new HttpResponseMessage();
}
[ActionName("TenLatest")]
public HttpResponseMessage GetTenLatest()
{
return new HttpResponseMessage();
}
}
Unfortunately, I have not found a way to get a single controller to handle both with and without the action at the same time.
e.g.
public class UsersController : ApiController
{
[ActionName("")] // Removing this attribute doesn't help
public HttpResponseMessage Get()
{
return new HttpResponseMessage();
}
[ActionName("All")]
public HttpResponseMessage GetAll()
{
return new HttpResponseMessage();
}
[ActionName("MostIQ")]
public HttpResponseMessage GetMostIQ()
{
return new HttpResponseMessage();
}
[ActionName("TenLatest")]
public HttpResponseMessage GetTenLatest()
{
return new HttpResponseMessage();
}
}
Being able to use a single controller for a collection resource and all of its subsets would be nice.
Someone will probably be along and wrap me on the knuckles for this, but you need to configure your routing to handle the Gets. This is how I got it working with the above operations:
config.Routes.MapHttpRoute(
name: "CustomApi",
routeTemplate: "api/{controller}/{action}",
defaults: new { id = RouteParameter.Optional }
);
So now your requests are mapped to the correct controller -> action via the route template. Note that the new route needs to be registered first in WebApiConfig.cs. If you keep the old, default one.
EDIT
Having re-read the question I realize I wasn't quite answering the design question. I would think that one way to go about it, from a REST perspective, would be to use a separate resource to expose the proper collections (Get10Latest for example) since I assume that there is a business reason for exposing that exact subset of data through the service. In that case you'd expose that resource though a single Get in its own Controller (if that is the desired behaviour).
Well why not have urls like this:
GET /users
GET /users/latest
GET /users/inactive
Using routing you could route them to
public classs UserController : ApiController
{
public IEnumerable<User> Get(string mode)
{
// mode is in routing restricted to be either empty, latest, or inactive
}
}
Otherwise use multiple controllers. The use of action names in Web API is kind of a wrong way to about it.

How do I do RPC style Asp.Net Web API calls properly?

UPDATE 9/12/2012:
I shared my code with a co-worker and everything worked fine for him the first time without any changes. So, there must be something environmental on my box, Anyone have any thoughts?
See Update Below
Set Up:
.Net 4.5
Self Hosted (console app) .Net 4.5 Web API application
Test harness using MSTest
My Web API app is mostly full of REST ApiControllers which all work properly as I expect with standard CRUD type stuff. Now I have a requirement (to add some objects to an internal queue) which doesn't seem to fit well into the REST CRUD model. I found this article which seems to say that you can do RPC style non-REST operations in Web API just fine.
I've written a new controller which looks like this:
public class TaskInstanceQueueController : ApiController
{
public void Queue(TaskInstance taskInstance)
{
// Do something with my taskInstance
Console.WriteLine("Method entered!");
}
}
In my proxy class which calls this, I have code which looks like this:
public class TaskInstanceQueueProxy : ITaskInstanceQueueProxy
{
readonly HttpClient _client = new HttpClient();
public TaskInstanceQueueProxy()
{
var apiBaseUrl = System.Configuration.ConfigurationManager.AppSettings["APIBaseUrl"];
_client.BaseAddress = new Uri(apiBaseUrl);
_client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
public void QueueTaskInstances(TaskInstance taskInstance)
{
QueueTaskInstanceViaAPI(taskInstance);
}
private async void QueueTaskInstanceViaAPI(TaskInstance taskInstance)
{
var response = await _client.PostAsJsonAsync("api/TaskInstanceQueue/Queue", taskInstance);
var msg = response.EnsureSuccessStatusCode();
}
}
Here are my routes:
config.Routes.MapHttpRoute("API Default", "api/{controller}/{id}", new {id = RouteParameter.Optional});
config.Routes.MapHttpRoute("API RPC Style", "api/{controller}/{action}", new { id = RouteParameter.Optional });
When I run a test against my proxy, I don't get any errors, but no break point ever hits inside my controller method, nor does the Method entered! message appear in the console. The break line on the var msg line never hits either. For whatever reason, it doesn't look like I'm properly using the HttpClient object to do this.
Again, this web api app is working just fine with a bunch of other apicontrollers, but they're all doing standard REST stuff.
Anyone have any clues?
UPDATE
If I put a try/catch around the PostAsJsonAsync call, I get the following:
A first chance exception of type 'System.Threading.ThreadAbortException' occurred in mscorlib.dll
System.Threading.ThreadAbortException: Thread was being aborted.
at System.Threading.Tasks.TaskHelpers.RunSynchronously(Action action, CancellationToken token)
at System.Net.Http.Formatting.JsonMediaTypeFormatter.WriteToStreamAsync(Type type, Object value, Stream writeStream, HttpContent content, TransportContext transportContext)
at System.Net.Http.ObjectContent.SerializeToStreamAsync(Stream stream, TransportContext context)
at System.Net.Http.HttpContent.LoadIntoBufferAsync(Int64 maxBufferSize)
at System.Net.Http.HttpClientHandler.PrepareAndStartContentUpload(RequestState state)
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at TaskManagerProxy.TaskInstanceQueueProxy.<QueueTaskInstanceViaAPI>d__0.MoveNext() in c:\Moso\MOSO\MOSO.Infrastructure\tm\TaskManagerProxy\TaskManagerProxy\TaskInstanceQueueProxy.cs:line 30
Line 30 is the line with the call.
This answer does kind of depends on how many other methods you have defined in TaskInstanceQueueController. Assuming Queue is your only one then I believe your routes would already work (albeit they are a bit untidy).
I have just built an example version of your code and managed to successfully Post to the Queue method and hit a break point by using Fiddler and Curl. I have elaborated on your example a little and showed how the RPC actions could be mixed with normal REST methods.
The example code is located on GitHub here.
Basically the issue is not specifically to do with the WebApi element (routes, config etc, although you should probably remove the Optional id and add the HttpPost attribute to the queue method) instead as your inital question suggested it is how you are calling the server and this should probably be another question.
It is unclear whether you have two projects and how the MS Test code is hosted etc?... but there is a good example of a WebApi integration test here that you can follow and when debugging the API using tools like Fiddler can quickly help eliminate and debug the routing config issues.
Working console program:
static void Main(string[] args)
{
// Set up server configuration
HttpSelfHostConfiguration config = new HttpSelfHostConfiguration("http://localhost:8080");
//Route Catches the GET PUT DELETE typical REST based interactions (add more if needed)
config.Routes.MapHttpRoute("API Default", "api/{controller}/{id}",
new { id = RouteParameter.Optional },
new { httpMethod = new HttpMethodConstraint(HttpMethod.Get, HttpMethod.Put, HttpMethod.Delete) });
//This allows POSTs to the RPC Style methods http://api/controller/action
config.Routes.MapHttpRoute("API RPC Style", "api/{controller}/{action}",
new { httpMethod = new HttpMethodConstraint(HttpMethod.Post) });
//Finally this allows POST to typeical REST post address http://api/controller/
config.Routes.MapHttpRoute("API Default 2", "api/{controller}/{action}",
new { action = "Post" },
new { httpMethod = new HttpMethodConstraint(HttpMethod.Post) });
using (HttpSelfHostServer server = new HttpSelfHostServer(config))
{
server.OpenAsync().Wait();
Console.WriteLine("Press Enter to quit.");
Console.ReadLine();
}
}
Working Controller
public class TaskInstanceQueueController : ApiController
{
public void Get(string id)
{
// Do something with my taskInstance
Console.WriteLine("Method entered!" + id);
}
[ActionName("Post")]
[HttpPost]
public void Post(TaskInstance taskInstance)
{
// Do something with my taskInstance
Console.WriteLine("REST Post Method entered!");
}
[ActionName("Queue")]
[HttpPost]
public void Queue(TaskInstance taskInstance)
{
// Do something with my taskInstance
Console.WriteLine("Queue Method entered!");
}
[ActionName("Another")]
[HttpPost]
public void Another(TaskInstance taskInstance)
{
Console.WriteLine("Another Method entered!");
}
}
You have ambiguous routes.
config.Routes.MapHttpRoute("API Default", "api/{controller}/{id}", new {id = RouteParameter.Optional});
config.Routes.MapHttpRoute("API RPC Style", "api/{controller}/{action}", new { id = RouteParameter.Optional });
When the request comes in for /api/TaskInstanceQueue/Queue, it matches the first route, and so the route data contains { controller = "TaskInstanceQueue", id = "Queue" }. The system then tries to discover the Post method, and cannot do so, because you have no Post (or PostXxx) method, so your HTTP call is failing.
You have a couple choices. One is you can put an explicit route (first) for this one controller:
config.Routes.MapHttpRoute("API RPC Style", "api/TaskInstanceQueue/{action}", new { controller = "TaskInstanceQueue" });
config.Routes.MapHttpRoute("API Default", "api/{controller}/{id}", new {id = RouteParameter.Optional});
The other is, if you know your IDs will always be numbers only, you can add a constraint to the ID-based route, which will cause it to not match "Queue" as an ID, and therefore fall down into the correct action-based route.

Resources