Asp.net mvc 4 如果返回的对象具有IList,则ASP.NET Web API无法返回XML<;T>;所有物

Asp.net mvc 4 如果返回的对象具有IList,则ASP.NET Web API无法返回XML<;T>;所有物,asp.net-mvc-4,asp.net-web-api,Asp.net Mvc 4,Asp.net Web Api,我正在使用ASP.NETWebAPI的ApiController将业务逻辑公开为Web服务。我正在测试XML和JSON,因为我们对两者都有需求,我一直在使用Fiddler进行测试。我已经把范围缩小到这样:由于某种原因,拥有IList属性会强制使用JSON,但是将属性更改为List可以使用JSON或XML。不幸的是,我需要这些来使用IList,那么我如何用IList属性的对象生成XML呢 如果我使用以下HTML标题获取http://localhost:4946/Api/MyBizLog/GetDo

我正在使用ASP.NETWebAPI的ApiController将业务逻辑公开为Web服务。我正在测试XML和JSON,因为我们对两者都有需求,我一直在使用Fiddler进行测试。我已经把范围缩小到这样:由于某种原因,拥有
IList
属性会强制使用JSON,但是将属性更改为
List
可以使用JSON或XML。不幸的是,我需要这些来使用
IList
,那么我如何用
IList
属性的对象生成XML呢

如果我使用以下HTML标题获取
http://localhost:4946/Api/MyBizLog/GetDomainObject?id=foo

Authorization: basic ***************
Accept: application/xml
Host: localhost:4946
如果抛出异常,我将返回JSON。如果我将
Content-Type
更改为
Content-Type:application/xml
,则在引发异常时会得到xml。但是,如果没有抛出异常,我总是得到JSON

我调用的方法有一个签名,如
公共虚拟MyDomainObject GetDomainObject(字符串id)

我如何让它在成功或失败时返回我要求的内容类型

我有以下WebApiConfig:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.Routes.MapHttpRoute(
            name: "AlternativeApi",
            routeTemplate: "api/{controller}/{action}",
            defaults: new { }
        );

        config.Formatters.XmlFormatter.UseXmlSerializer = true;
    }
}
更多信息

我根据@Darren Miller的建议安装了WebAPI跟踪,我得到以下信息:

iisexpress.exe Information: 0 : Message='Action returned 'DomainObjects.MyDomainObject'', Operation=ReflectedHttpActionDescriptor.ExecuteAsync
iisexpress.exe Information: 0 : Message='Will use same 'JsonMediaTypeFormatter' formatter', Operation=JsonMediaTypeFormatter.GetPerRequestFormatterInstance
iisexpress.exe Information: 0 : Message='Selected formatter='JsonMediaTypeFormatter', content-type='application/json; charset=utf-8'', Operation=DefaultContentNegotiator.Negotiate
iisexpress.exe Information: 0 : Operation=ApiControllerActionInvoker.InvokeActionAsync, Status=200 (OK)
iisexpress.exe Information: 0 : Operation=TransactionalApiFilterAttribute.ActionExecuted, Status=200 (OK)
iisexpress.exe Information: 0 : Operation=MyBizLogController.ExecuteAsync, Status=200 (OK)
iisexpress.exe Information: 0 : Response, Status=200 (OK), Method=GET, Url=http://localhost:4946/Api/MyBizLog/GetDomainObject?id=foo, Message='Content-type='application/json; charset=utf-8', content-length=unknown'
iisexpress.exe Information: 0 : Operation=JsonMediaTypeFormatter.WriteToStreamAsync
iisexpress.exe Information: 0 : Operation=MyBizLogController.Dispose
我在操作的第一行上放置了一个断点。然后我把提琴手的礼物寄了出去。当执行在断点处停止时,输出显示以下内容:

iisexpress.exe Information: 0 : Request, Method=GET, Url=http://localhost:4946/Api/MyBizLog/GetDomainObject?id=foo, Message='http://localhost:4946/Api/MyBizLog/GetDomainObject?id=foo'
iisexpress.exe Information: 0 : Message='MyBizLog', Operation=DefaultHttpControllerSelector.SelectController
iisexpress.exe Information: 0 : Message='WebApp.Api.MyBizLogController', Operation=DefaultHttpControllerActivator.Create
iisexpress.exe Information: 0 : Message='WebApp.Api.MyBizLogController', Operation=HttpControllerDescriptor.CreateController
iisexpress.exe Information: 0 : Message='Selected action 'GetDomainObject(String id)'', Operation=ApiControllerActionSelector.SelectAction
iisexpress.exe Information: 0 : Message='Parameter 'id' bound to the value 'foo'', Operation=ModelBinderParameterBinding.ExecuteBindingAsync
iisexpress.exe Information: 0 : Message='Model state is valid. Values: id=foo', Operation=HttpActionBinding.ExecuteBindingAsync
iisexpress.exe Information: 0 : Operation=TransactionalApiFilterAttribute.ActionExecuting
然后我让执行继续,我得到以下结果:

iisexpress.exe Information: 0 : Message='Action returned 'DomainObjects.MyDomainObject'', Operation=ReflectedHttpActionDescriptor.ExecuteAsync
iisexpress.exe Information: 0 : Message='Will use same 'JsonMediaTypeFormatter' formatter', Operation=JsonMediaTypeFormatter.GetPerRequestFormatterInstance
iisexpress.exe Information: 0 : Message='Selected formatter='JsonMediaTypeFormatter', content-type='application/json; charset=utf-8'', Operation=DefaultContentNegotiator.Negotiate
iisexpress.exe Information: 0 : Operation=ApiControllerActionInvoker.InvokeActionAsync, Status=200 (OK)
iisexpress.exe Information: 0 : Operation=TransactionalApiFilterAttribute.ActionExecuted, Status=200 (OK)
iisexpress.exe Information: 0 : Operation=MyBizLogController.ExecuteAsync, Status=200 (OK)
iisexpress.exe Information: 0 : Response, Status=200 (OK), Method=GET, Url=http://localhost:4946/Api/MyBizLog/GetDomainObject?id=foo, Message='Content-type='application/json; charset=utf-8', content-length=unknown'
iisexpress.exe Information: 0 : Operation=JsonMediaTypeFormatter.WriteToStreamAsync
iisexpress.exe Information: 0 : Operation=MyBizLogController.Dispose
我确实有一个
ActionFilterAttribute
,它读取基本身份验证并告诉业务逻辑层当前用户是谁,但是跳过它不会改变结果

更多信息

所以我把范围缩小到了IList和List。如果我定义作品,我就会得到XML。如果我
#define不起作用
,我就会得到JSON。这实际上是实际运行的代码

        public class Bar
        {
        }

        public class Foo
        {
#if WORKS
            public virtual List<Bar> Bars { get; set; }
#elif DOESNT_WORK
            public virtual IList<Bar> Bars { get; set; }
#endif
        }

        [HttpPost]
        [HttpGet]
        public Foo Test()
        {
            return new Foo();
        }
公共类栏
{
}
公开课Foo
{
#如果有效
公共虚拟列表栏{get;set;}
#艾利夫不工作
公共虚拟IList条{get;set;}
#恩迪夫
}
[HttpPost]
[HttpGet]
公开食物测试()
{
返回新的Foo();
}

这是因为您使用了错误的标题<代码>内容类型用于描述正在传输的有效负载。对于GET,没有有效负载,因此不需要内容类型或内容长度。您应该设置
Accept
标题,以指示您对将返回的媒体类型的偏好。

这是因为您使用了错误的标题<代码>内容类型用于描述正在传输的有效负载。对于GET,没有有效负载,因此不需要内容类型或内容长度。您应该设置
Accept
标题,以指示您对将返回的媒体类型的偏好。

@Darrel Miller给出了答案:

据我所知,XmlSerializer无法处理接口。任何一个 将属性更改为List,在您的服务器上实现IXmlSerializable 类,或使用DataContractSerializer。或者更好的是,不要试图 通过连接返回域对象


@达雷尔·米勒给出了答案:

据我所知,XmlSerializer无法处理接口。任何一个 将属性更改为List,在您的服务器上实现IXmlSerializable 类,或使用DataContractSerializer。或者更好的是,不要试图 通过连接返回域对象


正如@Patrick提到的,@Darrel的回答是正确的。我并不是在这里提出不同的答案,这只是一个整体解决方案,以防其他人在这里绊倒:

控制器:

[HttpPost]
[Route("myRoute")]
[ResponseType(typeof(MyCustomModel))]
/* Note: If your response type is of type IEnumerable, i.e. IEnumerable<MyCustomModel>, then Example in Swagger will look like this: 

<?xml version="1.0"?>
<Inline Model>
    <AttributeIdProperty>string</AttributeIdProperty>
    <PropertyForElement>string</PropertyForElement>
</Inline Model>

The real output will be correct representation, however:

<ArrayOfMyCustomModel xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<MyCustomModel AttributeIdProperty="Value for this attribute property">
    <PropertyForElement>Value for this element property</PropertyForElement>
</MyCustomModel>
</ArrayOfContentMetadata>
*/
public virtual IHttpActionResult MyMethod([FromBody]MyCustomModel myCustomModel)
{
    if (myCustomModel== null) throw new Exception("Invalid input", HttpStatusCode.BadRequest);
    return Ok(_myBusiness.MyMethod(myCustomModel);
}
Web API配置:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.MapHttpAttributeRoutes();
        config.Filters.Add(new GlobalExceptionFilter());
        //below line is what's most important for this xml serialization
        config.Formatters.XmlFormatter.UseXmlSerializer = true;
    }
}
招摇过市配置:

public class SwaggerConfig
{
    public static void Register()
    {

        var swaggerHeader = new SwaggerHeader();

        ///...

        GlobalConfiguration.Configuration
            .EnableSwagger(c =>
            {
                swaggerHeader.Apply(c);
            });
摆动式收割台:

public class SwaggerHeader : IOperationFilter
{
    public string Description { get; set; }
    public string Key { get; set; }
    public string Name { get; set; }

    public void Apply(SwaggerDocsConfig c)
    {
        c.ApiKey(Key).Name(Name).Description(Description).In("header");
        c.OperationFilter(() => this);
    }

    public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
    {
        if (!operation.produces.Contains("application/xml")) operation.produces.Add("application/xml");
        if (!operation.produces.Contains("text/xml")) operation.produces.Add("text/xml");
    }
}

正如@Patrick提到的,@Darrel的回答是正确的。我并不是在这里提出不同的答案,这只是一个整体解决方案,以防其他人在这里绊倒:

控制器:

[HttpPost]
[Route("myRoute")]
[ResponseType(typeof(MyCustomModel))]
/* Note: If your response type is of type IEnumerable, i.e. IEnumerable<MyCustomModel>, then Example in Swagger will look like this: 

<?xml version="1.0"?>
<Inline Model>
    <AttributeIdProperty>string</AttributeIdProperty>
    <PropertyForElement>string</PropertyForElement>
</Inline Model>

The real output will be correct representation, however:

<ArrayOfMyCustomModel xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<MyCustomModel AttributeIdProperty="Value for this attribute property">
    <PropertyForElement>Value for this element property</PropertyForElement>
</MyCustomModel>
</ArrayOfContentMetadata>
*/
public virtual IHttpActionResult MyMethod([FromBody]MyCustomModel myCustomModel)
{
    if (myCustomModel== null) throw new Exception("Invalid input", HttpStatusCode.BadRequest);
    return Ok(_myBusiness.MyMethod(myCustomModel);
}
Web API配置:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.MapHttpAttributeRoutes();
        config.Filters.Add(new GlobalExceptionFilter());
        //below line is what's most important for this xml serialization
        config.Formatters.XmlFormatter.UseXmlSerializer = true;
    }
}
招摇过市配置:

public class SwaggerConfig
{
    public static void Register()
    {

        var swaggerHeader = new SwaggerHeader();

        ///...

        GlobalConfiguration.Configuration
            .EnableSwagger(c =>
            {
                swaggerHeader.Apply(c);
            });
摆动式收割台:

public class SwaggerHeader : IOperationFilter
{
    public string Description { get; set; }
    public string Key { get; set; }
    public string Name { get; set; }

    public void Apply(SwaggerDocsConfig c)
    {
        c.ApiKey(Key).Name(Name).Description(Description).In("header");
        c.OperationFilter(() => this);
    }

    public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
    {
        if (!operation.produces.Contains("application/xml")) operation.produces.Add("application/xml");
        if (!operation.produces.Contains("text/xml")) operation.produces.Add("text/xml");
    }
}

Darrel——应该使用Accept,这是对的。不幸的是,使用
Accept:application/xml
会产生相同的结果。我修改了这个问题以反映您的更正,不过我认为这个问题仍然存在。@Don01001100您的域对象中很可能存在一些问题,使XML序列化程序无法呈现它。安装/启用WebAPI跟踪,这会告诉你为什么它不工作。我没有看到任何明显的问题。你能看一下吗?我将输出添加到我的问题中。@Don01001100 MyDomainObject中是否有循环?也就是说,它指向一个指向它的物体?我相信XmlSerializer会因为这个而窒息。@Don01001100据我所知,XmlSerializer无法处理接口。将属性更改为List,在类上实现IXmlSerializable,或者使用DataContractSerializer。或者更好的是,不要试图通过wire.Darrel返回域对象——应该使用Accept,这是正确的。不幸的是,使用
Accept:application/xml
会产生相同的结果。我修改了这个问题以反映您的更正,不过我认为这个问题仍然存在。@Don01001100您的域对象中很可能存在一些问题,使XML序列化程序无法呈现它。安装/启用WebAPI跟踪,这会告诉你为什么它不工作。我没有看到任何明显的问题。你能看一下吗?我将输出添加到我的问题中。@Don01001100 MyDomainObject中是否有循环?也就是说,它指向一个指向它的物体?我相信XmlSerializer会因为这个而窒息。@Don01001100据我所知,XmlSerializer无法处理接口。要么改变你的公关