将模型属性表达式传递给视图

Geo*_*ett 7 c# generics model-view-controller asp.net-mvc asp.net-mvc-2

我希望有一种方法可以通过通用方式从控制器获取视图以专注于特定的模型属性.

到目前为止我所拥有的是:


控制器:

// 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);
Run Code Online (Sandbox Code Playgroud)

视图

    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;
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

我现在面临的问题是,在视图的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; }
}
Run Code Online (Sandbox Code Playgroud)

在控制器的不同位置,我想关注一个不同的领域:

FocusOnField((MyModel m) => m.IntProperty);
...
FocusOnField((MyModel m) => m.StringProperty);
Run Code Online (Sandbox Code Playgroud)

现在,在第一种情况下,表达式是一个返回整数的函数,在第二种情况下,它返回一个字符串.因此,我不知道将ViewData ["FieldToFocus"]投射到(传递给它IdFor<>())的内容,因为它根据属性而变化.

Chr*_*ray 5

我想我已经找到了解决问题的方法 - 它可以在我的环境中运行,但后来我不得不猜测你的代码看起来如何.

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>");
    }
}
Run Code Online (Sandbox Code Playgroud)

也许还有更优雅的方法,但我认为它归结为你试图将一个对象(即返回类型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 });
Run Code Online (Sandbox Code Playgroud)

最后一个想法,输出HtmlHelper.IdFor是否会有所不同ExpressionHelper.GetExpressionText?我不完全理解IdFor,并想知道它是否总会给你一个与属性名称相匹配的字符串.

ViewData["FieldToFocus"] = ExpressionHelper.GetExpressionText(fieldExpression);
Run Code Online (Sandbox Code Playgroud)


Dar*_*rov 2

就像使用 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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后在你的控制器中:

public ActionResult Index()
{
    FocusOnField((MyModel m) => m.IntProperty);
    return View(new MyModel());
}
Run Code Online (Sandbox Code Playgroud)

在你看来:

@model MyModel

@Html.FocusScript()
Run Code Online (Sandbox Code Playgroud)

话虽这么说,我不加评论地指出控制器操作正在设置焦点这一事实。