I'm following this blog post to upload an image using C# Web API.
The article explains how to do it using ARC and it works fine.
But when I'm trying to do the same using POSTMAN it's failing.
Here is my request snapshot.
TL;DR: You are doing everything correct except that you are setting the Content-Type header explicitly in the tool. Get rid of the header and your issue will be resolved.
Detailed Explanation: To begin with, you attach the files using form-data option in the Body tab in the tool:
The moment you select the file(s), Postman auto-detects the Content-Type. Then behind the scenes, Postman inserts the automatically detected Content-Type into the POST request without us knowing it. So setting up Content-Type header explicitly on our own messes up the request.
Setting the value of Content-Type header to multipart/form-data involves a complex concept of setting up boundaries of multiple parts of the file as detailed here. It can be error-prone. So heavy lifting of setting up the boundaries is done automatically for us by the tool itself. This is the reason why it doesn't expect us to set the content-type header explicitly in this case. Please see how I've set only Authorization header while uploading the image file on my PC (Refer screenshot):
Authorization header is an optional header. It is required only if you've setup some sort of authentication on the web server. So if anonymous access is allowed on your website then Headers tab in your case should be empty i.e. no key value pairs at all.
Note: Just for information, the correct content type for image files in a POST request is multipart/form-data even though we don't need to set it explicitly in the tool. Screenshot in the question shows that Content-Type header is being set as application/x-www-form-urlencoded which is not right.
In the post you referrer to the data is being uploaded as "x-www-form-url-encoded"
Your Postman screen shot shows you are uploading it as "form-data"
Additionally, you adding a key "image01" where the ARC example doesn't appear to be sending a key.
If you want to upload the file using form-data you need a different approach:
// POST api/files
public async Task<HttpResponseMessage> Post()
{
// Check if the request contains multipart/form-data.
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
string root = HttpContext.Current.Server.MapPath("~/App_Data");
var provider = new MultipartFormDataStreamProvider(root);
string value;
try
{
// Read the form data and return an async data.
var result = await Request.Content.ReadAsMultipartAsync(provider);
// This illustrates how to get the form data.
foreach (var key in provider.FormData.AllKeys)
{
foreach (var val in provider.FormData.GetValues(key))
{
// return multiple value from FormData
if (key == "value")
value = val;
}
}
if (result.FileData.Any())
{
// This illustrates how to get the file names for uploaded files.
foreach (var file in result.FileData)
{
FileInfo fileInfo = new FileInfo(file.LocalFileName);
if (fileInfo.Exists)
{
//do somthing with file
}
}
}
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.Created, value);
response.Headers.Location = new Uri(Url.Link("DefaultApi", new { id = files.Id }));
return response;
}
catch (System.Exception e)
{
return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, e);
}
}
Related
I am using Insomnia for testing an API, but the same happens with Postman.
I want to test a file upload, with the following controller:
public async Task<IActionResult> Post([FromForm]IFormFile File)
If I set the request as a multipart request:
it works.
However, if I set it as a binary file:
I don't know how to get the data. How can it be done?
Also, in the controller method's signature, if I change [FromForm] to [FromBody], I'm not getting data.
Can someone clarify this for me?
As you've noticed already, using binary file option in Postman/Insomnia doesn't work the standard way. There are three different ways to upload file via RESTful API, and you have to choose one.
I've included code snippets that read the uploaded file contents to a string and output it -- try sending a text file, and you should get the contents of the file in the 200 response.
Form-data upload
This is the most popular/well-known upload method formatting the data you send as a set of key/value pairs. You normally need to specify Content-Type to multipart/form-data in the request, and then use [FromForm] attribute in MVC to bind values to variables. Also, you can use the built-in IFormFile class to access the file uploaded.
[HttpPost]
public async Task<IActionResult> PostFormData([FromForm] IFormFile file)
{
using (var sr = new StreamReader(file.OpenReadStream()))
{
var content = await sr.ReadToEndAsync();
return Ok(content);
}
}
Body upload
You can send body in the format that MVC understands, e.g. JSON, and embed the file inside it. Normally, the file contents would be encoded using Base64 or other encoding to prevent character encoding/decoding issues, especially if you are sending images or binary data. E.g.
{
"file": "MTIz"
}
And then specify [FromBody] inside your controller, and use class for model deserialization.
[HttpPost]
public IActionResult PostBody([FromBody] UploadModel uploadModel)
{
var bytes = Convert.FromBase64String(uploadModel.File);
var decodedString = Encoding.UTF8.GetString(bytes);
return Ok(decodedString);
}
// ...
public class UploadModel
{
public string File { get; set; }
}
When using large and non-text files, the JSON request becomes clunky and hard to read though.
Binary file
The key point here is that your file is the whole request. The request doesn't contain any additional info to help MVC to bind values to variables in your code. Therefore, to access the file, you need to read Body in the Request.
[HttpPost]
public async Task<IActionResult> PostBinary()
{
using (var sr = new StreamReader(Request.Body))
{
var body = await sr.ReadToEndAsync();
return Ok(body);
}
}
Note: the example reads Body as string. You may want to use Stream or byte[] in your application to avoid file data encoding issues.
In addition of the above, in case of multipart file conversion to base64String you can refer to the below:
if (File.Length> 0)
{
using (var ms = new MemoryStream())
{
File.CopyTo(ms);
var fileBytes = ms.ToArray();
string s = Convert.ToBase64String(fileBytes);
}
}
Note: I am using this code for .NET CORE 2.1
I wrote this code for show stream file in browser:
public IActionResult GetAvatar()
{
var id = httpContextAccessor.HttpContext.User.Identity.GetUserId<long>();
if (id > 0)
{
var user = dispatchers.QueryAsync(new GetUserByIdQuery { id = id }).Result;
if (user.Success)
{
return PhysicalFile(Path.Combine(this.finder.PathAvatarUserUploadFolder(), user.Result.Photo), "application/octet-stream");
}
return BadRequest(user.ErrorMessage);
}
return BadRequest("Id not valid");
}
this line : return PhysicalFile(Path.Combine(this.finder.PathAvatarUserUploadFolder(), user.Result.Photo), "application/octet-stream");
But it have problem because when I enter the url in the browser, it downloads the file and it must go to downloads and open that file.
I need to open file in the browser. How can i solve this problem?
When you check the response headers using Chrome Developer tools or Fiddler, what is your Content-Disposition header set to? If its set to attachment the browser will always download the file. You can set the Content-Disposition to inline to tell browser to show the content inline if possible and offer download if it can't show content inline. This header can be set in different ways, but the simplest would be adding a header right before returning the file contents like below.
Response.Headers["Content-Disposition"] = $"inline; filename={user.Result.Photo}";
return PhysicalFile(Path.Combine(this.finder.PathAvatarUserUploadFolder(), user.Result.Photo), "application/octet-stream");
I am using Insomnia for testing an API, but the same happens with Postman.
I want to test a file upload, with the following controller:
public async Task<IActionResult> Post([FromForm]IFormFile File)
If I set the request as a multipart request:
it works.
However, if I set it as a binary file:
I don't know how to get the data. How can it be done?
Also, in the controller method's signature, if I change [FromForm] to [FromBody], I'm not getting data.
Can someone clarify this for me?
As you've noticed already, using binary file option in Postman/Insomnia doesn't work the standard way. There are three different ways to upload file via RESTful API, and you have to choose one.
I've included code snippets that read the uploaded file contents to a string and output it -- try sending a text file, and you should get the contents of the file in the 200 response.
Form-data upload
This is the most popular/well-known upload method formatting the data you send as a set of key/value pairs. You normally need to specify Content-Type to multipart/form-data in the request, and then use [FromForm] attribute in MVC to bind values to variables. Also, you can use the built-in IFormFile class to access the file uploaded.
[HttpPost]
public async Task<IActionResult> PostFormData([FromForm] IFormFile file)
{
using (var sr = new StreamReader(file.OpenReadStream()))
{
var content = await sr.ReadToEndAsync();
return Ok(content);
}
}
Body upload
You can send body in the format that MVC understands, e.g. JSON, and embed the file inside it. Normally, the file contents would be encoded using Base64 or other encoding to prevent character encoding/decoding issues, especially if you are sending images or binary data. E.g.
{
"file": "MTIz"
}
And then specify [FromBody] inside your controller, and use class for model deserialization.
[HttpPost]
public IActionResult PostBody([FromBody] UploadModel uploadModel)
{
var bytes = Convert.FromBase64String(uploadModel.File);
var decodedString = Encoding.UTF8.GetString(bytes);
return Ok(decodedString);
}
// ...
public class UploadModel
{
public string File { get; set; }
}
When using large and non-text files, the JSON request becomes clunky and hard to read though.
Binary file
The key point here is that your file is the whole request. The request doesn't contain any additional info to help MVC to bind values to variables in your code. Therefore, to access the file, you need to read Body in the Request.
[HttpPost]
public async Task<IActionResult> PostBinary()
{
using (var sr = new StreamReader(Request.Body))
{
var body = await sr.ReadToEndAsync();
return Ok(body);
}
}
Note: the example reads Body as string. You may want to use Stream or byte[] in your application to avoid file data encoding issues.
In addition of the above, in case of multipart file conversion to base64String you can refer to the below:
if (File.Length> 0)
{
using (var ms = new MemoryStream())
{
File.CopyTo(ms);
var fileBytes = ms.ToArray();
string s = Convert.ToBase64String(fileBytes);
}
}
Note: I am using this code for .NET CORE 2.1
I have an API (https://www.readability.com/developers/api/parser#idm386426118064) to extract the contents of the webapges, but on passing a shortened url or an url that redirects to other, it gives error.
I am developing windows phone 8.1 (xaml) app. Is there any way to get the destination url in c# or any work around?
eg url - http://www.bing.com/r/2/BB7Q4J4?a=1&m=EN-IN
You could intercept the Location header value before the HttpClient follows it like this:
using (var handler = new HttpClientHandler())
{
handler.AllowAutoRedirect = false;
using (var client = new HttpClient(handler))
{
var response = await client.GetAsync("shortUrl");
var longUrl = response.Headers.Location.ToString();
}
}
This solution will always be the most efficient because it only issue one request.
It is possible however, that the short url will reference another short url and consequently cause this method to fail.
An alternative solution would be to allow the HttpClient to follow the Location header value and observe the destination:
using (var client = new HttpClient())
{
var response = client.GetAsync("shortUrl").Result;
var longUrl = response.RequestMessage.RequestUri;
}
This method is both terser and more reliable than the first.
The drawback is that this code will issue two requests instead of one.
You can get the ResponseUri from GetResponse():
string redirectedURL = WebRequest.Create("http://www.bing.com/r/2/BB7Q4J4?a=1&m=EN-IN")
.GetResponse()
.ResponseUri
.ToString();
Interesting article, by the way.
You need to inspect the headers returned from the URL.
If you get HTTP return codes 301 or 302, then you are being notified that the page is redirecting you to another URL.
See http://www.w3.org/Protocols/HTTP/HTRESP.html for more details about HTTP return codes.
I would like to set ServiceStack's default format to JSON, as opposed to the HTML formatted response it normally returns when a service is accessed from a browser. I know this can be specified on each request by sending a ?format=json parameter or setting the Accept header to application/json. Is there a way to change this without having to rely on these hints from the request?
In addition to specifying it on the QueryString with ?format=json, by appending the format .ext to the end of the route, e.g: /rockstars.json, or by specifying the HTTP Header (in your HttpClient): Accept: application/json.
Otherwise if your HttpClient doesn't send an Accept header you can specify JSON as the default content type in your AppHost with:
SetConfig(new HostConfig {
DefaultContentType = MimeTypes.Json
});
All Configuration options in ServiceStack are set here.
The issue when calling web services from a web browser is that they typically ask for Accept: text/html and not JSON which by contract ServiceStack obliges by returning back HTML if it is enabled.
To ensure JSON is returned you may also want to disable the HTML feature with:
SetConfig(new HostConfig {
EnableFeatures = Feature.All.Remove(Feature.Html),
});
Different ways to specify the Response Content Type
Otherwise if you want to override the Accept header you can force your service to always return json with any of these ways to Customize the HTTP Response, e.g:
Using a filter (AddHeader is built-in):
[AddHeader(ContentType=MimeTypes.Json)]
public object Any(Request request) { ... }
Setting the Response in the service:
public object Any(Request request)
{
base.Response.ContentType = MimeTypes.Json;
return dto;
}
Returning a decorated response:
return new HttpResult(dto, MimeTypes.Json);
I use the PreRequestFilter to force JSON responses to a browser. You still see the ?format=json on the querystring, but it's useful if you've disabled html & xml.
this.PreRequestFilters.Add( (req, res) =>
{
const string queryString = "format=json";
var jsonAccepted = req.AcceptTypes.Any(t => t.Equals(ContentType.Json, StringComparison.InvariantCultureIgnoreCase));
var jsonSpecifiedOnQuerystring = !string.IsNullOrEmpty(req.QueryString["format"]) && req.QueryString["format"].Equals("json", StringComparison.InvariantCultureIgnoreCase);
if (!jsonAccepted && !jsonSpecifiedOnQuerystring)
{
var sb = new StringBuilder(req.AbsoluteUri);
sb.Append(req.AbsoluteUri.Contains("?") ? "&" : "?");
sb.Append(queryString);
res.RedirectToUrl(sb.ToString(), HttpStatusCode.SeeOther);
res.Close();
}
});
Late to the question, but since I couldn't find the answer anywhere, I finally figured it out from ServiceStack's source code :)
The simplest way I found to default to Json instead of Html from the browser was this:
HttpRequestExtensions.PreferredContentTypes = new[] { MimeTypes.Json, MimeTypes.Xml };
Call this at the startup of your app, and it will override default's ServiceStack mime types and start with json (which will work with your browser's requests since / will match it).
Note that you should still disable Html and make Json the default mime type:
SetConfig(new HostConfig {
DefaultContentType = MimeTypes.Json
EnableFeatures = Feature.All.Remove(Feature.Html),
});
For the curious: ServiceStack uses internally HttpRequestExtensions.GetResponseContentType (see HttpRequestExtensions.cs), which loops through preferred content types. Because it contains MimeTypes.Html, it will catch the first accept type from the browser (text/html) and ignore whatever is coming after. By overriding this, text/html is not seen as a preferred content type, and it then skips to */* which defaults to json as expected.