Thursday, 22 September 2016

CRUD operations in ASP.NET Core 1.0 MVC Application Part 8

Let's discuss how to export data or list 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 last discussion  CRUD operations in ASP.NET Core 1.0 MVC Application Part 7.

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 .NET Core CSV Writer for more details.

 public class CsvWriter
 {
    private const string DELIMITER = ",";

    public string Write(IList list, bool includeHeader = true)
    {
        StringBuilder sb = new StringBuilder();

        Type type = list.GetType().GetGenericArguments()[0];

        //Get property collection and set selected property list
        PropertyInfo[] properties = type.GetProperties();

        //Add Header Names to Csv 
        if (includeHeader)
        {
            sb.AppendLine(this.CreateCsvHeaderLine(properties));
        }

        //Iterate through data list collection
        foreach (var item in list)
        {
            sb.AppendLine(this.CreateCsvLine(item, properties));
        }

        return sb.ToString();
    }

    private string CreateCsvHeaderLine(PropertyInfo[] properties)
    {
        List<string> propertyValues = new List<string>();

        foreach (var prop in properties)
        {
            string formatString = string.Empty;
            string value = prop.Name;

            var attribute = prop.GetCustomAttribute(typeof(DisplayAttribute));
            if (attribute != null)
            {
                value = (attribute as DisplayAttribute).Name;
            }

            this.CreateCsvStringItem(propertyValues, value);
        }

        return this.CreateCsvLine(propertyValues);
    }

    private string CreateCsvLine(object item, PropertyInfo[] properties)
    {
        List<string> propertyValues = new List<string>();

        foreach (var prop in properties)
        {
            string formatString = string.Empty;
            object value = prop.GetValue(item, null);

            if (prop.PropertyType == typeof(string))
            {
                this.CreateCsvStringItem(propertyValues, value);
            }
            else if (prop.PropertyType == typeof(string[]))
            {
                this.CreateCsvStringArrayItem(propertyValues, value);
            }
            else if (prop.PropertyType == typeof(List<string>))
            {
                this.CreateCsvStringListItem(propertyValues, value);
            }
            else
            {
                this.CreateCsvItem(propertyValues, value);
            }
        }

        return this.CreateCsvLine(propertyValues);
    }

    private string CreateCsvLine(IList<string> list)
    {
        return string.Join(CsvWriter.DELIMITER, list);
    }

    private void CreateCsvItem(List<string> propertyValues, object value)
    {
        if (value != null)
        {
            propertyValues.Add(value.ToString());
        }
        else
        {
            propertyValues.Add(string.Empty);
        }
    }

    private void CreateCsvStringListItem(List<string> propertyValues, object value)
    {
        string formatString = "\"{0}\"";
        if (value != null)
        {
            value = this.CreateCsvLine((List<string>)value);
            propertyValues.Add(string.Format(formatString, this.ProcessStringEscapeSequence(value)));
        }
        else
        {
            propertyValues.Add(string.Empty);
        }
    }

    private void CreateCsvStringArrayItem(List<string> propertyValues, object value)
    {
        string formatString = "\"{0}\"";
        if (value != null)
        {
            value = this.CreateCsvLine(((string[])value).ToList());
            propertyValues.Add(string.Format(formatString, this.ProcessStringEscapeSequence(value)));
        }
        else
        {
            propertyValues.Add(string.Empty);
        }
    }

    private void CreateCsvStringItem(List<string> propertyValues, object value)
    {
        string formatString = "\"{0}\"";
        if (value != null)
        {
            propertyValues.Add(string.Format(formatString, this.ProcessStringEscapeSequence(value)));
        }
        else
        {
            propertyValues.Add(string.Empty);
        }
    }

    private string ProcessStringEscapeSequence(object value)
    {
        return value.ToString().Replace("\"", "\"\"");
    }
 }

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. In simple, we have to add CsvOutputFormatter in collection of OutputFormatters of MVC Service.

 public class CsvOutputFormatter : IOutputFormatter
 {
    public bool CanWriteResult(OutputFormatterCanWriteContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }

        if (context.ContentType == null || context.ContentType.ToString() == "text/csv")
        {
            return true;
        }
        else
        {
            return false;
        }
    }

    public async Task WriteAsync(OutputFormatterWriteContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }

        var response = context.HttpContext.Response;
        response.ContentType = "text/csv";

        using (var writer = context.WriterFactory(response.Body, Encoding.UTF8))
        {
            IList lst = context.Object as IList;
            CsvWriter csvWriter = new CsvWriter();

            string csv = csvWriter.Write(lst, true);

            writer.Write(csv);

            await writer.FlushAsync();
        }
    }
 }


 public void ConfigureServices(IServiceCollection services)
 {
    // Add framework services.
    services.AddMvc(options=>options.OutputFormatters.Add(new CsvOutputFormatter()));

    services.AddSingleton<IConfigurationRoot>(sp => { return this.Configuration; });
    services.AddScoped<IContactDataAccess, ContactDataAccess>();
    services.AddScoped<IContactBusinessLogic, ContactBusinessLogic>();
 }

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 exception that it returns List<ContactListVM> and has [Produces("text/csv")] attribute to mark content type to enforce ustilization of CsvOutputFormatter. We are explicitly checking content type == "text/csv" in CanWriteResult.
  •  Add Get CSV link in Contract\Index.cshtml for GetCsv action method.

 [Produces("text/csv")]
 public List<ContactListVM> GetCsv()
 {
    List<Contact> contacts = this.ContactBusinessLogic.GetContacts();
    List<ContactListVM> contactVMs = new List<ContactListVM>();
    ContactListVM contactVM;

    foreach (Contact contact in contacts)
    {
        contactVM = new ContactListVM
        {
            ContactId = contact.ContactId,
            ContactNumber = contact.ContactNumber,
            Email = contact.Email,
            Name = contact.Name,
            WebSite = contact.WebSite
        };
        contactVMs.Add(contactVM);
    }

    return contactVMs;
 }

 <th>
    <a asp-controller="Contact" asp-action="GetCsv">Get CSV</a>
 </th>

Run Application in Debug Mode

  • Press F5 or Debug Menu >> Start Debugging or Start 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.

Sample Source Code

We have placed sample code for this session in "CRUD operations in ASP.NET Core 1.0 MVC Application Part 8_Code.zip" in https://aspdotnetcore.codeplex.com/SourceControl/latest CodePlex repository.


CRUD Operations in AP.NET Core 1.0 All Parts

CRUD operations in ASP.NET Core 1.0 MVC Application Part 1
CRUD operations in ASP.NET Core 1.0 MVC Application Part 2
CRUD operations in ASP.NET Core 1.0 MVC Application Part 3
CRUD operations in ASP.NET Core 1.0 MVC Application Part 4
CRUD operations in ASP.NET Core 1.0 MVC Application Part 5
CRUD operations in ASP.NET Core 1.0 MVC Application Part 6
CRUD operations in ASP.NET Core 1.0 MVC Application Part 7 
CRUD operations in ASP.NET Core 1.0 MVC Application Part 8

No comments:

Post a Comment