I have written a RESTful web service using MVC4 Web API. One of the clients that is calling my web service is posting an XML body, but getting a JSON response.
I have learned that the client is not setting the header value of Content-type: application/xml or Accept: application/xml.
The client who is calling my web service is one of the largest companies in the world and refuses to add the required header values.
So my question is, how do I add the missing header values that the client is not sending so that my web service can return a response in XML?
Or how do I get my web service to response in XML with missing?
I have tried adding the following to the Global.asax.cs;
protected void Application_BeginRequest(object sender, EventArgs e)
{
if (HttpContext.Current.Request.HttpMethod == "POST"
&& HttpContext.Current.Request.CurrentExecutionFilePath.Contains("OrderInformation"))
{
HttpContext.Current.Request.Headers.Add("content-type", "application/xml");
HttpContext.Current.Request.Headers.Add("Accept", "application/xml");
}
}
But an exception is throw at runtime.
TIA
Matt
Thanks GPW. your link help, I've created a DelegatingHandler to correct the request header
public class MediaTypeDelegatingHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var url = request.RequestUri.ToString();
if (url.Contains("OrderInformation"))
{
request.Headers.Accept.Clear();
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml"));
}
return await base.SendAsync(request, cancellationToken);
}
}
Related
I have the following code to set content-md5 in my GET method request using HttpClient
httpClient.DefaultRequestHeaders.TryAddWithoutValidation("content-md5", "value");
I cannot use HttpRequestMessage content to set it because it's not a POST method. When using Postman it works like a charm but fails when using HttpClient.GetAsync.
Client request a hmac to the server as follows
{
"content_to_hash": "my content"
}
The server will give response like this
{
"content_md5": "88af7ceab9fdafb76xxxxx",
"date": "Sat, 02 May 2020 00:13:16 +0700",
"hmac_value": "WfHgFyT792IENmK8Mqz9LysmP8ftOP00qA="
}
Now I have to access a GET request using that hmac where it's the problem because I cannot set in httpClient GET request header.
Here's the image
From reading the HttpClient and related source code, there's no way you can get around this and add the header to the actual request object headers. There is an internal list of invalid headers, which includes any Content-* headers. It has to be on a content object.
Therefore, my suggest solution is to create your own content object:
public class NoContentMd5 : HttpContent
{
protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
{
return Task.CompletedTask;
}
protected override bool TryComputeLength(out long length)
{
length = 0;
return false;
}
public NoContentMd5(byte[] contentMd5)
{
this.Headers.ContentMD5 = contentMd5;
}
public NoContentMd5(string contentMd5)
{
this.Headers.TryAddWithoutValidation("Content-MD5", contentMd5);
}
}
This will add the Content-MD5 header with a value of your choosing, but the request won't contain a body.
The next problem you'll encounter is that you're trying to make a GET request with content, which isn't supported by the helper client.GetAsync(...) method. You'll have to make your own request object and use client.SendAsync(...) instead:
HttpClient client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://localhost/my/test/uri");
request.Content = new NoContentMd5("d41d8cd98f00b204e9800998ecf8427e ");
var result = await client.SendAsync(request);
Note that if you have your Content-MD5 hash as bytes, I've also added a constructor to NoContentMd5 for byte[] too.
The only potential issue with this is that it includes a Content-Length: 0 header. Hopefully that's OK with the API you're working with.
There's an alternative solution described in this answer to question with a similar issue. I'd argue against using it since is vulnerable to changes in the implementation details of HttpRequestHeaders (because it uses reflection, so if MS change the code, it might break) .
Aside from the fact that it's not considered good practice to send a body with GET request (see HTTP GET with request body), you can try this:
using (var content = new StringContent(string.Empty))
using (var request = new HttpRequestMessage
{
Method = HttpMethod.Get,
RequestUri = new Uri("http://localhost"),
Content = content
})
{
request.Headers.TryAddWithoutValidation("content-md5", "value");;
using (var response = await httpClient.SendAsync(request))
{
response.EnsureSuccessStatusCode();
}
}
UPDATE:
The proper way would be to set the ContentMD5 property on the HttpContentHeaders, for example:
content.Headers.ContentMD5 = Convert.FromBase64String(hashString);
But as you pointed out in the comments, trying to send content in a GET request causes an error.
I've been working on both the backend and the frontend of a mobile application and am having trouble getting my post requests to work properly. I've been using Xamarin.Forms for the frontend and .Net for the backend.
The code on the client side:
var res = await App.Client.PostAsync("url", args);
var s =await res.Content.ReadAsStringAsync();
await DisplayAlert("Content", s, "OK");
I have checked and I am receiving an HttpResponseMessage, but when I try to read it the Content Stream is always null. I suspect it is something I am doing wrong on the server side though.
Server side code:
[MobileAppController,Authorize,RoutePrefix("api/SomeController")]
public class someController : ApController{
[HttpPost,Route("theRoute/{id}"),AllowAnonymous]
public HttpResponseMessage someFunction(args)
{
return new HttpResponseMessage(System.Net.HttpStatusCode.OK)
{
Content = new StringContent("Hello");
}
}
}
If I instead display the response with res.ToString(), I get a response message with
StatusCode:200
ReasonPhrase:'OK'
Version:1.1
Content: System.Net.HttpStreamContent
Headers:{
Server: Microsoft-IIS/10.0
X-Powered-By:ASP.NET,
Date: Tue,20,Feb 2018 17:08:42 GMT,
Content-Length:4
Content-Type: application/json; charset=utf-8
}
I've been trying to figure out why Content is null but I can't figure out why.
Edit 1: App.Client is the HttpClient used by the entire mobile application.
In my app.xaml.cs file:
static HttpClient client;
public static HttpClient Client
{
get
{
if (client == null)
{
client = new HttpClient();
}
return client;
}
}
Try to set the client DefaultRequestHeaders on the application response type. For example:
client.DefaultRequestHeaders.Add("Accept", "application/json");
I'm using ASP.NET Core for building REST service
I need to return custom response code if user tries to request an endpoint with unsupported method.
For example endpoint localhost/api/test supports GET method only, but user requests it with POST method. I need to return 404 response and custom body.
How to do this with ASP.NET Core?
UPD:
Possibly I formulated my question incorrectly.
I need ASP Core return 405 response code with custom JSON body in case if a method is not allowed.
This should be a standard behavior, but not implemented yet (according to this issue)
So I'm looking to workaround to return 405 response code nevertheless ASP Core does not support it out of box.
On a controller method level, probably this will guide you. You create a HttpResonseMessage, with your preferred status code and message. Note: if you want status like 302 then you also need to fill location header.
if (Request.Method.Method.Equals("POST", StringComparison.OrdinalIgnoreCase))
{
IHttpActionResult response;
HttpResponseMessage responseMsg = new HttpResponseMessage(HttpStatusCode.NotFound);
responseMsg.Content = new StringContent("Method doesn't support POST or whatever", System.Text.Encoding.UTF8, "text/html");
response = ResponseMessage(responseMsg);
return response;
}
Assuming you add a custom header in your controller method, to differencitate it from framework response.
In webapi.config register a CustomMessageHandler.
config.MessageHandlers.Add(new CustomMessageHandler());
//Define CustomMessageHandler like below and overide SendAsync
public class CustomMessageHandler: DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
var reasonInvalid = String.Empty;
var res= base.SendAsync(request, cancellationToken);
if (res.Result.StatusCode == HttpStatusCode.NotFound || res.Result.StatusCode == HttpStatusCode.MethodNotAllowed)
{
if(!res.Result.Headers.Contains("CustomHeaderforIntentional404"))
{
res.Result.StatusCode = HttpStatusCode.MethodNotAllowed;
res.Result.Content = new StringContent("Method doesn't support this method CUSTOM MESSAGE", System.Text.Encoding.UTF8, "text/html");
return res;
}
}
return res;
}
}
as per the official docs ...
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/error-handling
app.UseStatusCodePages();
// app.UseStatusCodePages(context => context.HttpContext.Response.SendAsync("Handler, status code: " + context.HttpContext.Response.StatusCode, "text/plain"));
// app.UseStatusCodePages("text/plain", "Response, status code: {0}");
// app.UseStatusCodePagesWithRedirects("~/errors/{0}"); // PathBase relative
// app.UseStatusCodePagesWithRedirects("/base/errors/{0}"); // Absolute
// app.UseStatusCodePages(builder => builder.UseWelcomePage());
// app.UseStatusCodePagesWithReExecute("/errors/{0}");
... something like that should work.
Can you tell me how to create a SOAP HTTP parameter in C#. I am not talking about the SOAP HEADER within the SOAP envelope, but the HTTP header. For example I have the Username and Password in the Header below:
POST /company/addThing HTTP/1.1
Host: webservices.foo
Connection: Keep-Alive
User-Agent: PHP-SOAP/5.6.2
Content-Type: text/xml; charset=utf-8
SOAPAction: ""
Content-Length: 295
Username: userx
Password: passwordy
I can add the parameters in PHP using the stream_context parameter of the SoapClient call, but I cannot find where to do it in .Net C#.
I have added the web Reference WSDL which has auto created the objects and methods for the web service and I'm comfortable with that part.
Any help would be appreciated.
Edit
I've ticked the answer below, but it only works for .Net 4.0 and above. One thing I failed to mention was that I am using Visual Studio 2008.
The final solution I used was to add the following code to the auto generated Reference.cs file:
protected override System.Net.WebRequest GetWebRequest(Uri uri)
{
System.Net.WebRequest request = base.GetWebRequest(uri);
request.Headers.Add("Username", "user");
request.Headers.Add("Password", "pass");
return request;
}
When you add a web reference from the WSDL it will generate a class for you that you use to make the calls to your service, let's call it "MyService". If you create a partial for that class, and include it in the same assembly, you can override the "GetWebRequest" method and directly add headers. Here's an example:
public partial class MyService
{
private ConcurrentDictionary<string, string> requestHeaders = new ConcurrentDictionary<string, string>();
public void SetRequestHeader(string headerName, string headerValue)
{
this.requestHeaders.AddOrUpdate(headerName, headerValue, (key, oldValue) => headerValue);
}
protected override WebRequest GetWebRequest(Uri uri)
{
var request = base.GetWebRequest(uri);
var httpRequest = request as HttpWebRequest;
if (httpRequest != null)
{
foreach (string headerName in this.requestHeaders.Keys)
{
httpRequest.Headers[headerName] = this.requestHeaders[headerName];
}
}
return request;
}
}
I'm building an API consumer using HttpClient. Because the provider require the consumer to authenticate using Digest Authentication, so I need to write a custom DelegatingHandler like below:
public class DigestAuthDelegatingHandler : DelegatingHandler
{
public DigestAuthDelegatingHandler(HttpMessageHandler innerHandler) : base(innerHandler) { }
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var response = await base.SendAsync(request, cancellationToken);
if (!response.IsSuccessStatusCode && response.StatusCode == HttpStatusCode.Unauthorized)//This line of code is never reached
{
//Generate the Digest Authorization header string and add to the request header,
//then try to resend the request to the API provider
}
return response;
}
}
I create a HttpClient and add my custom DelegatingHandler to the message handlers pineline
HttpClient httpClient = new HttpClient(new DigestAuthDelegatingHandler(new HttpClientHandler()));
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
httpClient.BaseAddress = new Uri("http://127.0.0.1/");
HttpResponseMessage response = httpClient.GetAsync("api/getTransactionInfo?TransactionNumber=1000).Result;
After doing that, it look like that my consumer runs forever. When I add a break point AFTER the code line await base.SendAsync above, I see that the code will never return, so I have no way to check if the response is get an 401 unauthorized to extract the Digest authorization header details. Nothing wrong at the API provider because I've successfully built another API consumer site using the traditional WebHttpRequest support Digest Authenticate and it works well.
IMPORTANT NOTE: if I switch to write consumer as a Console Application then it works well. So, I'm not sure but I think it's problem related to ASP.NET thread when running in asynchronous mode?
Is there anything wrong I'm doing?
I agree with Darrel - most probably, task is faulted and there is no result... you can use explicit continuation to inspect task state - for example,
return base.SendAsync(request, cancellationToken).ContinueWith(task =>
{
// put the code to check the task state here...
});
On different note, I am not sure if you need to create a custom DelegatingHandler for authenticating ... try using HttpClientHandler with Credentials properties (or UseDefaultCredentials to pass default credentials of current user)
var httpClient = new HttpClient(new HttpClientHandler() {
PreAuthenticate = true,
Credentials = new NetworkCredentials(...
});
EDIT: Found the example with digest authentication being used with http client using credential cache - see this SO Q & A: HttpRequestMessage and Digest Authentication
This should solve your actual problem w.r.t. digest authentication without building your own handler.
My guess is the .Result is blocking the continuation in your handler. Try changing the .Result to a .ContinueWith
Having run into this same issue, this is what ended up working for me. The problem was that the endpoint threw a 500 Internal Server Error and thus blocked the thread.
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return await base.SendAsync(request, cancellationToken)
.ContinueWith<HttpResponseMessage>(task =>
{
return task.Result;
});
}
Note that the return inside the anon function returns to the SendAsync function, and then we actually return the Result.