Generating and downloading an XPS document using ASP.NET
Windows Presentation Foundation includes a new API for generating XPS documents. This article describes the steps involved in generating an XPS document on the server side of an ASP.NET web application using WPF and sending the resulting document to the client browser.
What we need to do is create a flow document, convert it to XPS and send it to the client.
Step 1 - Creating a FlowDocument on the server
Creating a FlowDocument object from scratch is as simple as calling the constructor:
FlowDocument flowDocument = new FlowDocument();
This gives us an empty FlowDocument that we can fill with content. The content is represented by a collection of Blocks. Filling the Blocks collection manually will probably suffice for very simple documents Paragraph p1 = new Paragraph();p1.Inlines.Add("This is the text");p1.Inlines.Add("of the first paragraph");Section section1 = new Section(p1);flowDocument.Blocks.Add(section1);
@placeholder1 and @placeholder2 will later be replaced by live data.<FlowDocument xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"><Section><Section FontFamily="Times New Roman"><Paragraph Fontcolor: rgb(139, 0, 0);">18" FontWeight="Bold" >@placeholder1</Paragraph><Paragraph Fontcolor: rgb(139, 0, 0);">10">@placeholder2Lorem ipsum dolor sit amet, consectetur adipisicing elit,sed do eiusmod tempor incididunt ut labore et doloremagna aliqua.</Paragraph></Section></FlowDocument>
The template file can be saved (for example) as a global resource and later accessed by calling
Now that we have the template in memory as a string, it is a good time to replace the placeholders with real data and convert the final template to a real FlowDocument objectstring pageTemplate = (string)HttpContext.GetGlobalResourceObject("Resources", "MyTemplate");
The placeholder replacement process is really lame here, but you get the point...pageTemplate = pageTemplate.Replace("@placeholder1", MyData1);pageTemplate = pageTemplate.Replace("@placeholder2", MyData2);FlowDocument flowDocument = (FlowDocument)XamlReader.Parse(pageTemplate);
At this point, we have a filled-in FlowDocument object in memory ready to be converted to XPS.
Step 2 - Converting a FlowDocument to an (in-memory) XPS file
We start with a FlowDocument object and end up with an XPS file saved as an array of bytes. A code snippet is worth a thousand words here:public static byte[] FlowDocumentToXPS(FlowDocument flowDocument, int width, int height){MemoryStream stream = new MemoryStream();// create a packageusing (Package package = Package.Open(stream, FileMode.Create)){// create an empty XPS documentusing (XpsDocument xpsDoc = new XpsDocument(package, CompressionOption.Maximum)){// create a serialization managerXpsSerializationManager rsm = new XpsSerializationManager(new XpsPackagingPolicy(xpsDoc), false);// retrieve document paginatorDocumentPaginator paginator = ((IDocumentPaginatorSource)flowDocument).DocumentPaginator;// set page sizepaginator.PageSize = new System.Windows.Size(width, height);// save as XPSrsm.SaveAsXaml(paginator);rsm.Commit();}return stream.ToArray();}}
Step 3 - Sending an in-memory file to the client browser
If we want to generate the XPS file (or any other kind of file) based on a client request, it is very inconvenient to have to save the file on the server and give the client its URI.
What we will do instead is create a hidden form on the web page and send the XPS file to the client as a response to submitting this form.
The hidden form can be very simple, all we need to include is the parameters that specify the XPS (or other) file to retrieve.
<form id="generateFileForm" action="DownloadFile.aspx" method="post"><input runat="server" type="hidden" id="id" /></form>
Once we fill in the "id" parameter and submit the form
HtmlDocument doc = HtmlPage.Document;HtmlElement id = doc.GetElementById("id");id.SetAttribute("value", MyDocumentId);doc.Submit("generateFileForm");
the code-behind of DownloadFile.aspx then does the trick
public partial class DownloadFile : System.Web.UI.Page{[StaSyncOperationBehavior]protected void Page_Load(object sender, EventArgs e){string sid = Request.Form["id"];byte[] bytes = GetXpsFileBytes(sid);Response.Clear();Response.ContentType = "application/octet-stream";Response.AddHeader("Content-Disposition", "attachment; filename=document.xps");Response.OutputStream.Write(bytes, 0, bytes.Length);Response.Flush();Response.Close();}}
The [StaSyncOperationBehavior] attribute was developed by Scott Seely and you can read about it here. UPDATE: The blog no longer seems to work, but here is the source code for the attribute.
To ensure that the response generation runs on a STA thread, we also need to set the AspCompat attribute on the DownloadFile.aspx page like this
<%@ Page AspCompat="true" Language="C#" AutoEventWireup="true" CodeBehind="DownloadFile.aspx.cs" Inherits="MyNamespace.DownloadFile" %>
Conclusion
The good thing about this solution is that nothing is saved to disk on the server side and that the client page does not reload when requesting the document, which means that you can happily use it to generate and download documents in Silverlight applications.I hope this experience of mine will be useful to someone and if you have any questions or comments, please let me know in the discussion.
j.Labels: asp.net, silverlight, wpf, xps
3 Comments:
Hi,
I found relly interesting your code, can you help to do this?:
Load a XPS document in memory stream and show the file in a webpage, i tried with your code but i couldnt...
I appreciated
thanks!
How come that XpsDocument is available to ASP.NET?
Does that mean that your application is running on ASP.NET v4?
It uses WPF
Post a Comment
Subscribe to Post Comments [Atom]
<< Home