lundi 31 juillet 2017

Get contained type of list in generics, lambda

Even though I know that such libraries already exist, I have decided to create an MVC5 HtmlHelper library to generate Tables from a model. The point is to practice (actually learn) about generics and lambdas in C#.

I want my library to be able to generate an HTML table using a syntax such as:

@Html.DisplayTable(model => model.ListTest).Render()

At this point in time, the above code works perfectly. But when I attempted to add the method exclude it all went wrong ... And this is where I see how poor my understanding of lambdas and delegate is. (it is actually the first I wrote any code that took a delegate as a parameter)

Below is how I want my call to be made

@Html.DisplayTable(model => model.ListTest).Exclude(l => l.Col1).Render()

then here is the model that I provide to my view

public class TestViewModel
{
    public List<RowViewModel> ListTest { get; set; }
}

where RowViewModel is defined as follow

public class RowViewModel
{
    public string Col1 { get; set; } = "Col1Value";
    public string Col2 { get; set; } = "Col2Value";
    public string Col3 { get; set; } = "Col3Value";
}

My helper

public static class TableHelpers
{
    public static HtmlTable<TValue> DisplayTable<TModel, TValue>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TValue>> expression) where TValue : IList
    {
        Func<TModel, TValue> deleg = expression.Compile();
        var result = deleg(helper.ViewData.Model);

        return new HtmlTable<TValue>(result);
    }
}

And my "library logic"

 public class HtmlTable<TValue> where TValue : IList
{
    private TValue _inputModel;
    private readonly TableViewModel _outputViewModel = new TableViewModel();

    public HtmlTable(TValue model)
    {
        Init(model);
    }

    public HtmlTable<TValue> Init(TValue model)
    {
        if(model.Count == 0)
            throw new ArgumentException("The list must not be empty");

        _inputModel = model;

        UpdateViewModel();

        return this;
    }

    private void UpdateViewModel()
    {
        var subType = _inputModel.GetContainedType();

        var properties = subType.GetProperties();
        _outputViewModel.Header = properties.Select(p => p.Name).ToList();
        _outputViewModel.Rows = new List<List<string>>();
        foreach (var row in _inputModel)
        {
            var values = _outputViewModel.Header.Select(col => subType.GetProperty(col).GetValue(row, null).ToString()).ToList();
            _outputViewModel.Rows.Add(values);
        }
    }

    public HtmlTable<TValue> Exclude<TSubValue>(Expression<Func<TValue, TSubValue>> expression)
    {

        Func<TValue, TSubValue> deleg = expression.Compile();
        var result = deleg(_inputModel);

        return this;
    }
}

I kind of have an idea of how it should be done if i was to specify the model of a row to my helper such as

@Html.DisplayTable<RowViewModel>(model => model.ListTest).Exclude(l => l.Col1).Render()

but I would really like to be able to determine the type contained in a list to make lambdas work.

All of this might not make any sense, but after struggling with understand lambdas from a theory point of view for a while i decided that it was better to stop reading articles and try to do something with it ...





Aucun commentaire:

Enregistrer un commentaire