Possible to add a XSLT Stylesheet to a serialized XML document?

Viewed 6394

I have code that serializes a complex object to XML and saves it as a file, is there a quick way to include a style sheet in the xml during the serialization?

Using C# and .net framework v2.

4 Answers

For those who are wondering how to achieve similar things in modern dotnet core you gonna need to make few adjustments:

using System.Collections.Generic;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Serialization;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Extensions.DependencyInjection;

namespace ContentNegotiation
{
    public class Program
    {
        public static void Main(string[] args) => CreateWebHostBuilder(args).Build().Run();

        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>();
    }

    public class MyXmlSerializerOutputFormatter : XmlSerializerOutputFormatter
    {
        protected override void Serialize(XmlSerializer xmlSerializer, XmlWriter xmlWriter, object value)
        {
            // TODO: add me only if controller has some kind of custom attribute with XSLT file name
            xmlWriter.WriteProcessingInstruction("xml-stylesheet", "type=\"text/xsl\" href=\"template.xsl\"");
            base.Serialize(xmlSerializer, xmlWriter, value);
        }
    }

    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc(options =>
            {
                options.RespectBrowserAcceptHeader = true; // default is false
                // options.OutputFormatters.Add(new XmlSerializerOutputFormatter()); // not enough
                options.OutputFormatters.Add(new MyXmlSerializerOutputFormatter());
            })
            // .AddXmlSerializerFormatters() // does not added by default, but not enough
            .SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            app.UseStaticFiles();
            app.UseMvc();
        }
    }

    public class Post
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public string Body { get; set; }
    }

    [ApiController]
    public class DemoController : ControllerBase
    {
        // curl -k -i -s -H 'Accept: text/xml' http://localhost:5000/posts
        // curl -k -i -s -H 'Accept: application/json' http://localhost:5000/posts
        [HttpGet]
        [Route(nameof(Posts))]
        public IEnumerable<Post> Posts() => new[] {
            new Post {
                Id = 1,
                Title = "Hello World",
                Body = "Lorem ipsum dot color"
            },
            new Post {
                Id = 2,
                Title = "Post 2",
                Body = "Lorem ipsum dot color"
            }
        };
    }
}

We are turning on content negotiation in ConfigureServices and giving our implementation of XmlSerializerOutputFormatter which will add XSL to the output

dotnet core content negotiation plus xsl

So now our backend will respond with JSON to requests like:

curl -k -i -s -H 'Accept: application/json' http://localhost:5000/posts

and XML:

curl -k -i -s -H 'Accept: text/xml' http://localhost:5000/posts

Sample of xsl for demo purposes might be found here: https://mac-blog.org.ua/dotnet-content-negotiation/

I wrote this to reduce the problem to just adding an attribute on the class, as we do to describe every other xml construction directive:

Usage would be:

    [XmlStylesheet("USED-FILE.xsl")]
    public class Xxx
    {
        // etc
    }


    Xxx x = new Xxx();

    XmlSerializer s = new XmlSerializer(typeof(Xxx));
    using (var  tw = File.CreateText(@"c:\Temp\test.xml"))
    using (var xw = XmlWriter.Create(tw))
    {
        s.SerializeWithStyle(xw, x);    // only line here that needs to change. 
                                        // rest is standard biolerplate.
    }

The library code needed for this: (Keep in the same namespace, so when IntelliSense adds in the namespace for the attribute, it will pull in the extension methods as well)

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml.Serialization;
using System.Reflection;
using System.Xml;

namespace NovelTheory.Xml.Serialization
{
    public class XmlStylesheetAttribute : Attribute
    {
        public string Href { get; set; }
        public XmlStylesheetAttribute(string href)
        {
            Href = href;
        }
    }

    public static class XmlStylesheetAttributeExtenstions
    {
        public static void SerializeWithStyle(this XmlSerializer serializer, 
                XmlWriter textWriter, object o)
        {
            AddStyleSheet(textWriter, o);
            serializer.Serialize(textWriter, o);
        }

        public static void SerializeWithStyle(this XmlSerializer serializer, 
                XmlWriter textWriter, object o, XmlSerializerNamespaces namespaces)
        {
            AddStyleSheet(textWriter, o);
            serializer.Serialize(textWriter, o, namespaces);
        }
        private static void AddStyleSheet(XmlWriter textWriter, object o)
        {
            var dnAttribute = o.GetType()
                                            .GetTypeInfo()
                                            .GetCustomAttribute<XmlStylesheetAttribute>();
            if (dnAttribute != null)
                textWriter.WriteProcessingInstruction("xml-stylesheet", 
                                        $@"type=""text/xsl"" href=""{dnAttribute.Href}""");
        }
    }
}
Related