.Net Core MVC对象绑定简介

介绍与解释 (Introduction and Explanation)

Binding objects to fields using .Net Core MVC is one of those things that is so simple that the examples can almost all be taken directly from the code generated by Visual Studio when you tell it to build a new Controller for you. It is as easy as telling Razor what field your model is and what field to bind to so a basic edit form can be as simple as:

使用.Net Core MVC将对象绑定到字段是很简单的事情之一,当您告诉它为您构建新的Controller时,几乎所有示例都可以直接从Visual Studio生成的代码中获取。 告诉Razor您的模型是哪个字段以及绑定到哪个字段就很容易,因此基本的编辑表单可以很简单:

@model StarMap.Models.Atmosphere
...<div class="form-group">
<label asp-for="PlanetaryTemperature" class="control-label"></label>
<input asp-for="PlanetaryTemperature" class="form-control" />
<span asp-validation-for="PlanetaryTemperature" class="text-danger"></span>
</div>

With the controller having:

控制器具有:

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(Guid id,
[Bind("Id,Created,LastUpdated,TimeStamp,Name," +
"AtmospherePrimary,AtmosphereComposition," +
"PlanetaryTemperature")] Atmosphere atmosphere)
{ . . .

This is as far as many examples take the idea of binding objects. A hand-wave at the suggestion that the Foo object might have additional properties, and these properties would bind as quickly as the Name property did.

就许多示例而言,这就是绑定对象的想法。 建议Foo对象可能具有其他属性,这些属性将与Name属性一样快地绑定。

So, what exactly is binding? It is merely a term for telling one part of the MVC application how to fill out an object. It is passing to another part of the MVC subsystem. For base C# types and most .Net Core types, this is transparent to the person writing the code. You write some code as I did above, using property names — Foo.Name is the only property of the Foo class. The compiler and libraries are smart enough to build all of the hooks behind the scenes to make it work.

那么,绑定到底是什么? 它仅是一个术语,用于告诉MVC应用程序的一部分如何填充对象。 它传递给MVC子系统的另一部分。 对于基本的C#类型和大多数.Net Core类型,这对于编写代码的人是透明的。 您可以像上面一样使用属性名称编写一些代码-Foo.Name是Foo类的唯一属性。 编译器和库足够聪明,可以在后台构建所有挂钩以使其正常工作。

There are a few problems with this. First, not all objects are base types. When you get to your own objects that have complex values? You are on your own if they don’t strictly break down into base or standard .Net types. So if you attempt to use that slick third-party Vector class or a handy Unit class? The examples are silent, the documentation is silent, and web searches turn up examples from three or more years ago that no longer even compile with the newest versions of .Net.

这有一些问题。 首先,并非所有对象都是基本类型。 当您到达具有复杂值的对象时? 如果它们没有严格分解为基本或标准.Net类型,则您是自己一个人。 因此,如果您尝试使用光滑的第三方Vector类或方便的Unit类? 这些示例是无声的,文档是无声的,并且网络搜索出现了三年前或更多年前的示例,这些示例甚至不再使用最新版本的.Net进行编译。

So, since I just spent the last several days digging in the documentation and stepping through code looking at how this works. This will be an example that with any luck is straightforward enough to help in a wide range of cases while still being simple enough not to overwhelm anyone with the complexities of .Net Core MVC binding. There is quite a bit more to this subject than I am going to cover today, but I want to start with an introduction before I try to explain the more complex cases.

因此,由于我刚刚花了几天的时间来研究文档并逐步查看代码,以了解其工作原理。 这将是一个例子,运气足够简单,可以在各种情况下提供帮助,同时仍然足够简单,不会让任何人错失.Net Core MVC绑定的复杂性。 这个主题比我今天要讲的要多得多,但是在尝试解释更复杂的情况之前,我想先做一个介绍。

Image for post
https://pixabay.com/users/PublicDomainPictures-14/?utm_source=link-attribution&amp;utm_medium=referral&amp;utm_campaign=image&amp;utm_content=2264">PublicDomainPictures</a> from <a href=” https://pixabay.com/users/PublicDomainPictures-14/?utm_source=link-attribution&amp;utm_medium=referral&amp;utm_campaign=image&amp;utm_content=2264 "> PublicDomainPictures </a>来自<a href =” https://pixabay.com/?utm_source=link-attribution&amp;utm_medium=referral&amp;utm_campaign=image&amp;utm_content=2264">Pixabay</a> https://pixabay.com/?utm_source=link-attribution&amp;utm_medium=referral&amp;utm_campaign=image&amp;utm_content=2264 “> Pixabay </a>

手动绑定-失败 (Binding Manually — the failures)

So you ran into a case where your fancy object just returned its default value. Using, for example, what I ran into on Monday, my Temperature object returned zero Kalvin, no matter what value I typed into the form that was supposed to fill in the Temperature. I had a block of code that looked like this for my form:

因此,您遇到了一种情况,其中您的精美对象刚刚返回了其默认值。 例如,使用我周一遇到的问题,无论我在应该填写温度的表格中键入什么值,我的Temperature对象都返回零卡尔文。 我的表单中有一段代码如下:

@model StarMap.Models.Atmosphere

<div class=”form-group”>
<label asp-for=”PlanetaryTemperature” class=”control-label”></label>
<input asp-for=”PlanetaryTemperature” class=”form-control” />
<span asp-validation-for=”PlanetaryTemperature” class=”text-danger”></span>
</div>

controller:

控制器:

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(Guid id,[Bind(“Id,Created,LastUpdated,TimeStamp,Name,” +
“AtmospherePrimary,AtmosphereComposition,” +
“PlanetaryTemperature”)] Atmosphere atmosphere)
{ . . .

and model objects:

和模型对象:

public Temperature PlanetaryTemperature { get; set; }

Where the form and controller had been auto-generated, so I had figured they were okay. I spent a good part of Monday stepping through the code, trying to figure out why it wasn’t calling the data context objects conversion routine. Except on the initial load from the database before I realized that, duh, it was a data context object, it only happened when being loaded from the database. So, armed with that bit of insight, that there had to be an equivalent to the Entity<>.HasConversion() settings for Entity Framework I set to looking, and looking, and looking, and downloading code.

自动生成表单和控制器的位置,因此我认为它们还可以。 我在星期一的大部分时间里都逐步浏览了代码,试图弄清楚为什么它没有调用数据上下文对象转换例程。 除了从数据库进行初始加载之前,我还没有意识到这是一个数据上下文对象,它仅在从数据库加载时才发生。 因此,基于这一点,我必须将实体框架的Entity <>。HasConversion()设置等同于我设置为查看,查看和下载的代码。

As an aside, did you know you can get the whole .Net Core as a GitHub project? I suppose most people do by now, but it’s not large and is a useful thing to have for reference. I got it and read the part on binding, along with the associated documents and examples. It is interesting, but I would not recommend it for light reading, it’s as you would expect, slightly dense complex code, although not as bad as some I’ve seen.

顺便说一句,您是否知道可以将整个.Net Core作为GitHub项目获得? 我想现在大多数人都这样做了,但是它并不大,值得参考。 我明白了,并阅读了有关装订的部分以及相关的文档和示例。 这很有趣,但是我不建议您阅读它,就像您期望的那样,它是稍微密集的复杂代码,尽管没有我所看到的那么糟糕。

Anyway, I found three possible routes, one that was frankly a hack, and two that appeared to be .Net Core sanctioned. It was late, I was tired, I stuffed the hack into my code, and it worked, the hack involved making the following changes.

无论如何,我发现了三种可能的途径,其中一种坦率地说是黑客入侵,另外两种似乎是.Net Core认可的途径。 太晚了,我很累了,我将hack塞进了我的代码中,并且它起作用了,该hack涉及了以下更改。

1) Add a string property to my model object:

1)向我的模型对象添加一个字符串属性:

[NotMapped]
public string Temp
{
get { return PlanetaryTemperature.ToString(); }
set { PlanetaryTemperature = ConvertFromString(value); }
}

2) Add the ConvertFromString() method:

2)添加ConvertFromString()方法:

private Temperature ConvertFromString(string s)
{
Temperature retVal;
QuantityTypeConverter<Temperature> converter = new
QuantityTypeConverter<Temperature>(Temperature.Zero);
Object obj = converter.ConvertFromString(s);
retVal = obj as Temperature;
return retVal;
}

3) Changed the form to:

3)将表格更改为:

<input asp-for=”Temp” class=”form-control” />
<span asp-validation-for=”Temp” class=”text-danger”></span>

4) Changed the Bind attribute to:

4)将Bind属性更改为:

[Bind(“Id,Created,LastUpdated,TimeStamp,Name,” +
“AtmospherePrimary,AtmosphereComposition,” +
“Temp”)] Atmosphere atmosphere)

An ugly hack but fooled the binding subsystem into treating my complex Temperature object like it was a simple string object long enough to do the conversion between the two types. I went to bed unsatisfied with this result, but okay with the code working.

一个丑陋的骇客,但是欺骗了绑定子系统,将我复杂的Temperature对象当作一个简单的字符串对象,足够长时间来在两种类型之间进行转换。 我对这个结果不满意,但是代码正常工作了。

I will state for the record when I was younger, or under pressure to finish a project, this hack would have stayed in place. This hack is the type of ugly crap that causes technical debt, a workaround that only works because of a loophole in an API or framework that will fail if the provider of that tool ever closes the loophole. Unless you have to do things like this, don’t do it, find the way the tool provides to handle the situation. That’s what documentation, support, and internet searches are for, use them. In the worst-case step into the code and look at what it is doing, enough code is open-source now that you may be able to get the source even if you are not licensed to modify it.

我会在年轻时声明,或在完成项目的压力下作记录,此hack会一直存在。 这种hack是导致技术债务的丑陋废话的一种,这种变通办法仅是由于API或框架中的漏洞而起作用,如果该工具或提供者关闭漏洞,该漏洞将失败。 除非您必须做这样的事情,否则不要做,请找到该工具提供的处理情况的方法。 这就是文档,支持和Internet搜索的用途,请使用它们。 在最坏的情况下,进入代码并查看其功能,现在足够多的代码是开源的,即使您没有获得修改的许可,也可以获取源代码。

With that this morning I commented out the above hack and looked more closely at the two possible real solutions. The first and most tempting was something called a “BindingSourceValueProvider,” there is an almost useful example that you can get from GitHub. I say almost useful because it overrides the Cookie provider, which is about the least helpful kind of provider it could override. After all, there are existing objects to do all of the fiddly parts needed inside the example class. So it is impossible to tell which methods or properties need to be implemented by the new class and which are specific to a Cookie provider. Between that and the detail that the only type of object I could return was an IEnumerator<KeyValue<string, string>>, and what I was trying to convert from was a string this path turned out to be easy to get into but impossible to reach the destination. Starting down the path is as easy as adding your own ValueProviderFactory object instance to the MvcOptions when you call AddRazorPages in your startup like:

今天早上,我评论了上述漏洞,并更仔细地研究了两种可能的实际解决方案。 第一个也是最诱人的是一个叫做“ BindingSourceValueProvider”的东西,您可以从GitHub获得一个几乎有用的示例。 我说几乎有用,因为它覆盖了Cookie提供程序,这是它可以覆盖的最不有用的提供程序类型。 毕竟,示例类内部有一些现成的对象来完成所有必要的操作。 因此,无法确定新类需要实现哪些方法或属性,以及Cookie提供者特定的方法或属性。 在那和我唯一可以返回的对象类型的细节之间是IEnumerator <KeyValue <string,string >>,而我试图从中转换的是一个字符串,该路径原来很容易进入但不可能到达目的地。 沿着路径开始就像在启动时调用AddRazorPages时将自己的ValueProviderFactory对象实例添加到MvcOptions一样容易:

services.AddRazorPages()
.AddMvcOptions( options =>
{
options.ValueProviderFactories.Add(new
TemperatureValueProviderFactory());
});public class TemperatureValueProviderFactory : IValueProviderFactory
{
public Task CreateValueProviderAsync(
ValueProviderFactoryContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}var valueProvider = new TemperatureValueProvider(
BindingSource.ModelBinding,CultureInfo.InvariantCulture);

context.ValueProviders.Add(valueProvider);return Task.CompletedTask;
}
}

Unfortunately, it is the ValueProvider object that is less clear about its intent, and I never got one working since it was unclear even with walking through the code exactly what the various parts were supposed to be doing. The class definition was :

不幸的是,ValueProvider对象的意图不太清楚,而且我什至没有工作,因为即使遍历代码后各个部分应该做什么,也不清楚。 类的定义是:

public class TemperatureValueProvider : BindingSourceValueProvider

Three methods must be overridden, ContainsPrefix, GetKeysFromPrefix, and GetValue the first two were evident from the context, and I thought the third was also until I discovered that the only value it could return consisted entirely of strings.

必须重写三个方法,ContainsPrefix,GetKeysFromPrefix和GetValue,前两个是从上下文中显而易见的,我认为第三个方法也是直到我发现它可以返回的唯一值完全由字符串组成。

At this point, I moved on to my last hope, the IModelBinder object. I had held off on this because there is precisely one example that I was able to find, and that example bound an entire object made up of basic types. So, it was back to the most simplistic of the example types, plus reading code and stepping through code in the debugger.

至此,我继续了我的最后一个希望,即IModelBinder对象。 我之所以没有这么做,是因为我确实找到了一个示例,并且该示例绑定了由基本类型组成的整个对象。 因此,它回到了最简单的示例类型,再加上在调试器中读取代码和逐步执行代码。

Image for post
https://pixabay.com/users/StartupStockPhotos-690514/?utm_source=link-attribution&amp;utm_medium=referral&amp;utm_campaign=image&amp;utm_content=593313">StartupStockPhotos</a> from <a href=” https://pixabay.com/users/StartupStockPhotos-690514/?utm_source=link-attribution&amp;utm_medium=referral&amp;utm_campaign=image&amp;utm_content=593313 "> StartupStockPhotos </a>来自<a href =” https://pixabay.com/?utm_source=link-attribution&amp;utm_medium=referral&amp;utm_campaign=image&amp;utm_content=593313">Pixabay</a> https://pixabay.com/?utm_source=link-attribution&amp;utm_medium=referral&amp;utm_campaign=image&amp;utm_content=593313 “> Pixabay </a>

手动绑定-成功 (Binding Manually — success)

Skipping all of the blind alleys I took here is what I ended up with, along with an explanation for what I discovered and why.

我最终跳过了我在这里走过的所有盲区,并解释了我发现的原因以及原因。

First, you need an IModelBinder object; mine looks like this:

首先,您需要一个IModelBinder对象; 我的看起来像这样:

public class TemperatureEntityBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if(bindingContext is null)
{
throw new ArgumentNullException(
$"{nameof(bindingContext)} must not be null");
}
// ModelName is a horrible name, this is the Property Name
// of the property in the Model you are looking up.
// If you use the Model name, or the Type name of the
// object you are looking for this will fail right here
// due to not finding the correct value provider with this
// lookup.
string propertyName = bindingContext.ModelName;
var valueProviderResult =
bindingContext.ValueProvider.GetValue(propertyName);if (valueProviderResult == ValueProviderResult.None)
{
return Task.CompletedTask;
}bindingContext.ModelState.SetModelValue(propertyName,
valueProviderResult);var value = valueProviderResult.FirstValue;// Check if the argument value is null or empty
if (string.IsNullOrEmpty(value))
{
return Task.CompletedTask;
}if (!Temperature.TryParse(value, out Temperature retValue))
{
// Non-integer arguments result in model state errors
bindingContext.ModelState.TryAddModelError(
propertyName,
"value must be parsable as a Temperature.");return Task.CompletedTask;
}bindingContext.Result=ModelBindingResult.Success(retValue);
return Task.CompletedTask;
}
}

You also need a factory object:

您还需要一个工厂对象:

public class TemperatureBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder( ModelBinderProviderContext
context)
{
if(context == null)
{
throw new ArgumentNullException(nameof(context));
}if(context.Metadata.ModelType == typeof(Temperature))
{
return new BinderTypeModelBinder(
typeof(TemperatureEntityBinder));
}
return null;
}
}

In addition to Razor Pages configuration:

除了Razor Pages配置之外:

services.AddRazorPages().AddMvcOptions( options =>
{
options.ModelBinderProviders.Add(new TemperatureBinderProvider());
});

And finally adding the Bind Property attribute to the model object like this:

最后,将Bind Property属性添加到模型对象,如下所示:

[BindProperty(BinderType = typeof(TemperatureEntityBinder), Name = “PlanetaryTemperature”)]

Things to note that tripped me, first the Binder Type is the type of the object that performs the translation, not the object you are binding to, the first pass I put the wrong type in this field, and it crashed spectacularly — I had to reboot to get IIS to start again it crashed so hard. Second, the name is the name of the property, not the name of the type you are binding to; this doesn’t cause crashes; the values never get set correctly. There is one more field, “SupportsGet,” which my project does not need yet. You will need to set this to true if your project will need to be able to bind using this property in a Get handler in a Controller.

让我感到震惊的是,首先,活页夹类型是执行翻译的对象的类型,而不是要绑定到的对象,我在此字段中输入的错误类型是第一遍,然后它崩溃了-我不得不重新启动以使IIS重新启动,它崩溃得如此困难。 其次,名称是属性的名称,而不是绑定到的类型的名称; 这不会导致崩溃; 这些值永远不会正确设置。 还有一个字段“ SupportsGet”,我的项目现在不需要。 如果您的项目需要能够在Controller的Get处理程序中使用此属性进行绑定,则需要将其设置为true。

With this combination, I can go back to my original code for the form:

通过这种组合,我可以返回表单的原始代码:

@model StarMap.Models.Atmosphere

<div class=”form-group”>
<label asp-for=”PlanetaryTemperature” class=”control-label”></label>
<input asp-for=”PlanetaryTemperature” class=”form-control” />
<span asp-validation-for=”PlanetaryTemperature” class=”text-danger”></span>
</div>

controller:

控制器:

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(Guid id,[Bind(“Id,Created,LastUpdated,TimeStamp,Name,” +
“AtmospherePrimary,AtmosphereComposition,” +
“PlanetaryTemperature”)] Atmosphere atmosphere)
{ . . .

While the model now looks like:

现在该模型如下所示:

[BindProperty(BinderType=typeof(TemperatureEntityBinder),
Name=”PlanetaryTemperature”)]
public Temperature PlanetaryTemperature { get; set; }

With these changes editing or creating an object that contains a Temperature object gets the correct value entered into the form on the Razor page or view.

通过这些更改,编辑或创建包含“温度”对象的对象将获得在“剃刀”页面或视图上输入到表单中的正确值。

结论 (Conclusion)

Like so much with .Net Core, once you understand the background and do the setup, write support classes and configure everything correctly using the feature is simple. What I went through was recreating for the “Temperature” class, the functionality that exists for the “DateTime” class, or the int type. Once you have done this work and added it to the list of bindings, the use is as simple as adding the property name to the Binding list in your controller.

与.Net Core一样,一旦您了解背景并进行设置,编写支持类并使用该功能正确配置所有内容,就很简单。 我所经历的是为“温度”类,“日期时间”类或int类型创建的功能。 完成这项工作并将其添加到绑定列表后,使用就像将属性名称添加到控制器中的“绑定”列表一样简单。

I hope that this article helps demystify an area of .Net Core that isn’t well represented in tutorials or articles.

我希望本文有助于揭开教程或文章中未很好体现的.Net Core领域的神秘面纱。

Good luck, and have fun.

祝好运并玩得开心点。

翻译自: https://medium.com/the-innovation/introduction-to-net-core-mvc-object-binding-6d0cb623a961

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值