Генерация CSV из ASP.NET MVC приложения

21.01.2011 22:51 / Артём Волк / 1080 просмотров / ...

Интересный метод генерации CSV из ASP.NET MVC с помощью класса-наследника System.Web.Mvc.FileResult. Плюсы решения:

  • Используется стандартный класс ASP.NET MVC для генерации файла для скачивания
  • CSV можно сгенерировать практически из любой коллекции объектов
  • Заголовки колонок генерируются из значений атрибутов [DisplayName]
  • Можно указать имена свойств, которые не нужно экспортировать

Похоже, в оригинальном коде есть ошибка — исключение полей из набора не работало как нужно. К сожалению, комментарии к оригинальной статье закрыты, исправленный вариант кода ниже.

Если класс располагается не в ASP.NET MVC проекте (а, например, в проекте типа Class Library) для корректной компиляции важно не забыть добавить в References System.Web.Abstractions.

/*
	Author:
		http://www.notesfor.net/post/2010/06/28/AspNet-custom-ActionResult-CSV.aspx
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.IO;
using System.Web.Mvc;

namespace NotesForNet.CSV
{	
	public sealed class CSVResult<T> : System.Web.Mvc.FileResult
	{
		private IEnumerable<T> collection;
		private String[] columnsToExclude;

		public CSVResult(IEnumerable<T> collection, params String[] columnsToExclude)
			: base("text/csv")
		{
			this.collection = collection;
			this.columnsToExclude = columnsToExclude;
		}

		protected override void WriteFile(HttpResponseBase response)
		{
			System.IO.Stream outputStream = response.OutputStream;
			using (MemoryStream mstream = new MemoryStream())
			{
				WriteObject(mstream);
				outputStream.Write(mstream.GetBuffer(), 0, (int)mstream.Length);
			}
		}

		private void WriteObject(Stream stream)
		{
			// We will follow the recommandations stated in this article
			// http://www.commentcamarche.net/faq/sujet-7273-exporter-a-coup-sur-du-csv
			// - embedding all values in quotes
			// - double quote literal values
			// - add \n\r between each lines.

			StreamWriter writer = new StreamWriter(stream, System.Text.Encoding.Default);

			// Render columns
			Type modelType = typeof(T);
			List<ModelMetadata> metadatas = ModelMetadataProviders.Current.GetMetadataForProperties(null, modelType).ToList();

			for (int i = 0; i < metadatas.Count; )
			{
				if (Array.IndexOf<String>(columnsToExclude, metadatas[i].PropertyName) != -1)
				metadatas.RemoveAt(i);
				else
				{
					WriteValue(writer, metadatas[i].DisplayName ?? metadatas[i].PropertyName);
					i++;
				}
			}

			writer.WriteLine();
			// Render data
			var en = collection.GetEnumerator();

		// Start of fix				
			while (en.MoveNext())
			{
				ModelMetadata mprop = ModelMetadataProviders.Current.GetMetadataForType(() => en.Current, modelType);
				foreach (ModelMetadata prop in mprop.Properties)
				{
					if (Array.IndexOf<String>(columnsToExclude, prop.PropertyName) == -1)
					{
						WriteValue(writer, prop.SimpleDisplayText ?? String.Empty);
					}
				}
				writer.WriteLine();
			}
		// End of fix

			writer.Flush();
		}

		private static void WriteValue(StreamWriter writer, String literal)
		{
			// Enclose values in quote
			writer.Write("\"");
			string line = literal.Replace("\"", "\"\"");
			writer.Write(line);
			writer.Write("\";");
		}
	}
}