处理MediaTypeFormatter中的匿名IEnumerable类型

时间:2017-08-16 17:28:48

标签: c# asp.net-web-api mediatypeformatter

我遵循了这个great example,并且能够实现我的自定义媒体类型格式化程序。它适用于已知类型,例如产品,项目,地址等。但是如果我有匿名JSON对象(下面),我想以CSV格式下载,那么它会在Type itemType = type.GetGenericArguments()[0];抱怨时失败

  

索引超出了数组的范围。

感谢任何帮助。

var _list = _dt.AsEnumerable().Select(r => new
{
  LkpColCode = r.Field<string>("lkp_column_code"),
  LkpColName = r.Field<string>("Description")
});

return _list;

/* [{"lkpColCode":"BUS","lkpColName":"Bus"},{"lkpColCode":"COM","lkpColName":"Community Bus"},
    {lkpColCode":"STC","lkpColName":"Streetcar"},{"lkpColCode":"SUB","lkpColName":"Subway"},
    {"lkpColCode":"TRC","lkpColName":"Trolley Coach"}]*/

编辑:下面的完整工作代码可以处理除匿名

之外的任何类型
public class CSVFormatter : MediaTypeFormatter
    {
        private string FileName { get; set; }

        public CSVFormatter()
        {
            SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/csv"));

            SupportedEncodings.Add(new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
            SupportedEncodings.Add(Encoding.GetEncoding("iso-8859-1"));
        }

        public CSVFormatter(MediaTypeMapping mediaTypeMapping)
            : this()
        {
            MediaTypeMappings.Add(mediaTypeMapping);

            SupportedEncodings.Add(new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
            SupportedEncodings.Add(Encoding.GetEncoding("iso-8859-1"));
        }

        public CSVFormatter(IEnumerable<MediaTypeMapping> mediaTypeMappings)
            : this()
        {
            foreach (var mediaTypeMapping in mediaTypeMappings)
            {
                MediaTypeMappings.Add(mediaTypeMapping);
            }

            SupportedEncodings.Add(new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
            SupportedEncodings.Add(Encoding.GetEncoding("iso-8859-1"));
        }

        public override void SetDefaultContentHeaders(Type type, HttpContentHeaders headers, MediaTypeHeaderValue mediaType)
        {
            base.SetDefaultContentHeaders(type, headers, mediaType);
            headers.Add("Content-Disposition", string.Format("attachment; filename={0}", FileName));
        }

        public override MediaTypeFormatter GetPerRequestFormatterInstance(Type type, HttpRequestMessage request, MediaTypeHeaderValue mediaType)
        {
             //Usuage: In Controller Action:
            //if (!Request.Properties.ContainsKey("filename"))
            //Request.Properties.Add("filename", String.Format("SomeFileName_{0}.csv", DateTime.Now.ToString("yyyyMMdd-hhmmss")));

            if (request.Properties.ContainsKey("filename"))
            {
                FileName = request.Properties["filename"] as string;
            }
            else if (!String.IsNullOrWhiteSpace(FileName = request.GetQueryString("filename")))
            {
                FileName = FileName.CustomCompare(".csv") ? FileName : FileName + ".csv";
            }
            else
            {
                FileName = String.Format("Data-{0}.csv", DateTime.Now.ToString("yyyyMMdd-HHmmss"));
            }

            return this;
        }

        public 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(IEnumerable))
                { return true; }
            }
            return false;
        }

        public override bool CanReadType(Type type)
        {
            return false;
        }

        public override Task WriteToStreamAsync(Type type, object value, Stream stream, HttpContent content, TransportContext transportContext)
        {
            writeStream(type, value, stream, content);
            var tcs = new TaskCompletionSource<int>();
            tcs.SetResult(0);
            return tcs.Task;
        }

        private void writeStream(Type type, object value, Stream stream, HttpContent content)
        {
            //NOTE: We have check the type inside CanWriteType method. If request comes this far, the type is IEnumerable. We are safe. However it fails for Anonymous and errors out.

            Encoding effectiveEncoding = SelectCharacterEncoding(content.Headers);
            Type itemType = type.GetGenericArguments()[0];

            using (var writer = new StreamWriter(stream, effectiveEncoding))
            {
                //Write out columns
                writer.WriteLine(string.Join<string>(",", itemType.GetProperties().Select(x => x.Name)));

                foreach (var obj in (IEnumerable<object>)value)
                {
                    var vals = obj.GetType().GetProperties().Select(pi => new { Value = pi.GetValue(obj, null) });
                    string _valueLine = string.Empty;

                    foreach (var val in vals)
                    {
                        var columnValue = Escape(val.Value);
                        _valueLine = string.Concat(_valueLine, columnValue, ",");
                    }

                    _valueLine = _valueLine.Substring(0, _valueLine.Length - 1);
                    writer.WriteLine(_valueLine);
                }
            }
        }

        #region Escape Characters
        static char[] _specialChars = new char[] { ',', '\n', '\r', '"' };

        private string Escape(object o)
        {
            if (o == null)
                return String.Empty;

            string field = o.ToString();

            // Delimit the entire field with quotes and replace embedded quotes with "".
            if (field.IndexOfAny(_specialChars) != -1)
                return String.Format("\"{0}\"", field.Replace("\"", "\"\""));
            else return field;

            //Quote forcefully
            //return String.Format("\"{0}\"", field.Replace("\"", "\"\""));
        }
        #endregion
    }

1 个答案:

答案 0 :(得分:0)

首先它只是一个匿名类型,JSON与此无关。

其次,您正在使用的示例使用类型来确定它是否可以格式化数据。如果您使用的是匿名类型,则它无法正常工作。

最简单的路线是创建一个新类

public class Thing
{
    public string LkpColCode {get;set;}
    public string LkpColName {get;set;}
}

然后从示例中将Product更改为Thing(或您调用的任何内容),然后更改此代码

private void WriteItem(Thing thing, StreamWriter writer)
{
    writer.WriteLine("{0},{1}", Escape(thing.LkpColCode), Escape(thing.LkpColName));
}

给它一个去看看你需要的地方。如果您需要更多帮助,可能需要使用更多代码更新您的问题。