How to abstract common snippets of markup with ASP.NET MVC

匆匆过客 提交于 2019-12-04 11:06:15

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

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

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.

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
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!