【问题标题】:ASP.NET MVC4 Generalized Field InitializationASP.NET MVC4 通用字段初始化
【发布时间】:2014-02-01 23:34:23
【问题描述】:

在 ASP.NET MVC4 中,我想用值初始化字段。当用户获得该页面时,在他将其发回之前,我希望它从某些字段中的值开始。这些值将从查询字符串中提取,而不是硬编码。假设用户正在填写表格:他已登录,您知道他的姓名和地址。出于礼貌,将这些设置为默认值。实际上,它们来自哪里并不重要,只是它是一组键值对,并且值是字符串。我只是想在没有用户首先发布表单的情况下在字段中放置一些东西,而且我真的很想这样做,而不是在一个相当复杂的模型中对每个属性进行一长串的硬编码。

现在它是在$(document).ready() 的一个 JS 循环中完成的,但它属于服务器。不过,我想复制该逻辑:将查询参数名称视为唯一标识符。

在我的控制器的Index() 方法中,我尝试调用ModelState.TrySetModelValue()(当ModelState 被填充时,通过一个唯一的字符串标识每个字段)但是在这个阶段,ModelState 是空的,所以当然那没有用。我尝试更改 Index() 以期望模型的实例作为参数,但这无济于事。

我必须重写每个@Html.EditorFor()/TextBoxFor()/等。在应用程序中调用?这似乎很疯狂。正确地说,这是我会在一个地方循环执行的操作,而不是分散在越来越多的视图中的多个位置。

我有一种感觉,我没有掌握 MVC4 的工作方式的基本原理。

更新 2

事实证明,如果您使用 [HttpGet] 装饰您的操作方法,并且您希望模型作为参数,那么如果您使用字段 names (foo.bar) 而不是查询字符串中的 ID (foo_bar),它会自动执行我想要的操作。 ModelState 已填充。当我查看ModelState 时,我一定没有使用[HttpGet] 修饰的操作方法。

如果一个字段是通过查询字符串自动设置的,它将取代模型中的任何内容。这是合理的;重点是覆盖模型的默认值。但是,如果您想反过来覆盖可能的查询字符串值(例如,假设有一个“电子签名”复选框;这应该始终需要用户的明确努力),那么您必须通过 @987654334 来做到这一点@。

这意味着我下面的第一个解决方案没有实际效果(假设我在操作方法上有 [HttpGet] 属性)。它只设置框架已经在ModelState 中设置的模型的属性,因此模型中的值被忽略。

有点奇怪的是,ModelState 如果字段在查询字符串中不是,则为它们提供不同的键。 foo.bar.baz 如果它在查询字符串中,则仅使用它作为键,但如果不是,则键变为 foo.footypename.bar.bartypename.baz。如果属性的名称与其类型相同,则似乎有一个例外:我有一个Name 模型类,而另一个模型类有一个属性public Name Name { get; set }。 Name 类型的属性,即 named 名称,在 ModelState 键中永远不会跟随它们的类型名称。但是,我尚未排除该特定属性的类型名被排除的其他可能原因。这是一个猜测。在我的模型中,在所有情况下,“叶子”属性都排除了类型名。那是因为它们是系统已知的类型,还是“叶子”,还是什么?我不知道。

在任何情况下,模型的“根”类的叶属性始终使用自己的名称作为ModelState 中的键。

所以一般的答案是你分配给模型。但是对于查询字符串的初始化,有一个不同的具体答案。

更新

解决方案——剪掉很多代码

//  Controller base
public abstract class ControllerBase<TModel> : Controller 
{
    [HttpGet]
    public virtual ActionResult Index(TModel model)
    {
        HttpContext.Request.QueryString.CopyTo(model);

        return View("Index", model);
    }
}

public static class Extensions
{
    /// <summary>
    /// Given NameValueCollection of keys/values in the form 
    /// "foo.bar.baz" = "text", and an object which is the *parent* of
    /// foo, set properties of foo accordingly. 
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="src"></param>
    /// <param name="model"></param>
    public static void CopyTo<T>(this NameValueCollection src, T target)
    {
        String strkey;
        Object objval;

        foreach (var key in src.Keys)
        {
            strkey = "" + key;
            objval = src[strkey];

            target.TrySetPropertyValue(strkey, objval);
        }
    }

    /// <summary>
    /// Given a reference to an object objThis, the string "foo.bar.baz", 
    /// and an object o of a type optimistically hoped to be convertible 
    /// to that of objThis.foo.bar.baz, set objThis.foo.bar.baz = o
    ///
    /// If foo.bar is null, it must have a default constructor, or we fail 
    /// and return false. 
    /// </summary>
    /// <param name="objThis"></param>
    /// <param name="propPathName"></param>
    /// <param name="value"></param>
    /// <returns></returns>
    public static bool TrySetPropertyValue(this object objThis, 
                                           string propPathName, object value)
    {
        if (string.IsNullOrWhiteSpace(propPathName))
        {
            throw new ArgumentNullException(propPathName);
        }
        var names = propPathName.Split(new char[] { '.' }).ToList();

        var nextPropInfo = objThis.GetType().GetProperty(names.First());

        if (null == nextPropInfo)
            return false;

        if (names.Count > 1)
        {
            var nextPropValue = nextPropInfo.GetValue(objThis, null);

            if (null == nextPropValue)
            {
                nextPropValue = Activator
                    .CreateInstance(nextPropInfo.PropertyType);
                nextPropInfo.SetValue(objThis, nextPropValue);
            }

            names.RemoveAt(0);

            return nextPropValue.TrySetPropertyValue(
                                     String.Join(".", names), value);
        }
        else
        {
            try
            {
                var conv = System.ComponentModel.TypeDescriptor
                               .GetConverter(nextPropInfo.PropertyType);
                value = conv.ConvertFrom(value);

                nextPropInfo.SetValue(objThis, value);
            }
            catch (System.FormatException)
            {
                return false;
            }
            return true;
        }
    }
}

【问题讨论】:

  • 使用EditorFor 是一种廉价的原型制作方式,但IMO 不适合任何生产应用。至于使用TextBoxFor、DropDownListFor 等......是的,这对于如何构建 MVC 视图非常重要。
  • @Yuck 我根本不介意使用 TextBoxFor 等。我非常喜欢它们(那些能正常工作的——HiddenFor 不能)。我只是不想将这个小功能的实现分散在对这些事物的每次调用中,最终在几十个不同的视图中。丑陋的(上面编辑提到)。视图不属于它的位置。如果我们想关闭它,或者改变它的行为方式怎么办?我没有和 EditorFor 结婚。当需要时,我会用更具体的助手替换它。

标签: asp.net asp.net-mvc asp.net-mvc-4 query-string request.querystring


【解决方案1】:

您可以在控制器中使用默认值初始化模型,然后像这样使用它

@Html.TextBoxFor(m => Model.Name)

在控制器中初始化:

public ActionResult Index()
{
   MyModel model = new MyModel();
   model.Name = "myname";
   return View("myview", model);
}

你也可以在TextBoxFor中设置属性

@Html.TextBoxFor(m => Model.Name, new { value = "myname"})

更新

如果您的 url 看起来像 mysite/Edit?id=123,请尝试将您的控制器操作标记为

public ActionResult Edit(string id) 
{ ... 

也可以尝试使用HttpPost 或HttpGet 属性来装饰它

【讨论】:

  • 这不考虑查询字符串值。
  • @Yuck OP 说“不管它们来自哪里,我真的只想在字段中放一些东西,而不需要用户先发布表单”。所以用控制器中的值初始化模型的属性并将实例传递给视图是完全可以的。
  • 这些值将从查询字符串中提取,而不是硬编码。
  • @Yuck 是的,然后他说我已经引用了。
  • @VladL 我最终按照您的建议从 QueryString 初始化模型。不幸的是,在循环中发生这种情况涉及到相当程度的反射代码,而不是对foo.bar.baz = HttpContext.Request.QueryString["foo.bar.baz"] 的大型且维护密集型列表进行硬编码。我不知道我是否足够强调我想循环执行——但我确实提到过两次,而且提到TrySetModelState() 几乎与它不一致。
猜你喜欢
  • 1970-01-01
  • 2010-12-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多