简体   繁体   English

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

[英]Passing model property expression to view

I want to have a way of getting a view to focus on a particular model property from a controller in a generic way. 我希望有一种方法可以通过通用方式从控制器获取视图以专注于特定的模型属性。

What i have so far is: 到目前为止我所拥有的是:


Controller: 控制器:

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

The problem I'm faced with now, is that in the view's FocusScript method I don't know the return type of the property to focus on, and casting to (System.Linq.Expressions.Expression<Func<TModel, object>>) fails for any property that doesn't return an object. 我现在面临的问题是,在视图的FocusScript方法中,我不知道要关注的属性的返回类型,并且转换为(System.Linq.Expressions.Expression<Func<TModel, object>>)对于任何不返回对象的属性都会失败。

I can't just add a second generic parameter for the property because I don't know what the return type of the property the controller wants me to focus on is. 我不能只为属性添加第二个泛型参数,因为我不知道控制器要我关注的属性的返回类型是什么。

How can I write my view extension method FocusScript in a generic way so it can be used with properties of varying return types? 如何以通用方式编写视图扩展方法FocusScript ,以便它可以与不同返回类型的属性一起使用?


Why is there a problem? 为什么会有问题?

I know I could just pass the Id of the control i want to focus on in the controller and have javascript read that id, find the control and focus on it. 我知道我可以通过我想要在控制器中关注的控件的Id并让javascript读取该ID,找到控件并专注于它。 However, I don't like having something that belongs in the view (the Id of a control) hard-coded in the controller. 但是,我不喜欢在控制器中硬编码的视图(控件的Id)中存在某些东西。 I want to tell the method what property I want and it should know the Id to use in the same way the view normally gets/creates the Id for a control. 我想告诉方法我想要什么属性,它应该知道要使用的Id,就像视图通常获取/创建控件的Id一样。

Lets say I have a model: 让我们说我有一个模型:

class MyModel
{
    public int IntProperty { get; set; }
    public string StringProperty { get; set; }
}

In different places in the controller I want to focus one different fields: 在控制器的不同位置,我想关注一个不同的领域:

FocusOnField((MyModel m) => m.IntProperty);
...
FocusOnField((MyModel m) => m.StringProperty);

Now, in the first case the expression is a function returning an integer, in the second case it's returning a string. 现在,在第一种情况下,表达式是一个返回整数的函数,在第二种情况下,它返回一个字符串。 As a result I don't know what to cast my ViewData["FieldToFocus"] to (to pass it to IdFor<>() ) as it varies based on the property.. 因此,我不知道将ViewData [“FieldToFocus”]转换为(将其传递给IdFor<>() ),因为它根据属性而变化。

I think I've come up with a solution to your problem - it works in my environment but then I've had to guess how your code probably looks. 我想我已经找到了解决问题的方法 - 它可以在我的环境中运行,但后来我不得不猜测你的代码看起来如何。

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

Perhaps there's a more elegant way to do, but I think what it boils down to is you're trying to cast an object (ie return type of ViewData["FieldToFocus"] ) to the correct expression tree Expression<Func<TViewModel, TProperty>> but like you've said you don't know what TProperty should be. 也许有更优雅的方法,但我认为它归结为你试图将一个对象(即返回类型的ViewData["FieldToFocus"] )转换为正确的表达式Expression<Func<TViewModel, TProperty>>但是就像你说过你不知道TProperty应该是什么。

Also to make this work I had to add another static method to GetIdFor because at the moment I'm not too sure how to invoke an extension method. 为了完成这项工作,我不得不为GetIdFor添加另一个静态方法,因为目前我不太确定如何调用扩展方法。 Its just a wrapper to call the IdFor extension method. 它只是一个调用IdFor扩展方法的包装器。

You could make it less verbose (but probably less readable). 你可以减少冗长(但可能不太可读)。

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

One final thought, would the output of HtmlHelper.IdFor ever differ from ExpressionHelper.GetExpressionText ? 最后一个想法, HtmlHelper.IdFor的输出是否会与ExpressionHelper.GetExpressionText不同? I don't fully understand IdFor, and wonder if it will always give you a string that matches the property name. 我不完全理解IdFor,并想知道它是否总是会给你一个与属性名称相匹配的字符串。

ViewData["FieldToFocus"] = ExpressionHelper.GetExpressionText(fieldExpression);

It's as simple as using a LambdaExpression. 它就像使用LambdaExpression一样简单。 No need to go the Expression<Func<TModel, TProperty>> way. 无需使用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;
        }
    }
}

then in your controller: 然后在你的控制器中:

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

and in your view: 在你看来:

@model MyModel

@Html.FocusScript()

This being said I am leaving without comment the fact that a controller action is setting a focus . 这就是说我离开而没有评论控制器动作正在设置焦点这一事实。

You can take advantage of your out of the box Metadata to provide you the property Id etc.. 您可以利用开箱即用的元数据为您提供属性ID等。

And then in your Extensions: 然后在你的扩展中:

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

    }

Maybe I'm missing something, but since you're passing in the field name to your FocusOnField function, could you not also just pass the string of the field name as well which will be the default ID in the view then set a ViewData value? 也许我错过了什么,但是因为你将字段名称传递给了FocusOnField函数,你是否也可以只传递字段名称的字符串,这将是视图中的默认ID,然后设置一个ViewData值? Then your javascript could do something like... 然后你的JavaScript可以做类似的事情......

<script type="text/javascript">

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

</script>

Maybe this isn't exactly what you're after, but the JS will definitely work this way... 也许这不完全是你所追求的,但JS肯定会以这种方式工作......

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM