5

Trying to get a .net core 2.0 web api HttpPost method to work with xml input.

Expected Result: When the test endpoint is called from Postman, the input parameter (xmlMessage in the below code) should have the value being sent from the Postman HttpPost body.

Actual Result: input parameter is null.

In startup.cs of the web api project, we have the following code:

public class Startup
{
   public Startup(IConfiguration configuration)
   {
      Configuration = configuration;
   }

   public IConfiguration Configuration { get; }

   // This method gets called by the runtime. Use this method to add services to the container.
   public void ConfigureServices(IServiceCollection services)
   {
      services.AddMvc()
      .AddXmlDataContractSerializerFormatters();
   }

   // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
   public void Configure(IApplicationBuilder app, IHostingEnvironment env)
   {
      if (env.IsDevelopment())
      {
         app.UseDeveloperExceptionPage();
      }
      app.UseMvc();
   }
}

In controller:

[HttpPost, Route("test")]
public async Task<IActionResult> Test([FromBody] XMLMessage xmlMessage)
{
    return null; //not interested in the result for now
}

XMLMessage class:

[DataContract]
public class XMLMessage
{
    public XMLMessage()
    {
    }

    [DataMember]
    public string MessageId { get; set; }
}

In Postman Headers:

Content-Type:application/xml

Http Post Body:

<XMLMessage>
  <MessageId>testId</MessageId>
</XMLMessage>

Appreciate any help that could point me in the right direction. Thanks in advance..

Matt.G
  • 3,586
  • 2
  • 10
  • 23
  • This might help: https://stackoverflow.com/questions/37424559/issue-with-sending-xml-to-web-api-via-http-post-request – Rui Jarimba Jul 23 '18 at 13:52
  • Thanks for the link @RuiJarimba. using XElement as the parameter type gets the xml value into the input parameter xmlMessage. However, I'm interested in getting the xml value directly deserialized to my XMLMessage class object. – Matt.G Jul 23 '18 at 13:57
  • 2
    Just to help troubleshoot your `XMLMessage` class, try using the `XElement` to receive the XML and then write code [along the lines of this answer](https://stackoverflow.com/questions/5010191/using-datacontractserializer-to-serialize-but-cant-deserialize-back?answertab=votes#tab-top) to ensure you've configured the `DataContract` annotation correctly. It may be that Web API isn't able to match the class type to the incoming XML. – Sixto Saez Jul 23 '18 at 14:23
  • @SixtoSaez, that did the trick. I called the Deserialize method and got the error: `xmlns="http://schemas.datacontract.org/2004/07/MyWebApi.Controllers" SerializationException: Error in line 1 position 13. Expecting element XMLMessage from namespace http://schemas.datacontract.org/2004/07/MyWebApi.Controllers.. Encountered Element with name XMLMessage, namespace .` Once I added the namespace like: ``, it started working. Thanks a lot. – Matt.G Jul 23 '18 at 14:40
  • @Matt.G could you please post your full Startup.cs code? – Rui Jarimba Jul 23 '18 at 16:09
  • @RuiJarimba, I have edited the post to include the full Startup.cs code (working version). Please note that I removed the config.InputFormatters.Add(new XmlDataContractSerializerInputFormatter());, as it was working without that – Matt.G Jul 23 '18 at 16:15

3 Answers3

4

You should be using XmlRoot/XmlElement instead of the DataContract/DataElement annotations types. Below is what should be changed in order to make it work.

On Startup.cs

public void ConfigureServices(IServiceCollection services){
    services.AddMvc(options =>
    {
        options.OutputFormatters.Add(new XmlSerializerOutputFormatter());
    });
    // Add remaining settings
}

XMLMessage class:

[XmlRoot(ElementName = "XMLMessage")]
public class TestClass
{
    //XmlElement not mandatory, since property names are the same
    [XmlElement(ElementName = "MessageId")]
    public string MessageId { get; set; }
}

The other pieces look good (Controller and header).

Michał Białecki created a very nice post about the topic. Please refer to it for more a more detailed implementation: Accept XML request in ASP.Net MVC Controller

hiram
  • 140
  • 1
  • 7
2

I was able to make it work. The only thing I had to change was the method Startup.ConfigureServices as follows:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc()
            .AddXmlSerializerFormatters(); 
}
Rui Jarimba
  • 11,166
  • 11
  • 56
  • 86
  • Rui Jarumba, tried this now, getting error: `Parse: Error parsing request: Root element is missing.` – Matt.G Jul 23 '18 at 15:36
  • Seriously? I think my code and my request (using Postman) are exactly the same as yours. Let me double-check if there are any differences. Is the `Content-Type` the only header you added? – Rui Jarimba Jul 23 '18 at 15:38
  • @Matt.G I can't find any differences, other than the ConfigureServices method. Controller method, XMLMessage data contract and the Postman request seems to be exactly like yours – Rui Jarimba Jul 23 '18 at 16:05
  • @Matt.G Which versions of the `Microsoft.AspNetCore.All` and the `Microsoft.NETCore.App` nuget packages are you using? mine are v2.0.9 and v2.0.0 – Rui Jarimba Jul 23 '18 at 16:26
  • v2.0.8 and v2.0.0. tried upgrading to v.2.0.9, still getting same error – Matt.G Jul 23 '18 at 16:27
1

Startup.cs

using System.IO;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace test
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc(config =>
            {
                config.InputFormatters.Add(new XmlSerializerInputFormatter());
            }).AddXmlDataContractSerializerFormatters();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseMvc();
        }
    }
}

Controller.cs

using System.Runtime.Serialization;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;

namespace test.Controllers
{
    [DataContract]
    public class TestClass
    {
        [DataMember]
        public string Message { get; set; }
    }

    [Route("[controller]")]
    public class TestController : Controller
    {
        [HttpPost, Route("test")]
        public async Task<IActionResult> Test([FromBody]TestClass test)
        {
            return Ok("OK");
        }

    }

}

Program.cs

using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting;

namespace test
{
    public class Program
    {
        public static void Main(string[] args)
        {
            BuildWebHost(args).Run();
        }

        public static IWebHost BuildWebHost(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>()
                .Build();
    }
}

test.csproj

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>netcoreapp2.0</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.9" />
  </ItemGroup>
</Project>
Liu
  • 970
  • 7
  • 19