how do I handle and deflate a GZipped form post in asp.net MVC? - asp.net

I have an iPad app that submits orders to an ASP.NET MVC web site via form post. It is posting JSON which can be fairly large for a mobile device to send (200~300K) under certain conditions. I can GZip the form post but then my asp.net mvc chokes on the gzipped content.
How can I handle a GZipped form post in asp.net mvc?
UPDATE:
Darin's answer puts me on the right track but I still have no idea how to do what he suggests, so here is where I am at:
Have this code to decompress a string:
http://dotnet-snippets.com/dns/compress-and-decompress-strings-SID612.aspx
And I get the string like so:
StreamReader reader = new StreamReader(Request.InputStream);
string encodedString = reader.ReadToEnd();
but this gives me the error:
The input is not a valid Base-64 string as it contains a non-base 64 character, more than two padding characters, or a non-white space character among the padding characters.
EDIT - COMPLETED CODE
I am using asp.net MVC and this is working great for me. I also had to deal with some other encoding that happens when my gzipping occurs:
[Authorize]
[HttpPost]
[ValidateInput(false)]
public ActionResult SubmitOrder()
{
GZipStream zipStream = new GZipStream(Request.InputStream, CompressionMode.Decompress);
byte[] streamBytes = ReadAllBytes(zipStream);
var result = Convert.ToBase64String(streamBytes);
string sample = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(result));
string escaped = Uri.UnescapeDataString(sample);
// escaped now has my form values as a string like so: var1=value1&var2=value2&ect...
//more boring code
}
public static byte[] ReadAllBytes(Stream input)
{
byte[] buffer = new byte[16 * 1024];
using (MemoryStream ms = new MemoryStream())
{
int read;
while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
{
ms.Write(buffer, 0, read);
}
return ms.ToArray();
}
}

You can do this without a custom model binder. Write an Action that accepts HttpPostedFileBase, i.e, treat this as a file upload.
[HttpPost]
public ActionResult UploadCompressedJSON(HttpPostedFileBase file)
{
if (file != null && file.ContentLength > 0)
{
GZipStream zipStream = new GZipStream(file.InputStream, CompressionMode.Decompress);
byte[] streamBytes = ReadAllBytes(zipStream);
var result = Convert.ToBase64String(streamBytes);
}
return RedirectToAction("Index");
}
You are going to need to change your client side code to send a file upload request but that should be fairly easy. For example you can look at this code.

How can I handle a GZipped form post in asp.net mvc?
You could write a custom model binder that will directly read the Request.InputStream, unzip it and then parse the contents and instantiate some view model you want to bind to.

Use the System.IO.Compression.GZipStream class.
Codeproject example

Related

How do I write an image's MemoryStream to the page body?

I'm converting ASP.NET WebForms code to ASP.NET Core Razor pages which is new to me. I'm trying to retrieve an image MemoryStream from a business class (based on SixLabors awesome ImageSharp) and have the page render the JPEG -- no HTML, just the image. I intend to use this page elsewhere as an <img> src, like <img src="Render?imageID=42&mode=invert" />
In Render.cshtml.cs:
public class RenderModel : PageModel
{
public void OnGet()
{
//snip
Stream stream = new MemoryStream();
using (Image image1 = Image.Load(imagePath))
{
SixLabors.ImageSharp.Formats.Jpeg.JpegEncoder encoder = new SixLabors.ImageSharp.Formats.Jpeg.JpegEncoder();
encoder.Quality = 75;
image1.Save(stream, encoder);
//image.Save("/temp/xxx.jpg", encoder); //test to see image. it works
}
Response.Clear();
//Response.Headers.ContentLength = stream.Length;
Response.ContentType = "image/jpeg";
Response.Body = stream;
}
}
...but this is not working, I get:
System.InvalidOperationException: Response Content-Length mismatch: too few bytes written (0 of 135408).
135408 is the stream.Length.
I'm probably not doing this correctly in the ASP.NET Core/Razor way. Can anyone set me straight as to how to do this? Thanks!
EDIT: commenting out the Headers.ContentLength fixes the error. But now I get a broken-image icon in the browser. Closer...
You need to write to the Response.Body isntead of replacing it.
stream.Seek(0, SeekOrigin.Begin);
await stream.CopyToAsync(Response.Body);
await Response.Body.FlushAsync();
I think Razor pages are intented to return html content.
However it seems to be possible to return different types of result in OnGet e.g. you could return a new FileContentReset (FileStreamResult seems to have issues with contentlength)
// read as bytes
public FileContentResult OnGet()
{
var image = System.IO.File.ReadAllBytes(#"c:\temp\myimage.jpeg");
return new FileContentResult(image.ToArray(), "image/jpeg");
}
// example if image comes from stream
public FileContentResult OnGet()
{
using var imageStr = System.IO.File.Open(#"c:\temp\myimage.jpeg", FileMode.Open);
using var memStr = new MemoryStream();
imageStr.CopyTo(memStr);
return new FileContentResult(memStr.ToArray(), "image/jpeg");
}
Even better maybe it to not use a Razor page and to add a MVC controller to return the result.

How to process Excel file in memory?

I am trying to create an API that will accept the representation of an Excel file from the client. I wish to return a List<List<string>> as JSON array after processing the first sheet. However, I cannot write the file to disk, and all processing must happen in-memory. What are the ways in which this can be achieved?
I've tried referring to various solutions on the internet but all of them involve writing the file to disk and then using that file for further processing. I'm open to solutions that involve
Accepting base-64 representation of the file from the POST request body
Accepting file as part of multipart/form-data request
Any other standard request formats that accept files
The only condition is that the API should return a JSON array representation of the spreadsheet.
Here I am sending a file as part of multipart/form-data request to the API which written in .NET core.
which support .xlsx , .xls and .csv format
use ExcelDataReader and ExcelDataReader.DataSet NuGet packages for reading excel and convert in the dataset.
Here one problem i faced and solution in .NET core.
By default, ExcelDataReader throws a NotSupportedException "No data is available for encoding 1252." on .NET Core.
To fix, add a dependency to the package System.Text.Encoding.CodePages and then add code to register the code page in starting of API
System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);
This is required to parse strings in binary BIFF2-5 Excel documents encoded with DOS-era code pages. These encodings are registered by default in the full .NET Framework, but not on .NET Core.
public ActionResult ExcelOrCsvToArray()
{
if (Request.Form.Files.Count > 0)
{
IFormFile file = Request.Form.Files[0];
string fileName = file.FileName;
string fileContentType = file.ContentType;
System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);
Stream stream = file.OpenReadStream();
try
{
if (fileName.EndsWith(".csv"))
{
using (var reader = ExcelReaderFactory.CreateCsvReader(stream))
{
var result = SetAsDataSet(reader);
DataTable table = result.Tables[0];
return new OkObjectResult(table);
}
}
else
{
using (var reader = ExcelReaderFactory.CreateReader(stream))
{
var result = SetAsDataSet(reader);
DataTable table = result.Tables[0];
return new OkObjectResult(table);
}
}
}
catch (Exception e)
{
return new BadRequestObjectResult(e);
}
}
else
{
return new BadRequestResult();
}
}
private DataSet SetAsDataSet(IExcelDataReader reader)
{
var result = reader.AsDataSet(new ExcelDataSetConfiguration()
{
ConfigureDataTable = (_) => new ExcelDataTableConfiguration()
{
UseHeaderRow = true,
}
});
return result;
}

Receive byte array over HTTP in an ASP.NET 2.0 app

Trying to transfer a byte array to an ASP.NET 2.0 app in a light-weight manner without using SOAP. I decided to use a generic HTTP handler (.ashx) that interprets the HTTP request body as a Base64 string, decoding it to a byte array and saving to disk.
<%# WebHandler Language="C#" Class="PdfPrintService" %>
using System;
using System.Web;
public class PdfPrintService : IHttpHandler {
public void ProcessRequest (HttpContext context) {
string body;
using (System.IO.StreamReader reader =
new System.IO.StreamReader(context.Request.InputStream))
{
body = reader.ReadToEnd();
}
byte[] bytes = System.Convert.FromBase64String(body);
String filePath = System.IO.Path.GetTempFileName() + ".pdf";
System.IO.File.WriteAllBytes(filePath, bytes);
// Print file.
XyzCompany.Printing.PrintUtility.PrintFile(filePath);
}
public bool IsReusable {
get {
return false;
}
}
}
The client application (an iOS app in my case) will simply have to encode the bytes as Base64 and post them to the URL of this generic handler (ashx).
I imagine there is a better, more orthodox way to do this. Any ideas are appreciated!
The thing that comes to mind is POST & GET Requests handled through classes like the HttpWebResponse class. http://msdn.microsoft.com/en-us/library/system.net.httpwebresponse%28v=vs.71%29.aspxx
You can have your iOS app try to POST to the ASP.NET app, which is then set up to receive the POST and parse it for your byte array, which you'd include. More, or less, this is how some data was sent across the internet before SOAP.. all SOAP is is a schema for these types of requests.

Returning binary file from controller in ASP.NET Web API

I'm working on a web service using ASP.NET MVC's new WebAPI that will serve up binary files, mostly .cab and .exe files.
The following controller method seems to work, meaning that it returns a file, but it's setting the content type to application/json:
public HttpResponseMessage<Stream> Post(string version, string environment, string filetype)
{
var path = #"C:\Temp\test.exe";
var stream = new FileStream(path, FileMode.Open);
return new HttpResponseMessage<Stream>(stream, new MediaTypeHeaderValue("application/octet-stream"));
}
Is there a better way to do this?
Try using a simple HttpResponseMessage with its Content property set to a StreamContent:
// using System.IO;
// using System.Net.Http;
// using System.Net.Http.Headers;
public HttpResponseMessage Post(string version, string environment,
string filetype)
{
var path = #"C:\Temp\test.exe";
HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
result.Content = new StreamContent(stream);
result.Content.Headers.ContentType =
new MediaTypeHeaderValue("application/octet-stream");
return result;
}
A few things to note about the stream used:
You must not call stream.Dispose(), since Web API still needs to be able to access it when it processes the controller method's result to send data back to the client. Therefore, do not use a using (var stream = …) block. Web API will dispose the stream for you.
Make sure that the stream has its current position set to 0 (i.e. the beginning of the stream's data). In the above example, this is a given since you've only just opened the file. However, in other scenarios (such as when you first write some binary data to a MemoryStream), make sure to stream.Seek(0, SeekOrigin.Begin); or set stream.Position = 0;
With file streams, explicitly specifying FileAccess.Read permission can help prevent access rights issues on web servers; IIS application pool accounts are often given only read / list / execute access rights to the wwwroot.
For Web API 2, you can implement IHttpActionResult. Here's mine:
using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;
class FileResult : IHttpActionResult
{
private readonly string _filePath;
private readonly string _contentType;
public FileResult(string filePath, string contentType = null)
{
if (filePath == null) throw new ArgumentNullException("filePath");
_filePath = filePath;
_contentType = contentType;
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
var response = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StreamContent(File.OpenRead(_filePath))
};
var contentType = _contentType ?? MimeMapping.GetMimeMapping(Path.GetExtension(_filePath));
response.Content.Headers.ContentType = new MediaTypeHeaderValue(contentType);
return Task.FromResult(response);
}
}
Then something like this in your controller:
[Route("Images/{*imagePath}")]
public IHttpActionResult GetImage(string imagePath)
{
var serverPath = Path.Combine(_rootPath, imagePath);
var fileInfo = new FileInfo(serverPath);
return !fileInfo.Exists
? (IHttpActionResult) NotFound()
: new FileResult(fileInfo.FullName);
}
And here's one way you can tell IIS to ignore requests with an extension so that the request will make it to the controller:
<!-- web.config -->
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
For those using .NET Core:
You can make use of the IActionResult interface in an API controller method, like so.
[HttpGet("GetReportData/{year}")]
public async Task<IActionResult> GetReportData(int year)
{
// Render Excel document in memory and return as Byte[]
Byte[] file = await this._reportDao.RenderReportAsExcel(year);
return File(file, "application/vnd.openxmlformats", "fileName.xlsx");
}
This example is simplified, but should get the point across. In .NET Core this process is so much simpler than in previous versions of .NET - i.e. no setting response type, content, headers, etc.
Also, of course the MIME type for the file and the extension will depend on individual needs.
Reference: SO Post Answer by #NKosi
While the suggested solution works fine, there is another way to return a byte array from the controller, with response stream properly formatted :
In the request, set header "Accept: application/octet-stream".
Server-side, add a media type formatter to support this mime type.
Unfortunately, WebApi does not include any formatter for "application/octet-stream". There is an implementation here on GitHub: BinaryMediaTypeFormatter (there are minor adaptations to make it work for webapi 2, method signatures changed).
You can add this formatter into your global config :
HttpConfiguration config;
// ...
config.Formatters.Add(new BinaryMediaTypeFormatter(false));
WebApi should now use BinaryMediaTypeFormatter if the request specifies the correct Accept header.
I prefer this solution because an action controller returning byte[] is more comfortable to test. Though, the other solution allows you more control if you want to return another content-type than "application/octet-stream" (for example "image/gif").
For anyone having the problem of the API being called more than once while downloading a fairly large file using the method in the accepted answer, please set response buffering to true
System.Web.HttpContext.Current.Response.Buffer = true;
This makes sure that the entire binary content is buffered on the server side before it is sent to the client. Otherwise you will see multiple request being sent to the controller and if you do not handle it properly, the file will become corrupt.
The overload that you're using sets the enumeration of serialization formatters. You need to specify the content type explicitly like:
httpResponseMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
You could try
httpResponseMessage.Content.Headers.Add("Content-Type", "application/octet-stream");
You can try the following code snippet
httpResponseMessage.Content.Headers.Add("Content-Type", "application/octet-stream");
Hope it will work for you.

How to get image directly?

Follwing webpage includes light adult contents. Please do not click link if you don't want it.
go to : http://www.hqasians.com/tgp/bigasiantits/MaiNishida/at.htm
you can see several thumb images.
click one of them. you can see large image.
Check current page url. It will be like ~~~~~~~~~~~~~~~~/tgp/bigasiantits/MaiNishida/images/01.jpg
you can know how to access another image by changing last .jpg name of whole url
change 01.jpg to 02.jpg and enter.
But, you will encounter website's main page not 02.jpg.
Is this security way to block direct access by that site ?
Is there any work-around way to get image directly?
Following is my codes.
InputStream bmis;
bmis = new URL(params[0]).openStream();
final Drawable image =
new BitmapDrawable(BitmapFactory.decodeStream(new FlushedInputStream(bmis)));
if(image != null)
{
activity.setContentView(imageSwitcher);
imageSwitcher.setImageDrawable(image);
}
I'm only guessing here, but I think what this site does is to check the "Referer" field from the HTTP request header to check whether the request came from within the site, or from outside.
It isn't a secure way of blocking direct access. In fact, there's an workaround, but I don't think the site rules allow me to write it here, so, you'll have to figure out yourself.
It's because of the Referrer. You have to be referred by that main page to open the picture.
Sorry I'm not sure how to use Android, but C# code should look like this:
static void Main(string[] args)
{
for (int i = 1; i <= 15; i++)
{
HttpWebRequest request =
WebRequest.Create(
string.Format("http://www.hqasians.com/tgp/bigasiantits/MaiNishida/images/{0:00}.jpg", i)
) as HttpWebRequest;
request.Credentials = CredentialCache.DefaultCredentials;
request.Referer = "http://www.hqasians.com/tgp/bigasiantits/MaiNishida/at.htm";
request.Method = "POST";
WebResponse response = request.GetResponse();
string inputFile = string.Format("{0}.jpg", i);
Console.WriteLine(response.ResponseUri.AbsoluteUri);
using (Stream file = File.OpenWrite(inputFile))
{
CopyStream(response.GetResponseStream(), file);
}
}
}
/// <summary>
/// Copies the contents of input to output. Doesn't close either stream.
/// </summary>
public static void CopyStream(Stream input, Stream output)
{
byte[] buffer = new byte[8 * 1024];
int len;
while ((len = input.Read(buffer, 0, buffer.Length)) > 0)
{
output.Write(buffer, 0, len);
}
}
The CopyStream method is got from here: How do I save a stream to a file in C#?

Resources