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

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.

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.

In ASP.NET 5, how do I get the chosen route in middleware?

I am building an ASP.NET 5 (vNext) site that will host dynamic pages, static content, and a REST Web API. I have found examples of how to create middleware using the new ASP.NET way of doing things but I hit a snag.
I am trying write my own authentication middleware. I would like to create a custom attribute to attach to the controller actions (or whole controllers) that specifies that it requires authentication. Then during a request, in my middleware, I would like to cross reference the list of actions that require authentication with the action that applies to this current request. It is my understanding that I configure my middleware before the MVC middleware so that it is called first in the pipeline. I need to do this so the authentication is done before the request is handled by the MVC controller so that I can't prevent the controller from ever being called if necessary. But doesn't this also mean that the MVC router hasn't determined my route yet? It appears to me the determination of the route and the execution of that routes action happen at one step in the pipeline right?
If I want to be able to determine if a request matches a controller's action in a middleware pipeline step that happens before the request is handled by the controller, am I going to have to write my own url parser to figure that out? Is there some way to get at the routing data for the request before it is actually handled by the controller?
Edit: I'm beginning to think that the RouterMiddleware might be the answer I'm looking for. I'm assuming I can figure out how to have my router pick up the same routes that the standard MVC router is using (I use attribute routing) and have my router (really authenticator) mark the request as not handled when it succeeds authentication so that the default mvc router does the actual request handling. I really don't want to fully implement all of what the MVC middleware is doing. Working on trying to figure it out. RouterMiddleware kind of shows me what I need to do I think.
Edit 2: Here is a template for the middleware in ASP.NET 5
public class TokenAuthentication
{
private readonly RequestDelegate _next;
public TokenAuthentication(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
//do stuff here
//let next thing in the pipeline go
await _next(context);
//do exit code
}
}
I ended up looking through the ASP.NET source code (because it is open source now!) and found that I could copy the UseMvc extension method from this class and swap out the default handler for my own.
public static class TokenAuthenticationExtensions
{
public static IApplicationBuilder UseTokenAuthentication(this IApplicationBuilder app, Action<IRouteBuilder> configureRoutes)
{
var routes = new RouteBuilder
{
DefaultHandler = new TokenRouteHandler(),
ServiceProvider = app.ApplicationServices
};
configureRoutes(routes);
routes.Routes.Insert(0, AttributeRouting.CreateAttributeMegaRoute(
routes.DefaultHandler,
app.ApplicationServices));
return app.UseRouter(routes.Build());
}
}
Then you create your own version of this class. In my case I don't actually want to invoke the actions. I will let the typical Mvc middleware do that. Since that is the case I gut all the related code and kept just what I needed to get the route data which is in actionDescriptor variable. I probably can remove the code dealing with backing up the route data since I dont think what I will be doing will affect the data, but I have kept it in the example. This is the skeleton of what I will start with based on the mvc route handler.
public class TokenRouteHandler : IRouter
{
private IActionSelector _actionSelector;
public VirtualPathData GetVirtualPath(VirtualPathContext context)
{
EnsureServices(context.Context);
context.IsBound = _actionSelector.HasValidAction(context);
return null;
}
public async Task RouteAsync(RouteContext context)
{
var services = context.HttpContext.RequestServices;
EnsureServices(context.HttpContext);
var actionDescriptor = await _actionSelector.SelectAsync(context);
if (actionDescriptor == null)
{
return;
}
var oldRouteData = context.RouteData;
var newRouteData = new RouteData(oldRouteData);
if (actionDescriptor.RouteValueDefaults != null)
{
foreach (var kvp in actionDescriptor.RouteValueDefaults)
{
if (!newRouteData.Values.ContainsKey(kvp.Key))
{
newRouteData.Values.Add(kvp.Key, kvp.Value);
}
}
}
try
{
context.RouteData = newRouteData;
//Authentication code will go here <-----------
var authenticated = true;
if (!authenticated)
{
context.IsHandled = true;
}
}
finally
{
if (!context.IsHandled)
{
context.RouteData = oldRouteData;
}
}
}
private void EnsureServices(HttpContext context)
{
if (_actionSelector == null)
{
_actionSelector = context.RequestServices.GetRequiredService<IActionSelector>();
}
}
}
And finally, in the Startup.cs file's Configure method at the end of the pipeline I have it setup so that I use the same routing setup (I use attribute routing) for the both my token authentication and mvc router.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
//Other middleware delcartions here <----------------
Action<IRouteBuilder> routeBuilder = routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
};
app.UseTokenAuthentication(routeBuilder);
//Middleware after this point will be blocked if authentication fails by having the TokenRouteHandler setting context.IsHandled to true
app.UseMvc(routeBuilder);
}
Edit 1:
I should also note that at the moment I am not concerned about the extra time required to select the route twice which is what I think would happen here since both my middleware and the Mvc middleware will be doing that. If that becomes a performance problem then I will build the mvc and authentication in to one handler. That would be best idea performance-wise, but what I have shown here is the most modular approach I think.
Edit 2:
In the end to get the information I needed I had to cast the ActionDescriptor to a ControllerActionDescriptor. I am not sure what other types of actions you can have in ASP.NET but I am pretty sure all my action descriptors should be ControllerActionDescriptors. Maybe the old legacy Web Api stuff needs another type of ActionDescriptor.

Catch 404 errors in Asp.net Web API

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.

Controller invoking another controller C# WebApi

I have a controller, it needs to invoke another controller. We WERE doing this work on the client. We want to do this server side for performance reasons.
Request is a POST
Request Url = "http://example.com/api/foo/1234567 (pretty standard url with binding for an id)
Request Data
{
something1:'abc',
something2:'def',
copyFromUrl : '/api/bar/7654321'
};
The copyFromUrl could be any other controller in the application. I don't want to hand jam a bunch of if statements up and down the stack to do the binding.
Complicating the issue is most controllers have three different GET signatures.
Get(sting id)
Get(sting id, string xpath)
Get()
One way of doing this, would be to basically short-circuit HttpServer and HttpClient classes. I am using here ASP.NET Web API 2, but hopefully same technique can be used with original Web API.
Here is the minimalistic working sample:
public class BarController : ApiController
{
// GET http://localhost/api/bar
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] {"Foo Bar", "Progress Bar"};
}
// GET http://localhost/api/bar?bar=Towel Bar
[HttpGet]
public IEnumerable<string> GetCustomBar(string bar)
{
return new string[] {"Foo Bar", "Progress Bar", bar};
}
// POST http://localhost/api/bar?action=/api/bar?bar=Towel Bar
[HttpPost]
public HttpResponseMessage StartAction(string action)
{
var config = new HttpConfiguration();
WebApiConfig.Register(config);
var server = new HttpServer(config);
var client = new HttpClient(server);
var response = client.GetAsync("http://localhost/" + action).Result;
return response;
}
As you can see here, the first two actions differ in parameters, the third action accepts url (as in code example) that allows it to invoke any other action.
We are basically hosting a server in memory, applying same routes our real server has, and then immediately querying it.
Hard-coded localhost is actually not used run-time, the routes ignore it, but we need valid absolute URL name for the internal validation to pass.
This code is just an illustration, proof-of-concept if you may.

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.

Resources