【发布时间】: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