2

Scenario:

User submit XML using WebApi and I want to store it in SQL database in XML Column and retrieve later using Ajax/WebApi

Question:

How should I store it in the database? With or without declaration/encoding? Or should I add the encoding when returning the XML?

public async IActionResult Post([FromBody]XDocument xml)
{
   var entity = new MyDocument();
   entity.Xml = xml.ToString(); //?? 
   db.Documents.Add(entity);
   db.SaveChanges();
   return Created();
}

public async Task<IActionResult> Get(int id)
{
   var entity = db.Documents.Find(id);
   return Content(entity.Xml, "application/xml"); //missing xml declaration
}

My Observations:

  • XDocument.ToString() trims the XML declaration element:

    var xml = XDocument.Load(@"<?xml version=""1.0"" encoding=""utf-8""?>
                               <Root><Child>Content</Child></Root>");
    xml.ToString(); //<Root><Child>Content</Child></Root>
    

    It's easy to include it, but I tought that maybe it's for a reason.

  • Edge browser does not display the XML when the response does not include xml declaration:

    public IActionResult Get()
    {
        return Content("<Root><Child>Content</Child></Root>", "application/xml")
    }
    
  • When the response include xml declaration, but the encoding from the declaration does not match response encoding, it also fails with "Unable to switch encodings":

    public IActionResult Get()
    {
        return Content(@"<?xml version=""1.0"" encoding=""utf-8""?>
                         <Root><Child>Content</Child></Root>", "application/xml");
    }
    
  • In order to make Edge browser to display the XML properly, I have to do folowing:

    public IActionResult Get()
    {
        string xml = @"<?xml version=""1.0"" encoding=""utf-8""?>
                       <Root><Child>Content</Child></Root>")
        var xmlDoc = XDocument.Parse(xml);
        return Content(xml, "application/xml", Encoding.GetEncoding(xmlDoc.Declaration.Encoding));
    }
    

Since database also has some encoding, it's quite unclear to me, what is actually the right way.

Liero
  • 25,216
  • 29
  • 151
  • 297
  • Use the native XML type, convert the XML to `unicode` (`utf-16` / `UCS-2`) and pass it over **without the leading `` declaration**. I placed some related answers, read [this](https://stackoverflow.com/a/44894409/5089204) and [this](https://stackoverflow.com/a/41505440/5089204). – Shnugo Oct 19 '17 at 10:20
  • I use XML column in database. Is that what you mean by native xml type? In C#, any string is utf-16 and ORM framework (see first code snippet) takes care of translating it into proper SQL. What if I stored the leading ` xml ?>` declaration without the encoding attribute. That should work, shouldn't it? – Liero Oct 19 '17 at 11:22
  • Did you read the linked answers? `XML` column is the native type, yes. Internally XML is using *2-byte-character encding* (`ucs-2`), but it is not stored as the string you see, rather as a hierarchical tree. This makes XML astonishingly fast. The leading `` will be thrown away in any case... – Shnugo Oct 19 '17 at 11:26

1 Answers1

0

I've identified these rules as best practices:

  1. You should not store XML encoding in database

    • declaration is optional, but if present, it must not include encoding
  2. Your WebApi should always return XML Declaration.

    • encoding is optional, but if present, it must match response encoding

Explanation:

It's clear that you don't want to store encoding in the database, since the database threats XML columns on it own. However, it makes sense to store XML declaration in the database, because it can contain other information (version, standalone).

Your REST service should always return XML declaration in the response. In case it is not stored in database, you can construct it at runtime.

public async IActionResult Post([FromBody]XDocument xml)
{
   var entity = new MyDocument();
   var declarationWithoutEncoding = new XDeclaration(xml.Declaration?.Version, null, xml.Declaration?.Standalone);
   entity.Xml = $@"{declarationWithoutEncoding}\n\r{xml.ToString()}"
   db.Documents.Add(entity);
   db.SaveChanges();
   return Created();
}

public async Task<IActionResult> Get(int id)
{
   var entity = db.Documents.Find(id);
   return Content(entity.Xml, "application/xml"); //in case XML Declaration is in database
}

alternativelly

public async Task<IActionResult> Get(int id)
{
   var entity = db.Documents.Find(id);
   XDocument xmlDoc = XDocument.Parse(entity.xml);

   var declaration = new XDeclaration(xml.Declaration?.Version ?? "1.0", null, xml.Declaration?.Standalone ? "no");

   //you may change enccoding information to match response encoding, or just skip it
   //declaration.Encoding = ...

   string xml = $@"{declaration}\n\r{xml.ToString()}"

   return Content(xml, "application/xml"); 
}
Liero
  • 25,216
  • 29
  • 151
  • 297