Question

I have this line:

GlobalConfiguration.Configuration.Formatters.Add(New ExcelMediaTypeFormatter(Of Classification)(Function(t) New ExcelRow(ExcelCell.Map(t.ChemicalAbstractService), ExcelCell.Map(t.Substance), ExcelCell.Map(t.Columns("Classifidcation")), ExcelCell.Map(t.Columns("Classification"))), Function(format) "excel"))

It works fine and creates a excelfile from my web api.

I have several subclasses that inherits this Classification class and I want to make a mediaformatter for each subclass for getting specific columns in the excelformatter. The problems is that if I do like this:

GlobalConfiguration.Configuration.Formatters.Add(New ExcelMediaTypeFormatter(Of CustomClassification)(Function(t) New ExcelRow(ExcelCell.Map(t.ChemicalAbstractService), ExcelCell.Map(t.Substance), ExcelCell.Map(t.Columns("Classifidcation")), ExcelCell.Map(t.Columns("Classification"))), Function(format) "excel"))

Then it doesn't work at all. It just generates xml from the standard formatter instead. How can I make it react to a subclass, when the web api returns a

IQueryable(Of Classification)

The formatter:

public class ExcelMediaTypeFormatter<T> : BufferedMediaTypeFormatter 
    {
    private const string ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
    private readonly Func<T, ExcelRow> builder;

    public ExcelMediaTypeFormatter(Func<T, ExcelRow> value)
    {
        builder = value;
        SupportedMediaTypes.Add(new MediaTypeHeaderValue(ContentType));
    }

    public ExcelMediaTypeFormatter(Func<T, ExcelRow> value, params Func<object, string>[] type)
        : this(value)
    {
        foreach (var mediaTypeMapping in type) {
            this.MediaTypeMappings.Add(Map(mediaTypeMapping));
        }
    }

    public override bool CanWriteType(Type type)
    {
        return type == typeof(IQueryable<T>) || type == typeof(T);
    }

    public override bool CanReadType(Type type)
    {
        return false;
    }
public override void WriteToStream(Type type, object value, Stream writeStream, HttpContent content)
    {            
        using (Stream ms = new MemoryStream()) {
            using (var book = new ClosedXML.Excel.XLWorkbook()) {
                var sheet = book.Worksheets.Add("sample");

                ICollection<T> rows = type == typeof(IQueryable<T>) ? ((IQueryable<T>)value).ToList() : new List<T>() { (T)value };

                for (var r = 0; r < rows.Count; r++) {
                    var result = builder((T)rows.ElementAt(r));

                    for (var c = 0; c < result.Count(); c++) {
                        if (result.ElementAt(c) != null)
                            sheet.Cell(r + 2, c + 1).Value = result.ElementAt(c).Value.ToString();
                    }
                }

                sheet.Columns().AdjustToContents();

                book.SaveAs(ms);

                byte[] buffer = new byte[ms.Length];

                ms.Position = 0;
                ms.Read(buffer, 0, buffer.Length);

                writeStream.Write(buffer, 0, buffer.Length);
            }
        }
    }
Était-ce utile?

La solution

CanWriteType will return false, because T is CustomClassification and type is Classification. Your formatter will not be used if it returns false.

Because a Classification is not necessarily a CustomClassification this can't work.

To achieve what you want, you need to change your implementation a bit.

Your ExcelMediaTypeFormatter will no longer be generic. And it will not get passed one Func but a list of IRowsBuilder instances. These will be used in the WriteToStream method to select the correct builder:

public interface IRowsBuilder
{
    bool CanBuildFor(Type type);
    IEnumerable<Type> SupportedTypes { get; }
    ExcelRow BuildRow(object value);
}

public class RowsBuilder<T> : IRowsBuilder where T : Classification
{
    Func<T, ExcelRow> _builder;

    public RowsBuilder(Func<T, ExcelRow> builder)
    {
        if(builder == null) throw new ArgumentNullException("builder");

        _builder = builder;
    }

    public bool CanBuildFor(Type type)
    {
        return type.IsSubclassOf(typeof(T));
    }

    public IEnumerable<Type> SupportedTypes
    {
        get { yield return typeof(T); }
    }

    public ExcelRow BuildRow(object value)
    {
        if(!CanBuildFor(value.GetType()))
            throw new ArgumentException();

        return _builder((T)value);
    }
}

public class ExcelMediaTypeFormatter : BufferedMediaTypeFormatter 
{
    private readonly ILookup<Type, IRowsBuilder> _builder;

    public ExcelMediaTypeFormatter(IEnumerable<IRowsBuilder> builder)
    {
        _builder = builder.SelectMany(x => builder.SupportedTypes
                                                  .Select(y => new
                                                          {
                                                              Type = y,
                                                              Builder = x
                                                          }))
                          .ToLookup(x => x.Type, x => x.Builder); 
    }

    public override bool CanWriteType(Type type)
    {
        return type == typeof(IQueryable<Classification>) ||
               type == typeof(Classification);
    }

    // ...

    public override void WriteToStream(Type type, object value,
                                       Stream writeStream, HttpContent content)
    {

        // ...

        List<Classification> rows;
        if(type == typeof(IQueryable<Classification>))
            rows = ((IQueryable<Classification>)value).ToList();
        else
            rows = new List<Classification> { (Classification)value };

        for (var r = 0; r < rows.Count; r++)
        {
            var value = rows.ElementAt(r);
            var builder = _builder[value.GetType()];
            var result = builder(value);
            // ...
        }
    }
}

You would now only register one ExcelMediaTypeFormatter with all the builders:

var customBuilder = new RowsBuilder<CustomClassification>(
                        t => new ExcelRow(ExcelCell.Map(t.ChemicalAbstractService),
                                          ExcelCell.Map(t.Substance), 
                                          ExcelCell.Map(t.Columns("Classifidcation")),
                                          ExcelCell.Map(t.Columns("Classification"))));
var builders = new List<IRowsBuilder>();
builder.Add(customBuilder);
builder.Add(someOtherBuilder);
var excelFormatter = new ExcelMediaTypeFormatter(builders, format => "excel");
GlobalConfiguration.Configuration
                   .Formatters
                   .Add(excelFormatter);
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top