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.
Related
I have an ASP.NET MVC4 front end as one project in my solution, and a separate ASP.NET Web API as another project in the same solution. The Web API will contain all of my CRUD operations.
2 questions
How do I call my Web API from my front end to perform CRUD operations? I have my entity data model defined in my Web API project, and I will need to bind my front end views to it, how would I do that?
Once this is deployed to my web servers, the front end will reside on one server, and the Web API will reside on another server (the server that holds most of our web services). So, I guess along the same lines, how would I call the Web API from my front end once deployed? I understand Web API's are simply called with an HTTP request, but in terms of passing my models (which are defined in my Web API project) into my Views (in my front end project), how can I do this?
While Kevin is right, I did this the non-Ajax way. Keep in mind that I am working with JSON data, so this is centered around JSON.
In your controller page, remove anything that has to do with DbContext, Entity Framework, etc. The reason is by default, the controller will want to perform CRUD operations by calling the DbContext, and we don't want this. We want to call the WebAPI instead to do this.
First and foremost, declare some member variables in your controller. The rest of your controller will utilize these:
HttpClient client = new HttpClient();
HttpResponseMessage response = new HttpResponseMessage();
Uri contactUri = null;
In your controller, create a constructor for your controller, as such:
public ContactController()
{
// set base address of WebAPI depending on your current environment
client.BaseAddress = new Uri("http://server/YourAPI/");
// Add an Accept header for JSON format.
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
}
Replace the Index action's code with something like the following. Note that the only relevant pieces are the client.GetAsync() call and the var contacts assignment. Everything else is not necessary for the context of this problem. The value inside the client.GetAsync() should be the name of your controller, prepended by any custom routing you set up in your WebApiConfig.cs - in my case, I added the api part in my route to distinguish between API calls and normal calls:
public ActionResult Index()
{
response = client.GetAsync("api/contact").Result;
if (response.IsSuccessStatusCode)
{
var contacts = response.Content.ReadAsAsync<IEnumerable<Contact>>().Result;
return View(contacts);
}
else
{
// add something here to tell the user hey, something went wrong
return RedirectToAction("Index");
}
}
Replace the Create action (the HttpPost action) with something like the following. Again, the only important piece is the client.PostAsJsonAsync() part - this is what calls the WebAPI's POST action which takes care of, in my case, inserting a new record into the database:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Contact contact)
{
// Create a new product
response = client.PostAsJsonAsync("api/contact", contact).Result;
if (response.IsSuccessStatusCode)
{
return RedirectToAction("Index");
}
else
{
// add something here to tell the user hey, something went wrong
return RedirectToAction("Index");
}
}
Replace the Edit action (the non-HttpPost action) with something like the following. This was a little tricky because in order to edit, you had to retrieve the record first, so basically, the HttpPost version of Edit will contain somewhat similar code, with an additional line of code that performs the edit POST (PUT). Below, we're getting the response from the WebAPI by passing it a specific record ID. So, just like for Index (GET), we are doing the same thing only passing in the ID so we only get back one record. Then, we cast the response to an actual object that can be operated on in the View:
public ActionResult Edit(int id = 0)
{
response = client.GetAsync(string.Format("api/contact/{0}", id)).Result;
Contact contact = response.Content.ReadAsAsync<Contact>().Result;
if (contact == null)
{
return HttpNotFound();
}
return View(contact);
}
Replace the Edit action (the HttpPost action) with something like the following. Below, we're getting the record to be edited by calling client.GetAsync() and passing in the primary key as a parameter (contact_id). Then, we're getting the RequestUri from that response and saving it. Then, we're calling client.PutAsJsonAsync() and passing in the Uri.PathAndQuery (what we just saved) as well as the object to be edited.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(Contact contact)
{
response = client.GetAsync(string.Format("api/contact/{0}", contact.contact_id)).Result;
contactUri = response.RequestMessage.RequestUri;
response = client.PutAsJsonAsync(contactUri.PathAndQuery, contact).Result;
if (response.IsSuccessStatusCode)
{
return RedirectToAction("Index");
}
else
{
// add something here to tell the user hey, something went wrong
return RedirectToAction("Index");
}
}
Replace the Delete action (the non-HttpPost action) with something like the following. So again, we're getting the record from the database by simply calling client.GetAsync() and casting it to an actual object my app knows of.
public ActionResult Delete(int id = 0)
{
response = client.GetAsync(string.Format("api/contact/{0}", id)).Result;
Contact contact = response.Content.ReadAsAsync<Contact>().Result;
if (contact == null)
{
return HttpNotFound();
}
return View(contact);
}
Finally, replace the Delete action (the HttpPost action) with something like the following. Again, we're doing something similar to that of the Edit action. We are getting the record to be deleted, casting it to an object, and then passing that object into a client.DeleteAsync() call, as shown below.
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
response = client.GetAsync(string.Format("api/contact/{0}", id)).Result;
contactUri = response.RequestMessage.RequestUri;
response = client.DeleteAsync(contactUri).Result;
return RedirectToAction("Index");
}
You can call your Web API from the client using jQuery ajax method. But since you are calling from a site other than where the Web API is deployed you will have to use JSONP, instead of JSON. Take a look at this QA to see how you use JSONP with Web API. Your models will be passed as JSON which you will have to render on the client side, instead of using Razor to render it on the server side. I would use something like Knockout to create a View Model on the client that will bind your model to the HTML elements on the client.
I am running WebApi and Mvc from within the same project (so they are in-process). Mvc mostly for serving assets (pages and generated downloads) and web api for ajax data requests.
In order to be RESTish, most of the WebApi requests include a set of links where are generated by the following class:
public class ApiLinkMaker
{
public ApiLinkMaker(UrlHelper url, string authority) {
this.url = url;
this.authority = authority;
}
public ApiLinkMaker(ApiController controller)
: this(controller.Url, controller.Request.RequestUri.Authority) { }
public string MakeLink(string controller, string id) {
return "//" + authority + url.Route("DefaultApi", new { controller = controller, id = id });
}
}
There's a few other methods on there, but this is really the core of things and it works fine.
Now I want to optimize a particular page. Where previously I had two requests
Download the html
Do an Ajax query to get some data (and some links)
Now I realize that for optimization purposes it is better to do just one in this case.
Download the html with the data already JSON embedded into it.
The problem is that since the html is being generated by Mvc, I cannot create an Api UrlHelper that seems to work.
I tried
var url = new UrlHelper(new HttpRequestMessage(verb, controller.Request.Url.AbsoluteUri));
if (!url.Request.Properties.ContainsKey(HttpPropertyKeys.HttpConfigurationKey)) //http://stackoverflow.com/questions/11053598/how-to-mock-the-createresponset-extension-method-on-httprequestmessage
url.Request.Properties.Add(HttpPropertyKeys.HttpConfigurationKey, new HttpConfiguration());
But this still blows up
System.ArgumentException was unhandled by user code
HResult=-2147024809
Message=A route named 'DefaultApi' could not be found in the route collection.
Parameter name: name
Source=System.Web.Http
ParamName=name
StackTrace:
at System.Web.Http.HttpRouteCollection.GetVirtualPath(HttpRequestMessage request, String name, IDictionary`2 values)
at System.Web.Http.Routing.UrlHelper.GetHttpRouteHelper(HttpRequestMessage request, String routeName, IDictionary`2 routeValues)
at System.Web.Http.Routing.UrlHelper.GetHttpRouteHelper(HttpRequestMessage request, String routeName, Object routeValues)
at System.Web.Http.Routing.UrlHelper.Route(String routeName, Object routeValues)
at MyProject.Models.ApiLinkMaker.MakeLink(String controller, String id) in w:\MyProject\Models\ApiLinkMaker.cs:line 42
...
This leads me to think that I'm going about this wrong - that I need to create the url helper from the api routing configuration somehow.
Why create one? There is an instance of the UriHelper exposed as a property on both the MVC Controller and ApiController classes.
public ActionResult Index()
{
string url = Url.RouteUrl("DefaultApi", new {httproute = "", controller = "test"});
return View();
}
Edit: Updated code. While the url helpers are different you can use the MVC url helper to resolve the web api url.
Edit2: The correct method to use if you want to get webapi routes from an Mvc UrlHelper is
string url = Url.HttpRouteUrl("DefaultApi", new {httproute = "", controller = "test"});
I am trying to integrate AngularJS with Spring MVC; but I am not able to post parameters to spring controller as RequestBody. Can some one help me to achieve the same. Below is brief flow of my program.
After doing data entry TodoNewController gets executed. From here I am calling user-defined method "create" which I have defined in services.js. As per the flow after this it should call create method of TodoController.java along with input params; but it is not happening. Can some one let me know what is wrong with the code. Below is the code for same.
controller.js
function TodoNewController($scope, $location, Todo) {
$scope.submit = function () {
Todo.create($scope.todo, function (todo) {
$location.path('/');
});
};
$scope.gotoTodoListPage = function () {
$location.path("/")
};
}
services.js
angular.module('todoService', ['ngResource']).
factory('Todo', function ($resource) {
return $resource('rest/todo/:id', {}, {
'create': {method:'PUT'}
});
});
TodoController.java
#Controller
public class TodoController {
private static final AtomicLong todoIdGenerator = new AtomicLong(0);
private static final ConcurrentSkipListMap<Long, Todo> todoRepository = new ConcurrentSkipListMap<Long, Todo>();
#RequestMapping(value = "/todo", method = RequestMethod.PUT)
#ResponseStatus(HttpStatus.NO_CONTENT)
public void create(#RequestBody Todo todo) {
long id = todoIdGenerator.incrementAndGet();
todo.setId(id);
todoRepository.put(id, todo);
}
}
Spring expects application/x-www-form-urlencoded as the Content-Type of the request. You may try inject $http into your service and invoke $http.defaults.headers.put["Content-Type"] = "application/x-www-form-urlencoded"; at the beginning of it.
Modify the request mapping to match the actual mapping /rest/todo and change the databinding to use #ModelAttribute.
#RequestMapping(value = "/rest/todo", method = RequestMethod.PUT)
#ResponseStatus(HttpStatus.NO_CONTENT)
public void create(#ModelAttribute Todo todo) {
long id = todoIdGenerator.incrementAndGet();
todo.setId(id);
todoRepository.put(id, todo);
}
Isolate the problem first. Is it Spring or Angular that's causing the issue? I suggest you install a Rest client plugin either in Chrome or FireFox. Then create a PUT request and enter the correct endpoint URL. If you're able to receive the correct response, then it means your Angular request is constructed incorrectly.
Now, run your Angular-based client. Make a PUT request. Inspect the parameters and request sent (in Chrome, you can use Developer tools) and see if it matches the request you sent earlier. If it does, then it should work. If not, then you know the problem.
Also, your Angular resource:
$resource('rest/todo/:id')
has a different URL than what you have in your Spring controller
#RequestMapping(value = "/todo", method = RequestMethod.PUT)
So the first one is like 'rest/todo/1' and the latter is '/todo'. I don't think those would match.
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.
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.