Search code examples
angularjsasp.net-mvcmodel-bindingangular-httpcomplextype

MVC model binding complex type that has a property of complex type collection with Angular JS


I am developing an Web application using Angular JS and ASP.NET MVC. But I am having a problem with model binding complex type. I am uploading list of files, list of string basically. In the data base, they(list of string and list of file) are the same columns and table because I save the path after file is uploaded. For string I save the same column with file path. For example, a database table has Id(int) and Data(string) columns.

Then I save the string data posted by AngularJS in the Data column, for file path, in the data column as well. But the point is, I need to remember the order. For example, user add a text field and then enter value, then add file field dynamically and then choose file, then add a text field and enter value again. So the order must be [ "text 1 value" , File , "text 2 value" ]. But the problem is we cannot bind the list of data mixed both HttpPostedFileBase for file and string for text value. So what I did was created view models like below.

    public class CreateBlogVM
    {
        [Required]
        [MaxLength(250)]
        public string Title { get; set; }
        [Required]
        public ContentModel[] TextContents { get; set; }
        [Required]
        public ContentFileModel[] Files { get; set; }
    }

    public class ContentModel
    {
        public int OrderNo { get; set; }
        public string Content { get; set; }
    }

    public class ContentFileModel
    {
        public int OrderNo { get; set; }
        public HttpPostedFileBase File { get; set; }
    }

As you can see in the above, CreateBlogVM will be the ViewModel that I am binding now. That class will have two properties of complex type- TextContents and Files that I explained above what I was doing. So to remember the order, I created a complex type with OrderNo field (Client will pass this value) as you can see above since we cannot bind list of data something like this

[HttpPostedFileBase, String, String, HttpPostedFileBase]

But the problem is when I post data from Angular js, all values are null and not binding the data. Only the "Title" value is binding.

    var textContents = new Array();
    var photoContents = new Array();

    for(var i=0; i<$scope.rows.length; i++)
    {
        if($scope.rows[i].type=="text")
        {
            var value = $scope.rows[i].value;
            if (value == "" || value == null) {
                showAlert("Text field should not be empty", "danger");
                return;
            }
            else {
                var content = { OrderNo: i, Content: value }
                textContents.push(content)
            }
        }
        else if($scope.rows[i].type=="photo")
        {
            var file = $scope.rows[i].file;
            if(file==null)
            {
                showAlert("Photo file is required", "danger");
                return;
            }
            else {
                var content = { OrderNo: i, File: file };
                photoContents.push(file);
            }
        }
    }

    var fd = new FormData();
    fd.append('Title', $scope.title);
    fd.append("TextContents", textContents);
    fd.append("Files", photoContents);

    $http.post("/Admin/Blog/Create", fd, {
        transformRequest: angular.identity,
        headers: { 'Content-Type': undefined }
    })

    .success(function () {
    })

    .error(function () {
    });

Above code is how I submit data to server. When I post data, all values are null and mvc is not binding data. But when I bind values without using complex type like this

public JsonResult Create(HttpPostedFileBase files, String contents, String title)

But if I bind like above, I cannot order the files and string contents. So what is wrong with my code? How can I bind complex data that has list of complex type object properties?


Solution

  • Change the models so that you get a direct relationship between the Content and the associated File (the Order property is unnecessary)

    public class CreateBlogVM
    {
        [Required]
        [MaxLength(250)]
        public string Title { get; set; }
        public List<FileVM> Files { get; set; }
    }
    public class FileVM
    {
        [Required]
        public string Content { get; set; }
        [Required]
        public HttpPostedFileBase Image { get; set; }
    }
    

    You can only append simple name/value pairs to FormData (not arrays of objects). In your loop, append the data using

    var fd = new FormData();
    fd.append('Title', $scope.title);
    for (var i=0; i<$scope.rows.length; i++)
    {
        ....
        var content = $scope.rows[i].value;
        fd.append('Files[' + i + '].Content', content);
        ....
        var file = $scope.rows[i].file;
        fd.append('Files[' + i + '].Image', file);
    }
    ....
    

    so that your generating the names with indexers that relate to your collection property (Files[0].Content, Files[0].Image, Files[1].Content etc)

    Then your POST method signature will be

    public JsonResult Create(CreateBlogVM model)