Reputation: 689
I want to upload an Image for my webapi project and i am using WebImage class in Asp.net MVC 4 for Saving, cropping, rotating image by using this class.
I include WebHelper in ApiController with same functionality like mvc project My problem is in webapi project is when i upload image in Webapi controller i receive an error :
{
Message: "An error has occurred."
ExceptionMessage: "No MediaTypeFormatter is available to read an object of type 'WebImage' from content with media type 'multipart/form-data'."
ExceptionType: "System.InvalidOperationException"
StackTrace: " at System.Net.Http.HttpContentExtensions.ReadAsAsync[T](HttpContent content, Type type, IEnumerable`1 formatters, IFormatterLogger formatterLogger) at System.Net.Http.HttpContentExtensions.ReadAsAsync(HttpContent content, Type type, IEnumerable`1 formatters, IFormatterLogger formatterLogger) at System.Web.Http.ModelBinding.FormatterParameterBinding.ReadContentAsync(HttpRequestMessage request, Type type, IEnumerable`1 formatters, IFormatterLogger formatterLogger) at System.Web.Http.ModelBinding.FormatterParameterBinding.ExecuteBindingAsync(ModelMetadataProvider metadataProvider, HttpActionContext actionContext, CancellationToken cancellationToken) at System.Web.Http.Controllers.HttpActionBinding.<>c__DisplayClass1.<ExecuteBindingAsync>b__0(HttpParameterBinding parameterBinder) at System.Linq.Enumerable.WhereSelectArrayIterator`2.MoveNext() at System.Threading.Tasks.TaskHelpers.IterateImpl(IEnumerator`1 enumerator, CancellationToken cancellationToken)"
}
and my upload method sample:
[HttpPost]
public HttpResponseMessage filer(WebImage data)
{
HttpResponseMessage response = null;
if (data == null)
{
response = new HttpResponseMessage()
{
Content = new StringContent("Not a image file"),
StatusCode = HttpStatusCode.BadRequest
};
}
else {
response = new HttpResponseMessage()
{
Content = new StringContent(data.FileName.ToString()),
StatusCode = HttpStatusCode.OK
};
}
return response;
}
Please Explain me how to add MediaTypeFormatter to support WebImage class.
Upvotes: 1
Views: 762
Reputation: 1261
There are two approaches that involve not using a MediaFormatter, these would involve creating a custom ModelBinder or implementing an model class that accepts a base64 encoded string or a byte array to accept the data, then converting the data from that model class into a WebImage. However to answer the question, the process is very straightforward. Here is one implementation.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http.Formatting;
using System.Web;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Helpers;
using System.Net.Http.Headers;
using System.IO;
using System.Net.Http;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Text;
using System.Diagnostics;
namespace StackOverFlowWI.Infrastructure
{
public class WebImageMediaFormatter : MediaTypeFormatter
{
public WebImageMediaFormatter()
{
SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json"));
}
public override bool CanReadType(Type type)
{
return type == typeof(WebImage);
}
public override bool CanWriteType(Type type)
{
return false;
}
public async override Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger, CancellationToken cancellationToken)
{
byte[] buffer = new byte[content.Headers.ContentLength.Value];
while (await readStream.ReadAsync(buffer, (int)readStream.Position, buffer.Length - (int) readStream.Position) > 0) { }
string stringData = Encoding.Default.GetString(buffer);
JObject myJson = JObject.Parse(stringData);
JToken myJToken = myJson.GetValue("imageBytes");
byte[] myBytes = myJToken.Values().Select(x => (byte)x).ToArray();
return new WebImage(myBytes);
}
}
}
You have to register the mediaformatter in the instance of the HttpConfiguration object formatters collection in an IIS hosted application this would be in the WebApiConfig.Register method.
config.Formatters.Insert(0, new WebImageMediaFormatter());
I thought this was an interesting question so went through an implementation and am including some javascript code for the sake of completeness:
var ajaxCall = function (data) {
dataString = data.toString();
dataString = "[" + dataString + "]";
dataString = JSON.parse(dataString);
console.log(dataString.length);
//console.log(dataString);
var imageData = {};
imageData.imageBytes = dataString;
console.log(imageData);
//console.log(imageData);
var ajaxOptions = {};
ajaxOptions.url = "/api/image/PostWebImage";
ajaxOptions.type = "Post";
ajaxOptions.contentType = "application/json";
ajaxOptions.data = JSON.stringify(imageData);
ajaxOptions.success = function () {
console.log('no error detected');
};
ajaxOptions.error = function (jqXHR) {
console.log(jqXHR);
};
$.ajax(ajaxOptions);
};
var postImage = function () {
var file = $('input[type=file]')[0].files[0];
var myfilereader = new FileReader();
myfilereader.onloadend = function () {
var uInt8Array = new Uint8Array(myfilereader.result);
ajaxCall(uInt8Array);
}
if (file) {
myfilereader.readAsArrayBuffer(file);
} else {
console.log("failed to read file");
}
};
Also keep in mind the hard coded limit in that web api accepts a limited amount of data unless you modify the web.config file to modify the httpRuntime environment to accept large requests. (This is assuming that you do not buffer the upload into chunks which would be a better approach).
<httpRuntime targetFramework="4.5" maxRequestLength="1024000" />
Finally an alternate solution that would not require a mediaformatter as mentioned above would be to create a model class with a public property that accepts the data as it is sent.
namespace StackOverFlowWI.Models
{
public class myModel
{
public byte [] imageBytes { get; set; }
}
}
You could then create the object in your action method.
public IHttpActionResult Post( myModel imageData )
{
WebImage myWI = new WebImage(imageData.imageBytes);
string path = System.Web.Hosting.HostingEnvironment.MapPath("~/Images/somefile.png");
myWI.Save(path);
return Ok();
}
For future reference keep in mind that default model binder implementations in web api do not accept any class as a parameter in an action method that does not have a parameter-less constructor. The only exception to this rule is when dependency injection with dependency injection add ins such as ninject or unity are used.
Upvotes: 1