Web API 2:如何返回带有驼峰命名属性名称的JSON,包括对象及其子对象

115

更新

感谢所有的答案。我现在正在一个新项目上工作,看起来我终于找到了问题的原因:实际上以下代码是有问题的:

public static HttpResponseMessage GetHttpSuccessResponse(object response, HttpStatusCode code = HttpStatusCode.OK)
{
    return new HttpResponseMessage()
    {
        StatusCode = code,
        Content = response != null ? new JsonContent(response) : null
    };
}

其他地方...

public JsonContent(object obj)
{
    var encoded = JsonConvert.SerializeObject(obj, Newtonsoft.Json.Formatting.None, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore } );
    _value = JObject.Parse(encoded);

    Headers.ContentType = new MediaTypeHeaderValue("application/json");
}

我曾经忽略了看似无害的JsonContent,认为它是WebAPI,但事实并非如此。

这现在到处都在用......我能不能成为第一个说"wtf"的人?或者也许应该是"为什么他们要这样做?"


以下是原始问题

人们可能会认为这是一个简单的配置设置,但是我已经追寻了太久了。

我查看了各种解决方案和答案:

https://gist.github.com/rdingwall/2012642

似乎不适用于最新的WebAPI版本......

以下方法似乎无效-属性名称仍然是PascalCased。

var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;

json.UseDataContractJsonSerializer = true;
json.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;

json.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); 

Mayank在这里的回答:CamelCase JSON WebAPI子对象(嵌套对象,子对象)似乎是一个不太令人满意但可行的答案,直到我意识到这些属性需要添加到生成的代码中,因为我们正在使用linq2sql...

是否有自动完成这个步骤的方法?这个'讨厌的问题'困扰我很久了。


http://www.matskarlsson.se/blog/serialize-net-objects-as-camelcase-json - Aron
还有一个原因是为什么Linq2SQL会生成部分类。另外...Linq2SQL到底怎么了?! - Aron
1
谢谢,但这个链接是关于MVC的,我正在使用Web API 2,并不确定是否有一种方法可以像这样设置内容类型并返回一个字符串,但如果有的话,它似乎不是完全正确的解决方案。感谢您提供关于部分类的提示,但是在另一个部分中定义的属性上添加属性是否可能? - Tom
还有,linq2sql 真是让人摸不着头脑... 不是我的决定 :) - Tom
结果是相同的,唯一的区别在于您注入JsonSerializer的位置。https://dev59.com/K2Yr5IYBdhLWcg3w7uhx - Aron
是的,这是可能的,但不是在C#中。你可能想使用某种编译时AOP(如PostSharp或Fody)来实现它。 - Aron
9个回答

183

将所有内容综合起来,你会得到...

protected void Application_Start()
{
    HttpConfiguration config = GlobalConfiguration.Configuration;
    config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
    config.Formatters.JsonFormatter.UseDataContractJsonSerializer = false;
}

肯定是打开的方法,但我的问题是这个设置被忽略了(请参见我的答案)。 - Tom
1
@Tom 嗯... Tom 你知道 json.UseDataContractJsonSerializer = true; 是什么意思吗?它告诉 WebAPI 不要使用 Json.Net 进行序列化。>_< - Aron
是的,我现在知道了。然而,还有一个额外的问题。我进行了验证。请查看我的答案。同时,请参见https://dev59.com/514b5IYBdhLWcg3w5VT9#28553295。 - Tom
1
实际上,经过仔细检查,我发现之前我的结论是错误的。请看我的更新。 - Tom

31

以下是对我有效的方法:

internal static class ViewHelpers
{
    public static JsonSerializerSettings CamelCase
    {
        get
        {
            return new JsonSerializerSettings {
                ContractResolver = new CamelCasePropertyNamesContractResolver()
            };
        }
    }
}

然后:

[HttpGet]
[Route("api/campaign/list")]
public IHttpActionResult ListExistingCampaigns()
{
    var domainResults = _campaignService.ListExistingCampaigns();
    return Json(domainResults, ViewHelpers.CamelCase);
}

CamelCasePropertyNamesContractResolver来自Json.NET库中的Newtonsoft.Json.dll


3
当想要在应用程序中仅对某些API使用camelCasing时,此方法非常有用。 - droidbot

16

结果证明

return Json(result);

原因在于 culprit,导致序列化过程忽略了驼峰命名设置。而那个

return Request.CreateResponse(HttpStatusCode.OK, result, Request.GetConfiguration());

这就是我在找的那个机器人。

另外

json.UseDataContractJsonSerializer = true;

往机器中投入扳手结果并不是我在寻找的那个机器。


这实际上是错误的答案。请看我在问题中更新的内容。 - Tom
我实际上发现了这个问题。当返回 Json(result) 时,所有的内容都是 PascalCase 格式,但是当我返回 Content(StatusCode, result) 时,它按预期工作。 - DeeKayy90

14

对于使用Owin Hosting和Ninject的我,以上所有答案都没有起作用。这是对我有效的方法:

public partial class Startup
{
    public void Configuration(IAppBuilder app)
    {
        // Get the ninject kernel from our IoC.
        var kernel = IoC.GetKernel();

        var config = new HttpConfiguration();

        // More config settings and OWIN middleware goes here.

        // Configure camel case json results.
        ConfigureCamelCase(config);

        // Use ninject middleware.
        app.UseNinjectMiddleware(() => kernel);

        // Use ninject web api.
        app.UseNinjectWebApi(config);
    }

    /// <summary>
    /// Configure all JSON responses to have camel case property names.
    /// </summary>
    private void ConfigureCamelCase(HttpConfiguration config)
    {
        var jsonFormatter = config.Formatters.JsonFormatter;
        // This next line is not required for it to work, but here for completeness - ignore data contracts.
        jsonFormatter.UseDataContractJsonSerializer = false;
        var settings = jsonFormatter.SerializerSettings;
#if DEBUG
        // Pretty json for developers.
        settings.Formatting = Formatting.Indented;
#else
        settings.Formatting = Formatting.None;
#endif
        settings.ContractResolver = new CamelCasePropertyNamesContractResolver();
    }
}

关键区别是:使用 new HttpConfiguration() 而不是 GlobalConfiguration.Configuration。


对于通过OWIN进行自托管,这非常完美。谢谢! - Julian Melville
4
如果你正在使用 Owin,这个解决方案完美适用,但只有在你拔光了所有的头发之后才行! - Alastair

14

WebApiConfig的代码:

    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // Web API configuration and services
    
            // Web API routes
            config.MapHttpAttributeRoutes();
    
            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
    
            //This line sets json serializer's ContractResolver to CamelCasePropertyNamesContractResolver, 
            //  so API will return json using camel case
            config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
    
        }
    }
请确保您的API操作方法以以下方式返回数据,并且已安装最新版本的Json.Net/Newtonsoft.Json:
    [HttpGet]
    public HttpResponseMessage List()
    {
        try
        {
            var result = /*write code to fetch your result - type can be anything*/;
            return Request.CreateResponse(HttpStatusCode.OK, result);
        }
        catch (Exception ex)
        {
            return Request.CreateResponse(HttpStatusCode.InternalServerError, ex.Message);
        }
    }

在2023年,使用.NET Framework 4.7.2与WebAPI 2(具有所有最新的nuggets),config.Formatters.JsonFormatter行是我所需要做的一切,就可以将所有内容转换为驼峰命名法(甚至Swagger OpenAPI也自动识别了驼峰命名配置)。谢谢! - timmi4sa

4
在你的Owin启动代码中添加这一行...
 public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        var webApiConfiguration = ConfigureWebApi();            
        app.UseWebApi(webApiConfiguration);
    }

    private HttpConfiguration ConfigureWebApi()
    {
        var config = new HttpConfiguration();

        // ADD THIS LINE HERE AND DONE
        config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); 

        config.MapHttpAttributeRoutes();
        return config;
    }
}

3

这是一个比较晦涩的问题,当路由属性与GET url不匹配,但GET url与方法名匹配时,jsonserializer驼峰指令会被忽略,例如:

http://website/api/geo/geodata

//uppercase fail cakes
[HttpGet]
[Route("countries")]
public async Task<GeoData> GeoData()
{
    return await geoService.GetGeoData();
}

//lowercase nomnomnom cakes
[HttpGet]
[Route("geodata")]
public async Task<GeoData> GeoData()
{
    return await geoService.GetGeoData();
}

2
我用以下方式解决了这个问题。
[AllowAnonymous]
[HttpGet()]
public HttpResponseMessage GetAllItems(int moduleId)
{
    HttpConfiguration config = new HttpConfiguration();
            config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
            config.Formatters.JsonFormatter.UseDataContractJsonSerializer = false;

            try
            {
                List<ItemInfo> itemList = GetItemsFromDatabase(moduleId);
                return Request.CreateResponse(HttpStatusCode.OK, itemList, config);
            }
            catch (System.Exception ex)
            {
                return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex.Message);
            }
}

1

我正在使用带有Breeze的WebApi,当尝试在breeze控制器中执行非breeze操作时遇到了相同的问题。我尝试使用Request.GetConfiguration方法,但结果相同。因此,当我访问由Request.GetConfiguration返回的对象时,我意识到请求使用的序列化程序是breeze-server用于实现其功能的程序。无论如何,我通过创建不同的HttpConfiguration解决了我的问题:

public static HttpConfiguration BreezeControllerCamelCase
        {
            get
            {
                var config = new HttpConfiguration();
                var jsonSerializerSettings = config.Formatters.JsonFormatter.SerializerSettings;
                jsonSerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
                jsonSerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
                config.Formatters.JsonFormatter.UseDataContractJsonSerializer = false;

                return config;
            }
        }

将其作为参数传递给Request.CreateResponse,如下所示:
return this.Request.CreateResponse(HttpStatusCode.OK, result, WebApiHelper.BreezeControllerCamelCase);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接