1

How can I set a custom contract resolver in web api configuration? My code is relatively new and has no custom contract resolver till now.

I have added no other customization besides routing.

I tried in three different ways and none worked:

public static void Register(HttpConfiguration config)
{            
    // Web API routes
    config.MapHttpAttributeRoutes();

    config.Routes.MapHttpRoute(
        name: "DefaultApi",
        routeTemplate: "api/{controller}/{action}/{id}",
        defaults: new { id = RouteParameter.Optional }
    );

    //attempt 1
    config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CustomContractResolver();

    //attempt 2
    GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CustomContractResolver();

    //attempt 3
    JsonConvert.DefaultSettings = () => new JsonSerializerSettings
    {
        ContractResolver = new CustomContractResolver()
    };           
}

The custom contract resolver code, breakpoint never reaches here when I'm debugging:

public class CustomContractResolver : CamelCasePropertyNamesContractResolver
{
    protected override string ResolvePropertyName(string propertyName)
    {
        var regex = new Regex(@"([_])(\w)");

        if (regex.IsMatch(propertyName))
        {
            var result = regex.Replace(propertyName.ToLower(), (match) => { return match.Groups[2].Value.ToUpper(); });
            return result;
        }
        else
            return base.ResolvePropertyName(propertyName);
    }
}

Is there something that is missing?

Edit 1:

I'm using ASP.NET WebApi 5.2.1 AND MVC 5.2.7, JSON.NET (Newtonsoft.Json) v13.0.1 (and already tried the old v12)

My Global Asax is very simple as well:

public class MvcApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();
        GlobalConfiguration.Configure(WebApiConfig.Register); //<- web api configuration
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes); //<- mvc configuration
        BundleConfig.RegisterBundles(BundleTable.Bundles);
    }
}

The MVC RouteConfig class:

public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        routes.IgnoreRoute("{resource}.ashx/{*pathInfo}");

        routes.MapRoute(
            name: "Default",
            url: "{controller}/{action}/{id}",
            defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
        );
    }
}

Edit 2

Here is some test web api controllers:

using System.Web.Http;

namespace Kronos.Web.Geolocalizacao.Controllers.Api
{
    public class TestController : ApiController
    {
        [HttpGet]
        public TestModel Obtain()
        {
            return new TestModel { CODE_IDENTIFICATION = 1, DEFAULT_DESCRIPTION = "TEST DAT THING" };
        }
    }

    public class TestModel
    {
        public decimal CODE_IDENTIFICATION { get; set; }

        public string DEFAULT_DESCRIPTION { get; set; }
    }
}

Used the Tabbed Postman chrome addon to test

Postman tests

3
  • Is your issue with asp.net-web-api or asp.net-mvc-5? They use different serializers, and the serializer used changes with the version, so might you please edit your question to be specific as to exactly which version and setup you are using? Commented Mar 31, 2021 at 17:47
  • If MVC, be aware that asp.net-mvc-5 is pretty old, the first version was released in 2013. It doesn't use Json.NET by default. If you want to use Json.NET for serialization in MVC5, see Setting the default JSON serializer in ASP.NET MVC. If you want to use it for model binding, look at How to use Json.NET for JSON modelbinding in an MVC5 project?. Commented Mar 31, 2021 at 17:50
  • i'm using both, asp net mvc 5 and asp net web api, .net framework 4.8 Commented Mar 31, 2021 at 18:43

1 Answer 1

1

Your problem has nothing to do with how you are registering your global settings -- setting config.Formatters.JsonFormatter.SerializerSettings.ContractResolver is correct as per this question. Your problem is that Json.NET does not call ResolvePropertyName() when the contract resolver also has a NamingStrategy -- and your base class CamelCasePropertyNamesContractResolver does indeed have a naming strategy.

This can be verified by checking the current Json.NET reference source for DefaultContractResolver.SetPropertySettingsFromAttributes():

if (namingStrategy != null)
{
    property.PropertyName = namingStrategy.GetPropertyName(mappedName, hasSpecifiedName);
}
else
{
    property.PropertyName = ResolvePropertyName(mappedName);
}

Broken demo fiddle #1 here.

If I simply modify your CustomContractResolver to inherit from DefaultContractResolver (which has a null NamingStrategy by default), then it works:

public class CustomContractResolver : DefaultContractResolver
{
    readonly NamingStrategy baseNamingStrategy = new CamelCaseNamingStrategy();

    protected override string ResolvePropertyName(string propertyName)
    {
        var regex = new Regex(@"([_])(\w)");

        if (regex.IsMatch(propertyName))
        {
            var result = regex.Replace(propertyName.ToLower(), (match) => { return match.Groups[2].Value.ToUpper(); });
            return result;
        }
        else
            return baseNamingStrategy.GetPropertyName(propertyName, false);
    }
}

Fixed demo fiddle #2 here.

However, a cleaner solution would be to replace your custom contract resolver with a custom naming strategy:

public class CustomNamingStrategy : CamelCaseNamingStrategy
{
    public CustomNamingStrategy() : base() { }
    public CustomNamingStrategy(bool processDictionaryKeys, bool overrideSpecifiedNames) : base(processDictionaryKeys, overrideSpecifiedNames) { }
    public CustomNamingStrategy(bool processDictionaryKeys, bool overrideSpecifiedNames, bool processExtensionDataNames) : base(processDictionaryKeys, overrideSpecifiedNames, processExtensionDataNames) { }

    readonly Regex regex = new Regex(@"([_])(\w)");
    protected  override string ResolvePropertyName(string name)
    {
        if (regex.IsMatch(name))
        {
            var result = regex.Replace(name.ToLower(), (match) => { return match.Groups[2].Value.ToUpper(); });
            return result;
        }
        return base.ResolvePropertyName(name);
    }
}

And then configure it in settings like so:

settings.ContractResolver = new DefaultContractResolver
{
    // Set the constructor parameters as per your preference.  These values are consistent with CamelCasePropertyNamesContractResolver
    NamingStrategy = new CustomNamingStrategy(processDictionaryKeys: true, overrideSpecifiedNames: true),
};

Demo fiddle #3 here.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.