brunozell / jsonmodelbinder Goto Github PK
View Code? Open in Web Editor NEWAn explicit json model binder to allow json serialized parts in a multipart-formdata request of a .NET-Core controller action.
License: MIT License
An explicit json model binder to allow json serialized parts in a multipart-formdata request of a .NET-Core controller action.
License: MIT License
Hi, I think declaring this library as Microsoft.AspNetCore.Mvc.ModelBinding
is not "fair".
This makes programmers understand something wrong.
Hi,
I saw the reference to this library at a StackOverflow question.
Does this library work with dot net core 2, as I always get null value for the parameter decorated with
the [ModelBinder(BinderType = typeof(JsonModelBinder))]
controller action method.
I had created an empty project and then installed the library via Nuget.
Im trying with your solution but I have a problem. Model is always null. Only image file was read correctly. This is post request which is send from my video camera after license plate is recognized:
POST /alpv/receive.php HTTP/1.1\r\n Host: 10.13.24.13\r\n Content-Length: 7160\r\n Content-Type: multipart/form-data; boundary=------------------------3df4e8eee91da308\r\n --------------------------3df4e8eee91da308 Content-Disposition: form-data; name="event"; filename="20190204103017_10536event_7000175.json" Content-Type: application/octet-stream { "packetCounter":"7000175", "datetime":"20190204 103017000", "plateText":"\u004c\u0042\u0045\u0033\u0039\u0037", "plateCountry":"SWE", "plateConfidence":"0.716159", "carState":"new", "geotag":{"lat": 50.418114,"lon": 30.476213}, "imageType": "plate", "plateImageType": "png", "plateImageSize": "0", "carMoveDirection":"in", "timeProcessing":"0", "plateCoordinates":[1614, 880, 172, 28], "carID":"18", "GEOtarget":"Camera", "sensorProviderID":"defaultID" } --------------------------3df4e8eee91da308 Content-Disposition: form-data; name="image"; filename="20190204103016_999776lp_LBE397_7000175.png" Content-Type: application/octet-stream <IMAGE DATA> --------------------------3df4e8eee91da308--
There are two files: "image" and "event".
In controller I write this:
public IActionResult NewEvent([ModelBinder(BinderType = typeof(JsonModelBinder))] DataFromCameraModel @event, IFormFile image) { return Ok(); }
and DataFromCameraModel
public class DataFromCameraModel { public int packetCounter {get;set;} public string Datetime {get;set;} public string plateText {get;set;} }
Please, make a model validation optional.
As it is a slow operation via Reflection + LINQ etc.
It would be great to have bool IsValidateModel property in the binding constructor.
Looks like the Model's don't participate in the Data Validation system, when we custom Model Bind.
I added some code so that Data Model Validation would work:
public class JsonModelBinder : IModelBinder
{
private readonly MvcJsonOptions _options;
public JsonModelBinder(IOptions<MvcJsonOptions> options) =>
_options = options.Value;
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
// Test if a value is received
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (valueProviderResult != ValueProviderResult.None)
{
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);
// Deserialize from string
string serialized = valueProviderResult.FirstValue;
// Use custom json options defined in startup if available
object deserialized = _options?.SerializerSettings == null ?
JsonConvert.DeserializeObject(serialized, bindingContext.ModelType) :
JsonConvert.DeserializeObject(serialized, bindingContext.ModelType, _options.SerializerSettings);
// DataAnnotation Validation. Validate Properties and Fields.
var validationResultProps = from prop in TypeDescriptor.GetProperties(deserialized).Cast<PropertyDescriptor>()
from attribute in prop.Attributes.OfType<ValidationAttribute>()
where !attribute.IsValid(prop.GetValue(deserialized))
select new { Propertie = prop.Name, ErrorMessage = attribute.FormatErrorMessage(string.Empty) };
var validationResultFields = from field in TypeDescriptor.GetReflectionType(deserialized).GetFields().Cast<FieldInfo>()
from attribute in field.GetCustomAttributes<ValidationAttribute>()
where !attribute.IsValid(field.GetValue(deserialized))
select new { Propertie = field.Name, ErrorMessage = attribute.FormatErrorMessage(string.Empty) };
var errors = validationResultFields.Concat(validationResultFields);
// Add the ValidationResult's to the ModelState
foreach (var validationResultItem in errors)
bindingContext.ModelState.AddModelError(validationResultItem.Propertie, validationResultItem.ErrorMessage);
// Set successful binding result
bindingContext.Result = ModelBindingResult.Success(deserialized);
#if NET451
return Task.FromResult(0);
#else
return Task.CompletedTask;
#endif
}
#if NET451
return Task.FromResult(0);
#else
return Task.CompletedTask;
#endif
}
}
I hacked this together from various internet posts. I wonder if there isn't an easier way to just ask the deserialzied object to validate itself?
For now, I iterate the Properties and Fields, and call the ValidationAttribute.IsValid against the data.
For example: 1 parameter acquired but value is empty, it turn unhanded exception and Controller not invoked.
If add in JsonModelBinder.cs: 43 fix below, Controller will be invoked and parameter == null as is.
// Add null check for parameter
if (string.IsNullOrEmpty(serialized))
{
bindingContext.Result = ModelBindingResult.Success(null);
return Task.CompletedTask;
}
System.ArgumentNullException: Value cannot be null. (Parameter 'instance') at System.ComponentModel.TypeDescriptor.GetReflectionType(Object instance) at BrunoZell.ModelBinding.JsonModelBinder.BindModelAsync(ModelBindingContext bindingContext) in C:\Users\b.yanish\source\repos\JsonModelBinder\src\JsonModelB inder\JsonModelBinder.cs:line 59 at Microsoft.AspNetCore.Mvc.ModelBinding.Binders.BinderTypeModelBinder.BindMo delAsync(ModelBindingContext bindingContext) at Microsoft.AspNetCore.Mvc.ModelBinding.ParameterBinder.BindModelAsync(Actio nContext actionContext, IModelBinder modelBinder, IValueProvider valueProvider, ParameterDescriptor parameter, ModelMetadata metadata, Object value) at Microsoft.AspNetCore.Mvc.Controllers.ControllerBinderDelegateProvider.<>c_ _DisplayClass0_0.<<CreateBinderDelegate>g__Bind|0>d.MoveNext() --- End of stack trace from previous location where exception was thrown --- at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInn erFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, St ate next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourc eFilter>g__Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scop e scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceEx ecutedContextSealed context) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipel ineAsync>g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Sco pe scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Aw aited|17_0(ResourceInvoker invoker, Task task, IDisposable scope) at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTas k|6_0(Endpoint endpoint, Task requestTask, ILogger logger) at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpCont ext context) at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.Proces sRequests[TContext](IHttpApplication
1 application)
`
First of all, thank you very much for this code.
I just wanted to let you know there is a bug in your code.
This line:
var errors = validationResultFields.Concat(validationResultFields);
should be
var errors = validationResultFields.Concat(validationResultProps);
Again, thank you!
.NET Core 3.0 introduces a new platform native Json deserializer. This is used by default by ASP.NET Core 3.0. This package, however, does imply you use Newtonsoft.Json
and we blindly inject IOptions<MvcNewtonsoftJsonOptions>
.
This is suboptimal as you cannot use this package with the new default Json deserializer and needs to be addressed.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.