【问题标题】:Passing model property expression to view将模型属性表达式传递给视图
【发布时间】:2012-07-12 03:22:35
【问题描述】:

我希望有一种方法可以让视图以通用方式从控制器中关注特定模型属性。

我目前拥有的是:


控制器:

// 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&lt;Func&lt;TModel, object&gt;&gt;) 都会失败返回一个对象。

我不能只为属性添加第二个通用参数,因为我不知道控制器要我关注的属性的返回类型是什么。

如何以通用方式编写我的视图扩展方法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&lt;&gt;()),因为它因属性而异..

【问题讨论】:

  • 在视图模型中完成所有管道并将视图键入到该视图模型中是否可行?那么您就可以访问属性而不必担心返回类型。
  • 您的意思是模型中有FocusScript 方法?我仍然不确定这将如何工作,因为控制器可能希望绑定到该模型上的许多不同属性(具有不同的返回类型)。如果您认为它会起作用,我会很感激一个代码示例,因为也许我没有正确遵循。
  • 你能解释一下你所说的不同返回类型是什么意思吗?为什么你需要知道这一点?
  • 我问是因为 javascript focus() 只需要知道要关注的元素而不是视图模型类的属性信息
  • 你不能不显眼吗?例如,从您的角度来看,在您想要关注的元素上添加一个data-focus 属性,然后有一个通用脚本在加载时关注这个元素?如果您更愿意在模型和视图上执行此操作,您甚至可以使用自定义属性来执行此操作,尽管我认为这是视图问题,而不是模型或控制器问题。

标签: c# asp.net-mvc model-view-controller generics asp.net-mvc-2


【解决方案1】:

我想我已经为您的问题想出了一个解决方案 - 它适用于我的环境,但我不得不猜测您的代码可能看起来如何。

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&lt;Func&lt;TViewModel, TProperty&gt;&gt;,但就像你一样我说过你不知道 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】:

    就像使用 LambdaExpression 一样简单。无需走Expression&lt;Func&lt;TModel, TProperty&gt;&gt; 方式。

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

    话虽如此,我将不加评论地离开,控制器操作正在设置焦点

    【讨论】:

    • 感谢您的回答,我会测试并假设它有效,接受并奖励赏金。为什么控制器不应该设置焦点。重新控制器设置焦点,在特定操作完成后我应该如何/在哪里设置焦点(例如)(相同的视图用于显示结果)?
    • 只是看一下,你依赖于一些 asp.net 4 方法。
    • @GeorgeDuckett 很失望达林在我回答后突然介入并接受了赏金 - 嗯,至少我的活动引起了一些兴趣并得到了你的回答。 :)
    • @mouters:我可能太仓促了——如果我使用的是 MVC4 IMO,这是一个更好的解决方案,因为它不使用反射并且看起来不那么老套。说我没有使用 MVC4,所以这不会按原样工作。我会在它即将到期时审查将赏金提供给谁。
    • @GeorgeDuckett 实际上我并不是说这是一个更糟糕的答案 - 它看起来更干净,看起来更优雅。
    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

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

      <script type="text/javascript">
      
          onload = focusonme;
          function focusonme() {
              var element = document.getElementById(@ViewData["FieldToFocus"]);
              element.focus();
          }
      
      </script>
      

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

      【讨论】:

      • 我没有传递字段的名称,而是传递了一个表达式。传递表达式的目的是让我不必对字段名称进行硬编码。
      猜你喜欢
      • 2021-12-01
      • 2013-10-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多