400 Bad Request when trying to send api request with IFormFile in request object - asp.net-core

I am having a hard time figuring out how to send an IFormFile object part of the request. It is an API call to upload an image. I have found a few resources and have tried each suggestion but I always get a 400 Bad Request response when I try and hit the API. Both the API and client are ASP.NET Core 2.1
Call to the API
public async Task<ApiResponse<ImageDto>> AddImageToWebsite(AddImageToWebsiteRequest request)
{
try
{
HttpClient client = new HttpClient();
var url = $"{_apiInfo.Url}/portal/AddImageToWebsite";
byte[] data;
using (var br = new BinaryReader(request.Image.OpenReadStream()))
{
data = br.ReadBytes((int) request.Image.OpenReadStream().Length);
}
var bytes = new ByteArrayContent(data);
MultipartFormDataContent multiContent = new MultipartFormDataContent();
multiContent.Add(bytes, "file", request.Image.FileName);
multiContent.Add(new StringContent(request.WebsiteId.ToString()), "WebsiteId");
multiContent.Add(new StringContent(request.AltText), "AltText");
// BREAKS AFTER THIS POST CALL
var apiResponse = await client.PostAsync(url, multiContent);
// DESERIALIZE RESPONSE TO RESPONSE OBJECT HERE
}
catch (Exception ex)
{
Log.Error(ex, "Error calling api");
return ApiResponse.InternalError<ImageDto>(ex.Message);
}
}
AddImageToWebsiteRequest
public class AddImageToWebsiteRequest
{
public int WebsiteId { get; set; }
public IFormFile Image { get; set; }
public string AltText { get; set; }
}
API CALL
[HttpPost]
[Route("AddImageToWebsite")]
public async Task<JsonResult> AddImageToWebsite(AddImageToWebsiteRequest request)
{
return await this.HandleRequest(async () =>
{
var website = _dataAccess.GetWebsite(request.WebsiteId);
if (website == default(Website))
{
return ApiResponse.NotFound<ImageDto>("Website not found");
}
// UPLOAD IMAGE CODE HERE
}
}
It does not even hit the API call. I also tried posting it as follows, and it worked as long as I did not have an image in the serialized object.
Another Attempt
var stringContent = new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, "application/json");
var apiResponse = await client.PostAsync(url, stringContent);
// DESERIALIZE RESPONSE TO RESPONSE OBJECT HERE
I have tried so many different recommendations online and none seem to work.

IFormFile is only for multipart/form-data encoded POST requests, i.e. a traditional form post. If you're sending JSON, your "upload" needs to be a Base64 string and you need to bind to a byte[]:
public class AddImageToWebsiteRequest
{
public int WebsiteId { get; set; }
public byte[] Image { get; set; }
public string AltText { get; set; }
}
JsonConvert.SerializeObject will automatically convert byte[]s into Base64 strings.

How are you sending this from the view? If you are using a form, you can just give it the multipart/form-data type, give the input type of file and then bind it to the IFormFile in the parameter.
View:
<form id="fileupload" action="yourpath/AddImageToWebsite/" method="POST" enctype="multipart/form-data">
<button type="submit" class="btn btn-primary start">
</button>
<input type="file" name="YourFile"/>
<!--Whatever other things you need to input, use hidden fields-->
</form>
Controller:
[HttpPost]
[Route("AddImageToWebsite")]
public async Task<JsonResult> AddImageToWebsite(IFormFile YourFile)
{
//Do what you need....
}

Related

Pass Composite Object to a Post Web API Method

I have the following composite object which I want to pass in web API post method in Xamarin:
public class ShoppingCartCustomizedItems
{
public AddToCart addToCart { get; set; }
public List<AddCustomizedProductSelectionsToCart> AddCustomizedProductSelectionsToCart { get; set; }
}
Below is the API service method to pass the data to the web API:
public static async Task<bool> AddCustomizedItemsInCart(ShoppingCartCustomizedItems addToCart)
{
var httpClient = new HttpClient();
var json = JsonConvert.SerializeObject(addToCart);
var content = new StringContent(json, Encoding.UTF8, "application/json");
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", Preferences.Get("accessToken", string.Empty));
var response = await httpClient.PostAsync(AppSettings.ApiUrl + "api/ShoppingCartItems/addCustomizedShoppingCartItem", content);
if (!response.IsSuccessStatusCode) return false;
return true;
}
Finally the web API post method has the following signature:
[HttpPost("[action]")]
public IActionResult addCustomizedShoppingCartItem([FromBody] ShoppingCartCustomizedItem shoppingCartCustomizedItem)
Now whenever I send the post method from Xamarin, the shoppingCartCustomizedItem is always null. How can I address this and what is the best practice to pass composite object in web API method?

Blazor: How to pass multiple parameter's from NavigateTo to a WEB API controller to download a file

I'm trying to use NavivgateTo in Blazor to pass a file id and name to download a file from my Download controller.
What is the proper setup? I've tried a number of possibilities and I keep seeing an error: Sorry, there is nothing at this address.
Razor Page
public async Task SelectedDisplayDbItemChanged(DisplayDbItemsComboBoxItemDTO item)
{
Data = null;
Data = GetDataTable();
var fileId = await utilities.ExportDataTableToFile((DataTable)Data).ConfigureAwait(false);
//navigationManager.NavigateTo($"api/download/fileId/" + fileId + "/fileName/" + "myfile", true);
//?data1=678&data2=c-sharpcorner
navigationManager.NavigateTo($"api/Download/{fileId}/{"myfile"}", true);
}
Controller:
[HttpPost("Download/{fileId}/{fileName}")]
public async Task<IActionResult> Download(string fileId, string fileName)
{
using (var ms = new MemoryStream())
{
var fullPath = Path.Combine(DownloadPath, fileId);
await using (var stream = new FileStream(fullPath, FileMode.Open))
{
await stream.CopyToAsync(ms);
}
ms.Position = 0;
return File(ms, "application/octet-stream", $"{fileName}.xlsx");
}
}
I've seen a lot of examples from the Razor page to the Razor page, but not from NavigateTo to a controller with passing multiple parameters.
I've tried these responses as well: https://stackoverflow.com/a/71130256/9594249
https://stackoverflow.com/a/71130256/9594249
Not like Asp.net MVC or razor page, in Blazor parameters are passed by [Parameter] tag
#page "/Download/{fileId}/{fileName}"
#code {
[Parameter]
public string? fileId { get; set; }
[Parameter]
public string? fileName { get; set; }
}
please refer : https://learn.microsoft.com/en-us/aspnet/core/blazor/fundamentals/routing?view=aspnetcore-6.0
(Updated)
add to Program.cs or Startup.cs:
builder.Services.AddRazorPages(options => {
options.Conventions.AddPageRoute("/DownloadPage", "Download/{fileId?}/{fileName?}");
}
});
Pages/DownloadPage.cshtml
#page "{fileId?}/{fileName?}"
#model BlazorApp.Pages.DownloadModel
Pages/DownloadPage.cshtml.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace BlazorApp.Pages;
public class DownloadModel : PageModel
{
private readonly IWebHostEnvironment _env;
public DownloadModel(IWebHostEnvironment env)
{
_env = env;
}
public IActionResult OnGet()
{
// work with RouteData.Values["fileId"] and RouteData.Values["fileName"]
}
}
please refer :
https://learn.microsoft.com/en-us/answers/questions/243420/blazor-server-app-downlaod-files-from-server.html
https://learn.microsoft.com/ko-kr/aspnet/core/razor-pages/razor-pages-conventions?view=aspnetcore-6.0

How to upload pictures to controller

I save pictures as byte[] in the database.
Im familiar with the HttpPostedFileBase in asp.net but they got rid of that in .net Core 5 apparently
How can I send the uploaded picture to the controller which converts it into picture and inserts into the database?
<input name="UploadedPic" type="file"readonly />
<button type="submit">Submit</button>
C# controller
public IActionResult UploadPictures()
{
return null;
}
At first, you have to create ViewModel for upload. and this ViewModel uses your [HttpGet] methods view.And your model should be public byte[] Image { get; set; } and your ViewModel should be public IFormFile Image { get; set; }
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> UploadPictures([Bind("Image")] ProductVM productVM)
{
string msg = "";
if (ModelState.IsValid)
{
Product p = new Product();
//productImage
string webroot = _he.WebRootPath; // _he comes from IHostingEnvironment
string folder = "Product_Images";
string imgfilename = Path.GetFileName(productVM.Image.FileName);
string filewrite = Path.Combine(webroot, folder, imgfilename);
using(MemoryStream ms=new MemoryStream())
{
await productVM.Image.CopyToAsync(ms);
p.Image = ms.ToArray();
p.ImageFile = "/" + folder + "/" + imgfilename;
}
using(var stream=new FileStream(filewrite, FileMode.Create))
{
await productVM.Image.CopyToAsync(stream);
}
_context.Add(p);
await _context.SaveChangesAsync();
msg = "Product inserted successfully!!!";
TempData["msg"] = msg;
return RedirectToAction(nameof(Index));
}
else
{
msg = "Product image incomplete. Please try again...";
}
TempData["msg"] = msg;
return RedirectToAction("Create");
}
I have always used the .net core docs on upload files, here https://learn.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads?view=aspnetcore-5.0#file-upload-scenarios
and
I check it just now also, this particular section https://learn.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads?view=aspnetcore-5.0#upload-small-files-with-buffered-model-binding-to-a-database should answer your question.

WebApi for Email delivery

I created a Web API controller inside my ASP.NET MVC 4 Website to specifically send emails so that I can consume it for this purpose and eventually provide it to other consumers (Mobile, etc).
Everything is working fine but I want to use it asynchronously, altough it's not working that way, my website blocks until the work is finished.
I have a regular Controller where I call a helper class that calls the API.
Controller Code :
[HttpPost]
public async Task<ActionResult> ContactUs(ContactUsModel model)
{
ExternalApiCalls extApi = new ExternalApiCalls();
await extApi.PostContactUs(model).ConfigureAwait(false);
return RedirectToAction("ContactUsSuccess", "Account");
}
Helper Class Code :
public class ExternalApiCalls
{
public HttpClient client { get; set; }
public ExternalApiCalls()
{
client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:10239/");
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
}
public async Task PostContactUs(ContactUsModel model)
{
try
{
var response = await client.PostAsJsonAsync("api/WAPIMail", model).ConfigureAwait(false);
}
catch (HttpRequestException ex)
{
}
catch (System.FormatException)
{
}
finally
{
}
}
}
Web API Controller :
public class WAPIMailController : ApiController
{
public void PostContactUs(ContactUsModel model)
{
// Send Email Here
}
}
Thanks a lot for your help
I finally managed how to do it..
For brevity's sake, I am showing oversimplified code, see below:
Controller :
[HttpPost]
public ActionResult ContactUs(ContactUsModel model)
{
new ExternalApiCalls().MailContactUs(model);
return RedirectToAction("ContactUsSuccess", "Account");
}
Helper Class :
public void MailContactUs(ContactUsModel model)
{
client.PostAsJsonAsync("api/WAPIMail/MailContactUs", model).ConfigureAwait(false);
}
Web API Controller :
[HttpPost]
public void MailContactUs(ContactUsModel model)
{
//Email Logic Here
}

MVC Web Api returning serialized response instead of css

I am having an issue returning css from a web api controller. The code takes a request for a css file and returns it after reading it from the database.
The problem is that the web api code seems to be serializing the response and returning that instead of the css itself.
Here you can see a link tag that the browser is sending to the server which should return css. You can also see that the response looks like a serialization of my css instead of just the css string.
My request and response headers:
My controller looks like this:
public HttpResponseMessage Get(string fileName, string siteId, int id)
{
var fileData = ReadSomeCssFromTheDatabase();
var result = new HttpResponseMessage(HttpStatusCode.OK);
result.Content = new ByteArrayContent(fileData);
result.Content.Headers.ContentType = new MediaTypeHeaderValue("text/css");
result.Headers.CacheControl = new CacheControlHeaderValue();
result.Headers.CacheControl.MaxAge = TimeSpan.FromHours(0);
result.Headers.CacheControl.MustRevalidate = true;
return result;
}
There is a “text/css” formatter installed that is being created but not being hit for some reason.
public class CssFormatter : MediaTypeFormatter
{
public CssFormatter()
{
SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/css"));
}
public override Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger)
{
var taskCompletionSource = new TaskCompletionSource<object>();
try
{
var memoryStream = new MemoryStream();
readStream.CopyTo(memoryStream);
var s = System.Text.Encoding.UTF8.GetString(memoryStream.ToArray());
taskCompletionSource.SetResult(s);
}
catch (Exception e)
{
taskCompletionSource.SetException(e);
}
return taskCompletionSource.Task;
}
public override bool CanReadType(Type type)
{
return type == typeof(string);
}
public override bool CanWriteType(Type type)
{
return false;
}
}
What am I doing wrong?
Your formatter would not be hit because you are not going through content negotiation process (as you are returning HttpResponseMessage in your action...you could use Request.CreateResponse<> to make conneg process run)
You are trying to 'write' the css content right?...but i see that CanWriteType is returning 'false' and also you seem to be overriding ReadFromStreamAsync instead of WriteToStreamAsync?
An example of how you could do(from what i understood about the above scenario):
public class DownloadFileInfo
{
public string FileName { get; set; }
public string SiteId { get; set; }
public int Id { get; set; }
}
public HttpResponseMessage Get([FromUri]DownloadFileInfo info)
{
// validate the input
//Request.CreateResponse<> would run content negotiation and get the appropriate formatter
//if you are asking for text/css in Accept header OR if your uri ends with .css extension, you should see your css formatter getting picked up.
HttpResponseMessage response = Request.CreateResponse<DownloadFileInfo>(HttpStatusCode.OK, info);
response.Headers.CacheControl = new CacheControlHeaderValue();
response.Headers.CacheControl.MaxAge = TimeSpan.FromHours(0);
response.Headers.CacheControl.MustRevalidate = true;
return response;
}
public class CssFormatter : MediaTypeFormatter
{
public CssFormatter()
{
SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/css"));
}
public override bool CanReadType(Type type)
{
return false;
}
public override bool CanWriteType(Type type)
{
return type == typeof(DownloadFileInfo);
}
public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext)
{
//use the 'value' having DownloadFileInfo object to get the details from the database.
// Fead from database and if you can get it as a Stream, then you just need to copy it to the 'writeStream'
}
}