C#Fluent API:如何构建

时间:2014-06-02 13:42:25

标签: c# asp.net-mvc

目前,我正在创建自定义MVC Html Helpers,我将通过流畅的API使用它。 举一个例子来理解它,我将有以下帮助器(或者应该在不太遥远的未来)生成一个网格:

@(Html.GridFor(Model)
  .WithName("MyName")
  .WithColumns(model =>
  {
      model.Bind(x => x.Name);
      model.Bind(x => x.DateCreated);
      model.Bind(x => x.DateUpdated);
  }).Render());

现在,一切都是以起点构建的。 IGridBuilder。

/// <summary>
///     When implemented by a class, it defines the class as an object that can construct a grid by using a fluent API.
/// </summary>
public interface IGridBuilder<TModel> : IHtmlHelper, IDataSource<TModel>
{
    #region Properties

    /// <summary>
    ///     Gets the name of the <see cref="IGridBuilder{TModel}" />.
    /// </summary>
    string Name { get; }

    #endregion

    #region Methods

    /// <summary>
    ///     Sets the name of the <see cref="IGridBuilder{TModel}" />. This name will be used as an id on the outer element that
    ///     holds the entire grid.
    /// </summary>
    /// <param name="name">The name that the <see cref="IGridBuilder{TModel}" /> should have.</param>
    /// <returns>An <see cref="IGridBuilder{TModel}" /> that can be used to construct the grid through a fluent API.</returns>
    IGridBuilder<TModel> WithName(string name);

    /// <summary>
    ///     Set the columns of the model that should be bound to grid.
    /// </summary>
    /// <param name="bindAllColumns">The action that will bind all the columns.</param>
    /// <returns>An <see cref="IGridBuilder{TModel}" /> that is used to construct the grid.</returns>
    IGridBuilder<TModel> WithColumns(Action<IColumnBinder<TModel>> bindAllColumns);

    /// <summary>
    ///     Renders the grid with all the set properties.
    /// </summary>
    /// <returns>A <see cref="MvcHtmlString" /> that contains the HTML representation of the grid.</returns>
    MvcHtmlString Render();

    #endregion
}

并生成绑定命令,我正在使用IColumnBinder接口:

/// <summary>
/// When implemented by a class, this class is marked as being an builder that can construct a column through a fluent API.
/// </summary>
/// <typeparam name="TModel"></typeparam>
public interface IColumnBinder<TModel> : IHtmlHelper, IDataSource<TModel>
{
    #region Methods

    /// <summary>
    ///     Binds an column to the grid.
    /// </summary>
    /// <typeparam name="TItem">The type of the column on which to bind the items.</typeparam>
    /// <param name="propertySelector">The functional that will bind the control to the grid.</param>
    void Bind<TItem>(Expression<Func<TModel, TItem>> propertySelector);

/// <summary>
///     Apply a specific css class on an element.
/// </summary>
/// <param name="className">The name of the css class that should be placed on the element.</param>
/// <returns>As <see cref="IColumnBinder{TModel}"/> that is used to construct this column through a fluent API.</returns>
IColumnBinder<TModel> WithCss(string className);

    #endregion
}

现在,将IColumnBuilder链接到IGridBuilder的最佳方法是什么?

为了做得很短,我正在努力解决以下问题:

IColumnBuilder设置特定属性,但渲染在IGridBuilder界面中进行。

主要问题在于以下代码:

/// <summary>
///     Set the columns of the model that should be bound to grid.
/// </summary>
/// <param name="bindAllColumns">The action that will bind all the columns.</param>
/// <returns>An <see cref="IGridBuilder{TModel}" /> that is used to construct the grid.</returns>
public IGridBuilder<TModel> WithColumns(Action<IColumnBinder<TModel>> bindAllColumns)
{
    bindAllColumns(new ColumnBinder<TModel>());

    return this;
}

所以在这里我执行绑定列的操作:

model.Bind(x => x.Name)

但是,如何在IGridBuilder和IColumnBuilder之间保持一个引用,以便以适当的方式构建它?

还是有其他解决方案吗?

1 个答案:

答案 0 :(得分:4)

确定,

经过数小时的搜索,我找到了解决方案,因此我回答了自己的问题。但是,如果有用户有另一种方法来解决同样的问题,请告诉我,以便我可以调整代码。

此代码主要使用类来传输对象,因为类是一种引用类型,它可以传递给另一个对象,该对象可以操作该对象。

所以,我编写了一个自定义HTML帮助程序,它应该如下所示:

@(Html.GridFor(Model)
  .WithName("MyName")
  .WithColumns(model =>
  {
      model.Bind(x => x.Name).WithCss("row first");
      model.Bind(x => x.DateCreated);
      model.Bind(x => x.DateUpdated);
  }).Render());

所以,我确实有一个可枚举的模型,我会传递给网格。网格需要3列模型并渲染网格。

对于这段代码,我有几个接口可以帮助我完成整个过程:

  1. HTML Helper接口(只保存HtmlHelper的对象):

    /// <summary>
    ///     Provides a way to extend the <see cref="HtmlHelper" /> to construct objects of various kinds.
    /// </summary>
    public static class HtmlHelperExtensions
    {
        #region Grid
    
        /// <summary>
        ///     Constructs a grid for a property that holds a collection.
        /// </summary>
        /// <typeparam name="TModel">The type of the model on which this grid is being build.</typeparam>
        /// <typeparam name="TEntity">The type of a single item in the collection.    </typeparam>
        /// <param name="htmlHelper">The helper on which this method is executed.    </param>
        /// <param name="dataSource">The datasource on which the items are bound.    </param>
        /// <returns>An <see cref="IGridBuilder{TEntity}" /> that is used to construct the grid.</returns>
        public static IGridBuilder<TEntity> GridFor<TModel, TEntity>(this     HtmlHelper<TModel> htmlHelper,
            IEnumerable<TEntity> dataSource)
        {
            return new GridBuilder<TEntity>(htmlHelper, dataSource);
        }
    
        #endregion
    }
    
  2. 数据源接口(只保存数据源的接口):

    public interface IDataSource<out TModel>
    {
        #region Properties
    
         /// <summary>
         ///     Gets the source that will be bound to the implemented object.
         /// </summary>
         IEnumerable<TModel> DataSource { get; }
    
         #endregion
    }
    
  3. 然后我们拥有所有其他代码。

    1. HTML Helper扩展类是第一个:

      /// <summary>
      ///     Provides a way to extend the <see cref="HtmlHelper" /> to construct objects of various kinds.
      /// </summary>
      public static class HtmlHelperExtensions
      {
          #region Grid
      
          /// <summary>
          ///     Constructs a grid for a property that holds a collection.
          /// </summary>
          /// <typeparam name="TModel">The type of the model on which this grid is being build.</typeparam>
          /// <typeparam name="TEntity">The type of a single item in the collection.</typeparam>
          /// <param name="htmlHelper">The helper on which this method is executed.</param>
          /// <param name="dataSource">The datasource on which the items are bound.</param>
          /// <returns>An <see cref="IGridBuilder{TEntity}" /> that is used to construct the grid.</returns>
          public static IGridBuilder<TEntity> GridFor<TModel, TEntity>(this HtmlHelper<TModel> htmlHelper,
              IEnumerable<TEntity> dataSource)
          {
              return new GridBuilder<TEntity>(htmlHelper, dataSource);
          }
      
          #endregion
      }
      
    2. 然后下一个是IGridBuilder接口的实现:

      /// <summary>
      ///     Provides an implemention of the <see cref="IGridBuilder{TModel}" /> that is used to construct the grid through a
      ///     fluent API.
      /// </summary>
      /// <typeparam name="TModel">The type of the model that the grid will hold.</typeparam>
      public class GridBuilder<TModel> : IGridBuilder<TModel>
      {
          #region Constructors
      
          /// <summary>
          ///     Creates a new instance of the <see cref="GridBuilder{TModel}" />.
          /// </summary>
          /// <param name="helper">The <see cref="HtmlHelper" /> that is used to construct the grid.</param>
          /// <param name="dataSource">The collection of objects that will be bound to the grid.</param>
          public GridBuilder(HtmlHelper helper, IEnumerable<TModel> dataSource)
          {
              htmlHelper = helper;
              DataSource = dataSource;
              Constructor = new GridConstructor<TModel>(htmlHelper, DataSource);
          }
      
          #endregion
      
          #region IGridBuilder Members
      
          /// <summary>
          ///     Gets the name of the <see cref="IGridBuilder{TModel}" />.
          /// </summary>
          public string Name { get; private set; }
      
          /// <summary>
          ///     Gets the constructor that will be used to construct this <see cref="IGridBuilder{TModel}" />.
          /// </summary>
          public IGridContructor<TModel> Constructor { get; set; }
      
          /// <summary>
          ///     Gets the source that will be bound to the implemented object.
          /// </summary>
          public IEnumerable<TModel> DataSource { get; private set; }
      
          /// <summary>
          ///     Gets the <see cref="HtmlHelper" /> object.
          /// </summary>
          public HtmlHelper htmlHelper { get; private set; }
      
          /// <summary>
          ///     Sets the name of the <see cref="IGridBuilder{TModel}" />. This name will be used as an id on the outer element that
          ///     holds the entire grid.
          /// </summary>
          /// <param name="name">The name that the <see cref="IGridBuilder{TModel}" /> should have.</param>
          /// <returns>An <see cref="IGridBuilder{TModel}" /> that can be used to construct the grid through a fluent API.</returns>
          public IGridBuilder<TModel> WithName(string name)
          {
              Name = name;
              return this;
          }
      
          /// <summary>
          ///     Set the columns of the model that should be bound to grid.
          /// </summary>
          /// <param name="bindAllColumns">The action that will bind all the columns.</param>
          /// <returns>An <see cref="IGridBuilder{TModel}" /> that is used to construct the grid.</returns>
          public IGridBuilder<TModel> WithColumns(Action<IColumnBinder<TModel>> bindAllColumns)
          {
              var columnBinder = new ColumnBinder<TModel>(Constructor);
      
              bindAllColumns(columnBinder);
              return this;
          }
      
          /// <summary>
          ///     Renders the grid with all the set properties.
          /// </summary>
          /// <returns>A <see cref="MvcHtmlString" /> that contains the HTML representation of the grid.</returns>
          public MvcHtmlString Render()
          {
              var outputBuilder = new StringBuilder();
      
              BaseElementBuilder parentElement = DivFactory.DivElement().WithCss("header");
      
              outputBuilder.Append(parentElement.ToString(TagRenderMode.StartTag));
              outputBuilder.Append(parentElement.ToString(TagRenderMode.EndTag));
      
              return new MvcHtmlString(outputBuilder.ToString());
          }
      
          #endregion
      }
      
    3. 然后是IGridColumnBinder的实现:

      /// <summary>
      ///     Provides an implementation of the <see cref="IColumnBinder{TModel}" /> that can be used to construct a column
      ///     through a fluent API.
      /// </summary>
      /// <typeparam name="TModel">The type of the datasource that's bound to the grid.</typeparam>
      public class ColumnBinder<TModel> : IColumnBinder<TModel>
      {
          #region Constructors
      
          /// <summary>
          ///     Creates a new instance of the <see cref="ColumnBinder{TModel}" />.
          /// </summary>
          /// <param name="constructor">An <see cref="IGridContructor{TModel}" /> that contains the builder to construct the grid.</param>
          public ColumnBinder(IGridContructor<TModel> constructor)
          {
              Constructor = constructor;
          }
      
          #endregion
      
          #region IColumnBinder Members
      
          /// <summary>
          ///     Gets the values that are bound to this <see cref="IColumnBinder{TModel}" />.
          /// </summary>
          public IGridContructor<TModel> Constructor { get; private set; }
      
          /// <summary>
          ///     Gets the css class of the <see cref="IColumnBinder{TModel}" />.
          /// </summary>
          public string CssClass { get; private set; }
      
          /// <summary>
          ///     Gets the values that are bound to this <see cref="IColumnBinder{TModel}" />.
          /// </summary>
          public IList<object> Values { get; set; }
      
          /// <summary>
          ///     Binds an column to the grid.
          /// </summary>
          /// <typeparam name="TItem">The type of the column on which to bind the items.</typeparam>
          /// <param name="propertySelector">The functional that will bind the control to the grid.</param>
          /// <returns>As <see cref="IColumnBinder{TModel}" /> that is used to construct this column through a fluent API.</returns>
          public IColumnBinder<TModel> Bind<TItem>(Expression<Func<TModel, TItem>> propertySelector)
          {
              string name = ExpressionHelper.GetExpressionText(propertySelector);
              name = Constructor.htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);
      
              ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForProperty(() => default(TModel),
                  typeof (TModel), name);
      
              // Get's the name to display on the column in grid. The Display attribute is used if present, otherwise the name of the property is used.
              string displayName = string.IsNullOrEmpty(metadata.DisplayName)
                  ? metadata.PropertyName
                  : metadata.DisplayName;
      
              Values =
                  Constructor.DataSource.Select(myVar => propertySelector.Compile()(myVar))
                      .Select(dummy => (object) dummy)
                      .ToList();
      
              Constructor.builderProperties.Add(displayName, this);
              return this;
          }
      
          /// <summary>
          ///     Apply a specific css class on an element.
          /// </summary>
          /// <param name="className">The name of the css class that should be placed on the element.</param>
          /// <returns>As <see cref="IColumnBinder{TModel}" /> that is used to construct this column through a fluent API.</returns>
          public IColumnBinder<TModel> WithCss(string className)
          {
              CssClass = className;
              return this;
          }
      
          #endregion
      }
      
    4. 最后执行IGridConstructor。

      /// <summary>
      ///     Provides an implemention of the <see cref="IGridContructor{TModel}" /> that is used to construct the grid through a
      ///     fluent API.
      /// </summary>
      /// <typeparam name="TModel">The type of the model that the grid will hold.</typeparam>
      public class GridConstructor<TModel> : IGridContructor<TModel>
      {
          #region Constructors
      
          /// <summary>
          ///     Creates a new instance of the <see cref="GridConstructor{TModel}" />.
          /// </summary>
          /// <param name="helper">The <see cref="HtmlHelper" /> that is used to built the model.</param>
          /// <param name="source">The model that is bound to the grid.</param>
          public GridConstructor(HtmlHelper helper, IEnumerable<TModel> source)
          {
              htmlHelper = helper;
              DataSource = source;
      
              builderProperties = new Dictionary<string, IColumnBinder<TModel>>();
          }
      
          #endregion
      
          #region Properties
      
          /// <summary>
          ///     Provides a dictionary that contains all the properties for the builder.
          /// </summary>
          public IDictionary<string, IColumnBinder<TModel>> builderProperties { get; set; }
      
          /// <summary>
          ///     Gets the source that will be bound to the implemented object.
          /// </summary>
          public IEnumerable<TModel> DataSource { get; private set; }
      
          /// <summary>
          ///     Gets the <see cref="HtmlHelper" /> object.
          /// </summary>
          public HtmlHelper htmlHelper { get; private set; }
      
          #endregion
      }
      
    5. 现在,这是如何工作的?

      1. HtmlHelper返回一个实现IGridBuilder的成员,所以在上面的例子中,它返回一个GridBuilder。

      2. 在那个GridBuilder上,你可以调用几个元素,另一个更重要的是采用IColumnBinder动作的WithColumns方法,这就是诀窍。 IColumnBinder的实现引用了IGridConstructor。而且它的构造函数将完全构建起来。

      3. 因此,我们需要知道的所有呈现内容,包括给定列的每个css类都通过GridBuilder.GridContructor公开

        所以,这是一篇很长篇文章,但我希望它可以帮助一些人。