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

时间:2012-07-12 11:23:44

标签: c# asp.net-mvc model-view-controller generics 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);

视图

    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<>()),因为它因属性而异。

4 个答案:

答案 0 :(得分: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>");
    }
}

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

答案 1 :(得分: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;
        }
    }
}

然后在你的控制器中:

public ActionResult Index()
{
    FocusOnField((MyModel m) => m.IntProperty);
    return View(new MyModel());
}

并在您看来:

@model MyModel

@Html.FocusScript()

这就是说我离开时没有评论控制器动作正在设置焦点

答案 2 :(得分:1)

您可以利用开箱即用的元数据为您提供属性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);

    }

答案 3 :(得分:-1)

也许我错过了什么,但是因为你将字段名称传递给了FocusOnField函数,你是否也可以只传递字段名称的字符串,这将是视图中的默认ID然后设置一个ViewData值?然后你的javascript可以做类似......

<script type="text/javascript">

    onload = focusonme;
    function focusonme() {
        var element = document.getElementById(@ViewData["FieldToFocus"]);
        element.focus();
    }

</script>

也许这不完全是你所追求的,但JS肯定会以这种方式工作......