Reputation: 1555
I'm looking for a way to make a generic set of methods in web api 2 that I can use with an http client to hook up to another server.
First, an explanation of my infrastructure. A simplified set up typically employed at my company (omitting database servers and other things) consists of two main servers. There is a web layer server that is used to host apps and sites, and an app layer server that contains services/apis etc.
House rules dictate that an application sitting on the web server cannot directly talk to anything on the app server without going through a proxy or broker service first.
My current solution to this problem is to create an SDK web api service which contains copies of all the routes in the api. The app (in this case an angular app) calls this SDK service via a REST api call, and the job of this intermediate service is to use the .NET HttpClient to translate this call into a call to the "actual" api service running on the other server.
This works great and I've had no problems with it so far, but I get the feeling that there might be a better way to do this sort of stuff. I've searched online for articles describing this kind of thing but have come up short, and I'm by no means an expert at using .NET or web api.
I'll give an example of the solution I have just now - a call to get all the clients that are currently stored in my database.
public class ClientsController: ApiController
{
// http client used to translate calls to api service
private HttpClient httpClient = new HttpClient()
{
BaseAddress = new Uri(ConfigurationManager.AppSettings["apiUrl"])
};
[Route("clients")]
[HttpGet]
public IHttpActionResult GetClients()
{
httpClient.DefaultRequestHeaders.Accept.Clear();
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json");
List<Client> clients = null;
try
{
HttpResponseMessage result = httpClient.GetAsync("clients").Result;
if (result.IsSuccessStatusCode)
{
clients = result.Content.ReadAsAsync<List<Client>>().Result;
return Content(result.StatusCode, clients);
}
// return the failed reason code
return Content(result.StatusCode, result.ReasonPhrase);
}
catch (Exception e)
{
return Content(HttpStatusCode.InternalServerError, e.Message);
}
}
}
This route is then implemented in my api service that is running on the other server, and retrieves the clients from the database, returns that list of clients back to this service to then return to my application to be displayed on the screen.
The pair of methods (one as shown above plus real implementation) are present for every route in the api.
Ideally, I'd like my sdk service to contain one method for each HTTP verb, where it takes the route that is passed to it, calls that route from the api service and handles the return of items or result codes back to it, similar to what it does now, except I would be able to add a new set of endpoints to the api without having to add them in two places.
And this is where I'm stuck. Any insight into either how this could be done or any alternatives that I may not have explored would be great.
Thank you for your time in reading this long question.
Upvotes: 4
Views: 4637
Reputation: 1555
Late reply to this question but I got pulled off the project for something more urgent and am back to finish this one off - managed to get a solution that worked for me - so I thought I would post it here in case it helps anyone else.
In my setup - my pass-through service is under the guise of 'SDK' and I am in complete control of the endpoints to and from both the SDK and the other service which I will call 'API'.
public class SdkController : ApiController
{
private HttpClient httpClient = new HttpClient()
{
BaseAddress = new Uri(ConfigurationManager.AppSettings["apiUrl"])
};
[HttpGet, HttpPost, HttpPut, HttpDelete, Route("v2/{*url}")]
public HttpResponseMessage PerformRequest(string url)
{
httpClient.DefaultRequestHeaders.Accept.Clear();
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
try
{
HttpResponseMessage response = null;
HttpContent requestBody = null;
string requestUrl = Request.RequestUri.LocalPath.Replace("/SDK/v2/", "");
string requestMethod = Request.Method.ToString();
switch (requestMethod)
{
case "GET":
response = httpClient.GetAsync(requestUrl).Result;
break;
case "POST":
requestBody = Request.Content;
response = httpClient.PostAsync(requestUrl, requestBody).Result;
break;
case "PUT":
requestBody = Request.Content;
response = httpClient.PutAsync(requestUrl, requestBody).Result;
break;
case "DELETE":
response = httpClient.DeleteAsync(requestUrl).Result;
break;
}
return response;
}
catch
{
return new HttpResponseMessage()
{
StatusCode = HttpStatusCode.InternalServerError,
ReasonPhrase = "Internal Server Error"
};
}
}
}
I was able to do this because all my endpoint requests to the SDK have the common 'v2/' prefix, and I don't have to care inside this pass-through method where the request is going or what the request is returning or sending - as all the logic to deal with this is sitting in the more bulky API service.
The thing that isn't so good here is my error handling - although I will be fixing it to more accurately project any errors that may be sent back from my API.
Upvotes: 8
Reputation:
You can use ASP.NET Module (implements IHttpModule). This way you can read and alter the request / response anyway you like.
To give you an idea: Some years back I wrote a module which broadcasted the received request to multiple listeners. One of the listeners would handle the response (or if not defined the module would respond with 200 OK). The received url parameters or object are easy to pass to the listeners.
The handling is quit simple. Pass the request to all listeners but ignore the response (fire-and-forget), except for the handler (if defined). Once the response is received just pass this as the response of the module. In case this takes to long you'll get automatically a time-out.
In the configuration I described all accepted routes (so you'll need just one method for all verbs) which mapped to (multiple) other routes including the protocol, verb, and whether the listener should handle the response. This means that you can even convert an https POST to http GET + url parameters.
One point of interest is security. It may not be safe to just pass anything that is received. Also I would not connect the proxy to the database. Before sending you can do some checks and sanitizing.
Please not that you may need to send headers as well. For instance in case a Token was sent.
I hope this gives you an idea. There is enough information to find on the internet, but here are some links:
Upvotes: 2