我试图创建一个实用程序类,在那里我可以传递匿名类型(AT)的列表,它会产生一个CSV文件,AT的属性作为其列和属性值作为其各自的数据。
我有一个工作代码,但我觉得它可以改进(很多!)。我从FileResult
继承了一个类,并用我的自定义实现来修饰它。以下是目前为止的内容:
public class ExportCSVAnonymous : FileResult {
public dynamic List {
set;
get;
}
public char Separator {
set;
get;
}
public ExportCSVAnonymous(dynamic list, string fileDownloadName, char separator = ',') : base("text/csv") {
List = list;
Separator = separator;
FileDownloadName = fileDownloadName;
}
public ExportCSVAnonymous(dynamic list, string fileDownloadName, char separator = ',') : base("text/csv") {
List = list;
Separator = separator;
FileDownloadName = fileDownloadName;
}
protected override void WriteFile(HttpResponseBase response) {
var outputStream = response.OutputStream;
using (var memoryStream = new MemoryStream()) {
WriteList(memoryStream);
outputStream.Write(memoryStream.GetBuffer(), 0, (int)memoryStream.Length);
}
}
private void WriteList(Stream stream) {
var streamWriter = new StreamWriter(stream, Encoding.Default);
WriteHeaderLine(streamWriter);
streamWriter.WriteLine();
WriteDataLines(streamWriter);
streamWriter.Flush();
}
//I wish this part could be improved
private void WriteHeaderLine(StreamWriter streamWriter) {
foreach (var line in List) {
foreach (MemberInfo member in line.GetType().GetProperties()) {
WriteValue(streamWriter, member.Name);
}
break;
}
}
private void WriteValue(StreamWriter writer, String value) {
writer.Write(""");
writer.Write(value.Replace(""", """"));
writer.Write(""" + Separator);
}
private void WriteDataLines(StreamWriter streamWriter) {
foreach (var line in List) {
foreach (MemberInfo member in line.GetType().GetProperties()) {
WriteValue(streamWriter, GetPropertyValue(line, member.Name));
}
streamWriter.WriteLine();
}
}
private static string GetPropertyValue(object src, string propName) {
object obj = src.GetType().GetProperty(propName).GetValue(src, null);
return (obj != null) ? obj.ToString() : "";
}
}
我用dynamic
作为一种方式来通过我的AT内的类。有更好的方法吗?最后,我想对WriteHeaderLine
方法进行改进。由于我使用的是dynamic
类型,所以我不能成功地强制转换它来检查AT的属性。最好的方法是什么?
在某种程度上,我觉得为了编写CSV,使用匿名类型(或任何类型)有点矫枉过正。真的)传递信息纯粹,所以你可以传递头也命名属性。我明白了,但是问问你自己代码到底在做什么/你在解决什么问题?
你想写N个字符串到一个文件。
差不多了。所以你写一些简单的方法:
void WriteCsvLine(path, string[] cells){
File.AppendAllText(path, string.Join(",", cells) + Environment.NewLine);
}
你把它叫做:
someContext.Employees.Select(e =>
new [] { e.Name, e.Dept, e.Salary.ToString() }
).ToList().ForEach(x => WriteCsvLine("c:\...", x) ;
啊,但是我们不想每次都传递路径…所以你把它升级为一个类,把路径作为构造函数参数。
啊,但是我们需要转义逗号…所以升级为引号
啊,但是我们需要提供一些变量分隔符…所以你把它升级为另一个构造函数arg
啊,但是我们可以优化一次写多行…所以你把它升级为List<string[]>
或者其他的
啊,但是我们需要写一个标题行…所以你只是让你传递的第一个字符串数组成为头(你可以LINQ Concat你的数据到一个new[]{"Name","Dept","Salary"}
或使它成为一个构造函数参数..)
所以我们需要一些东西我们可能会这样使用:
var x = new CsvWriter("c:\...", ',', new[]{"EmployeeName","DepartmentName","Salary"});
x.WriteEnumerable(someContext.Employees.Select(e => new [] {
e.Name,
e.Dept,
e.Salary.ToString()
}));
但这不是很酷-当然我们可以做得更酷…所以你决定你将传递一个KeyValuePair<string, string>[]
(或record
或ValueTuple
),其中关键是头,值是数据。你的调用代码会变大,因为你每次都用数据指定头名称。
啊,但是对于所有这些字符串来说,这仍然不是很酷。因此,您决定传递一个匿名类型,其中属性名是头,属性值是数据。
你的代码有一些更少的"
字符,但现在接收端已经成为一个痛苦的噩梦,将属性名称解包成字符串,以便它们可以写成标题行。(我甚至不知道你是否还能轻易地控制列的顺序)
最后,问题很简单:"找到一种方法传递列的标题应该是什么",或者换句话说"传递一个字符串给一个方法">
. .然后我们从:
void Print(string what){
Console.WriteLine(what);
}
...
Print("Hello World");
转换成:
using System.Reflection;
static void Print<T>(T what)
{
PropertyInfo[] propertyInfos = what.GetType().GetProperties();
Console.WriteLine(propertyInfos[0].Name.Replace("_", " "));
}
...
Print(new { Hello_World = 0 });
它可以工作,但是这是一种相当相当疯狂的将字符串传递给方法的方式当你想到…
. .现在老板希望标题有百分比符号,所以我要去弄清楚如何把这些属性名称,并添加另一个bool标志,所以我们可以跳过写标题有时..😀