无法使用 ExpressionHelper 从表达式中获取控件名称
Can't get control name from expression with ExpressionHelper
我正在创建一个帮助程序,它允许我创建级联下拉列表,这些列表可以使用 AJAX 自行填充。辅助方法如下所示:
public static MvcHtmlString AjaxSelectFor<TModel, TProperty>(
this HtmlHelper<TModel> html,
Expression<Func<TModel, TProperty>> expression,
Expression<Func<TModel, TProperty>> cascadeFrom,
string sourceUrl,
bool withEmpty = false)
{
string controlFullName = html.GetControlName(expression);
string cascadeFromFullName = html.GetControlName(cascadeFrom);
var selectBuilder = GetBaseSelect(controlFullName.GetControlId(), controlFullName, sourceUrl, withEmpty);
selectBuilder.Attributes.Add("data-selected-id", html.GetValue(expression));
selectBuilder.Attributes.Add("data-cascade-from", "#" + cascadeFromFullName.GetControlId());
return new MvcHtmlString(selectBuilder.ToString());
}
private static TagBuilder GetBaseSelect(string controlId, string controlName, string sourceUrl, bool withEmpty)
{
var selectBuilder = new TagBuilder("select");
selectBuilder.Attributes.Add("id", controlId);
selectBuilder.Attributes.Add("name", controlName);
selectBuilder.Attributes.Add("data-toggle", "ajaxSelect");
selectBuilder.Attributes.Add("data-source-url", sourceUrl);
selectBuilder.Attributes.Add("data-with-empty", withEmpty.ToString());
selectBuilder.AddCssClass("form-control");
return selectBuilder;
}
internal static string GetControlName<TModel, TProperty>(this HtmlHelper<TModel> html, Expression<Func<TModel, TProperty>> expression)
{
string controlName = ExpressionHelper.GetExpressionText(expression);
return html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(controlName);
}
internal static string GetControlId(this string controlName)
{
return TagBuilder.CreateSanitizedId(controlName);
}
第一个表达式针对将在控件中绑定的 属性,我可以毫无问题地获取它的 id 和 name 属性。第二个目标是帮助程序将从中级联的 属性,但是当我通过 GetControlName 方法时,ExpressionHelper.GetExpressionText(expression) returns 一个空字符串而不是 属性 名称.我在"expression"上添加了一个watch来检查哪里出了问题,它的值如下:
{model => Convert(model.TopCategoryId)}
虽然我在获取第一个表达式的 属性 名称时得到以下值:
{model => model.CategoryId}
我真的不明白为什么这两个表达方式会有差异。这是我在我看来如何调用助手,以防它无论如何相关:
@Html.AjaxSelectFor(model => model.CategoryId, model => model.TopCategoryId, "/api/Categories/GetSelectList", true)
知道这里发生了什么吗?
在使用了一段时间的 hacky 解决方法之后,我终于弄明白了。正如 Stephen Muecke 指出的那样,问题来自于对 "expression" 和 "cascadeFrom" 使用 TProperty 类型。所以,这里是如何正确(好吧,有点)解决这个问题:
public static MvcHtmlString AjaxSelectFor<TModel, TProperty, TCascadeProperty>(
this HtmlHelper<TModel> html,
Expression<Func<TModel, TProperty>> expression,
Expression<Func<TModel, TCascadeProperty>> cascadeFrom,
string sourceUrl,
bool withEmpty = false)
{
[...]
}
希望对某人有所帮助!
[编辑]
顺便说一下,这里有 jQuery 代码来完成这项工作:
var common = {};
$(document).ready(function() {
common.bindAjaxSelect();
})
common.bindAjaxSelect = function () {
$('[data-toggle="ajaxSelect"]').each(function () {
common.clearSelect($(this));
});
$('[data-toggle="ajaxSelect"]').not('[data-cascade-from]').each(function () {
common.fillAjaxSelect($(this));
$(this).on('change', function () {
common.bindAjaxSelectCascade('#' + $(this).attr('id'));
});
});
};
common.bindAjaxSelectCascade = function (selector) {
$('[data-toggle="ajaxSelect"][data-cascade-from="' + selector + '"]').each(function () {
common.fillAjaxSelect($(this), selector);
$(this).unbind('change');
$(this).on('change', function () {
common.bindAjaxSelectCascade('#' + $(this).attr('id'));
});
});
};
common.fillAjaxSelect = function (select, cascadeFromSelector) {
var controlId = select.attr('id');
var sourceUrl = select.attr('data-source-url');
var withEmpty = select.attr('data-with-empty');
var selectedId = select.attr('data-selected-id');
var parentId = $(cascadeFromSelector).val();
var emptyCheck = withEmpty ? 1 : 0;
$('[data-toggle="ajaxSelect"][data-cascade-from="#' + select.attr('id') + '"]').each(function () {
common.clearSelect($(this));
});
var requestParameters = parentId === undefined
? { ajax: true, withEmpty: withEmpty }
: { ajax: true, parentId: parentId, withEmpty: withEmpty };
$.getJSON(sourceUrl, requestParameters, function (response) {
if (response.Success === true) {
if (response.Data.length > emptyCheck) {
var options = [];
$.each(response.Data, function (key, item) {
if (selectedId !== undefined && item.Id === selectedId) {
options.push('<option value="' + item.Id + '" selected>' + item.Value + '</option>');
} else {
options.push('<option value="' + item.Id + '">' + item.Value + '</option>');
}
});
select.html(options.join(''));
select.enable();
if (selectedId !== undefined && selectedId !== '') {
common.bindAjaxSelectCascade('#' + controlId);
}
} else {
common.clearSelect(select);
}
} else {
common.clearSelect(select);
//TODO : append error message to page.
}
});
};
common.clearSelect = function (select) {
select.disable();
select.html('');
$('[data-toggle="ajaxSelect"][data-cascade-from="' + select.attr('id') + '"]').each(function () {
common.clearSelect($(this));
});
};
我正在创建一个帮助程序,它允许我创建级联下拉列表,这些列表可以使用 AJAX 自行填充。辅助方法如下所示:
public static MvcHtmlString AjaxSelectFor<TModel, TProperty>(
this HtmlHelper<TModel> html,
Expression<Func<TModel, TProperty>> expression,
Expression<Func<TModel, TProperty>> cascadeFrom,
string sourceUrl,
bool withEmpty = false)
{
string controlFullName = html.GetControlName(expression);
string cascadeFromFullName = html.GetControlName(cascadeFrom);
var selectBuilder = GetBaseSelect(controlFullName.GetControlId(), controlFullName, sourceUrl, withEmpty);
selectBuilder.Attributes.Add("data-selected-id", html.GetValue(expression));
selectBuilder.Attributes.Add("data-cascade-from", "#" + cascadeFromFullName.GetControlId());
return new MvcHtmlString(selectBuilder.ToString());
}
private static TagBuilder GetBaseSelect(string controlId, string controlName, string sourceUrl, bool withEmpty)
{
var selectBuilder = new TagBuilder("select");
selectBuilder.Attributes.Add("id", controlId);
selectBuilder.Attributes.Add("name", controlName);
selectBuilder.Attributes.Add("data-toggle", "ajaxSelect");
selectBuilder.Attributes.Add("data-source-url", sourceUrl);
selectBuilder.Attributes.Add("data-with-empty", withEmpty.ToString());
selectBuilder.AddCssClass("form-control");
return selectBuilder;
}
internal static string GetControlName<TModel, TProperty>(this HtmlHelper<TModel> html, Expression<Func<TModel, TProperty>> expression)
{
string controlName = ExpressionHelper.GetExpressionText(expression);
return html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(controlName);
}
internal static string GetControlId(this string controlName)
{
return TagBuilder.CreateSanitizedId(controlName);
}
第一个表达式针对将在控件中绑定的 属性,我可以毫无问题地获取它的 id 和 name 属性。第二个目标是帮助程序将从中级联的 属性,但是当我通过 GetControlName 方法时,ExpressionHelper.GetExpressionText(expression) returns 一个空字符串而不是 属性 名称.我在"expression"上添加了一个watch来检查哪里出了问题,它的值如下:
{model => Convert(model.TopCategoryId)}
虽然我在获取第一个表达式的 属性 名称时得到以下值:
{model => model.CategoryId}
我真的不明白为什么这两个表达方式会有差异。这是我在我看来如何调用助手,以防它无论如何相关:
@Html.AjaxSelectFor(model => model.CategoryId, model => model.TopCategoryId, "/api/Categories/GetSelectList", true)
知道这里发生了什么吗?
在使用了一段时间的 hacky 解决方法之后,我终于弄明白了。正如 Stephen Muecke 指出的那样,问题来自于对 "expression" 和 "cascadeFrom" 使用 TProperty 类型。所以,这里是如何正确(好吧,有点)解决这个问题:
public static MvcHtmlString AjaxSelectFor<TModel, TProperty, TCascadeProperty>(
this HtmlHelper<TModel> html,
Expression<Func<TModel, TProperty>> expression,
Expression<Func<TModel, TCascadeProperty>> cascadeFrom,
string sourceUrl,
bool withEmpty = false)
{
[...]
}
希望对某人有所帮助!
[编辑]
顺便说一下,这里有 jQuery 代码来完成这项工作:
var common = {};
$(document).ready(function() {
common.bindAjaxSelect();
})
common.bindAjaxSelect = function () {
$('[data-toggle="ajaxSelect"]').each(function () {
common.clearSelect($(this));
});
$('[data-toggle="ajaxSelect"]').not('[data-cascade-from]').each(function () {
common.fillAjaxSelect($(this));
$(this).on('change', function () {
common.bindAjaxSelectCascade('#' + $(this).attr('id'));
});
});
};
common.bindAjaxSelectCascade = function (selector) {
$('[data-toggle="ajaxSelect"][data-cascade-from="' + selector + '"]').each(function () {
common.fillAjaxSelect($(this), selector);
$(this).unbind('change');
$(this).on('change', function () {
common.bindAjaxSelectCascade('#' + $(this).attr('id'));
});
});
};
common.fillAjaxSelect = function (select, cascadeFromSelector) {
var controlId = select.attr('id');
var sourceUrl = select.attr('data-source-url');
var withEmpty = select.attr('data-with-empty');
var selectedId = select.attr('data-selected-id');
var parentId = $(cascadeFromSelector).val();
var emptyCheck = withEmpty ? 1 : 0;
$('[data-toggle="ajaxSelect"][data-cascade-from="#' + select.attr('id') + '"]').each(function () {
common.clearSelect($(this));
});
var requestParameters = parentId === undefined
? { ajax: true, withEmpty: withEmpty }
: { ajax: true, parentId: parentId, withEmpty: withEmpty };
$.getJSON(sourceUrl, requestParameters, function (response) {
if (response.Success === true) {
if (response.Data.length > emptyCheck) {
var options = [];
$.each(response.Data, function (key, item) {
if (selectedId !== undefined && item.Id === selectedId) {
options.push('<option value="' + item.Id + '" selected>' + item.Value + '</option>');
} else {
options.push('<option value="' + item.Id + '">' + item.Value + '</option>');
}
});
select.html(options.join(''));
select.enable();
if (selectedId !== undefined && selectedId !== '') {
common.bindAjaxSelectCascade('#' + controlId);
}
} else {
common.clearSelect(select);
}
} else {
common.clearSelect(select);
//TODO : append error message to page.
}
});
};
common.clearSelect = function (select) {
select.disable();
select.html('');
$('[data-toggle="ajaxSelect"][data-cascade-from="' + select.attr('id') + '"]').each(function () {
common.clearSelect($(this));
});
};