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

7

我希望能以通用的方式从控制器中获取视图并集中于特定的模型属性。

目前我已经有了以下内容:


控制器:

// 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);

View

    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<>()),因为它基于属性而变化..


将所有的管道工作都放在视图模型中,然后将视图类型化为该视图模型,这样做是否可行?这样你就可以访问属性而不必担心返回类型。 - Brian
你的意思是在Model中添加FocusScript方法吗?我还不确定它如何工作,因为控制器可能想要绑定到该模型上的许多不同属性(具有不同的返回类型)。如果您认为它可以工作,我会很感激您提供一个代码示例,也许我理解得不正确。 - George Duckett
你能解释一下什么是不同的返回类型以及为什么需要了解它吗? - Chris Moutray
我之所以问是因为JavaScript的focus()只需要知道要聚焦的元素,而不需要视图模型类的属性信息。 - Chris Moutray
@JeradRose:当你说“检查这个”的时候,我不确定代码会是什么样子。例如,如果我的视图显示一个相同类型的复杂对象列表,并且我想关注列表中第三个对象的属性,那会发生什么呢?我认为我的问题可能可以在FocusScript方法内部使用反射解决,以使用正确的泛型参数调用IdFor,但我还无法完全理解它。 - George Duckett
显示剩余6条评论
4个回答

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);

2

使用Lambda表达式就像简单的一样。不需要使用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()

话虽如此,我并不会评论“控制器操作正在设置焦点”的事实。


谢谢您的回答,我会进行测试,如果可以正常工作,我会接受并奖励悬赏。为什么控制器不应该设置焦点?关于控制器设置焦点,我应该在哪里/如何在特定操作完成后(使用相同视图显示结果)设置文本框(例如)的焦点? - George Duckett
只是看一下,你就依赖了几个 asp.net 4 的方法。 - George Duckett
1
@GeorgeDuckett感到失望,因为Darin在我的回答之后抢走了悬赏 - 哎呀,至少我的活动引起了一些兴趣,并得到了你的答案。 :) - Chris Moutray
@mouters:我可能太匆忙了——如果我使用的是MVC4,我认为它是更好的解决方案,因为它不使用反射并且似乎不那么hacky。话虽如此,我没有使用MVC4,所以这个方法不能直接套用。当准备到期时,我会重新审视该奖励应该授予给谁。 - George Duckett
我曾经(从查看MSDN)认为GetFullHtmlFieldId仅适用于MVC4,但你是正确的。它确实可行。 - George Duckett
显示剩余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);

    }

-1
也许我漏掉了什么,但既然您将字段名称传递给FocusOnField函数,那么您是否也可以传递字段名称的字符串作为视图中的默认ID,然后设置ViewData值?然后您的JavaScript可以执行类似以下操作...
<script type="text/javascript">

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

</script>

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

1
我传递的不是字段名称,而是表达式。传递表达式的目的是为了避免硬编码字段名称。 - George Duckett

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接