Creating HTTP Header parameters in a SOAP interface in .Net - c#

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;
}
}

Related

Strict ordering of HTTP headers in HttpWebrequest

In spite of the RFC stating that the order of uniquely-named headers shouldn't matter, the website I'm sending this request to does implement a check on the order of headers.
This works:
GET https://www.thewebsite.com HTTP/1.1
Host: www.thewebsite.com
Connection: keep-alive
Accept: */*
User-Agent: Mozilla/5.0 etc
This doesn't work:
GET https://www.thewebsite.com HTTP/1.1
Accept: */*
User-Agent: Mozilla/5.0 etc
Host: www.thewebsite.com
Connection: keep-alive
The default HttpWebRequest seems to put the Host and Connection headers at the end, before the blank line, rather than just after the url.
Is there any way (using a fork of HttpWebRequest or some other library in Nuget even) to specify the order of headers in a HttpWebRequest?
If possible, I'd rather not start going down the route of implementing a proxy to sort them or having to code the whole thing up using a TcpClient.
I'd appreciate any hints at all on this.
Update: With Fiddler running, header order in HttpWebrequest can be re-shuffled in CustomRules.cs. Still no closer to a solution without a proxy though.
Some server implement header ordering as a precaution for any attacks or spam, an article explaining Why ordering HTTP headers is important.
But the standard is, the order in which header fields with differing field names are received is not significant.
HttpWebRequest, there is no easy way to order the headers and the Connection and Host is added internally.
If ordering is really important, use the HttpClient instead, it can easily arrange the Headers based on the example of #Jason.
If you will be using HttpClient, you can create a custom HttpClientHandler and you can arrange your header from there. It can be something like this.
HANDLER
public class CustomHttpClientHandler : HttpClientHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.Clear();
request.Headers.Add("Host", $"{request.RequestUri.Authority}");
request.Headers.Add("Connection", "keep-alive");
request.Headers.Add("Accept", "*/*");
request.Headers.Add("User-Agent", "Mozilla/5.0 etc");
return await base.SendAsync(request, cancellationToken);
}
}
IMPLEMENTATION
HttpClient clientRequest = new HttpClient(new CustomHttpClientHandler());
await clientRequest.GetAsync(url);
.Net Core
If you set the headers yourself, you can specify the order. When the common headers are added it will find the existing headers instead of appending them:
using System.Net;
namespace ConsoleApp2
{
class Program
{
static void Main(string[] args)
{
var request = WebRequest.Create("http://www.google.com");
request.Headers.Add("Host", "www.google.com");
// this will be set within GetResponse.
request.Headers.Add("Connection", "");
request.Headers.Add("Accept", "*/*");
request.Headers.Add("User-Agent", "Mozilla/5.0 etc");
request.GetResponse();
}
}
}
Here is an example with HttpClient:
using System.Net.Http;
using System.Threading.Tasks;
namespace ConsoleApp3
{
class Program
{
static async Task Main(string[] args)
{
var client = new HttpClient();
client.DefaultRequestHeaders.Add("Host", "www.google.com");
client.DefaultRequestHeaders.Add("Connection", "keep-alive");
client.DefaultRequestHeaders.Add("Accept", "*/*");
client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 etc");
await client.GetAsync("http://www.google.com");
await client.PostAsync("http://www.google.com", new StringContent(""));
}
}
}
Edit
The above code did not work on .Net Framework only .Net Core
.Net Framework
On .Net Framework the headers are reserved so they cannot be set like this, see Cannot set some HTTP headers when using System.Net.WebRequest.
One work around is to use reflection to modify the behavior of the framework class, but be warned this could break if the libraries are updated so it's not recommended!.
Essentially, HttpWebRequest calls ToString on WebHeaderCollection to serialize.
See https://referencesource.microsoft.com/#System/net/System/Net/HttpWebRequest.cs,5079
So a custom class can be made to override ToString. Unfortunately reflection is needed to set the headers as WebRequest copies the collection on assignment to Headers, instead of taking the new reference.
WARNING, THE FOLLOWING CODE CAN BREAK IF FRAMEWORK CHANGES
If you use this, write some unit tests that verify the behavior still stays consistent after updates to .NET Framework
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Reflection;
namespace ConsoleApp2
{
class Program
{
static void Main(string[] args)
{
// WARNING, CODE CAN BREAK IF FRAMEWORK CHANGES
// If you use this, write some unit tests that verify the behavior still stays consistent after updates to .NET Framework
var request = (HttpWebRequest)WebRequest.Create("http://www.google.com");
var field = typeof(HttpWebRequest).GetField("_HttpRequestHeaders", BindingFlags.Instance | BindingFlags.NonPublic);
var headers = new CustomWebHeaderCollection(new Dictionary<string, string>
{
["Host"] = "www.google.com",
["Connection"] = "keep-alive",
["Accept"] = "*/*",
["User-Agent"] = "Mozilla/5.0 etc"
});
field.SetValue(request, headers);
request.GetResponse();
}
}
internal class CustomWebHeaderCollection : WebHeaderCollection
{
private readonly Dictionary<string, string> _customHeaders;
public CustomWebHeaderCollection(Dictionary<string, string> customHeaders)
{
_customHeaders = customHeaders;
}
public override string ToString()
{
// Could call base.ToString() split on Newline and sort as needed
var lines = _customHeaders
.Select(kvp => $"{kvp.Key}: {kvp.Value}")
// These two new lines are needed after the HTTP header
.Concat(new [] { string.Empty, string.Empty });
var headers = string.Join("\r\n", lines);
return headers;
}
}
}

Add missing Header values

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);
}
}

.NET Core forward a local API form-data post request to remote API

I have an AJAX form which post a form-data to a local API url: /api/document. It contains a file and a custom Id. We simply want to take the exact received Request and forward it to a remote API at example.com:8000/document/upload.
Is there a simple way of achieve this "forward" (or proxy?) of the Request to a remote API using Asp.NET Core?
Below we had the idea to simply use Web API Http client to get the request and then resend it (by doing so we want to be able to for example append a private api key from the backend), but it seems not to work properly, the PostAsync doesn't accept the Request.
Raw request sent by Ajax
POST http://localhost:62640/api/document HTTP/1.1
Host: localhost:62640
Connection: keep-alive
Content-Length: 77424
Accept: application/json
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryn1BS5IFplQcUklyt
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.8,fr;q=0.6
------WebKitFormBoundaryn1BS5IFplQcUklyt
Content-Disposition: form-data; name="fileToUpload"; filename="test-document.pdf"
Content-Type: application/pdf
...
------WebKitFormBoundaryn1BS5IFplQcUklyt
Content-Disposition: form-data; name="id"
someid
------WebKitFormBoundaryn1BS5IFplQcUklyt--
Backend Code
Our .NET Core backend has a simple "forward to another API" purpose.
public class DocumentUploadResult
{
public int errorCode;
public string docId;
}
[Route("api/[controller]")]
public class DocumentController : Controller
{
// POST api/document
[HttpPost]
public async Task<DocumentUploadResult> Post()
{
client.BaseAddress = new Uri("http://example.com:8000");
client.DefaultRequestHeaders.Accept.Clear();
HttpResponseMessage response = await client.PostAsync("/document/upload", Request.Form);
if (response.IsSuccessStatusCode)
{
retValue = await response.Content.ReadAsAsync<DocumentUploadResult>();
}
return retValue;
}
}
We have a GET request (not reproduced here) which works just fine. As it doesn't have to fetch data from locally POSTed data.
My question
How to simply pass the incoming local HttpPost request and forwarding it to the remote API?
I searched A LOT on stackoverflow or on the web but all are old resources talking about forwarding Request.Content to the remote.
But on Asp.NET Core 1.0, we don't have access to Content. We only are able to retrieve Request.Form (nor Request.Body) which is then not accepted as an argument of PostAsync method:
Cannot convert from Microsoft.AspNetCore.Http.IformCollection to
System.Net.Http.HttpContent
I had the idea to directly pass the request to the postAsync:
Cannot convert from Microsoft.AspNetCore.Http.HttpRequest to
System.Net.Http.HttpContent
I don't know how to rebuild expected HttpContent from the local request I receive.
Expected response
For information, When we post a valid form-data with the custom Id and the uploaded file, the remote (example.com) API response is:
{
"errorCode": 0
"docId": "585846a1afe8ad12e46a4e60"
}
Ok first create a view model to hold form information. Since file upload is involved, include IFormFile in the model.
public class FormData {
public int id { get; set; }
public IFormFile fileToUpload { get; set; }
}
The model binder should pick up the types and populate the model with the incoming data.
Update controller action to accept the model and proxy the data forward by copying content to new request.
[Route("api/[controller]")]
public class DocumentController : Controller {
// POST api/document
[HttpPost]
public async Task<IActionResult> Post(FormData formData) {
if(formData != null && ModelState.IsValid) {
client.BaseAddress = new Uri("http://example.com:8000");
client.DefaultRequestHeaders.Accept.Clear();
var multiContent = new MultipartFormDataContent();
var file = formData.fileToUpload;
if(file != null) {
var fileStreamContent = new StreamContent(file.OpenReadStream());
multiContent.Add(fileStreamContent, "fileToUpload", file.FileName);
}
multiContent.Add(new StringContent(formData.id.ToString()), "id");
var response = await client.PostAsync("/document/upload", multiContent);
if (response.IsSuccessStatusCode) {
var retValue = await response.Content.ReadAsAsync<DocumentUploadResult>();
return Ok(reyValue);
}
}
//if we get this far something Failed.
return BadRequest();
}
}
You can include the necessary exception handlers as needed but this is a minimal example of how to pass the form data forward.

Using Cookie aware WebClient

I'm using this enhanced version of WebClient to login in a site:
public class CookieAwareWebClient : WebClient
{
public CookieAwareWebClient()
{
CookieContainer = new CookieContainer();
}
public CookieContainer CookieContainer { get; private set; }
protected override WebRequest GetWebRequest(Uri address)
{
var request = (HttpWebRequest)base.GetWebRequest(address);
request.CookieContainer = CookieContainer;
return request;
}
}
And this way I send cookie to the site:
using (var client = new CookieAwareWebClient())
{
var values = new NameValueCollection
{
{ "username", "john" },
{ "password", "secret" },
};
client.UploadValues("http://example.com//dl27929", values);
// If the previous call succeeded we now have a valid authentication cookie
// so we could download the protected page
string result = client.DownloadString("http://domain.loc/testpage.aspx");
}
But when I run my program and capture the traffic in Fiddler I get 302 status code. I tested the request in Fiddler this way and everything is OK and I get the status code of 200.
The request in the Fiddler:
GET http://example.com//dl27929 HTTP/1.1
Cookie: username=john; password=secret;
Host: domain.loc
And here is the request sending by the application:
POST http://example.com//dl27929 HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Host: www.domain.loc
Content-Length: 75
Expect: 100-continue
Connection: Keep-Alive
As you can see it doesn't send the cookie.
Any idea?
Everything works fine, just I forgot to set the cookie, Thanks Scott:
client.CookieContainer.SetCookies(new Uri("http://example.com//dl27929"), "username=john; password=secret;");

How to add credential information as a transport header in webservice API call using C#

I tried doing the following but it not add the credential to the http header(SOAP Request).
MyWebService mySrv = new MyWebService();
System.Net.CredentialCache sysCredentail = new System.Net.CredentialCache();
NetworkCredential netCred = new NetworkCredential("admin", "password");
sysCredentail.Add(new Uri(strSysURL), "Basic", netCred);
mySrv.Credentials = sysCredentail;
I am expecting the following after adding credential information, when I call any webservice API.
SOAP::Transport::HTTP::Client::send_receive: POST http:/myurl/SYS HTTP /1.1
Accept: text/xml
Accept: multipart/*
Accept: application/soap
Content-Length: 431
Content-Type: text/xml; charset=utf-8
Authorization:: Basic YWRtaW46YnJvY2FkZQ==
SOAPAction: "urn:webservicesapi#getSystemName"
<?xml version="1.0" encoding="UTF-8"?>
etc...
but Authorization:: Basic YWRtaW46YnJvY2FkZQ== is missing even after adding the credential.
Kindly advise.
Following the below steps had solved the issue.
1) Create a class derived from the webserivce class(auto generated class created after adding wsdl)
2) Override two functions GetWebRequest and SetRequestHeader.
3) Instead of creating the object of the webservice, create the object for the class which you created by Step 1.
4) Set the header information(credential information) using SetRequestHeader.
5) Access webservice API after step 4, it will work with authentication.
public class MyDerivedService : MyService
{
private String m_HeaderName;
private String m_HeaderValue;
//----------------
// GetWebRequest
//
// Called by the SOAP client class library
//----------------
protected override WebRequest GetWebRequest(Uri uri)
{
// call the base class to get the underlying WebRequest object
HttpWebRequest req = (HttpWebRequest)base.GetWebRequest(uri);
if (null != this.m_HeaderName)
{
// set the header
req.Headers.Add(this.m_HeaderName, this.m_HeaderValue);
}
// return the WebRequest to the caller
return (WebRequest)req;
}
//----------------
// SetRequestHeader
//
// Sets the header name and value that GetWebRequest will add to the
// underlying request used by the SOAP client when making the
// we method call
//----------------
public void SetRequestHeader(String headerName, String headerValue)
{
this.m_HeaderName = headerName;
this.m_HeaderValue = headerValue;
}
}
eg:
MyDerivedService objservice = new MyDerivedService ();
string usernamePassword = username + ":" + password;
objservice.SetRequestHeader("Authorization", "Basic " + Convert.ToBase64String(new ASCIIEncoding().GetBytes(usernamePassword)));
objservice.CallAnyAPI();

Categories

Resources