gpt4 book ai didi

asp.net-mvc - 如何用不同的名称绑定(bind) View 模型属性

转载 作者:行者123 更新时间:2023-12-03 13:33:25 32 4
gpt4 key购买 nike

是否有一种方法可以对 View 模型属性进行反射,使其作为html端具有不同名称和id值的元素。

这是我要实现的主要问题。因此,该问题的基本介绍如下:

1-我有一个 View 模型(作为示例),它是为 View 侧的过滤操作创建的。

public class FilterViewModel
{
public string FilterParameter { get; set; }
}

2-我有一个为获取表单值而创建的 Controller Action (这里是过滤器)
public ActionResult Index(FilterViewModel filter)
{
return View();
}

3-我认为用户可以过滤某些数据并通过querystring在表单提交上发送参数。
@using (Html.BeginForm("Index", "Demo", FormMethod.Get))
{
@Html.LabelFor(model => model.FilterParameter)
@Html.EditorFor(model => model.FilterParameter)
<input type="submit" value="Do Filter" />
}

4-我想在渲染 View 输出中看到的是
<form action="/Demo" method="get">
<label for="fp">FilterParameter</label>
<input id="fp" name="fp" type="text" />
<input type="submit" value="Do Filter" />
</form>

5-作为解决方案,我想像这样修改我的 View 模型:
public class FilterViewModel
{
[BindParameter("fp")]
[BindParameter("filter")] // this one extra alias
[BindParameter("param")] //this one extra alias
public string FilterParameter { get; set; }
}

因此,基本问题是有关BindAttribute的问题,但有关复杂类型属性的用法。但是,如果有内置的方法,这样做会更好。
内置的优点:

1-与TextBoxFor,EditorFor,LabelFor和其他强类型 View 模型一起使用时,帮助者可以相互理解和更好地沟通。

2-网址路由支持

3-没有框架通过设计问题:

In general, we recommend folks don’t write custom model binders because they’re difficult to get right and they’re rarely needed. The issue I’m discussing in this post might be one of those cases where it’s warranted.



Link of quote

经过研究,我发现这些有用的作品:

Binding model property with different name

One step upgrade of first link

Here some informative guide

结果:但是他们都不给我确切的解决方案。我正在寻找针对此问题的强类型解决方案。当然,如果您知道其他方法,请分享。

更新

我要这样做的根本原因基本上是:

1-每次我想更改html控件名称时,都必须在编译时更改PropertyName。 (在代码中更改字符串与更改属性名称有所不同)

2-我想向最终用户隐藏(伪装)不动产名称。大多数情况下, View 模型属性名称与映射的实体对象属性名称相同。 (出于开发人员可读性的原因)

3-我不想删除开发人员的可读性。考虑许多具有2-3个字符长且具有mo含义的属性。

4-写了很多 View 模型。因此,更改其名称将比该解决方案花费更多的时间。

5-到目前为止,这将是比其他问题中描述的其他方法更好的解决方案(在我的POV中)。

最佳答案

实际上,有一种方法可以做到。

在ASP.NET中,绑定(bind)是由 TypeDescriptor 收集的元数据,而不是直接通过反射收集的。更为珍贵的是,使用了 AssociatedMetadataTypeTypeDescriptionProvider ,它依次以我们的模型类型作为参数来调用TypeDescriptor.GetProvider:

public AssociatedMetadataTypeTypeDescriptionProvider(Type type)
: base(TypeDescriptor.GetProvider(type))
{
}

因此,我们所需要做的就是为模型设置自定义 TypeDescriptionProvider

让我们实现我们的自定义提供程序。首先,让我们为自定义属性名称定义属性:
[AttributeUsage(AttributeTargets.Property)]
public class CustomBindingNameAttribute : Attribute
{
public CustomBindingNameAttribute(string propertyName)
{
this.PropertyName = propertyName;
}

public string PropertyName { get; private set; }
}

如果您已经具有具有所需名称的属性,则可以重复使用它。上面定义的属性仅是示例。我更喜欢使用 JsonPropertyAttribute ,因为在大多数情况下,我使用json和Newtonsoft的库,并且只想定义一次自定义名称。

下一步是定义自定义类型描述符。我们将不会实现整个类型描述符逻辑,而将使用默认实现。仅属性访问将被覆盖:
public class MyTypeDescription : CustomTypeDescriptor
{
public MyTypeDescription(ICustomTypeDescriptor parent)
: base(parent)
{
}

public override PropertyDescriptorCollection GetProperties()
{
return Wrap(base.GetProperties());
}

public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
return Wrap(base.GetProperties(attributes));
}

private static PropertyDescriptorCollection Wrap(PropertyDescriptorCollection src)
{
var wrapped = src.Cast<PropertyDescriptor>()
.Select(pd => (PropertyDescriptor)new MyPropertyDescriptor(pd))
.ToArray();

return new PropertyDescriptorCollection(wrapped);
}
}

还需要实现自定义属性描述符。同样,除属性名称之外的所有内容都将由默认描述符处理。注意,由于某种原因, NameHashCode是一个单独的属性。由于名称更改,因此它的哈希码也需要更改:
public class MyPropertyDescriptor : PropertyDescriptor
{
private readonly PropertyDescriptor _descr;
private readonly string _name;

public MyPropertyDescriptor(PropertyDescriptor descr)
: base(descr)
{
this._descr = descr;

var customBindingName = this._descr.Attributes[typeof(CustomBindingNameAttribute)] as CustomBindingNameAttribute;
this._name = customBindingName != null ? customBindingName.PropertyName : this._descr.Name;
}

public override string Name
{
get { return this._name; }
}

protected override int NameHashCode
{
get { return this.Name.GetHashCode(); }
}

public override bool CanResetValue(object component)
{
return this._descr.CanResetValue(component);
}

public override object GetValue(object component)
{
return this._descr.GetValue(component);
}

public override void ResetValue(object component)
{
this._descr.ResetValue(component);
}

public override void SetValue(object component, object value)
{
this._descr.SetValue(component, value);
}

public override bool ShouldSerializeValue(object component)
{
return this._descr.ShouldSerializeValue(component);
}

public override Type ComponentType
{
get { return this._descr.ComponentType; }
}

public override bool IsReadOnly
{
get { return this._descr.IsReadOnly; }
}

public override Type PropertyType
{
get { return this._descr.PropertyType; }
}
}

最后,我们需要我们的自定义 TypeDescriptionProvider 以及将其绑定(bind)到我们的模型类型的方法。默认情况下, TypeDescriptionProviderAttribute 设计为执行该绑定(bind)。但是在这种情况下,我们将无法获得要在内部使用的默认提供程序。在大多数情况下,默认提供程序将为 ReflectTypeDescriptionProvider。但这不能保证,并且由于提供程序的保护级别( internal),因此无法访问此提供程序。但是我们仍然希望回退到默认提供程序。
TypeDescriptor还允许通过 AddProvider 方法为我们的类型显式添加提供程序。那就是我们将要使用的。但是首先,让我们定义自定义提供程序本身:
public class MyTypeDescriptionProvider : TypeDescriptionProvider
{
private readonly TypeDescriptionProvider _defaultProvider;

public MyTypeDescriptionProvider(TypeDescriptionProvider defaultProvider)
{
this._defaultProvider = defaultProvider;
}

public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)
{
return new MyTypeDescription(this._defaultProvider.GetTypeDescriptor(objectType, instance));
}
}

最后一步是将我们的提供程序绑定(bind)到我们的模型类型。我们可以按照我们想要的任何方式实现它。例如,让我们定义一些简单的类,例如:
public static class TypeDescriptorsConfig
{
public static void InitializeCustomTypeDescriptorProvider()
{
// Assume, this code and all models are in one assembly
var types = Assembly.GetExecutingAssembly().GetTypes()
.Where(t => t.GetProperties().Any(p => p.IsDefined(typeof(CustomBindingNameAttribute))));

foreach (var type in types)
{
var defaultProvider = TypeDescriptor.GetProvider(type);
TypeDescriptor.AddProvider(new MyTypeDescriptionProvider(defaultProvider), type);
}
}
}

然后通过网络激活来调用该代码:
[assembly: PreApplicationStartMethod(typeof(TypeDescriptorsConfig), "InitializeCustomTypeDescriptorProvider")]

或者简单地用 Application_Start方法调用它:
public class MvcApplication : HttpApplication
{
protected void Application_Start()
{
TypeDescriptorsConfig.InitializeCustomTypeDescriptorProvider();

// rest of init code ...
}
}

但这还不是故事的结局。 :(

考虑以下模型:
public class TestModel
{
[CustomBindingName("actual_name")]
[DisplayName("Yay!")]
public string TestProperty { get; set; }
}

如果我们尝试在 .cshtml View 中编写如下内容:
@model Some.Namespace.TestModel
@Html.DisplayNameFor(x => x.TestProperty) @* fail *@

我们将获得 ArgumentException:

An exception of type 'System.ArgumentException' occurred in System.Web.Mvc.dll but was not handled in user code

Additional information: The property Some.Namespace.TestModel.TestProperty could not be found.



那是因为所有帮助者迟早都会调用 ModelMetadata.FromLambdaExpression 方法。这个方法采用我们提供的表达式( x => x.TestProperty),直接从成员信息中获取成员名称,对我们的任何属性元数据一无所知(谁在乎,呵呵):
internal static ModelMetadata FromLambdaExpression<TParameter, TValue>(/* ... */)
{
// ...

case ExpressionType.MemberAccess:
MemberExpression memberExpression = (MemberExpression) expression.Body;
propertyName = memberExpression.Member is PropertyInfo ? memberExpression.Member.Name : (string) null;
// I want to cry here - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^

// ...
}

对于 x => x.TestProperty(其中 xTestModel),此方法将返回 TestProperty,而不是 actual_name,但是模型元数据包含 actual_name属性,没有 TestProperty。这就是为什么 the property could not be found错误引发的原因。

这是设计失败。

但是,尽管有一些不便,但还是有几种解决方法,例如:
  • 最简单的方法是通过其重新定义的名称访问我们的成员:
    @model Some.Namespace.TestModel
    @Html.DisplayName("actual_name") @* this will render "Yay!" *@

    不是很好。完全没有智能,随着我们模型的改变,我们将不会有任何编译错误。在任何更改上,任何东西都可能被破坏,没有简单的方法可以检测到。
  • 另一种方法稍微复杂一些-我们可以创建自己的帮助程序版本,并禁止任何人为具有重命名属性的模型类调用默认帮助程序或ModelMetadata.FromLambdaExpression
  • 最后,将前两者结合起来是更可取的:编写自己的类似物以获取具有重定义支持的属性名称,然后将其传递给默认帮助器。像这样:
    @model Some.Namespace.TestModel
    @Html.DisplayName(Html.For(x => x.TestProperty))

    编译时和智能感知支持,无需花费大量时间来获得完整的帮助程序集。利润!

  • 同样,上述所有内容都像模型绑定(bind)的附件一样起作用。在模型绑定(bind)过程中,默认绑定(bind)器还使用 TypeDescriptor收集的元数据。

    但是我想绑定(bind)json数据是最好的用例。您知道,许多Web软件和标准都使用 lowercase_separated_by_underscores命名约定。不幸的是,这不是C#的常规约定。使用以不同约定命名的成员的类看起来很丑,并且可能导致麻烦。尤其是当您拥有每次都在抱怨违反命名的工具时。

    ASP.NET MVC默认模型绑定(bind)器不会将json绑定(bind)为与调用newtonsoft的 JsonConverter.DeserializeObject方法时所用的相同方法。而是将json解析为字典。例如:
    {
    complex: {
    text: "blabla",
    value: 12.34
    },
    num: 1
    }

    将被翻译成以下字典:

    { "complex.text", "blabla" }
    { "complex.value", "12.34" }
    { "num", "1" }

    然后,这些值以及由 IValueProvider 的不同实现收集的来自查询字符串,路由数据等的其他值,将默认由绑定(bind)程序用于结合由 TypeDescriptor收集的元数据来绑定(bind)模型。

    因此,我们从创建模型,渲染,将其绑定(bind)回并使用它来进行了全面研究。

    关于asp.net-mvc - 如何用不同的名称绑定(bind) View 模型属性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20869735/

    32 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com