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

看法

    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

4 回答 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);
于 2012-07-17T06:07:33.610 回答
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()

话虽这么说,我没有评论控制器动作正在设置焦点这一事实。

于 2012-07-17T06:41:25.063 回答
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);

    }
于 2012-07-17T13:18:51.973 回答
-1

也许我遗漏了一些东西,但是由于您将字段名称传递给您的 FocusOnField 函数,您是否也可以不只传递字段名称的字符串,这将是视图中的默认 ID,然后设置 ViewData 值? 然后你的javascript可以做类似的事情......

<script type="text/javascript">

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

</script>

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

于 2012-07-16T16:02:53.203 回答