*Update:
Was able to follow this tutorial, and now the error I'm getting states:
"message": "No HTTP resource was found that matches the request URI 'http://localhost:0000/api/v1/Pets('dog')/Color'."
"message": "No routing convention was found to select an action for the OData path with template '~/entityset/key/unresolved'."
Any ideas?*
I am getting an error when trying to retrieve the Color in my Pet query using OData V4.
I'm having quite a bit of trouble, ideally I would use an expand on color (e.g. localhost:0000/api/v1/Pets('dog')?$expand=Colors)
The JSON I need returned is something like:
[
{
"_Key": "1",
"animalname": "dog",
"furcolorname": "black,white",
"color": {
"_Key": "1",
"colorname": "black"
},
{
"_Key": "2",
"colorname": "white"
}
}
]
Maybe I'm on the completely wrong path, either way any input is appreciated!
If I query localhost:0000/api/v1/Pets('dog') :
…
"Message\": \"Invalid column name 'Pet__Key'.\"
…
If I query localhost:0000/api/v1/Pets('dog')?$exand=Colors :
…
"The query specified in the URI is not valid. Could not find a property named 'Colors' on type 'PetShop.Odata.Models.Pet'."
…
Pet.cs model:
namespace PetShop.Odata.Models
{
[Table("pet")]
public class Pet
{
[Key]
public string _Key { get; set; }
public string AnimalName { get; set; }
[ForeignKey("Color")]
public string FurColorName { get; set; }
public virtual Color Color { get; set; }
public virtual ICollection<Color> Colors { get; set; }
}
}
Color.cs model:
namespace PetShop.Odata.Models
{
[Table("color")]
public class Color
{
public Color()
{
new HashSet<Pet>();
}
[Key]
public string _Key { get; set; }
public string ColorName { get; set; }
}
}
PetsController.cs:
namespace PetShop.Odata.Controllers
{
[ApiVersion("1.0")]
[ODataRoutePrefix("Pets")]
[ApiControllerMetricReport]
public class PetsController : ODataController
{
private readonly MyContext context = new MyContext ();
[HttpGet]
[ODataRoute]
[EnableQuery]
[ResponseType(typeof(List<Pet>))]
public IHttpActionResult Get()
{
return Ok(context.Pets.AsQueryable());
}
[EnableQuery]
public IQueryable<Color> Get ([FromODataUri] string key)
{
return context.Pets.Where(m => m._Key == key).SelectMany(a => a.Colors);
}
protected override void Dispose(bool disposing)
{
context.Dispose();
base.Dispose(disposing);
}
}
}
ColorsController.cs:
namespace PetShop.Odata.Controllers
{
[ApiVersion("1.0")]
[ODataRoutePrefix("Colors")]
[ApiControllerMetricReport]
public class ColorsController : ODataController
{
private readonly MyContext context = new MyContext ();
[HttpGet]
[ODataRoute]
[EnableQuery]
[ResponseType(typeof(List<Color>))]
public IHttpActionResult Get()
{
return Ok(context.Colors.AsQueryable());
}
protected override void Dispose(bool disposing)
{
context.Dispose();
base.Dispose(disposing);
}
}
}
PetConfig.cs:
namespace PetShop.Odata.Configuration
{ public class PetModelConfiguration : IModelConfiguration
{
public void Apply(ODataModelBuilder builder, ApiVersion apiVersion)
{
builder.EntitySet<Pet>("Pets");
}
}
}
ColorConfig.cs:
namespace PetShop.Odata.Configuration
{ public class ColorModelConfiguration : IModelConfiguration
{
public void Apply(ODataModelBuilder builder, ApiVersion apiVersion)
{
builder.EntitySet<Color>("Colors");
}
}
}
MyContext.cs:
…
public DbSet<Pet> Pets { get; set; }
public DbSet<Color> Colors { get; set; }
…
Setup.cs:
…
public static HttpServer CreateHttpServer()
{
var httpConfig = new HttpConfiguration();
var webApiServer = new HttpServer(httpConfig);
httpConfig.AddApiVersioning(options => options.ReportApiVersions = true);
httpConfig.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
var modelBuilder = new VersionedODataModelBuilder(httpConfig)
{
ModelBuilderFactory = () => new ODataConventionModelBuilder().EnableLowerCamelCase(),
ModelConfigurations =
{
new PetConfig(),
new ColorConfig()
},
};
var models = modelBuilder.GetEdmModels();
httpConfig.Count().Filter().OrderBy().Expand().Select().MaxTop(null);
…
Assuming you are using default routing convention, your Get colors method in PetsController.cs does not match the expected OData route format.
Instead of the following:
[EnableQuery]
public IQueryable<Color> Get ([FromODataUri] string key)
{
return context.Pets.Where(m => m._Key == key).SelectMany(a => a.Colors);
}
You should try:
[EnableQuery]
public IQueryable<Color> GetColors ([FromODataUri] string key)
{
return context.Pets.Where(m => m._Key == key).SelectMany(a => a.Colors);
}
This definition would make the OData route: http://localhost:0000/api/v1/Pets('dog')/Colors
For more information on routing conventions see https://learn.microsoft.com/en-us/odata/webapi/built-in-routing-conventions.
A couple alternative approaches exist as well:
Register a different name to a custom nav property
Define a custom OData Function
Related
I need to deserialize jsons to a type that contains a property of interface type - IExceptionModel. I prescribe maps for interfaces to classes like this:
public static class JsonSerialization
{
public static T FromJson<T>(this string obj) => JsonConvert.DeserializeObject<T>(obj, Settings);
public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
{
ContractResolver = new ContractResolver()
};
private class ContractResolver : DefaultContractResolver
{
protected override JsonContract CreateContract(Type objectType)
{
var result = base.CreateContract(objectType);
if (objectType == typeof(IExceptionModel))
{
result.CreatedType = typeof(ExceptionModel);
result.DefaultCreator = () => result.CreatedType.GetConstructor(new Type[0]).Invoke(null);
}
return result;
}
}
}
Here are my interface and class types:
public interface IExceptionModel : IModelBase
{
public string Message { get; set; }
public int Index { get; set; }
}
public class ExceptionModel : IExceptionModel
{
public string Message { get ; set ; }
public byte Index { get; set; }
}
Here is the class to deserialize to:
public class Status
{
public IExceptionModel Error { get; set; }
}
When I take a proper input string like:
{
"Error" : {
"Message": "Error",
"Index": 404
}
}
and feed it to FromJson<Status> method, described above, I get Error property set to null, although I believe I have resolved the contract for the interface.
What else do I need to do in order to make it work?
Update.
When preparing this example, I messed some details. The IExceptionModel Error property doesn't have setter on the interface. It does on implementation. So now, when I have added setter to the interface, the property ends up with the needed value. If I wipe it, it has null after deserialization.
So the question turns into, how do I tell Newtonsoft Serializer to use the constructor of the implementation, use ITS getters and setters to fill what properties it can and only then treat it as the interface instance?
I found a workaround to assign an internal setter to the interface property and then instruct:
jsonContract.DefaultCreatorNonPublic = true;
But it makes the interface look crooked, to say the least.
I made some corrections and this worked for me:
result.CreatedType = typeof(Status); --> result.CreatedType = typeof(ExceptionModel);
public byte Index { get; set; } --> public int Index { get; set; }
I uploaded this online example: https://dotnetfiddle.net/ETSJee
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
public interface IModelBase {}
public interface IExceptionModel : IModelBase
{
public string Message { get; set; }
public int Index { get; set; }
}
public class ExceptionModel : IExceptionModel
{
public string Message { get ; set ; }
public int Index { get; set; }
}
public class Status
{
public IExceptionModel Error { get; set; }
}
public static class JsonSerialization
{
public static T FromJson<T>(this string obj)
{
return JsonConvert.DeserializeObject<T>(obj, Settings);
}
public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
{
ContractResolver = new ContractResolver()
};
private class ContractResolver : DefaultContractResolver
{
protected override JsonContract CreateContract(Type objectType)
{
var result = base.CreateContract(objectType);
if (objectType == typeof(IExceptionModel))
{
result.CreatedType = typeof(ExceptionModel);
result.DefaultCreator = () => result.CreatedType.GetConstructor(new Type[0]).Invoke(null);
}
return result;
}
}
}
class Program
{
static void Main(string[] args)
{
var txt = #"
{
'Error' : {
'Message': 'Error',
'Index': 404
}
}
";
var obj = JsonSerialization.FromJson<Status>(txt);
Console.WriteLine(obj.Error.Index);
Console.WriteLine(obj.Error.Message);
}
}
this works for me without any contract resolvers
var status = JsonConvert.DeserializeObject<Status>(txt);
public class Status
{
public IExceptionModel Error { get; set; }
[JsonConstructor]
public Status (ExceptionModel error) {
Error=error;
}
public Status() {}
}
if you need to use it in many classes you can use this code instead
public class Status
{
[JsonProperty("Error")]
private ExceptionModel _error
{
set { Error = value; }
get { return (ExceptionModel)Error; }
}
[JsonIgnore]
public IExceptionModel Error { get; set; }
}
test
var status = JsonConvert.DeserializeObject<MyClass>(txt);
Console.WriteLine(status.Error.Index); //404
Console.WriteLine(status.Error.Message); //Error
public class MyClass:Status
{
public string Name {get; set;}
}
I'm struggling to find a way to catch an exception thrown by a model property (actually by its type struct), which must be bound to a POST request body data.
I have a general scenario where I need to treat very specific data types, so I'm using structs to validate them accordingly each case.
Despite of the following codes are just drafts, all suggestions are very welcome!
So the following is an example of a Controller:
[ApiController]
[TypeFilter(typeof(CustomExceptionFilter))]
public class OrdersController : ControllerBase
{
public OrdersController(ILogger<OrdersController> logger, IDataAccess dataAccess)
{
_dataAccess = dataAccess;
_logger = logger;
}
private readonly IDataAccess _dataAccess;
private readonly ILogger<OrdersController> _logger;
[EnableCors]
[Route("api/[controller]/Sales")]
[HttpPost]
public async Task<ActionResult> PostSaleAsync(
[FromBody] SaleOrder saleOrder)
{
try
{
Guid saleOrderId = Guid.NewGuid();
saleOrder.SaleOrderId = saleOrderId;
foreach (SaleOrderItem item in saleOrder.items)
item.SaleOrderId = saleOrderId;
OrderQuery query = new OrderQuery(_dataAccess);
await query.SaveAsync(saleOrder);
_dataAccess.Commit();
var response = new
{
Error = false,
Message = "OK",
Data = new
{
SaleOrderId = saleOrderId
}
};
return Ok(response);
}
catch (DataAccessException)
{
_dataAccess.Rollback();
//[...]
}
//[...]
}
}
and an example of a model, Order, and a struct, StockItemSerialNumber:
public class SaleOrder : Order
{
public Guid SaleOrderId { get => OrderId; set => OrderId = value; }
public Guid CustomerId { get => StakeholderId; set => StakeholderId = value; }
public Guid? SellerId { get; set; }
public SaleModelType SaleModelType { get; set; }
public SaleOrderItem[] items { get; set; }
}
public class SaleOrderItem : OrderItem
{
public Guid SaleOrderId { get; set; }
public StockItemSerialNumber StockItemSerialNumber { get; set; }
//[JsonConverter(typeof(StockItemSerialNumberJsonConverter))]
//public StockItemSerialNumber? StockItemSerialNumber { get; set; }
}
public struct StockItemSerialNumber
{
public StockItemSerialNumber(string value)
{
try
{
if ((value.Length != 68) || Regex.IsMatch(value, #"[^\w]"))
throw new ArgumentOutOfRangeException("StockItemSerialNumber");
_value = value;
}
catch(RegexMatchTimeoutException)
{
throw new ArgumentOutOfRangeException("StockItemSerialNumber");
}
}
private string _value;
public static implicit operator string(StockItemSerialNumber value) => value._value;
public override string ToString() => _value;
}
I would like to catch ArgumentOutOfRangeException thrown by StockItemSerialNumber struct and then return a response message informing a custom error accordingly.
Since this exception is not catch by the try...catch block from Controller, I've tried to build a class that extends IExceptionFilter and add as a filter:
public class CustomExceptionFilter : IExceptionFilter
{
private readonly IWebHostEnvironment _hostingEnvironment;
private readonly IModelMetadataProvider _modelMetadataProvider;
public CustomExceptionFilter(
IWebHostEnvironment hostingEnvironment,
IModelMetadataProvider modelMetadataProvider)
{
_hostingEnvironment = hostingEnvironment;
_modelMetadataProvider = modelMetadataProvider;
}
public void OnException(ExceptionContext context)
{
context.Result = new BadRequestObjectResult(new {
Error = false,
Message = $"OPS! Something bad happened, Harry :( [{context.Exception}]."
});
}
}
Startup.cs :
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
// [...]
});
services.AddTransient<IDataAccess>(_ => new DataAccess(Config.DBCredentials));
services.AddControllers(options => options.Filters.Add(typeof(CustomExceptionFilter)));
}
But that approach also doesn't work, I mean, the ArgumentOutOfRangeException remains not being catch and crashes the execution during a POST request.
Finally, here's an example of a JSON with request data:
{
"CustomerId":"fb2b0555-6d32-404b-b2f0-50032a7e0f59",
"SellerId":null,
"items": [
{
"StockItemSerialNumber":"22B6E75510AB459B8DB2874F20C722B6F3DC19C6E474337D5F73BB87699E9A1001"
}, // Invalid
{
"StockItemSerialNumber":"022B6E755122B659B8DB2874F20C780030F3DC19C6E47465AS1673BB87699E9A1001"
} // Valid
]
}
So I appreciate any help or suggestion! Thanks!
I have an ASP.Net Core 2.1 application.
Below is my DTO
public class Movie
{
public int Id { get; set;}
public bool IsSpecial {get; set;}
public IEnumerable<Ticket> Tickets { get; set; }
public Movie()
{
if(IsSpecial)
{
this.Tickets = new List<TicketSpecial>();
}
else
{
this.Tickets = new List<Ticket>();
}
}}}
Tickets (Base Class)
public class Ticket
{
public int Id { get; set;}
public string Name { get; set;}
public decimal price { get; set;}
}
TicketsSpecial (Child/Derived Class)
public class TicketsSpecial : Ticket
{
public string SpecialProp1 { get; set;}
public string SpecialProp2 { get; set;}
}
WebAPI Controller
public class MovieController : ControllerBase
{
public IActionResult Post([FromBody]Movie movie)
{
}
}
Postman (HTTPPost Req payload Content-Type = application/json)
{
"IsSpecial": true,
"SpecialProp1": "Mumbai Test",
}
When I call the above API via Postman & debug at Movie ctor, it always catches the value of IsSpecial = false & all fields default value (ex. for string type null)
Thanks!
There are two issues in your current implementation. First, your request json is invalid for nested properties and json deserializer would not deserialize for you with TicketsSpecial.
Follow steps below for a workaround:
Movie
public class Movie
{
public int Id { get; set; }
public bool IsSpecial { get; set; }
public IEnumerable<Ticket> Tickets { get; set; }
}
MyJsonInputFormatter
public class MyJsonInputFormatter : JsonInputFormatter
{
public MyJsonInputFormatter(ILogger logger, JsonSerializerSettings serializerSettings, ArrayPool<char> charPool, ObjectPoolProvider objectPoolProvider, MvcOptions options, MvcJsonOptions jsonOptions) : base(logger, serializerSettings, charPool, objectPoolProvider, options, jsonOptions)
{
}
public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
{
var result = await base.ReadRequestBodyAsync(context);
if (result.Model is Movie movie && movie.IsSpecial)
{
context.HttpContext.Request.Body.Position = 0;
string request = await new StreamReader(context.HttpContext.Request.Body).ReadToEndAsync();
var tickets = JObject.Parse(request)["Tickets"].ToObject<List<TicketSpecial>>();
movie.Tickets = tickets;
}
return result;
}
}
Register MyJsonInputFormatter
services.AddMvc(o =>
{
var serviceProvider = services.BuildServiceProvider();
var customJsonInputFormatter = new MyJsonInputFormatter(
serviceProvider.GetRequiredService<ILoggerFactory>().CreateLogger<MyJsonInputFormatter>(),
serviceProvider.GetRequiredService<IOptions<MvcJsonOptions>>().Value.SerializerSettings,
serviceProvider.GetRequiredService<ArrayPool<char>>(),
serviceProvider.GetRequiredService<ObjectPoolProvider>(),
o,
serviceProvider.GetRequiredService<IOptions<MvcJsonOptions>>().Value
);
o.InputFormatters.Insert(0, customJsonInputFormatter);
})
Request
{
"IsSpecial": true,
"Tickets": [
{"SpecialProp1": "Mumbai Test"}
]
}
Change "Isspecial" to "isSpecial", same with the other property.
Another problem is that you're checking "IsSpecial" in the constructor and at this time it should be false anyway.
I would like to know if entity framework automatically tracks changes in navigational properties for an entity and also updates those changes when the related entity is updated using SaveChanges(). I am using a generic repository, unit of work, ninject with code first entity framework 5 in MVC 4. I have a post and tags tables that has a many-to-many relationship. I have a join table for this relationship, named PostTagMap, with two columns, Post_id and Tag_id. When I add a new post with its associated tags, the new post is successfully added to the post table and the PostTagMap also saves the tags associated with this post. However, when I try to edit/update the post and its associated tags, only the scalar properties of the post gets updated and not the associated tags. I tried debugging and I find that the post entity to be updated does have the updated values for the tags navigational property but entity framework only generates the update statement for the post table and not for the PostTagMap. What should I do to update the tags navigational property for the post as well.
Following are the classes used
DBContext class
public class EFDbContext : DbContext
{
public DbSet<Post> Posts { get; set; }
public DbSet<Tag> Tags { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Post>()
.HasMany<Tag>(p => p.Tags)
.WithMany(u => u.Posts)
.Map(m =>
{
m.ToTable("PostTagMap");
m.MapLeftKey("Post_id");
m.MapRightKey("Tag_id");
});
}
}
Post class
[Table("Post")]
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
}
Tag class
[Table("Tag")]
public class Tag
{
public int Id { get; set; }
public string Name { get; set; }
[JsonIgnore]
public ICollection<Post> Posts { get; set; }
}
Generic repository that implements IGenericRepository
public class EFGenericRepository<T> : IGenericRepository<T> where T : class
{
private DbContext DbContext { get; set; }
private DbSet<T> DbSet { get; set; }
public virtual void Add(T newEntity)
{
DbSet.Add(newEntity);
}
public virtual void Update(T entityToUpdate)
{
DbSet.Attach(entityToUpdate);
DbContext.Entry(entityToUpdate).State = EntityState.Modified;
}
}
Unit of work that implements IUnitOfWork
public class EfUnitOfWork : IUnitOfWork
{
private EFDbContext dbContext;
private IGenericRepository<Post> postRepository;
private IGenericRepository<Tag> tagRepository;
public EfUnitOfWork()
{
this.dbContext = new EFDbContext();
}
public void Commit()
{
dbContext.SaveChanges();
}
public IGenericRepository<Post> PostRepository
{
get
{
if (this.postRepository == null)
{
this.postRepository = new EFGenericRepository<Post>(dbContext);
}
return postRepository;
}
}
public IGenericRepository<Tag> TagRepository
{
get
{
if (this.tagRepository == null)
{
this.tagRepository = new EFGenericRepository<Tag>(dbContext);
}
return tagRepository;
}
}
private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
dbContext.Dispose();
}
}
this.disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
service layer
public class ServiceRepository : IServiceRepository
{
private readonly IUnitOfWork UoW;
public ServiceRepository(IUnitOfWork _UoW)
{
UoW = _UoW;
}
public int AddPost(Post post)
{
UoW.PostRepository.Add(post);
UoW.Commit();
return post.Id;
}
public void EditPost(Post post)
{
UoW.PostRepository.Update(post);
UoW.Commit();
}
}
admin controller
public class AdminController : Controller
{
private readonly IServiceRepository _serviceRepository;
public AdminController(IServiceRepository serviceRepository)
{
_serviceRepository = serviceRepository;
}
[HttpPost, ValidateInput(false)]
public ContentResult AddPost(Post post)
{
string json;
ModelState.Clear();
if (TryValidateModel(post))
{
var id = _serviceRepository.AddPost(post);
json = JsonConvert.SerializeObject(new
{
id = id,
success = true,
message = "Post added successfully"
});
}
else
{
json = JsonConvert.SerializeObject(new
{
id = 0,
success = false,
message = "Post was not created"
});
}
return Content(json, "application/json");
}
[HttpPost, ValidateInput(false)]
public ContentResult EditPost(Post post)
{
string json;
ModelState.Clear();
if (TryValidateModel(post))
{
_serviceRepository.EditPost(post, Tags);
json = JsonConvert.SerializeObject(new
{
id = post.Id,
success = true,
message = "Post updated successfully"
});
}
else
{
json = JsonConvert.SerializeObject(new
{
id = 0,
success = false,
message = "Failed to save the changes."
});
}
return Content(json, "application/json");
}
}
I am also using a custom model binder for Post which is as follows
public class PostModelBinder : DefaultModelBinder
{
private readonly IKernel _kernel;
public PostModelBinder(IKernel kernel)
{
_kernel = kernel;
}
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var post = (Post)base.BindModel(controllerContext, bindingContext);
var _blogRepository = _kernel.Get<IServiceRepository>();
}
var tags = bindingContext.ValueProvider.GetValue("Tags").AttemptedValue.Split(',');
if (tags.Length > 0)
{
post.Tags = new List<Tag>();
foreach (var tag in tags)
{
post.Tags.Add(_serviceRepository.Tag( int.Parse(tag.Trim()) ));
}
}
return post;
}
I'm posting json with variables names with underscores (like_this) and attempting to bind to a model that is camelcased (LikeThis), but the values are unable to be bound.
I know I could write a custom model binder, but since the underscored convention is so common I'd expect that a solution already existed.
The action/model I'm trying to post to is:
/* in controller */
[HttpPost]
public ActionResult UpdateArgLevel(UserArgLevelModel model) {
// do something with the data
}
/* model */
public class UserArgLevelModel {
public int Id { get; set; }
public string FirstName { get; set; }
public string Surname { get; set; }
public int ArgLevelId { get; set; }
}
and the json data is like:
{
id: 420007,
first_name: "Marc",
surname: "Priddes",
arg_level_id: 4
}
(Unfortunately I can't change either the naming of either the json or the model)
You can start writing a custom Json.NET ContractResolver:
public class DeliminatorSeparatedPropertyNamesContractResolver :
DefaultContractResolver
{
private readonly string _separator;
protected DeliminatorSeparatedPropertyNamesContractResolver(char separator)
: base(true)
{
_separator = separator.ToString();
}
protected override string ResolvePropertyName(string propertyName)
{
var parts = new List<string>();
var currentWord = new StringBuilder();
foreach (var c in propertyName)
{
if (char.IsUpper(c) && currentWord.Length > 0)
{
parts.Add(currentWord.ToString());
currentWord.Clear();
}
currentWord.Append(char.ToLower(c));
}
if (currentWord.Length > 0)
{
parts.Add(currentWord.ToString());
}
return string.Join(_separator, parts.ToArray());
}
}
This is for your particular case, becase you need a snake case ContractResolver:
public class SnakeCasePropertyNamesContractResolver :
DeliminatorSeparatedPropertyNamesContractResolver
{
public SnakeCasePropertyNamesContractResolver() : base('_') { }
}
Then you can write a custom attribute to decorate your controller actions:
public class JsonFilterAttribute : ActionFilterAttribute
{
public string Parameter { get; set; }
public Type JsonDataType { get; set; }
public JsonSerializerSettings Settings { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.HttpContext.Request.ContentType.Contains("application/json"))
{
string inputContent;
using (var reader = new StreamReader(filterContext.HttpContext.Request.InputStream))
{
inputContent = reader.ReadToEnd();
}
var result = JsonConvert.DeserializeObject(inputContent, JsonDataType, Settings ?? new JsonSerializerSettings());
filterContext.ActionParameters[Parameter] = result;
}
}
}
And finally:
[JsonFilter(Parameter = "model", JsonDataType = typeof(UserArgLevelModel), Settings = new JsonSerializerSettings { ContractResolver = new SnakeCasePropertyNamesContractResolver() })]
public ActionResult UpdateArgLevel(UserArgLevelModel model) {
{
// model is deserialized correctly!
}