SpringBoot Required request body is missing from browser - javascript

i have a POST request using springboot, everything works fine when i make tests on postman but when i try it from the browser i get this error,
Resolved [org.springframework.http.converter.HttpMessageNotReadableException: Required request body is missing: public org.springframework.
this is my code.
the Service class
#Transactional(rollbackFor = {SQLException.class})
public ResponseEntity<Message> save(Cursos cursos) {
Optional<Cursos> optionalCursos = cursosRepository.findByTituloCursos(cursos.getTituloCursos());
if (optionalCursos.isPresent()) {
return new ResponseEntity<>(new Message("la entrada ya existe", null), HttpStatus.BAD_REQUEST);
}
return new ResponseEntity<>(new Message("ok", false, cursosRepository.saveAndFlush(cursos)), HttpStatus.OK);
}
DTO class
public class CursosDTO {
long id;
#NotNull
String tituloCursos;
#NotNull
String cuerpocursos;
public CursosDTO() {
}
public CursosDTO(long id, String tituloCursos, String cuerpocursos) {
this.id = id;
this.tituloCursos = tituloCursos;
this.cuerpocursos = cuerpocursos;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getTituloCursos() {
return tituloCursos;
}
public void setTituloCursos(String tituloCursos) {
this.tituloCursos = tituloCursos;
}
public String getCuerpocursos() {
return cuerpocursos;
}
public void setCuerpocursos(String cuerpocursos) {
this.cuerpocursos = cuerpocursos;
}
}
controller class
#PostMapping("/")
public ResponseEntity<Message> save(#RequestBody CursosDTO cursosDTO) {
Cursos saveCursos = new Cursos(cursosDTO.getTituloCursos(), cursosDTO.getCuerpocursos());
return cursosService.save(saveCursos);
}
and this is my JavaScript code
fetch(url)
.then((response) => response.json())
.then((data) => {
console.log(data);
let dataUpd = {
tituloCursos: titulo,
cuerpocursos: contenido
};
console.log(JSON.stringify(dataUpd) + " prueba");
fetch(url, {
method: "POST",
BODY: dataUpd,
headers: {
"Content-Type": "application/json",
},
})
.then((res) => res.json())
.catch((error) => console.error("error al subir datos: ", error))
.then((response) => {
console.log("Datos subidos: ", response);
})
})
when i fetch data it brings all the data stored in the db correctly and this is the info that im trying to store
{"tituloCursos":"Stack","cuerpocursos":"<p>Overflown</p>"}
just in case it is relevant im using edge browser and im trying to store info from a rich text editor using tinymce

Probably a very simple typo. You capitalized BODY in your Post requests js code. It needs to be lowercase: body
See: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#supplying_request_options
Also, you would need to JSON.stringify your Data so your request options for the Post request would look like this:
method: "POST",
body: JSON.stringify(dataUpd),
headers: {
"Content-Type": "application/json",
},

Related

Getting HttpCode 415 error of Unsupported media type from ASP.NET Core API endpoint

I am trying to call an asp.net core 5 API from a react code but I keep getting the error 415 from the server.
Here is my server endpoint where the frontend is trying to call
public class OauthToken
{
public string TokenId;
}
[AllowAnonymous]
[HttpPost("signin-google")]
[Consumes("application/json")]
public async Task<IActionResult> GoogleLogin(OauthToken userView)
{
....
}
And the frontend code is as follows:
const googleResponse = (response) => {
const options = {
method: 'POST',
body: { TokenId: response.tokenId },
mode: 'no-cors',
accepts: "application/json",
cache: 'default',
contentType : "application/json",
}
fetch(config.GOOGLE_AUTH_CALLBACK_URL, options)
.then(r => {
console.log(r)
})
.catch(e=>{
console.log(e)
})
}
Below is my startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDatabaseDeveloperPageExceptionFilter();
services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = false)
.AddRoles<ApplicationRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
services.AddCors(opts =>
{
opts.AddPolicy("AllowAll", builder =>
{
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
//.AllowCredentials();
});
});
services.AddAuthentication()
.AddIdentityServerJwt()
.AddJwtBearer(cfg =>
{
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
cfg.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["AppSettings:JwtSecret"])),
ValidateIssuer = false,
ValidateAudience = false
};
})
.AddGoogle(opt =>
{
opt.ClientId = "MY_CLIENT_ID";
opt.ClientSecret = "MY_CLIENT_SECRET";
opt.SignInScheme = IdentityConstants.ExternalScheme;
})
//.AddTwitter(twitterOptions => { })
.AddFacebook(facebookOptions => {
facebookOptions.AppId = Configuration["Authentication:Facebook:AppId"];
facebookOptions.AppSecret = Configuration["Authentication:Facebook:AppSecret"];
});
services.AddControllersWithViews();
services.AddRazorPages();
// In production, the React files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/build";
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSpaStaticFiles();
app.UseRouting();
app.UseCors("AllowAll");
app.UseAuthentication();
app.UseIdentityServer();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "/api/v1/{controller}/{action=Index}/{id?}");
endpoints.MapRazorPages();
});
app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseReactDevelopmentServer(npmScript: "start");
}
});
}
I don't know where the issue is coming from.
Please help me resolve this issue.
Thank you.
Based on your comment I have tested and reproduce your issue as you can have a look below:
Reproduced Issue:
How do I call the endpoint to get the data get to it?:
If you could have a look into your client-side code and HTTP Verb it should sent request within the FromBody but you are not sending that way, so you have two way to achieve that:
Way 1 : Set [FromBody] On Method:
[AllowAnonymous]
[HttpPost("signin-google")]
[Consumes("application/json")]
public async Task<IActionResult> GoogleLogin([FromBody] OauthToken userView)
{
return Ok();
}
Output:
Way 2 : Set TokenId as string on Method:
[AllowAnonymous]
[HttpPost("signin-google")]
[Consumes("application/json")]
public async Task<IActionResult> GoogleLogin(string TokenId)
{
return Ok();
}
Output:
Note: So you could try above steps to call your API endpoint accordingly. I noticed that problem was in API routing and method argurment additionally, I would suggest you to have a look on our
offical docs for indepth insight here
Hope it would guide you as expected and help to resolve the issue.
I finally got the code working after making the following changes:
I noticed that the TokenId property of OathToken class had no getter and no setter. So, I updated it as follows:
public class OauthToken
{
//added {get; set;}
public string TokenId { get; set; }
}
Changed the body of the fetch request from an object to a blob as follows:
const tokenBlob = new Blob([JSON.stringify({ TokenId: response.tokenId }, null, 2)], { type: 'application/json' });
Changed the mode of the request from "no-cors" to "cors" since Cors is already declared at the startup.cs class for the project
So, the updated working fetch request is as follows:
const googleResponse = (response) => {
const tokenBlob = new Blob([JSON.stringify({ TokenId: response.tokenId }, null, 2)], { type: 'application/json' });
const options = {
method: 'POST',
body: tokenBlob,
mode: 'cors',
accepts: "application/json",
cache: 'default',
contentType : "application/json",
}
fetch(config.GOOGLE_AUTH_CALLBACK_URL, options)
.then(r => {
r.json().then(user => {
console.log(user.tokenId);
});
})
.catch(e=>{
console.log(e)
})
}
And the code at the endpoint is as follows:
public class OauthToken
{
public string TokenId { get; set; }
}
[AllowAnonymous]
[Consumes("application/json")]
[HttpPost("signin-google")]
public async Task<IActionResult> GoogleLogin([FromBody] OauthToken clientToken)
{
return Ok(clientToken);
}
I guess this might help someone else facing such issues.
Thank you

Controller method won't got parameters

I made a post ajax request to server side.
the request reached to the server but the recieved parameters are empty/null.
no idea why this is happening, the problem is probably in the server. tried many solutions but nothing changed.
I hope some of you can help me.
fetch('Home/AddMovie', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
movie: data_object
}),
}).then(res => res.text())
.then(text => {
})
.catch((error) => {
console.error('Error:', error);
});
[HttpPost]
public void AddMovie(Movie movie)
{
var movies = new List<Movie>();
var newJson = "";
using (StreamReader r = new StreamReader(JsonFilePath))
{
string json = r.ReadToEnd();
movies = JsonConvert.DeserializeObject<List<Movie>>(json);
movies.Add(movie);
newJson = JsonConvert.SerializeObject(movies, Formatting.Indented);
}
System.IO.File.WriteAllText(JsonFilePath, newJson);
}
This will work out
public void AddMovie([FromBody] Movie movie) { }
Edit:
Remove JSON.stringify() Just assign the object itself

Why RedirectToAction after Fetch POST request does not return new View()?

I am using Asp.Net Core 2.2
I do not understand why, after Fetch POST call, my POST IActionResult does not redirect to another action and its view?
My Fetch in JS:
window.onload = function () {
var requestToken = document.getElementsByName("__RequestVerificationToken")[0].value
fetch('http://localhost:53801/User/Checkout', {
method: 'post',
headers: {
"X-ANTI-FORGERY-TOKEN": requestToken,
"Content-Type": "application/json",
"Accept": "application/json"
},
body: JSON.stringify({
token: 'sometoken',
payerID: 'someid',
amount: price,
users: usersQty,
years: yearsQty
})
}).catch(function (err) {
console.log(`error: ${err}`);
});
}
My POST Action:
[HttpPost]
[Authorize]
public async Task<IActionResult> Checkout([FromBody] TransactionCompletedModel transaction)
{
AppUser user = await _userManager.GetUserAsync(User);
if (user != null)
{
if (user.PremiumExpiring == null || user.PremiumExpiring < DateTime.UtcNow)
{
user.PremiumExpiring = DateTime.UtcNow.AddYears(1); //if not yet have full access
}
else
{
user.PremiumExpiring = user.PremiumExpiring.AddYears(1); //if already have full access
}
await _userManager.UpdateAsync(user);
}
return RedirectToAction(nameof(TransactionCompleted));
}
And method that just supposed to return new View, but it does not:
[Authorize]
public IActionResult TransactionCompleted()
{
return View();
}
After executing Fetch call I receive in browsers console:
XHR GET http://localhost:53801/User/TransactionCompleted [HTTP/1.1 200 OK 724ms]
As I understand, there is nothing wrong with my RedirectToAction, so why public IActionResult TransactionCompleted() does not return / reload new view, just I stuck with the old view, where Fetch call was executed?
After advices from Christoph Lütjen I had to modify my controller POST action to return URL:
[HttpPost]
[Authorize]
public async Task<IActionResult> Checkout([FromBody] TransactionCompletedModel transaction)
{
//other rows without change
return Ok("TransactionCompleted?token=" + transaction.Token);
}
And now my Fetch API call takes response from POST call to Checkout, and uses it for redirection:
var requestToken = document.getElementsByName("__RequestVerificationToken")[0].value
fetch('http://localhost:53801/User/Checkout', {
method: 'post',
headers: {
"X-ANTI-FORGERY-TOKEN": requestToken,
"Content-Type": "application/json",
"Accept": "application/json"
},
body: JSON.stringify({
token: 'sometoken',
payerID: 'someid',
amount: price,
users: usersQty,
years: yearsQty
})
})
.then((response) => { return response.json(); })
.then((result) => { window.location = result })
.catch(function (err) {
console.log(`error: ${err}`);
});

How to test Post

This is a dotnet asp core 3 react application.
My startup.cs looks like this:
public class JwtAuthentication
{
public string SecurityKey { get; set; }
public string ValidIssuer { get; set; }
public string ValidAudience { get; set; }
public SymmetricSecurityKey SymmetricSecurityKey => new SymmetricSecurityKey(Convert.FromBase64String(SecurityKey));
public SigningCredentials SigningCredentials => new SigningCredentials(SymmetricSecurityKey, SecurityAlgorithms.HmacSha256);
}
public class ConfigureJwtBearerOptions : IPostConfigureOptions<JwtBearerOptions>
{
private readonly IOptions<JwtAuthentication> _jwtAuthentication;
public ConfigureJwtBearerOptions(IOptions<JwtAuthentication> jwtAuthentication)
{
_jwtAuthentication = jwtAuthentication ?? throw new System.ArgumentNullException(nameof(jwtAuthentication));
}
public void PostConfigure(string name, JwtBearerOptions options)
{
var jwtAuthentication = _jwtAuthentication.Value;
options.ClaimsIssuer = jwtAuthentication.ValidIssuer;
options.IncludeErrorDetails = true;
options.RequireHttpsMetadata = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateActor = true,
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = jwtAuthentication.ValidIssuer,
ValidAudience = jwtAuthentication.ValidAudience,
IssuerSigningKey = jwtAuthentication.SymmetricSecurityKey,
NameClaimType = ClaimTypes.NameIdentifier
};
}
}
public class Startup
{
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
var connectionStringOs =
"Server=xx.xx.xx.xxIntegrated Security=false;Trusted_Connection=false;Database=Options;User Id=xx;Password=xx;Connection Timeout=60";
var connectionStringDs =
"Server=xx.xx.xx.x;Integrated Security=false;Trusted_Connection=false;Database=DY;User Id=xx;Password=xx";
services.AddDbContext<OptionsDbContext>(o =>
o.UseSqlServer(connectionStringOs));
services.AddDbContext<DYDbContext>(o =>
o.UseSqlServer(connectionStringDs));
//services.AddRazorPages();
services.AddMvc();
services.AddMvc(option => option.EnableEndpointRouting = false);
services.Configure<JwtAuthentication>(Configuration.GetSection("JwtAuthentication"));
// I use PostConfigureOptions to be able to use dependency injection for the configuration
// For simple needs, you can set the configuration directly in AddJwtBearer()
services.AddSingleton<IPostConfigureOptions<JwtBearerOptions>, ConfigureJwtBearerOptions>();
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
//public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
public void Configure(IApplicationBuilder app,
//IHostingEnvironment env,
IHostEnvironment env,
OptionsDbContext optionsDbContext)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
//app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions
//{
// HotModuleReplacement = true
//});
}
app.UseStaticFiles();
/// Endpoint aware middleware.
// Middleware can use metadata from the matched endpoint.
//app.UseCookiePolicy();
app.UseAuthorization();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id:int?}");
});
}
}
I add a controller to get a token:
using System;
using System.Linq;
using System.Security.Claims;
using System.IdentityModel.Tokens.Jwt;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Options;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using OptionsAPI.Entities;
[Route("user/[controller]")]
public class UserController : Controller
{
private readonly IOptions<JwtAuthentication> _jwtAuthentication;
public UserController(IOptions<JwtAuthentication> jwtAuthentication)
{
_jwtAuthentication = jwtAuthentication ?? throw new ArgumentNullException(nameof(jwtAuthentication));
}
[HttpPost]
[AllowAnonymous]
public IActionResult GenerateToken([FromBody]GenerateTokenModel model)
{
// TODO use your actual logic to validate a user
if (model.Password != "654321")
return BadRequest("Username or password is invalid");
var token = new JwtSecurityToken(
issuer: _jwtAuthentication.Value.ValidIssuer,
audience: _jwtAuthentication.Value.ValidAudience,
claims: new[]
{
// You can add more claims if you want
new Claim(JwtRegisteredClaimNames.Sub, model.Username),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
},
expires: DateTime.UtcNow.AddDays(30),
notBefore: DateTime.UtcNow,
signingCredentials: _jwtAuthentication.Value.SigningCredentials);
return Ok(new
{
token = new JwtSecurityTokenHandler().WriteToken(token)
});
}
public class GenerateTokenModel
{
[Required]
public string Username { get; set; }
[Required]
public string Password { get; set; }
}
}
I have an html file to test this:
<script type="text/javascript">
const response = await fetch("http://www.awebsite.com/user/generatetoken", {
method: "POST",
body: JSON.stringify({
username: "foo#bar",
password: "654321"
}),
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
}
});
const json = await response.json();
const token = json.token;
console.log(token);
</script>
When I load this into a browser, nothing happens. Not sure how to test calling the controller that hands a token to be used by an API, or if this code is missing something to tie it all together?
The first thing is to use Fiddler or browser's developer tools to trace the request and check the error message . But keep in mind that in order to call a function using the await keyword, it must be within the async function :
async function postData(url = '', data = {}) {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
"Accept": "application/json"
},
body: JSON.stringify(data)
});
return await response.json();
}
postData('http://www.awebsite.com/user/generatetoken', {
username: "foo#bar",
password: "654321"
})
.then((data) => {
console.log(data.token);
});
Or using :
fetch('http://www.awebsite.com/user/generatetoken', {
method: "POST",
body: JSON.stringify({
username: "foo#bar",
password: "654321"
}),
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
}
})
.then(response => response.json()).then(data => {
alert(data.token)
});
Take care of other potential issues like CORS .

in Vue PUT POST DELETE method showing not allowed when middlewear included

I am trying to implepement vue in my laravel application. Simple CRUD. The fetching and showing of data is working fine, but edit, delete, add is not. It is working if there is no auth checking middlewear in feedController. Need to make it work while the middlewear included.
Here is my add method:
fetch('api/feed', {
method: 'post',
body: JSON.stringify(this.feed),
headers: {
'content-type': 'application/json'
}
})
.then(res => res.json())
.then( data=> {
this.feed.title = '';
this.feed.body = '';
alert('Feed Added');
this.fetchFeeds();
})
Here is my delete method:
deleteFeed(id){
if (confirm('Are you sure you want to delete?')){
fetch(`api/feed/${id}`, {
method: 'delete'
})
.then(res => res.json())
.then(data=> {
alert('Article Removed');
this.fetchFeeds();
})
.catch(err => console.log(err));
}
}
Here are the routes in api.php
/Create new feed
Route::post('feed', 'FeedController#store');
//Update feed
Route::put('feed', 'FeedController#store');
//Delete feed
Route::delete('feed/{id}', 'FeedController#destroy');
And finally here is the controller's functions where the middlewear is.
For adding:
public function store(Request $request)
{
$feed = $request->isMethod('put')?Feeds::findOrFail($request->feed_id):new Feeds;
$feed->id= $request->input('feed_id');
$feed->title= $request->input('title');
$feed->body= $request->input('body');
// $feed->image= "Test Image String";
// $feed->link="Test Link String";
// $feed->user_id=4;
if($feed->save())
{
return new FeedResource($feed);
}
}
For deleting:
public function destroy($id)
{
$feed = Feeds::findOrFail($id);
if ($feed->delete()){
return new FeedResource($feed);
}
}

Categories

Resources