Wai Yan Hein Wai Yan Hein - 20 days ago 5
AngularJS Question

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. Please let me explain what I am doing first. 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 angular js 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 please what is wrong with my code? How can I bind complext data that has list of complex type object properties please? Please help me.

Answer

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 you 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', content);
}
....

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)
Comments