Patrick Laramee
Patrick Laramee

Reputation: 171

ASP.NET Core [FromBody] vs MVC 5 binding

I got an MVC 5 application that i'm porting to asp.net Core.

In the MVC application call to controller we're made using AngularJS $resource (sending JSON) and we we're POSTing data doing :

ressource.save({ entries: vm.entries, projectId: vm.project.id }).$promise...

that will send a JSON body like:

{
  entries: 
  [
    {
      // lots of fields
    }
  ],
  projectId:12
}

the MVC controller looked like this :

[HttpPost]
public JsonResult Save(List<EntryViewModel> entries, int projectId) {
// code here
}

How can I replicate the same behaviour with .NET Core since we can't have multiple [FromBody]

Upvotes: 3

Views: 2589

Answers (3)

Patrick Laramee
Patrick Laramee

Reputation: 171

It's still rough but I made a Filter to mimic the feature.

public class OldMVCFilter : IActionFilter
{
    public void OnActionExecuted(ActionExecutedContext context)
    {
    }
    public void OnActionExecuting(ActionExecutingContext context)
    {
        if (context.HttpContext.Request.Method != "GET")
        {
            var body = context.HttpContext.Request.Body;
            JToken token = null;
            var param = context.ActionDescriptor.Parameters;

            using (var reader = new StreamReader(body))
            using (var jsonReader = new JsonTextReader(reader))
            {
                jsonReader.CloseInput = false;
                token = JToken.Load(jsonReader);
            }
            if (token != null)
            {
                var serializer = new JsonSerializer();
                serializer.DefaultValueHandling = DefaultValueHandling.Populate;
                serializer.FloatFormatHandling = FloatFormatHandling.DefaultValue;

                foreach (var item in param)
                {
                    JToken model = token[item.Name];

                    if (model == null)
                    {
                        // try to cast the full body as the current object
                        model = token.Root;
                    }

                    if (model != null)
                    {
                        model = this.RemoveEmptyChildren(model, item.ParameterType);
                        var res = model.ToObject(item.ParameterType, serializer);
                        context.ActionArguments[item.Name] = res;
                    }
                }
            }
        }
    }
    private JToken RemoveEmptyChildren(JToken token, Type type)
    {
        var HasBaseType = type.GenericTypeArguments.Count() > 0;
        List<PropertyInfo> PIList = new List<PropertyInfo>();
        if (HasBaseType)
        {
            PIList.AddRange(type.GenericTypeArguments.FirstOrDefault().GetProperties().ToList());
        }
        else
        {
            PIList.AddRange(type.GetTypeInfo().GetProperties().ToList());
        }

        if (token != null)
        {
            if (token.Type == JTokenType.Object)
            {
                JObject copy = new JObject();
                foreach (JProperty jProp in token.Children<JProperty>())
                {
                    var pi = PIList.FirstOrDefault(p => p.Name == jProp.Name);
                    if (pi != null) // If destination type dont have this property we ignore it
                    {
                        JToken child = jProp.Value;
                        if (child.HasValues)
                        {
                            child = RemoveEmptyChildren(child, pi.PropertyType);
                        }
                        if (!IsEmpty(child))
                        {
                            if (child.Type == JTokenType.Object || child.Type == JTokenType.Array)
                            {
                                // nested value has been checked, we add the object
                                copy.Add(jProp.Name, child);
                            }
                            else
                            {
                                if (!pi.Name.ToLowerInvariant().Contains("string"))
                                {
                                    // ignore empty value when type is not string
                                    var Val = (string)child;
                                    if (!string.IsNullOrWhiteSpace(Val))
                                    {
                                        // we add the property only if it contain meningfull data
                                        copy.Add(jProp.Name, child);
                                    }
                                }
                            }
                        }
                    }
                }
                return copy;
            }
            else if (token.Type == JTokenType.Array)
            {
                JArray copy = new JArray();
                foreach (JToken item in token.Children())
                {
                    JToken child = item;
                    if (child.HasValues)
                    {
                        child = RemoveEmptyChildren(child, type);
                    }
                    if (!IsEmpty(child))
                    {
                        copy.Add(child);
                    }
                }
                return copy;
            }
            return token;
        }
        return null;
    }

    private bool IsEmpty(JToken token)
    {
        return (token.Type == JTokenType.Null || token.Type == JTokenType.Undefined);
    }
}

Upvotes: 0

bashkan
bashkan

Reputation: 464

you cannot have multiple parameter with the FromBody attibute in an action method. If that is need, use a complex type such as a class with properties equivalent to the parameter or dynamic type like that

[HttpPost("save/{projectId}")]
public JsonResult Save(int projectId, [FromBody] dynamic entries) {
// code here
}

Upvotes: 3

Karel Tamayo
Karel Tamayo

Reputation: 3750

As pointed out in the comment, one possible solution is to unify the properties you're posting onto a single model class.

Something like the following should do the trick:

public class SaveModel
{
    public List<EntryViewModel> Entries{get;set;}

    public int ProjectId {get;set;}
}

Don't forget to decorate the model with the [FromBody] attribute:

[HttpPost]
public JsonResult Save([FromBody]SaveViewModel model) 
{
    // code here
}

Hope this helps!

Upvotes: 2

Related Questions