1

我正在使用 ASP.NET Web API 的 ApiController 将业务逻辑公开为 Web 服务。我正在测试 XML 和 JSON,因为我们对两者都有需求,而且我一直在使用 Fiddler 进行测试。我已将其缩小为:IList<T>由于某种原因,拥有一个属性会强制使用 JSON,但将属性更改为List<T>允许 JSON 或 XML。不幸的是,我需要这些来使用IList<T>,那么我怎样才能从具有IList<T>属性的对象中生成 XML 呢?

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

Authorization: basic ***************
Accept: application/xml
Host: localhost:4946

如果抛出异常,我会返回 JSON。如果我更改Content-TypeContent-Type: application/xml,如果抛出异常,我会得到 XML。但是,如果没有抛出异常,我总是会得到 JSON。

我正在调用的方法有一个签名,如public virtual MyDomainObject GetDomainObject(String 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 跟踪,得到以下信息:

我在动作的第一行设置了一个断点。然后我从 Fiddler 发送了 GET。当执行在断点处停止时,输出显示如下:

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。如果我#define WORKS,我会得到 XML。如果我#define DOESNT_WORK,我会得到 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();
        }
4

3 回答 3

3

那是因为您使用了错误的标题。 Content-Type用于描述您正在传输的有效负载。在 GET 的情况下,没有有效负载,因此不需要 Content-Type 或 Content-Length。您应该设置Accept标题以指示您对将返回的媒体类型的偏好。

于 2013-06-14T17:41:34.903 回答
3

@Darrel Miller 有答案:

据我了解, XmlSerializer 无法处理接口。将您的属性更改为 List<>,在您的类上实现 IXmlSerializable,或使用 DataContractSerializer。或者更好的是,不要尝试通过网络返回域对象。

于 2015-02-20T21:55:36.800 回答
1

就像@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);
}

模型:

public class MyCustomModel : IXmlSerializable
{
    [JsonProperty("attributeIdProperty ")]
    [XmlAttribute("AttributeIdProperty")]
    public string AttributeIdProperty { get; set; }

    [JsonProperty("propertyForElement ")]
    [XmlElement("PropertyForElement ")]
    public string PropertyForElement { get; set; }

    public XmlSchema GetSchema()
    {
        return null;
    }

    public void ReadXml(XmlReader reader)
    {
        if (reader.MoveToContent() == XmlNodeType.Element && reader.LocalName == "MyCustomModel")
        {
            AttributeIdProperty = reader["AttributeIdProperty"];
            PropertyForElement = reader["PropertyForElement"];
            reader.Read();
        }
    }

    public void WriteXml(XmlWriter writer)
    {
        writer.WriteAttributeString("AttributeIdProperty", AttributeIdProperty);
        writer.WriteElementString("PropertyForElement", PropertyForElement);
    }
}

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");
    }
}
于 2018-10-31T21:53:31.457 回答