I'm encountering a problem sending files stored in a database back to the user in ASP.NET MVC. What I want is a view listing two links, one to view the file and let the mimetype sent to the browser determine how it should be handled, and the other to force a download.
If I choose to view a file called SomeRandomFile.bak and the browser doesn't have an associated program to open files of this type, then I have no problem with it defaulting to the download behavior. However, if I choose to view a file called SomeRandomFile.pdf or SomeRandomFile.jpg I want the file to simply open. But I also want to keep a download link off to the side so that I can force a download prompt regardless of the file type. Does this make sense?
I have tried FileStreamResult and it works for most files, its constructor doesn't accept a filename by default, so unknown files are assigned a file name based on the URL (which does not know the extension to give based on content type). If I force the file name by specifying it, I lose the ability for the browser to open the file directly and I get a download prompt. Has anyone else encountered this?
These are the examples of what I've tried so far.
//Gives me a download prompt.
return File(document.Data, document.ContentType, document.Name);
//Opens if it is a known extension type, downloads otherwise (download has bogus name and missing extension)
return new FileStreamResult(new MemoryStream(document.Data), document.ContentType);
//Gives me a download prompt (lose the ability to open by default if known type)
return new FileStreamResult(new MemoryStream(document.Data), document.ContentType) {FileDownloadName = document.Name};
Any suggestions?
UPDATE:
This questions seems to strike a chord with a lot of people, so I thought I'd post an update. The warning on the accepted answer below that was added by Oskar regarding international characters is completely valid, and I've hit it a few times due to using the ContentDisposition class. I've since updated my implementation to fix this. While the code below is from my most recent incarnation of this problem in an ASP.NET Core (Full Framework) app, it should work with minimal changes in an older MVC application as well since I'm using the System.Net.Http.Headers.ContentDispositionHeaderValue class.
using System.Net.Http.Headers;
public IActionResult Download()
{
Document document = ... //Obtain document from database context
//"attachment" means always prompt the user to download
//"inline" means let the browser try and handle it
var cd = new ContentDispositionHeaderValue("attachment")
{
FileNameStar = document.FileName
};
Response.Headers.Add(HeaderNames.ContentDisposition, cd.ToString());
return File(document.Data, document.ContentType);
}
// an entity class for the document in my database
public class Document
{
public string FileName { get; set; }
public string ContentType { get; set; }
public byte[] Data { get; set; }
//Other properties left out for brevity
}
public ActionResult Download()
{
var document = ...
var cd = new System.Net.Mime.ContentDisposition
{
// for example foo.bak
FileName = document.FileName,
// always prompt the user for downloading, set to true if you want
// the browser to try to show the file inline
Inline = false,
};
Response.AppendHeader("Content-Disposition", cd.ToString());
return File(document.Data, document.ContentType);
}
NOTE: This example code above fails to properly account for international characters in the filename. See RFC6266 for the relevant standardization. I believe recent versions of ASP.Net MVC's File() method and the ContentDispositionHeaderValue class properly accounts for this. - Oskar 2016-02-25
I had trouble with the accepted answer due to no type hinting on the "document" variable: var document = ... So I'm posting what worked for me as an alternative in case anybody else is having trouble.
public ActionResult DownloadFile()
{
string filename = "File.pdf";
string filepath = AppDomain.CurrentDomain.BaseDirectory + "/Path/To/File/" + filename;
byte[] filedata = System.IO.File.ReadAllBytes(filepath);
string contentType = MimeMapping.GetMimeMapping(filepath);
var cd = new System.Net.Mime.ContentDisposition
{
FileName = filename,
Inline = true,
};
Response.AppendHeader("Content-Disposition", cd.ToString());
return File(filedata, contentType);
}
To view file (txt for example):
return File("~/TextFileInRootDir.txt", MediaTypeNames.Text.Plain);
To download file (txt for example):
return File("~/TextFileInRootDir.txt", MediaTypeNames.Text.Plain, "TextFile.txt");
note: to download file we should pass fileDownloadName argument
Darin Dimitrov's answer is correct. Just an addition:
Response.AppendHeader("Content-Disposition", cd.ToString()); may cause the browser to fail rendering the file if your response already contains a "Content-Disposition" header. In that case, you may want to use:
Response.Headers.Add("Content-Disposition", cd.ToString());
I believe this answer is cleaner, (based on
https://stackoverflow.com/a/3007668/550975)
public ActionResult GetAttachment(long id)
{
FileAttachment attachment;
using (var db = new TheContext())
{
attachment = db.FileAttachments.FirstOrDefault(x => x.Id == id);
}
return File(attachment.FileData, "application/force-download", Path.GetFileName(attachment.FileName));
}
Below code worked for me for getting a pdf file from an API service and response it out to the browser - hope it helps;
public async Task<FileResult> PrintPdfStatements(string fileName)
{
var fileContent = await GetFileStreamAsync(fileName);
var fileContentBytes = ((MemoryStream)fileContent).ToArray();
return File(fileContentBytes, System.Net.Mime.MediaTypeNames.Application.Pdf);
}
FileVirtualPath --> Research\Global Office Review.pdf
public virtual ActionResult GetFile()
{
return File(FileVirtualPath, "application/force-download", Path.GetFileName(FileVirtualPath));
}
Action method needs to return FileResult with either a stream, byte[], or virtual path of the file. You will also need to know the content-type of the file being downloaded. Here is a sample (quick/dirty) utility method. Sample video link
How to download files using asp.net core
[Route("api/[controller]")]
public class DownloadController : Controller
{
[HttpGet]
public async Task<IActionResult> Download()
{
var path = #"C:\Vetrivel\winforms.png";
var memory = new MemoryStream();
using (var stream = new FileStream(path, FileMode.Open))
{
await stream.CopyToAsync(memory);
}
memory.Position = 0;
var ext = Path.GetExtension(path).ToLowerInvariant();
return File(memory, GetMimeTypes()[ext], Path.GetFileName(path));
}
private Dictionary<string, string> GetMimeTypes()
{
return new Dictionary<string, string>
{
{".txt", "text/plain"},
{".pdf", "application/pdf"},
{".doc", "application/vnd.ms-word"},
{".docx", "application/vnd.ms-word"},
{".png", "image/png"},
{".jpg", "image/jpeg"},
...
};
}
}
If, like me, you've come to this topic via Razor components as you're learning Blazor, then you'll find you need to think a little more outside of the box to solve this problem. It's a bit of a minefield if (also like me) Blazor is your first forray into the MVC-type world, as the documentation isn't as helpful for such 'menial' tasks.
So, at the time of writing, you cannot achieve this using vanilla Blazor/Razor without embedding an MVC controller to handle the file download part an example of which is as below:
using Microsoft.AspNetCore.Mvc;
using Microsoft.Net.Http.Headers;
[Route("api/[controller]")]
[ApiController]
public class FileHandlingController : ControllerBase
{
[HttpGet]
public FileContentResult Download(int attachmentId)
{
TaskAttachment taskFile = null;
if (attachmentId > 0)
{
// taskFile = <your code to get the file>
// which assumes it's an object with relevant properties as required below
if (taskFile != null)
{
var cd = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
{
FileNameStar = taskFile.Filename
};
Response.Headers.Add(HeaderNames.ContentDisposition, cd.ToString());
}
}
return new FileContentResult(taskFile?.FileData, taskFile?.FileContentType);
}
}
Next, make sure your application startup (Startup.cs) is configured to correctly use MVC and has the following line present (add it if not):
services.AddMvc();
.. and then finally modify your component to link to the controller, for example (iterative based example using a custom class):
<tbody>
#foreach (var attachment in yourAttachments)
{
<tr>
<td>#attachment.Filename </td>
<td>#attachment.CreatedUser</td>
<td>#attachment.Created?.ToString("dd MMM yyyy")</td>
<td><ul><li class="oi oi-circle-x delete-attachment"></li></ul></td>
</tr>
}
</tbody>
Hopefully this helps anyone who struggled (like me!) to get an appropriate answer to this seemingly simple question in the realms of Blazor…!
Related
I have a .NET 7 web app, where I have a controller that results in a sitemap.xml file. When I run the application locally, I get an XML file as a result with this content:
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"/>
And it looks like this:
However, when this is pushed to production (everything is hosted as a web app on Azure), the same endpoint returns nothing. It does recognize the endpoint and looks like this:
My code to generate this, is shown below:
[Route("/sitemap.xml")]
public async Task SitemapXml()
{
var countries = await _countryService.GetBySpecificationAsync(new CountrySpecification()
{
Take = int.MaxValue
});
Response.ContentType = "application/xml";
using (var xml = XmlWriter.Create(Response.Body, new XmlWriterSettings { Indent = true }))
{
xml.WriteStartDocument();
xml.WriteStartElement("urlset", "http://www.sitemaps.org/schemas/sitemap/0.9");
xml.WriteEndElement();
}
}
My question:
I am completely lost. At first I thought it was because I didn't add support for static files and this is considered a static file, but I do have:
app.UseStaticFiles();
In the Program.cs.
Any hints where I should be starting?
I spent some time this week wanting to answer this question, and I have time now.
The main issue with your attempt is you are not returning XML results. To do so I suggest using IActionResult interface.
Now time to create sitemap.xml. IMO there are 2 ways to go from here, either using a library OR writing your own sitemap method.
I will start with a library. For instance, there is a very simple library (NuGet) called SimpleMvcSitemap.Core. Install it in your project, and in your controller insert the following code:
[Route("/sitemap.xml")]
public async Task<IActionResult> SitemapXml()
{
// your await call etc
List<SitemapNode> nodes = new List<SitemapNode>
{
new SitemapNode(Url.Action("Index","Home")),
new SitemapNode(Url.Action("About","Home")),
//other nodes
};
return new SitemapProvider().CreateSitemap(new SitemapModel(nodes));
}
Btw for this test, I created an asp.net MVC .net 7 project.
I have deployed the solution to azure and it works both on local development and on azure. Here is the result:
If you do want to do it manually, you can do following
var listUrls = new List<string>
{
Url.Action("Index", "Home"),
Url.Action("About", "Home")
};
return new SitemapResult(listUrls);
And here is the implementation:
public class SitemapResult : ActionResult
{
private readonly IEnumerable<string> _urls;
public SitemapResult(IEnumerable<string> urls)
{
_urls = urls;
}
public override async Task ExecuteResultAsync(ActionContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var response = context.HttpContext.Response;
response.ContentType = "application/xml; charset=utf-8";
var settings = new XmlWriterSettings() { Async = true, Encoding = Encoding.UTF8, Indent = false };
using (var writer = XmlWriter.Create(response.Body, settings))
{
WriteToXML(writer);
await writer.FlushAsync();
}
}
private void WriteToXML(XmlWriter writer)
{
writer.WriteStartDocument();
// Write the urlset.
writer.WriteStartElement("urlset", "http://www.sitemaps.org/schemas/sitemap/0.9");
// url element
foreach (var item in _urls)
{
writer.WriteStartElement("url");
// loc
writer.WriteStartElement("loc");
writer.WriteValue(item);
writer.WriteEndElement();
writer.WriteEndElement();
}
writer.WriteEndElement();
writer.WriteEndDocument();
}
}
The manual way is also deployed on azure and works, but in the manual way you need to do a lot of work that is already done in a library. To be fair both above outcome is inspired form the question How to dynamically create a sitemap.xml in .NET core 2?.
from this msdn magazine: "A controller that returns void will produce an EmptyResult." I assume this holds true also for Task.
So maybe you need to change your return type of your method from Task to Task<IActionResult> (or whatever suits you most) and return the content with any of these availablle methods.
Then though, I cannot understand why without these mods is currently working locally.
I'm trying to post a file to my PlanGrid project using the following code. Once the upload is complete, I log into the website, open the Publish Log, then click on "Publish Your Sheets", at which point it asks me again to define version set. Can someone clarify what the UploadVersionRequest.VersionName property is used for then?
public static async Task Upload(string project_uid, string filename, Stream payload)
{
var api = PlanGridClient.Create(Properties.Settings.Default.ApiKey);
var versionRequest = new UploadVersionRequest
{
NumberOfFiles = 1,
VersionName = "MyVersion" // how does this get used??
};
var versionUpload = await api.UploadVersion(project_uid, versionRequest);
foreach (var fileUploadRequest in versionUpload.FileUploadRequests)
{
var uploadFile = new UploadFile
{
FileName = filename
};
var fileUpload = await api.UploadFileToVersion(project_uid, versionUpload.Uid, fileUploadRequest.Uid, uploadFile);
await api.Upload<object>(fileUpload, payload);
}
await api.CompleteVersionUpload(project_uid, versionUpload.Uid);
}
Thanks for the question. We released an update to the upload process that included both version name and issue date. That change has not been reflected in the API, so when you set the version name in the API, it is not reflected during the publishing process.
I have a view that is, instead of returning a View(), is returning a dynamically created PDF and then showing the PDF in a new tab. I'm not saving the PDF anywhere, or storing it anywhere. What I would like to do is have a loading screen show up while the PDF is being created. Can this be done?
public ActionResult SolicitorActionReport_Load(SolicitorActionParamsViewModel viewModel) {
var cultivationModel = new CultivationModel(viewModel, ConstituentRepository, CampaignRepository);
var cultivationData = cultivationModel.GetCultivationActivityData();
var reportParamModel = new List<ReportParamModel>
{new ReportParamModel {AgencyName = SelectedUserAgency.AgencyName, StartDate = viewModel.StartDate, EndDate = viewModel.EndDate}};
var reportToRun = "ActionDateCultivationReport";
if (viewModel.SortActionBy == SolicitorActionReportSortType.Constituent) {
reportToRun = "ConstituentCultivationReport";
} else if (viewModel.SortActionBy == SolicitorActionReportSortType.Solicitor) {
reportToRun = "SolicitorCultivationReport";
}
return FileContentPdf("Constituent", reportToRun, cultivationData, reportParamModel, new List<FundraisingAppealMassSummary>(), new List<FundraisingAppealPortfolioSummary>());
}
public FileContentResult FileContentPdf(string folder, string reportName, object dataSet,object reportParamModel,object appealMassDataSet, object appealPortfolioDataSet) {
var localReport = new LocalReport();
localReport.ReportPath = Server.MapPath("~/bin/Reports/" + folder + "/rpt" + reportName + ".rdlc");
var reportDataSource = new ReportDataSource(reportName + "DataSet", dataSet);
var reportParamsDataSource = new ReportDataSource("ReportParamModelDataSet", reportParamModel);
var reportParamsDataSourceMass = new ReportDataSource("FundraisingAppealMassSummaryDataSet", appealMassDataSet);
var reportParamsDataSourcePortfolio = new ReportDataSource("FundraisingAppealPortfolioSummaryDataSet", appealPortfolioDataSet);
#region Setting ReportViewControl
localReport.DataSources.Add(reportDataSource);
localReport.DataSources.Add(reportParamsDataSource);
localReport.DataSources.Add(reportParamsDataSourceMass);
localReport.DataSources.Add(reportParamsDataSourcePortfolio);
localReport.SubreportProcessing += (s, e) => { e.DataSources.Add(reportDataSource); };
string reportType = "pdf";
string mimeType;
string encoding;
string fileNameExtension;
//The DeviceInfo settings should be changed based on the reportType
//http://msdn2.microsoft.com/en-us/library/ms155397.aspx
string deviceInfo = "<DeviceInfo><OutputFormat>PDF</OutputFormat></DeviceInfo>";
Warning[] warnings;
string[] streams;
byte[] renderedBytes;
//Render the report
renderedBytes = localReport.Render(reportType, deviceInfo, out mimeType, out encoding, out fileNameExtension, out streams, out warnings);
#endregion
return File(renderedBytes, mimeType);
}
I'm not saving the PDF anywhere, or storing it anywhere. What I would like to do is have a loading screen show up while the PDF is being created. Can this be done?
Short Answer
No, not in a new tab.
The main problem with what you're trying to do is the lack of power you have when it comes to controlling the browser. Specifically, when you tell an anchor to open its hyperlink in a new tab (ie target="_blank"). There are hacky ways around this that generally are just going to frustrate your user because you're changing behavior that they might be dependent/relying on.
Workaround
You can get very close to your desired outcome by using this jQuery File Download plugin (view a demo). Basically, it manipulates an iframe to queue a download. This makes it possible to show a loading div while also keeping the user on the active page (not directing them to another tab). Then, the user can click the downloaded PDF which will most-likely open in a new tab (view compatible browsers here).
If you decide to use this plugin, here are the steps to applying it:
Download the plugin js source and include it in your Scripts.
Include the FileDownloadAttribute class provided in the plugin MVC Demo:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class FileDownloadAttribute: ActionFilterAttribute
{
public FileDownloadAttribute(string cookieName = "fileDownload", string cookiePath = "/")
{
CookieName = cookieName;
CookiePath = cookiePath;
}
public string CookieName { get; set; }
public string CookiePath { get; set; }
/// <summary>
/// If the current response is a FileResult (an MVC base class for files) then write a
/// cookie to inform jquery.fileDownload that a successful file download has occured
/// </summary>
/// <param name="filterContext"></param>
private void CheckAndHandleFileResult(ActionExecutedContext filterContext)
{
var httpContext = filterContext.HttpContext;
var response = httpContext.Response;
if (filterContext.Result is FileResult)
//jquery.fileDownload uses this cookie to determine that a file download has completed successfully
response.AppendCookie(new HttpCookie(CookieName, "true") { Path = CookiePath });
else
//ensure that the cookie is removed in case someone did a file download without using jquery.fileDownload
if (httpContext.Request.Cookies[CookieName] != null)
{
response.AppendCookie(new HttpCookie(CookieName, "true") { Expires = DateTime.Now.AddYears(-1), Path = CookiePath });
}
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
CheckAndHandleFileResult(filterContext);
base.OnActionExecuted(filterContext);
}
}
github source
Apply the FileDownload attribute to your ActionResult method:
[FileDownload]
public ActionResult SolicitorActionReport_Load(SolicitorActionParamsViewModel viewModel) {
...
return FileContentPdf("Constituent", reportToRun, cultivationData, reportParamModel, new List<FundraisingAppealMassSummary>(), new List<FundraisingAppealPortfolioSummary>());
}
Include the necessary markup in the View to which you'll be linking to the report:
<a class="report-download" href="/Route/To/SolicitorActionReport">Download PDF</a>
Attach an event handler to the report-download anchor:
$(document).on("click", "a.report-download", function () {
$.fileDownload($(this).prop('href'), {
preparingMessageHtml: "We are preparing your report, please wait...",
failMessageHtml: "There was a problem generating your report, please try again."
});
return false; //this is critical to stop the click event which will trigger a normal file download!
});
You can view working demos at http://jqueryfiledownload.apphb.com/. There is also a demo that uses pre-styled jQuery UI modals to "prettify" the user experience.
You can also download the demo ASP.NET MVC solution from johnculviner / jquery.fileDownload github to see all of this working.
I think you have two choices:
Redirect to a "loading" page with fancy GIF spinners, then direct the request to the PDF (this would work if the PDF take a little server time to generate - the visitor would be looking at a loading page while waiting for next page to load)
or
Use an iFrame: load a page that has an iframe. This page can overlay a spinning GIF and loading message while the iFrame loads the PDF itself. Note: you could make the iframe 100% width and height
I would like to integrate flowplayer in ASP mvc 3 application.
I'm using SQL Server 2008. Database contains path to files as well which are stored in folder. I wrote library which is able to convert video to flv. Now i would like to use this video in page.
Does anyone have knowledge how to implement that?
I found the flowplayer sample code.
http://flowplayer.org/docs/
But I'm looking for codebehind solution (C#) how to implement something like that (how to return path to video).
If anyone have some code I'll be grateful for help.
Please take a look.
public string FindMusicByID(int musicID)
{
var pathh = from plik in _data.musicMusicTables
where plik.musicMusicID == musicID
select new PathToFile { PathFile = plik.musicMusicPath };
return pathh.ToString();
}
This is the linq part code which i'm using. FindMusicByID(int musicID) return path to .flv file. I'm looking for how to send output filepath to webpage. I would like to use this path in flowplayer.
<div class="flowplayer" data-engine="flash"> <video src="some path"></video>
Fox example this is my ouptut path d:\file.flv
I suppose that it's necessary to send output to JavaScript.
How can i insert path into flowplayer by ID?
I'm not sure what you are asking for, but if you want to know how to return .flv file for your player you just need Action that return FileStremResult using this mimetype: video/x-flv
Something along the lines of:
public string FindMusicByID(int musicID)
{
var pathh= from plik in _data.musicMusicTables
where plik.musicMusicID == musicID
select new PathToFile { PathFile = plik.musicMusicPath };
var cd = new System.Net.Mime.ContentDisposition {
FileName = "filename",
Inline = false
};
Response.AppendHeader("Content-Disposition", cd.ToString());
return File(path, asset.AssetType.MimeType);
}
Is there a way to synchronously process an uploaded file POSTed to a controller in the ASP.Net Web API?
I've tried the process Microsoft proposed here, and it works as described, but I'd like to return something other than a Task<> from the Controller method in order to match the rest of my RESTful API.
Basically, I'm wondering if there is there any way to make this work:
public MyMugshotClass PostNewMugshot(MugshotData data){
//get the POSTed file from the mime/multipart stream <--can't figure this out
//save the file somewhere
//Update database with other data that was POSTed
//return a response
}
Again, I have made the asynchronous example work but am hoping for a way to process the uploaded file before responding to the client.
public class UploadController : ApiController
{
public async Task<HttpResponseMessage> Post()
{
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
var appData = HostingEnvironment.MapPath("~/App_Data");
var folder = Path.Combine(appData, Guid.NewGuid().ToString());
Directory.CreateDirectory(folder);
var provider = new MultipartFormDataStreamProvider(folder);
var result = await Request.Content.ReadAsMultipartAsync(provider);
if (result.FileData.Count < 1)
{
// no files were uploaded at all
// TODO: here you could return an error message to the client if you want
}
// at this stage all files that were uploaded by the user will be
// stored inside the folder we specified without us needing to do
// any additional steps
// we can now read some additional FormData
string caption = result.FormData["caption"];
// TODO: update your database with the other data that was posted
return Request.CreateResponse(HttpStatusCode.OK, "thanks for uploading");
}
}
You might notice that the uploaded files are stored inside the specified folder with names that might look like this: BodyPart_beddf4a5-04c9-4376-974e-4e32952426ab. That's a deliberate choice that the Web API team made that you could override if you want.