Usually in our applications, we need to create PDF, XLS or CVS files and serve them on-demand to be downloaded by users. The goal of this post is to show how to implement this functionality in an ASP.NET Boilerplate (ABP) project.
This post follows this answer in the ABP's official forum.
In general, the recommended solution is to implement it in two iterations:
For a better understanding, we will show an example of how to serve PDF files on-demand, breaking down by tier (from ABP's architecture) the solution and analyzing its responsibilities.
When the user request to download a file, we'll generate it in this tier. It's recommended to implements this functionality in this tier due to reasons like re-utilization, responsibility (business logic matters) and separation of concerns.
The PDF file generation can be done in two ways:
The selection is an architecture matter and must consider the non-functional requirements.
In this case, we're going to generate the PDF file and store it in a temporal folder on the server's hard disk.
This is a sample code:
In this case, we're going to use the ABD cache support (check this) to store in-memory the generated PDF file. But first we need to inject the ICacheManager dependency in the service
This is a sample code:
In this example, we used a
In this tier, we will serve the downloads requests for PDF files and will use a MVC controller to do it.
The controller is very easy. First, load the file named
This is a sample code:
Similarly, the implementation is very easy plus a few details. First, we need to inject
This is a sample code:
In this tier, at first, we are going to request generate the PDF file and will get an ID (
The following is a sample in an AngularJS app.
In this post, we show a solution to how to download file on-demand in an ABP project. As could be appreciated, the implementation take place in 3 tiers and involved differents technologies. This is a basic implementation, you surely have to fit it to your needs. I hope it is useful.
This post follows this answer in the ABP's official forum.
1. Overview
In general, the recommended solution is to implement it in two iterations:
- The client requests to generate the file. In server side, the file is generated and stored (in memory or on the hard disk) and the server returns an ID for it.
- The client requests to download the file, using the ID for the file.
2. Implementation
For a better understanding, we will show an example of how to serve PDF files on-demand, breaking down by tier (from ABP's architecture) the solution and analyzing its responsibilities.
2.1. Application Tier
When the user request to download a file, we'll generate it in this tier. It's recommended to implements this functionality in this tier due to reasons like re-utilization, responsibility (business logic matters) and separation of concerns.
The PDF file generation can be done in two ways:
- Generate the file and store it on the server's hard disc.
- Generate the file and store it in memory.
The selection is an architecture matter and must consider the non-functional requirements.
Storing files in the server's hard disk
In this case, we're going to generate the PDF file and store it in a temporal folder on the server's hard disk.
This is a sample code:
namespace namespace DemoAbp.Services { public class DemoAbpAppService : ApplicationService, IDemoAbpAppService { public FileTokenOutput GeneratePdf() { // Generate unique token based in a timestamp string token = DateTime.Now.Ticks + ".pdf"; // Store files in the temporal folder FileStream fileStream = new FileStream( AppDomain.CurrentDomain.BaseDirectory + "tmp\\" + token, FileMode.Create, FileAccess.Write, FileShare.ReadWrite); // Generate de PDF file and write the content in the // "fileStream" stream. return new FileOutput { File = new FileDto() { Token = token, Filename = "MyFilename.pdf" } }; } } }
Storing files in-memory
In this case, we're going to use the ABD cache support (check this) to store in-memory the generated PDF file. But first we need to inject the ICacheManager dependency in the service
This is a sample code:
namespace namespace DemoAbp.Services
{
public class DemoAbpAppService : ApplicationService, IDemoAbpAppService
{
// Other dependencies
// ABD cache dependency
private readonly ICacheManager cacheManager;
// Inject other dependencies
public DemoAppService(ICacheManager cacheManager)
{
this.cacheManager = cacheManager;
}
public FileTokenOutput GeneratePdf(IdInput input)
{
// Generate unique token based in a timestamp
string token = DateTime.Now.Ticks + ".pdf";
MemoryStream memoryStream = new MemoryStream();
// Generate the PDF file and write the content in the
// in the "ms" stream
// Storing the stream in the cache.
// The third param sets to delete the object after 1 minute
cacheManager.GetCache("pdf").Set(
token,
memoryStream,
TimeSpan.FromMinutes(1));
return new FileOutput
{
File = new FileDto()
{
Token = token,
Filename = "MyFilename.pdf"
}
};
}
}
}
In this example, we used a
MemoryStream
object to write the PDF file content in memory. Next, we'll save the MemoryStream
object in the cache, using the token as an identifier.2.2. Web Tier
In this tier, we will serve the downloads requests for PDF files and will use a MVC controller to do it.
Storing files in the server's hard disk
The controller is very easy. First, load the file named
token
from the temporal folder (previously mentioned), write the content on the HTTP response stream using the helper function File
and delete the file at end.This is a sample code:
namespace DemoAbp.Controllers { [AbpMvcAuthorize] public class FileController : DemoAbpControllerBase { public FileResult DownloadPdfFile(string token) { try { // Load file from the temporal folder FileStream fileStream = new FileStream( AppDomain.CurrentDomain.BaseDirectory + "tmp\\" + token, FileMode.Open, FileAccess.Read, FileShare.Delete); // Set the proper HTTP response content type FileStreamResult fileStreamResult = File(fileStream, "application/pdf", token); return fileStreamResult; } catch (Exception e) { throw new UserFriendlyException("Error", "Error downloading file. Please try again.", e); } finally { System.IO.File.Delete(AppDomain.CurrentDomain.BaseDirectory + "tmp\\" + token); } } } }
Storing files in-memory
Similarly, the implementation is very easy plus a few details. First, we need to inject
ICacheManager
dependency to get access to the cache mechanism. After that, load the PDF file from cache as a MemoryStream
type, using the token
parameter as it's identifier. At the end, remove the object from the cache.This is a sample code:
namespace DemoAbp.Controllers { [AbpMvcAuthorize] public class FileController : DemoAbpControllerBase { private readonly ICacheManager cacheManager; public FileController(ICacheManager cacheManager) { this.cacheManager = cacheManager; } public FileResult DownloadPdfFile(string token) { try { // Loading file from cache. MemoryStream ms = (MemoryStream) this.cacheManager.GetCache("pdf").GetOrDefault(token); // Set the proper HTTP response content type FileContentResult fileContentResult = File(ms.ToArray(), "application/pdf", token); return fileContentResult; } catch (Exception e) { throw new UserFriendlyException("Error", "Error downloading file. Please try again.", e); } finally { this.cacheManager.GetCache("pdf").Remove(token); } } } }
2.3. Presentation Tier
In this tier, at first, we are going to request generate the PDF file and will get an ID (
token
) as a result. Then, we'll use token
to request the file download from server.The following is a sample in an AngularJS app.
$scope.exportToPdf = function() { abp.ui.setBusy(null, demoAbpService.generatePdf({}).success(function (result) { // Get PDF file token var token = result.file.token; // Get PDF file name var filename = result.file.filename; // Do HTTP request to download file $http.post('/file/downloadPdfFile', { token }, { responseType: 'arraybuffer' }).then( function successCallback(response) { // This callback will be executed asynchronously when the request will be available. var file = new Blob([response.data], { type: 'application/pdf' }); var fileURL = window.URL.createObjectURL(file); // Create an element to execute the download. var element = document.createElement("a"); document.body.appendChild(element); element.style = "display: none"; element.href = fileURL; element.download = filename; element.click(); abp.notify.success('PDF file generated successfully.'); }, function errorCallback(response) { // This callback will be executed asynchronously if there is an error or if server return an error code. abp.notify.error('Error generating the PDF file.'); }); })); }
3. Conclusion
In this post, we show a solution to how to download file on-demand in an ABP project. As could be appreciated, the implementation take place in 3 tiers and involved differents technologies. This is a basic implementation, you surely have to fit it to your needs. I hope it is useful.
Hi. You won't know how useful this post was for me. Thank you very much for sharing.
ReplyDeleteSmall correction on the presentation code in Javascript.
WRONG
var element = document.createElement("element");
CORRECT
var element = document.createElement("a");
Rationale: The HTML element being created should be a link ("a"), so that it respond to the click action appropriately.
Thank for your comment!
Delete