Spring MVC AngularJS - Post Redirect - javascript

I believe I am just missing something obvious.
I have a post that works but it gives me HTML as response. Normally that would be great if I was actually trying to load some more information on the current page. But I really want it to redirect.
The post in MainService.js
searchOpportunities : function(title, location) {
return $http
.post(
'/',
$.param({
title : title,
location : location
}),
{
headers : {
'Content-Type' : 'application/x-www-form-urlencoded'
}
})
}
The response
#RequestMapping(value = "/", method = RequestMethod.POST)
public ModelAndView search_Post(#RequestParam(value = "title", required = true) String title, #RequestParam(value = "location", required = true) String location) {
ModelAndView searchView = new ModelAndView("search");
searchView.addObject("searchTitle", title);
searchView.addObject("searchLocation", location);
return searchView;
}
To clarify:
I want the page to change to the searchView after the post is sent. Right now it's just sending HTML as response... but I want redirect with the right objects "searchTitle" and "searchLocation"

Because you're just rendering the searchView view after POST, not redirecting to somewhere. If you really want to Redirect, use redirect: prefix. Suppose you have a /search endpoint and gonna redirect to it after successful POST on /, then:
#RequestMapping(value = "/", method = RequestMethod.POST)
public String search_Post(#RequestParam(value = "title", required = true) String title, #RequestParam(value = "location", required = true) String location) {
...
return "redirect:/search";
}
You could render your searchView in /search endpoint, if you want to. Read more about Redirect Views here.

Related

Send form data from React to ASP.NET Core API

I editted the whole question because it was very long and confusing.
Clear, concise new question
I was sending all data from my forms as a stringified JSON, but now I need to append a file, so I can no longer do this. How do I send my form data from React to an ASP.NET Core API in a format that allows appending files?
Old question
I have several forms working perfectly: I send the data using fetch from React and receive it correctly as body in my ASP.NET Core API.
However, I need to send files now, and I don't know how to append them since I am just sending all of my content in a strinfified JSON.
fetch("localhost/api/test", {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(body)
}).then(result => result.json()).then(
(result) => {
console.log(result);
}
);
I tried sending a FormData object built like this instead of the JSON.stringify(body).
let formData = new FormData();
for (var property in body) {
formData.append(property, body[property]);
}
But when I send this object instead of the stringified JSON I always get null for all the values in ASP.NET Core.
I also tried sending this:
URLSearchParams(data)
And this:
let formBody = [];
for (var property in details) {
var encodedKey = encodeURIComponent(property);
var encodedValue = encodeURIComponent(details[property]);
formBody.push(encodedKey + "=" + encodedValue);
}
formBody = formBody.join("&");
And I tried different combinations of headers with every type of data encoding:
No headers
'Content-Type': 'multipart/formdata'
'Content-Type': 'application/json'
'Content-Type': 'application/x-www-form-urlencoded'
I also tried getting the data from ASP.NET with both [FromBody] and [FromForm].
I think I have tried every possible combination of all the options I have explained above, with no result. I always get null values in my API.
Edit:
Right now, I am not even trying to send a file. I am trying to successfully send common data in the proper format before trying to attach a file. I don't know if I should change the title of the question.
This is my API code:
[HttpPost]
[Route("login")]
public object Login([FromBody] Credentials cred)
{
// check credentials
return CustomResult.Ok;
}
The class Credentials:
public class Credentials
{
public string Username { get; set; }
public string Password { get; set; }
}
The object body from React looks like this:
{
username: "user",
password: "pass"
}
My whole question was a mess and, in my case, the problem was the for loop. However, I will try to explain clearly what I needed because there is a lot of missleading information online about how to submit a form to an API with React.
Solution
Backend
In the backend, accept POST petitions and get the data with FromForm. If you need to use credentials, it is probably better to pass them through headers instead of putting them as hidden inputs inside every single form.
[HttpPost]
[Route("test")]
public object Test([FromHeader] string token, [FromForm] MyDataClass data)
{
// check token
// do something with data
return CustomResult.Ok;
}
If you bind an object, the properties must have the same name as the ones you are sending from the frontend and must have a public setter.
public class MyDataClass
{
public string SomeInfo { get; set; }
public string SomeOtherInfo { get; set; }
}
Frontend
Send the data as FormData. To do this, you can follow this tutorial. That way, you won't need to worry about handling input changes and formatting your data before submitting it.
However, if your data is already in a plain JavaScript object, you can transform it in a FormData as shown below.
let formData = new FormData();
Object.keys(data).forEach(function (key) {
formData.append(key, data[key]);
});
Nonetheless, this is very basic and if you have a complex object (with arrays, nested objects, etc) you will need to handle it differently.
Finally, I used fetch to call my API. If you use it, it is important to NOT set the Content-Type header, because you will be overwritting the one that fetch automatically chooses for you, which will be the right one in most cases.
fetch("localhost/api/test", {
method: 'POST',
headers: {
'Token': "dummy-token"
// DON'T overwrite Content-Type header
},
body: formData
}).then(result => result.json()).then(
(result) => {
console.log(result);
}
);
A bit more time now. (10 fish caught.)
I notice from your code you had used the header "multipart/formdata", It should have a hyphen; "multipart/form-data".
On my API I am specifying that it consumes the same type: [Consumes("multipart/form-data")]
I am not clear what the .net core defaults are and whether it should automatically de-serialise the form data but specifying it should rule out any ambiguity.
With my code I am sending a file with some parameters. On the api side it looks like:
public class FileUpload_Single
{
public IFormFile DataFile { get; set; }
public string Params { get; set; }
}
// POST: api/Company (Create New)
[HttpPost]
[Authorize(PermissionItem.SimulationData, PermissionAction.Post)]
[RequestSizeLimit(1024L * 1024L * 1024L)] // 1 Gb
[RequestFormLimits(MultipartBodyLengthLimit = 1024L * 1024L * 1024L)] // 1 Gb
[Consumes("multipart/form-data")]
public async virtual Task<IActionResult> Post([FromForm] FileUpload_Single data)
....
Then on the client side it looks like:
let formData = new FormData();
formData.append("DataFile", file);
formData.append("Params", JSON.stringify(params));
fetch(apicall, {
method: "POST",
mode: "cors",
headers: {
Authorization:
"Bearer " + (localStorage.getItem("token") || "").replace(/['"]+/g, ""),
Accept: "multipart/form-data",
},
body: formData,
})
With the file coming from
const changeHandler = (event) => {
setSelectedFile(event.target.files[0]);
setIsSelected(true);
};
(from my React component)

Error sending data with Ajax and receiving in controller

I am sending Json data but when I verify my method in my controller it arrives as null
html:
<div class="hijo">
#Html.LabelFor(m => m.Proyecto, new { #class = "", style = "color:#040404" })
#Html.DropDownListFor(Model => Model.ProyectoID, Model.Proyecto, "Seleccionar")
#Html.ValidationMessageFor(Model => Model.Proyecto, null, new { #class = "label label-danger", id = "Proyecto" })
</div>
<div class="hijo">
<input type="button" id="adicionar" value="Agregar" class="myButton">
</div>
JS:
$('#adicionar').click(function () {
debugger;
var data= {
Proyecto: $("#ProyectoID option:selected").text()
};
$.ajax({
url: "../ManejoDatos",
type: "POST",
dataType: "json",
data: JSON.stringify(data),
success: function (mydata) {
$("#UpdateDiv").html(mydata);
history.pushState('', 'New URL: ' + href, href); // This Code lets you to change url howyouwant
}
});
}
My Controller
public JsonResult ManejoDatos(string Proyecto)
{
........
...
return Json(Proyecto);
}
Console
I don't know if it's something very simple to fix but I don't see the error
regards
I found your JavaScript code is absolutely fine. The weird things is controller with your format ghostly doesn't work But I got success below way:
[System.Web.Http.HttpPost]
public HttpResponseMessage ManejoDatos(string Proyecto)
{
var data = " my test data ";
return Request.CreateResponse(HttpStatusCode.OK, data);
}
I will investigate that obviously and update the answer again .
Hope above idea help you to resolve your problem. Let me know if you encounter any more problem.
Simple types like string,int,float etc are to be fetched from query string in .Net framework where as complex types always fetched from body.
Solution-1
In this case .Net Framework is expecting string Proyecto from query parameters and you can call your API like this without making any change to your server side code
Note:Also you don't need to define key of the parameter (in http request) which is being defined as [From Body]. As total of one simple type can be passed using [FromBody] attribute which you can see below like this =ProyectoValue
POST ../ManejoDatos HTTP/1.1
Host: localhost:59353
Content-Type: application/x-www-form-urlencoded
=ProyectoValue
Solution-2
Or you can call your API by putting all your params in complex model and pass params through body instead of query parameters like this
Complex Type Model
public class MyModel
{
public string Proyecto{ get; set; }
}
API Code
public JsonResult ManejoDatos(MyModel myModel)
{
//Access using mymodel.Proyecto
........
...
return Json(mymodel.Proyecto);
}
And now you can consume your API by passing paramters through body like this
POST ../ManejoDatos HTTP/1.1
Host: localhost:59353
Content-Type: application/x-www-form-urlencoded
proyecto=myproyecto

All variables are null at the request with Axios to ASP.Net Core Controller

Here is my client request code:
import request from 'axios';
//...
let url = 'Login/SignIn',
headers = {
headers: {
'Content-Type': 'application/json'
}
},
data = JSON.stringify( {
name: 'danil',
password: 'pwd'
});
request.post(url, data, headers);
looks fine by the first glance.
Request is pending here:
But it all ended up like that in my controller:
Here's the code btw:
[HttpPost]
public async Task<ActionResult> SignIn([FromBody]string name, [FromBody]string password)
{
var userLoginCommand = new UserLogInCommand {
Login = name,
Password = password
};
await _dispatcher.DispatchAsync(userLoginCommand);
return Content(userLoginCommand.Result, "application/json");
}
Whats wrong with it? What did I forgot?
I tried to play around with JSON.stringify by adding it and removing, tried to not sending headers (and then it throws 415 error) but no changes there. Still got nulls.
UPD:
As Ali suggested in comments, passing data is goes fine if we use LoginModel for this like that:
public class LoginModel
{
public string name { get; set; }
public string password { get; set; }
}
But why it's not going work just like that in a simple way?
Only one parameter is allowed to read from the message body.
In your example you have two parameters with FromBody attribute, that's why you have null values.
Please find documentation here:
https://learn.microsoft.com/en-us/aspnet/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api
"At most one parameter is allowed to read from the message body."

Where exactly to put the antiforgeryToken

I have a layout page that has a form with AntiForgeryToken
using (Html.BeginForm(action, "Account", new { ReturnUrl = returnUrl }, FormMethod.Post, new { Id = "xcrf-form" }))
This generates a hidden field
<input name="__RequestVerificationToken" type="hidden" value="p43bTJU6xjctQ-ETI7T0e_0lJX4UsbTz_IUjQjWddsu29Nx_UE5rcdOONiDhFcdjan88ngBe5_ZQbHTBieB2vVXgNJGNmfQpOm5ATPbifYE1">
In my angular view (that is loaded in a div in the layout page, I do this
<form class="form" role="form" ng-submit="postReview()">
And my code for postReview() is as follows
$scope.postReview = function () {
var token = $('[name=__RequestVerificationToken]').val();
var config = {
headers: {
"Content-Type": "multipart/form-data",
// the following when uncommented does not work either
//'RequestVerificationToken' : token
//"X-XSRF-TOKEN" : token
}
}
// tried the following, since my other MVC controllers (non-angular) send the token as part of form data, this did not work though
$scope.reviewModel.__RequestVerificationToken = token;
// the following was mentioned in some link I found, this does not work either
$http.defaults.headers.common['__RequestVerificationToken'] = token;
$http.post('/Review/Create', $scope.reviewModel, config)
.then(function (result) {
// Success
alert(result.data);
}, function (error) {
// Failure
alert("Failed");
});
}
My MVC Create method is as follows
[HttpPost]
[ValidateAntiForgeryToken]
[AllowAnonymous]
public ActionResult Create([Bind(Include = "Id,CommentText,Vote")] ReviewModel reviewModel)
{
if (User.Identity.IsAuthenticated == false)
{
// I am doing this instead of [Authorize] because I dont want 302, which browser handles and I cant do client re-direction
return new HttpStatusCodeResult(HttpStatusCode.Forbidden);
}
// just for experimenting I have not yet added it to db, and simply returning
return new JsonResult {Data = reviewModel, JsonRequestBehavior = JsonRequestBehavior.AllowGet};
}
So no matter where I put the token, no matter what I use for 'Content-Type' (I tried application-json and www-form-urlencoded) I always get the error "The required anti-forgery form field "__RequestVerificationToken" is not present."
I even tried naming __RequestVerificationToken and RequestVerificationToken
Why does my server not find the damn token?
I also looked at couple of links that ask you to implement your own AntiForgeryToeknVerifyAttrbute and verify the token that is sent as cookieToken:formToken, I have not tried that but why I am not able to get it working whereas this works for the MVC controllers (non-angular posts)
Yes. By default, MVC Framework will check for Request.Form["__RequestVerificationToken"].
Checking the MVC source code
public AntiForgeryToken GetFormToken(HttpContextBase httpContext)
{
string value = httpContext.Request.Form[_config.FormFieldName];
if (String.IsNullOrEmpty(value))
{
// did not exist
return null;
}
return _serializer.Deserialize(value);
}
You need to create your own filter to check it from Request.Header
Code Snippet from Phil Haack's Article - MVC 3
private class JsonAntiForgeryHttpContextWrapper : HttpContextWrapper {
readonly HttpRequestBase _request;
public JsonAntiForgeryHttpContextWrapper(HttpContext httpContext)
: base(httpContext) {
_request = new JsonAntiForgeryHttpRequestWrapper(httpContext.Request);
}
public override HttpRequestBase Request {
get {
return _request;
}
}
}
private class JsonAntiForgeryHttpRequestWrapper : HttpRequestWrapper {
readonly NameValueCollection _form;
public JsonAntiForgeryHttpRequestWrapper(HttpRequest request)
: base(request) {
_form = new NameValueCollection(request.Form);
if (request.Headers["__RequestVerificationToken"] != null) {
_form["__RequestVerificationToken"]
= request.Headers["__RequestVerificationToken"];
}
}
public override NameValueCollection Form {
get {
return _form;
}
}
}
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class,
AllowMultiple = false, Inherited = true)]
public class ValidateJsonAntiForgeryTokenAttribute :
FilterAttribute, IAuthorizationFilter {
public void OnAuthorization(AuthorizationContext filterContext) {
if (filterContext == null) {
throw new ArgumentNullException("filterContext");
}
var httpContext = new JsonAntiForgeryHttpContextWrapper(HttpContext.Current);
AntiForgery.Validate(httpContext, Salt ?? string.Empty);
}
public string Salt {
get;
set;
}
// The private context classes go here
}
Check out here for MVC 4 implementation, to avoid salt issue
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class,
AllowMultiple = false, Inherited = true)]
public sealed class ValidateJsonAntiForgeryTokenAttribute
: FilterAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
var httpContext = filterContext.HttpContext;
var cookie = httpContext.Request.Cookies[AntiForgeryConfig.CookieName];
AntiForgery.Validate(cookie != null ? cookie.Value : null,
httpContext.Request.Headers["__RequestVerificationToken"]);
}
}
I had the same problem. Turned out that I don't need to set antiforgery token anywhere explicitly in my angular js code. The MVC controller expects this token value to be delivered from 1. the form field, 2. cookie. The filter equates and is happy when they match.
When we submit the form, hidden field for the anti forgery token automatically supplies its value. Cookie is automatically set by the browser. So as I said, we don't need to do anything explicitly.
The problem really is request's content-type. By default it goes as as application/json and therefore the a.f. token value (or rather any form data) is not received.
Following worked for me:
// create the controller
var RegisterController = function ($scope, $http) {
$scope.onSubmit = function (e) {
// suppress default form submission
e.preventDefault();
var form = $("#registerform");
if (form.valid()) {
var url = form.attr('action');
var data = form.serialize();
var config = {
headers: {
'Content-type':'application/x-www-form-urlencoded',
}
};
$http.post(url, data, config).success(function (data) {
alert(data);
}).error(function(reason) {
alert(reason);
});
}
};
};
As Murali suggested I guess I need to put the toekn in the form itself, so I tried putting the token as part of form data and I needed to encode the form data as explained in https://stackoverflow.com/a/14868725/2475810
This approach does not require any additional code on server side, also we do not need to create and join cookie and form token. Just by form-encoding the data and including token as one of the fields as explained in the answer above we can get it rolling.
You should perform the HTTP request in this way:
$http({
url: '/Review/Create',
data: "__RequestVerificationToken=" + token + "&param1=1&param2=2",
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest',
'Accept': 'application/json, text/javascript, */*; q=0.01',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
}
}).success(function(result) {
alert(result.data);
}).error(function(error) {
alert("Failed");
});

Response in file upload with Jersey and ExtJS

I have a method which save an image file in the database as a BLOB file. The method works fine, but when I get the callback in ExtJS filefield component, it always goes through failure function and I don't know what I have to respond to go through success function, this is my code:
Server method:
#POST
#Path("/upload")
#Consumes(MediaType.MULTIPART_FORM_DATA)
#Produces({ MediaType.APPLICATION_JSON })
public ServiceResponse uploadFile(#QueryParam("id") Long iconId, FormDataMultiPart form) {
CatIcon icon;
if (iconId != null) {
icon = catIconBean.getOne(iconId);
} else {
icon = new CatIcon();
}
byte[] image = form.getField("iconBmp").getValueAs(byte[].class);
if (image.length != 0) {
MultivaluedMap<String, String> headers = form.getField("iconBmp").getHeaders();
String type = headers.getFirst("Content-type");
List<String> list = Arrays.asList("image/gif", "image/png", "image/jpg", "image/jpeg",
"image/x-icon", "image/bmp");
if (list.contains(type)) {
icon.setIconBmp(image);
icon.setType(type);
}
}
icon.setDescription(form.getField("description").getValue());
icon.setFileName(form.getField("fileName").getValue());
icon = catIconBean.saveIcon(icon);
ServiceResponse sr = new ServiceResponse();
sr.httpResponse = true;
return sr;
}
What I have to return in the code above?
Client:
uploadIcon : function(item, e, eOpts) {
var me = this;
var form = this.getDetail().getForm();
var valid = form.isValid();
if (!valid) {
return false;
}
var values = form.getValues();
if(values) {
form.submit({
url : myApplication.defaultHost() + 'icon/upload?id=' + values.id,
waitMsg : 'Uploading...',
success : function(form, action) {
me.onCompleteSaveOrDelete();
},
failure : function(form, action) {
me.onCompleteSaveOrDelete();
}
});
}
},
I write the same function, me.onCompleteSaveOrDelete(), in both callback functions to make it be called, that's the method which I want to be called in success.
Greetings.
UPDATE:
I did almost the same Alexander.Berg answered. The only difference was that I write #Produces({ MediaType.APPLICATION_JSON }) instead of #Produces({ MediaType.TEXT_HTML }), because I need Json Response. But when I debug in chrome and check the response, I get this:
In failure:
failure : function(form, action) {
me.onCompleteSaveOrDelete();
}
In action param, within responseText:
"{"data":"{\"success\":true}","httpResponse":true,"totalCount":0}"
But It's still going through failure...I think I'm very close, any help??
Greetings.
The fileupload in Extjs is more tricky, because it is using iframe and submit, not a real ajax request for uploading files.
Try this on Server method:
#POST
#Path("/upload")
#Consumes(MediaType.MULTIPART_FORM_DATA)
#Produces(MediaType.TEXT_HTML)
public String uploadFile(#QueryParam("id") Long iconId, FormDataMultiPart form) {
(...)
JSONObject json = new JSONObject();
json.put("success", true);
json.put("msg", "Success");
return json.toString();
}
this is because the upload accepts Content-Type text/html,
see Example at http://docs.sencha.com/extjs/4.2.2/#!/api/Ext.form.field.File -> Example Usage -> Live Preview
Use Firefox browser with Firebug plugin and on Net tab the following URL -> http://docs.sencha.com/extjs/4.2.2/photo-upload.php
Response Headersview source
(...)
Content-Type text/html
(...)
Request Headersview source
Accept text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
(...)

Categories

Resources