best practice for uploading images to azure blob - c#

I am building a mobile app using xamarin while the server is hosted in azure. I am uploading images in the following way:
Client:
public static async Task<string> UploadImage (string url, byte[] imageData)
{
var content = new MultipartFormDataContent();
var fileContent = new ByteArrayContent(imageData);
fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
FileName = Guid.NewGuid() + ".Png"
};
content.Add(fileContent);
using (var client = new HttpClient())
{
try
{
HttpResponseMessage msg = await client.PutAsync (url, content);
if(msg.StatusCode == System.Net.HttpStatusCode.OK)
{
return msg.Headers.GetValues ("ImageUrl").First();
}
return string.Empty;
}
catch (Exception ex)
{
return string.Empty;
}
}
}
and here is the server code:
[HttpPut]
public async Task<HttpResponseMessage> PostNewDishImage(string imageID)
{
try
{
_dishImagescontainer = BlobStorageHandler.GetContainer("dishuserimages");
if (!Request.Content.IsMimeMultipartContent("form-data"))
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.UnsupportedMediaType));
}
var provider = new BlobStorageProvider(_dishImagescontainer);
await Request.Content.ReadAsMultipartAsync(provider);
IList<string> urls = provider.Urls;
if (urls.Count > 0)
{
var response = new HttpResponseMessage();
response.StatusCode = HttpStatusCode.OK;
response.Headers.Add("ImageUrl", urls[0]);
return response;
}
return new HttpResponseMessage(HttpStatusCode.InternalServerError);
}
catch (System.Exception e)
{
return new HttpResponseMessage(HttpStatusCode.InternalServerError) { ReasonPhrase = e.ToString() };
}
}
It works fine but I don't like the way I am returning the new imageurl back to the client (through the http headers) I have tried some other ways but this is the best one so far :)
Does anyone have any better ideas?
Thanks

Returning data to the client in an HTTP header like that does have a bit of a smell. How about returning a DTO serialized to JSON?
An example DTO class:
public class ImageUploadResponse
{
public string Url { get; set; }
}
Then change your server side code to something like this:
var responseDto = new ImageUploadResponse { Url = urls[0] };
var response = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(JsonConvert.SerializeObject(responseDto),
Encoding.UTF8, "application/json")
};
which will send the results back to the client in the content of the HTTP response as JSON. Then the client can parse the JSON into an object (or not) as you see fit. This approach will also be more friendly to making such a call from JavaScript in the future if you desire because the result is standard JSON.

Related

HttpClient PostAsycn Always returns 404 Not Found but controller works with Postman and Javascript application

I have a Post Web Api Method in a controller and it's already working, I've been testing the method sending file from postman and a my own web application and it works but now I'm trying to send file from console application using Httpclient but always get 404.
Controller Method
public async Task<IHttpActionResult> Post()
{
FileServerConfig config = FileServerConfiguration.ObtenerConfiguracion(ConfigurationManager.AppSettings);
var path = string.Empty;
try
{
if (!Request.Content.IsMimeMultipartContent())
{
return StatusCode(HttpStatusCode.UnsupportedMediaType);
}
var filesReadToProvider = await Request.Content.ReadAsMultipartAsync();
foreach (var stream in filesReadToProvider.Contents)
{
IFileServerWrapper _cliente = FileServerConfiguration.CrearCliente(config);
var name = stream.Headers.ContentDisposition.FileName.Replace("\"", "").Replace("\\", "");
var fileUploaded = await _cliente.FileUpload(path, await stream.ReadAsStreamAsync(), name, false);
}
return Ok();
}
catch (HttpException httpex)
{
if (httpex.GetHttpCode() == (int)HttpStatusCode.Conflict)
{
return Conflict();
}
else
{
return InternalServerError(httpex);
}
}
catch (Exception ex)
{
return InternalServerError(ex);
}
}
Console application
static void Main(string[] args)
{
string filePath = #"C:\Software\itextTifftoPDF.rar";
using (var client = new HttpClient())
using (var content = new MultipartFormDataContent())
{
// Make sure to change API address
//client.BaseAddress = new Uri("http://localhost:80/FileServerAPI/");
client.DefaultRequestHeaders.Add("Accept-Language", "en-GB,en-US;q=0.8,en;q=0.6,ru;q=0.4");
client.DefaultRequestHeaders.CacheControl = CacheControlHeaderValue.Parse("no-cache");
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// Add first file content
var fileContent1 = new ByteArrayContent(File.ReadAllBytes(filePath));
fileContent1.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data")
{
FileName = "itextTifftoPDF.rar"
};
content.Add(fileContent1);
// Make a call to Web API
var result = client.PostAsync("http://localhost/FileServerAPI/api/File", content).Result;
Console.WriteLine(result.StatusCode);
Console.ReadLine();
}
}
Finally I found a solution. the code is ugly because it was made just to be sure it works but I'll hope it may help others with the same problem.
string filePath = #"C:\temp\webdavtest.txt";
using (HttpClient httpClient = new HttpClient())
{
using (MultipartFormDataContent content =
new MultipartFormDataContent())
{
using (FileStream stream = File.Open(
filePath, FileMode.Open, FileAccess.Read))
{
using (StreamContent streamConent =
new StreamContent(stream))
{
content.Add(
streamConent, "webdavtest.txt", "webdavtest.txt");
var result = await httpClient.PostAsync("http://localhost:3983/api/File?path=/nivel5/", content);
return result.StatusCode.ToString();
}
}
}
}

How to receive a byte array and json in a Web API Controller

I need to receive a json object together with a byte array in a c# Web API application.
This is how I am sending the data:
public bool SendMedia(string method, Media media)
{
string filePath = Path.GetFullPath(Path.Combine(filesDirectory, media.FileName));
if (!File.Exists(filePath))
{
return false;
}
using (var client = new HttpClient())
using (var content = new MultipartContent() )
{
content.Add(new StringContent(JsonConvert.SerializeObject(media), Encoding.UTF8, "application/json"));
byte[] b = File.ReadAllBytes(filePath);
content.Add(new ByteArrayContent(b, 0, b.Length));
var response = client.PostAsync(new Uri(baseUri, method).ToString(), content).Result;
if (response.IsSuccessStatusCode)
return true;
return false;
}
}
And this is how I am trying to receive it:
// POST: api/Media
[ResponseType(typeof(Media))]
public HttpResponseMessage PostMedia(Media media, byte[] data)
{
int i = data.Length;
HttpResponseMessage response = new HttpResponseMessage();
if (!ModelState.IsValid)
{
response.StatusCode = HttpStatusCode.ExpectationFailed;
return response;
}
if (MediaExists(media.MediaId))
WebApplication1Context.db.Media.Remove(WebApplication1Context.db.Media.Where(p => p.MediaId == media.MediaId).ToArray()[0]);
WebApplication1Context.db.Media.Add(media);
try
{
WebApplication1Context.db.SaveChanges();
}
catch (DbUpdateException)
{
response.StatusCode = HttpStatusCode.InternalServerError;
return response;
throw;
}
response.StatusCode = HttpStatusCode.OK;
return response;
}
I don't know much about developing for web at the moment. Is sending a MultipartContent the right approach?
The framework can only bind one item from the body so what you are trying to attempt would not work.
Instead, read the request content just as you sent it and extract the parts.
[ResponseType(typeof(Media))]
public async Task<IHttpActionResult> PostMedia() {
if (!Request.Content.IsMimeMultipartContent()) {
return StatusCode(HttpStatusCode.UnsupportedMediaType); }
var filesReadToProvider = await Request.Content.ReadAsMultipartAsync();
var media = await filesReadToProvider.Contents[0].ReadAsAsync<Media>();
var data = await filesReadToProvider.Contents[1].ReadAsByteArrayAsync();
int i = data.Length;
if (!ModelState.IsValid) {
return StatusCode(HttpStatusCode.ExpectationFailed);
}
if (MediaExists(media.MediaId))
WebApplication1Context.db.Media.Remove(WebApplication1Context.db.Media.Where(p => p.MediaId == media.MediaId).ToArray()[0]);
WebApplication1Context.db.Media.Add(media);
try {
WebApplication1Context.db.SaveChanges();
} catch (DbUpdateException) {
return StatusCode(HttpStatusCode.InternalServerError);
}
return Ok(media);
}
Note also that in your original code you state the the action has a [ResponseType(typeof(Media))] but an object of that type was never returned. The above answer includes the model in the Ok(media) response.
The is a very simplified example. add any validation as necessary.

Client code to upload file via ASP.NET MVC WebApi

I am trying to write code to upload file(s) by WinForm app to WebApi.
The WebApi code is like:
[HttpPost]
[Route("UploadEnvelope")]
[HostAuthentication(DefaultAuthenticationTypes.ExternalBearer)]
public Task<HttpResponseMessage> PostUploadEnvelope()
{
HttpRequestMessage request = this.Request;
if (!request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
string root = System.Web.HttpContext.Current.Server.MapPath("~/App_Data/uploads");
var provider = new MultipartFormDataStreamProvider(root);
var task = request.Content.ReadAsMultipartAsync(provider).ContinueWith<HttpResponseMessage>(o =>
{
foreach (MultipartFileData fileData in provider.FileData)
{
if (string.IsNullOrEmpty(fileData.Headers.ContentDisposition.FileName))
{
return Request.CreateResponse(HttpStatusCode.NotAcceptable, "This request is not properly formatted");
}
string fileName = fileData.Headers.ContentDisposition.FileName;
if (fileName.StartsWith("\"") && fileName.EndsWith("\""))
{
fileName = fileName.Trim('"');
}
if (fileName.Contains(#"/") || fileName.Contains(#"\"))
{
fileName = Path.GetFileName(fileName);
}
File.Move(fileData.LocalFileName, Path.Combine(root, fileName));
}
return new HttpResponseMessage()
{
Content = new StringContent("Files uploaded.")
};
}
);
return task;
}
But I am not sure how to call it and pass file in a client app.
static string UploadEnvelope(string filePath, string token, string url)
{
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
// How to pass file here ???
var response = client.GetAsync(url + "/api/Envelope/UploadEnvelope").Result;
return response.Content.ReadAsStringAsync().Result;
}
}
Any help or suggestion is welcome. Thanks in advance!
First you are using Get method which is used for reading. You have to use Post instead.
Try the following:
public static string UploadEnvelope(string filePath,string token, string url)
{
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
using (var content = new MultipartFormDataContent("Envelope" + DateTime.Now.ToString(CultureInfo.InvariantCulture)))
{
content.Add(new StreamContent(new MemoryStream(File.ReadAllBytes(filePath))), "filename", "filename.ext");
using (var message = await client.PostAsync(url + "/api/Envelope/UploadEnvelope", content))
{
var input = await message.Content.ReadAsStringAsync();
return "success";
}
}
}
}
Note: For a large file you have to change configuration on IIS web.config.

Upload file to Pushbullet in Windows 10 app c#

I'm currently using Pushbullet API and need to upload a file.
I can successfully get an upload url as specified in the docs using this method:
public static async Task<Uploads> GetUploadUrl(string file_name, string file_type)
{
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Access-Token", AccessToken);
var json = new JObject
{
["file_name"] = file_name,
["file_type"] = file_type
};
var result = await client.PostAsync(new Uri(_uploadUrl, UriKind.RelativeOrAbsolute), new HttpStringContent(json.ToString(), UnicodeEncoding.Utf8, "application/json"));
if (result.IsSuccessStatusCode)
{
var textresult = await result.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<Uploads>(textresult);
}
}
return null;
}
The problem is when I try to upload the file. I'm currently using this method:
public static async Task<bool> UploadFile(StorageFile file, string upload_url)
{
try
{
System.Net.Http.HttpClient client = new System.Net.Http.HttpClient();
var content = new MultipartFormDataContent();
if (file != null)
{
var streamData = await file.OpenReadAsync();
var bytes = new byte[streamData.Size];
using (var dataReader = new DataReader(streamData))
{
await dataReader.LoadAsync((uint)streamData.Size);
dataReader.ReadBytes(bytes);
}
var streamContent = new ByteArrayContent(bytes);
content.Add(streamContent);
}
client.DefaultRequestHeaders.Add("Access-Token", AccessToken);
var response = await client.PostAsync(new Uri(upload_url, UriKind.Absolute), content);
if (response.IsSuccessStatusCode)
return true;
}
catch { return false; }
return false;
}
but I get a Http 400 error. What's the right way to upload a file using multipart/form-data in a UWP app?
HTTP 400 error indicates Bad Request, it means the request could not be understood by the server due to malformed syntax. In the other word, the request sent by the client doesn't follow server's rules.
Let's look at the document, and we can find in the example request it uses following parameter:
-F file=#cat.jpg
So in the request, we need to set the name for the uploaded file and the name should be "file". Besides, in this request, there is no need to use access token. So you can change your code like following:
public static async Task<bool> UploadFile(StorageFile file, string upload_url)
{
try
{
System.Net.Http.HttpClient client = new System.Net.Http.HttpClient();
var content = new MultipartFormDataContent();
if (file != null)
{
var streamData = await file.OpenReadAsync();
var bytes = new byte[streamData.Size];
using (var dataReader = new DataReader(streamData))
{
await dataReader.LoadAsync((uint)streamData.Size);
dataReader.ReadBytes(bytes);
}
var streamContent = new ByteArrayContent(bytes);
content.Add(streamContent, "file");
}
//client.DefaultRequestHeaders.Add("Access-Token", AccessToken);
var response = await client.PostAsync(new Uri(upload_url, UriKind.Absolute), content);
if (response.IsSuccessStatusCode)
return true;
}
catch { return false; }
return false;
}
Then your code should be able to work. You will get a 204 No Content response and UploadFile method will return true.

Web API POST MultipartFormDataContent: Can response return multipartform content?

I am trying to send one or more files (.doc) to an ASP.NET Web API 2 service and return a modified version (.docx). I am able to send the file and get a response but the HttpContentMultipartExtensions that I used within the service on the HTTPContent in the request are not available back in the client to use on the response. Is this something that isn't available out of the box that can be wired up, or is this a misuse of multipartform?
There are two apps: the MVC client and Web API service:
Controller for Client (reads sample files from App_Data, POST to serviceserver/api/mpformdata):
public async Task<ActionResult> PostMpFormData()
{
DirectoryInfo dir = new DirectoryInfo(Server.MapPath(#"~\App_Data"));
var files = dir.GetFiles().ToList();
using (HttpClient client = new HttpClient())
{
HttpResponseMessage result = new HttpResponseMessage();
using (MultipartFormDataContent mpfdc = new MultipartFormDataContent())
{
foreach (var file in files)
{
mpfdc.Add(new StreamContent(file.OpenRead()), "File", file.Name);
}
var requestUri = ConfigurationManager.AppSettings["DocumentConverterUrl"] + "/api/mpformdata";
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("multipart/form-data"));
result = client.PostAsync(requestUri, mpfdc).Result;
}
ViewBag.ResultStatusCode = result.StatusCode;
ViewBag.ContentLength = result.Content.Headers.ContentLength;
// Fiddler show that it returns multipartform content, but how do I use it?
// var resultContent = result.Content;
}
return View();
}
Controller for Web API service:
public class UploadController : ApiController
{
[HttpPost, Route("api/mpformdata")]
public async Task<HttpResponseMessage> PostMpFormData()
{
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
return await UseMultipartFormDataStream();
}
private async Task<HttpResponseMessage> UseMultipartFormDataStream()
{
string root = HttpContext.Current.Server.MapPath("~/App_Data");
var provider = new MultipartFormDataStreamProvider(root);
MultipartFormDataContent mpfdc = new MultipartFormDataContent();
try
{
await Request.Content.ReadAsMultipartAsync(provider);
foreach (MultipartFileData file in provider.FileData)
{
var filename = file.Headers.ContentDisposition.FileName;
Trace.WriteLine(filename);
Trace.WriteLine("Server file path: " + file.LocalFileName);
mpfdc.Add(new ByteArrayContent(File.ReadAllBytes(file.LocalFileName)), "File", filename);
}
var response = Request.CreateResponse();
response.Content = mpfdc;
response.StatusCode = HttpStatusCode.OK;
return response;
}
catch (System.Exception e)
{
return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, e);
}
}
}
Multipart extensions are part of System.Net.Http.Formatting dll. Make sure you have the nuget package Microsoft.AspNet.WebApi.Client installed at your client.

Categories