Fluent API:引用类型从彼此获取值

本文关键字:获取 API 引用类型 Fluent | 更新日期: 2023-09-27 18:06:16

我目前正在为MVC编写一个网格,这是一个HtmlHelper扩展,我遇到了一个奇怪的问题。

首先,这是我用来构建网格的代码:

RenderedOutput = HtmlHelper.GridFor(Model)
.WithColumns(column =>
{
    column.Bind(x => x.Name)
        .WithCss("inline");
    column.Bind(x => x.Age)
        .WithCss("inline fixed right");
})
.Render();

现在,整个流畅的API是通过使用接口建立起来的,为了使复制过去的工作更小一点,我不会在这里发布接口,但我会发布实现:

首先,HtmlHelper:
public static IGridBuilder<TEntity> GridFor<TModel, TEntity>(this HtmlHelper<TModel> htmlHelper,
    IEnumerable<TEntity> dataSource)
{
    return new GridBuilder<TEntity>(htmlHelper, dataSource);
}

返回一个GridBuilder,它的实现在这里:

#region Constructors
public GridBuilder(HtmlHelper helper, IEnumerable<TModel> data)
{
    HtmlHelper = helper;
    DataSource = data;
    ColumnBuilders = new List<IColumnBuilder<TModel>>();
}
#endregion
#region Properties
private string Id { get; set; }
#endregion
#region IGridBuilder Members
public IList<IColumnBuilder<TModel>> ColumnBuilders { get; protected set; }
public IEnumerable<TModel> DataSource { get; private set; }
public HtmlHelper HtmlHelper { get; private set; }
public IGridBuilder<TModel> WithId(string id)
{
    Id = id;
    return this;
}
public IGridBuilder<TModel> WithColumns(Action<IColumnBuilder<TModel>> bindAllColumns)
{
    bindAllColumns(new ColumnBuilder<TModel>(this));
    return this;
}
public HtmlString Render()
{
    var outputBuilder = new StringBuilder();
    var headerMember = new TagBuilder("div");
    headerMember.MergeAttribute("class", "gridHolder v-scroll");
    if (!string.IsNullOrEmpty(Id))
    {
        headerMember.GenerateId(Id);
    }
    outputBuilder.AppendLine(headerMember.ToString(TagRenderMode.StartTag));
    // Process all the available columns.
    var rowBuilder = new TagBuilder("div");
    rowBuilder.MergeAttribute("class", "row");
    outputBuilder.AppendLine(rowBuilder.ToString(TagRenderMode.StartTag));
    foreach (var column in ColumnBuilders)
    {
        var columnBuilder = new TagBuilder("div");
        outputBuilder.AppendLine(columnBuilder.ToString(TagRenderMode.StartTag));
        outputBuilder.AppendLine(columnBuilder.ToString(TagRenderMode.EndTag));
    }
    outputBuilder.AppendLine(rowBuilder.ToString(TagRenderMode.EndTag));
    outputBuilder.AppendLine(headerMember.ToString(TagRenderMode.EndTag));
    return new HtmlString(outputBuilder.ToString());
}
#endregion

}

GridBuilder使用IColumnBuilder的Action来构建列,所以它是:

public class ColumnBuilder<TModel> : IColumnBuilder<TModel>
{
    #region Constructors
    public ColumnBuilder(IGridBuilder<TModel> gridBuilder)
    {
        GridBuilderReference = gridBuilder;
    }
    #endregion
    #region IColumnBuilder Members
    public IGridBuilder<TModel> GridBuilderReference { get; private set; }
    public string CssClass { get; private set; }
    public IColumnBuilder<TModel> Bind<TItem>(Expression<Func<TModel, TItem>> propertySelector)
    {
        // Reset the properties. This is needed because they are not cleared automatticly. It's not a new instance which is created.
        CssClass = null;
        GridBuilderReference.ColumnBuilders.Add(this);
        return this;
    }
    public IColumnBuilder<TModel> WithCss(string className)
    {
        CssClass = className;
        return this;
    }
    #endregion
}

首先,这是我的第一个流畅的接口实现,所以如果这不是一个好的方法,请为我指出正确的方向。

这句话的语境是:

  • 当我构造网格时,我传入一个动作来绑定列,这些列也是流畅的(例如,我可以在列上添加一个类)。现在,为了渲染这些,我认为在我的IGridBuilder中保存对所有IColumnBuilder实例的引用是一个好主意,这样在我的GridBuilder渲染方法中,我可以做一些像

    //列前显示

    foreach (var c in myColumns){c.Render ();}

因此,我在GridBuilder中创建了一个包含所有columnbuilder的列表。当我执行WithColumns时,我的GridBuilder对象(this)被传递,然后在ColumnBuilder中,在每个Bind()函数上,我将ColumnBuilder对象添加到传递的引用中。

但是这有一个奇怪的行为(例如,包含ColumnBuilder的列表,并不都匹配上次执行的ColumnBuilder的属性)。

Fluent API:引用类型从彼此获取值

方法1

public class ColumnBuilderFactory<TModel> : IColumnBuilderFactory<TModel>
{
    #region Constructors
    public ColumnBuilderFactory(IGridBuilder<TModel> gridBuilder)
    {
        gridBuilderReference = gridBuilder;
    }
    #endregion
    #region IColumnBuilderFactory Members
    private IGridBuilder<TModel> gridBuilderReference { get; private set; }
    internal IList<IColumnBuilder<TModel>> Columns {get; private set; }
    public IColumnBuilder<TModel> New()
    {
        var column = new ColumnBuilder(gridBuilderReference);
        Columns.Add(column);
        return column;
    }
    #endregion
}

和GridBuilder WithColumns变成

public IGridBuilder<TModel> WithColumns(Action<IColumnBuilderFactory<TModel>> bindAllColumns)
{
    var factory = new ColumnBuilderFactory<TModel>(this);
    bindAllColumns(factory);
    foreach(var column in factory)
    {
        this.ColumnBuilders.Add(column );
    }        
    return this;
}

它的用法是

RenderedOutput = HtmlHelper.GridFor(Model)
.WithColumns(columnFactory =>
{
    columnFactory.New().Bind(x => x.Name)
        .WithCss("inline");
    columnFactory.New().Bind(x => x.Age)
        .WithCss("inline fixed right");
})
.Render();

方法2

的替代方法是通过欺骗和忽略它(使用FluentAPI来替换它),基本上它和你现在的用法一样,但是Bind变成了

public IColumnBuilder<TModel> Bind<TItem>(Expression<Func<TModel, TItem>> propertySelector)
{
    var builder = new ColumnBuilder<TModel>(GridBuilderReference);
    GridBuilderReference.ColumnBuilders.Add(builder);
    return builder;
}

它依赖于GC整理第一个(无用的)实例

所以…

RenderedOutput = HtmlHelper.GridFor(Model)
.WithColumns(column =>
{
//column at this point isn't used, it's only there to avoid a NullReferenceException on the first call to Bind
    column
.Bind(x => x.Name)
//Bind has returned a new ColumnBuilder to play with so the next call will be on the new instance
        .WithCss("inline");
//Again this is just so we can make the next bind call
    column
//Again bind replaces the previous instance with a new one so we won't overwrite Name with this call
.Bind(x => x.Age)
//This now sets the css on the new ColumnBuilder we just created for Age
        .WithCss("inline fixed right");
})
.Render();

示例控制台应用程序显示方法2

using System;
using System.Collections.Generic;
using System.Linq.Expressions;
namespace ConsoleApplication3
{
    class Program
    {
        static void Main(string[] args)
        {
            var master = FluentHelper.GridFor(new Model())
            .WithColumns(column =>
                {
                    column.Bind(x => x.Name)
                        .WithCSS("inline");
                    column.Bind(x => x.Age)
                        .WithCSS("inline fixed right");
                });
            foreach(var c in master.children)
            {
                Console.WriteLine(c.Binding.ToString());
                Console.WriteLine(c.CSS);
            }
            Console.ReadKey(true);
        }
    }
    class Model
    {
        public string Name { get; set; }
        public string Age { get; set; }
    }
    class GridBuilder<T>
    {
        public GridBuilder<T> WithColumns(Action<ColumnBuilder<T>> bindAllColumns)
        {
            bindAllColumns(new ColumnBuilder<T>(this));
            return this;
        }
        public List<ColumnBuilder<T>> children = new List<ColumnBuilder<T>>();
    }
    class ColumnBuilder<T>
    {
        private GridBuilder<T> grid;
        public string Binding;
        public string CSS;
        public ColumnBuilder(GridBuilder<T> grid)
        {
            // TODO: Complete member initialization
            this.grid = grid;
        }
        public void WithCSS(string css)
        {
            this.CSS = css;
        }
        public ColumnBuilder<T> Bind<TItem>(Expression<Func<T, TItem>> propertySelector)
        {
            var builder = new ColumnBuilder<T>(grid);
            builder.Binding = (propertySelector.Body as MemberExpression).Member.Name;
            grid.children.Add(builder);
            return builder;
        }
    }
    static class FluentHelper
    {
        internal static GridBuilder<T> GridFor<T>(T model)
        {
            return new GridBuilder<T>();
        }
    }
}