dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.44k stars 10.02k forks source link

WebAPI try to create not nessesary XmlSerializer instance #13121

Open rranzmaier opened 5 years ago

rranzmaier commented 5 years ago

Describe the bug

I write an ASP.net Core 3 WebAPI. For some reasons I need JSON and in some cases XML output.

I noticed that aspnetcore also created an instance for xml in the responses for json. Most of my Models have no Parameterless constructor and therefore not suitable for xml.

To Reproduce

Steps to reproduce the behavior:

  1. Using this version of ASP.NET Core '...' 3.0.100-preview7-012821
  2. Run this code '....' Use Attached Sample or Create an ASP.NET Core WebAPI Add nuget Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.0.0-preview8.19405.7" Newtonsoft.Json" Version="12.0.2" WebApiTest.zip

startup.cs

public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers()
                .AddXmlSerializerFormatters()
                .AddNewtonsoftJson();
        }

Add a Controller

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;

namespace WebAPITest.Controllers
{
    [ApiController]
    [Route("[controller]")]
    [Produces("application/json")]
    public class TestController : ControllerBase
    {

        private readonly ILogger<TestController> _logger;

        public TestController(ILogger<TestController> logger)
        {
            _logger = logger;
        }

        [HttpGet("A")]
        public ActionResult<TestValue> GetA()
        {
           return Ok(new TestValue() {Id = 1,Text="Test A"});
        }
        [HttpGet("B")]
        public ActionResult<TestValue> GetB()
        {
            return Ok(new TestValue2(1, "This call produce the hidden error"));
        }
    }
    public class TestValue
    {
        public int Id { get; set; }
        public string Text { get; set; }
    }

    public class TestValue2
    {
        public TestValue2(int id, string text)
        {
            Id = id;
            Text = text;
        }
        [JsonProperty("id")]
        public int Id { get; }

        [JsonProperty("txt")]
        public string Text { get; }
    }
}

Start and browse to https://localhost:5001/test/B The Response is ok it's Json and correct but the console show this

  Request starting HTTP/2 GET https://localhost:5001/test/B

info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] Executing endpoint 'WebAPITest.Controllers.TestController.GetB (WebAPITest)' info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[3] Route matched with {action = "GetB", controller = "Test"}. Executing controller action with signature Microsoft.AspNetCore.Mvc.ActionResult`1[WebAPITest.Controllers.TestValue] GetB() on controller WebAPITest.Controllers.TestController (WebAPITest). info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[1] Executing action method WebAPITest.Controllers.TestController.GetB (WebAPITest) - Validation state: Valid info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[2] Executed action method WebAPITest.Controllers.TestController.GetB (WebAPITest), returned result Microsoft.AspNetCore.Mvc.OkObjectResult in 0.6145ms. warn: Microsoft.AspNetCore.Mvc.Formatters.XmlSerializerOutputFormatter[1] An error occurred while trying to create an XmlSerializer for the type 'WebAPITest.Controllers.TestValue2'. System.InvalidOperationException: WebAPITest.Controllers.TestValue2 cannot be serialized because it does not have a parameterless constructor. at System.Xml.Serialization.TypeDesc.CheckSupported() at System.Xml.Serialization.TypeScope.GetTypeDesc(Type type, MemberInfo source, Boolean directReference, Boolean throwOnError) at System.Xml.Serialization.ModelScope.GetTypeModel(Type type, Boolean directReference) at System.Xml.Serialization.XmlReflectionImporter.ImportTypeMapping(Type type, XmlRootAttribute root, String defaultNamespace) at System.Xml.Serialization.XmlSerializer..ctor(Type type, String defaultNamespace) at System.Xml.Serialization.XmlSerializer..ctor(Type type) at Microsoft.AspNetCore.Mvc.Formatters.XmlSerializerOutputFormatter.CreateSerializer(Type type) info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1] Executing ObjectResult, writing value of type 'WebAPITest.Controllers.TestValue2'. info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[2] Executed action WebAPITest.Controllers.TestController.GetB (WebAPITest) in 697.043ms info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] Executed endpoint 'WebAPITest.Controllers.TestController.GetB (WebAPITest)' info: Microsoft.AspNetCore.Hosting.Diagnostics[2] Request finished in 791.3005ms 200 application/json; charset=utf-8

Expected behavior

In my opinion it's not necessary to create an XmlSerializer instance for json output

Screenshots

webapi

Additional context

Include the output of dotnet --info

.NET Core SDK (reflecting any global.json): Version: 3.0.100-preview7-012821 Commit: 6348f1068a

Runtime Environment: OS Name: Windows OS Version: 10.0.18362 OS Platform: Windows RID: win10-x64 Base Path: C:\Program Files\dotnet\sdk\3.0.100-preview7-012821\

Host (useful for support): Version: 3.0.0-preview7-27912-14 Commit: 4da6ee6450

.NET Core SDKs installed: 2.1.500 [C:\Program Files\dotnet\sdk] 2.1.502 [C:\Program Files\dotnet\sdk] 2.1.503 [C:\Program Files\dotnet\sdk] 2.1.504 [C:\Program Files\dotnet\sdk] 2.1.505 [C:\Program Files\dotnet\sdk] 2.1.507 [C:\Program Files\dotnet\sdk] 2.1.508 [C:\Program Files\dotnet\sdk] 2.1.602 [C:\Program Files\dotnet\sdk] 2.1.700 [C:\Program Files\dotnet\sdk] 2.1.701 [C:\Program Files\dotnet\sdk] 2.1.800-preview-009677 [C:\Program Files\dotnet\sdk] 2.1.800-preview-009696 [C:\Program Files\dotnet\sdk] 2.1.800 [C:\Program Files\dotnet\sdk] 2.1.801 [C:\Program Files\dotnet\sdk] 2.2.103 [C:\Program Files\dotnet\sdk] 3.0.100-preview7-012821 [C:\Program Files\dotnet\sdk]

.NET Core runtimes installed: Microsoft.AspNetCore.All 2.1.6 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.All 2.1.7 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.All 2.1.8 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.All 2.1.9 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.All 2.1.11 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.All 2.1.12 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.All 2.2.1 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.All 2.2.2 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.App 2.1.6 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 2.1.7 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 2.1.8 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 2.1.9 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 2.1.11 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 2.1.12 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 2.2.1 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 2.2.2 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 3.0.0-preview7.19365.7 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.NETCore.App 2.1.6 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 2.1.7 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 2.1.8 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 2.1.9 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 2.1.11 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 2.1.12 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 2.2.1 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 2.2.2 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 3.0.0-preview7-27912-14 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.WindowsDesktop.App 3.0.0-preview7-27912-14 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]

pranavkm commented 5 years ago

Does the client specify an Accept: application/json? In the absence of this or a Produces attribute on the action, MVC will simply iterate through available formatters in the order in which they are configured and attempt to format it. There's a few ways to solve this

a) Have the client request for a specific content type i.e. application/json b) Configure the action with a [Produces("application/json")] attribute c) Configure MVC to specify the JSON formatters before XML:

public void ConfigureServices(IServiceCollection services)
{
            services.AddControllers()
                .AddNewtonsoftJson()
                .AddXmlSerializerFormatters();
}
rranzmaier commented 5 years ago

a) Have the client request for a specific content type i.e. application/json Yes b) Configure the action with a [Produces("application/json")] attribute Makes no difference c) Configure MVC to specify the JSON formatters before XML: This works better !

For performance reasons it would be better to check the required output and use the correct serializer. With the wrong order of AddNewtonsoftJson and AddXmlSerializerFormatters every application/json request creates an XmlSerializer instance try to serialize and in my example it throws and catch an exception.

pranavkm commented 5 years ago

@rranzmaier if the client specifies a accept-type, it seems fairly odd that it attempts to use the xml formatter. Worth a investigation.

rranzmaier commented 5 years ago

@rranzmaier if the client specifies a accept-type, it seems fairly odd that it attempts to use the xml formatter. Worth a investigation.

Thanks. I use Postman and specified the accept-type.

GET /Test/B HTTP/1.1 Host: localhost:5001 Accept: application/json User-Agent: PostmanRuntime/7.15.0 Cache-Control: no-cache Postman-Token: 3e725c6f-4dbc-477d-b0de-4e1acb5bec1a,f147d6fa-82e6-469d-a1a0-ef0cf5b37829 Host: localhost:5001 accept-encoding: gzip, deflate Connection: keep-alive cache-control: no-cache

If you need more information let me know

pranavkm commented 5 years ago

Thanks for the info @rranzmaier. I'll ping you once I have had a chance to investigate.

CarlosBustos1 commented 4 years ago

Hello, I have the same warning when I add: services.AddControllers().AddXmlSerializerFormatters(); 1) The client is sending Accept:'application/json' 2) The methods are decorated with [Produces("application/json")]

The response is OK but it appears it is trying to serialize with xmlFormatter Dependencies: Net core 3.1 Microsoft.AspNetCore.Mvc.NewtonsoftJson 3.1.1

The warning: Microsoft.AspNetCore.Mvc.Formatters.XmlSerializerOutputFormatter: Warning: An error occurred while trying to create an XmlSerializer for the type 'NetTopologySuite.Features.FeatureCollection'.

mkArtakMSFT commented 4 years ago

We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. However, keep in mind that there are many other high priority features with which it will be competing for resources.

ghost commented 2 years ago

Thanks for contacting us. We're moving this issue to the .NET 8 Planning milestone for future evaluation / consideration. Because it's not immediately obvious that this is a bug in our framework, we would like to keep this around to collect more feedback, which can later help us determine the impact of it. We will re-evaluate this issue, during our next planning meeting(s). If we later determine, that the issue has no community involvement, or it's very rare and low-impact issue, we will close it - so that the team can focus on more important and high impact issues. To learn more about what to expect next and how this issue will be handled you can read more about our triage process here.

Raslif commented 1 year ago

The XmlSerializer will not work for properties with private setters. Just give it a try by adding a parameterless constructor, modifying the properties setters, and removing the Produce attribute.

From the attached code of the TestController class, the Produce attribute will try to return only those mentioned types.

ecedemirel commented 8 months ago

Are there any updates regarding this issue? Has the problem been resolved?

sergiojrdotnet commented 7 months ago

Same issue here. Upon initialization, the application is trying to create a XmlSerializer for each method, even those decorated with [Produces("application/json")] attribute

[21:14:30 INF] Now listening on: http://[::]:9150
[21:14:31 WRN] An error occurred while trying to create an XmlSerializer for the type 'DFeTech.Resources.FilesResource'.
System.InvalidOperationException: There was an error reflecting type 'DFeTech.Resources.FilesResource'.
 ---> System.InvalidOperationException: Cannot serialize member 'DFeTech.Resources.FilesResource.Files' of type 'System.Collections.Generic.List`1[[DFeTech.Resources.FileResource, NFEio.ProductInvoice, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]', see inner exception for more details.
 ---> System.InvalidOperationException: DFeTech.Resources.FileResource cannot be serialized because it does not have a parameterless constructor.
   --- End of inner exception stack trace ---
   at System.Xml.Serialization.StructModel.CheckSupportedMember(TypeDesc typeDesc, MemberInfo member, Type type)
   at System.Xml.Serialization.StructModel.GetPropertyModel(PropertyInfo propertyInfo)
   at System.Xml.Serialization.StructModel.GetFieldModel(MemberInfo memberInfo)
   at System.Xml.Serialization.XmlReflectionImporter.InitializeStructMembers(StructMapping mapping, StructModel model, Boolean openModel, String typeName, RecursionLimiter limiter)
   at System.Xml.Serialization.XmlReflectionImporter.ImportStructLikeMapping(StructModel model, String ns, Boolean openModel, XmlAttributes a, RecursionLimiter limiter)
   at System.Xml.Serialization.XmlReflectionImporter.ImportTypeMapping(TypeModel model, String ns, ImportContext context, String dataType, XmlAttributes a, Boolean repeats, Boolean openModel, RecursionLimiter limiter)
   --- End of inner exception stack trace ---
   at System.Xml.Serialization.XmlReflectionImporter.ImportTypeMapping(TypeModel model, String ns, ImportContext context, String dataType, XmlAttributes a, Boolean repeats, Boolean openModel, RecursionLimiter limiter)
   at System.Xml.Serialization.XmlReflectionImporter.ImportElement(TypeModel model, XmlRootAttribute root, String defaultNamespace, RecursionLimiter limiter)
   at System.Xml.Serialization.XmlReflectionImporter.ImportTypeMapping(Type type, XmlRootAttribute root, String defaultNamespace)
   at System.Xml.Serialization.XmlSerializer..ctor(Type type, String defaultNamespace)
   at Microsoft.AspNetCore.Mvc.Formatters.XmlSerializerOutputFormatter.CreateSerializer(Type type)
[21:14:31 WRN] An error occurred while trying to create an XmlSerializer for the type 'Microsoft.AspNetCore.Mvc.FileStreamResult'.
System.InvalidOperationException: Microsoft.AspNetCore.Mvc.FileStreamResult cannot be serialized because it does not have a parameterless constructor.
   at System.Xml.Serialization.TypeDesc.CheckSupported()
   at System.Xml.Serialization.TypeScope.GetTypeDesc(Type type, MemberInfo source, Boolean directReference, Boolean throwOnError)
   at System.Xml.Serialization.ModelScope.GetTypeModel(Type type, Boolean directReference)
   at System.Xml.Serialization.XmlReflectionImporter.ImportTypeMapping(Type type, XmlRootAttribute root, String defaultNamespace)
   at System.Xml.Serialization.XmlSerializer..ctor(Type type, String defaultNamespace)
   at Microsoft.AspNetCore.Mvc.Formatters.XmlSerializerOutputFormatter.CreateSerializer(Type type)
[21:14:31 INF] Application started. Press Ctrl+C to shut down.
[21:14:31 INF] Hosting environment: Development
...