I am trying to upload an image to a server with Axios, but having Error 400 "Bad Request".
I have tried many different ways to implement it: explicitly specifying a "Content-Type", changing controller, trying $.ajax, etc. from Google.
A brief description of what I am expecting: I choose an image as suggested by <input/> and once I submit my choice it is sent to the server.
Currently, in my controller, I set a breakpoint, however, I've never seen request entering it.
My stack is Asp.Core WebAPI + React.js + Axios.
I have stopped on the following code:
Controller:
[Route("api/char")]
[ApiController]
public class CharacterController : ControllerBase
{
[HttpPost]
[Route("[action]")]
public async Task<IActionResult> SetProfilePhoto(IFormFile file)
{
return Ok();
}
}
React Component:
export default class BioEdit extends React.Component {
...
changePhoto = event => {
event.preventDefault();
const file = event.target.files[0];
const formData = new FormData();
formData.append("File", file);
Axios.post("api/char/SetProfilePhoto", formData, {
headers: { 'Content-Type': 'multipart/form-data' }
})
.catch(error => console.log(error));
}
render(){
return (
<label >
<img src={this.state.char.image} width="30%" height="30%" className="border border-secondary" />
<input type="file" style={{ display: "none" }} accept="image/*" onChange={this.changePhoto} />
</label>
);
}
}
Here is what I have in the browser (Yandex browser it is to be specific, based on Chrome):
There are two options based on whether you need API Controller or not.
The first is to remove the APIControllerAttribute from off your controller, then nothing else is needed to be done;
The second is to set up the Startup.cs as doc prescribes. Adding
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); solved my problem.
NOTE: it is not necessary to set up headers to undefined or whatever. Axios deals with it in the right way itself.
add undefined content type. it should work then
Axios.post("api/char/SetProfilePhoto", formData, {
headers: { 'Content-Type': undefined }
})
Related
=============== UPDATE ===============
Apologies, but with all the chopping and changing, commenting out and back in again, to try and get this working, I had accidentally stopped the form calling the script in Create.cshtml. The form of the View file should actually read as follows:
<form asp-controller="Documents" asp-action="Create" method="post" enctype="multipart/form-data" onSubmit="AJAXSubmit(this); return false;">
I haven't closed the question, because the script in Create.cshtml is receiving a 400 (Bad Request). This appears to be the real problem - but, I cannot understand why it's happening.
If and when I get to the bottom of it, I will update the question. Hopefully, it will be a useful answer to somebody.
=============== END UPDATE ===============
I've been following https://learn.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads?view=aspnetcore-3.1#upload-large-files-with-streaming to upload large files to a web app. Unfortunately, when I come to use the MultipartReader (reader.ReadNextSectionAsync), I get the error unexpected end of stream, the content may have already been read by another component.
I've downloaded the sample app (https://github.com/dotnet/AspNetCore.Docs/tree/master/aspnetcore/mvc/models/file-uploads/samples/3.x/SampleApp) and that works fine, but they're using Razor pages and my project isn't.
One notable difference: when running the demo app from github, when I click Upload, to upload the large file, the Controller method is called immediately. However, when I click to upload the document on my app, the browser shows me the file being uploaded, then the Controller method is called.
I'm fairly new to ASP.NET, and I've been banging my head against a brick wall for a couple of days, so any help would be much appreciated.
My relevant code snippets are as follows:
GenerateAntiforgeryTokenCookieAttribute: adds antiforgery token to cookie, copied exactly from the example docs
DisableFormValueModelBindingAttribute: attribute class to prevent form binding, copied exactly from the example docs
MultipartRequestHelper: copied exactly from the example docs
startup.cs: this is maybe where my problem is, as my project is not using Razor pages, and is using Feature folders, so I've deviated from the example. Plus, my project is using Feature folders.
public void ConfigureServices(IServiceCollection services)
{
...
services.AddMvc(o => o.Conventions.Add(new FeatureConvention()))
// first part is for feature folders
.AddRazorOptions(options =>
{
options.ViewLocationFormats.Clear();
options.ViewLocationFormats.Add("/Features/{3}/{1}/{0}.cshtml");
options.ViewLocationFormats.Add("/Features/{3}/{0}.cshtml");
options.ViewLocationFormats.Add("/Features/Shared/{0}.cshtml");
options.ViewLocationExpanders.Add(new FeatureViewLocationExpander());
})
// FeatureConvention and FeatureViewLocationExpander taken from https://learn.microsoft.com/en-us/archive/msdn-magazine/2016/september/asp-net-core-feature-slices-for-asp-net-core-mvc#feature-folders-in-aspnet-core-mvc
// next part is to prevent form value binding and to add the anti forgery token to the cookie
.AddRazorPagesOptions(options =>
{
options.Conventions.AddPageApplicationModelConvention(
"/Documents/Create",
model =>
{
model.Filters.Add(new GenerateAntiForgeryTokenCookieAttribute());
model.Filters.Add(new DisableFormValueModelBindingAttribute());
}
);
});
...
}
My folder structure is as follows:
root
|
`-- Features
|
`-- Documents
|
`-- Create.cshtml
`-- DocumentsController.cs
Create.cshtml
#model DocumentCreateViewModel
<form asp-controller="Documents" asp-action="Create" method="post" enctype="multipart/form-data">
<div>
<label asp-for="filename"></label>
<input asp-for="filename""><input/>
</div>
<div>
<label asp-for="file"></label>
<input asp-for="file"><input/>
</div>
<div>
<button type="submit">submit</button>
</div>
</form>
#section Scripts {
<script>
"use strict";
async function AJAXSubmit (oFormElement) {
const formData = new FormData(oFormElement);
try {
const response = await fetch(oFormElement.action, {
method: 'POST',
headers: {
'RequestVerificationToken': getCookie('RequestVerificationToken')
},
body: formData
});
oFormElement.elements.namedItem("result").value =
'Result: ' + response.status + ' ' + response.statusText;
} catch (error) {
console.error('Error:', error);
}
}
function getCookie(name) {
var value = "; " + document.cookie;
var parts = value.split("; " + name + "=");
if (parts.length == 2) return parts.pop().split(";").shift();
}
</script>
}
DocumentCreateViewModel.cs
public class DocumentCreateViewModel
{
[Required]
public string Name { get; set; }
[Required]
public IFormFile File { get; set; }
}
DocumentsController.cs: is the contents of UploadPhysical, copied exactly from the example docs, UploadPhysical is renamed to Create
[HttpPost]
[DisableFormValueModelBinding]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create()
{
if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
{
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 1).");
// Log error
return BadRequest(ModelState);
}
var boundary = MultipartRequestHelper.GetBoundary(
MediaTypeHeaderValue.Parse(Request.ContentType),
_defaultFormOptions.MultipartBoundaryLengthLimit);
var reader = new MultipartReader(boundary, HttpContext.Request.Body);
var section = await reader.ReadNextSectionAsync();
// ^^^^ this is where the "unexpected end of stream" exception is thrown
Firstly, thank you to #Brando Zhang for getting me back on track.
The problem, the 400 (Bad Request), was due to DocumentsController attempting to verify an anti-forgery token that didn't exist - which was down to me unsuccessfully porting the Razor example into MVC.
In the example code, in startup.c, the following section is only applicable to Razor pages - not MVC:
.AddRazorPagesOptions(options =>
{
options.Conventions.AddPageApplicationModelConvention(
"/Documents/Create",
model =>
{
model.Filters.Add(new GenerateAntiForgeryTokenCookieAttribute());
model.Filters.Add(new DisableFormValueModelBindingAttribute());
}
);
});
Therefore, that could be removed. However, now there is not anti-forgery token being generated, so when a POST is made to Create() in DocumentsController, no token is present, so an HTTP 400 is returned.
The solution is a simple one (although it took me long enough to find it!): add [GenerateAntiForgeryTokenCookie] to the GET method that presents the form in DocumentsController:
[HttpGet]
[GenerateAntiForgeryTokenCookie]
public IActionResult Index()
{
return View("Create");
}
[HttpPost]
[DisableFormValueModelBinding]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create()
{
if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
...
}
Hope it helps!
I'm working on an asp.net core application. There is a settings page that is initially empty. The user can then update the settings by selecting a json file. These settings are then confirmed and sent to the controller. The controller should return the same page but displaying the updated settings.
The file is being read and sent to the controller, it is then correctly being deserialized into a settings object and stored in the session.
I have tried using a GET for updating the settings but the query string is too long.
This is the form the user submits, it's part of a dialog that is shared between multiple different functions so is very generic.
<form method="post">
<button type="submit" class="btn btn-primary" id="modalSubmitButton">Okay</button>
</form>
After the user chooses a file the content of it is retrieved and an event handler is set for the above button.
function OpenFile(event) {
const input = event.target
if ('files' in input && input.files.length > 0) {
var file = (event.target.files)[0];
var fileReader = new FileReader();
fileReader.onload = (function (file) {
return function (e) {
var contents = e.target.result;
document.getElementById("modalHeading").innerHTML = "Please confirm overwrite";
document.getElementById("modalSubmitButton").addEventListener("click", function (e) {
LoadSettings(contents);
});
$("#dialog").modal('show');
};
})(file);
fileReader.readAsText(file);
}
}
Adding this to the click event makes the controller only called once (but with null for the model)
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
return false;
The ajax call to call the controller when the button is clicked.
function LoadSettings(settings) {
data = JSON.stringify(settings);
$.ajax({
type: "POST",
data: data,
url: "https://localhost:44365/Site/LoadSettings",
contentType: "application/json; charset=utf-8"
})
}
This is the function that is called, there are two different settings pages depending on the json.
[HttpPost]
public void LoadSettings([FromBody]SiteSettings config)
{
HttpContext.Session.SetObject("config", config);
if (config.Config.Count < 2)
{
return Settings();
}
return MultiSettings();
}
Which calls this (the controller function responsible for this page).
[HttpGet]
public IActionResult Settings()
{
var config = HttpContext.Session.GetObject<SiteSettings>("config");
Model = new SettingsViewModel();
if (config != null)
{
Model.Config = config;
}
return View(Model);
}
That constructor takes the settings json and deserializes it.
public SiteSettings Config { get; set; }
public SettingsViewModel(string settingsConfig)
{
try
{
Config = JsonConvert.DeserializeObject<SiteSettings>(settingsConfig);
}
catch (Exception ex)
{
Config = new SiteSettings();
}
}
I am getting a 415 error when calling this using Ajax. I am able to make a POST and send the settings via Postman and receive the page with the updated settings. Looking at it in Fiddler the function is called twice, the first with the json and the second without.
In Fiddler the first request has the 'Session was aborted by the client, Fiddler or the server' icon.
Pass a string variable from the front end (HTML/.ts to C# controller).
I'm skimming through angular.io documentation and walkthroughs made by unofficial people. I watched videos and it appears none of it is relevant to my code. I started a new project(ASP.NET Core web application with Angular) in Visual Studio 2019. There are .ts components and .cs controllers. My HTML is set up to take a string input. I have tried using HTTP POST request and ajax requests. I may have done them incorrectly with the wrong arguments. I've consulted
How to send data to an ASP.NET Controller with Angular2
ASP.Net MVC How to pass data from view to controller
Passing Model data from View to Controller and using its values
AngularJS & asp.net MVC - passing data to controller?
and countless others not on StackOverflow.
.html
<input #box (keyup)="onKey(box.value)">
Your name is: {{name}}
.ts
onKey(value: string)
{
this.name = value;
}
I expect the name string to be available in the controller where I can print it out and later use it in a database.
I create a demo using asp.net core Angular template.It passes data on Keyup.
1.home.component.html
<input #box (keyup)="onKey(box.value)">
Your name is: {{name}}
2.home.component.ts
import { Component } from '#angular/core';
import { HttpClient } from '#angular/common/http';
#Component({
selector: 'app-home',
templateUrl: './home.component.html',
})
export class HomeComponent {
name: string;
constructor(
private http: HttpClient
) { }
onKey(value: string):void {
this.name = value;
const formData: FormData = new FormData();
formData.append('name', this.name);
this.http.post('https://localhost:44336/api/SampleData/TestName', formData).subscribe(result => {
console.log(result);
}, error => console.error(error));
}
}
3.SampleData controller(/api/sampleData)
[HttpPost("TestName")]
public JsonResult TestName(string name)
{
//your logic
return Json(name);
}
As per official guide line: https://angular.io/guide/user-input
<input (keyup)="onKey($event)">
onKey(event: any) {
console.log(event.target.value);
let inputValue = event.target.value;
}
Now inputValue will store your value from Input Field on Keyup Event.
Scenario is my MVC view is returning data to Controller action and from my action requirement is to build an object and pass it to an external Web API. I m getting data in my action and building an object as well. Can you please direct me how I should pass object to external Web API.
Also should it be JSON, object or xml ?
I m giving my controller and Web API code below:
Controller action:
public ActionResult Submit(FormCollection form)
{
Options lead = new Options();
lead.Situation = form.GetValue("InsuranceFor").AttemptedValue;
lead.State = form.GetValue("InsuranceState").AttemptedValue;
//Here I want to pass object to Web API
return RedirectToAction("Parameters");
}
Web API method:
public void Post(Lead_Options lead)
{
leadOptService.AddListOptions(lead);
}
I just completed a complex implementation just to satisfy similar requirement. I was assigned to post object from C# MVC Controller to an external RESTful Web API. In the future, the Web API will remain, but the C# MVC may be replaced with NodeJS / Angular application. So what I did was, assign the object to a TempData in a Serialized JSON format, then in the View where the page redirects to, conditionally added AngularJS, and implement AngularJS post to the external WebAPI. In your case, the TempData would look something like this:
this.TempData["lead"] = new JavaScriptSerializer().Serialize(this.Json(lead, JsonRequestBehavior.AllowGet).Data);
Then, in the redirected view "Parameters", you could add this angular code:
#if (this.TempData["lead"] != null)
{
<script type="text/javascript" src="#Url.Content("~/Contents/Scripts/angular.js")"></script>
<script type="text/javascript">
angular
.module('app', [])
.controller('controllerName', ['$http', '$scope', 'apiFactory', function ($http, $scope, apiFactory) {
var leadRecord = '#Html.Raw(this.TempData["lead"])';
var apiUrl = 'https://xxxxxxxxxxxxxx';
apiFactory({
method: 'POST',
url: apiUrl + '/api/apiControllerName/Post',
data: '=' + leadRecord,
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8' }
}).then(function (result) {
console.log(result);
});
}])
.factory('apiFactory', function ($http, $q) {
return function (config) {
var defered = $q.defer();
$http(config)
.success(function (result, status, headers, config) {
defered.resolve(result);
})
return defered.promise;
}
})
</script>
}
<div ng-app="app" class="col-sm-12 sign-in-page">
<div class="row" ng-controller="controllerName">
..... contents of redirected page ....
</div>
</div>
Your WebAPI - (Assuming it's C# Web API 2.2 should look something like this:
[HttpPost]
public string Post([FromBody]string jsonString)
{
try
{
IDictionary<string, string> data = JsonConvert.DeserializeObject<IDictionary<string, string>>(jsonString);
Assuming your object's values are all strings ....
This implementation may not be ideal but it does its job for sure
Oh, alternatively, you could simply add the angular POST to your original view that contains the form controls. But in my case this was not an option because the View must make a full post, the data from the full post must be processed in the model, then the controller gets some of the data from the models and combine it with session information to make up the object, which then has to be sent to a Web API controller..
I have looked around, but have not found anything (Angular post) that can actually make a successful call to a MVC Controller. I know there are a lot of Angular/.Net devs out there. Can I get some help?
Let's keep answers bare bones simple!!!
If I set a linebreak on the controller, I can see that this code is not actually hitting the controller.
HTML
<!-- I click this button -->
<input type="button" value="click" onclick="postit()" />
Javascript/Angular Post
function postit() {
$http({
method: 'POST',
url: 'Home/Give/',
data: { id: 4 }
}).success(successFn).error(errorFn);
}
function successFn() {
alert("success");
}
MVC C# Controller
[AcceptVerbs("OPTIONS")]
public ActionResult Give(int id)
{
var response = "some response" + id.ToString();
return Json(new JavaScriptSerializer().Serialize(response));
}
king Puppy, I've seen a few responses that dictate that the controller parameters should be an object that matches the object that is being sent, however, it seems that it's a little more forgiving than that. Consider the following example (I've updated your function a little):
Javascript:
$scope.postIt = function() {
var data = {
id = 4
};
$http
.post('Home/Give', data)
.success(function(data, status, headers, config) {
successFn();
})
.errors(function(data, status, headers, config) {
errorFn();
});
};
function successFn() {
alert("success");
};
function errorFn() {
alert("error");
};
MVC:
public ActionResult Give(int id)
{
var response = "some response" + id.ToString();
return Json(new JavaScriptSerializer().Serialize(response));
}
If you set a breakpoint, you will see that the id passed in is 4.
If you needed to pass in an object (so more than just one id), you could either create a matching class or struct on the controller side, or have multiple parameters (provided that they are simple value types)
ie:
public JsonResult Give (int id, int reasonId)
{
...
}
Anyway, I realize the post is old, but perhaps it will help you or others.
#kingPuppy this is my way to how to make angularjs post to mvc controller
first, html button for passing the angular js button click function;
<button class="btn btn-info" id="runButton" ng-click="runService('Hi')">Run</button>
so runService angular click (ng-click) function;
// Operation Type is my mvc controller's param
$scope.runService = function (optionType) {
$http({
url: '/System/RunService',
method: "POST",
data: {operationType : optionType}
}).then(function onSuccess(response) {
// Handle success
console.log(response);
}).catch(function onError(response) {
// Handle error
console.log(response);
});
}
And finally this is system controller's action;
NOT : Dont forget to [HttpPost] attribute
[HttpPost]
public ActionResult RunService(string operationType)
{
// Codes
Response.StatusCode = 200;
return Json(JsonRequestBehavior.AllowGet);
}
Hope this could help to you for how to make angular post to mvc controller. Thanks.
There is nothing special you have to do to get Angular to post to a standard MVC controller, and in fact I have several Angular/MVC apps that are using code almost identical to what you have above to POST to controllers that work fine.
I would use Firebug to confirm that your app is posting to the right place. One thing I noticed is that you might not want that trailing / at the end of your URL (so Home/Give instead of Home/Give/)
Good luck!