Как я могу улучшить дизайн этого расширения HtmlHelper? - PullRequest
1 голос
/ 17 марта 2010

примечание: Первоначально я разместил вопрос, подобный этому здесь , но я решил сделать репост, потому что преодолел исходную проблему и в процессе изменил дизайн. Я думал, что это оправдывает новую тему, потому что, как дизайн изменился, вопрос также существенно изменился. Я просто хочу прояснить, что я не пытаюсь залить ТАК тем же вопросом.

вопрос: Я работал над небольшим экспериментом, чтобы посмотреть, смогу ли я создать вспомогательный метод для сериализации любого из моих типов в любой тип 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
}

Я также должен добавить, что Fund & 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 Ответ

1 голос
/ 17 марта 2010

Рассматривали ли вы использование соглашения для создания ваших тегов? Похоже, вам требуется много повторяющегося кода в ваших представлениях. Каждый раз, когда вы хотите раскрывающийся список, вам нужно будет скопировать полдюжины строк.

Использование самоуверенных конструкторов ввода значительно упростит ваше представление, но потребует некоторой настройки с вашей стороны. По моему опыту, эта установка ХОРОШАЯ СТОИМОСТЬ времени, которое вы экономите в будущем!

Идея построителя ввода заключается в том, что вы указываете, что для свойства вашей модели представления есть элемент ввода (или отображение, метка и т. Д.) Затем ваш конструктор входных данных проверяет свойство на предмет его типа и атрибутов и определяет правильный тип вводимых данных.

Вот пример того, как в моем текущем проекте построен раскрывающийся список:

//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);

Здесь - пошаговое руководство по использованию конструкторов ввода MVCContrib. Я знаю, что в MVC2 также есть поддержка конструктора ввода, но я не знаком с этим. На мой взгляд, лучшие сборщики на основе конвенций доступны от FubuMVC. У меня есть пост здесь о том, как использовать их с ASP.NET MVC. У меня еще нет сообщений о том, как их настроить, хотя я планирую это в ближайшее время.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...