ASP。NET MVC的部分视图和部分模型

下载source - 1.7 MB

介绍

本文解决了返回视图内容包含表单元素的部分视图的问题。

代码重用是一种非常有用的节省时间的特性,任何优秀的工程师都会在他们的工作过程中构建许多有用的函数。对于Web应用程序来说,反复使用相同的代码是一种常识。然而,在这个例子中,我们也有HTML标记代码。ASP。NET MVC有部分视图、子操作和编辑/显示模板来解决这个问题。部分视图可以使用页面模型作为它们的数据,而子操作则使用来自控制器的独立数据。编辑器/显示模板将项目从模型传递到系统,但是可以被用户部分视图覆盖。本文以一个公司地址为例,其中部分内容是邮政地址,通过这个例子来研究从部分视图发回数据时的问题。当将邮寄地址实现为部分视图时,可以在其他页面(如销售地址)中重用。一旦解决了这个问题,大页面就可以通过将它们分解为几个部分来变得更易于管理。一个很好的例子就是选项卡下的内容。

背景

在深入研究代码之前,我想提供一个简短的背景知识。我是一家小公司的专业软件工程师。从Visual Basic 6开始,我就一直追随微软的潮流。让我兴奋的是,当我解决了一个我认为对未来的项目有用并且对我的同事有益的问题时。这种情况并不经常发生,但当发生时,我将其添加到类库中。我已经在样例项目中包含了其中的一部分。我也用一个笔记来快速记录这些想法和其他工程师的发现的链接。

当您遇到问题时,第一个调用端口是查看其他人是否遇到过问题。所有的工程师都需要有良好的搜索能力。这里有很多论坛,包括代码项目,在着手做自己的事情之前可以获得很多想法。

当使用Microsoft MVC进行Web设计时,大多数功能都包含其中,但偶尔您还需要其他功能。表单区域就是其中之一。Microsoft使用HTML helper来生成表单元素。这些都有很长的路要走,但也有很多遗漏。幸运的是,它们很容易扩展,在互联网上可以找到许多例子。

本文将提出一个问题,并通过首先查看其他人都做了什么,然后生成一个解决这个问题的新HTML helper来解决这个问题。

这个问题

首先让我们看看用于表单的标准MVC控制器模式:

隐藏,复制Code[HttpGet]
public ActionResult Index()
{
// load the test data
TestViewModel model = new TestViewModel();
return View(model);
}

[HttpPost]
public ActionResult Index(TestViewModel model)
{
if (ModelState.IsValid)
{
// save the test data
}
return View(model);
}

HttpGet操作将数据加载到模型中,并调用视图来显示数据。视图在用户可以编辑的表单上显示数据。单击submit按钮将表单发送回控制器的HttpPost操作,该操作将表单上的数据绑定到模型。控制器验证模型,如果一切正常,则保存数据。然后控制器调用相同的视图来显示编辑过的数据。注意,在两个操作中使用相同的模型,坚持此模式可以保证数据绑定即使在包含其他类和/或列表的复杂模型中也可以工作。事实上,如果我们坚持这种模式,这是MVC为我们做的非常聪明的事情之一。

这是一个非常简单的模型:

隐藏,复制Codepublic class TestViewModel
{
public TestModel Test { get; set;}
}

public class TestModel
{
[Display(Name = “Name:”)]
[Required(ErrorMessage = “Please provide a name”)]
public string Name { get; set; }
public TestPartialModel Partial { get; set; }
}

public class TestPartialModel
{
[Display(Name=“Partial Name:”)]
[Required(ErrorMessage=“Please provide a name”)]
public string Name { get; set; }
}

这里是观点:

隐藏,收缩,复制Code@model CSE.Partial.WebApp.Models.TestViewModel
@{
ViewBag.Title = “Test”;
}

@ViewBag.Title

@using (Html.BeginForm()) { @Html.AntiForgeryToken()

@Html.LabelFor(m => m.Test.Name)
@Html.EditorFor(m => m.Test.Name) @Html.ValidationMessageFor(m => m.Test.Name)
  <dt>@Html.LabelFor(m => m.Test.Partial.Name)</dt>
  <dd>
    @Html.EditorFor(m => m.Test.Partial.Name)
    @Html.ValidationMessageFor(m => m.Test.Partial.Name)
  </dd>

  <dt></dt>
  <dd><input class="btn btn-primary" type="submit" value="Save" /></dd>
</dl>
}

这和预期的一样工作,当按下save按钮时,所有输入的值都被发送回控制器,并根据这些值返回新视图。因此,测试只是持久化的值。

下一步是将部分位移动到部分视图中。

_TestPartial,它可以在同一个视图文件夹中,也可以在Shared中。

隐藏,复制Code @model CSE.Partial.WebApp.Models.TestViewModel

@Html.LabelFor(m => m.Test.Partial.Name)
@Html.EditorFor(m => m.Test.Partial.Name) @Html.ValidationMessageFor(m => m.Test.Partial.Name)

现在的观点是

隐藏,复制Code@model CSE.Partial.WebApp.Models.TestViewModel
@{
ViewBag.Title = “Test”;
}

@ViewBag.Title

@using (Html.BeginForm()) { @Html.AntiForgeryToken()

@Html.LabelFor(m => m.Test.Name)
@Html.EditorFor(m => m.Test.Name) @Html.ValidationMessageFor(m => m.Test.Name)
    @Html.Partial("_TestPartial")

   <dt></dt>
   <dd><input class="btn btn-primary" type="submit" value="Save" /></dd>
</dl>
}

当整个模型默认地传递给部分视图时,这将正常工作。但我希望部分视图是可重用的,在上面的例子中,它是绑定到页面的模型。

让我们试着把部分模型传递给部分视图。修改部分视图以使用它自己的模型:

隐藏,复制Code@model CSE.Partial.Service.Models.TestPartialModel

@Html.LabelFor(m => m.Name)
@Html.EditorFor(m => m.Name) @Html.ValidationMessageFor(m => m.Name)

从页面视图传递正确的模型:

隐藏,复制Code@Html.Partial("_TestPartial", Model.Test.Partial)

现在,当你发布回有一个例外。在检查发回的数据时,部分模型丢失了。这就是问题所在!

解决这个问题

到底出了什么问题?当您查看使用F12工具或视图源生成的HTML时,这一点非常明显。工作测试中出现的元素命名在最终测试中是不同的。

如此:

隐藏,复制Code

这个操作失败:

隐藏,复制Code

在部分HTML中缺少名称前缀。真的非常明显;它怎么可能知道它是一个更大模型的一部分呢!

搜索一下论坛就会发现其他人也在做同样的事情,我们发现MVC是一个叫做TemplateInfo的类。它有一个名为HtmlFieldPrefix的属性。如果我们在调用部分视图之前设置这个,我们可以强制所有元素名称都有前缀。

隐藏,复制Code@{ Html.ViewData.TemplateInfo.HtmlFieldPrefix = “Test.Partial”; }
@Html.Partial("_TestPartial", Model.Test.Partial)

这在这个场景中是可行的,但是分部之后的任何元素也会得到前缀。

然后是@Html中的第三个参数。Partial(, ViewDataDictionary)。我们可以传递一个全新的ViewData到HtmlFieldPrefix设置为上面的部分视图。

隐藏,复制Code@Html.Partial("_TestPartial", Model.Test.Partial, new ViewDataDictionary
{
TemplateInfo = new System.Web.Mvc.TemplateInfo
{
HtmlFieldPrefix = “Test.Partial”
}
})

这样就将前缀与部分视图隔离开来。但是现在模型状态和视图数据丢失了!错误信息现在不会出现。在默认传递的第3个参数中有很多东西丢失了。如果我们首先获取视图数据的副本,然后修改TemplateInfo,这应该可以解决这个问题。ViewDataDictionary构造函数将复制Html。显示数据。

隐藏,复制Code@Html.Partial("_TestPartial",
Model.Test.Partial,
new ViewDataDictionary(Html.ViewData)
{
TemplateInfo = new System.Web.Mvc.TemplateInfo
{
HtmlFieldPrefix = “Test.Partial”
}
})

还有最后一个问题。如果我们对嵌套的部分使用这种技术,前缀将不得不被追加而不是设置。我在MVC中找到了一个方法来做这个。这是一个可行的解决方案。

隐藏,复制Code@{
string name = Html.NameFor(m => m.Test.Partial).ToString();
string prefix = Html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);
ViewDataDictionary viewData = new ViewDataDictionary(Html.ViewData)
{
TemplateInfo = new TemplateInfo { HtmlFieldPrefix = prefix }
};
}

@Html.Partial("_TestPartial", Model.Test.Partial, viewData)

这样做可以达到这个目的,但它有点混乱,而且要在每个Html.Partial前面放很多代码。现在它可以工作了,让我们把它变成一个HTML助手。这就是我们想要的:

隐藏,复制Code@Html.PartialFor(m => m.Test.Partial)

下面是扩展方法。注意lambda表达式。这是向helper传递部分模型的名称和值的一种很好的类型安全方法。部分视图名可以作为参数传递,或者它将被设置为TProperty的类名或UIHint(“模板名”),如果存在的话。

隐藏,收缩,复制Code///
/// Return Partial View.
/// The element naming convention is maintained in the partial view by setting the prefix name from the expression.
/// The name of the view (by default) is the class name of the Property or a UIHint(“partial name”).
/// @Html.PartialFor(m => m.Address) - partial view name is the class name of the Address property.
///
/// Model expression for the prefix name (m => m.Address)
/// Partial View as Mvc string
public static MvcHtmlString PartialFor<tmodel, tproperty>(this HtmlHelper html,
Expression<func<TModel, TProperty>> expression)
{
return html.PartialFor(expression, null);
}

///
/// Return Partial View.
/// The element naming convention is maintained in the partial view by setting the prefix name from the expression.
///
/// Partial View Name
/// Model expression for the prefix name (m => m.Group[2])
/// Partial View as Mvc string
public static MvcHtmlString PartialFor<TModel, TProperty>(this HtmlHelper html,
Expression<Func<TModel, TProperty>> expression,
string partialName
)
{
string name = ExpressionHelper.GetExpressionText(expression);
string modelName = html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);
ModelMetadata metaData = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
object model = metaData.Model;

if (partialName == null)
{
    partialName = metaData.TemplateHint == null
        ? typeof(TProperty).Name    // Class name
        : metaData.TemplateHint;    // UIHint("template name")
}

// Use a ViewData copy with a new TemplateInfo with the prefix set
ViewDataDictionary viewData = new ViewDataDictionary(html.ViewData)
{
    TemplateInfo = new TemplateInfo { HtmlFieldPrefix = modelName }
};

// Call standard MVC Partial
return html.Partial(partialName, model, viewData);

}

这里额外的位是来自MVC的两个方法:

隐藏,复制CodeExpressionHelper.GetExpressionText gets the name of the expression m => m.Test.Partial would return “Test.Partial”.
ModelMetadata.FromLambdaExpression gets the value of the expression. e.g. the model Partial.

我不是说我是这个扩展的发明者,但更多的是几个人的想法形成一个工作方法的蒸馏器。

编辑/显示模板

在解决了上面的问题之后,我想我应该研究一下编辑器模板。根据微软MSDN:

EditorFor方法用于根据传递给它的表达式的数据类型生成MVCHtmlString标记。

web上有很多这样的例子,您可能会认为这是一个单一的方法,它可以调整其呈现的输出以适应属性的类型,并使用属性的元数据(属性属性)来添加额外的细节。例如,它将显示一个文本框的字符串和一个复选框的bool。它将把输入的类型设置为适当的Html5属性。当你使用一个对象时,它会输出带有标签和每个属性的输入元素的div。这就是它变得有趣的地方,因为你可以通过添加一个名为EditorTemplates和/或DisplayTemplates的文件夹到视图的文件夹或共享文件夹来覆盖所有系统模板的默认行为。在此视图中放置一个带有要覆盖的类型名称的部分视图。这适用于简单类型和复杂类型。我在上面的项目中尝试了这个方法,就像魔术一样,它以与PartialFor extension方法完全相同的方式工作。它还通过层向下传递前缀。

在项目中(可以通过zip链接获得),我用它们的模型类名命名了所有的部分,并将它们放在ViewName/DisplayTemplate或共享的/DisplayTemplate文件夹中。你可以这样使用它们:

隐藏,复制Code@Html.EditorFor(m => m.Partial.First)

Html。Partial期望Partial视图位于当前视图文件夹中或共享文件夹中,而不是子文件夹中。为了进行测试,我使用部分名称参数来覆盖此内容,以使用EditorFor使用的相同部分视图。

隐藏,复制Code@Html.PartialFor(m => m.Partial.First, “EditorTemplates/FirstPartialModel”)

PartialFor vs EditorFor

那么有什么区别呢?我看不出有什么。主要的区别是局部视图的位置。

PartialFor—与Partial相同;在当前视图文件夹或共享文件夹editorfor中视图或共享文件夹的EditorTemplates子文件夹中

我运行调试器并跟踪到MVC编辑器中的代码(关于如何做到这一点,请参阅附录)。它计算出位置,然后测试类型,以确定它是一个复杂的类,复制ViewData,在TemplateInfo中设置附加前缀,并调用与@Html相同的部分视图代码。部分。如果你在互联网上搜索“html”。editorfor vs partial view"你会发现很多讨论。我希望我很久以前就发现了这个。PartialFor的一个优点是,当您想要控制partial视图的绝对位置时。PartialFor还可以处理继承、接口和集合。

@Html。PartialFor(表情,partialName);partialName可以是名称、完整路径或相对路径。@HtmlEditorFor(表情,templateName);templateName可以是一个名称,也可以是一个相对路径。“EditorTemplates”是硬编码在源代码中。

接口和继承

有了这些新知识,我开始使用模板将数据对象的视图定义为u使用表单对页面进行sed。一切都进行得很顺利,直到我试图将类的继承部分分割成它自己的部分视图。我认为这是一个非常好的候选代码重用。EditorFor完全没有渲染,而PartrialFor工作的预期。在调试器和跟踪MVC代码中发现,EditorFor代码正在测试之前访问的对象,并拒绝对象的继承部分。它基本上是试图保护我们不受递归,但在我们的情况下,它不会递归,它只是将对象的派生和继承部分呈现为单独的部分视图。所以我很高兴我没有删除我的PartialFor版本。

Partial View, ChildAction, EditorFor

,中心描述使用部分视图,视图中心工作,没有模型或页面模型。如果子模型是只读的,则可以使用它。非常适合在复杂的页面上分割标记。例如标签。在布局页面上使用时动态视图选择。以创建部分视图为中心的控制器方法[ChildActionOnly]。调用使用@Html.Action(“行动”)。在呈现视图之前可以使用控制器逻辑。不使用页面模型。适合在多个页面上重用独立视图。editorfor模型中心使用简单类型和复杂模型,并维护元素命名约定,以实现正确的后推模型绑定。元素命名遵循模型层次结构。非常适合用于表单中可重用的数据模型视图。以约定为中心的EditorFor只读版本。,以模型为中心,类似于EditorFor,用于复杂对象。不测试递归对象。具有对象的继承部分或接口部分。使用集合。

哪个?鼓励最大限度的代码重用,避免重复,这取决于您。

使用的代码

您将需要Microsoft Visual Studio 2013来运行此解决方案。测试项目可以以zip文件的形式下载。将文件解压缩到一个测试文件夹中,然后双击解决方案文件(css . part . webapp .sln)。在尝试运行该项目之前,您将需要恢复NuGet包。这可能会自动工作,这取决于您的Visual Studio设置。如果它不尝试:

右键点击解决方案,点击启用Nuget包恢复确保工具/选项/ Nuget包管理器/常规包恢复,并检查允许Nuget下载丢失的包。

菜单项包含一个名为“Partial”的下拉列表。单击它将显示3个测试视图。

“邮政地址”显示了一个包含公司详细信息和邮政地址的表单。邮政地址在部分视图中实现。这是一个简单的演示,但是展示了部分视图的分离。将来,邮政地址可以扩展为使用邮政编码查找服务,从而提供一个非常好的可重用“组件”。“嵌套部分”是一个页面,用于在部分中测试部分,并在多个深度上显示数据绑定工作。它还证明了ModelState错误处理仍然有效,ViewData被传递到部分中。我在论坛上找到的几个备选方案在这方面都失败了。“测试”是上述“问题”和“解决问题”的来源。

的兴趣点

我相信上面使用的控制器模式很棒,数据绑定也能神奇地工作。我发现通过MVC代码跟踪的能力非常有帮助。事实上,如果没有它,我不可能做到以上这些。CSE名称空间使用我的公司剑桥软件工程的首字母。

附录

进入。net框架代码

来自MSDN的关于符号/源服务器的指令。

要配置Visual Studio为符号/服务器使用,请遵循以下说明:

去工具->选项→调试器→将军。取消选中“仅启用我的代码(仅托管)”。取消选中“启用。net Framework源步进”。是的,这是一种误导,但是如果你不这样做,那么Visual Studio将会忽略你的自定义服务器订单(见下文)。检查“启用源服务器支持”。取消选中“要求源文件与原始版本完全匹配”,转到Tools ->选项→调试器→符号。为本地符号/源缓存选择一个文件夹。http://srv.symbolsource.org/pdb/Public添加mvc设置如下

历史

版本1 - 10 2016年2月

本文转载于:http://www.diyabc.com/frontweb/news18910.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值