CRUD Operations In ASP.NET Core 1.0 MVC Application - Part Eight

Let's discuss how to export data or lists in CSV format. For this purpose, we will implement Custom Response Formatter CsvOutputFormatter and a generic helper CsvWriter to convert any List of objects to CSV format. We are going to extend our application from our last discussion CRUD Operations In ASP.NET Core 1.0 MVC Application - Part Seven.

Add CsvWriter in WebApplicationCore.NetCore

  • Open existing Solution in Visual Studio 2015.
  • Now, add a new folder "Common" in WebApplicationCore.NetCore.

    • Open Context Menu of project >> Add >> New Folder.
    • Name it Common.

  • Now, add a new class CsvWriter.cs in Common folder in WebApplicationCore.NetCore.

    • Open Add New Item Screen through Solution Context Menu of Common folder >> Add >> Class >> Installed >> .NET Core >> Class.
    • Name it CsvWriter.cs.
    • Click OK button.
    • It will add a new class CsvWriter.cs in Common folder.

  • Add required implementation in CsvWriter to generate CSV string for provided list of objects. CsvWriter is a helper class and it provides facility to generate CSV of provided List of objects. We have changed it for our requirements, please refer to How To Implement .NET Core CSV Writer for more details.

    Add CsvWriter in WebApplicationCore.NetCore
    1. public class CsvWriter {  
    2.     privateconst string DELIMITER = ",";  
    3.   
    4.     public string Write(IList list, bool includeHeader = true) {  
    5.         StringBuildersb = new StringBuilder();  
    6.   
    7.         Type type = list.GetType().GetGenericArguments()[0];  
    8.   
    9.         //Get property collection and set selected property list  
    10.         PropertyInfo[] properties = type.GetProperties();  
    11.   
    12.         //Add Header Names to Csv   
    13.         if (includeHeader) {  
    14.             sb.AppendLine(this.CreateCsvHeaderLine(properties));  
    15.         }  
    16.   
    17.         //Iterate through data list collection  
    18.         foreach(var item in list) {  
    19.             sb.AppendLine(this.CreateCsvLine(item, properties));  
    20.         }  
    21.   
    22.         returnsb.ToString();  
    23.     }  
    24.   
    25.     private string CreateCsvHeaderLine(PropertyInfo[] properties) {  
    26.         List < string > propertyValues = new List < string > ();  
    27.   
    28.         foreach(var prop in properties) {  
    29.             stringstringformatString = string.Empty;  
    30.             string value = prop.Name;  
    31.   
    32.             var attribute = prop.GetCustomAttribute(typeof(DisplayAttribute));  
    33.             if (attribute != null) {  
    34.                 value = (attribute as DisplayAttribute).Name;  
    35.             }  
    36.   
    37.             this.CreateCsvStringItem(propertyValues, value);  
    38.         }  
    39.   
    40.         returnthis.CreateCsvLine(propertyValues);  
    41.     }  
    42.   
    43.     private string CreateCsvLine(object item, PropertyInfo[] properties) {  
    44.         List < string > propertyValues = new List < string > ();  
    45.   
    46.         foreach(var prop in properties) {  
    47.             stringstringformatString = string.Empty;  
    48.             object value = prop.GetValue(item, null);  
    49.   
    50.             if (prop.PropertyType == typeof(string)) {  
    51.                 this.CreateCsvStringItem(propertyValues, value);  
    52.             } else if (prop.PropertyType == typeof(string[])) {  
    53.                 this.CreateCsvStringArrayItem(propertyValues, value);  
    54.             } else if (prop.PropertyType == typeof(List < string > )) {  
    55.                 this.CreateCsvStringListItem(propertyValues, value);  
    56.             } else {  
    57.                 this.CreateCsvItem(propertyValues, value);  
    58.             }  
    59.         }  
    60.   
    61.         returnthis.CreateCsvLine(propertyValues);  
    62.     }  
    63.   
    64.     private string CreateCsvLine(IList < string > list) {  
    65.         returnstring.Join(CsvWriter.DELIMITER, list);  
    66.     }  
    67.   
    68.     private void CreateCsvItem(List < string > propertyValues, object value) {  
    69.         if (value != null) {  
    70.             propertyValues.Add(value.ToString());  
    71.         } else {  
    72.             propertyValues.Add(string.Empty);  
    73.         }  
    74.     }  
    75.   
    76.     private void CreateCsvStringListItem(List < string > propertyValues, object value) {  
    77.         string formatString = "\"{0}\"";  
    78.         if (value != null) {  
    79.             value = this.CreateCsvLine((List < string > ) value);  
    80.             propertyValues.Add(string.Format(formatString, this.ProcessStringEscapeSequence(value)));  
    81.         } else {  
    82.             propertyValues.Add(string.Empty);  
    83.         }  
    84.     }  
    85.   
    86.     private void CreateCsvStringArrayItem(List < string > propertyValues, object value) {  
    87.         string formatString = "\"{0}\"";  
    88.         if (value != null) {  
    89.             value = this.CreateCsvLine(((string[]) value).ToList());  
    90.             propertyValues.Add(string.Format(formatString, this.ProcessStringEscapeSequence(value)));  
    91.         } else {  
    92.             propertyValues.Add(string.Empty);  
    93.         }  
    94.     }  
    1. private void CreateCsvStringItem(List < string > propertyValues, object value) {  
    2.     string formatString = "\"{0}\"";  
    3.     if (value != null) {  
    4.         propertyValues.Add(string.Format(formatString, this.ProcessStringEscapeSequence(value)));  
    5.     } else {  
    6.         propertyValues.Add(string.Empty);  
    7.     }  
    8. }  
    9.   
    10. private string ProcessStringEscapeSequence(object value) {  
    11.     returnvalue.ToString().Replace("\"", "\"\"");  
    12. }  
    13. }  

Add CsvOutputFormatter in WebApplicationCore.NetCore

  • Now, add a new class CsvOutputFormatter.cs in Common folder in WebApplicationCore.NetCore.

    • Open "Add New Item" screen through Solution Context Menu of Common folder >> Add >> Class >> Installed >> .NET Core >> Class.
    • Name it CsvOutputFormatter.cs.
    • Click OK button. 
    • It will add a new class CsvOutputFormatter.cs in Common folder.

  • Implement Custom Output Formatter in CsvOutputFormatter class to generate response in CSV format using CsvWriter. To create a new Custom Output Formatter we can use one of following approaches

    • Inherent existing output formatter base class TextOutputFormatter, OutputFormatter or StreamOutputFormatter as per requirement. For example, JsonOutputFormatter and XmlSerializerOutputFormatter inherit TextOutputFormatter. Although, this approach provides many features out of box due to base class, yet it makes formatter little heavier. 
    • Alternatively, Implement IOutputFormatter interface and we just have to implement two methods: CanWriteResult and WriteAsync. Where CanWriteResult is used to check if this formatter can be used to process response or not, while WriteAsync method performs actual processing on response. We have used this approach as it is simple and efficient.
    • ASP.NET Core provisions us to implement Custom Response Formatter and Custom Request Formatter separately. In this way, we can use any kind of Response and Request formatter as per requirements. We will discuss Custom Response and Request Formatter in detail in future sessions.

  • We also need to register CsvOutputFormatter, for this purpose, we register it in ConfigureServices method of Startup class with MVC service as Setup Action. Simply put, we have to add CsvOutputFormatter in collection of OutputFormatters of MVC Service.

    Add CsvWriter in WebApplicationCore.NetCore
    1. public class CsvOutputFormatter: IOutputFormatter {  
    2.     public bool CanWriteResult(OutputFormatterCanWriteContext context) {  
    3.         if (context == null) {  
    4.             throw new ArgumentNullException(nameof(context));  
    5.         }  
    6.   
    7.         if (context.ContentType == null || context.ContentType.ToString() == "text/csv") {  
    8.             return true;  
    9.         } else {  
    10.             return false;  
    11.         }  
    12.     }  
    13.   
    14.     publicasync Task WriteAsync(OutputFormatterWriteContext context) {  
    15.         if (context == null) {  
    16.             throw new ArgumentNullException(nameof(context));  
    17.         }  
    18.   
    19.         var response = context.HttpContext.Response;  
    20.         response.ContentType = "text/csv";  
    21.   
    22.         using(var writer = context.WriterFactory(response.Body, Encoding.UTF8)) {  
    23.             IListlst = context.Object as IList;  
    24.             CsvWritercsvWriter = new CsvWriter();  
    25.   
    26.             string csv = csvWriter.Write(lst, true);  
    27.   
    28.             writer.Write(csv);  
    29.   
    30.             awaitwriter.FlushAsync();  
    31.         }  
    32.     }  
    33. }  
    1. public void ConfigureServices(IServiceCollection services) {  
    2.     // Add framework services.  
    3.     services.AddMvc(options => options.OutputFormatters.Add(new CsvOutputFormatter()));  
    4.   
    5.     services.AddSingleton < IConfigurationRoot > (sp => {  
    6.         return this.Configuration;  
    7.     });  
    8.     services.AddScoped < IContactDataAccess, ContactDataAccess > ();  
    9.     services.AddScoped < IContactBusinessLogic, ContactBusinessLogic > ();  
    10. }  

GetCsv Action Method and Get CSV Link

  • Now, add a new action method GetCsv in WebApplicationCore.NetCore.ContactController class. It is similar to Index Action Method with the exception that it returns List<ContactListVM> and has [Produces("text/csv")] attribute to mark content type to enforce ustilizationof CsvOutputFormatter. We are explicitly checking content type == "text/csv" in CanWriteResult.

  • Add Get CSV link in Contract\Index.cshtml for GetCsv action method.

    Add CsvWriter in WebApplicationCore.NetCore
    1. [Produces("text/csv")]  
    2. public List < ContactListVM > GetCsv() {  
    3.     List < Contact > contacts = this.ContactBusinessLogic.GetContacts();  
    4.     List < ContactListVM > contactVMs = new List < ContactListVM > ();  
    5.     ContactListVMcontactVM;  
    6.   
    7.     foreach(Contact contact in contacts) {  
    8.         contactVM = new ContactListVM {  
    9.             ContactId = contact.ContactId,  
    10.                 ContactNumber = contact.ContactNumber,  
    11.                 Email = contact.Email,  
    12.                 Name = contact.Name,  
    13.                 WebSite = contact.WebSite  
    14.         };  
    15.         contactVMs.Add(contactVM);  
    16.     }  
    17.   
    18.     returncontactVMs;  
    19. }  
    20.   
    21. <  
    22. th >  
    23.     <  
    24.     a asp - controller = "Contact"  
    25. asp - action = "GetCsv" > Get CSV < /a> <  
    26.     /th>  

Run Application in Debug Mode

  • Press F5 or Debug Menu >> Start Debugging or press IIS Express button on Toolbar to start application in debugging mode.
  • It will show Home Page in browser.
  • Click Contact List Menu Open to open Contact List Page.
  • Click Get CSV link it will show open save dialog.
  • Save response as CSV.
  • Open file, it will open CSV file in default program, most probably in Excel or Notepad.

    Add CsvWriter in WebApplicationCore.NetCore

Sample Source Code

I have placed sample code for this session in "CRUD operations in ASP.NET Core 1.0 MVC Application Part 8_Code.zip" in CodePlex repository.