2

I'm struggling with uploading an xml file with axios to my asp .net server. I'm using the following code from the vue side to get an xml file, which is working and afterwards upload it to my server:

uploadXmlFile(file: any) {
    const rawFile = new XMLHttpRequest();
    rawFile.open('GET', file, false);
    rawFile.onreadystatechange = () => {
        if (rawFile.readyState === 4) {
            if (rawFile.status === 200 || rawFile.status === 0) {
                const allText = rawFile.responseText;

                axios.post(`${url}`, rawFile, {
                headers: {
                    'Content-Type': 'application/xml',
                    'Accept-Language': 'application/xml',
                },
                });
            }
        }
    };
    rawFile.send(null);
}

On the asp .net side I got this function:

[HttpPost]
public IActionResult Post(object xml)
{
    // Do something with the xml
    ...
}

Uploading the file results in a code 415 : Unsupported Media Type.

I found some suggestions to add the xml formatter to my project, which didn't work and furthermore I don't want to parse the xml file, I just want to save the file to my filesystem.

I tried to upload the rawFile and the parsed text in combination with different media types, e.g. text/plain, text/xml and application/xml

I also tried to add the accept-language

I also tried the proposed way from the microsoft documentation with this function header: public async Task<IActionResult> OnPostUploadAsync(List<IFormFile> files)

And I tried to debug this with Postman, uploading a simple image, which gave me the same error.

vchan
  • 835
  • 2
  • 11
  • 27
TobiasW
  • 861
  • 3
  • 13
  • 36
  • Do **_not**_ send synchronous XMLHttpRequests! – Aluan Haddad Aug 10 '20 at 21:56
  • @AluanHaddad Yeah I know, but this shouldn't be the issue here right? I mean I get the xml file before I upload it. I will improve on that as soon as this stuff is working... – TobiasW Aug 11 '20 at 08:56
  • @TobiasW Could you try executing the script again but this time upload a screenshot from the browser's developer tools and the network tab. I mainly want to see the request headers and body. I believe that there is an error either on the request or the parameter type (`object`) of the action. – vchan Aug 13 '20 at 10:23
  • @vchan I'm not quite sure, how I would upload an image with my browsers dev tools. But I'm debugging this also with Postman and tried to upload an image with Postman, which also doesn't work and gives me the same error – TobiasW Aug 13 '20 at 12:12
  • @vchan As soon as I change my body to a json object it works, but all other content-types are blocked – TobiasW Aug 13 '20 at 12:30
  • @TobiasW Could you make your action like this and see if it works: `public IActionResult Post([FromForm] object xml)` . If it does not work, could you add a breakpoint at the beginning of your action and see what the content of the parameter `xml`? – vchan Aug 13 '20 at 12:41
  • What looks odd in your code is ``axios.post(`${url}`, rawFile, ...``. Maybe you just meant to write ``axios.post(`${url}`, allText, ...)`` So `allText` instead of `rawFile`. – Christian Aug 13 '20 at 13:33
  • Does this answer your question: https://stackoverflow.com/questions/44538772/asp-net-core-form-post-results-in-a-http-415-unsupported-media-type-response – Jason Aug 13 '20 at 14:46
  • @vchan `public IActionResult Post([FromForm] object xml)` works but the object is empty – TobiasW Aug 14 '20 at 06:35

2 Answers2

1

If you write the backend like this, it will always try to do serialize the content to JSON, because the default serializer. If you send content type XML, it won't know how to deserialize your XML into an object.

This is how we usually process file uploads:

[HttpPost]
public async Task<IActionResult> ReceiveFileUpload(CancellationToken cancellationToken = default(CancellationToken))
{
    var contentType = Request.ContentType; // Or HttpContext.Request
    var stream = Request.Body; // Or HttpContext.Request
    // Process file stream in whatever way, e.g. XmlDocument.Load(stream);
}

This should at least make it work in e.g. Postman.

Your axios client looks almost ok, a couple of things to note:

  • you probably want to post alltext, not rawFile
  • you should remove 'Accept-Language': 'application/xml'
  • we usually use a FileReader to get the file, not quite sure what you are doing and if that is correct.
            axios.post(url, content, {
                headers: {
                    "Content-Type": "application/xml"
                }
            })
<input type='file' accept='text/plain' onchange='openFile(event)'>

var openFile = function(event) {
    var input = event.target;
    var text = "";
    var reader = new FileReader();
    var onload = function(event) {
        text = reader.result;
        console.log(text);

    };

    reader.onload = onload;
    reader.readAsText(input.files[0]);

};

(https://codepen.io/mlasater9/pen/jqQdOM/)

Alex AIT
  • 17,361
  • 3
  • 36
  • 73
  • Your suggested changes at the server code work. But there is the declaration for the Request object missing, no? – TobiasW Aug 14 '20 at 06:25
  • `Request` should always be available in your ASP.NET Controller. And you can parse the stream into any structure you like, e.g. `XmlDocument`. Do you need something else? – Alex AIT Aug 14 '20 at 06:29
  • Are you talking from ASP.NET or ASP.NET CORE, which is this question about? – TobiasW Aug 14 '20 at 06:32
  • ASP.NET Core. You can also try `HttpContext.Request` instead of just `Request` – Alex AIT Aug 14 '20 at 06:38
  • That worked, I can receive the XML now from postman and my axios call! One open point is the XmlDocument.Load(stream) which I can't use in a static context which my controller is. – TobiasW Aug 14 '20 at 07:05
  • 1
    I don't really understand what your issue is, maybe open a new question for it? – Alex AIT Aug 14 '20 at 07:26
1

First of all, regarding your Front-end you need to send the allText constant and not the rawFile. So your code will be like this:

axios.post(
    url,
    allText,
    {
        headers: {
            'Content-Type': 'application/xml'
        }
    }
)

Now for your Back-end, ASP.NET core does not recognize xml by default. You need to go to the file Startup.cs which is on the root of your application and on the method ConfigureServices you need to add services.AddXmlSerializerFormatters(). So your code will be like this:

services
    .AddMvc()
    .AddXmlSerializerFormatters()
    .SetCompatibilityVersion(CompatibilityVersion.Version_2_1); // this depends on your .net core version

You must also create a class model for your xml to be parsed. For example, if you have an xml like this

<document>
    <id>12345</id>
    <content>This is a Test</content>
    <author>vchan</author>
</document>

you should have a class model like the one below

[XmlRoot(ElementName = "document", Namespace = "")]
public class DocumentDto
{
    [XmlElement(DataType = "string", ElementName = "id")]
    public string Id { get; set; }

    [XmlElement(DataType = "string", ElementName = "content")]
    public string Content { get; set; }

    [XmlElement(DataType = "string", ElementName = "author")]
    public string Author { get; set; }
}

Also, .net can understand by its own the raw xml body and thus, your controller's action should be like below

[HttpPost]
public IActionResult TestMethod([FromBody] DocumentDto xml) {
    // Do something with the xml
}

Be advised though that you should always use the HTTP header Content-Type: application/xml for your request in order for the .net to understand, parse and deserialize it. You do not need to reinvent the wheel for each different action which you are going to implement, .net does that for you!

vchan
  • 835
  • 2
  • 11
  • 27