0

I've got a controller which returns a custom made XML string because the application that consumes the Api needs a specific format without any attributes and without the <?xml ... /> tag on top of default XML strings. EDIT: the consumer also doesn't have a request header that asks for 'text/xml'.

My ConfigureServices in my Startup.cs looks like this:

    public void ConfigureServices(IServiceCollection services)
    {
        // Add framework services.
        var mvc = services.AddMvc();

        mvc.AddMvcOptions(options =>
        {
            options.InputFormatters.Remove(new JsonInputFormatter());
            options.OutputFormatters.Remove(new JsonOutputFormatter());
        });

        mvc.AddXmlDataContractSerializerFormatters();
    }

In my controller, I've tried some solutions I've found on internet (commented out), but none give me the XML content with the response header 'Content-Type: application/xml' in chrome devtools:

[HttpGet("{ssin}")]
[Produces("application/xml")]
public string Get(string ssin)
{    
    var xmlString = "";
    using (var stream = new StringWriter())
    {
        var xml = new XmlSerializer(person.GetType());
        xml.Serialize(stream, person);
        xmlString = stream.ToString();
    }
    var doc = XDocument.Parse(xmlString);
    doc.Root.RemoveAttributes();
    doc.Descendants("PatientId").FirstOrDefault().Remove();
    doc.Descendants("GeslachtId").FirstOrDefault().Remove();
    doc.Descendants("GeboorteDatumUur").FirstOrDefault().Remove();
    doc.Descendants("OverledenDatumUur").FirstOrDefault().Remove();
    Response.ContentType = "application/xml";
    Response.Headers["Content-Type"] = "application/xml";

    /*var response = new HttpResponseMessage
    {
        Content = new  StringContent(doc.ToString(), Encoding.UTF8, "application/xml"),
    };*/
    return doc.ToString(); //new HttpResponseMessage { Content = new StringContent(doc., Encoding.UTF8, "application/xml") };
}

What can I try to get it respond with application/xml? Response

EDIT1 (after Luca Ghersi's answer): Startup.cs:

    public Startup(IHostingEnvironment env)
    {
        // Set up configuration sources.
        var builder = new ConfigurationBuilder()
            .AddJsonFile("appsettings.json")
            .AddEnvironmentVariables();
        Configuration = builder.Build();
    }

    public IConfigurationRoot Configuration { get; set; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        // Add framework services.
        var mvc = services.AddMvc(config => {
            config.RespectBrowserAcceptHeader = true;
            config.InputFormatters.Add(new XmlSerializerInputFormatter());
            config.OutputFormatters.Add(new XmlSerializerOutputFormatter());
        });

        mvc.AddMvcOptions(options =>
        {
            options.InputFormatters.Remove(new JsonInputFormatter());
            options.OutputFormatters.Remove(new JsonOutputFormatter());
        });

        //mvc.AddXmlDataContractSerializerFormatters();
    }
    /*
     * Preconfigure if the application is in a subfolder/subapplication on IIS
     * Temporary fix for issue: https://github.com/aspnet/IISIntegration/issues/14 
     */
    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        app.Map("/rrapi", map => ConfigureApp(map, env, loggerFactory));
    }


    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void ConfigureApp(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        loggerFactory.AddConsole(Configuration.GetSection("Logging"));
        loggerFactory.AddDebug();

        //app.UseIISPlatformHandler();

        app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");
        });
    }

    // Entry point for the application.
    public static void Main(string[] args) => WebApplication.Run<Startup>(args);

Controller:

        [HttpGet("{ssin}")]
    [Produces("application/xml")]
    public IActionResult Get(string ssin)
    {
        var patient = db.Patienten.FirstOrDefault(
            p => p.Rijksregisternummer.Replace(".", "").Replace("-", "").Replace(" ", "") == ssin
        );

        var postcode = db.Postnummers.FirstOrDefault(p => p.PostnummerId == db.Gemeentes.FirstOrDefault(g =>
            g.GemeenteId == db.Adressen.FirstOrDefault(a =>
                a.ContactId == patient.PatientId && a.ContactType == "pat").GemeenteId
            ).GemeenteId
        ).Postcode;

        var person = new person
        {
            dateOfBirth = patient.GeboorteDatumUur.Value.ToString(""),
            district = postcode,
            gender = (patient.GeslachtId == 101 ? "MALE" : "FEMALE"),
            deceased = (patient.OverledenDatumUur == null ? "FALSE" : "TRUE"),
            firstName = patient.Voornaam,
            inss = patient.Rijksregisternummer.Replace(".", "").Replace("-", "").Replace(" ", ""),
            lastName = patient.Naam
        };
        var xmlString = "";
        using (var stream = new StringWriter())
        {
            var opts = new XmlWriterSettings { OmitXmlDeclaration = true };
            using (var xw = XmlWriter.Create(stream, opts))
            {
                var xml = new XmlSerializer(person.GetType());
                xml.Serialize(xw, person);
            }
            xmlString = stream.ToString();
        }
        var doc = XDocument.Parse(xmlString);
        doc.Root.RemoveAttributes();
        doc.Descendants("PatientId").FirstOrDefault().Remove();
        doc.Descendants("GeslachtId").FirstOrDefault().Remove();
        doc.Descendants("GeboorteDatumUur").FirstOrDefault().Remove();
        doc.Descendants("OverledenDatumUur").FirstOrDefault().Remove();

        return Ok(doc.ToString()); 
Mr Lister
  • 45,515
  • 15
  • 108
  • 150
Appsum Solutions
  • 999
  • 2
  • 10
  • 30

2 Answers2

0

Looks like this article is what you're looking for. Instead of trying to do it manually, you should try the XML formatter, like this:

 // Add framework services.
  services.AddMvc(config =>
  {
    // Add XML Content Negotiation
    config.RespectBrowserAcceptHeader = true;
    config.InputFormatters.Add(new XmlSerializerInputFormatter());
    config.OutputFormatters.Add(new XmlSerializerOutputFormatter());
  });

This outputFormatter dependes on:

"Microsoft.AspNet.Mvc.Formatters.Xml": "6.0.0-rc1-final"

Also you need to leave [Produces("application/xml")] as method attribute as detailed in this answer.

Check also this extremely detailed article about Formatters in MVC 6. It's the updated version. I guess it will be helpful.

To modify how the responde will be generated you can use a XmlWriterSettings options object, like this (more info here):

var settings = new XmlWriterSettings { OmitXmlDeclaration = true };
config.OutputFormatters.Add(new XmlSerializerOutputFormatter(settings);

Hope it helps!

Luca Ghersi
  • 3,261
  • 18
  • 32
  • Thanks for the quick response, but sadly (I forgot to mention that, adding it to my post) the consumer does not add a request header of 'text/xml',so content negotiation is no option. I have to force it to either ask for xml or force the header on the response to say it returns 'application/xml' – Appsum Solutions Mar 17 '16 at 08:15
  • Forget the content negotiation then. If you remove the JSON formatter, as you did, and leave the XML only, it should work I guess because it will be the only available formatter for ASP.NET to use. – Luca Ghersi Mar 17 '16 at 08:17
  • Still it returns 'text/plain'. I'll add an EDIT1 to my post to show my changes. – Appsum Solutions Mar 17 '16 at 08:27
  • Did you left the [Produces("application/xml")] on the method? I updated my answer with that. – Luca Ghersi Mar 17 '16 at 08:32
  • Yes I did, as seen in my EDIT1, I've added the complete controller getSSIN action code and the complete Startup.cs code in case you find another fault in it. – Appsum Solutions Mar 17 '16 at 08:35
  • I solved the header, but now i'm using the XMLformatter and now the XML has the attributes again. Is there a way to change the way the formatter serializes the object to XML? – Appsum Solutions Mar 17 '16 at 18:29
  • The XmlSerializerOutputFormatter() takes a XmlWriterSettings as parameter. Have you checked for customization options there? I will update my answer with a couple of links. – Luca Ghersi Mar 18 '16 at 08:57
  • I thing, that for now there isn't chance to produce XML from MVC 6. In my opinion, there is a bug with XmlFormatter, because the controller always returns JSON result. – Synthy Apr 28 '16 at 14:28
0

Create an XmlWriter padding options to block the creation of an XML Declaration. Then use one of the XmlSerializer.Serialize overloads that takes an XmlWriter. The XmlWriter can write to a string (see here):

using (var sw = new StringWriter()) {
  var opts = new XmlWriterSettings { OmitXmlDeclaration = true };
  using (var xw = XmlWriter.Create(sw, opts) {

    xml.Serialize(xw, person);

  }
  xmlString = sw.ToString();
}

NB You are already setting Response.ContentType therefore something else if overriding this. Check for filters and modules that may be overriding your setting.

Community
  • 1
  • 1
Richard
  • 106,783
  • 21
  • 203
  • 265
  • It is a file-new-project with only the 1 controller with 2 action (Index to show the api is working and the ssin action), and the 'person' class file, so I doubt that something overrrides it. Still, I've updated my code to your additions, but it still returns text/plain. – Appsum Solutions Mar 17 '16 at 08:26
  • @Asum [This answer](http://stackoverflow.com/a/23381647/67392) may help: appears you cannot directly manipulate the content type directly. – Richard Mar 17 '16 at 08:50
  • I solved the header, but now i'm using the XMLformatter and now the XML has the attributes again. Is there a way to change the way the formatter serializes the object to XML? – Appsum Solutions Mar 17 '16 at 18:29
  • @Asum there are lots of options (which is generally why I find writing the XML myself easier: much simpler to control). – Richard Mar 18 '16 at 14:01