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
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.
Related
I'm trying to setup Swagger for my API, I have the interface at http://localhost/myAPI/swagger, but my controllers/routes are not displayed.
I'm using .net-framework, not .net-core
Startup:
public class WebApiApplication : HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
}
}
Route config :
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { id = UrlParameter.Optional }
);
}
}
Controller:
[RoutePrefix("v1/controller")]
public class TestController : ApiController
{
[Route("client")]
[HttpPut]
public HttpResponseMessage CreateClient([FromUri] string id)
{
return new HttpResponseMessage(HttpStatusCode.OK);
}
[Route("portfolio")]
[HttpPost]
public IResponseItem<int> CreatePortfolio([FromUri] string id)
{
return new ResponseItem<int>
{
StatusCode = HttpStatusCode.Created,
Message = "Portfolio successfully created",
Item = 12
};
}
}
Swagger config :
public class SwaggerConfig
{
public static void Register()
{
var thisAssembly = typeof(SwaggerConfig).Assembly;
GlobalConfiguration.Configuration
.EnableSwagger(c =>
{
c.SingleApiVersion("v1", "WebAPI");
})
.EnableSwaggerUi(c =>
{
});
}
}
I'm new to this so I'm probably missing something
first of all, it seems you made a mistake in RoutePrefix section. If you mean by defining v1/controller to use the name of your controller dynamically in the path, you should put [] around it like this:
[RoutePrefix("v1/[Controller]")]
and about the registration. you need to remove {} from your code.your code should be like the following:
GlobalConfiguration.Configuration
.EnableSwagger(c =>c.SingleApiVersion("v1", "WebAPI"))
.EnableSwaggerUi();
I recommend to register swagger directly into the Application_Start() section.
Your final code should be like this :
public class WebApiApplication : HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configuration
.EnableSwagger(c => c.SingleApiVersion("v1", "title of your api"))
.EnableSwaggerUi();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
}
}
and the address to refer is: http://localhost:yourProgramPort/swagger/docs/v1
I hope it is helpful.
I'm struggling with ASP.NET MVC 6 (beta 4 release) trying to inject a service within a controller filter attribute of type AuthorizationFilterAttribute.
This is the service (it has another service injected)
public class UsersTableRepository
{
private readonly NeurosgarContext _dbContext;
public UsersTableRepository(NeurosgarContext DbContext)
{
_dbContext = DbContext;
}
public ICollection<User> AllUsers
{
get
{
return _dbContext.Users.ToList();
}
}
//other stuff...
}
This is the ConfigureServices method in Startup class for services enabling
public void ConfigureServices(IServiceCollection services)
{
//...
services.AddSingleton<NeurosgarContext>(a => NeurosgarContextFactory.GetContext());
services.AddSingleton<UifTableRepository<Nazione>>();
services.AddSingleton<UsersTableRepository>();
}
A simple "dummy" controller with two filters defined on it. You can notice that I already done DI inside this controller by decorating the property with [FromServices]and it works.
[Route("[controller]")]
[BasicAuthenticationFilter(Order = 0)]
[BasicAuthorizationFilter("Admin", Order = 1)]
public class DummyController : Controller
{
[FromServices]
public UsersTableRepository UsersRepository { get; set; }
// GET: /<controller>/
[Route("[action]")]
public IActionResult Index()
{
return View();
}
}
Doing the same DI within BasicAuthenticationFilterdoes not work and at runtime UserRepository property is a null reference.
public class BasicAuthenticationFilterAttribute : AuthorizationFilterAttribute
{
[FromServices]
public UsersTableRepository UsersRepository { get; set; }
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (!Authenticate(filterContext.HttpContext))
{
// 401 Response
var result = new HttpUnauthorizedResult();
// Add the header for Basic authentication require
filterContext.HttpContext.Response.Headers.Append("WWW-Authenticate", "Basic");
filterContext.Result = result;
//if (!HasAllowAnonymous(context))
//{
// base.Fail(context);
//}
}
}
// ...
}
Any idea about how solve this?
Refrain from injecting dependencies into your attributes as explained here. Make your attributes passive, or make your attribute a humble object as described here.
var dependencyScope = context.HttpContext.RequestServices;
var usersRepository = dependencyScope.GetService(typeof(UsersTableRepository)) as UsersTableRepository;
// usersRepository is now ready to be used
So your BasicAuthenticationFilter will look like this:
public class BasicAuthenticationFilterAttribute : AuthorizationFilterAttribute
{
public UsersTableRepository UsersRepository { get; set; }
public override void OnAuthorization(AuthorizationContext filterContext)
{
var dependencyScope = context.HttpContext.RequestServices;
UsersRepository = dependencyScope.GetService(typeof(UsersTableRepository)) as UsersTableRepository;
if (!Authenticate(filterContext.HttpContext))
{
// 401 Response
var result = new HttpUnauthorizedResult();
// Add the header for Basic authentication require
filterContext.HttpContext.Response.Headers.Append("WWW-Authenticate", "Basic");
filterContext.Result = result;
//if (!HasAllowAnonymous(context))
//{
// base.Fail(context);
//}
}
}
// ...
}
I'm new to NHibernate and trying to use it in ASP.NET WEB API. Firstly I used it successfully with one table named "Category" which the controller class is as follow:
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using TestMVCProject.Web.Api.HttpFetchers;
using TestMVCProject.Web.Api.Models;
using TestMVCProject.Web.Api.TypeMappers;
using TestMVCProject.Web.Common;
//using TestMVCProject.Web.Common.Security;
using NHibernate;
namespace TestMVCProject.Web.Api.Controllers
{
[LoggingNHibernateSession]
public class CategoryController : ApiController
{
private readonly ISession _session;
private readonly ICategoryMapper _categoryMapper;
private readonly IHttpCategoryFetcher _categoryFetcher;
public CategoryController(
ISession session,
ICategoryMapper categoryMapper,
IHttpCategoryFetcher categoryFetcher)
{
_session = session;
_categoryMapper = categoryMapper;
_categoryFetcher = categoryFetcher;
}
public IEnumerable<Category> Get()
{
return _session
.QueryOver<Data.Model.Category>()
.List()
.Select(_categoryMapper.CreateCategory)
.ToList();
}
public Category Get(long id)
{
var category = _categoryFetcher.GetCategory(id);
return _categoryMapper.CreateCategory(category);
}
public HttpResponseMessage Post(HttpRequestMessage request, Category category)
{
var modelCategory = new Data.Model.Category
{
Description = category.Description,
CategoryName = category.CategoryName
};
_session.Save(modelCategory);
var newCategory = _categoryMapper.CreateCategory(modelCategory);
//var href = newCategory.Links.First(x => x.Rel == "self").Href;
var response = request.CreateResponse(HttpStatusCode.Created, newCategory);
//response.Headers.Add("Location", href);
return response;
}
public HttpResponseMessage Delete()
{
var categories = _session.QueryOver<Data.Model.Category>().List();
foreach (var category in categories)
{
_session.Delete(category);
}
return new HttpResponseMessage(HttpStatusCode.OK);
}
public HttpResponseMessage Delete(long id)
{
var category = _session.Get<Data.Model.Category>(id);
if (category != null)
{
_session.Delete(category);
}
return new HttpResponseMessage(HttpStatusCode.OK);
}
public Category Put(long id, Category category)
{
var modelCateogry = _categoryFetcher.GetCategory(id);
modelCateogry.CategoryName = category.CategoryName;
modelCateogry.Description = category.Description;
_session.SaveOrUpdate(modelCateogry);
return _categoryMapper.CreateCategory(modelCateogry);
}
}
}
But when I add The "Product" table which has a foreign key of the Category table, the product controller doesn't work and throws below exception:
No session bound to the current context
ProductController class is as follow:
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using TestMVCProject.Web.Api.HttpFetchers;
using TestMVCProject.Web.Api.Models;
using TestMVCProject.Web.Api.TypeMappers;
using TestMVCProject.Web.Common;
//using TestMVCProject.Web.Common.Security;
using NHibernate;
namespace TestMVCProject.Web.Api.Controllers
{
[LoggingNHibernateSession]
public class ProductController : ApiController
{
private readonly ISession _session;
private readonly IProductMapper _productMapper;
private readonly IHttpProductFetcher _productFetcher;
public ProductController(
ISession session,
IProductMapper productMapper,
IHttpProductFetcher productFetcher)
{
_session = session;
_productMapper = productMapper;
_productFetcher = productFetcher;
}
public IEnumerable<Product> Get()
{
return _session
.QueryOver<Data.Model.Product>()
.List()
.Select(_productMapper.CreateProduct)
.ToList();
}
public Product Get(long id)
{
var product = _productFetcher.GetProduct(id);
return _productMapper.CreateProduct(product);
}
public HttpResponseMessage Post(HttpRequestMessage request, Product product)
{
var modelProduct = new Data.Model.Product
{
Description = product.Description,
ProductName = product.ProductName
};
_session.Save(modelProduct);
var newProduct = _productMapper.CreateProduct(modelProduct);
//var href = newproduct.Links.First(x => x.Rel == "self").Href;
var response = request.CreateResponse(HttpStatusCode.Created, newProduct);
//response.Headers.Add("Location", href);
return response;
}
public HttpResponseMessage Delete()
{
var categories = _session.QueryOver<Data.Model.Product>().List();
foreach (var product in categories)
{
_session.Delete(product);
}
return new HttpResponseMessage(HttpStatusCode.OK);
}
public HttpResponseMessage Delete(long id)
{
var product = _session.Get<Data.Model.Product>(id);
if (product != null)
{
_session.Delete(product);
}
return new HttpResponseMessage(HttpStatusCode.OK);
}
public Product Put(long id, Product product)
{
var modelProduct = _productFetcher.GetProduct(id);
modelProduct.ProductName = product.ProductName;
modelProduct.Description = product.Description;
_session.SaveOrUpdate(modelProduct);
return _productMapper.CreateProduct(modelProduct);
}
}
}
and the mapping class for Product table:
using TestMVCProject.Data.Model;
using FluentNHibernate.Mapping;
namespace TestMVCProject.Data.SqlServer.Mapping
{
public class ProductMap : ClassMap<Product>
{
public ProductMap()
{
Id(x => x.ProductId);
Map(x => x.ProductName).Not.Nullable();
Map(x => x.Description).Nullable();
Map(x => x.CreateDate).Not.Nullable();
Map(x => x.Price).Not.Nullable();
References<Category>(x => x.CategoryId).Not.Nullable();
}
}
}
What is wrong?
Your snippets are missing the way, how the ISessionFactory is created and how ISession is passed into your controllers... You should follow this really comprehensive story (by Piotr Walat):
NHibernate session management in ASP.NET Web API
Where you can see that we, can use 2.3. Contextual Sessions:
NHibernate.Context.WebSessionContext - stores the current session in HttpContext. You are responsible to bind and unbind an ISession instance with static methods of class CurrentSessionContext.
The configuration
<session-factory>
..
<property name="current_session_context_class">web</property>
</session-factory>
In the article you can check that we need at the app start initialize factory (just an extract):
public class WebApiApplication : System.Web.HttpApplication
{
private void InitializeSessionFactory() { ... }
protected void Application_Start()
{
InitializeSessionFactory();
...
Next we should create some AOP filter (just an extract):
public class NhSessionManagementAttribute : ActionFilterAttribute
{
...
public override void OnActionExecuting(HttpActionContext actionContext)
{
// init session
var session = SessionFactory.OpenSession();
...
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
// close session
...
session = CurrentSessionContext.Unbind(SessionFactory);
}
For more details check the source mentioned above
Your approach of passing the session to the constructor of the controller factory does not seems to be working, there are a few ways to do this
1. Using dependency injection
If you are using a dependency injection framework, you have to configure controller so that it's constructed per request, it should looks like this (I have used the code for Ninject)
Step 1 - setup the session for injection
public class DIModule : NinjectModule
{
public override void Load()
{
this.Bind<ISessionFactory>()... bind to the session factory
this.Bind<ISession>().ToMethod(ctx => ctx.Kernel.Get<ISessionFactory>().OpenSession())
.InRequestScope();
}
private ISession CreateSessionProxy(IContext ctx)
{
var session = (ISession)this.proxyGenerator.CreateInterfaceProxyWithoutTarget(typeof(ISession), new[] { typeof(ISessionImplementor) }, ctx.Kernel.Get<SessionInterceptor>());
return session;
}
}
Step 2 - Create the controller factory so that it will inject the session when resolving
public class NinjectControllerFactory : DefaultControllerFactory, IDependencyResolver
{
private IDependencyResolver _defaultResolver;
public NinjectControllerFactory(IDependencyResolver defaultResolver)
{
_defaultResolver = defaultResolver;
}
protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
return controllerType == null
? null
: (IController)DependencyKernel.Kernel.Get(controllerType);
}
public IDependencyScope BeginScope()
{
return this;
}
public object GetService(Type serviceType)
{
try
{
return DependencyKernel.Kernel.Get(serviceType);
}
catch (Exception)
{
return GetService(serviceType);
}
}
public IEnumerable<object> GetServices(Type serviceType)
{
try
{
object item = DependencyKernel.Kernel.Get(serviceType);
return new List<object>() {item};
}
catch (Exception)
{
return GetServices(serviceType);
}
}
public void Dispose()
{
}
}
Step 3 - Register the controller factory
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
var factory = new NinjectControllerFactory(GlobalConfiguration.Configuration.DependencyResolver);
ControllerBuilder.Current.SetControllerFactory(factory);
GlobalConfiguration.Configuration.DependencyResolver = factory;
}
}
Now what will happen is that when your controller is created it will inject the a new NH session per each request.
2. Using a filter
This is much simpler, but you may need to change your controllers a bit this to work,
Step 1 - Setup the correct session context for the factory
_sessionFactory = CreateConfiguration()
.ExposeConfiguration(c => c.SetProperty("current_session_context_class","web"))
.BuildSessionFactory();
Step 2 - Create the filter
public class SessionPerRequestAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var session = SessionFactory.OpenSession();
NHibernate.Context.CurrentSessionContext.Bind(session);
base.OnActionExecuting(actionContext);
}
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
var session = SessionFactory.GetCurrentSession();
session.Flush();
session.Clear();
session.Close();
base.OnActionExecuted(actionExecutedContext);
}
}
Step 3 - Register the filter in global configuration
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
//Do other config here
config.Filters.Add(new SessionPerRequestAttribute());
}
}
Step 4 - Modify your controller a bit,
public class CategoryController : ApiController
{
private readonly ICategoryMapper _categoryMapper;
private readonly IHttpCategoryFetcher _categoryFetcher;
public CategoryController(
ICategoryMapper categoryMapper,
IHttpCategoryFetcher categoryFetcher)
{
_categoryMapper = categoryMapper;
_categoryFetcher = categoryFetcher;
}
public IEnumerable<Category> Get()
{
var session = SessionFactory.GetCurrentSession();
return session
.QueryOver<Data.Model.Category>()
.List()
.Select(_categoryMapper.CreateCategory)
.ToList();
}
}
Here what happens is, when a request comes it will create a new session and it is bound to the request context and same is used for the web API method.
I am working on an ASP.NET MVC app. I am trying to create a basic API. I created my first Web API controller by right-clicking on Controllers, Add -> Controller... then choosing "Web API 2 Controller - Empty". In the controller code, I have the following:
namespace MyProject.Controllers
{
public class MyApiController : ApiController
{
public IHttpActionResult Get()
{
var results = new[]
{
new { ResultId = 1, ResultName = "Bill" },
new { ResultId = 2, ResultName = "Ted" }
};
return Ok(results);
}
}
}
When I run the app, I enter http://localhost:61549/api/myApi in the browser's address bar. Unfortunately, I get a 404. I'm just trying to create an API endpoint that returns a hard-coded set of JSON objects. I need this to test some client-side JavaScript. What am I doing wrong?
Here are how my routes are registered:
WebApiConfig.cs
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
RouteConfig.cs
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
Make sure that you have the WebApiConfig registration being called, possibly in the Global.asax Application_Start() method. Something like:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
You did not add method name at the end of call. Try this one:
http://localhost:61549/api/myapi/get
Try this approach
namespace MyProject.Controllers
{
public class MyApiController : ApiController
{
public IHttpActionResult Get()
{
var results = new List<ResultModel>
{
new ResultModel() {ResultId = 1, ResultName = "Bill"},
new ResultModel() {ResultId = 2, ResultName = "Ted"}
};
return Ok(results);
}
}
public class ResultModel
{
public int ResultId { get; set; }
public string ResultName { get; set; }
}
}
Api: http://localhost:61549/api/MyApi/get
Hope this helps.
Hy there,
I need to enforce Lowercase routes in my Web API project.
If it was an MVC project i would use something like
routes.LowercaseUrls = true;
But in Web API that property does not exists.
I tried the LowercaseRoutesMVC4 NuGet extension but my routes needs to have a custom handler so that extension does not help me.
What can I do?
This looks like it does what you need
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional },
constraints: new { url = new LowercaseRouteConstraint() }
);
}
}
public class LowercaseRouteConstraint : IRouteConstraint
{
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
var path = httpContext.Request.Url.AbsolutePath;
return path.Equals(path.ToLowerInvariant(), StringComparison.InvariantCulture);
}
}
I found this at https://gist.github.com/benfoster/3274578#file-gistfile1-cs-L4
I have json config like
public class JsonConfig
{
public static void Initiliaze(HttpConfiguration config, bool isCamelCase)
{
var json = config.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling = PreserveReferencesHandling.None;
json.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
json.SerializerSettings.Formatting = Formatting.None;
if (isCamelCase)
json.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
config.Formatters.Remove(config.Formatters.XmlFormatter);
}
}
You can call this method in WebApiConfig.cs like
JsonConfig.Initiliaze(config, true);