I have been looking at MSDN and codeproject, but I am still a bit confused.
Synchronous Service vs an Asynchronous service.
I have a WCF service end point. This service has a 2way SSL applied to its web.config file.
The client end point is a Oracle based java Weblogic Suite. This has its own private key and public key. The client needs to communicate with our service both asynchronously and synchronously.
I CAN ONLY CHANGE THINGS ON THE SERVER SIDE
http://www.codeproject.com/Articles/91528/How-to-Call-WCF-Services-Synchronously-and-Asynchr
http://msdn.microsoft.com/en-us/library/ms731177.aspx
The following code is the synchronous part of the SVC, cs file:-
public getQuoteSyncResponse1 getQuoteSync(getQuoteSyncRequest request)
{
// Create new response
getQuoteSyncResponse1 res = new getQuoteSyncResponse1();
res.GetQuoteSyncResponse = new GetQuoteSyncResponse();
res.GetQuoteSyncResponse.Header = new GetQuoteResponseHeaderType();
res.GetQuoteSyncResponse.Response = new GetQuoteSyncResponseType();
// Create and populate header
res.GetQuoteSyncResponse.Header.MessageId = request.GetQuoteRequestSync.Header.MessageId;
res.GetQuoteSyncResponse.Header.Timestamp = request.GetQuoteRequestSync.Header.Timestamp;
res.GetQuoteSyncResponse.Header.QuoteId = request.GetQuoteRequestSync.Header.QuoteId;
res.GetQuoteSyncResponse.Header.CarrierId = request.GetQuoteRequestSync.Header.CarrierId;
List<RejectionType> rj = new List<RejectionType>();
string _sTotalEmployees = request.GetQuoteRequestSync.Request.Employer.TotalEmployees;
int _TotalEmployees = 0;
if (int.TryParse(_sTotalEmployees, out _TotalEmployees) == false)
{
RejectionType rt;
rt = new RejectionType();
rt.ReasonCode = "R01";
rt.ReasonDescription = "Invalid Number of Employees";
rj.Add(rt);
res.GetQuoteSyncResponse.Response.Rejections = rj.ToArray();
res.GetQuoteSyncResponse.Response.ReceiptStatus = AcceptanceContentType.Reject;
return res;
}
res.GetQuoteSyncResponse.Response.ReceiptStatus = AcceptanceContentType.Success;
List<QuoteType> q = new List<QuoteType>();
QuoteType qt;
qt = new QuoteType();
qt.PlanId = "P345678";
qt.EEPremium = 1220;
qt.EESPPremium = 2222;
qt.EEDepPremium = 3333;
qt.EEFamilyPremium = 4444;
qt.TotalMonthlyPremium = 3456;
qt.CoverageEffectiveDate = DateTime.Now;
q.Add(qt);
res.GetQuoteSyncResponse.Response.Quotes = q.ToArray();
return res;}
so this Synchronous part of the service is working. Now, how do I use this to transform it into the asynchronous equivalent?
Should I be starting the async method in the cs file? or in the svc file? I am confused...
public getQuoteAsyncResponse getQuoteAsync(getQuoteAsyncRequest request, AsyncCallback callback, Object state)
{
// Create new response
getQuoteAsyncResponse res = new getQuoteAsyncResponse();
return new getQuoteAsyncResponse();
}
I sort of understand about the callback deli-gator, object state and such, but can someone illustrate this further for me? How do I format the asynchronous part of the service? The web has so many examples... but all very confusing. I must have some inherent misunderstanding on this concept.
Edit:- I was told in the answer that, the server side needs no manipulation for async style of communication. However I found this:-
Implementing Asynchronous Operations in WCF
Just as the WCF plumbing enables clients to call server operations asynchronously, without the server needing to know anything about it, WCF also allows service operations to be defined asynchronously. So an operation like:
[OperationContract]
string DoWork(int value);
…might instead be expressed in the service contract as:
[OperationContract(AsyncPattern = true)]
IAsyncResult BeginDoWork(int value, AsyncCallback callback, object state);
string EndDoWork(IAsyncResult result);
Note that the two forms are equivalent, and indistinguishable in the WCF metadata: they both expose an operation called DoWork[1]:
The async part needs to be done in the client. This means you are probably doing something similar to:
var response = ServiceReference.GetSomething();
Instead, make a proxy to get the callback. Create and event (or delegate) that gets fired (or called) whenever the callback receives the response. In the above statement, you are obviously waiting for the response to be assigned into the variable before moving to the next line.
Instead, you could
On the Service contract, be sure to decorate with [OperationContract(IsOneWay = true)]
If you use ServiceReference or serviceutil, it will automatically create "incoming events" and do all the client side async work for you.
If you are using TCP, create an callback contract as well, then on client you can do something like
ServiceReference1.IncomingSomething += new eventHandler.
Now you can do ServiceReferecnce1.GetSomething(), and the response will go to the eventhandler function.
If this is RESTFUL:
public void MakeAsyncRequest(string url, string contentType)
{
try
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.ContentType = contentType;
request.Method = WebRequestMethods.Http.Get;
request.Timeout = 10000;
request.Proxy = null;
request.BeginGetResponse(new AsyncCallback(ReadCallback), request);
}
catch (Exception ex)
{
LogManager.LogException(ex);
}
}
private void ReadCallback(IAsyncResult asyncResult)
{
HttpWebRequest request = (HttpWebRequest)asyncResult.AsyncState;
string strContent = string.Empty;
string s;
try
{
using (HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(asyncResult))
{
Stream responseStream = response.GetResponseStream();
using (StreamReader sr = new StreamReader(responseStream))
{
//Need to return this response
strContent = sr.ReadToEnd();
}
}
}
catch (Exception ex)
{
throw;
}
}
Related
I am trying to implement the certificate authentication in .net core API(Server/target) and this API will be invoked in to another API(Client) .Here is the piece of code of client api which makes request to server/target api.But I'm facing an error on the server/target api .I'm running these two services from local and both certificates have already installed
Client side controller logic
[HttpGet]
public async Task<List<WeatherForecast>> Get()
{
List<WeatherForecast> weatherForecastList = new List<WeatherForecast>();
X509Certificate2 clientCert = Authentication.GetClientCertificate();
if (clientCert == null)
{
HttpActionContext actionContext = null;
actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden)
{
ReasonPhrase = "Client Certificate Required"
};
}
HttpClientHandler requestHandler = new HttpClientHandler();
requestHandler.ClientCertificates.Add(clientCert);
requestHandler.ServerCertificateCustomValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
HttpClient client = new HttpClient(requestHandler)
{
BaseAddress = new Uri("https://localhost:11111/ServerAPI")
};
client.DefaultRequestHeaders
.Accept
.Add(new MediaTypeWithQualityHeaderValue("application/xml"));//ACCEPT head
using (var httpClient = new HttpClient())
{
//httpClient.DefaultRequestHeaders.Accept.Clear();
var request = new HttpRequestMessage()
{
RequestUri = new Uri("https://localhost:44386/ServerAPI"),
Method = HttpMethod.Get,
};
request.Headers.Add("X-ARR-ClientCert", clientCert.GetRawCertDataString());
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));//ACCEPT head
//using (var response = await httpClient.GetAsync("https://localhost:11111/ServerAPI"))
using (var response = await httpClient.SendAsync(request))
{
if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
string apiResposne = await response.Content.ReadAsStringAsync();
weatherForecastList = JsonConvert.DeserializeObject<List<WeatherForecast>>(apiResposne);
}
}
}
return weatherForecastList;
}
authentication class
public static X509Certificate2 GetClientCertificate()
{
X509Store userCaStore = new X509Store(StoreName.TrustedPeople, StoreLocation.CurrentUser);
try
{
string str_API_Cert_Thumbprint = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
userCaStore.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certificatesInStore = userCaStore.Certificates;
X509Certificate2Collection findResult = certificatesInStore.Find(X509FindType.FindByThumbprint, str_API_Cert_Thumbprint, false);
X509Certificate2 clientCertificate = null;
if (findResult.Count == 1)
{
clientCertificate = findResult[0];
if(System.DateTime.Today >= System.Convert.ToDateTime(clientCertificate.GetExpirationDateString()))
{
throw new Exception("Certificate has already been expired.");
}
else if (System.Convert.ToDateTime(clientCertificate.GetExpirationDateString()).AddDays(-30) <= System.DateTime.Today)
{
throw new Exception("Certificate is about to expire in 30 days.");
}
}
else
{
throw new Exception("Unable to locate the correct client certificate.");
}
return clientCertificate;
}
catch (Exception ex)
{
throw;
}
finally
{
userCaStore.Close();
}
}
Server/target api code
[HttpGet]
public IEnumerable<WeatherForecast> Getcertdata()
{
IHeaderDictionary headers = base.Request.Headers;
X509Certificate2 clientCertificate = null;
string certHeaderString = headers["X-ARR-ClientCert"];
if (!string.IsNullOrEmpty(certHeaderString))
{
//byte[] bytes = Encoding.ASCII.GetBytes(certHeaderString);
//byte[] bytes = Convert.FromBase64String(certHeaderString);
//clientCertificate = new X509Certificate2(bytes);
clientCertificate = new X509Certificate2(WebUtility.UrlDecode(certHeaderString));
var serverCertificate = new X509Certificate2(Path.Combine("abc.pfx"), "pwd");
if (clientCertificate.Thumbprint == serverCertificate.Thumbprint)
{
//Valida Cert
}
}
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
}).ToArray();
//return new List<WeatherForecast>();
}
You have much more problems here, the code is significantly flawed and insecure in various ways. Let's explain each issue:
HttpClient in using clause in client side controller logic
Although you expect to wrap anything that implements IDisposable in using statement. However, it is not really the case with HttpClient. Connections are not closed immediately. And with every request to client controller action, a new connection is established to remote endpoint, while previous connections sit in TIME_WAIT state. Under certain constant load, your HttpClient will exhaust TCP port pool (which is limited) and any new attempt to create a new connection will throw an exception. Here are more details on this problem: You're using HttpClient wrong and it is destabilizing your software
Microsoft recommendation is to re-use existing connections. One way to do this is to Use IHttpClientFactory to implement resilient HTTP requests. Microsoft article talks a bit about this problem:
Though this class implements IDisposable, declaring and instantiating
it within a using statement is not preferred because when the
HttpClient object gets disposed of, the underlying socket is not
immediately released, which can lead to a socket exhaustion problem.
BTW, you have created a client variable, but do not use it in any way.
Ignore certificate validation problems
The line:
requestHandler.ServerCertificateCustomValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
make you vulnerable to MITM attack.
you are doing client certificate authentication wrong
The line:
request.Headers.Add("X-ARR-ClientCert", clientCert.GetRawCertDataString());
It is not the proper way how to do client cert authentication. What you literally doing is passing certificate's public part to server. That's all. You do not prove private key possession which is required to authenticate you. The proper way to do so is:
requestHandler.ClientCertificates.Add(clientCert);
This will force client and server to perform proper client authentication and check if you possess the private key for certificate you pass (it is done in TLS handshake automatically). If you have ASP.NET on server side, then you read it this way (in controller action):
X509Certificate2 clientCert = Request.HttpContext.Connection.ClientCertificate
if (clientCert == null) {
return Unauthorized();
}
// perform client cert validation according server-side rules.
Non-standard cert store
In authentication class you open StoreName.TrustedPeople store, while normally it should be StoreName.My. TrustedPeople isn't designed to store certs with private key. It isn't a functional problem, but it is bad practice.
unnecessary try/catch clause in authentication class
If you purposely throw exceptions in method, do not use try/catch. In your case you simply rethrow exception, thus you are doing a double work. And this:
throw new Exception("Certificate is about to expire in 30 days.");
is behind me. Throwing exception on technically valid certificate? Really?
server side code
As said, all this:
IHeaderDictionary headers = base.Request.Headers;
X509Certificate2 clientCertificate = null;
string certHeaderString = headers["X-ARR-ClientCert"];
if (!string.IsNullOrEmpty(certHeaderString))
{
//byte[] bytes = Encoding.ASCII.GetBytes(certHeaderString);
//byte[] bytes = Convert.FromBase64String(certHeaderString);
//clientCertificate = new X509Certificate2(bytes);
clientCertificate = new X509Certificate2(WebUtility.UrlDecode(certHeaderString));
var serverCertificate = new X509Certificate2(Path.Combine("abc.pfx"), "pwd");
if (clientCertificate.Thumbprint == serverCertificate.Thumbprint)
{
//Valida Cert
}
}
must be replaced with:
X509Certificate2 clientCert = Request.HttpContext.Connection.ClientCertificate
if (clientCert == null) {
return Unauthorized();
}
// perform client cert validation according server-side rules.
BTW:
var serverCertificate = new X509Certificate2(Path.Combine("abc.pfx"), "pwd");
if (clientCertificate.Thumbprint == serverCertificate.Thumbprint)
{
//Valida Cert
}
This is another disaster in your code. You are loading the server certificate from PFX just to compare their thumbprints? So, you suppose that client will have a copy of server certificate? Client and server certificates must not be the same. Next thing is you are generating a lot of copies of server certificate's private key files. More private key files you generate, the slower the process is and you just generate a lot of garbage. More details on this you can find in my blog post: Handling X509KeyStorageFlags in applications
I am using .NET 4.5.2 for a web application and I have a HTTP handler that returns a processed image. I am making async calls to the process handler using jQuery and i have started getting the following error:
An asynchronous operation cannot be started at this time. Asynchronous operations may only be started within an asynchronous handler or module or during certain events in the Page lifecycle. If this exception occurred while executing a Page, ensure that the Page is marked <%# Page Async="true" %>. This exception may also indicate an attempt to call an "async void" method, which is generally unsupported within ASP.NET request processing. Instead, the asynchronous method should return a Task, and the caller should await it.
This is the handler code:
public void ProcessRequest(HttpContext context)
{
string CaseID = context.Request.QueryString["CaseID"].ToString();
int RotationAngle = Convert.ToInt16(context.Request.QueryString["RotationAngle"].ToString());
string ImagePath = context.Request.QueryString["ImagePath"].ToString();
applyAngle = RotationAngle;
string ImageServer = ConfigurationManager.AppSettings["ImageServerURL"].ToString();
string FullImagePath = string.Format("{0}{1}", ImageServer, ImagePath);
WebClient wc = new WebClient();
wc.DownloadDataCompleted += wc_DownloadDataCompleted;
wc.DownloadDataAsync(new Uri(FullImagePath));
}
private void wc_DownloadDataCompleted(object sender, DownloadDataCompletedEventArgs e)
{
Stream BitmapStream = new MemoryStream(e.Result);
Bitmap b = new Bitmap(BitmapStream);
ImageFormat ImageFormat = b.RawFormat;
b = RotateImage(b, applyAngle, true);
using (MemoryStream ms = new MemoryStream())
{
if (ImageFormat.Equals(ImageFormat.Png))
{
HttpContext.Current.Response.ContentType = "image/png";
b.Save(ms, ImageFormat.Png);
}
if (ImageFormat.Equals(ImageFormat.Jpeg))
{
HttpContext.Current.Response.ContentType = "image/jpg";
b.Save(ms, ImageFormat.Jpeg);
}
ms.WriteTo(HttpContext.Current.Response.OutputStream);
}
}
Any idea what this means and I could do to overcome this?
Thanks in advance.
You code won't work as it is, because you create a WebClient inside the ProcessRequest method but don't wait for it to finish. As a result, the client will be orphaned as soon as the method finishes. By the time a response arrives, the request itself has finished. There is no context or output stream to which you can write the response.
To create an asynchronous HTTP Handler you need to derive from the HttpTaskAsyncHandler class and implement the ProcessRequestAsync method:
public class MyImageAsyncHandler : HttpTaskAsyncHandler
{
public override async Task ProcessRequestAsync(HttpContext context)
{
//...
using(WebClient wc = new WebClient())
{
var data=await wc.DownloadDataTaskAsync(new Uri(FullImagePath));
using(var BitmapStream = new MemoryStream(data))
{
//...
ms.WriteTo(context.Response.OutputStream);
//...
}
}
}
}
I am a newbie into async programming and am trying to use the httpclient to fire bulk URL requests for the page content.
Here is my attempt:
private async void ProcessUrlAsyncWithHttp(HttpClient httpClient, string purl)
{
Stopwatch sw = new Stopwatch();
sw.Start();
HttpResponseMessage response = null;
try
{
Interlocked.Increment(ref _activeRequestsCount);
var request = new HttpRequestMessage()
{
RequestUri = new Uri(purl),
Method = HttpMethod.Get,
};
request.Headers.TryAddWithoutValidation("User-Agent", "MozillaMozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36");
request.Headers.TryAddWithoutValidation("Accept", "text/html,*.*");
request.Headers.TryAddWithoutValidation("Connection", "Keep-Alive");
request.Headers.TryAddWithoutValidation("Accept-Encoding", "gzip, deflate, sdch");
request.Headers.TryAddWithoutValidation("Accept-Language", "en-US,en;q=0.8");
response = await httpClient.SendAsync(request).ConfigureAwait(false);
string html = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
response.Dispose();
if (IsCaptcha(html)) throw new Exception("Captcha was returned");
request.Dispose();
Interlocked.Increment(ref _successfulCalls);
}
catch (HttpRequestException hex)
{
Console.WriteLine("http:" + hex.Message);
Interlocked.Increment(ref _failedCalls);
}
catch (Exception ex)
{
Console.WriteLine(ex.GetType().AssemblyQualifiedName + " " + ex.Message);
Interlocked.Increment(ref _failedCalls);
}
finally
{
Interlocked.Decrement(ref _activeRequestsCount);
Interlocked.Decrement(ref _itemsLeft);
if (response != null) response.Dispose();
if (httpClient != null) httpClient.Dispose();
sw.Stop();
DateTime currentTime = DateTime.UtcNow;
TimeSpan elapsedTillNow = (currentTime - _overallStartTime).Duration();
Console.WriteLine("Left:" + _itemsLeft + ", Current execution:" + sw.ElapsedMilliseconds + " (ms), Average execution:" + Math.Round((elapsedTillNow.TotalMilliseconds / (_totalItems - _itemsLeft)), 0) + " (ms)");
lock(_syncLock)
{
if (_itemsLeft == 0)
{
_overallEndTime = DateTime.UtcNow;
this.DisplayTestResults();
}
}
}
}
As you can see I am passing an httpclient to the function and it gets destroyed everytime the URL is downloaded. I know this is an overkill and ideally we should be reusing the httpclient. But since I cant use a single httpclient with different proxies for each URL (the handler needs to be passed to the constructor of httpclient and cannot be changed, hence a fresh proxy cant be given without recreating the httpclient object), I needed to use this approach.
At the caller side, I have a pretty basic code:
public async void TestAsyncWithHttp()
{
ServicePointManager.DefaultConnectionLimit = 10;
//ServicePointManager.UseNagleAlgorithm = false;
List<string> urlList = SetUpURLList();
urlList = urlList.GetRange(1, 50);
_itemsLeft = urlList.Count();
_totalItems = _itemsLeft;
List<string> proxies = new List<string>();
proxies.Add("124.161.94.8:80");
proxies.Add("183.207.228.8:80");
proxies.Add("202.29.97.5:3128");
proxies.Add("210.75.14.158:80");
proxies.Add("203.100.80.81:8080");
proxies.Add("218.207.172.236:80");
proxies.Add("218.59.144.120:81");
proxies.Add("218.59.144.95:80");
proxies.Add("218.28.35.234:8080");
proxies.Add("222.88.236.236:83");
Random rnd = new Random();
foreach (string url in urlList)
{
int ind = rnd.Next(0, proxies.Count-1);
var httpClientHandler = new HttpClientHandler
{
Proxy = new WebProxy(proxies.ElementAt(ind), false),
UseProxy = true
};
HttpClient httpClient = new HttpClient(httpClientHandler);
//HttpClient httpClient = new HttpClient();
httpClient.Timeout = TimeSpan.FromMinutes(2);
ProcessUrlAsyncWithHttp(httpClient, url);
}
}
Question is:
1) Why the TCP ports gets closed for each request. I wanted to open the max connections number of ports and reuse them across calls. e.g in the example above I can have 10 concurrent connections. Hence I wanted this to open 10 TCP ports and the rest of the 40 requests could then use these 10 ports in tandem. This is a normal behaviour expected in httpwebrequest. I have a working code for using httpwebrequest that depicts this behavior of reusing ports. Can post the code of that on demand for anyone who might want to have a look. So its kind of weird that httpclient does not mimic this behaviour although it is based on httpwebrequest.
2) How do we assign autoredirect to false for such calls?
3) I intend to use this function for multiple calls - say around 50K. Anything wrong in the way the code is written that might need a correction
4) Lets assume that I somehow manage to use a single httpclient object instead of one object per request. What is the way to ensure I read cookies for all these individual requests and also alter them if necessary all the while remembering that I have a single httpclient class for the whole set of URL requests?
Tks
Kallol
In my experience (I once had a similar problem with TCP port congestion, because of ports always getting closed, when I was hitting a server with around 6000 connections a minute) it suffices to reuse the HttpClientHandler objects, which actually manage the connection pooling, and always recreate the HttpClient objects for each request (using the constructor with HttpClientManager parameter).
Hope this helps.
Matthias
have you tried putting the HttpClient code in class and create 10 classes, each with a HttpClient?
I have a legacy logging DLL that logs errors into a database. Instead of consuming the DLL within each application in our environment, we would like to make web calls to log errors.
I have built up a web.api app that will log errors into a database. When tested with POSTMAN it works as advertised.
I have added a class within a demo MVC app and wired up one of my constructors to execute a log command, but the call not only does not make it to my web.api, but fiddler does not show a call even being made.
Any input on making this actually run would be greatly appreciated.
Here's my code:
Logging Utility Called within Web.API
public class Utilities
{
public void LogException(string exceptionMessage, string stackTrace, string appCode, string runMode, int entityId, int itmsUserID, string updateBy, string path, string method)
{
ErrorLog.Entry _error = new ErrorLog.Entry();
_error.ErrorMessage = exceptionMessage;
_error.StackTrace = stackTrace;
_error.AppCode = appCode;
_error.Path = path;
_error.Method = method;
_error.UpdateBy = updateBy;
_error.RunMode = runMode;
_error.EntityID = entityId;
//_error.Server = server; server will have to be changed to accept a setter
_error.ITMSUserID = CommonFunctions.Get_ITMSUserID(updateBy);
_error.Save();
}
}
Web.API
// POST: api/ErrorLog
public void Post([FromBody]ErrorLogEntryDTO item)
{
var utils = new Utilities();
utils.LogException(item.ErrorMessage, item.StackTrace, item.AppCode, item.RunMode, item.EntityID, item.ITMSUserID, item.UpdateBy, item.Path, item.Method);
}
MVC Controller Code
// GET: BillingRules/Create
public virtual ActionResult CauseHandledError()
{
try
{
throw new Exception("Handled exception test");
}
catch (Exception ex)
{
var utils = new Utilities();
utils.LogException(ex, "system", MVC.BillingRules.Name, MVC.BillingRules.ActionNames.CauseHandledError);
}
return RedirectToAction(MVC.BillingRules.ActionNames.Index, MVC.BillingRules.Name);
}
Utilities Code within MVC App
public void LogException(Exception exception, string updateBy, string path, string method)
{
try
{
var itmsUserID = CommonFunctions.Get_ITMSUserID(updateBy);
var errorDTO = new ErrorLogEntryDTO();
errorDTO.ITMSUserID = itmsUserID;
errorDTO.AppCode = _appCode.Value;
errorDTO.ErrorMessage = exception.Message;
errorDTO.StackTrace = exception.StackTrace;
errorDTO.Path = path;
errorDTO.Method = method;
errorDTO.UpdateBy = updateBy;
var client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:52316");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
var result = client.PostAsJsonAsync("api/ErrorLog", errorDTO).Result; //ContinueWith(readTask => client.Dispose()); //
}
catch (Exception ex)
{
var myError = ex;
throw;
}
}
I'm pretty sure calling .Result in this instance does not immediately invoke the PostAsJsonAsync method. Because you're not doing anything with the Result, it never actually executes. Since it doesn't appear you care about the response, you should be able to use:
client.PostAsJsonAsync("api/ErrorLog", errorDTO).Wait();
I think .Result invokes the PostAsJsonAsync call. You are waiting for the respsonse, so the call must be finished after this line. Regardless if you use the Result or not.
You can remove the [FromBody] attribute, because the complex type is per default read from the body.
And I can't reproduce your issue. I've created a new Web API project and a new console project. In the Web API I've changed the post of the valuescontroller to yours.
In the console project I'm yousing your LogException() method from the MVC app.
It hits my web api app.
Are both in the same host or in different hosts?
Edit:
To make your logging async you can use fire-and-forget with Task.Run() but it depends on the application you have. In ASP.Net Task.Run() is an anti-pattern according to Task.Run Etiquette Examples: Don't Use Task.Run in the Implementation.
I have the following method that writes a stream in a HttpResponse object.
public HttpResponse ShowPDF(Stream stream)
{
MemoryStream memoryStream = (MemoryStream) stream;
httpResponse.Clear();
httpResponse.Buffer = true;
httpResponse.ContentType = "application/pdf";
httpResponse.BinaryWrite(memoryStream.ToArray());
httpResponse.End();
return httpResponse;
}
In order to test it, I need to recover the processed stream.
Is there someway to read the stream from the httpResponse object?
I have two ideas... one to mock the HttpResponse, and the other is to simulate a web server.
1. Mocking HttpResponse
I wrote this before I knew which mocking framework you used. Here's how you could test your method using TypeMock.
This assumes that you pass your httpResponse variable to the method, changing the method as follows:
public void ShowPDF(Stream stream, HttpResponse httpResponse)
Of course you would change this to passing it to a property on your Page object instead, if it is a member of your Page class.
And here's an example of how you could test using a fake HttpResponse:
internal void TestPDF()
{
FileStream fileStream = new FileStream("C:\\deleteme\\The Mischievous Nerd's Guide to World Domination.pdf", FileMode.Open);
MemoryStream memoryStream = new MemoryStream();
try
{
memoryStream.SetLength(fileStream.Length);
fileStream.Read(memoryStream.GetBuffer(), 0, (int)fileStream.Length);
memoryStream.Flush();
fileStream.Close();
byte[] buffer = null;
var fakeHttpResponse = Isolate.Fake.Instance<HttpResponse>(Members.ReturnRecursiveFakes);
Isolate.WhenCalled(() => fakeHttpResponse.BinaryWrite(null)).DoInstead((context) => { buffer = (byte[])context.Parameters[0]; });
ShowPDF(memoryStream, fakeHttpResponse);
if (buffer == null)
throw new Exception("It didn't write!");
}
finally
{
memoryStream.Close();
}
}
2. Simulate a Web Server
Perhaps you can do this by simulating a web server. It might sound crazy, but it doesn't look like it's that much code. Here are a couple of links about running Web Forms outside of IIS.
Can I run a ASPX and grep the result without making HTTP request?
http://msdn.microsoft.com/en-us/magazine/cc163879.aspx