WebAPIContrib icon indicating copy to clipboard operation
WebAPIContrib copied to clipboard

MvcActionValueBinder Execption in post

Open allnnde opened this issue 8 years ago • 9 comments

Hi, i have a problem in angular when send data in post method, in my client

this.prueba = function (id, nombre) {
        return $http.post(ApiURL + "AEmpleado/prueba", { id: id, nombre: nombre })
            .then(
                function (respuesta) {
                    MensajeServices.MostarMensaje("", "Se elimino el Registro Correctamente", "success", null);
                    //vm.CargarTabla();
                    return respuesta
                },
                function (respuesta) {
                    console.log(respuesta.data.Message);
                    return false;
                }
        );
}

in my server

[RoutePrefix("api/AEmpleado")]
[MvcStyleBinding]
public class AEmpleadoController : ApiController
{   
    [HttpPost, Route("prueba")]
    public IHttpActionResult prueba(int id, string nombre)
    {
        return Ok();
    }
}

The error is:

Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'System.Net.Http.Formatting.FormDataCollection' because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly. To fix this error either change the JSON to a JSON array (e.g. [1,2,3]) or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List<T>) that can be deserialized from a JSON object. JsonObjectAttribute can also be added to the type to force it to deserialize from a JSON object. Path 'id', line 1, position 6.

in

private class MvcActionBinding : HttpActionBinding
{
    // Read the body upfront , add as a ValueProvider
    public override Task ExecuteBindingAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
    {
        HttpRequestMessage request = actionContext.ControllerContext.Request;
        HttpContent content = request.Content;
        if (content != null)
        {
            FormDataCollection fd = content.ReadAsAsync<FormDataCollection>().Result;
            if (fd != null)
            {
                IValueProvider vp = new NameValuePairsValueProvider(fd, CultureInfo.InvariantCulture);
                request.Properties.Add(Key, vp);
            }
        }

        return base.ExecuteBindingAsync(actionContext, cancellationToken);
    }
}

anybody help? Sorry for my english

allnnde avatar Dec 06 '16 21:12 allnnde

I see inconsistence: you're using [MvcStyleBinding] or [MvcActionBinding] or both ?

abatishchev avatar Dec 06 '16 21:12 abatishchev

in the IHttpActionResult ? i'm use [MvcStyleBinding]

allnnde avatar Dec 07 '16 02:12 allnnde

But the error occurs in private class MvcActionBinding?

abatishchev avatar Dec 07 '16 03:12 abatishchev

Yes, the complete class is:

public class MvcActionValueBinder : DefaultActionValueBinder
 {
        // Per-request storage, uses the Request.Properties bag. We need a unique key into the bag.
        private const string Key = "{5DC187FB-BFA0-462A-AB93-9E8036871EC8}";

        public override HttpActionBinding GetBinding(HttpActionDescriptor actionDescriptor)
        {
            var actionBinding = new MvcActionBinding();

            HttpParameterDescriptor[] parameters = actionDescriptor.GetParameters().ToArray();
            HttpParameterBinding[] binders = Array.ConvertAll(parameters, DetermineBinding);

            actionBinding.ParameterBindings = binders;

            return actionBinding;
        }

        private HttpParameterBinding DetermineBinding(HttpParameterDescriptor parameter)
        {
            HttpConfiguration config = parameter.Configuration;
            var attr = new ModelBinderAttribute(); // use default settings

            ModelBinderProvider provider = attr.GetModelBinderProvider(config);
            IModelBinder binder = provider.GetBinder(config, parameter.ParameterType);

            // Alternatively, we could put this ValueProviderFactory in the global config.
            var vpfs = new List<ValueProviderFactory>(attr.GetValueProviderFactories(config)) { new BodyValueProviderFactory() };
            return new ModelBinderParameterBinding(parameter, binder, vpfs);
        }

        // Derive from ActionBinding so that we have a chance to read the body once and then share that with all the parameters.
        private class MvcActionBinding : HttpActionBinding
        {
            // Read the body upfront , add as a ValueProvider
            public override Task ExecuteBindingAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
            {
                HttpRequestMessage request = actionContext.ControllerContext.Request;
                HttpContent content = request.Content;
                if (content != null)
                {
                    FormDataCollection fd = content.ReadAsAsync<FormDataCollection>().Result;
                    if (fd != null)
                    {
                        IValueProvider vp = new NameValuePairsValueProvider(fd, CultureInfo.InvariantCulture);
                        request.Properties.Add(Key, vp);
                    }
                }

                return base.ExecuteBindingAsync(actionContext, cancellationToken);
            }
        }

        // Get a value provider over the body. This can be shared by all parameters.
        // This gets the values computed in MvcActionBinding.
        private class BodyValueProviderFactory : ValueProviderFactory
        {
            public override IValueProvider GetValueProvider(HttpActionContext actionContext)
            {
                object vp;
                actionContext.Request.Properties.TryGetValue(Key, out vp);
                return (IValueProvider)vp; // can be null 
            }
        }
    }

allnnde avatar Dec 07 '16 12:12 allnnde

hello, i'm chage

FormDataCollection fd = content.ReadAsAsync<FormDataCollection>().Result;

for

var fd = content.ReadAsAsync<IDictionary<string, object>>().Result;

and it work but doesn't when send array :(

allnnde avatar Dec 07 '16 12:12 allnnde

btw don't use Result, use async/await instead

abatishchev avatar Dec 07 '16 16:12 abatishchev

when? in the content.ReadAsAsync<IDictionary<string, object>>().Result?

allnnde avatar Dec 07 '16 18:12 allnnde

It's unrelated to the problem you're facing but yes, there and elsewhere, calling the Result property blocks current thread so you're not having async operation spite it looks like you will. If shortly, this is incorrect way to use it.

abatishchev avatar Dec 07 '16 18:12 abatishchev

Regarding your problem, i frankly don't know, I'm not familiar with this part of the codebase. Please ask a question on Stack Overflow and provide a link to the question here, I'll assist if you're facing any issues there

abatishchev avatar Dec 07 '16 18:12 abatishchev