Is there a notification when ASP.NET Web API completes sending to the client - asp.net

I'm using Web API to stream large files to clients, but I'd like to log if the download was successful or not. That is, if the server sent the entire content of the file.
Is there some way to get a a callback or event when the HttpResponseMessage completes sending data?
Perhaps something like this:
var stream = GetMyStream();
var response = new HttpResponseMessage(HttpStatusCode.OK);
response.Content = new StreamContent(stream);
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
// This doesn't exist, but it illustrates what I'm trying to do.
response.OnComplete(context =>
{
if (context.Success)
Log.Info("File downloaded successfully.");
else
Log.Warn("File download was terminated by client.");
});

EDIT: I've now tested this using a real connection (via fiddler).
I inherited StreamContent and added my own OnComplete action which checks for an exception:
public class StreamContentWithCompletion : StreamContent
{
public StreamContentWithCompletion(Stream stream) : base (stream) { }
public StreamContentWithCompletion(Stream stream, Action<Exception> onComplete) : base(stream)
{
this.OnComplete = onComplete;
}
public Action<Exception> OnComplete { get; set; }
protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
{
var t = base.SerializeToStreamAsync(stream, context);
t.ContinueWith(x =>
{
if (this.OnComplete != null)
{
// The task will be in a faulted state if something went wrong.
// I observed the following exception when I aborted the fiddler session:
// 'System.Web.HttpException (0x800704CD): The remote host closed the connection.'
if (x.IsFaulted)
this.OnComplete(x.Exception.GetBaseException());
else
this.OnComplete(null);
}
}, TaskContinuationOptions.ExecuteSynchronously);
return t;
}
}
Then I use it like so:
var stream = GetMyStream();
var response = new HttpResponseMessage(HttpStatusCode.OK);
response.Content = new StreamContentWithCompletion(stream, ex =>
{
if (ex == null)
Log.Info("File downloaded successfully.");
else
Log.Warn("File download was terminated by client.");
});
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
return response;

I am not sure if there is direct signaling that all is ok, but you can use a trick to find out that the connection is exist just before you end it up, and right after you fully send the file.
For example the Response.IsClientConnected is return true if the client is still connected, so you can check something like:
// send the file, make a flush
Response.Flush();
// and now the file is fully sended check if the client is still connected
if(Response.IsClientConnected)
{
// log that all looks ok until the last byte.
}
else
{
// the client is not connected, so maybe have lost some data
}
// and now close the connection.
Response.End();

if the server sent the entire content of the file
Actually there is nothing to do :)
This might sound very simplistic but you will know if an exception is raised - if you care about server delivering and not client cancelling halfway. IsClientConnected is based on ASP.NET HttpResponse not the WebApi.

Related

SocketError when creating multiple HttpClient Connections with the Load balancer(F5)

This is the exact exception in the error logs.
I have a requirement where I should be creating a set of service calls and wait till all of them are completed successfully before moving on to do a different set of service calls.
I have two services Service1 and Service2.
There are 2 instances of Service2 in 2 different servers and we have set up an F5(Load Balancer) to distribute the load evenly.
Let's say I have 10 service calls to be made from Service1 to Service2 at a time and F5 will share those 10 calls among the 2 servers. i.e. 5 Calls to Service2 of each server.
But I observed that if any one of those 10calls is taking lot of time to complete the work it should do(The work to be done in Service2 has some heavy lifting) then i get a socket exception thrown and entire process gets stopped.
However when I dont use the F5 load balancer and just use 1 instance of the Service2. Then however long the process takes for any of those 10 calls it doesn't throw any exception.
I am not sure if this is an issue with F5 configuration or with the way connections are made with the F5 from .Net code.
Please go through the below code to get some idea of what i am trying to do and let me know if any code change would help me resolve it.
for (int i=0 ; i< ReqList.Count;i++)
{
maxTasks++;
ClassA reqList = new ClassA();
reqList = ReqList[i];
List<ClassA> recsByReq = recs.Where(x => x.ReqId == reqList.ReqtId).ToList();
ClassC service2Input = new ClassC();
service2Input.DetaiList = listofRecs;
service2Input.RecList = recsByReq;
taskList.Add(_service2.Service2MethodCall(service2Input, service2Resource));
if(maxTasks == 10 || ReqList.Count == 10 || i==ReqList.Count-1)
{
Task.WaitAll(taskList.ToArray(),-1);
maxTasks = 0;
taskList.Clear();
}
}
The Service2MethodCall is where I am creating a HttpClient to make connections with the 2nd service i.e. Service2,
public class Service2: IService2 {
private ServiceClient GetService2(string resource)
{
return new ServiceClient(_service2BaseUrl, TimeSpan.FromMinutes(60))
{
Resource = resource,
};
}
public async Task Service2MethodCall(ClassC service2Input, string resource)
{
try
{
var client = GetService2(resource);
await client.PostAsync(JsonConvert.SerializeObject(service2Input).ToString());
}
catch (Exception ex)
{
throw new Exception("The Service encountered an error during the Service2 call for Req ID : " +
service2Input.RecsList.Find(x => x.ReqId != "").ReqId.ToString(), ex);
}
}
}
The PostAsync() method creates a new HttpClient with HttpClientHandler object for each of the call.
public async Task PostAsync(object data) {
using(var httpClientHandler = new HttpClientHandler()) {
httpClientHandler.ServerCertificateCustomValidationCallback = (message, cert, chain, sslPolicyErrors) => {
if (sslPolicyErrors == SslPolicyErrors.None) {
return true; //Is valid
}
if (cert.GetCertHashString().ToUpper() == _acceptedThumbprint.ToUpper()) {
return true;
}
return false;
};
using(var client = new HttpClient(httpClientHandler)) {
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(ResponseFormatter.MediaType);
client.Timeout = Timeout.InfiniteTimeSpan;
Uri uri = BuildUri();
if (!String.IsNullOrWhiteSpace(SignatureKey)) {
client.DefaultRequestHeaders.TryAddWithoutValidation("Authorization",
RequestFormatter.GenerateAuthHeaderEncodedUserSignature(uri, AuthUserName, SignatureKey, data));
}
//_logger.WriteUsage(client.BaseAddress.ToString());
HttpResponseMessage response = await RequestFormatter.PostAsync(client, uri, data);
string content = await response.Content.ReadAsStringAsync();
try {
response.EnsureSuccessStatusCode();
}#
pragma warning disable CS0168 // The variable 'rex' is declared but never used
catch (HttpRequestException rex)# pragma warning restore CS0168 // The variable 'rex' is declared but never used
{
//_logger.WriteUsage("Response for POST to: {0} did not yield a successful status code. Message: {1}" + uri.ToString() + content);
throw new ApiHttpException(response.StatusCode, content);
}
}
}
}
Is there some thing I can do within the code to avoid this situation once and for all?

Stream.WriteAsync throws The remote host closed the connection exception

I have an asp.net webforms application and to retrieve video from database that saved in varbinary format and show it as html5 video tag.
after a googled it, i found a way that i should play it asynchronously using ASP.Net WebApi, it works fine
First problem
When video played first time and the user click on play button to replay the video, The remote host closed the connection. The error code is 0x800704CD exception throws at line await outputStream.WriteAsync(buffer, 0, bytesRead);.
Second Problem
When user click on seek bar, the video goes to played from first.
NOTE
Internet Explorer 11 plays the video without any problem, but firefox and chrome have both problems.
how can i solve this problem?
Here is my codes:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.EnableCors();
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "VideoApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
public class VideoController : ApiController
{
public IVideoRepository videoRepository;
public HttpResponseMessage Get(long id)
{
try
{
videoRepository = new VideoRepository();
Video video = videoRepository.load(id);
if (video != null)
{
var videoStream = new VideoStream(video.fileContent);
string ext = video.extension;
var response = Request.CreateResponse();
response.Content = new PushStreamContent((Action<Stream, HttpContent, TransportContext>)videoStream.WriteToStream, new MediaTypeHeaderValue("video/" + ext));
response.Content.Headers.Add("Content-Disposition", "attachment;filename=" + video.fullName.Replace(" ", ""));
response.Content.Headers.Add("Content-Length", videoStream.FileLength.ToString());
return response;
}
else
{
return Request.CreateResponse(HttpStatusCode.NotFound);
}
}
catch (Exception e)
{
return Request.CreateErrorResponse(HttpStatusCode.ServiceUnavailable, e);
}
}
}
public class VideoStream
{
private readonly byte[] _fileContent;
private long _contentLength;
public long FileLength
{
get { return _contentLength; }
}
public VideoStream(byte[] content)
{
_contentLength = content.Length;
_fileContent = content;
}
public async void WriteToStream(Stream outputStream, HttpContent content, TransportContext context)
{
try
{
var buffer = new byte[65536];
MemoryStream memoryStream = new MemoryStream();
memoryStream.Write(_fileContent, 0, _fileContent.Length);
memoryStream.Position = 0;
using (memoryStream)
{
var length = (int)memoryStream.Length;
var bytesRead = 1;
while (length > 0 && bytesRead > 0)
{
bytesRead = memoryStream.Read(buffer, 0, Math.Min(length, buffer.Length));
await outputStream.WriteAsync(buffer, 0, bytesRead);
length -= bytesRead;
}
}
}
catch (Exception e)
{
throw e;
}
finally
{
outputStream.Close();
}
}
}
UPDATE
after this way didn't worked properly, i had to use this way, but the new way have seekbar problem, when user click on seek bar to seek to time it dosn't work in Chrome and FireFox.
ASP.NET is not very good at video streaming. Third-party video streaming solution is the best option.
There are a few video-streaming servers (like Wowza), but they require installation and you have to buy license.
Cloud streaming service is another option. I personally prefer AWS Cloudfront. They propose distribution in various globally distributed content delivery zones. It costs really cheap and you can be sure that it will survive any traffic amount (even if all your users will watch the same video simultaneously).
You might have got the answer by now. But this might help others-
My best bet is removing the Content-length from the response headers.
Content-Length tells the caller that it needs to receive this fixed length in the response.
When you click on a play button, the complete video stream is not received (i.e., the entire Content-Length is not received.) & therefore, the error.
Another approach could be using response.Headers.TransferEncodingChunked = true, which tells the caller that it will receive a response in chunks. The only catch here is you will get a 200OK even if the stream is not present.

.Net Server-Sent Events using HttpHandler not working

I have been trying to implement an event driven push to a client browser. I am using ReactiveX to produce the async task from the events but I can't even get my HttpHandlers to output their response.
I have tried with a simple HttpHandler:
public class Handler2 : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "text/event-stream";
HttpResponse response = context.Response;
DateTime startdate = DateTime.Now;
while (startdate.AddMinutes(10) > DateTime.Now)
{
JavaScriptSerializer js = new JavaScriptSerializer();
string responseText = DateTime.Now.TimeOfDay.ToString();
response.Write(string.Format("data: {0}",js.Serialize(responseText)));
response.Flush();
System.Threading.Thread.Sleep(1000);
}
response.Close();
}
public bool IsReusable
{
get
{
return false;
}
}
}
with the following client side code:
function initialize() {
if (window.EventSource == undefined) {
document.getElementById('targetDiv').innerHTML = "Your browser doesn't support Server Side Events.";
return;
}
var source = new EventSource('Handler2.ashx');
source.onopen = function (event) {
document.getElementById('targetDiv').innerHTML += 'Connection Opened.<br>';
};
source.onerror = function (event) {
if (event.eventPhase == EventSource.CLOSED) {
document.getElementById('targetDiv').innerHTML += 'Connection Closed.<br>';
}
};
source.onmessage = function (event) {
document.getElementById('targetDiv').innerHTML += event.data + '<br>';
};
}
I have more a more complex HttpTaskAsyncHandler ready to hook up, but I can't even get this working >_<
I get the Connection Opened message, Handler2.ashx appears to remain connected (Looking at Chrome dev tools / Network).
I am, on the other hand, getting some data from a SignalR connection?
"ws://localhost:50022/ed4b66c7eb394a8789b5f6a631f4ff09/arterySignalR/connect?.."
Have I set it up wrong?
As far as I've seen on other examples, this code should be working as-is. Please could anyone help me. I just want a simple SSE control that I can trigger from server side events.
Thanks in advance
I had given this answer before, but let me elaborate:
Looking at the Network tab in Google Chrome developer tools reveals quite a lot from your http://live.meetscoresonline.com/test-sse.aspx
There are no SSE being generated at all - to see this click on the Others button under Network, this is where you would normally be able to track the SSE data stream
I use the following code in my SSE's with a simple HTTPListener and it works well without the delays you mentioned, and always shows up correctly across browsers when using this polyfill
res.AddHeader("Content-Type", "text/event-stream")
res.AddHeader("Cache-Control", "no-cache")
res.AddHeader("Access-Control-Allow-Origin", "*")
res.KeepAlive = True

Web API Async Upload with XmlHttpRequest to get progress

I'm trying to drag and drop file upload with a progress bar.
I have a div which is listening to files being dropped on which is working perfectly.
I'm then..
//Setting up a XmlHttpRequest
xhr = new XMLHttpRequest();
//Open connection
xhr.open("post", "api/ImageUpload", true);
// Set appropriate headers
xhr.setRequestHeader("Content-Type", "multipart/form-data");
xhr.setRequestHeader("X-File-Type", uf.type);
xhr.setRequestHeader("X-File-Name", uf.name);
xhr.setRequestHeader("X-File-Size", uf.size);
This sends fine, with the stream as the body of the request to the Web API (not async).
[System.Web.Mvc.HttpPost]
public string Post()
{
Stream stream = HttpContext.Current.Request.InputStream;
String filename = HttpContext.Current.Request.Headers["X-File-Name"];
FileModel file = uploadService.UploadFile(stream, filename);
return file.Id.ToString();
}
I'm trying to chance the request to "public async Task< string> Post(){ }
If the method was using a multipart form on the page instead of XmlHttpRequest I would have used "await Request.Content.ReadAsMultipartAsync(provider)" but this doesn't seem to be populated at the time I need it.
So what is the correct was to handle and an Async call from XmlHttpRequest on a Web API in order to record progress during the request with XHR's progress event?
I have looked at a great deal of pages so far to find a solution but this is the page I have used primarily.
http://robertnyman.com/html5/fileapi-upload/fileapi-upload.html
Thanks for any help
Oliver
It looks like someone else had the same question with you and got an answer yet. please have a look at ASP.NET MVC 4 Web Api ajax file upload.
And here is an example from microsoft http://www.asp.net/web-api/overview/working-with-http/sending-html-form-data,-part-2.
I combined the two above solution together and worked for me (just adjust a little bit)
one line change in Javascritp
xhr.open("post", "api/upload", true);
Save the file using stream
public class UploadController : ApiController
{
public async Task<HttpResponseMessage> PostFormData()
{
string root = HttpContext.Current.Server.MapPath("~/App_Data");
var fileName = Path.Combine(root, Request.Headers.GetValues("X-File-Name").First());
try
{
var writer = new StreamWriter(fileName);
await Request.Content.CopyToAsync(writer.BaseStream);
writer.Close();
return Request.CreateResponse(HttpStatusCode.OK);
}
catch (System.Exception e)
{
return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, e);
}
}
}

Async calls in WP7

I have been experimenting with WP7 apps today and have hit a bit of a wall.
I like to have seperation between the UI and the main app code but Ive hit a wall.
I have succesfully implemented a webclient request and gotten a result, but because the call is async I dont know how to pass this backup to the UI level. I cannot seem to hack in a wait for response to complete or anything.
I must be doing something wrong.
(this is the xbox360Voice library that I have for download on my website: http://www.jamesstuddart.co.uk/Projects/ASP.Net/Xbox_Feeds/ which I am porting to WP7 as a test)
here is the backend code snippet:
internal const string BaseUrlFormat = "http://www.360voice.com/api/gamertag-profile.asp?tag={0}";
internal static string ResponseXml { get; set; }
internal static WebClient Client = new WebClient();
public static XboxGamer? GetGamer(string gamerTag)
{
var url = string.Format(BaseUrlFormat, gamerTag);
var response = GetResponse(url, null, null);
return SerializeResponse(response);
}
internal static XboxGamer? SerializeResponse(string response)
{
if (string.IsNullOrEmpty(response))
{
return null;
}
var tempGamer = new XboxGamer();
var gamer = (XboxGamer)SerializationMethods.Deserialize(tempGamer, response);
return gamer;
}
internal static string GetResponse(string url, string userName, string password)
{
if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(password))
{
Client.Credentials = new NetworkCredential(userName, password);
}
try
{
Client.DownloadStringCompleted += ClientDownloadStringCompleted;
Client.DownloadStringAsync(new Uri(url));
return ResponseXml;
}
catch (Exception ex)
{
return null;
}
}
internal static void ClientDownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
if (e.Error == null)
{
ResponseXml = e.Result;
}
}
and this is the front end code:
public void GetGamerDetails()
{
var xboxManager = XboxFactory.GetXboxManager("DarkV1p3r");
var xboxGamer = xboxManager.GetGamer();
if (xboxGamer.HasValue)
{
var profile = xboxGamer.Value.Profile[0];
imgAvatar.Source = new BitmapImage(new Uri(profile.ProfilePictureMiniUrl));
txtUserName.Text = profile.GamerTag;
txtGamerScore.Text = int.Parse(profile.GamerScore).ToString("G 0,000");
txtZone.Text = profile.PlayerZone;
}
else
{
txtUserName.Text = "Failed to load data";
}
}
Now I understand I need to place something in ClientDownloadStringCompleted but I am unsure what.
The problem you have is that as soon as an asynchronous operation is introduced in to the code path the entire code path needs to become asynchronous.
Because GetResponse calls DownloadStringAsync it must become asynchronous, it can't return a string, it can only do that on a callback
Because GetGamer calls GetResponse which is now asynchronous it can't return a XboxGamer, it can only do that on a callback
Because GetGamerDetails calls GetGamer which is now asynchronous it can't continue with its code following the call, it can only do that after it has received a call back from GetGamer.
Because GetGamerDetails is now asynchronous anything call it must also acknowledge this behaviour.
.... this continues all the way up to the top of the chain where a user event will have occured.
Here is some air code that knocks some asynchronicity in to the code.
public static void GetGamer(string gamerTag, Action<XboxGamer?> completed)
{
var url = string.Format(BaseUrlFormat, gamerTag);
var response = GetResponse(url, null, null, (response) =>
{
completed(SerializeResponse(response));
});
}
internal static string GetResponse(string url, string userName, string password, Action<string> completed)
{
WebClient client = new WebClient();
if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(password))
{
client.Credentials = new NetworkCredential(userName, password);
}
try
{
client.DownloadStringCompleted += (s, args) =>
{
// Messy error handling needed here, out of scope
completed(args.Result);
};
client.DownloadStringAsync(new Uri(url));
}
catch
{
completed(null);
}
}
public void GetGamerDetails()
{
var xboxManager = XboxFactory.GetXboxManager("DarkV1p3r");
xboxManager.GetGamer( (xboxGamer) =>
{
// Need to move to the main UI thread.
Dispatcher.BeginInvoke(new Action<XboxGamer?>(DisplayGamerDetails), xboxGamer);
});
}
void DisplayGamerDetails(XboxGamer? xboxGamer)
{
if (xboxGamer.HasValue)
{
var profile = xboxGamer.Value.Profile[0];
imgAvatar.Source = new BitmapImage(new Uri(profile.ProfilePictureMiniUrl));
txtUserName.Text = profile.GamerTag;
txtGamerScore.Text = int.Parse(profile.GamerScore).ToString("G 0,000");
txtZone.Text = profile.PlayerZone;
}
else
{
txtUserName.Text = "Failed to load data";
}
}
As you can see async programming can get realy messy.
You generally have 2 options. Either you expose your backend code as an async API as well, or you need to wait for the call to complete in GetResponse.
Doing it the async way would mean starting the process one place, then return, and have the UI update when data is available. This is generally the preferred way, since calling a blocking method on the UI thread will make your app seem unresponsive as long as the method is running.
I think the "Silverlight Way" would be to use databinding. Your XboxGamer object should implement the INotifyPropertyChanged interface. When you call GetGamer() it returns immediately with an "empty" XboxGamer object (maybe with GamerTag=="Loading..." or something). In your ClientDownloadStringCompleted handler you should deserialize the returned XML and then fire the INotifyPropertyChanged.PropertyChanged event.
If you look at the "Windows Phone Databound Application" project template in the SDK, the ItemViewModel class is implemented this way.
Here is how you can expose asynchronous features to any type on WP7.

Resources