Short version:
I have a third-party MVC Extension that returns the FileResult. I would like to be able to process the file before returning it to the client. How can I do that?
If you're interested in boring details, read below:
I'm using Devexpress XtraReports for reporting purposes. The problem is that it's PDF Export is terrible, but RTF one works great. I would like to create action&result filter that would trick DevExpress into generating rtf instead of pdf (done) and convert the rtf to pdf using some other third-party library. The only problem is that I need to obtain rtf file from FileResult and return my own FileResult with the converted content.
//EDIT:
The code right now looks as follows:
public virtual ActionResult ReportExport(TParameters parameters)
{
return DevExpress.Web.Mvc.ReportViewerExtension.ExportTo(this.GetReport(parameters));
}
You said you are using a third-party MVC extension that returns a FileResult and you want to access the file details that are wrapped inside the result.
If you see the FileResult, it is an abstract class and I just know there are three built-in implementations are available in MVC (I don't aware about others): FileContentResult, FileStreamResult and FilePathResult.
All these classes provide public properties to access the details of the file. For ex. the FileContentResult contains a public property called FileContents that returns the content of a file as a byte array.
The third-party extension can return any FileResult implementation of the built-in types or badly(if you can't access those type) their own types as well. You can check the returned FileResult instance's type and cast it accordingly to access the file details.
var fileResult = ... returned by the MVC extension
if(fileResult is FileContentResult)
{
var fileContentResult = fileResult as FileContentResult;
var fileContent = fileContentResult.FileContents;
// process the file
}
else if(fileResult is FileStreamResult)
{
var fileStreamResult = fileResult as FileStreamResult;
var fileStream = fileStreamResult .Stream;
//...
}
else if(fileResult is FilePathResult) // this result only contains the path
// of the file name
{
//...
}
If you want make the things reusable you can implement a custom file result that takes the FileResult returned by the extension in the constructor and override the WriteFile method.
The FileResult probably writes the file contents to the response stream. You can use a standard ASP.NET response stream using a filter. See this ancient article for details:
https://web.archive.org/web/20211029043851/https://www.4guysfromrolla.com/articles/120308-1.aspx
Alternatively you could create your own ActionResult that invokes the DevExpress FileResult using a custom HttpContext with a fake response stream, which is where you would do your conversion.
Related
I'm working a Controller that will generate/retrieve files. These will optionally set headers.
public IActionResult SampleFileReport()
{
I see the return type is IActionResult (a data contract). I see inside the function I can still set
response.ContentType
Is there a preferred pattern for how to set ContentType in a controller?
I'm thinking it should be part of the DataContract and setting response.contentype is an anti-pattern, however I see examples such as this that utilize it. Returning a file to View/Download in ASP.NET MVC
All you need to do is return File:
public IActionResult SampleFileReport()
{
// do stuff
return File(bytes, mimetype, filename);
}
File also has overloads that accept Stream and string (path and filename to a file on the filesystem) in addition to byte[]. The mimetype is your content type, e.g. application/pdf, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet (Excel), etc. The final filename param is optional. If it's provided, a Content-Disposition: attachment header is sent with the response, which prompts the browser to pop a download dialog. Otherwise, the default Content-Disposition: inline is used, and the browser will try to load the returned file directly the browser tab/window, assuming the mime-type is supported for native rendering the browser. If not, then you'll get a download dialog, regardless.
If we are talking about MVC (not .NET Core) then you can change IActionResult to FileContentResult
public FileContentResult SampleFileReport()
{
byte[] fileBytes = GetFileBytes();
return File(fileBytes, MediaTypeNames.Application.Octet, "fileName");
}
Just checked this class still exists. FileContentResult .NET Core
In my Asp.Net MVC application, I am using a customized template of twitter bootstrap.
I have placed this template folder named "b3-detail-admin" on root of my mvc application.
Using Browser's address bar, when I am trying to access one file directly from this folder like so:
http://my-pc/MyWebAppMvc/b3-detail-admin/font/fontawesome-webfont.woff
I am getting following error:
HTTP Error 404.3 - Not Found
The page you are requesting cannot be served because of the extension configuration. If the page is a script, add a handler. If the file should be downloaded, add a MIME map.
What should I do to have direct access to all files/folders under "b3-detail-admin" folder?
In mvc you can only get access to the routes defined in RouteConfig.
You can add a ActionResult which returns the document like so:
public ActionResult GetPdf()
{
string file = Server.MapPath("~/folder/file.pdf");
return File(file, "application/pdf", "file.pdf");
}
You then can make it dynamic to get all sorts of files:
public ActionResult GetFile(string url, string mime)
{
string file = Server.MapPath(url);
string filename = url.Split(new char[]{'/'})[url.Split(new char[]{'/'}).Count()];
return File(file, "mime", filename);
}
I'm trying to do pdf viewer functionality in mvc application. When user click the "read the pdf" link it should open a new tab/window and user should be able view the pdf file. So I checked the examples but I couldn't find. Could you suggest me any article or example ?
Show an anchor tag in your first view and pass an id (to identify what PDF to show)
#Html.ActionLink("read the pdf","view","doc",new { #id=123},null)
Now in the doc controller, have an action method which have a parameter called id and return the pdf there using the File method.
public ActionResult View(int id)
{
byte[] byteArrayOfFile=GetFieInByteArrayFormatFromId(id);
return File(byteArrayOfFile,"application/pdf");
}
Assuming GetFileInByteArrayFormatFromId is the method which returns the byte array format of the PDF file.
You can also return the PDF if you know the full path to the PDF file physically stored, using this overload.
public ActionResult Show()
{
string path="FullPAthTosomePDFfile.pdf";
return File(path, "application/pdf","someFriendlyName.pdf");
}
Show the PDF in a browser without downloading it
Based on the browser setting of the end user, the above solution will either ask user whether he/she wishes to download or open the file or simply download/open the file. If you prefer to show the file content directly in the browser without it gets downloaded to the user's computer, you may send a filestream to the browser.
public ActionResult Show(int id)
{
// to do : Using the id passed in,build the path to your pdf file
var pathToTheFile=Server.MapPath("~/Content/Downloads/sampleFile.pdf");
var fileStream = new FileStream(pathToTheFile,
FileMode.Open,
FileAccess.Read
);
return new FileStreamResult(fileStream, "application/pdf");
}
The above code expects you to have a pdf file named sampleFile.pdf in ~/Content/Downloads/ location. If you store the file(s) with a different name/naming convention, you may update the code to build the unique file name/path from the Id passed in.
If you want to display the PDF Content in browser, you can use iTextShare dll.
Refer the link http://www.codeproject.com/Tips/387327/Convert-PDF-file-content-into-string-using-Csharp.
Reading PDF content with itextsharp dll in VB.NET or C#
Assume this code in One of my Actions
[HttpPost]
public ActionResult Generate (Params){
....
InsertOneRawToDB();
return RedirectToAction("Index", new { info = info });
}
So every thing is OK yet but when I change return to:
InsertOneRawToDB();
byte[] myfile = GenerateAZipFile();
return File( myfile , "application/zip" , "MyFileName");
In this case I see a weird behavior: before return, One raw inserted to DB, and after return another raw inserted, it seems the InsertOneRawToDB called again.
Does any one know about this? what happening here?
PS: I use Visual Studio 2012 RTM + ASP.NET MVC4
PS: OK I Use IDM (Internet Download Manager) to download zip file and that cause MultiPost on this Action So How can I handle this?
Based on your conclusion that the problem is related to using a download manager... That's what download mangers do. They create multiple connections to the file.
One thing you could do is store a session variable that says "Already started downloading", and then only insert the record the first time.
however if the user legitimately downloaded the file multiple times then you would only get one record.
Another option would be to examine the Http headers and look for the "Range" header, which is what is used to download a file in multiple pieces (or resume a file). You would then have to take the Range parameters and only return the portion of the file requested.
Here's an example of how to do a Ranged download: http://tpeczek.com/2011/10/range-requests-in-aspnet-mvc.html
I'm actually quite surprised that this hasn't come up before... I guess most people don't do database actions in a download action or notice it.
You can redirect to new action and in this new action return file.
[HttpPost]
public ActionResult Generate (Params){
....
InsertOneRawToDB();
return RedirectToAction("GetFile"};
}
[HttpGet]
public ActionResult GetFile
{
byte[] myfile = GenerateAZipFile();
return File(myfile, "application/zip", "filename.ext");
}
What I'm doing:
I am creating an email mailing engine that takes html templates, replaces tokens with data and then sends off the mail to an SMTP server. Eventually we'll want a CMS UI on top of this to allow them to add/remove file templates or update the content in them via a CMS UI. Anyway I am putting the .htm files for the email templates inside my MVC web project and need to read their contents and get the string of HTML back to work with. If I move all this code out of my MVC project into like a utility C# project layer, the problem is then I have to worry about maintaining physical paths ("C:...\theMVCWebSiteFolder...\Email\Templates\SomeTemplate.htm") and that to me would have to be hard coded I think to keep track if you were to move the site to a different server, different hard drive partition, etc. So I'd like to be able to work with the files using the current application's context unless there's a way to do this agnostic to my MVC app and still not have to worry about having to ever change the location of the physical root every time we move folders.
I've got the following utility method I created in a utility class that sits in a folder somewhere in my ASP.NET MVC web project in just a folder (a folder outside of any view folders:
public static string GetFileData(string filePath)
{
if (!File.Exists(HttpContext.Current.Request.PhysicalApplicationPath + filePath))
throw new FileNotFoundException(String.Format("the file {0} was not found", filePath));
string text;
FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.None);
using(StreamReader read = new StreamReader(fileStream))
text = read.ReadToEnd();
return text;
}
I'm trying to figure out why the context is turning up null here. I originally tried HttpContext.Current but current is coming up null so it can't find the current context in my Utility.cs method that sits in my MVC web project.
UPDATE:
Ok so the consensus is to use HttpRequestBase object and not the HttpContext.Current object. I still find it weird that the HttpContext.Current is null. But moving on, if my Utility.cs is outside any context of a controller or view, then how the heck do I get an instance of the current request (HttpRequestBase) to work with and send an instance that implements HttpRequestBase (I do not know what object that would be if I want to do this in the "MVC way" outside a controller or view) to this utility method?
No idea why this returns null but since it is a bad idea to tie your business layers with ASP.NET specifics I'd recommend you the following change to your method:
public static string GetFileData(string filePath)
{
if (!File.Exists(filePath))
throw new FileNotFoundException(String.Format("the file {0} was not found", filePath));
return File.ReadAllText(filePath);
}
and then when you need to consume it from a web application:
public ActionResult Foo()
{
var result = MyClass.GetFileData(Server.MapPath("~/foo/bar.txt"));
...
}
and when you need to consume it in a WinForms application:
protected void Button1_Click(object sender, EventArgs e)
{
var filePath = Path.Combine(Environment.CurrentDirectory, "bar.txt");
var result = MyClass.GetFileData(filePath);
...
}
In a utility class I would remove the dependency on any web-related stuff.
For a path relative to the application root I would use:
Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, fileName)
which will give you what you probably want (a path relative to the web root directory in a web application; relative to the path containing the executable in a WinForms app, ...)
If want to rewrite that method in asp.net mvc way, you could rewrite it this way and remove coupling with HttpContext class
public static string GetFileData(HttpRequestBase request, string filePath)
{
if (!File.Exists(request.PhysicalApplicationPath + filePath))
throw new FileNotFoundException(String.Format("the file {0} was not found", filePath));
string text;
FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.None);
using(StreamReader read = new StreamReader(fileStream))
text = read.ReadToEnd();
return text;
}
And generally in MVC HttpContext, HttpRequest and HttpResponse are abstracted into HttpContextBase, HttpRequestBase and HttpResponseBase accordingly