Earlier we have shown how to build streaming xml reader from business data and have reminded about ForwardXPathNavigator which helps to create a streaming xslt transformation. Now we want to show how to stream content produced with xslt out of WCF service.
To achieve streaming in WCF one needs:
1. To configure service to use streaming. Description on how to do this can be found in the internet. See web.config of the sample Streaming.zip for the details.
2. Create a service with a method returning Stream:
Stream
[ServiceContract(Namespace = "http://www.nesterovsky-bros.com")] [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)] public class Service { [OperationContract] [WebGet(RequestFormat = WebMessageFormat.Json)] public Stream GetPeopleHtml(int count, int seed) { ... } }
2. Return a Stream from xsl transformation.
Unfortunately (we mentioned it already), XslCompiledTransform generates its output into XmlWriter (or into output Stream) rather than exposes result as XmlReader, while WCF gets input stream and passes it to a client.
XslCompiledTransform
XmlWriter
XmlReader
We could generate xslt output into a file or a memory Stream and then return that content as input Stream, but this will defeat a goal of streaming, as client would have started to get data no earlier that the xslt completed its work. What we need instead is a pipe that form xslt output Stream to an input Stream returned from WCF.
.NET implements pipe streams, so our task is trivial. We have defined a utility method that creates an input Stream from a generator populating an output Stream:
public static Stream GetPipedStream(Action<Stream> generator) { var output = new AnonymousPipeServerStream(); var input = new AnonymousPipeClientStream( output.GetClientHandleAsString()); Task.Factory.StartNew( () => { using(output) { generator(output); output.WaitForPipeDrain(); } }, TaskCreationOptions.LongRunning); return input; }
We wrapped xsl transformation as such a generator:
[OperationContract] [WebGet(RequestFormat = WebMessageFormat.Json)] public Stream GetPeopleHtml(int count, int seed) { var context = WebOperationContext.Current; context.OutgoingResponse.ContentType = "text/html"; context.OutgoingResponse.Headers["Content-Disposition"] = "attachment;filename=reports.html"; var cache = HttpRuntime.Cache; var path = HttpContext.Current.Server.MapPath("~/People.xslt"); var transform = cache[path] as XslCompiledTransform; if (transform == null) { transform = new XslCompiledTransform(); transform.Load(path); cache.Insert(path, transform, new CacheDependency(path)); } return Extensions.GetPipedStream( output => { // We have a streamed business data. var people = Data.CreateRandomData(count, seed, 0, count); // We want to see it as streamed xml data. using(var stream = people.ToXmlStream("people", "http://www.nesterovsky-bros.com")) using(var reader = XmlReader.Create(stream)) { // XPath forward navigator is used as an input source. transform.Transform( new ForwardXPathNavigator(reader), new XsltArgumentList(), output); } }); }
This way we have build a code that streams data directly from business data to a client in a form of report. A set of utility functions and classes helped us to overcome .NET's limitations and to build simple code that one can easily support.
The sources can be found at Streaming.zip.
Remember Me
a@href@title, b, blockquote@cite, em, i, strike, strong, sub, super, u