简体   繁体   中英

passing multiple objects as parameters to mvc 6 action

I have an MVC 5 project that is working fine, and I need to migrate that project to MVC 6 with.net core. After managing to tweak everything to work I got stuck with a problem: Many of my actions accept more than 1 object as a parameter. The model binder MVC 5 is using has no problem with that, but MVC 6 seems to place null in all the parameters of such actions, I guess it's part of the unification of MVC and WebAPI. My question is if there is anyway around it

for example:

    [HttpPost]
    public ActionResult GetVersionData(OvlEnvironment environment, Pipeline pipeline)
    {
        BL.SetEnviromentVersion(pipeline, environment);
        return PartialView("_Version", environment);
    }

On the mvc 5 project ajax requests containing json data in the form

{ "environment" : {*Data...*},
  "pipeline" : {*Data...*}
}

were accepted. In mvc 6 both objects in response to the same request appear to be null.
Thanks

You have to give the ASP.NET Core MVC Framework the hint, that data to bind to the model is to be found in the body of the post request. This is done via the [FromBody] attribute .

[FromBody]: Use the configured formatters to bind data from the request body. The formatter is selected based on content type of the request.

By design it is not possible to bind multiple parameters to one JSON source as it is described here . In order to avoid additional model classes you can use a generic JObject like this:

[HttpPost]
public ActionResult GetVersionData([FromBody] JObject data)
{
    var environment = data["environment"].ToObject<OvlEnvironment>();
    var pipline = data["pipeline"].ToObject<Pipeline>();

    BL.SetEnviromentVersion(pipeline, environment);
    return PartialView("_Version", environment);
}

I know this post is old, but I also ran into this problem.

I think the accepted answer is right, in ASP.NET MVC5 it was possible to seek the HttpRequest.InputStream, but this is not allowed anymore in ASP.NET Core 2. So the work-around could be to read the stream once and store the parsed JObject in the ModelState ( https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.modelbinding.modelstatedictionary?view=aspnetcore-2.1 ). Simple tests show that this approach could work.

This is an example of such an approach:

public class JsonMultiParameterModelBinder : IModelBinder
{
    private static IModelBinder DefaultModelBinder;
    private const string JSONKEY = "json_body";

    public JsonMultiParameterModelBinder(IModelBinder defaultModelBinder)
    {
        DefaultModelBinder = defaultModelBinder;
    }

    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        var httpContext = bindingContext.HttpContext;
        var stream = bindingContext.HttpContext.Request.Body;
        if (httpContext.Request.ContentType.Contains("application/json"))
        {
            JObject jobject = null;
            ModelStateEntry entry = null;
            bindingContext.ModelState.TryGetValue(JSONKEY, out entry);
            if (entry == null)
            {

                using (var readStream = new StreamReader(stream, Encoding.UTF8))
                using (var jsonReader = new JsonTextReader(readStream))
                {
                    jobject = JObject.Load(jsonReader);
                }

                bindingContext.ModelState.SetModelValue(JSONKEY, jobject, null);
            }
            else
            {
                jobject = entry.RawValue as JObject;
            }
            var jobj = jobject[bindingContext.FieldName];

            if (jobj == null)
            {
                httpContext.Response.StatusCode = 500; // error has occured
                throw new JsonReaderException(string.Format("The rootnode '{0}' is not found in the Json message.", bindingContext.ModelName));
            }

            object obj = null;

            var JSONSerializer = new JsonSerializer();

            try
            {
                obj = jobj.ToObject(bindingContext.ModelType, JSONSerializer);
                bindingContext.Model = obj;
                bindingContext.Result = ModelBindingResult.Success(obj);
                return Task.CompletedTask;
            }
            catch (Exception ex)
            {
                httpContext.Response.StatusCode = 500; // error has occured
                throw ex;
            }

        }
        return DefaultModelBinder.BindModelAsync(bindingContext);
    }
}

And the provider for this binder :

public class JsonMultiParameterModelBinderProvider : IModelBinderProvider
{
    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        if (context == null) throw new ArgumentNullException(nameof(context));

        if (context.Metadata.IsComplexType)
        {
            ComplexTypeModelBinderProvider p = new ComplexTypeModelBinderProvider();

            return new JsonMultiParameterModelBinder(p.GetBinder(context));
        }
        return null;
    }
}

Register in Startup.cs:

        services.AddMvc(options =>
        {
            options.ModelBinderProviders.Insert(0, new JsonMultiParameterModelBinderProvider());
        }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM