问题
I'm using Razor pages for my project on dotnet core 2.1, and the application doesn't seem to bind my properties correctly, the simple types (int and string types) binds correctly but not the list of complex types, is there a work around for this?
my page handler looks like this:
public async Task<IActionResult> OnGetDTResponseAsync(DataTableOptions options) {// Some Code}
When I step through with my debugger all simple type properties for "DataTableOptions options" are well populated but the complex type returns null.
my model looks like this :
public class DataTableOptions
{
public string Draw { get; set; }
public int Start { get; set; }
public int Length { get; set; }
public List<DataTableColumnOrder> Order { get; set; }
public List<DataTableColumn> Columns { get; set; }
public DataTableColumnSearch Search { get; set; }
public List<string> Params { get; set; }
public DataTableOptions() { }
public class DataTableColumn
{
public string Data { get; set; }
public string Name { get; set; }
public bool Searchable { get; set; }
public bool Orderable { get; set; }
public DataTableColumnSearch Search { get; set; }
public DataTableColumn() { }
}
public class DataTableColumnSearch
{
public string Value { get; set; }
public bool Regex { get; set; }
public DataTableColumnSearch() { }
}
public class DataTableColumnOrder
{
public int Column { get; set; }
public string Dir { get; set; }
public DataTableColumnOrder() { }
}
}
While trying to solve this, I tried using
public async Task<IActionResult> OnGetDTResponseAsync(List<Dictionary<string, string>> columns)
in my page handler in place of the columns property of DataTableOptions so i could manually bind the properties to my class: I got a full list of my columns with it's properties binded to it, except for the DataTableColumn's DataTableColumnSearch property which is also a complex type that came out as null.
public async Task<IActionResult> OnGetDTResponseAsync(List<Dictionary<string, object>> columns)
doesn't work either.
This is what the request looks like in fiddler:
GET /CMS/Index?handler=DTResponse&draw=1&columns%5B0%5D%5Bdata%5D=id&columns%5B0%5D%5Bname%5D=&columns%5B0%5D%5Bsearchable%5D=false&columns%5B0%5D%5Borderable%5D=false&columns%5B0%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B0%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B1%5D%5Bdata%5D=name&columns%5B1%5D%5Bname%5D=&columns%5B1%5D%5Bsearchable%5D=true&columns%5B1%5D%5Borderable%5D=true&columns%5B1%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B1%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B2%5D%5Bdata%5D=webPage.name&columns%5B2%5D%5Bname%5D=&columns%5B2%5D%5Bsearchable%5D=true&columns%5B2%5D%5Borderable%5D=true&columns%5B2%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B2%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B3%5D%5Bdata%5D=value&columns%5B3%5D%5Bname%5D=&columns%5B3%5D%5Bsearchable%5D=true&columns%5B3%5D%5Borderable%5D=true&columns%5B3%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B3%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B4%5D%5Bdata%5D=contentType.name&columns%5B4%5D%5Bname%5D=&columns%5B4%5D%5Bsearchable%5D=true&columns%5B4%5D%5Borderable%5D=true&columns%5B4%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B4%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B5%5D%5Bdata%5D=&columns%5B5%5D%5Bname%5D=&columns%5B5%5D%5Bsearchable%5D=false&columns%5B5%5D%5Borderable%5D=false&columns%5B5%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B5%5D%5Bsearch%5D%5Bregex%5D=false&order%5B0%5D%5Bcolumn%5D=2&order%5B0%5D%5Bdir%5D=asc&start=0&length=10&search%5Bvalue%5D=&search%5Bregex%5D=false&_=1545122652329 HTTP/1.1
回答1:
I had to build a custom model binding class to handle this scenario. For some reason a Collection List of a complex object that has another complex object as part of it's properties can't be automatically binded correctly in core 2.1 -Razor pages.
See my solution below:
using Microsoft.AspNetCore.Mvc.ModelBinding;
using RestaurantDataModel.Data.Requests;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ExampleDataModel.Data
{
public class CustomDataTableEntityBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
var allValues = bindingContext.HttpContext.Request.Query;
DataTableOptions DTOs = new DataTableOptions {
Draw = allValues.FirstOrDefault(a => a.Key == "draw").Value,
Start = Convert.ToInt32(allValues.FirstOrDefault(a => a.Key == "start").Value),
Length = Convert.ToInt32(allValues.FirstOrDefault(a => a.Key == "length").Value)
};
if (allValues.Any(a => a.Key.Length > 9 && a.Key.Substring(0, 9).Contains("columns")))
{
var myListIndex = 0;
var myListIndexComparer = 0;
var allColumns = allValues.Where(a => a.Key.Length > 9 && a.Key.Substring(0, 9).Contains("columns")).ToList();
DataTableColumn DTC = new DataTableColumn();
DataTableColumnSearch DTCS = new DataTableColumnSearch();
DTOs.columns = new List<DataTableColumn>();
foreach (var column in allColumns)
{
var perColumnArray = column.Key.Split(new char[] { '[', ']' }, StringSplitOptions.RemoveEmptyEntries);
var rawIndex = perColumnArray[1];
if (!int.TryParse(rawIndex, out myListIndex))
{
return Task.CompletedTask;
}
if (myListIndexComparer != myListIndex)
{
DTC.search = DTCS;
DTOs.columns.Add(DTC);
DTC = new DataTableColumn();
DTCS = new DataTableColumnSearch();
}
myListIndexComparer = myListIndex;
switch (perColumnArray[2])
{
case "data":
DTC.data = column.Value;
break;
case "name":
DTC.name = column.Value;
break;
case "searchable":
DTC.searchable = String.IsNullOrWhiteSpace(column.Value) ? false : Convert.ToBoolean(column.Value);
break;
case "orderable":
DTC.orderable = String.IsNullOrWhiteSpace(column.Value) ? false : Convert.ToBoolean(column.Value);
break;
case "search":
if (perColumnArray[3] == "regex")
{
DTCS.regex = String.IsNullOrWhiteSpace(column.Value) ? false : Convert.ToBoolean(column.Value);
}
if (perColumnArray[3] == "value")
{
DTCS.value = column.Value;
}
break;
}
if(allColumns.IndexOf(column) == allColumns.IndexOf(allColumns.Last()))
{
DTC.search = DTCS;
DTOs.columns.Add(DTC);
}
}
}
if (allValues.Any(a => a.Key.Length > 7 && a.Key.Substring(0, 7).Contains("order")))
{
var myListIndex = 0;
var myListIndexComparer = 0;
var allOrders = allValues.Where(a => a.Key.Length > 7 && a.Key.Substring(0, 7).Contains("order")).ToList();
DataTableColumnOrder DTCO = new DataTableColumnOrder();
DTOs.order = new List<DataTableColumnOrder>();
foreach (var order in allOrders)
{
var perColumnArray = order.Key.Split(new char[] { '[', ']' }, StringSplitOptions.RemoveEmptyEntries);
var rawIndex = perColumnArray[1];
if (!int.TryParse(rawIndex, out myListIndex))
{
return Task.CompletedTask;
}
if (myListIndexComparer != myListIndex)
{
DTOs.order.Add(DTCO);
DTCO = new DataTableColumnOrder();
}
myListIndexComparer = myListIndex;
switch (perColumnArray[2])
{
case "column":
DTCO.Column = Convert.ToInt32(order.Value);
break;
case "dir":
DTCO.Dir = order.Value;
break;
}
if(allOrders.IndexOf(order) == allOrders.IndexOf(allOrders.Last()))
{
DTOs.order.Add(DTCO);
}
}
}
if (allValues.Any(a => a.Key.Length > 7 && a.Key.Substring(0, 8).Contains("search")))
{
var allSearches = allValues.Where(a => a.Key.Length > 8 && a.Key.Substring(0, 8).Contains("search")).ToList();
DataTableColumnSearch DTCS = new DataTableColumnSearch();
DTOs.search = new DataTableColumnSearch();
foreach (var search in allSearches)
{
var perColumnArray = search.Key.Split(new char[] { '[', ']' }, StringSplitOptions.RemoveEmptyEntries);
switch (perColumnArray[1])
{
case "value":
DTCS.value = search.Value;
break;
case "regex":
DTCS.regex = String.IsNullOrWhiteSpace(search.Value) ? false : Convert.ToBoolean(search.Value);
break;
}
if(allSearches.IndexOf(search) == allSearches.IndexOf(allSearches.Last()))
{
DTOs.search = DTCS;
}
}
}
bindingContext.Result = ModelBindingResult.Success(DTOs);
return Task.CompletedTask;
}
}
}
And then I added this to the top of my model class:
[ModelBinder(BinderType = typeof(CustomDataTableEntityBinder))]
来源:https://stackoverflow.com/questions/53840449/issue-with-binding-a-list-of-complex-type-object-dotnet-core-2-1-razor-pages