2010-03-17 42 views
1

筆記:我原本發佈了一個類似於這個here的問題,但我決定轉發,因爲我克服了原來的問題,並在此過程中修改了設計。我認爲這是一個新的話題,因爲隨着設計的改變,這個問題也發生了根本性的變化。我只想說清楚,我沒有試圖用同樣的問題來淹沒SO。如何改進此HtmlHelper擴展的設計?

問題:我一直在做一個小實驗,看看我是否可以創建一個幫助器方法來將我的任何類型序列化爲我指定的任何類型的HTML標記。我想我會把它交給社區,以幫助我識別設計中的代碼異味或其他缺陷/低效率,以改進設計。

基本上,我有這樣的代碼,將產生一個選擇框(或任何其他HTML元素)與多個選項:

// the idea is I can use one method to create any complete tag of any type 
// and put whatever I want in the content area. 
// This makes the generation of all html completely testable 

<% using (Html.GenerateTag<SelectTag>(Model, new { href = Url.Action("ActionName") })) { %> 
    // model is type ShareClass. It contains a list of Funds 
    <%foreach (var fund in Model.Funds) {%> 
     <% using (Html.GenerateTag<OptionTag>(fund)) { %> 
      <%= fund.Name %> 
     <% } %> 
    <% } %> 
<% } %> 

這將產生下面的HTML輸出:

<select shareclassname="MyShareClass" 
     shareclasstype="ShareClass_A" 
     href="/Ctrlr/ActionName"> 
    <option selected="selected" id="1" name="MyFund_1">MyFund_1</option> 
    <option id="2" name="MyFund_2">MyFund_2</option> 
    <option id="3" name="MyFund_3">MyFund_3</option> 
    <option id="N" name="MyFund_N">MyFund_N</option> 
</select> 

這Html.GenerateTag助手定義爲:

public static MMTag GenerateTag<T>(this HtmlHelper htmlHelper, object elementData, object attributes) where T : MMTag 
{ 
    return (T)Activator.CreateInstance(typeof(T), htmlHelper.ViewContext, elementData, attributes); 
} 

根據T的類型,它會創建的類型之一定義如下:

public abstract class HtmlTypeBase : MMTag 
{   
    public HtmlTypeBase(ViewContext viewContext, params object[] elementData) 
    { 
     _tag = this.GetTag(); 
     base._viewContext = viewContext; 
     base.MergeDataToTag(viewContext, elementData); 
    } 

    public abstract TagBuilder GetTag(); 
} 

public class SelectTag : HtmlTypeBase 
{ 
    public SelectTag(ViewContext viewContext, params object[] elementData) 
     : base(viewContext, elementData) 
    { 
     base._tag = new TagBuilder("select"); 
    } 

    public override TagBuilder GetTag() 
    { 
     return new TagBuilder("select"); 
    } 
} 

public class OptionTag : HtmlTypeBase 
{ 
    public OptionTag(ViewContext viewContext, params object[] elementData) 
     : base(viewContext, elementData) 
    { 
     base._tag = new TagBuilder("option"); 
    } 

    public override TagBuilder GetTag() 
    { 
     return new TagBuilder("option"); 
    } 
} 

public class AnchorTag : HtmlTypeBase 
{ 
    public AnchorTag(ViewContext viewContext, params object[] elementData) 
     : base(viewContext, elementData) 
    { 
     base._tag = new TagBuilder("a"); 
    } 

    public override TagBuilder GetTag() 
    { 
     return new TagBuilder("a"); 
    } 
} 

,這是MMTag的定義:

public class MMTag : IDisposable 
{ 
    internal bool _disposed; 
    internal ViewContext _viewContext; 
    internal TextWriter _writer; 
    internal TagBuilder _tag; 

    public MMTag() {} 

    public MMTag(ViewContext viewContext, params object[] elementData){ } 

    protected void MergeDataToTag(ViewContext viewContext, object[] elementData) 
    { 
     MergeTypeDataToTag(elementData[0]); 
     MergeAttributeDataToTag(elementData[1]); 

     this._viewContext = viewContext; 

     this._viewContext.Writer.Write(_tag.ToString(TagRenderMode.StartTag)); 
    } 

    private void MergeAttributeDataToTag(object attributeData) 
    { 
     var dic = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase); 
     var attributes = attributeData; 
     if (attributes != null) 
     { 
      foreach (PropertyDescriptor descriptor in TypeDescriptor.GetProperties(attributes)) 
      { 
       object value = descriptor.GetValue(attributes); 
       dic.Add(descriptor.Name, value); 
      } 
     } 

     _tag.MergeAttributes<string, object>(dic); 
    } 

    private void MergeTypeDataToTag(object typeData) 
    { 
     Type elementDataType = typeData.GetType(); 
     foreach (PropertyInfo prop in elementDataType.GetProperties()) 
     { 
      if (prop.PropertyType.IsPrimitive || prop.PropertyType == typeof(Decimal) || prop.PropertyType == typeof(String)) 
      { 
       object propValue = prop.GetValue(typeData, null); 
       string stringValue = propValue != null ? propValue.ToString() : String.Empty; 
       _tag.Attributes.Add(prop.Name, stringValue); 
      } 
     } 
    } 

    #region IDisposable 
    public void Dispose() 
    { 
     Dispose(true /* disposing */); 
     GC.SuppressFinalize(this); 
    } 

    protected virtual void Dispose(bool disposing) 
    { 
     if (!_disposed) 
     { 
      _disposed = true; 
      if (disposing) 
      { 
       _writer = _viewContext.Writer; 
       _writer.Write(_tag.ToString(TagRenderMode.EndTag)); 
      } 
     } 
    } 
    #endregion 
} 

我還要補充一點,基金& ShareClass被定義爲:

public class Fund 
{ 
    public int ID { get; set; } 
    public string Name { get; set; } 

    public Fund() 
    { 
     this.ID = 123; 
     this.Name = "MyFund"; 
    } 

    public Fund(int id, string name) 
    { 
     this.ID = id; 
     this.Name = name; 
    } 
} 

public class ShareClass 
{ 
    public string ShareClassName { get; set; } 
    public string ShareClassType { get; set; } 
    public IEnumerable<Fund> Funds { get; set; } 

    public ShareClass(string name, string shareClassType) 
    { 
     this.ShareClassName = name; 
     this.ShareClassType = shareClassType; 
    } 
} 

回答

1

有無你考慮使用約定來創建你的標籤?看起來您需要在視圖中使用大量重複代碼。每次你想要一個下拉菜單,你都必須複製六十行。

使用獨立輸入構建器將大大簡化您的視圖,但您需要一些設置。根據我的經驗,這個設置是值得的!

輸入生成器的想法是,您指定在視圖模型上有一個屬性的輸入元素(或顯示或標籤等)。您的輸入生成器框架然後檢查該屬性的類型和屬性,並確定要呈現的正確輸入類型。

這裏是一個下拉是如何建立在我的當前項目的例子:

//View Model 
[OptionsFrom("Years")] 
public int ContractYear{ get; set; } 

public IDictionary Years 
{ 
    get 
    { 
     var currentYear = DateTime.Today.Year; 
     return Enumerable.Range(0, 10).ToDictionary(i => currentYear + i, i => (currentYear + i).ToString()); 
    } 
} 

//View 
Html.InputFor(x => x.ContractYear); 

Here是步行通過使用MVCContrib的輸入建設者。我知道在MVC2中也有一些輸入構建器的支持,但我並不熟悉它。在我看來,FubuMVC提供了最好的基於常規的建造者。我有一篇關於如何在ASP.NET MVC中使用它們的文章here。我還沒有任何關於如何配置它們的文章,儘管我很快就計劃了。

+0

在這裏你可以找到關於'MVC2中的一些輸入生成器支持'的啓動信息:http://bradwilson.typepad.com/blog/2009/10/aspnet-mvc-2-templates-part-1-introduction。 html :) – 2010-03-17 16:05:46