Servlet in AEM, body in POST request is lost - javascript

I created a custom tool in AEM and I wanted the custom tool to call a servlet(POST json).
The servlet was called but, request.getParameterMap() return empty map.
My servlet code is
#Component(
service=Servlet.class,
property={
Constants.SERVICE_DESCRIPTION + "=Custom Servlet",
"sling.servlet.methods=" + HttpConstants.METHOD_POST,
"sling.servlet.paths=" + "/apps/myapp/customServlet"
}
)
public class CustomServlet extends SlingAllMethodsServlet{
#Override
protected void doPost(SlingHttpServletRequest request, SlingHttpServletResponse response)
throws IOException {
String method = request.getMethod(); // method POST OK
Map map = request.getParameterMap(); // return map but empty
String name = request.getParameter("foo"); // also this return null
response.setStatus(SlingHttpServletResponse.SC_OK);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().print("{\"response message\" : \"" + foo + "\"}");
}
}
and my JS code(loaded to AEM custom tool page as a client library)
window.onload = function() {
var url = '/apps/myapp/customServlet';
var button = document.getElementById('btnSubmit');
button.addEventListener('click', event => {
var foo = document.getElementById('foo').value;
if (foo.length < 1){
alert("input required!!");
} else {
var requestData = {};
requestData.foo= foo;
console.log(requestData); // it is ok
postData(url,requestData).then(data => {console.log(data);}); // got {response message:null}
}
});
}
async function postData(url, data){
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data)
});
return response.json();
}
In addition, I deleted POST at filter methods in Adobe Granite CSRF Filter Config.
Do I need any other OSGi config or something wrong with my code to use Post Servlet in AEM?

To get payload from request body, you can use request.getReader()
For example:
String body = IOUtils.toString(request.getReader()); //apache commons io
Or use some json mapper to immediately get your java object
YourObject yourObject = new ObjectMapper().readValue(request.getReader(), YourObject.class); // jackson

I'm seeing
var requestData = {}; // creating an empty array
and you are posting this to the servlet.
I think you want to parse 'foo' from document.getElementById to the servlet.

Related

Send variables in url and object in Body using Fetch

I'm working with my project where using Asp.net with c# and Javascript.
I have my controller set up with c# where it accepts an object, and two additional variables. It is a HttpPost request, and it works fine until I added the additional variables. I've used console.log in my fetch json function to check that i truly get the desired values, which I am.
This fetch works fine with postman!
I've also tried swiching places on the Rented object, so that it is in the last position, if the Body of fetch was sent in last aswell, but it did not work.
This is what I get back when calling the function in my chrome browser:
https://localhost:44363/rent/save?movieId=2&maxRents=3 Status = 400
Feels like I'm missing something very basic here, maybe you can't send both variables and body at the same time with a request?
Here's the controller code:
[HttpPost("save")]
public ActionResult<Rented> SaveRent(Rented newLoan, int movieId, int maxRents)
{
var amountOfRent = _appDbContext.Rents.Where(m => m.MovieId == movieId);
if (maxRents <= amountOfRent.Count())
{
_appDbContext.Rents.Add(newLoan);
_appDbContext.SaveChanges();
return newLoan;
}
else
{
return BadRequest();
}
}
And here's the Fetch function:
function addToStudio(element) {
var movieId = element.id;
var studioId = element.value;
var maxRents = element.name;
var newId = Number(movieId);
var newStudioId = Number(studioId);
console.log(maxRents);
fetch('https://localhost:44363/rent/save?movieId=' + movieId + '&maxRents=' + maxRents, {
method: 'POST',
body: JSON.stringify({
studioId: newStudioId,
movieId: newId
}),
headers: {
"Content-type": "application/json; charset=UTF-8"
}
})
.catch(err => console.error("response-error", err))
.then(response => response.json())
.then(json => console.log(json))
.catch(err => console.error("json error", err))
container.innerHTML = `<h1>Saved</h1>`
}
I can see a couple of problems here:
You are using both the body of the request and in the URL query.
You're missing some important attributes in the control method signature .
I personally always try to use a single approach (either body or URL query). I find that it keeps the code more readable and is less bug-prone.
First of all, decide how you want to pass the data:
Through the request body
In the URL query
Mixed approach
Then write your methods accordingly.
In the body
The easiest approach is encapsulating your information in a .Net object and Jsonify its fields in the JavaScript:
public class RentDetails
{
public int MovieId {get; set;}
public int MaxRents {get; set;}
public Studio {get; set;} // you can also use objects, deserializing the Json will fill them as well.
}
Then you want to specify in the signature of your method that you are receiving a RentDetails in the body of the request.
[HttpPost("save")]
public ActionResult<Rented> SaveRent([FromBody] RentDetails rentDetails) { ... }
And lastly, you want the Jsonified body to reflect the object that should be received (pay attention to field names):
const request = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
movieId : movieID,
maxRent : maxRent,
studio:
{
// nested fields for nested objects
}
})
};
If you follow these steps, your controller will automatically deserialize the json in the body to the object and it will be ready for you to use.
In the URL Query
This is easier to handle but might grow to be messy.
Concatenate the fields in the javascript how you were already doing:
const link = baseLink.concat('?movieId=', movieId, '&maxRent=', maxRent, ...);
const result = await fetch(link); // no request is needed here, you already have all you need.
And then access those fields from the controller:
[HttpPost("save")]
public ActionResult<Rented> SaveRent() //<- no parameters are needed here
{
var allUrlKeyValues = HttpUtility.ParseQueryString(Request.QueryString.Value);
var movieId = int.Parse(allUrlKeyValues["movieId"]);
var maxRent = int.Parse(allUrlKeyValues["maxRent"]);
....
// as you see you can only pass strings in this way
}
Mixed Approach
Of course you can opt for a mixed approach, just use a combination of the code above.
Note that the simpler, the better ;).

Can i send java object in a json response

For example i have java class post:
public class Post{
String title;
String text;
}
If i create an instance of this class and convert it into ajax response in my servlet controller
#RestController
public class AjaxNewsController {
#JsonView(Views.Public.class)
#PostMapping(value = "/getPost")
public AjaxResponseBody getSearchResultViaAjax(#RequestBody AjaxPostResponse postId) {
AjaxResponseBody result = new AjaxResponseBody();
result.setCode("200");
result.setMsg("found POST");
result.setResult(post);
return result;
}
}
My question is: can i retrieve post fields title and text with javascript on a client side and if i can then how?
Here is an example of console with my response in browse
console
but how can i extract my post with fields in ajax and jquery?
UPD
after some reaserach i found that somehow my serlvet doesn't convert my java pojo into json. How should i do it?
UPD2
my request sends normaly but serlvet doesn't convert POST class into json.
here is my javascript :
function likePost(postId,ratingElem, ratingChange) {
var search = {}
search["postId"] = postId;
search["rating"] = ratingChange;
$.ajax({
type : "POST",
contentType : 'application/json; charset=utf-8',
dataType : 'json',
url : "likePost",
data : JSON.stringify(search),
timeout : 100000,
success : function(data) {
console.log("SUCCESS: ", data);
changeRating(ratingElem,data.post.getTopic());
},
error : function(e) {
console.log("ERROR: ", e);
changeRating(ratingElem,'error');
},
done : function(e) {
console.log("DONE");
enableSearchButton(true);
}
});
}
status and message is fine but result is empty.
try it:
var req = new XMLHttpRequest();
req.open('POST', 'your_url', false);
req.send(null);
if(req.status == 200)
dump(req.responseText);
and if you want to get data from another domain please read cors
So i solved my problem. I use com.fasterxml.jackson.core so i just need to mark fields in my class that i want to convert into json with #JsonView annotation like
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "post_id",
unique = true, nullable = false)
#JsonView(Views.Public.class)
private Integer postId;
Everything that marked will be converted.

.Net Core Web API not accepting a json string field value

I have a problem whereby I would like to pass a json string as a field value but I keep getting "The input was not valid". So to be clear I have an object in my front end that I use the below to pass to my API:
let j: Settings = {} as Settings;
j.user_settings_ID = object.user_settings_ID;
j.user_ID = object.user_ID;
j.user_filter = JSON.stringify(object.user_filter);
j.user_test_filter = JSON.stringify(object.user_test_filter);
fetch('api/Profile/UpdateProfileSettings/?id=' + object.user_settings_ID, {
method: 'put',
headers: {
'Content-Type': 'application/json; charset=utf-8',
'Authorization': 'Bearer ' + sessionStorage.getItem('token')
},
body: "'" + JSON.stringify(j) + "'",
}).then(data => {
if (!data.ok) {
alert("Failed");
}
}).catch((ex) => {
alert("Failed");
});
In my API I have:
[HttpPut("[action]")]
public async Task<string> UpdateProfileSettings(int id, [FromBody] string obj)
{
HttpClient clientRoute = new HttpClient();
var response = await clientRoute.PutAsync("https://something.com/api/UserSettings/put/" + id, new StringContent(obj, Encoding.UTF8, "application/json"));
var contents = await response.Content.ReadAsStringAsync();
return contents;
}
I don't have a problem when I set the j.user_filter and j.user_test_filter with any normal string, but I would like to put the 'jsonified' string as the value for the field, but the Web API doesn't like it for some reason (probably because it isn't seeing it as a string but a json object perhaps)
If someone could help I would be most grateful.
Ok after messing about with this for a long time, I came up with this 'solution'.
So as #Niladri pointed out the "'" before JSON.stringify(j) was a factor but was not the only thing that needed to be changed. The main problem was actually in the controller itself.
I had this previously in my controller:
public async Task<string> UpdateProfileSettings(int id,[FromBody] string obj)
{
HttpClient clientRoute = new HttpClient();
var response = await clientRoute.PutAsync("https://something.com/api/UserSettings/put/" + id, new StringContent(obj, Encoding.UTF8, "application/json"));
var contents = await response.Content.ReadAsStringAsync();
return contents;
}
But I had to change it to:
public async Task<string> UpdateProfileSettings(int id,[FromBody] object obj)
{
HttpClient clientRoute = new HttpClient();
var response = await clientRoute.PutAsync("https://something.com/api/UserSettings/put/" + id, new StringContent(obj.ToString(), Encoding.UTF8, "application/json"));
var contents = await response.Content.ReadAsStringAsync();
return contents;
}
Notice the change of [FromBody] string obj to [FromBody] object obj and also
changed StringContent(obj, Encoding.UTF8, "application/json")) to StringContent(obj.ToString(), Encoding.UTF8, "application/json"))
My previous method with "'" before JSON.stringify(j) works if your controller [FromBody] is of string type and you aren't looking to pump a string which looks like JSON into your controller.
I apologise if this is a bad explanation but tried my best and it worked for me
This is very similar to mcc20's own fix, but I didn't get that code to work. The 2.1 framework has Issues https://github.com/aspnet/Mvc/issues/7609 and https://github.com/aspnet/Mvc/issues/7799. I got this JSON post to complex class working in 2.1: client javascript is unchanged:
var payload = JSON.stringify({ "name": document.getElementById('firstname').value, "email": document.getElementById('email').value, "captcha": grecaptcha.getResponse() });
var oReq = new XMLHttpRequest();
oReq.ContentType = "application/json";
oReq.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
document.getElementById("update").innerHTML = this.responseText;
}
else document.getElementById("update").innerHTML = "not 4&200! : " + this.responseText;
};
oReq.open('POST', 'api/u');
oReq.send(payload);
and the controller has:
[Route("api/[controller]")]
// [ApiController] // works either way
public class UController : ControllerBase
{
[HttpPost]
public async Task<string> signUp()
{
String body;
using (StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8))
{
body = await reader.ReadToEndAsync();
}
UserSignup user = JsonConvert.DeserializeObject<UserSignup>(body);
return user.email;
}
}
I was trying something like ([FromBody] UserSignup user) getting Mvc.SerializableError and "The input was not valid." Soon to be on https://github.com/Hover-Us/

BreezeJs - Ajax Calls with Parameters, No Caching

BreezeJs' POST Ajax calls with parameters that I make to retrieve data are cached by the browser. The "cache: false" attribute does not work, neither does the $.ajaxSetup({ cache: false }); option. How do I prevent it from happening? And if, I need to make GET requests instead, how do I procede? Here is a portion of my code...
JS
var id = 100;
var serviceName = '/breeze/sample';
var ajaxImpl = breeze.config.getAdapterInstance('ajax');
ajaxImpl.ajax({
type: 'POST',
url: serviceName + '/getdata',
data: { dataid: id },
success: function(data) {
// Do something with data
}
});
ApiController
[HttpPost]
[Authorize("User")]
[ActionName("getdata")]
public object GetData(HttpRequestMessage request)
{
if (!IsAuthorized()) // Pre-defined function
throw new HttpResponseException(HttpStatusCode.Unauthorized);
var data = request.Content.ReadAsFormDataAsync().Result;
var dataId = data["dataid"];
var query = "sp_getdata #id"; // Pass parameter #id to stored procedure "sp_getdata"
var id = new SqlParameter("#id", dataId);
return unitOfWork.Context().ExecuteStoreQuery<GetData>(query, id).ToList();
}
Thanks in advance.
We run breeze with GET and POST.
Do you have a Global.asax? Add this to it... not sure if looking for /api/ or /breeze/ would be the only way, but adjust to your circumstances.
protected void Application_PreSendRequestHeaders(object sender, EventArgs e)
{
// Never Cache API (Web API or Breeze.Sharp) Data Requests
if (Request.RawUrl.Contains("/api/") || Request.RawUrl.Contains("/breeze/"))
Response.Cache.SetCacheability(HttpCacheability.NoCache);
}

How to download file from System.Web.HttpContext.Current.Response.BinaryWrite(byteArray); from javascript instead of C#

I have a C# method that works if you call it from another C# method, but not from javascript. How do I make it work from my ajax call?
I need to get a file based on an ID that is passed in, so I have an ajax post with the statusID. That ID is pulling the proper file in my C# method, it is just not giving the file save dialog.
However, if I call this from my C# page load method with a static statusID for testing purposes, it works just fine.
Here is the C# method:
public void Get_Attachment_By_StatusID(int statusID)
{
SqlDataReader _reader = null;
string _connString = "Data Source=133.31.32.33;Initial Catalog=Reports;Integrated Security=True";
string sql = "SELECT a.StatusID ,a.DocumentName ,a.MIMETypeID ,a.Binary ,a.CreatedDate ,a.CreatedBy " +
",b.MIMEType FROM Attachments a Inner join MIME_Types b on a.MIMETypeID = b.ID " +
"WHERE [StatusID] = {0} ";
sql = string.Format(sql, statusID);
try
{
_connection = new SqlConnection(_connString);
_connection.Open();
using (SqlCommand cmd = new SqlCommand(sql, _connection))
{
cmd.CommandType = System.Data.CommandType.Text;
_reader = cmd.ExecuteReader();
if (_reader.HasRows)
{
while (_reader.Read())
{
System.Web.HttpContext.Current.Response.ClearContent();
System.Web.HttpContext.Current.Response.ContentEncoding = System.Text.Encoding.UTF8;
System.Web.HttpContext.Current.Response.ContentType = _reader["MIMEType"].ToString();
System.Web.HttpContext.Current.Response.AddHeader("Content-Disposition", "attachment; filename=" + _reader["DocumentName"].ToString() + ";");
byte[] b = (byte[])_reader["Binary"];
System.Web.HttpContext.Current.Response.BinaryWrite(b);
System.Web.HttpContext.Current.Response.Flush();
System.Web.HttpContext.Current.Response.Close();
}
}
_reader.Close();
_connection.Close();
}
}
catch (Exception ex)
{
//Log exception to error Logging app
string err = ex.ToString();
}
finally
{
if (_connection != null)
_connection.Close();
if (_reader != null)
_reader.Close();
}
}
Here is how I am calling it from my page, in javascript:
function GetFile(statusID) {
var url = '/Home/Get_Attachment_By_StatusID';
$.ajax({
url: url,
type: 'post',
cache: false,
data: JSON.stringify({ "statusID": statusID }),
contentType: 'application/json',
success: function (data) {
}
});
}
Nothing happens. In Chrome, I don't see anything in the javascript console, and in IE, my console spits this out: "XML5619: Incorrect document syntax."
Again, if I go into the controller, and call the method in my page load method, it presents the save file dialog and saves the file just fine. So I must be doing something wrong with my javascript/jquery/ajax...
I am new to MVC4 and know I'm missing something here. What am I missing?
Use window.open('CONTROLLER_URL/STATUS_ID'); instead of an AJAX request.
<a target="_blank" href="javascript:window.open('/Home/Get_Attachment_By_StatusID/12345');">Test</a>
Here's one suggestion, largely based on answer from LastCoder:
Decorate your action with the [HttpGet] attribute and change the parameter name to id:
[HttpGet]
public void Get_Attachment_By_StatusID(int id) ...
Now in your client-side code, simply do this:
function GetFile(statusID) {
var url = '/Home/Get_Attachment_By_StatusID/'+statusID;
window.location=url;
}

Categories

Resources