Пользовательский медиатипформатор не работает на унаследованных классах
У меня есть эта строка:
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"))
Он отлично работает и создает Excelfile из моего веб-API.
У меня есть несколько подклассов, которые наследуют этот класс Classification, и я хочу сделать mediaformatter для каждого подкласса для получения определенных столбцов в excelformatter. Проблема в том, что если я делаю так:
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"))
Тогда это не работает вообще. Он просто генерирует XML из стандартного форматера. Как я могу заставить его реагировать на подкласс, когда веб-API возвращает
IQueryable(Of Classification)
Форматировщик:
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);
}
}
}
1 ответ
CanWriteType
вернусь false
, так как T
является CustomClassification
а также type
является Classification
, Ваш форматер не будет использоваться, если он возвращает false.
Потому что Classification
не обязательно CustomClassification
это не может работать.
Чтобы достичь того, чего вы хотите, вам нужно немного изменить свою реализацию.
Ваш ExcelMediaTypeFormatter
больше не будет общим. И он не пройдет мимо Func
но список IRowsBuilder
экземпляров. Они будут использованы в WriteToStream
Способ выбора правильного застройщика:
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);
// ...
}
}
}
Вы бы теперь только зарегистрировать один ExcelMediaTypeFormatter
со всеми строителями:
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);