【问题标题】:ASP.NET MVC : Good Replacement for User Control?ASP.NET MVC:用户控件的良好替代品?
【发布时间】:2010-05-10 16:55:43
【问题描述】:

我发现用户控件在使用 ASP.NET 网络表单时非常有用。通过使用标记封装显示控件所需的代码,可重用组件的创建非常简单且非常有用。

虽然 MVC 提供了方便的关注点分离,但这似乎破坏了封装(即,您可以在不添加或使用其支持代码的情况下添加控件,从而导致运行时错误)。每次向视图添加控件时都必须修改控制器,在我看来似乎是为了整合关注点,而不是分离它们。我宁愿打破纯粹的 MVC 意识形态,也不愿放弃可重用的打包控件的好处。

我需要能够在整个站点中包含类似于 webforms 用户控件的组件,但不是针对整个站点,也不是在属于母版页的级别。这些组件应该有自己的代码,而不仅仅是标记(与业务层交互),如果页面控制器不需要了解控件,那就太好了。由于 MVC 用户控件没有代码隐藏,我看不出有什么好的方法。

更新 最后,一个很好的(回想起来,很明显)方法来实现这一点。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace K.ObjectModel.Controls
{
    public class TestControl : ViewUserControl
    {
        protected override void Render(System.Web.UI.HtmlTextWriter writer)
        {
            writer.Write("Hello World");
            base.Render(writer);
        }
    }
}

创建一个继承ViewUserControl的新类

如上所示覆盖.Render() 方法。

通过关联的 ASCX 注册控件,就像在 webForm 中一样:

<%@ Register TagName="tn" TagPrefix="k" Src="~/Views/Navigation/LeftBar.ascx"%>

在您需要的任何视图或母版页中使用相应的标签:

<k:tn runat="server"/>

确保您的 .ascx 继承您的新控件:

<%@ Control Language="C#" Inherits="K.ObjectModel.Controls.TestControl" %>

瞧,你已经启动并运行了。这是使用 ASP.NET MVC 2、VS 2010 和 .NET 4.0 测试的。

您的自定义标记引用 ascx 部分视图,该视图继承自 TestControl 类。然后,该控件将覆盖 Render() 方法,该方法被调用以呈现视图,让您完全控制从标记到输出的过程。

使用这种方法与调用 Html.RenderPartial() 或 `Html.RenderAction()' 的区别在于将控件添加到视图是使用类似 webforms 的标签完成的,这不仅对设计师来说更舒适,而且可以保留他们不必知道控制器名称和方法。控件类的名称与 ASCX 隔离,这也使得将它们放入程序集中并在不同的项目中重用它们变得更容易。

有些人可能会说这违反了 SoC,但我相信这种方法在功能上等同于将部分视图和控制器捆绑在一起,同时保持清晰的标记。然而,应该清楚的是,在控制中只保留与表示相关的逻辑仍然取决于开发人员。业务和数据访问逻辑仍然属于各自的层。

【问题讨论】:

  • Argh...由于您更改了问题,您的编辑令人沮丧。 MVC 的“可重用、打包控件”与 RenderPartial 和 RenderAction 生活在一个完全不同的世界中。 -- 你开始谈论的是 RenderPartial 与 RenderAction 的辩论/讨论,之前已经回答过,主要是关于代码责任的哲学论证。
  • @jfar ??我添加了“选项”部分,因为这些似乎是每个人都提供的解决方案。如果你有更好的主意,我会很高兴听到它。
  • @David Lively - 如果不定义您尝试解决的解决方案的范围,您将无法比较选项 RenderPartial 和 Portable Areas。这就像说“我应该如何杀死这个虫子?苍蝇拍、棒球棒或轨道上的核武器”,而没有告诉对方你是想用酸性血液杀死蚊子还是合成的外来物种。当您说“可重用的打包控件”时,您谈论的是一个巨大的问题,但是通过将 RenderPartial 作为选项包括在内,您会说“简单的标记重用”。哪一个?你想杀死什么样的虫子?
  • @jfar 好点。我需要能够在整个站点中包含类似于 webforms 用户控件的组件,但不是针对整个站点,也不是在属于母版页的级别。这些组件应该有自己的代码不仅仅是标记(与业务层交互),如果页面控制器不需要了解控件,那就太好了。由于 MVC 用户控件没有代码隐藏,因此我看不到这样做的好方法。清楚吗?
  • @David Lively 我希望我能这样说而不会听起来刻薄,但您不认为这些细节可能存在于您的原始问题中吗?基于这些细节,答案很明确:“使用 RenderAction”

标签: asp.net-mvc user-controls


【解决方案1】:

我有点困惑。

首先,与用户控件等效的 .NET MVC 是 Partial Views。部分视图是将常用视图功能封装在单个位置的便捷方式。然后,您可以从另一个 View 中调用 Partial View。

其次,修改视图不应该意味着也修改控制器。如果仅仅因为您的视图发生了更改(而不是基础数据)就需要对两者进行更改,那么可能存在代码问题。

【讨论】:

  • 我理解你的观点(我认为),但请考虑一下:我想在页面上显示一个命中计数器。如果没有链接到该控件或部分视图的代码,我将需要修改底层控制器以确保在传递给视图的 ViewData 或模型中提供当前命中计数。任何需要显示此局部视图的控制器都需要提供此数据,因此必须进行修改。想法?这可以在通用的基本控制器类中提供,但显示它的要求可能不反映类层次结构。
  • @David Lively:将计数器 html 代码放在母版页中(使用或不使用局部视图)。在 BaseController 类的 ViewData 中设置计数器值。您应该有基本的控制器类来共享通用功能。
  • @LukLed 这在许多情况下都有帮助,但在这种情况下,只有需要显示计数器的页面具有层次关系时才有意义。此外,如果我开始将所有这些都放在 BaseController 类中,则从该类继承的每个控制器都将携带这些数据,而并非所有视图都需要这些数据。
  • " .NET MVC 等效于用户控件是部分视图。" - :(这就像比较苹果和橙子一样。它们几乎不等价。唯一的相似之处是它们都包含标记。
  • @jfar - 在这种情况下,使用任何一个的目的是相同的。 .NET MVC 中的部分视图用于封装代码以供重用,这与使用用户控件重用相同控件代码的方式非常相似。
【解决方案2】:

乍一看,MVC 很容易被认为没有可重用组件的功能。

一旦你了解了 ASP.NET MVC,你会发现有 several techniques for creating rich controls and components 和封装方面的 MVC 遵循 along the same pathways 作为封装 WebForms 应用程序。

我认为您正在做的只是查看 MVC 的视图方面,而不是如何将所有底层 MC 封装和捆绑在一起。 Partial Views、Render Action/Partial 只是 MVC 底层组件功能的一小部分。幕后有更多的丰富。

【讨论】:

  • 他是这么说的“幕后的财富”。
【解决方案3】:

一个用户控件只是一些渲染html的东西,在mvc中你有html助手和部分视图和普通视图(你可以用renderaction渲染它们) p>

Html.Helper("someStuff")
Html.RenderPartial("viewname")
Html.RenderAction<Controller>(o => o.Action());

所以基本上它只是帮手

您实际上可以轻松地替换调用

Html.TextBoxFor(o => o.Name);

Html.RenderPartial("textbox", Model.Name);

【讨论】:

    【解决方案4】:

    考虑以下示例:

    • 我的视图 (CustomerDetail.ascx) 绑定到 ICustomerDetail 视图模型,如下所示:

      接口 ICustomerDetail {
      字符串名称 { 获取; }
      地址 CurrentAddress { get; }
      }

    • 我可以创建一个绑定到 IAddress 视图模型的局部视图 Address.ascx

    • 当我创建 CustomerDetail.ascx 时,我可以将 Address.ascx 放在同一个表面并将其绑定到 oCustomerDetail.Address 字段

    • IMO - 我们应该在 MVC 中从多个此类较小的局部视图组合视图,在这里您将看到用户控件的可重用性和强大功能(局部视图)

    • 现在,如果我的控制器返回 ICustomerDetail,我将能够毫无问题地重新使用 Address.ascx

    HTH。

    【讨论】:

    • 这是一个很好的解释,但是,每个人似乎都忽略了一点,即使用 RenderAction,您将始终拥有一些首先运行的控制器操作代码。因此,如果您需要在 View 或 ViewUserControl 被渲染之前发生一些特殊的事情,请使用 RenderAction。当您不需要从某处加载任何其他内容时使用 RenderPartial() (您已经拥有视图中的当前模型中的所有内容,从那里调用 RenderPartial() 或者您不需要其他任何内容并且可以调用 RenderPartial () 仅以 ViewUserControl 名称作为其参数)。
    【解决方案5】:

    我们以电子商务网站的注册页面为例。您提示用户输入他们的姓名、密码、邮政信息、最喜欢的犬种等。在应用程序的其他地方,您还需要收集帐单地址和送货地址。要强制执行 DRY,您需要创建一个用户控件来管理地址信息的输入。

    因此,为了进一步说明,您的地址类如下所示:

    public class Address
    {
        public string StreetAddress { get; set; }
        public string City { get; set; }
        ...
    }
    

    您的注册班级:

    public class UserReg
    {
        public string UserName { get; set; }
        public Address MailingAddress { get; set; }
        ...
    }
    

    您的帐单和送货地址可能来自 Address 类:

    public class BillingAddress : Address
    { 
        ...
    }
    
    public class ShippingAddress : Address
    { 
        ...
    }
    

    对于以下示例,我假设您已将 System.Web.Mvc 添加到 web.confignamespaces 部分。基于此类层次结构,您的用户控件将有一个仅引用 Address 类的控件标记:

    <%@ Control Language="C#" Inherits="ViewUserControl<Address>" %>
    

    既然您已经这样做了,您只需从页面传递适当的模型引用。在用户注册页面:

    <%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="ViewPage<UserReg>" %>
        ...
        <% Html.RenderPartial("AddressControl", Model.MailingAddress); %>
    

    在帐单地址页面中:

    <%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="ViewPage<BillingAddress>" %>
        ...
        <% Html.RenderPartial("AddressControl", Model); %>
    

    在送货地址页面中:

    <%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="ViewPage<ShippingAddress>" %>
        ...
        <% Html.RenderPartial("AddressControl", Model); %>
    

    我可以直接从计费和运输页面传递模型,因为该类直接来自 Address。只要有正确处理地址的逻辑,您就不必对控制器进行很多更改(如果有的话)。

    【讨论】:

    • +1 表示代码,但这似乎没有回答问题的核心,可能措辞不佳。我需要将部分视图的代码与视图一起保留,以便它可以执行而无需修改控制器。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-01
    • 2018-03-15
    • 2011-03-11
    • 2010-09-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多