[英]Passing model property expression to view
我希望有一种方法可以通过通用方式从控制器获取视图以专注于特定的模型属性。
到目前为止我所拥有的是:
// To become an extension/base class method
private void FocusOnField<TModel, TProperty>(Expression<Func<TModel, TProperty>> fieldExpression)
{
ViewData["FieldToFocus"] = fieldExpression;
}
...
FocusOnField((ConcreteModelClass m) => m.MyProperty);
public static class ViewPageExtensions
{
public static MvcHtmlString FocusScript<TModel>(this ViewPage<TModel> viewPage)
{
if (viewPage.ViewData["FieldToFocus"] != null)
{
return MvcHtmlString.Create(
@"<script type=""text/javascript"" language=""javascript"">
$(document).ready(function() {
setTimeout(function() {
$('#" + viewPage.Html.IdFor((System.Linq.Expressions.Expression<Func<TModel, object>>)viewPage.ViewData["FieldToFocus"]) + @"').focus();
}, 500);
});
</script>");
}
else
{
return MvcHtmlString.Empty;
}
}
}
我现在面临的问题是,在视图的FocusScript
方法中,我不知道要关注的属性的返回类型,并且转换为(System.Linq.Expressions.Expression<Func<TModel, object>>)
对于任何不返回对象的属性都会失败。
我不能只为属性添加第二个泛型参数,因为我不知道控制器要我关注的属性的返回类型是什么。
如何以通用方式编写视图扩展方法FocusScript
,以便它可以与不同返回类型的属性一起使用?
我知道我可以通过我想要在控制器中关注的控件的Id并让javascript读取该ID,找到控件并专注于它。 但是,我不喜欢在控制器中硬编码的视图(控件的Id)中存在某些东西。 我想告诉方法我想要什么属性,它应该知道要使用的Id,就像视图通常获取/创建控件的Id一样。
让我们说我有一个模型:
class MyModel
{
public int IntProperty { get; set; }
public string StringProperty { get; set; }
}
在控制器的不同位置,我想关注一个不同的领域:
FocusOnField((MyModel m) => m.IntProperty);
...
FocusOnField((MyModel m) => m.StringProperty);
现在,在第一种情况下,表达式是一个返回整数的函数,在第二种情况下,它返回一个字符串。 因此,我不知道将ViewData [“FieldToFocus”]转换为(将其传递给IdFor<>()
),因为它根据属性而变化。
我想我已经找到了解决问题的方法 - 它可以在我的环境中运行,但后来我不得不猜测你的代码看起来如何。
public static class ViewPageExtensions
{
public static MvcHtmlString GetIdFor<TViewModel, TProperty>(ViewPage<TViewModel> viewPage, Expression<Func<TViewModel, TProperty>> expression)
{
return viewPage.Html.IdFor(expression);
}
public static MvcHtmlString FocusScript<TViewModel>(this ViewPage<TViewModel> viewPage)
{
if (viewPage.ViewData["FieldToFocus"] == null)
return MvcHtmlString.Empty;
object expression = viewPage.ViewData["FieldToFocus"];
Type expressionType = expression.GetType(); // expressionType = Expression<Func<TViewModel, TProperty>>
Type functionType = expressionType.GetGenericArguments()[0]; // functionType = Func<TViewModel, TProperty>
Type[] functionGenericArguments = functionType.GetGenericArguments(); // functionGenericArguments = [TViewModel, TProperty]
System.Reflection.MethodInfo method = typeof(ViewPageExtensions).GetMethod("GetIdFor").MakeGenericMethod(functionGenericArguments); // method = GetIdFor<TViewModel, TProperty>
MvcHtmlString id = (MvcHtmlString)method.Invoke(null, new[] { viewPage, expression }); // Call GetIdFor<TViewModel, TProperty>(viewPage, expression);
return MvcHtmlString.Create(
@"<script type=""text/javascript"" language=""javascript"">
$(document).ready(function() {
setTimeout(function() {
$('#" + id + @"').focus();
}, 500);
});
</script>");
}
}
也许有更优雅的方法,但我认为它归结为你试图将一个对象(即返回类型的ViewData["FieldToFocus"]
)转换为正确的表达式Expression<Func<TViewModel, TProperty>>
但是就像你说过你不知道TProperty应该是什么。
为了完成这项工作,我不得不为GetIdFor
添加另一个静态方法,因为目前我不太确定如何调用扩展方法。 它只是一个调用IdFor
扩展方法的包装器。
你可以减少冗长(但可能不太可读)。
object expression = viewPage.ViewData["FieldToFocus"];
MethodInfo method = typeof(ViewPageExtensions).GetMethod("GetIdFor")
.MakeGenericMethod(expression.GetType().GetGenericArguments()[0].GetGenericArguments());
MvcHtmlString id = (MvcHtmlString)method.Invoke(null, new[] { viewPage, expression });
最后一个想法, HtmlHelper.IdFor
的输出是否会与ExpressionHelper.GetExpressionText
不同? 我不完全理解IdFor,并想知道它是否总是会给你一个与属性名称相匹配的字符串。
ViewData["FieldToFocus"] = ExpressionHelper.GetExpressionText(fieldExpression);
它就像使用LambdaExpression一样简单。 无需使用Expression<Func<TModel, TProperty>>
方式。
public static class HtmlExtensions
{
public static string IdFor(
this HtmlHelper htmlHelper,
LambdaExpression expression
)
{
var id = ExpressionHelper.GetExpressionText(expression);
return htmlHelper.ViewData.TemplateInfo.GetFullHtmlFieldId(id);
}
public static MvcHtmlString FocusScript(
this HtmlHelper htmlHelper
)
{
if (htmlHelper.ViewData["FieldToFocus"] != null)
{
return MvcHtmlString.Create(
@"<script type=""text/javascript"">
$(document).ready(function() {
setTimeout(function() {
$('#" + htmlHelper.IdFor((LambdaExpression)htmlHelper.ViewData["FieldToFocus"]) + @"').focus();
}, 500);
});
</script>");
}
else
{
return MvcHtmlString.Empty;
}
}
}
然后在你的控制器中:
public ActionResult Index()
{
FocusOnField((MyModel m) => m.IntProperty);
return View(new MyModel());
}
在你看来:
@model MyModel
@Html.FocusScript()
这就是说我离开而没有评论控制器动作正在设置焦点这一事实。
您可以利用开箱即用的元数据为您提供属性ID等。
然后在你的扩展中:
public static MvcHtmlString FocusFieldFor<TModel, TValue>(
this HtmlHelper<TModel> html,
Expression<Func<TModel, TValue>> expression)
{
var metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
var fullPropertyName = html.ViewData.TemplateInfo.GetFullHtmlFieldId(metadata.PropertyName);
var jsonData =
@"<script type=""text/javascript"">
$(document).ready(function() {
setTimeout(function() {
$('#" + fullPropertyName + @"').focus();
}, 500);
});
</script>";
return MvcHtmlString.Create(jsonData);
}
也许我错过了什么,但是因为你将字段名称传递给了FocusOnField函数,你是否也可以只传递字段名称的字符串,这将是视图中的默认ID,然后设置一个ViewData值? 然后你的JavaScript可以做类似的事情......
<script type="text/javascript">
onload = focusonme;
function focusonme() {
var element = document.getElementById(@ViewData["FieldToFocus"]);
element.focus();
}
</script>
也许这不完全是你所追求的,但JS肯定会以这种方式工作......
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.