Capturing and caching headers for each request - c#

We are limiting access to an enterprise system by forcing the client to make their CRUD calls through our application, and then our application will forward that very same request to its destination, saving the header information.
Client makes a request to an ApiController
We pass the request to the service layer
The service layer forwards the request its intended enterprise system destination.
To elaborate on the points above:
The client issues a request against this:
[HttpGet]
[Route("opportunities({id:guid})")]
[Route("opportunities")]
public async Task<HttpResponseMessage> GetOpportunity()
{
var query = Request.RequestUri.AbsolutePath.Split('/').Last() + Request.RequestUri.Query;
var response = await _opportunityService.GetOpportunity(query);
return response;
}
The service method GetOpportunity is defined as:
public async Task<HttpResponseMessage> GetOpportunity(string query)
{//at the line below is where i want to send the same headers that were passed in originally at step 1
var response = Client.Instance.GetAsync(Client.Instance.BaseAddress + query); //this is just using HttpClient to make this call
var responseType = response.Result.StatusCode;
if (responseType == HttpStatusCode.NotFound)
return new HttpResponseMessage
{
StatusCode = responseType
};
return await response;
}
How do we save the header information from Step 1?
By using the following middleware I have been able to grab ALL header information; however, I am not sure on how to cache them or make them available to the service layer:
public class HeaderAuthenticationAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var headers = actionContext.Request.Headers;
}
}

You are basically acting as a proxy. I see a few possible options.
One would be to pass the Original Request into the service as an explicit dependency
[HttpGet]
[Route("opportunities({id:guid})")]
[Route("opportunities")]
public async Task<HttpResponseMessage> GetOpportunity() {
var response = await _opportunityService.GetOpportunity(this.Request);
return response;
}
and extract the information there
public async Task<HttpResponseMessage> GetOpportunity(HttpRequestMessage Request) {
//at the line below is where i want to send the same headers that were passed in originally at step 1
var query = Request.RequestUri.AbsolutePath.Split('/').Last() + Request.RequestUri.Query;
var headers = Request.Headers;
var url = Client.Instance.BaseAddress + query;
//create new request and copy headers
var proxy = new HttpRequestMessage(HttpMethod.Get, url);
foreach (var header in headers) {
proxy.Headers.Add(header.Key, header.Value);
}
var response = await Client.Instance.SendAsync(proxy);//This is an assumption.
var responseType = response.StatusCode; //Do not mix blocking calls. It can deadlock
if (responseType == HttpStatusCode.NotFound)
return new HttpResponseMessage {
StatusCode = responseType
};
return response;
}
If you do not want to mix the layers and concerns you can extract the needed information into your own model and pass that to the service in order to recreate the needed request.

Related

How to return whatever is returned from remote server?

I have the following WebClient code that is supposed my code acts as a proxy, and link to remote server, I would like to throw up whatever response that is returned from the remote server, how can I do so? I do not want to handle exceptions or anything, just merely throwing responses.
using (var client = new WebClient())
{
client.Headers[HttpRequestHeader.ContentType] = "application/json";
Uri NODE_LOGIN_PATH = new Uri(URI_Combine(NodeAPI, "auth/login"));
string jsonString = JsonConvert.SerializeObject(login_details);
JObject data = JObject.Parse(await client.UploadStringTaskAsync(NODE_LOGIN_PATH, jsonString));
return data;
}
You appear to be using WebClient in an MVC 4 project, so you're on old stuff. Consider upgrading to HttpClient and ASP.NET Core.
The principle that you want goes something like this:
public class FooController : ApiController
{
public HttpResponseMessage Get()
{
// Do the HTTP call
var httpResponse = client.DoSomeRequest();
// Translate
var apiResponse = new HttpResponseMessage
{
StatusCode = httpResponse.StatusCode.Map(...),
Headers = httpResponse.Headers.Map(...),
Body = httpResponse.Body.Map(...),
};
// Return
return apiResponse;
}
}
So: do the request, and translate (map) it to the HttpResponseMessage (or IHttpActionResult, or ...) that your Web API platform requires.

how to handle Third party API

I have my api created, which internally calls third party api (for which i will refer their api). when their api returns success result (status code 200) and data, i am saving it to database and myapi will also returns 200 status code.
But when their api returns statuscode other than 200 (like- statusCode 401, statusCode 403) then how to handle that in myapi?
plus, in this case myapi should return 200 statuscode or should return the same statusCode which their api returns, if yes How to do that.?
i need to show different toasters on client side based on what response their api returns in angular? how to do that.
i am new to asp.net core web api so can someone help me.
here is my api -
public async Task<IActionResult> AddStudent(student stud)
{
Student Student = await _empRepo.AddOrUpdateStudent(stud);
return new OkObjectResult(student)
}
In the Repo -
public async Task<object> AddOrUpdateStudent(student stud)
{
var response= await testapi.getexternalstudentdata(student.Id);
if(response is Student)
{
//save data in database
// return the same object which saved in DB
}
else{
// what should i do here to return whatever error i am getting to client in asp .net core 3.1
}
}
calling third party api-
public async Task<object> getexternalstudentdata(int StudId)
{
var relativePath = "url"
var response = await SendAsync(HttpMethod.Get, relativePath);
if (!response.IsSuccessStatusCode) return response;
var responseString = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<Student>(responseString);
}
here is sendAsync Method -
private async Task<HttpResponseMessage> SendAsync(HttpMethod method, string relativePath)
{
var request = new HttpRequestMessage(method, _httpClient.BaseAddress + relativePath);
return await _httpClient.SendAsync(request);
}

httpClient.PutAsync() not updating, 415 Unsupported media type

I am building a Rest API and a Rest Client, the api url is https://localhost:44341 and the client url is https://localhost:44305/, specifically I want to be able to edit pages in the client for a small custom cms.
Anyway, to view a page in the client, I do this:
public async Task<IActionResult> Edit(long id)
{
Page page = new Page();
using (var httpClient = new HttpClient())
{
using var response = await httpClient.GetAsync("https://localhost:44341/api/Pages/" + id);
string apiResponse = await response.Content.ReadAsStringAsync();
page = JsonConvert.DeserializeObject<Page>(apiResponse);
}
return View(page);
}
And it works, I get the actual page data from the API, however the PUT method in the client is not working, this is it:
[HttpPost]
public async Task<IActionResult> Edit(Page page)
{
using (var httpClient = new HttpClient())
{
using var response = await httpClient.PutAsync("https://localhost:44341/api/Pages/" +
page.Id, new StringContent(page.ToString()));
string apiResponse = await response.Content.ReadAsStringAsync();
}
return Redirect(Request.Headers["Referer"].ToString());
}
When I submit the form for the above method it just redirects to the previous request but the changes aren't saved.
Here's the put method from the api:
[HttpPut("{id}")]
public async Task<ActionResult> PutPage(long id, Page page)
{
if (id != page.Id)
{
return BadRequest();
}
context.Entry(page).State = EntityState.Modified;
await context.SaveChangesAsync();
return NoContent();
}
When I inspect using breakpoints, I can see that the response in the POST method says 415 unsupported media type
The 415 unsupported media type status code means that
The server is refusing to service the request because the entity of
the request is in a format not supported by the requested resource for
the requested method.
When you use new StringContent(page.ToString()) then the media type for the StringContent created defaults to text/plain.
You need to send content in json format:
var content = new StringContent(JsonConvert.SerializeObject(page, Encoding.UTF8, "application/json");
using var response = await httpClient.PutAsync($"https://localhost:44341/api/Pages/{page.Id}", content);

Is it possible to read results of the XmlSerializer being sent by HTTPClient?

I am using an extension method to post xml using the HTTPClient which works great.
My question: Is it possible to read, log, or display the results of the XmlSerializer data that is being sent/posted ?
public static class HttpExtensions {
public static Task<HttpResponseMessage> PostAsXmlWithSerializerAsync<T>(this HttpClient client, string requestUri, T value)
{
return client.PostAsync(requestUri
, value
, new XmlMediaTypeFormatter { UseXmlSerializer = true }
);
}
}
PostAsync hides the actually sent HttpRequestMessage from you, though you can retrieve it from the response, too so you can trace both the request and response contents:
var response = await client.PostAsync(uri, value, formatter);
Log(response);
If you really want to log the request only, create the request manually:
var request = new HttpRequestMessage(HttpMethod.Post, uri);
request.Content = new StreamContent(myXmlStream);
Log(request);
var response = await client.SendAsync(request);
Log(response);
And now you can create one or two Log overloads. I show it for the response, which includes both the request and response log. This is independent from the format, works for both XML and json content.
protected virtual void Log(HttpResponseMessage response)
{
// Use any log/trace engine here, this example uses Debug
Debug.WriteLine($"Response of the API Call [{response.RequestMessage.Method}] {response.RequestMessage.RequestUri}: {response.StatusCode} {FormatResponse(response)}");
}
private static string FormatResponse(HttpResponseMessage response)
{
var result = new StringBuilder();
result.AppendLine();
result.AppendLine("Original request:");
result.AppendLine(FormatHttpMessage(response.RequestMessage.Headers, response.RequestMessage.Content));
result.AppendLine();
result.AppendLine("Obtained response:");
result.AppendLine(FormatHttpMessage(response.Headers, response.Content));
}
private static string FormatHttpMessage(HttpHeaders headers, HttpContent content)
{
var result = new StringBuilder();
var headersString = headers.ToString();
if (!string.IsNullOrWhiteSpace(headersString))
{
result.AppendLine("Headers:");
result.AppendLine(headersString);
result.AppendLine();
}
if (content != null)
{
result.AppendLine("Content:");
result.AppendLine(content.ReadAsStringAsync().Result);
}
return result.ToString();
}
Yes, you can.
Download and install Fiddler , then filter your requestUri , you can monitor all transferring data such you serialized xml.

HttpClient GetAsync not working as expected

When testing my web API with Postman my API get executes fine!
When it comes to running the code with HttpClient in my client application the code executes without error but without the expected result on the server.
What could be happening?
From my client application:
private string GetResponseFromURI(Uri u)
{
var response = "";
HttpResponseMessage result;
using (var client = new HttpClient())
{
Task task = Task.Run(async () =>
{
result = await client.GetAsync(u);
if (result.IsSuccessStatusCode)
{
response = await result.Content.ReadAsStringAsync();
}
});
task.Wait();
}
return response;
}
Here is the API controller:
[Route("api/[controller]")]
public class CartsController : Controller
{
private readonly ICartRepository _cartRepo;
public CartsController(ICartRepository cartRepo)
{
_cartRepo = cartRepo;
}
[HttpGet]
public string GetTodays()
{
return _cartRepo.GetTodaysCarts();
}
[HttpGet]
[Route("Add")]
public string GetIncrement()
{
var cart = new CountedCarts();
_cartRepo.Add(cart);
return _cartRepo.GetTodaysCarts();
}
[HttpGet]
[Route("Remove")]
public string GetDecrement()
{
_cartRepo.RemoveLast();
return _cartRepo.GetTodaysCarts();
}
}
Note these API calls work as expected when called from Postman.
You shouldn't use await with client.GetAsync, It's managed by .Net platform, because you can only send one request at the time.
just use it like this
var response = client.GetAsync("URL").Result; // Blocking call!
if (response.IsSuccessStatusCode)
{
// Parse the response body. Blocking!
var dataObjects = response.Content.ReadAsAsync<object>().Result;
}
else
{
var result = $"{(int)response.StatusCode} ({response.ReasonPhrase})";
// logger.WriteEntry(result, EventLogEntryType.Error, 40);
}
You are doing fire-and-forget approach. In your case, you need to wait for the result.
For example,
static async Task<string> GetResponseFromURI(Uri u)
{
var response = "";
using (var client = new HttpClient())
{
HttpResponseMessage result = await client.GetAsync(u);
if (result.IsSuccessStatusCode)
{
response = await result.Content.ReadAsStringAsync();
}
}
return response;
}
static void Main(string[] args)
{
var t = Task.Run(() => GetResponseFromURI(new Uri("http://www.google.com")));
t.Wait();
Console.WriteLine(t.Result);
Console.ReadLine();
}
Simple sample used to get page data.
public string GetPage(string url)
{
HttpResponseMessage response = client.GetAsync(url).Result;
if (response.IsSuccessStatusCode)
{
string page = response.Content.ReadAsStringAsync().Result;
return "Successfully load page";
}
else
{
return "Invalid Page url requested";
}
}
I've had a problem with chace control when using httpclient.
HttpBaseProtocalFilter^ filter = ref new HttpBaseProtocolFilter();
filter->CacheControl->ReadBehavior = Windows::Web::Http::Filters::HttpCacheReadBehavior::MostRecent;
HttpClient^ httpClient = ref new HttpClient(filter);
I'm not really sure what the expected results are or what results your getting at all so this is really just a guessing game right now.
When I POST something using HttpClient I found adding headers by hand seemed to work more often than using default headers.
auto httpClient = ref new HttpClient();
Windows::Web::Http::Headers::HttpMediaTypeHeaderValue^ type = ref new Windows::Web::http::Headers::HttpMediaTypeHeaderValue("application/json");
content->Headers->ContentType = type;
If I don't do these 2 things I found, for me anyways, that half the time my web requests were either not actually being sent or the headers were all messed up and the other half of the time it worked perfectly.
I just read a comment where you said it would only fire once, that makes me think it is the cachecontrol. I think what happens is something (Windows?) sees 2 requests being sent that are the exact same, so to speed things up it just assumes the same answer and never actually sends the request a 2nd time

Categories

Resources