如何使用 ASP.NET MVC 抽象常见的标记片段

发布于 2024-09-30 08:40:09 字数 1976 浏览 0 评论 0原文

我的 ASP.NET MVC 2 站点中有很多内容丰富的视图。其中包含几个重复出现的 HTML 模式。使用 ASP.NET Webforms 时,从 WebControl 派生的类可以封装这些模式。我想要一些有关 MVC 问题的正确方法的指导。

详细说明

与以下 HTML 标记类似的模式在这些视图中不断出现。标记呈现为一个独立的内容框:

<div class="top container">
    <div class="header">
       <p>The title</p>
       <em>(and a small note)</em>
    </div>
    <div class="simpleBox rounded">
       <p>This is content.</p>
       <p><strong>Some more content</strong></p>
    </div>
</div>

这是一个简单的示例,但还有更复杂的重复模式。在 ASP.NET Webforms 中,我会将此类代码抽象为 WebControl(假设我将其命名为 BoxControl),并将其包含在如下页面中:

<foo:BoxControl runat="server">
  <Header>The title</Header>
  <Note>(and a small note)</Note>
  <Content>
     <p>This is content.</p>
     <p><strong>Some more content</strong></p>
  </Content>
</foo:BoxControl>

这种抽象使在整个站点中构建框的方式变得容易,只需更改 BoxControl 源即可。它还将静态 HTML 内容整齐地保留在视图页面中,即使在页面上组合多个 BoxControl 时也是如此。另一个好处是用作内容的 HTML 可以被 IDE 识别,从而提供语法突出显示/检查。

据我了解,ASP.NET MVC 中不鼓励使用 WebControl。我可以使用部分视图来完成抽象,而不是使用 WebControl。这样的视图将包含在视图页面中,如下所示:

<%= Html.Partial("BoxControl", new {
  Header="The Title", 
  Note="(and a small note)", 
  Content="<p>This is content.</p><p><strong>Some more content</strong></p>"});
%>

这并不理想,因为“Content”参数可能会变得非常长,并且以这种方式传递时 IDE 不会将其视为 HTML。

考虑的解决方案 强类型 ViewModel 可以传递给 Html.Partial 调用,而不是上面显示的冗长参数。但随后我必须从其他地方(CMS 或资源文件)提取内容。我希望内容包含在视图页面中。

我还考虑过 Jeffrey Palermo 提出的解决方案,但这意味着项目中会分散大量额外的文件。我希望任何视图的文本内容仅限于一个文件。

我是否应该不想将标记抽象出来?或者是否有一种适合 MVC 的方法,但我在这里忽略了?使用 WebControl 的“犯罪”有什么缺点?

I have a lot of content-heavy views in my ASP.NET MVC 2 site. These contain several re-occurring HTML patterns. When using ASP.NET Webforms, a class derived from WebControl could encapsulate these patterns. I'd like some pointers on the correct approach for this problem with MVC.

Detailed Explanation

Patterns not unlike the following HTML markup keep occurring throughout these views. The markup renders into an isolated a box of content:

<div class="top container">
    <div class="header">
       <p>The title</p>
       <em>(and a small note)</em>
    </div>
    <div class="simpleBox rounded">
       <p>This is content.</p>
       <p><strong>Some more content</strong></p>
    </div>
</div>

This is a trivial example, but there are more complex recurring patterns. In ASP.NET Webforms I would have abstracted such code into a WebControl (let's say I'd have named it BoxControl), being included on a page like this:

<foo:BoxControl runat="server">
  <Header>The title</Header>
  <Note>(and a small note)</Note>
  <Content>
     <p>This is content.</p>
     <p><strong>Some more content</strong></p>
  </Content>
</foo:BoxControl>

This abstraction makes it easy to adapt the way the box is constructed throughout the site, by just altering the BoxControl source. It also keeps the static HTML content neatly together in the View Page, even when combining several BoxControls on a page. Another benefit is that the HTML used as content is recognized by the IDE, thus providing syntax highlighting/checking.

To my understanding, WebControls are discouraged in ASP.NET MVC. Instead of a WebControl, I could accomplish the abstraction with a partial view. Such a view would then be included in a View Page as follows:

<%= Html.Partial("BoxControl", new {
  Header="The Title", 
  Note="(and a small note)", 
  Content="<p>This is content.</p><p><strong>Some more content</strong></p>"});
%>

This is not ideal, since the 'Content' parameter could become very long, and the IDE does not treat it as HTML when passed this way.

Considered Solutions
Strongly-Typed ViewModels can be passed to the Html.Partial call instead of the lengthy parameters shown above. But then I'd have to pull the content in from somewhere else (a CMS, or Resource file). I'd like for the content to be contained in the View Page.

I have also considered the solution proposed by Jeffrey Palermo, but that would mean lots of extra files scattered around the project. I'd like the textual content of any view to be restricted to one file only.

Should I not want to abstract the markup away? Or is there maybe an approach, suitable for MVC, that I am overlooking here? What is the drawback to 'sinning' by using a WebControl?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

追我者格杀勿论 2024-10-07 08:40:09

这个问题有一个解决方案,尽管实现方法比 Ruby on Rails 等其他框架稍微复杂一些。

我使用此方法为 Twitter Bootstrap 的控制组语法创建标记,如下所示:

<div class="control-group">
  <label class="control-label">[Label text here]</label>
  <div class="controls">
    [Arbitrary markup here]
  </div>
</div>

方法如下:

1) 为通用标记片段创建模型。模型应该在构造时编写标记,并在处理时再次编写标记:

using System;
using System.Web.Mvc;

namespace My.Name.Space
{
  public class ControlGroup : IDisposable
  {
    private readonly ViewContext m_viewContext;
    private readonly TagBuilder m_controlGroup;
    private readonly TagBuilder m_controlsDiv;

    public ControlGroup(ViewContext viewContext, string labelText)
    {
      m_viewContext = viewContext;
      /*
       * <div class="control-group">
       *  <label class="control-label">Label</label>
       *  <div class="controls">
       *   input(s)
       *  </div>
       * </div>
       */

      m_controlGroup = new TagBuilder("div");
      m_controlGroup.AddCssClass("control-group");
      m_viewContext.Writer.Write(m_controlGroup.ToString(TagRenderMode.StartTag));

      if (labelText != null)
      {
        var label = new TagBuilder("label");
        label.AddCssClass("control-label");
        label.InnerHtml = labelText;
        m_viewContext.Writer.Write(label.ToString());
      }

      m_controlsDiv = new TagBuilder("div");
      m_controlsDiv.AddCssClass("controls");
      m_viewContext.Writer.Write(m_controlsDiv.ToString(TagRenderMode.StartTag));

    }

    public void Dispose()
    {
      m_viewContext.Writer.Write(m_controlsDiv.ToString(TagRenderMode.EndTag));
      m_viewContext.Writer.Write(m_controlGroup.ToString(TagRenderMode.EndTag));
    }
  }
}

2)创建一个漂亮的 Html 帮助器

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

using My.Name.Space

namespace Some.Name.Space
{
  public static class FormsHelper
  {
    public static ControlGroup ControlGroup(this HtmlHelper helper, string labelText)
    {
      return new ControlGroup(helper.ViewContext, labelText);
    }
  }
}

3)在视图中使用它(Razor 代码)

@using (Html.ControlGroup("My label"))
{
  <input type="text" />
  <p>Arbitrary markup</p>
  <input type="text" name="moreInputFields" />
}

这也是 MVC 框架使用 Html.BeginForm 呈现表单的方式代码>方法

There is a solution to this problem, although the way to get there is a little more clutsy than other frameworks like Ruby on Rails.

I've used this method to create markup for Twitter Bootstrap's control group syntax which looks like this:

<div class="control-group">
  <label class="control-label">[Label text here]</label>
  <div class="controls">
    [Arbitrary markup here]
  </div>
</div>

Here's how:

1) Create a model for the common markup snippet. The model should write markup on construction and again on dispose:

using System;
using System.Web.Mvc;

namespace My.Name.Space
{
  public class ControlGroup : IDisposable
  {
    private readonly ViewContext m_viewContext;
    private readonly TagBuilder m_controlGroup;
    private readonly TagBuilder m_controlsDiv;

    public ControlGroup(ViewContext viewContext, string labelText)
    {
      m_viewContext = viewContext;
      /*
       * <div class="control-group">
       *  <label class="control-label">Label</label>
       *  <div class="controls">
       *   input(s)
       *  </div>
       * </div>
       */

      m_controlGroup = new TagBuilder("div");
      m_controlGroup.AddCssClass("control-group");
      m_viewContext.Writer.Write(m_controlGroup.ToString(TagRenderMode.StartTag));

      if (labelText != null)
      {
        var label = new TagBuilder("label");
        label.AddCssClass("control-label");
        label.InnerHtml = labelText;
        m_viewContext.Writer.Write(label.ToString());
      }

      m_controlsDiv = new TagBuilder("div");
      m_controlsDiv.AddCssClass("controls");
      m_viewContext.Writer.Write(m_controlsDiv.ToString(TagRenderMode.StartTag));

    }

    public void Dispose()
    {
      m_viewContext.Writer.Write(m_controlsDiv.ToString(TagRenderMode.EndTag));
      m_viewContext.Writer.Write(m_controlGroup.ToString(TagRenderMode.EndTag));
    }
  }
}

2) Create a nifty Html helper

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

using My.Name.Space

namespace Some.Name.Space
{
  public static class FormsHelper
  {
    public static ControlGroup ControlGroup(this HtmlHelper helper, string labelText)
    {
      return new ControlGroup(helper.ViewContext, labelText);
    }
  }
}

3) Use it in the view (Razor code)

@using (Html.ControlGroup("My label"))
{
  <input type="text" />
  <p>Arbitrary markup</p>
  <input type="text" name="moreInputFields" />
}

This is also the way MVC framework renders a form with the Html.BeginForm method

初懵 2024-10-07 08:40:09

好吧,你不会像这样渲染部分,而是传递一个强类型的 ViewModel,如下所示:

<%= Html.RenderPartial("BoxControl", contentModel) %>

contentModel 是 ViewModel(只是视图的类似 POCO 的存储机制),它是强类型的部分视图将绑定到。

所以你可以在你的部分视图中执行此操作:

<h1><%: Model.Header %></h1>
<p><%: Model.Content %></p>

等等

Well you wouldn't render the partial like that, pass it a strongly-typed ViewModel, like this:

<%= Html.RenderPartial("BoxControl", contentModel) %>

contentModel is the ViewModel (just a POCO-like storage mechanism for your views), which the strongly typed partial view would bind to.

So you can do this in your partial view:

<h1><%: Model.Header %></h1>
<p><%: Model.Content %></p>

etc etc

极度宠爱 2024-10-07 08:40:09

在考虑答案并运行实验之后,我倾向于坚持纯 MVC 方法并在整个视图页面中复制一些演示代码。我想详细说明这一决定的理由。

部分视图
使用部分视图时,框的内容需要作为视图模型传递,与当场声明内容 HTML 相比,这使得视图页面的可读性较差。请记住,内容并非来自 CMS,因此这意味着在控制器中使用 HTML 填充视图模型或在视图页面中设置局部变量。这两种方法都无法利用 IDE 功能来处理 HTML。

网页控制
另一方面,WebControl 派生类不被鼓励,而且也存在一些实际问题。主要问题是传统 ASP.NET .aspx 页面的声明式分层样式不适合 MVC.NET 视图页面的过程式样式。您必须选择完全成熟的传统方法,或者完全采用 MVC。

为了说明这一点,我的实验实现中最突出的问题是变量范围:当迭代产品列表时,MVC 方式是使用 foreach 循环,但这引入了一个局部变量在 WebControl 范围内不可用。传统的 ASP.NET 方法是使用 Repeater 而不是 foreach。使用任何传统的 ASP.NET 控件似乎都是一个滑坡,因为我怀疑您很快就会发现自己需要组合越来越多的控件才能完成工作。

纯 HTML
完全放弃抽象,您将留下重复的表示代码。这违背了 DRY,但会产生非常可读的代码。

After considering the answers and running an experiment, I'm inclined to adhere to the pure MVC approach and duplicate some presentation code throughout View Pages. I'd like to elaborate on the rationale for that decision.

Partial View
When using a Partial View, The content for the box needs to be passed as a View Model, making the View Page less readable versus declaring the content HTML on the spot. Remember that the content does not come from a CMS, so that would mean filling the View Model with HTML in a controller or setting a local variable in the View Page. Both of these methods fail to take advantage of IDE features for dealing with HTML.

WebControl
On the other hand, a WebControl-derived class is discouraged and also turns out to have some practical issues. The main issue that the declarative, hierarchical style of traditional ASP.NET .aspx pages just does not fit the procedural style of MVC.NET View Pages. You have to choose for either a full blown traditional approach, or go completely MVC.

To illustrate this, the most prominent issue in my experimental implementation was one of variable scope: when iterating a list of products, the MVC-way is to use a foreach loop, but that introduces a local variable which will not be available in the scope of the WebControl. The traditional ASP.NET approach would be to use a Repeater instead of the foreach. It seems to be a slippery slope to use any traditional ASP.NET controls at all, because I suspect you'll soon find yourself needing to combine more and more of them to get the job done.

Plain HTML
Forgoing the abstraction at all, you are left with duplicate presentation code. This is against DRY, but produces very readable code.

回忆那么伤 2024-10-07 08:40:09

对我来说,webforms 的 html 看起来并没有那么少,它看起来更像是横向移动。在 MVC 中使用部分可以使其更清晰,但您需要的 html 标记仍然存在,在一个地方或另一个地方。如果主要是额外的 html 困扰您,您可以看看 NHaml 视图引擎或查看 haml
haml 网站

我绝不是 Haml 专家,但 html 看起来确实干净多了......

.top container
    .header
       %p 
         The title
       %em 
         (and a small note)
    .simpleBox rounded
       %p 
         This is content.
       %p 
         Some more content

It doesnt look like webforms has that much less html to me, it seems more like a lateral move. Using a partial in MVC can make it cleaner but the html markup you needed will still be there, in one place or another. If its mostly the extra html that bothers you, you might take a look at the NHaml view engine or check out haml
the haml website.

I'm by no means a Haml expert but the html does look a lot cleaner...

.top container
    .header
       %p 
         The title
       %em 
         (and a small note)
    .simpleBox rounded
       %p 
         This is content.
       %p 
         Some more content
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文