I'm using ASP.NET Web API targeting the 4.5.2 framework and am trying to push out a CSV file generated by exporting data from a table. In Firefox and Chrome, everything works as expected, but with IE (I'm testing with 11), the filename is being ignored and IE is using the URL instead (with no extension). What am I doing wrong and how can I get around it?
Here's my controller method:
[HttpGet]
public HttpResponseMessage ExportToCSV([FromUri]DistributionSearchCriteria criteria)
{
// This creates the csv in the temp folder and returns the temp file name
var file = _repository.ExportToCSV(criteria);
// This FileHttpResponseMessage is a custom type which just deletes the temp file in dispose
var result = new FileHttpResponseMessage(file, HttpStatusCode.OK)
{
Content = new StreamContent(File.OpenRead(file))
};
result.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
{
FileName = "Distributions.csv"
};
result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
return result;
}
Here's the custom FileHttpResponseMessage
public class FileHttpResponseMessage : HttpResponseMessage
{
private string _filePath;
public FileHttpResponseMessage(string filePath, HttpStatusCode statusCode) : base(statusCode)
{
_filePath = filePath;
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
Content.Dispose();
if (File.Exists(_filePath))
{
try
{
System.Diagnostics.Debug.WriteLine("Deleting {0}", (object)_filePath);
File.Delete(_filePath);
System.Diagnostics.Debug.WriteLine("{0} deleted", (object)_filePath);
}
catch
{
System.Diagnostics.Debug.WriteLine("Error Deleting {0}", (object)_filePath);
}
}
}
}
}
and these are my two JavaScript methods in my AngularJS controller which launch the downloads:
vm.exportToCSV = function () {
var params = $httpParamSerializer(vm.searchCriteria);
$window.open(apiBase + 'Distribution/ExportToCSV?' + params, '_blank');
};
vm.exportAllToCSV = function () {
$window.open(apiBase + 'Distribution/ExportToCSV', '_blank');
};
From what I've read in other questions... setting the attachment; filename= should've been sufficient for IE. IE is prompting for a filename of "ExportToCSV".
I've also tried appending a bogus parameter like ?/distribution.csv and it changed the download filename but instead of distribution.csv it replaced the . with _ so the result was distribution_csv. Oh the pains of IE.
Update 1:
I've created a separate project to address only this issue and come up with specific workarounds. I've tried with and without quotes around the filename but I'm still no difference:
Update 2:
So I thought I would attempt to be "clever" and try to create a custom HTTP Handler for files with an extension:
Web.config
<!-- Route all files through asp -->
<modules runAllManagedModulesForAllRequests="true" />
<!-- Route all files through asp -->
<add name="FileDownloadHandler" path="/api/File/test.csv" verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0"/>
WebApiConfig.cs
config.Routes.MapHttpRoute(
name:"download",
routeTemplate: "api/File/test.csv",
defaults: new { controller = "File", action = "Get" }
);
as expected it used the test.csv but it replaced the . with _ resulting in a test_csv extensionless download.
This is the weirdest bug I've ever ran into. I'm not sure whether to attribute it to my anitvirus or not, but my CPU recently maxed out at 100% as a result of McAFEE going bananas and bringing my system to a crawl. As a result, I rebooted my machine. Now Internet Explorer is working as expected! I never would've guessed... I was beginning to question everything I thought I knew...
Moral of the story:
Related
I am making a dynamic CVS report file in my controller in the format of StringBuilder. I have tried this code to return the file:
public async Task GetCvsAsync(....)
{
....
//making the file
using (MemoryStream stream = new MemoryStream())
{
StreamWriter objstreamwriter = new StreamWriter(stream);
objstreamwriter.Write(res.ToString());
objstreamwriter.Flush();
objstreamwriter.Close();
return File(stream.ToArray(), "application/octet-stream", "test.csv");
}
}
it makes the correct result but in the console response header (nothing happens in the front) like this:
if I right-click on the console and click open in a new tab, it will start to download the file. I have tried this way as well:
var f = File(Encoding.UTF8.GetBytes(res.ToString()), "text/csv", "authors.csv");
return f;
res is in the type of StringBuilder.
I want to download automatically begins after the file is ready, I have no idea what should I do.
note:
this action called by a href tag onClick event on my razor page:
function cvs(meID, oID, tID) {
$.get("/Dashboard/GetCVS?meID=" + meID
+ "&oID=" + oID+ "&tID=" + tID);
}
I couldn't solve my problem with jquery I just change my code to direct calling my action instead of ajax call and my problem solved!
I'm trying to build a simple Nancy self host. I got it working if I send in the proper path, but if I send empty path after port #, I get is error 404. If I send an invalid path I get error 500. What I want is to have a catch-all Get which is used whenever a request is sent with an invalid path.
Here is my program.cs
using System.Diagnostics;
using Nancy;
using Nancy.Hosting.Self;
namespace NancyDataService
{
class Program
{
static void Main(string[] args)
{
var uri = new Uri("http://localhost:8080");
var hostConfig = new HostConfiguration();
hostConfig.UrlReservations.CreateAutomatically = true;
hostConfig.RewriteLocalhost = false;
using (var nancyHost = new NancyHost(uri, new DefaultNancyBootstrapper(), hostConfig))
{
nancyHost.Start();
Console.WriteLine("Nancy now listening on http://localhost:8080. Press enter to stop");
try
{
Process.Start("http://localhost:8080");
}
catch (Exception)
{
}
Console.ReadKey();
}
Console.WriteLine("Stopped. Good bye!");
}
}
}
Here is my main module:
using Nancy;
namespace NancyDataService
{
public class MainModule: NancyModule
{
public MainModule()
{
string json_error = #"{""status"":""fail"",""reason"":""{0}""}";
string json;
Get("test", parms =>
{
return "test";
});
// this is default if desired path not sent
Get("{parm1}", parms =>
{
json = string.Format(json_error, "Invalid method name supplied");
//return (Response)json;
return json;
});
}
}
}
I've changed the Get syntax to match the Nancy 2.0 way. I was expecting that the last Get in the above code would be processed, and give me a default error message. If I enter http://localhost:8080/ in browser, I get error 404 response. If I enter http://localhost:8080/test it works fine. If I enter http://localhost:8080/anythingElse I get Error 500, Internal Server Error.
What I would like is to have a "default" get section so any unexpected path entered (including no path at all) after port #, it would take that branch.
BTW, this is targeting .Net Core 3.0, which Nancy says may not work. The warning in my Nancy.Hosting.Self package has a warning which says it was restored using .Net Framework (4.6.1 - 4.8). Could that be the issue?
Any ideas how to make that work? Thanks...
Solved most issues. Need Get("/", ... for empty path, and Get("/{parm}",... for bad path. Seems string.Format causes error 500 but json_error.Replace("{0}", "new text") works fine. What's up with that?
tl;dr What is the best way to pass binary data (up to 1MBish) from a WPF application to a WebAPI service method?
I'm currently trying to pass binary data from a WPF application to a WebAPI web service, with variable results. Small files (< 100k) generally work fine, but any larger and the odds of success reduce.
A standard OpenFileDialog, and then File.ReadAllBytes pass the byte[] parameter into the client method in WPF. This always succeeds, and I then post the data to WebAPI via a PostAsync call and a ByteArrayContent parameter.
Is this the correct way to do this? I started off with a PostJSONAsync call, and passed the byte[] into that, but thought the ByteArrayContent seemed more appropriate, but neither work reliably.
Client Method in WPF
public static async Task<bool> UploadFirmwareMCU(int productTestId, byte[] mcuFirmware)
{
string url = string.Format("productTest/{0}/mcuFirmware", productTestId);
ByteArrayContent bytesContent = new ByteArrayContent(mcuFirmware);
HttpResponseMessage response = await GetClient().PostAsync(url, bytesContent);
....
}
WebAPI Method
[HttpPost]
[Route("api/productTest/{productTestId}/mcuFirmware")]
public async Task<bool> UploadMcuFirmware(int productTestId)
{
bool result = false;
try
{
Byte[] mcuFirmwareBytes = await Request.Content.ReadAsByteArrayAsync();
....
}
Web Config Settings
AFAIK these limits in web.config should be sufficient to allow 1MB files through to the service?
<security>
<requestFiltering>
<requestLimits maxAllowedContentLength="1073741824" />
</requestFiltering>
</security>
<httpRuntime targetFramework="4.5" maxRequestLength="2097152"/>
I receive errors in WebAPI when calling ReadAsByteArrayAsync(). These vary, possibly due to the app pool in IIS Express having crashed / getting into a bad state, but they include the following (None of which have lead to any promising leads via google):
Specified argument was out of the range of valid values. Parameter name: offset
at System.Web.HttpInputStream.Seek(Int64 offset, SeekOrigin origin)\r\n
at System.Web.HttpInputStream.set_Position(Int64 value)\r\n at System.Web.Http.WebHost.SeekableBufferedRequestStream.SwapToSeekableStream()\r\n at System.Web.Http.WebHost.Seek
OR
Message = "An error occurred while communicating with the remote host. The error code is 0x800703E5."
InnerException = {"Overlapped I/O operation is in progress. (Exception from HRESULT: 0x800703E5)"}
at System.Web.Hosting.IIS7WorkerRequest.RaiseCommunicationError(Int32 result, Boolean throwOnDisconnect)\r\n
at System.Web.Hosting.IIS7WorkerRequest.ReadEntityCoreSync(Byte[] buffer, Int32 offset, Int32 size)\r\n
at System.Web.Hosting.IIS7WorkerRequ...
Initially I thought this was most likely down to IIS Express limitations (running on Windows 7 on my dev pc) but we've had the same issues on a staging server running Server 2012.
Any advice on how I might get this working would be great, or even just a basic example of uploading files to WebAPI from WPF would be great, as most of the code I've found out there relates to uploading files from multipart forms web pages.
Many thanks in advance for any help.
tl;dr It was a separate part of our code in the WebApi service that was causing it to go wrong, duh!
Ah, well, this is embarrassing.
It turns out our problem was down to a Request Logger class we'd registered in WebApiConfig.Register(HttpConfiguration config), and that I'd forgotten about.
It was reading the request content via async as StringContent, and then attempting to log it to the database in an ncarchar(max) field. This itself is probably OK, but I'm guessing all the weird problems started occurring when the LoggingHandler as well as the main WebApi controller, were both trying to access the Request content via async?
Removing the LoggingHandler fixed the problem immediately, and we're now able to upload files of up to 100MB without any problems. To fix it more permanently, I guess I rewrite of the LoggingHandler is required to set a limit on the maximum content size it tries to log / to ignore certain content types.
It's doubtful, but I hope this may be of use for someone one day!
public class LoggingHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
LogRequest(request);
return base.SendAsync(request, cancellationToken).ContinueWith(task =>
{
var response = task.Result;
// ToDo: Decide if/when we need to log responses
// LogResponse(response);
return response;
}, cancellationToken);
}
private void LogRequest(HttpRequestMessage request)
{
(request.Content ?? new StringContent("")).ReadAsStringAsync().ContinueWith(x =>
{
try
{
var callerId = CallerId(request);
var callerName = CallerName(request);
// Log request
LogEntry logEntry = new LogEntry
{
TimeStamp = DateTime.Now,
HttpVerb = request.Method.ToString(),
Uri = request.RequestUri.ToString(),
CorrelationId = request.GetCorrelationId(),
CallerId = callerId,
CallerName = callerName,
Controller = ControllerName(request),
Header = request.Headers.ToString(),
Body = x.Result
};
...........
I need to handle different content types from f:///. My application renders offline websites in a WPF application. Everything is working except for links to other content files (csv, mailto and pdf).
If I register a CefCustomScheme for "mailto", then I get the ProcessRequestAsync and can run the Process.Start(...). However another blank window also popup.
If I then add a second CefCustomScheme for "file", then nothing happens. None of the ISchemeHandler ProcessRequestAsync methods are invoked.
I must be able to handle all requests, excluding *.html, in a separate handler
Essentially I just want to replicate the behavior of the MS Web-browser Control. There all I did was point to the entry page (index.htm), and everything loaded. Then if a user clicks any link, the control handled the action and started the correct process (content handler, i.e. Excel for Csv).
The code:
// Startup
var settings = new CefSettings();
settings.LogFile = #"c:\temp\ceflog.txt";
settings.LogSeverity = LogSeverity.Verbose;
settings.IgnoreCertificateErrors = true;
CefCustomScheme mailtoScheme = new CefCustomScheme();
mailtoScheme.SchemeName = "mailto";
mailtoScheme.SchemeHandlerFactory = new SchemeHandlerFactory();
CefCustomScheme filesScheme = new CefCustomScheme();
mailtoScheme.SchemeName = "file";
mailtoScheme.SchemeHandlerFactory = new SchemeHandlerFactory();
settings.RegisterScheme(mailtoScheme);
settings.RegisterScheme(filesScheme);
if (!Cef.Initialize(settings))
throw new InvalidOperationException("Failed to initialize the browser factory");
-- SchemeHandlerFactory
public class SchemeHandlerFactory : ISchemeHandlerFactory {
public ISchemeHandler Create() {
return new CustomSchemeHandler();
}
}
-- Handler
public class CustomSchemeHandler : ISchemeHandler {
private static readonly ILog _log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
public bool ProcessRequestAsync(IRequest request, ISchemeHandlerResponse response, OnRequestCompletedHandler requestCompletedCallback) {
_log.DebugFormat("Processing url: {0}", request.Dump());
var knownContentTypes = new[] {".csv", ".xsls", ".xlsx", ".pdf", ".txt"};
var ext=Path.GetExtension(request.Url);
if(knownContentTypes.Contains(ext)) {
_log.DebugFormat("Starting process for: {0}",request.Url);
Process.Start(request.Url);
return false;
}
return true;
}
The solution was to implement an IRequestHandler and use the OnBeforeResourceLoad event to check what content was requested. The ISchemeHandler is used for the "mailto" actions.
In my case I had to assign the request handler after the frame loaded. This allowed the web browser to render all content first.
Code sample GitHub example
i have a form with a FormPanel, a FileUpload and a Button
final FormPanel formPanel = new FormPanel();
formPanel.setAction("uploadServlet");
formPanel.setMethod(FormPanel.METHOD_POST);
formPanel.setEncoding(FormPanel.ENCODING_MULTIPART);
formPanel.setSize("100%", "100%");
setWidget(formPanel);
AbsolutePanel absolutePanel = new AbsolutePanel();
formPanel.setWidget(absolutePanel);
absolutePanel.setSize("249px", "70px");
final FileUpload fileUpload = new FileUpload();
fileUpload.setName("uploadFormElement");
absolutePanel.add(fileUpload, 0, 0);
Button btnOpen = new Button("Open");
absolutePanel.add(btnOpen, 10, 30);
Button btnCancel = new Button("Cancel");
absolutePanel.add(btnCancel, 63, 30);
this.setText("Open...");
this.setTitle(this.getText());
this.setAnimationEnabled(true);
this.setGlassEnabled(true);
btnOpen.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
formPanel.submit();
}
});
the servlet gets called but the request contains a error message "error post".
When i try it on the local server it works, the request contains the file, but on the app engine server only the error
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
FileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
List<?> items = null;
String json = null;
try {
items = upload.parseRequest(request);
}
catch (FileUploadException e) {
e.printStackTrace();
}
Iterator<?> it = items.iterator();
while (it.hasNext()) {
System.out.println("while (it.hasNext()) {");
FileItem item = (FileItem) it.next();
json = item.getString();
}
response.setContentType("text/html");
ServletOutputStream out = response.getOutputStream();
response.setContentLength(json.length());
out.write(json.getBytes());
out.close();
}
DiskFileItemFactory is the default implementation for the commons-fileupload library, and based in it's javadoc:
This implementation creates FileItem instances which keep their content either in memory, for smaller items, or in a temporary file on disk, for larger items. The size threshold, above which content will be stored on disk, is configurable, as is the directory in which temporary files will be created.
If not otherwise configured, the default configuration values are as follows:
Size threshold is 10KB. Repository is the system default temp directory, as returned by System.getProperty("java.io.tmpdir").
So as you see, this implementation is going to write in filesystem when it does not have enough memory.
In GAE, there are many constrains, like the memory you are allow to use, or the prohibition of writing in the filesystem.
Your code should fail in GAE developing mode, but maybe you have not reached the memory limitation, or whatever since GAE dev tries to emulate the same constrains than production server, but it is not identical.
Said, that I could take a look to gwtupload library, they have a servlet for GAE which can save files in different ways: BlobStore, FileApi and MemCache.