In my project I need to upload large JSON data from a desktop app. So, I need it to be compressed.
I searched everywhere but I did not find a complex solution to my problem. So, I put several snippets together. See the answer below. I hope you find it useful.
I extended WebClient
and shadow its UploadString
method (all overloads):
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;
}
}
}
You can use ExtendedWebClient
like:
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);
}
}
On the server GZIP uploads are not normally supported, so I needed to add support for this.
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;
}
}
}
This handler must be registered in GlobalConfig file (under 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());
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With