diff --git a/samples/WebApiContrib.Core.Samples/Controllers/CsvTestController.cs b/samples/WebApiContrib.Core.Samples/Controllers/CsvTestController.cs
index 2b94380..cd7d0a0 100644
--- a/samples/WebApiContrib.Core.Samples/Controllers/CsvTestController.cs
+++ b/samples/WebApiContrib.Core.Samples/Controllers/CsvTestController.cs
@@ -19,7 +19,7 @@ public IActionResult Get()
[Produces("text/csv")]
public IActionResult GetDataAsCsv()
{
- return Ok( DummyDataList());
+ return Ok(DummyDataList());
}
[HttpGet]
diff --git a/samples/WebApiContrib.Core.Samples/Controllers/FluentCsvTestController.cs b/samples/WebApiContrib.Core.Samples/Controllers/FluentCsvTestController.cs
new file mode 100644
index 0000000..664c171
--- /dev/null
+++ b/samples/WebApiContrib.Core.Samples/Controllers/FluentCsvTestController.cs
@@ -0,0 +1,182 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using Microsoft.AspNetCore.Mvc;
+using WebApiContrib.Core.Formatter.Csv;
+using WebApiContrib.Core.Samples.Model;
+
+namespace WebApiContrib.Core.Samples.Controllers
+{
+ ///
+ ///
+ /// Configuration is used for both CSV Output AND Input formatters
+ ///
+ /// 1. Only primitive value type properties are allowed for UseProperty (no reference types and no method calls are allowed)
+ /// 2. Chain parameterless ForHeader() method when:
+ /// a) Not using headers (UseProperty is always chained after UseHeader)
+ /// b) Using headers but you want them generated automatically based on property name (or path)
+ /// 3. Chain method UseCsvDelimiter(string) when you want to override default delimiter (semilocolon)
+ /// 4. Chain method UseEncoding when you want to override default encoding (ISO-8859-1)
+ /// 5. Chain method UseFormatProvider when you want to provide custom formatting for your primitive types
+ ///
+ ///
+ public class AuthorModelConfiguration : IFormattingConfiguration
+ {
+ public void Configure(IFormattingConfigurationBuilder builder)
+ {
+ CultureInfo culture = CultureInfo.CreateSpecificCulture("en-US");
+ DateTimeFormatInfo dtfi = culture.DateTimeFormat;
+ dtfi.DateSeparator = "-";
+ builder
+ .UseHeaders()
+ .UseFormatProvider(culture)
+ .ForHeader("Identifier")
+ .UseProperty(x => x.Id)
+ .ForHeader("First Name")
+ .UseProperty(x => x.FirstName)
+ .ForHeader("Last Name")
+ .UseProperty(x => x.LastName)
+ .ForHeader("Date of Birth")
+ .UseProperty(x => x.DateOfBirth)
+ .ForHeader("IQ")
+ .UseProperty(x => x.IQ)
+ .ForHeader("Street")
+ .UseProperty(x => x.Address.Street)
+ .ForHeader("City")
+ .UseProperty(x => x.Address.City)
+ // Header name will be inferred from property path 'Address.City'
+ .ForHeader()
+ .UseProperty(x => x.Address.Country)
+ // Header name will be inferred from property name 'Signature'
+ .ForHeader()
+ .UseProperty(x => x.Signature);
+ }
+ }
+
+ [Route("api/[controller]")]
+ public class FluentCsvTestController : Controller
+ {
+ // GET api/fluentcsvtest
+ [HttpGet]
+ public IActionResult Get()
+ {
+ return Ok(DummyDataList());
+ }
+
+ [HttpGet]
+ [Route("data.csv")]
+ [Produces("text/csv")]
+ public IActionResult GetDataAsCsv()
+ {
+ return Ok(DummyDataList());
+ }
+
+ [HttpGet]
+ [Route("dataarray.csv")]
+ [Produces("text/csv")]
+ public IActionResult GetArrayDataAsCsv()
+ {
+ return Ok(DummyDataArray());
+ }
+
+ private static IEnumerable DummyDataList()
+ {
+ return new List
+ {
+ new AuthorModel
+ {
+ Id = 1,
+ FirstName = "Joanne",
+ LastName = "Rowling",
+ DateOfBirth = DateTime.Now,
+ IQ = 70,
+ Signature = "signature",
+ Address = new AuthorAddress
+ {
+ Street = null,
+ City = "London",
+ Country = "UK"
+ }
+ },
+ new AuthorModel
+ {
+ Id = 1,
+ FirstName = "Hermann",
+ LastName = "Hesse",
+ DateOfBirth = DateTime.Now,
+ IQ = 180,
+ Signature = "signature"
+ }
+ };
+ }
+
+ private static AuthorModel[] DummyDataArray()
+ {
+ return new AuthorModel[]
+ {
+ new AuthorModel
+ {
+ Id = 1,
+ FirstName = "Joanne",
+ LastName = "Rowling",
+ DateOfBirth = DateTime.Now,
+ IQ = 70,
+ Signature = "signature",
+ Address = new AuthorAddress
+ {
+ Street = null,
+ City = "London",
+ Country = "UK"
+ }
+ },
+ new AuthorModel
+ {
+ Id = 1,
+ FirstName = "Hermann",
+ LastName = "Hesse",
+ DateOfBirth = DateTime.Now,
+ IQ = 180,
+ Signature = "signature",
+ Address = new AuthorAddress
+ {
+ Street = null,
+ City = "Berlin",
+ Country = "Germany"
+ }
+ }
+ };
+ }
+
+ // POST api/fluentcsvtest/import
+ [HttpPost]
+ [Route("import")]
+ public IActionResult Import([FromBody]List value)
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(ModelState);
+ }
+ else
+ {
+ List data = value;
+ return Ok();
+ }
+ }
+
+ // POST api/fluentcsvtest/import
+ [HttpPost]
+ [Route("importarray")]
+ public IActionResult ImportArray([FromBody]AuthorModel[] value)
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(ModelState);
+ }
+ else
+ {
+ var data = value;
+ return Ok();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/WebApiContrib.Core.Samples/Model/AuthorModel.cs b/samples/WebApiContrib.Core.Samples/Model/AuthorModel.cs
new file mode 100644
index 0000000..a9e7a66
--- /dev/null
+++ b/samples/WebApiContrib.Core.Samples/Model/AuthorModel.cs
@@ -0,0 +1,23 @@
+using System;
+
+namespace WebApiContrib.Core.Samples.Model
+{
+ public class AuthorModel
+ {
+ public long Id { get; set; }
+ public string FirstName { get; set; }
+ public string LastName { get; set; }
+ public DateTime DateOfBirth { get; set; }
+ public int IQ { get; set; }
+ public object Signature { get; set; }
+
+ public AuthorAddress Address { get; set; }
+ }
+
+ public class AuthorAddress
+ {
+ public string Street { get; set; }
+ public string City { get; set; }
+ public string Country { get; set; }
+ }
+}
diff --git a/samples/WebApiContrib.Core.Samples/Program.cs b/samples/WebApiContrib.Core.Samples/Program.cs
index 036091f..83244de 100644
--- a/samples/WebApiContrib.Core.Samples/Program.cs
+++ b/samples/WebApiContrib.Core.Samples/Program.cs
@@ -1,12 +1,5 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Threading.Tasks;
-using Microsoft.AspNetCore;
+using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.Logging;
namespace WebApiContrib.Core.Samples
{
diff --git a/samples/WebApiContrib.Core.Samples/Startup.cs b/samples/WebApiContrib.Core.Samples/Startup.cs
index 60c450a..46f0d20 100644
--- a/samples/WebApiContrib.Core.Samples/Startup.cs
+++ b/samples/WebApiContrib.Core.Samples/Startup.cs
@@ -11,7 +11,6 @@
using WebApiContrib.Core.Versioning;
using WebApiContrib.Core.Samples.Services;
using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Server.Kestrel.Core;
namespace WebApiContrib.Core.Samples
{
@@ -37,7 +36,15 @@ public void ConfigureServices(IServiceCollection services)
{
o.AddJsonpOutputFormatter();
o.UseFromBodyBinding(controllerPredicate: c => c.ControllerType.AsType() == typeof(BindingController));
- }).AddCsvSerializerFormatters()
+ })
+ // Register fluent csv formatters
+ .AddCsvSerializerFormatters(
+ builder =>
+ {
+ builder.RegisterConfiguration(new AuthorModelConfiguration());
+ })
+ // Register standard csv formatters
+ .AddCsvSerializerFormatters()
.AddPlainTextFormatters()
.AddVersionNegotiation(opt =>
{
diff --git a/samples/WebApiContrib.Core.Samples/WebApiContrib.Core.Samples.csproj b/samples/WebApiContrib.Core.Samples/WebApiContrib.Core.Samples.csproj
index a5d8253..2d59450 100644
--- a/samples/WebApiContrib.Core.Samples/WebApiContrib.Core.Samples.csproj
+++ b/samples/WebApiContrib.Core.Samples/WebApiContrib.Core.Samples.csproj
@@ -1,27 +1,27 @@
-
- netcoreapp2.0
- WebApiContrib.Core.Samples
+
+ netcoreapp2.0
+ WebApiContrib.Core.Samples
-
-
- PreserveNewest
-
+
+
+ PreserveNewest
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
+
+
diff --git a/src/WebApiContrib.Core.Formatter.Csv/CsvFormatterMvcBuilderExtensions.cs b/src/WebApiContrib.Core.Formatter.Csv/CsvFormatterMvcBuilderExtensions.cs
index 9ba1fd3..5699ad9 100644
--- a/src/WebApiContrib.Core.Formatter.Csv/CsvFormatterMvcBuilderExtensions.cs
+++ b/src/WebApiContrib.Core.Formatter.Csv/CsvFormatterMvcBuilderExtensions.cs
@@ -1,45 +1,66 @@
-using Microsoft.Extensions.DependencyInjection;
-using System;
+using System;
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Net.Http.Headers;
namespace WebApiContrib.Core.Formatter.Csv
{
public static class CsvFormatterMvcBuilderExtensions
- {
- public static IMvcBuilder AddCsvSerializerFormatters(this IMvcBuilder builder)
- {
- if (builder == null)
- {
- throw new ArgumentNullException(nameof(builder));
- }
-
- return AddCsvSerializerFormatters(builder, csvFormatterOptions: null);
- }
-
- public static IMvcBuilder AddCsvSerializerFormatters( this IMvcBuilder builder, CsvFormatterOptions csvFormatterOptions)
- {
- if (builder == null)
- {
- throw new ArgumentNullException(nameof(builder));
- }
-
- builder.AddFormatterMappings(m => m.SetMediaTypeMappingForFormat("csv", new MediaTypeHeaderValue("text/csv")));
-
- if (csvFormatterOptions == null)
- {
- csvFormatterOptions = new CsvFormatterOptions();
- }
-
- if (string.IsNullOrWhiteSpace(csvFormatterOptions.CsvDelimiter))
- {
- throw new ArgumentException("CsvDelimiter cannot be empty");
- }
-
- builder.AddMvcOptions(options => options.InputFormatters.Add(new CsvInputFormatter(csvFormatterOptions)));
- builder.AddMvcOptions(options => options.OutputFormatters.Add(new CsvOutputFormatter(csvFormatterOptions)));
-
-
- return builder;
- }
- }
-}
+ {
+ public static IMvcBuilder AddCsvSerializerFormatters(this IMvcBuilder builder)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+
+ return AddCsvSerializerFormatters(builder, csvFormatterOptions: null);
+ }
+
+ public static IMvcBuilder AddCsvSerializerFormatters(this IMvcBuilder builder,
+ CsvFormatterOptions csvFormatterOptions)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+
+ builder.AddFormatterMappings(m => m.SetMediaTypeMappingForFormat("csv", new MediaTypeHeaderValue("text/csv")));
+
+ if (csvFormatterOptions == null)
+ {
+ csvFormatterOptions = new CsvFormatterOptions();
+ }
+
+ if (string.IsNullOrWhiteSpace(csvFormatterOptions.CsvDelimiter))
+ {
+ throw new ArgumentException("CsvDelimiter cannot be empty");
+ }
+
+ builder.AddMvcOptions(options => options.InputFormatters.Add(new StandardCsvInputFormatter(csvFormatterOptions)));
+ builder.AddMvcOptions(options => options.OutputFormatters.Add(new StandardCsvOutputFormatter(csvFormatterOptions)));
+
+ return builder;
+ }
+
+ public static IMvcBuilder AddCsvSerializerFormatters(this IMvcBuilder builder,
+ Action configuration)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+
+ builder.AddFormatterMappings(m => m.SetMediaTypeMappingForFormat("csv", new MediaTypeHeaderValue("text/csv")));
+
+ // Register provided configurations
+ var configCollection = new FormattingConfigurationCollection(builder.Services);
+ configuration.Invoke(configCollection);
+ var registeredTypes = configCollection.GetRegistredTypes();
+
+ builder.AddMvcOptions(options => options.InputFormatters.Add(new FluentCsvInputFormatter(registeredTypes)));
+ builder.AddMvcOptions(options => options.OutputFormatters.Add(new FluentCsvOutputFormatter(registeredTypes)));
+
+ return builder;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/WebApiContrib.Core.Formatter.Csv/CsvFormatterMvcCoreBuilderExtensions.cs b/src/WebApiContrib.Core.Formatter.Csv/CsvFormatterMvcCoreBuilderExtensions.cs
index 867564c..52fb9b2 100644
--- a/src/WebApiContrib.Core.Formatter.Csv/CsvFormatterMvcCoreBuilderExtensions.cs
+++ b/src/WebApiContrib.Core.Formatter.Csv/CsvFormatterMvcCoreBuilderExtensions.cs
@@ -1,6 +1,6 @@
-using Microsoft.Extensions.DependencyInjection;
+using System;
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Net.Http.Headers;
-using System;
namespace WebApiContrib.Core.Formatter.Csv
{
@@ -35,8 +35,29 @@ public static IMvcCoreBuilder AddCsvSerializerFormatters(this IMvcCoreBuilder bu
throw new ArgumentException("CsvDelimiter cannot be empty");
}
- builder.AddMvcOptions(options => options.InputFormatters.Add(new CsvInputFormatter(csvFormatterOptions)));
- builder.AddMvcOptions(options => options.OutputFormatters.Add(new CsvOutputFormatter(csvFormatterOptions)));
+ builder.AddMvcOptions(options => options.InputFormatters.Add(new StandardCsvInputFormatter(csvFormatterOptions)));
+ builder.AddMvcOptions(options => options.OutputFormatters.Add(new StandardCsvOutputFormatter(csvFormatterOptions)));
+
+ return builder;
+ }
+
+ public static IMvcCoreBuilder AddCsvSerializerFormatters(this IMvcCoreBuilder builder,
+ Action configuration)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+
+ builder.AddFormatterMappings(m => m.SetMediaTypeMappingForFormat("csv", new MediaTypeHeaderValue("text/csv")));
+
+ // Register provided configurations
+ var configCollection = new FormattingConfigurationCollection(builder.Services);
+ configuration.Invoke(configCollection);
+ var registeredTypes = configCollection.GetRegistredTypes();
+
+ builder.AddMvcOptions(options => options.InputFormatters.Add(new FluentCsvInputFormatter(registeredTypes)));
+ builder.AddMvcOptions(options => options.OutputFormatters.Add(new FluentCsvOutputFormatter(registeredTypes)));
return builder;
}
diff --git a/src/WebApiContrib.Core.Formatter.Csv/CsvFormatterOptions.cs b/src/WebApiContrib.Core.Formatter.Csv/CsvFormatterOptions.cs
index ee2078a..4ad6b4e 100644
--- a/src/WebApiContrib.Core.Formatter.Csv/CsvFormatterOptions.cs
+++ b/src/WebApiContrib.Core.Formatter.Csv/CsvFormatterOptions.cs
@@ -8,4 +8,4 @@ public class CsvFormatterOptions
public string Encoding { get; set; } = "ISO-8859-1";
}
-}
+}
\ No newline at end of file
diff --git a/src/WebApiContrib.Core.Formatter.Csv/CsvInputFormatter.cs b/src/WebApiContrib.Core.Formatter.Csv/CsvInputFormatter.cs
deleted file mode 100644
index 8257716..0000000
--- a/src/WebApiContrib.Core.Formatter.Csv/CsvInputFormatter.cs
+++ /dev/null
@@ -1,127 +0,0 @@
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.IO;
-using System.Reflection;
-using System.Threading.Tasks;
-using Microsoft.AspNetCore.Mvc.Formatters;
-using Microsoft.Net.Http.Headers;
-using System.Text;
-
-namespace WebApiContrib.Core.Formatter.Csv
-{
- ///
- /// ContentType: text/csv
- ///
- public class CsvInputFormatter : InputFormatter
- {
- private readonly CsvFormatterOptions _options;
-
- public CsvInputFormatter(CsvFormatterOptions csvFormatterOptions)
- {
- SupportedMediaTypes.Add(Microsoft.Net.Http.Headers.MediaTypeHeaderValue.Parse("text/csv"));
-
- if (csvFormatterOptions == null)
- {
- throw new ArgumentNullException(nameof(csvFormatterOptions));
- }
-
- _options = csvFormatterOptions;
- }
-
- public override Task ReadRequestBodyAsync(InputFormatterContext context)
- {
- var type = context.ModelType;
- var request = context.HttpContext.Request;
- MediaTypeHeaderValue requestContentType = null;
- MediaTypeHeaderValue.TryParse(request.ContentType, out requestContentType);
-
-
- var result = ReadStream(type, request.Body);
- return InputFormatterResult.SuccessAsync(result);
- }
-
- public override bool CanRead(InputFormatterContext context)
- {
- var type = context.ModelType;
- if (type == null)
- throw new ArgumentNullException("type");
-
- return IsTypeOfIEnumerable(type);
- }
-
- private bool IsTypeOfIEnumerable(Type type)
- {
-
- foreach (Type interfaceType in type.GetInterfaces())
- {
-
- if (interfaceType == typeof(IList))
- return true;
- }
-
- return false;
- }
-
- private object ReadStream(Type type, Stream stream)
- {
- Type itemType;
- var typeIsArray = false;
- IList list;
- if (type.GetGenericArguments().Length > 0)
- {
- itemType = type.GetGenericArguments()[0];
- list = (IList)Activator.CreateInstance(type);
- }
- else
- {
- typeIsArray = true;
- itemType = type.GetElementType();
-
- var listType = typeof(List<>);
- var constructedListType = listType.MakeGenericType(itemType);
-
- list = (IList)Activator.CreateInstance(constructedListType);
- }
-
- var reader = new StreamReader(stream, Encoding.GetEncoding(_options.Encoding));
-
- bool skipFirstLine = _options.UseSingleLineHeaderInCsv;
- while (!reader.EndOfStream)
- {
- var line = reader.ReadLine();
- var values = line.Split(_options.CsvDelimiter.ToCharArray());
- if(skipFirstLine)
- {
- skipFirstLine = false;
- }
- else
- {
- var itemTypeInGeneric = list.GetType().GetTypeInfo().GenericTypeArguments[0];
- var item = Activator.CreateInstance(itemTypeInGeneric);
- var properties = item.GetType().GetProperties();
- for (int i = 0;i
- /// Original code taken from
- /// http://www.tugberkugurlu.com/archive/creating-custom-csvmediatypeformatter-in-asp-net-web-api-for-comma-separated-values-csv-format
- /// Adapted for ASP.NET Core and uses ; instead of , for delimiters
- ///
- public class CsvOutputFormatter : OutputFormatter
- {
- private readonly CsvFormatterOptions _options;
-
- public string ContentType { get; private set; }
-
- public CsvOutputFormatter(CsvFormatterOptions csvFormatterOptions)
- {
- ContentType = "text/csv";
- SupportedMediaTypes.Add(Microsoft.Net.Http.Headers.MediaTypeHeaderValue.Parse("text/csv"));
- _options = csvFormatterOptions ?? throw new ArgumentNullException(nameof(csvFormatterOptions));
- }
-
- protected override bool CanWriteType(Type type)
- {
-
- if (type == null)
- throw new ArgumentNullException("type");
-
- return IsTypeOfIEnumerable(type);
- }
-
- private bool IsTypeOfIEnumerable(Type type)
- {
-
- foreach (Type interfaceType in type.GetInterfaces())
- {
-
- if (interfaceType == typeof(IList))
- return true;
- }
-
- return false;
- }
-
- public async override Task WriteResponseBodyAsync(OutputFormatterWriteContext context)
- {
- var response = context.HttpContext.Response;
-
- Type type = context.Object.GetType();
- Type itemType;
-
- if (type.GetGenericArguments().Length > 0)
- {
- itemType = type.GetGenericArguments()[0];
- }
- else
- {
- itemType = type.GetElementType();
- }
-
- var streamWriter = new StreamWriter(response.Body, Encoding.GetEncoding(_options.Encoding));
-
- if (_options.UseSingleLineHeaderInCsv)
- {
- await streamWriter.WriteLineAsync(
- string.Join(
- _options.CsvDelimiter, itemType.GetProperties().Select(x => x.GetCustomAttribute(false)?.Name ?? x.Name)
- )
- );
- }
-
- foreach (var obj in (IEnumerable