C# 请注意,这项工作基于上述答案,使用QueryStringAlias示例
目前,在TestModelType具有复杂嵌套类型的情况下,这可能会失败。理想情况下,还有其他一些事情:C# 请注意,这项工作基于上述答案,使用QueryStringAlias示例,c#,asp.net-mvc,asp.net-mvc-2,C#,Asp.net Mvc,Asp.net Mvc 2,目前,在TestModelType具有复杂嵌套类型的情况下,这可能会失败。理想情况下,还有其他一些事情: 可靠地处理复杂的嵌套类型 在类上启用属性以激活IModelBuilder,而不是在注册中 使同一IModelBuilder同时在控制器和APIController中工作 但现在我满足于我自己的需要。希望有人觉得这篇文章很有用。这可能是对安德拉斯·佐尔坦答案的简短评论,但没有足够的声誉,对不起 谢谢你的解决方案,我刚刚用过,它仍然很好用!但是,我的某些属性的别名相同,但大小写不同,例如 [
- 可靠地处理复杂的嵌套类型
- 在类上启用属性以激活IModelBuilder,而不是在注册中
- 使同一IModelBuilder同时在控制器和APIController中工作
但现在我满足于我自己的需要。希望有人觉得这篇文章很有用。这可能是对安德拉斯·佐尔坦答案的简短评论,但没有足够的声誉,对不起 谢谢你的解决方案,我刚刚用过,它仍然很好用!但是,我的某些属性的别名相同,但大小写不同,例如
[BindAlias("signature")]
public string Signature { get; set; }
当自定义模型绑定器尝试将别名添加到模型时,会引发错误
PropertyMetadata字典,因为基本模型绑定器已经添加了它们的主要属性名版本,并且模型绑定不区分大小写
要解决此问题,只需执行不区分大小写的检查-
替换
if (bindingContext.PropertyMetadata.ContainsKey(p.Name))
与
啊,是的-但这是操作方法上的一个参数-这是操作方法参数上的一个属性…将
BindAttribute
放在属性上是不可能的,因为它仅对类或参数声明有效。不过,这将是一个完美的解决方案——正是我一直在寻找的那种东西。@AndrasZoltan哦,没错。我的错误,我不确定那一个。手动组合模型参数和longPropertyName参数可能是最快的选择。自定义模型绑定器可能是“最干净的”。最后,我扩展了类型上已经存在的模型绑定器,方法是在它和DefaultModelBinder
之间插入一个基,从应用于任何要“别名”的属性的属性中添加额外的PropertyDescriptor
s。同时,它还将同一属性的PropertyMetaData克隆回BindingContext中,这样当模型绑定逻辑开始通过别名读取/写入值时,它就可以正常工作了。我现在可以对任何其他类型的代码重复使用它了——而且它只被告知了大约50行代码——哦,我的q字符串再次是合法的!是的,很相似。我将在不久的将来发布我的答案,以便我们能够进行比较和对比:)我已经发布了我的代码。我的版本没有绑定到Request.Form,因为它将属性添加到模型的元数据中,所以所有其他绑定机制仍然被使用(值提供者等)。它适用于所有类型,可以作为全局绑定器的替代品,也可以作为您自己的自定义绑定器的基础。如何生成出站URL,并考虑到您的BindAlias属性?@hival我承认这是一件我不需要太担心的事。每当我需要的时候,我通常会手动测量路线值。我确实考虑过一个解决方案(一个定制的RouteValueDictionary
,它可能使用第一个BindAlias
属性作为名称),但它就是这样一个不完美的解决方案,我把它留下了。效果很好,谢谢。这真的应该是框架中默认内容的一部分。感谢此解决方案,它完全符合我的需要,能够扩展我们用于支持此功能的自定义第三方绑定。嘿@AndrasZoltan感谢您的回答。今天的大部分时间我都在尝试在ApiController中实现同样的功能(请参见下面的答案)。看起来已经快10年了,但我很想了解您对此更新的想法。嘿,我最初来到这里的时候,还想知道如何解决asp.net core的完全相同的问题。事情变了,所以我打开了。链接到这里。@NathanCooper cool:)@AndrasZoltan:非常感谢您的精彩问答。我认为你自己的答案应该是公认的答案。。。对于像我这样看到这篇文章并希望通过快速浏览找到正确答案的人来说,这将不会那么令人困惑。很高兴为ApiController和新堆栈提供了一个新的解决方案:)好的建议-我注意到你建议这是一个编辑,不幸的是,被2:1拒绝了。我已经批准编辑它并链接到这个答案,这样人们就可以看到它来自你。
public ActionResult Submit(MyModel myModel, [Bind(Prefix = "L")] string[] longPropertyName) {
if(myModel != null) {
myModel.LongPropertyName = longPropertyName;
}
}
/// <summary>
/// Allows you to create aliases that can be used for model properties at
/// model binding time (i.e. when data comes in from a request).
///
/// The type needs to be using the DefaultModelBinderEx model binder in
/// order for this to work.
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = true)]
public class BindAliasAttribute : Attribute
{
public BindAliasAttribute(string alias)
{
//ommitted: parameter checking
Alias = alias;
}
public string Alias { get; private set; }
}
internal sealed class AliasedPropertyDescriptor : PropertyDescriptor
{
public PropertyDescriptor Inner { get; private set; }
public AliasedPropertyDescriptor(string alias, PropertyDescriptor inner)
: base(alias, null)
{
Inner = inner;
}
public override bool CanResetValue(object component)
{
return Inner.CanResetValue(component);
}
public override Type ComponentType
{
get { return Inner.ComponentType; }
}
public override object GetValue(object component)
{
return Inner.GetValue(component);
}
public override bool IsReadOnly
{
get { return Inner.IsReadOnly; }
}
public override Type PropertyType
{
get { return Inner.PropertyType; }
}
public override void ResetValue(object component)
{
Inner.ResetValue(component);
}
public override void SetValue(object component, object value)
{
Inner.SetValue(component, value);
}
public override bool ShouldSerializeValue(object component)
{
return Inner.ShouldSerializeValue(component);
}
}
public class DefaultModelBinderEx : DefaultModelBinder
{
protected override System.ComponentModel.PropertyDescriptorCollection
GetModelProperties(ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
var toReturn = base.GetModelProperties(controllerContext, bindingContext);
List<PropertyDescriptor> additional = new List<PropertyDescriptor>();
//now look for any aliasable properties in here
foreach (var p in
this.GetTypeDescriptor(controllerContext, bindingContext)
.GetProperties().Cast<PropertyDescriptor>())
{
foreach (var attr in p.Attributes.OfType<BindAliasAttribute>())
{
additional.Add(new AliasedPropertyDescriptor(attr.Alias, p));
if (bindingContext.PropertyMetadata.ContainsKey(p.Name)
&& !string.Equals(p.Name, attr.Alias, StringComparison.OrdinalIgnoreCase)))
{
bindingContext.PropertyMetadata.Add(
attr.Alias,
bindingContext.PropertyMetadata[p.Name]);
}
}
}
return new PropertyDescriptorCollection
(toReturn.Cast<PropertyDescriptor>().Concat(additional).ToArray());
}
}
public class TestModelType
{
[BindAlias("LPN")]
//and you can add multiple aliases
[BindAlias("L")]
//.. ad infinitum
public string LongPropertyName { get; set; }
}
public class MyPropertyBinder : DefaultModelBinder
{
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor)
{
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
for (int i = 0; i < propertyDescriptor.Attributes.Count; i++)
{
if (propertyDescriptor.Attributes[i].GetType() == typeof(BindingNameAttribute))
{
// set property value.
propertyDescriptor.SetValue(bindingContext.Model, controllerContext.HttpContext.Request.Form[(propertyDescriptor.Attributes[i] as BindingNameAttribute).Name]);
break;
}
}
}
}
public class BindingNameAttribute : Attribute
{
public string Name { get; set; }
public BindingNameAttribute()
{
}
}
public class EmployeeViewModel
{
[BindingName(Name = "txtName")]
public string TestProperty
{
get;
set;
}
}
[HttpPost]
public ActionResult SaveEmployee(int Id, [ModelBinder(typeof(MyPropertyBinder))] EmployeeViewModel viewModel)
{
// do stuff here
}
using System.Reflection;
using System.Web;
using System.Web.Http.Controllers;
using System.Web.Http.ModelBinding;
using QueryStringAlias.Attributes;
namespace QueryStringAlias.ModelBinders
{
public class AliasModelBinder : IModelBinder
{
private bool TryAdd(PropertyInfo pi, NameValueCollection nvc, string key, ref object model)
{
if (nvc[key] != null)
{
try
{
pi.SetValue(model, Convert.ChangeType(nvc[key], pi.PropertyType));
return true;
}
catch (Exception e)
{
Debug.WriteLine($"Skipped: {pi.Name}\nReason: {e.Message}");
}
}
return false;
}
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
Type bt = bindingContext.ModelType;
object model = Activator.CreateInstance(bt);
string QueryBody = actionContext.Request.Content.ReadAsStringAsync().Result;
NameValueCollection nvc = HttpUtility.ParseQueryString(QueryBody);
foreach (PropertyInfo pi in bt.GetProperties())
{
if (TryAdd(pi, nvc, pi.Name, ref model))
{
continue;
};
foreach (BindAliasAttribute cad in pi.GetCustomAttributes<BindAliasAttribute>())
{
if (TryAdd(pi, nvc, cad.Alias, ref model))
{
break;
}
}
}
bindingContext.Model = model;
return true;
}
}
}
[HttpPost]
[Route("mytestendpoint")]
[System.Web.Mvc.ValidateAntiForgeryToken]
public async Task<MyApiCallResult> Signup(TestModelType tmt) // note that [FromBody] does not appear in the signature
{
// code happens here
}
[BindAlias("signature")]
public string Signature { get; set; }
if (bindingContext.PropertyMetadata.ContainsKey(p.Name))
if (bindingContext.PropertyMetadata.ContainsKey(p.Name)
&& !string.Equals(p.Name, attr.Alias, StringComparison.OrdinalIgnoreCase))