问题
I am using DevExpress control within an ASP.NET MVC 4 project.
I am using unbound columns in the GridView
extension for ASP.NET MVC. In the CustomUnboundColumnData
event handler, the e.GetListSourceFieldValue
always returns null for me.
I then tried getting the value directly from the model instead of calling this method (please see the commented line just above the call to the method) but even though that worked, it had side-effects, which I won't get into just now.
I am using ASP.NET MVC 4 with Visual Web Developer Express 2010 edition. I am using DevExpress extensions for MVC v12.2.10.0.
My OS is Windows 7, 64-bit. However, the extensions I am using are 32-bit only.
I cannot ship my whole solution as it is broken down into multiple projects, most of which have lots of IP code I've written for my client. But here are the relevant pieces from my code.
Index.cshtml (Razor View Engine) -------------------------------------------------------------------
@model List<GlobalizationUI.Presentation.ViewModels.StringTableRow>
@{
ViewBag.Title = "Strings";
}
<div id = "pageCaption">Strings</div>
@Html.Partial("_StringsPartial", Model)
_StringsPartial.cshtml (Razor View Engine) -------------------------------------------------------------------
@using System.Web.UI.WebControls;
@using System.Data;
@model List<GlobalizationUI.Presentation.ViewModels.StringTableRow>
@Html.DevExpress().GridView(settings =>
{
settings.Name = "gvStrings";
settings.CallbackRouteValues = new { Controller = "Strings", Action = "StringsPartial" };
settings.Width = 1200;
settings.SettingsPager.Position = PagerPosition.TopAndBottom;
settings.SettingsPager.FirstPageButton.Visible = true;
settings.SettingsPager.LastPageButton.Visible = true;
settings.SettingsPager.PageSizeItemSettings.Visible = true;
settings.SettingsPager.PageSizeItemSettings.Items = new string[] { "10", "20", "50", "100", "200" };
settings.SettingsPager.PageSize = 50;
settings.Settings.ShowFilterRow = true;
settings.Settings.ShowFilterRowMenu = true;
settings.CommandColumn.Visible = true;
settings.CommandColumn.ClearFilterButton.Visible = true;
settings.Settings.ShowHeaderFilterButton = true;
settings.KeyFieldName = "ResourceKeyId";
settings.Columns.Add("Key");
var categoryColumn = settings.Columns.Add("CategoryId", "Category");
categoryColumn.ColumnType = MVCxGridViewColumnType.ComboBox;
var categoryColumnEditProperties = categoryColumn.PropertiesEdit as ComboBoxProperties;
categoryColumnEditProperties.DataSource = ViewBag.AllCategories;
categoryColumnEditProperties.TextField = "Name";
categoryColumnEditProperties.ValueField = "Id";
categoryColumnEditProperties.ValueType = typeof(long);
if (Model != null && Model.Count > 0 &&
Model[0] != null && Model[0].StringValues != null && Model[0].StringValues.Count > 0)
{
foreach (var kvp in Model[0].StringValues)
{
settings.Columns.Add(col =>
{
col.FieldName = kvp.CultureShortName;
col.Caption = kvp.CultureShortName;
col.UnboundType = DevExpress.Data.UnboundColumnType.Object;
col.SetDataItemTemplateContent(container => { ViewContext.Writer.Write(DataBinder.Eval(container.DataItem, col.FieldName + ".StringValue")); });
col.SetEditItemTemplateContent(container =>
{
Html.DevExpress().TextBox(s =>
{
s.Name = string.Format("txt{0}", kvp.CultureShortName);
}).Bind(kvp.StringValue).Render();
});
});
}
}
settings.CustomUnboundColumnData = (sender, e) =>
{
var fixedColumns = new List<string> { "ResourceKeyId", "Key", "CategoryId" };
if (!fixedColumns.Contains(e.Column.FieldName))
{
if (e.IsGetData)
{
try
{
// var values = Model[e.ListSourceRowIndex].StringValues;
var values = e.GetListSourceFieldValue(e.ListSourceRowIndex, "StringValues") as IList<GlobalizationUI.Presentation.ViewModels.CultureNameAndStringValue>;
if (values != null)
{
var value = values.FirstOrDefault(pair => pair.CultureShortName == e.Column.FieldName);
var defaultValue = default(GlobalizationUI.Presentation.ViewModels.CultureNameAndStringValue);
e.Value = value.Equals(defaultValue) ? defaultValue : new GlobalizationUI.Presentation.ViewModels.CultureNameAndStringValue();
}
}
catch (Exception ex)
{
System.Diagnostics.Debugger.Break();
System.Diagnostics.Debug.Print(ex.ToString());
}
}
}
};
foreach (GridViewDataColumn column in settings.Columns)
{
column.Settings.HeaderFilterMode = HeaderFilterMode.CheckedList;
}
settings.SettingsEditing.AddNewRowRouteValues = new { Controller = "Strings", Action = "CreateNew" };
settings.SettingsEditing.UpdateRowRouteValues = new { Controller = "Strings", Action = "Edit" };
settings.SettingsEditing.DeleteRowRouteValues = new { Controller = "Strings", Action = "Delete" };
settings.SettingsEditing.Mode = GridViewEditingMode.Inline;
settings.SettingsBehavior.ConfirmDelete = true;
settings.CommandColumn.Visible = true;
settings.CommandColumn.NewButton.Visible = true;
settings.CommandColumn.EditButton.Visible = true;
settings.CommandColumn.UpdateButton.Visible = true;
settings.CommandColumn.DeleteButton.Visible = true;
}).Bind(Model).GetHtml()
StringsController -------------------------------------------------------------------
using System.Data;
using System.Web.Mvc;
using GlobalizationUI.BusinessObjects;
using Resources.BaseServices.Caching;
using System.Collections.Generic;
using System.ComponentModel;
using GlobalizationUI.Presentation.ViewModels;
using Resources.Util;
namespace GlobalizationUI.Presentation.Controllers
{
public class StringsController : Controller
{
private static string CacheKey_StringTable = "CacheKey_StringTable";
private static string CacheKey_AllCategories = "CacheKey_AllCategories";
private static object padLock = new object();
public ActionResult Index()
{
var stringTable = GetStringTable();
ViewBag.AllCategories = GetCategoryList();
return View(stringTable);
}
public ActionResult StringsPartial()
{
var stringTable = GetStringTable();
ViewBag.AllCategories = GetCategoryList();
return PartialView("_StringsPartial", stringTable);
}
[HttpPost]
public ActionResult CreateNew(StringTableRow row)
{
System.Diagnostics.Debugger.Break();
foreach (PropertyDescriptor prop in TypeDescriptor.GetProperties(row))
{
System.Diagnostics.Debug.Print(prop.Name);
}
return Content("Hello, there!");
}
[HttpPost]
public ActionResult Edit(DataRow row)
{
return new EmptyResult();
}
[HttpPost]
public ActionResult Delete(long resourceKeyId)
{
return new EmptyResult();
}
private IEnumerable<Category> GetCategoryList()
{
lock (padLock)
{
if (CacheManager.Contains(CacheKey_AllCategories))
{
return CacheManager.Get<IEnumerable<Category>>(CacheKey_AllCategories);
}
}
var list = Category.All;
lock (padLock)
{
CacheManager.Add(CacheKey_AllCategories, list);
}
return list;
}
private List<StringTableRow> GetStringTable()
{
List<StringTableRow> stringTable;
lock (padLock)
{
if (CacheManager.Contains(CacheKey_StringTable))
{
return CacheManager.Get<List<StringTableRow>>(CacheKey_StringTable);
}
}
stringTable = new StringTable().ToListOfStringTableRows();
lock (padLock)
{
CacheManager.Add(CacheKey_StringTable, stringTable);
}
return stringTable;
}
}
}
View Models -------------------------------------------------------------------
using System.Collections.Generic;
namespace GlobalizationUI.Presentation.ViewModels
{
public class StringTableRow
{
public long ResourceKeyId { get; set; }
public string Key { get; set; }
public long CategoryId { get; set; }
public List<CultureNameAndStringValue> StringValues { get; set; }
}
}
namespace GlobalizationUI.Presentation.ViewModels
{
public class CultureNameAndStringValue
{
public CultureNameAndStringValue() : this(null, null) { }
public CultureNameAndStringValue(string cultureShortName, string stringValue)
{
CultureShortName = cultureShortName;
StringValue = stringValue;
}
public string CultureShortName { get; set; }
public string StringValue { get; set; }
}
}
Model: -------------------------------------------------------------------
using System.Data;
using GlobalizationUI.Data;
namespace GlobalizationUI.BusinessObjects
{
public class StringTable : DataTable
{
public StringTable()
{
var sql = @"
declare @stmt nvarchar(max)
select @stmt =
isnull(@stmt + ', ', '') +
'max(case when s.CultureId = ' + cast(c.Id as nvarchar(max)) +
' then s.ResourceValue end) as ' + quotename(c.ShortName)
from Culture as c
where c.Supported = 1
select @stmt = '
select
rk.Id AS ResourceKeyId,
rk.Name AS [Key],
c.Id AS CategoryId,
c.Name as CategoryName, ' + @stmt + '
from StringCategory as sc
LEFT OUTER join Category as c on c.Id = sc.CategoryId
RIGHT OUTER JOIN ResourceKey as rk on rk.Id = sc.ResourceKeyId
inner join Strings as s on s.ResourceKeyId = rk.Id
group by rk.Id, rk.Name, c.Id, c.Name
'
exec sp_executesql @stmt = @stmt;";
this.Merge(Database.DefaultDatabase.GetDataTable(sql));
}
}
}
Model to View Model conversion: -------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data;
using System.Data.SqlTypes;
namespace GlobalizationUI.Presentation.ViewModels
{
public static class DataTableExtensions
{
public static List<StringTableRow> ToListOfStringTableRows(this DataTable dataTable)
{
var ret = new List<StringTableRow>();
if (dataTable == null || dataTable.Rows.Count == 0) return ret;
foreach (DataRow row in dataTable.Rows)
{
StringTableRow stringTableRow = new StringTableRow();
foreach (DataColumn column in dataTable.Columns)
{
if (string.Compare(column.ColumnName, "ResourceKeyId", true) == 0)
{
stringTableRow.ResourceKeyId = (long)row[column.ColumnName];
}
else if (string.Compare(column.ColumnName, "Key", true) == 0)
{
stringTableRow.Key = (string)row[column.ColumnName];
}
else if (string.Compare(column.ColumnName, "CategoryId", true) == 0)
{
var categoryId = row[column.ColumnName];
stringTableRow.CategoryId = categoryId == DBNull.Value ? 0 : (long)categoryId;
}
else if (string.Compare(column.ColumnName, "CategoryName", true) == 0)
{
continue;
}
else
{
if (stringTableRow.StringValues == null)
stringTableRow.StringValues = new List<CultureNameAndStringValue>();
stringTableRow.StringValues.Add(new CultureNameAndStringValue(column.ColumnName, (string)row[column.ColumnName]));
}
}
ret.Add(stringTableRow);
}
return ret;
}
}
}

The user interface is supposed to look somewhat like the the picture shown above. The number of columns returned by the SQL query is variable depending upon the number of cultures supported by a particular installation/deployment/business client. Hence the need for using unbound columns.
I took this picture not through this code but through an older version of my code when I bound data directly to a System.Data.DataTable
and did not use any unbound columns. That was all well until I had to edit the data in the actions at the MVC/server side. So, I switched from using a DataTable
to a POCO and used unbound columns for all cultures.
Kindly help.
回答1:
Okay, though I posted this question only 2 hours or so ago, I've had this trouble for over 8 hours now and have been trying various ways to solve this problem.
And just now, one of the things I did made this problem go away. Here it is.
If you are binding your DevExpress GridViewExtension
for ASP.NET MVC to a custom POCO like mine, and that POCO has one or more members that are collections of any kind, then, you must and absolutely must initialize those properties that represent collections in the constructor of your POCO.
For e.g., in my case, the model was a list of StringTableRow
's, where StringTableRow
was a POCO.
In the StringTableRow
class, I had a property named StringValues
which was of type IList<CultureNameAndStringValue>
, which is a collection. Therefore, for my code to work, I initialized the StringValues
property in the construct of my StringTableRow
class and everything started to work.
using System.Collections.Generic;
namespace GlobalizationUI.Presentation.ViewModels
{
public class StringTableRow
{
public StringTableRow()
{
// I added this ctor and initialized the
// StringValues property and the broken
// custom binding to unbound columns started
// to work.
StringValues = new List<CultureNameAndStringValue>();
}
public long ResourceKeyId { get; set; }
public string Key { get; set; }
public long CategoryId { get; set; }
public IList<CultureNameAndStringValue> StringValues { get; set; }
}
}
来源:https://stackoverflow.com/questions/18871574/e-getlistsourcefieldvalue-returns-null-in-the-customunboundcolumndata-event-hand