如何使用C#中的System.Net.WebClient上传GZip压缩数据

7
在我的项目中,我需要从桌面应用程序上传大型JSON数据。因此,我需要对其进行压缩。
我搜索了各个地方,但是没有找到解决我的问题的复杂解决方案。因此,我将几个代码片段组合在一起。请参见下面的答案。我希望这对您有用。
1个回答

9

我扩展了WebClient并覆盖了它的UploadString方法(包括所有重载):

using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Text;

namespace DesktopApp
{
    public class ExtendedWebClient : WebClient
    {
        protected override WebRequest GetWebRequest(Uri uri)
        {
            WebRequest w = base.GetWebRequest(uri);
            w.Timeout = 60 * 60 * 1000;
            return w;
        }

        private byte[] GZipBytes(string data)
        {
            //Transform string into byte[]  
            byte[] ret = null;

            using (System.IO.MemoryStream outputStream = new System.IO.MemoryStream())
            {
                using (GZipStream gzip = new GZipStream(outputStream, System.IO.Compression.CompressionMode.Compress))
                {
                    //write to gzipper
                    StreamWriter writer = new StreamWriter(gzip);
                    writer.Write(data);
                    writer.Flush();

                    //write to output stream
                    gzip.Flush();
                    gzip.Close();

                    ret = outputStream.ToArray();
                }
            }

            return ret;
        }

        /// <summary>
        /// Overriden method using GZip compressed data upload.
        /// </summary>
        /// <param name="address">Remote server address.</param>
        /// <param name="data">String data.</param>
        /// <returns>Server response string.</returns>
        public new string UploadString(string address, string data)
        {
            string ret = null;
            byte[] bytes = GZipBytes(data);

            this.Headers.Add("content-encoding", "gzip");
            bytes = base.UploadData(address, bytes);
            ret = System.Text.Encoding.UTF8.GetString(bytes);

            return ret;
        }

        /// <summary>
        /// Overriden method using GZip compressed data upload.
        /// </summary>
        /// <param name="address">Remote server URI.</param>
        /// <param name="data">String data.</param>
        /// <returns>Server response string.</returns>
        public new string UploadString(Uri address, string data)
        {
            string ret = null;
            byte[] bytes = GZipBytes(data);

            this.Headers.Add("content-encoding", "gzip");
            bytes = base.UploadData(address, bytes);
            ret = System.Text.Encoding.UTF8.GetString(bytes);

            return ret;
        }

        /// <summary>
        /// Overriden method using GZip compressed data upload.
        /// </summary>
        /// <param name="address">Remote server address.</param>
        /// <param name="method">HTTP method (e.g. POST, PUT, DELETE, GET).</param>
        /// <param name="data">String data.</param>
        /// <returns>Server response string.</returns>
        public new string UploadString(string address, string method, string data)
        {
            string ret = null;
            byte[] bytes = GZipBytes(data);

            this.Headers.Add("content-encoding", "gzip");
            bytes = base.UploadData(address, method,bytes);
            ret = System.Text.Encoding.UTF8.GetString(bytes);

            return ret;
        }


        /// <summary>
        /// Overriden method using GZip compressed data upload.
        /// </summary>
        /// <param name="address">Remote server URI.</param>
        /// <param name="method">HTTP method (e.g. POST, PUT, DELETE, GET).</param>
        /// <param name="data">String data.</param>
        /// <returns>Server response string.</returns>
        public new string UploadString(Uri address, string method, string data)
        {
            string ret = null;
            byte[] bytes = GZipBytes(data);

            this.Headers.Add("content-encoding", "gzip");
            bytes = base.UploadData(address, method, bytes);
            ret = System.Text.Encoding.UTF8.GetString(bytes);

            return ret;
        }
    }
}

您可以像这样使用ExtendedWebClient:
        using (ExtendedWebClient client = new ExtendedWebClient())
        {
            try
            {
                //requestData object represents any data you need to send to server
                string data = JsonConvert.SerializeObject(
                                requestData,
                                new JsonSerializerSettings { ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver() });

                client.Headers.Add("Content-Type", "application/json; charset=utf-8");
                client.Encoding = System.Text.Encoding.UTF8;
                string url = "http://yourdomain.com/api";
                string response = client.UploadString(url, data);

                //Deal with response as you need
            }
            catch (Exception ex)
            {
                Console.Error.Write(ex.Message);
            }
        }

在服务器上通常不支持GZIP上传,所以我需要添加对此的支持。

using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Web;

namespace WebServer
{
    public class CompressedRequestHandler : DelegatingHandler
    {
        protected override System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
        {
            if (IsRequetCompressed(request))
            {
                request.Content = DecompressRequestContent(request);
            }

            return base.SendAsync(request, cancellationToken);
        }

        private bool IsRequetCompressed(HttpRequestMessage request)
        {
            if (request.Content.Headers.ContentEncoding != null &&
                request.Content.Headers.ContentEncoding.Contains("gzip"))
            {
                return true;
            }

            return false;
        }

        private HttpContent DecompressRequestContent(HttpRequestMessage request)
        {
            // Read in the input stream, then decompress in to the outputstream.
            // Doing this asynronously, but not really required at this point
            // since we end up waiting on it right after this.
            MemoryStream outputStream = new MemoryStream();

            Task task = request.Content.ReadAsStreamAsync().ContinueWith(t =>
                {
                    Stream inputStream = t.Result;

                    using (GZipStream gzipStream = new GZipStream(inputStream, CompressionMode.Decompress))
                    {
                        gzipStream.CopyTo(outputStream);
                    }

                    // Setting output streem position to begin is ESSENTIAL!
                    outputStream.Seek(0, SeekOrigin.Begin);
                });

            // Wait for inputstream and decompression to complete. Would be nice
            // to not block here and work async when ready instead, but I couldn't 
            // figure out how to do it in context of a DelegatingHandler.
            task.Wait();

            // Save the original content
            HttpContent origContent = request.Content;

            // Replace request content with the newly decompressed stream
            HttpContent newContent = new StreamContent(outputStream);

            // Copy all headers from original content in to new one
            // Change content-encoding and content-length
            foreach (var header in origContent.Headers)
            {
                // Change content-encoding header to default value
                if (header.Key.ToLowerInvariant() == "content-encoding")
                {
                    newContent.Headers.Add(header.Key, "identity");
                    continue;
                }

                // Change content-length header value to decompressed length
                if (header.Key.ToLowerInvariant() == "content-length")
                {
                    newContent.Headers.Add(header.Key, outputStream.Length.ToString());
                    continue;
                }

                // Copy other headers
                newContent.Headers.Add(header.Key, header.Value);
            }

            ////For testing purpose only!
            //Task task2 = newContent.ReadAsStringAsync().ContinueWith(x =>
            //{
            //    string strConent = x.Result;
            //});

            //task2.Wait();


            return newContent;
        }
    }
}

这个处理程序必须在GlobalConfig文件中注册(位于App_Start下):

using System.Web.Http;
using Newtonsoft.Json.Serialization;
using System.Web.Http.Filters;

namespace WebServer
{
    public static class GlobalConfig
    {
        public static void CustomizeConfig(HttpConfiguration config)
        {
            //REGISTER CompressedRequestHandler
            config.MessageHandlers.Add(new CompressedRequestHandler());

            // Remove Xml formatters. This means when we visit an endpoint from a browser,
            // Instead of returning Xml, it will return Json.
            // More information from Dave Ward: http://jpapa.me/P4vdx6
            config.Formatters.Remove(config.Formatters.XmlFormatter);

            // Configure json camelCasing per the following post: http://jpapa.me/NqC2HH
            // Here we configure it to write JSON property names with camel casing
            // without changing our server-side data model:
            //var json = config.Formatters.JsonFormatter;
            //json.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
            //json.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;

            //remove standard JSON formatter
            config.Formatters.Remove(config.Formatters.JsonFormatter);

            //add JSONP formatter to support both JSON and JSONP
            var jsonp = new JsonpMediaTypeFormatter();
            jsonp.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
            jsonp.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
            config.Formatters.Add(jsonp);

            // Add model validation, globally
            config.Filters.Add(new ValidationActionFilter());

            //            config.Filters.Add(new AuthorizeAttribute());

            //
            //config.Filters.Add(new CustomSecurityAttribute());
            //config.Filters.Add(new XchangeSecurityAttribute());
            config.Filters.Add(new ExceptionHandlingAttribute());
        }
    }
}

1
干得好。你为我节省了几个小时 :-) - Fred Mauroy
谢谢。真是一堆垃圾,.net居然不让我们对此有更多的控制权。对我来说,需要这么多工作量来完成这个简直荒谬。 - Bon
不错的解决方案!对我也完美地起作用了。谢谢! - lcryder
有没有办法让它 AllowAutoRedirect? - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接