Web API Async Upload with XmlHttpRequest to get progress - asynchronous

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

Related

.Net Core - Send image through api = Resource interpreted as Document but transferred with MIME type image/jpeg

In the wwwroot folder of my .Net Core MVC application, I have some images. I need to serve these pictures to TopDesk, where I can put in a url which gets embedded. I have no influence on Topdesk. I can only change the way the image is served.
When I use a direct link to the image, it works. The image gets embedded
Example of direct url:
https://my.web.site/images/image001.jpeg
But there is a limited embedded size (600px) so i need to resize the images. For that purpose I wrote a very simple api controller:
[HttpGet]
[Route("api/Images/GetImage/{id}")]
public IActionResult GetImage(string id)
{
try
{
var pad = $"c:\\Images\\{id}";
if(System.IO.File.Exists(path))
{
var fileBytes = System.IO.File.ReadAllBytes(path);
var smallImage = ..... doing resizing;
new FileExtensionContentTypeProvider().TryGetContentType(Path.GetFileName(path), out var contentType);
return File(smallImage , contentType ?? "application/octet-stream", $"{id}");
}
return NotFound();
}
catch(Exception ex)
{
return BadRequest(ex.Message);
}
}
but the url
https://my.web.site/api/images/GetImage/image001.jpeg
results in
Resource interpreted as Document but transferred with MIME type
image/jpeg
The image doesn't show.
When i test the url in Postman, it returns the image without warning.
What am i missing here?
Instead of returning a File, try using FileContentResult instead:
[HttpGet]
[Route("api/Images/GetImage/{id}")]
public IActionResult GetImage(string id)
{
try
{
var path = $"c:\\Images\\{id}";
if(System.IO.File.Exists(path))
{
var fileBytes = System.IO.File.ReadAllBytes(path);
var smallImage = ..... doing resizing;
new FileExtensionContentTypeProvider().TryGetContentType(Path.GetFileName(path), out var contentType);
return new FileContentResult(fileBytes, contentType ?? "application/octet-stream");
}
return NotFound();
}
catch(Exception ex)
{
return BadRequest(ex.Message);
}
}
When navigating to /GetImage/{id} with a browser, you will see that with File the browser tends to download the file, but with FileContentResult it displays the image in the browser tab directly, which is the same behavior as using static files. This is probably happening because of the Response Headers being added when using File/FileContentResult (probably a Content-Disposition header). Not sure how TopDesk is using these images though.
Off-topic: It's also a good practice to not instantiate a FileExtensionContentTypeProvider with every request. Instead, you can register it as a singleton in your Startup.cs like:
services.AddSingleton(new FileExtensionContentTypeProvider());
and inject it in your controller's constructor.

.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: No MediaTypeFormatter is available to read an object of type 'IEnumerable`1' from content with media type 'text/plain'

I get this error in my client (an ASP.NET MVC application) from a call to my ASP.NET Web API. I checked and the Web API is returning the data alright.
No MediaTypeFormatter is available to read an object of type
'IEnumerable`1' from content with media type 'text/plain'.
I believe that I can inherit from DataContractSerializer and implement my own serializer which can attach the Content-Type HTTP header as text/xml.
But my question is: is that necessary?
Because if it was, it would mean that the default DataContractSerializer does not set this essential header. I was wondering if Microsoft could leave such an important thing out. Is there another way out?
Here's the relevant client side code:
public ActionResult Index()
{
HttpClient client = new HttpClient();
var response = client.GetAsync("http://localhost:55333/api/bookreview/index").Result;
if (response.IsSuccessStatusCode)
{
IEnumerable<BookReview> reviews = response.Content.ReadAsAsync<IEnumerable<BookReview>>().Result;
return View(reviews);
}
else
{
ModelState.AddModelError("", string.Format("Reason: {0}", response.ReasonPhrase));
return View();
}
}
And here's the server side (Web API) code:
public class BookReviewController : ApiController
{
[HttpGet]
public IEnumerable<BookReview> Index()
{
try
{
using (var context = new BookReviewEntities())
{
context.ContextOptions.ProxyCreationEnabled = false;
return context.BookReviews.Include("Book.Author");
}
}
catch (Exception ex)
{
var responseMessage = new HttpResponseMessage
{
Content = new StringContent("Couldn't retrieve the list of book reviews."),
ReasonPhrase = ex.Message.Replace('\n', ' ')
};
throw new HttpResponseException(responseMessage);
}
}
}
I believe (because I don't have time to test it now) that you need to explicitly set the Status Code on the responseMessage you are passing to HttpResponseException. Normally, HttpResponseException will set the status code for you, but because you are providing a responsemessage explicitly, it will use the status code from that. By default, `HttpResponseMessage has a status code of 200.
So what is happening is you are getting an error on the server, but still returning a 200. Which is why your client is trying to deserialize the text/plain body produced by StringContent, as if it were an IEnumerable.
You need to set
responseMessage.StatusCode = HttpStatusCode.InternalServerError
in your exception handler on the server.
How about just using ReadAsStringAsync if your WebAPI is expecting to return content in plain text?
response.Content.ReadAsStringAsync().Result;

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

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.

SignalR recording when a Web Page has closed

I am using MassTransit request and response with SignalR. The web site makes a request to a windows service that creates a file. When the file has been created the windows service will send a response message back to the web site. The web site will open the file and make it available for the users to see. I want to handle the scenario where the user closes the web page before the file is created. In that case I want the created file to be emailed to them.
Regardless of whether the user has closed the web page or not, the message handler for the response message will be run. What I want to be able to do is have some way of knowing within the response message handler that the web page has been closed. This is what I have done already. It doesnt work but it does illustrate my thinking. On the web page I have
$(window).unload(function () {
if (event.clientY < 0) {
// $.connection.hub.stop();
$.connection.exportcreate.setIsDisconnected();
}
});
exportcreate is my Hub name. In setIsDisconnected would I set a property on Caller? Lets say I successfully set a property to indicate that the web page has been closed. How do I find out that value in the response message handler. This is what it does now
protected void BasicResponseHandler(BasicResponse message)
{
string groupName = CorrelationIdGroupName(message.CorrelationId);
GetClients()[groupName].display(message.ExportGuid);
}
private static dynamic GetClients()
{
return AspNetHost.DependencyResolver.Resolve<IConnectionManager>().GetClients<ExportCreateHub>();
}
I am using the message correlation id as a group. Now for me the ExportGuid on the message is very important. That is used to identify the file. So if I am going to email the created file I have to do it within the response handler because I need the ExportGuid value. If I did store a value on Caller in my hub for the web page close, how would I access it in the response handler.
Just in case you need to know. display is defined on the web page as
exportCreate.display = function (guid) {
setTimeout(function () {
top.location.href = 'GetExport.ashx?guid=' + guid;
}, 500);
};
GetExport.ashx opens the file and returns it as a response.
Thank you,
Regards Ben
I think a better bet would be to implement proper connection handling. Specifically, have your hub implementing IDisconnect and IConnected. You would then have a mapping of connectionId to document Guid.
public Task Connect()
{
connectionManager.MapConnectionToUser(Context.ConnectionId, Context.User.Name);
}
public Task Disconnect()
{
var connectionId = Context.ConnectionId;
var docId = connectionManager.LookupDocumentId(connectionId);
if (docId != Guid.Empty)
{
var userName = connectionManager.GetUserFromConnectionId(connectionId);
var user = userRepository.GetUserByUserName(userName);
bus.Publish( new EmailDocumentToUserCommand(docId, user.Email));
}
}
// Call from client
public void GenerateDocument(ClientParameters docParameters)
{
var docId = Guid.NewGuid();
connectionManager.MapDocumentIdToConnection(Context.ConnectionId, docId);
var command = new CreateDocumentCommand(docParameters);
command.Correlationid = docId;
bus.Publish(command);
Caller.creatingDocument(docId);
}
// Acknowledge you got the doc.
// Call this from the display method on the client.
// If this is not called, the disconnect method will handle sending
// by email.
public void Ack(Guid docId)
{
connectionManager.UnmapDocumentFromConnectionId(connectionId, docId);
Caller.sendMessage("ok");
}
Of course this is from the top of my head.

Resources