Spring MVC 3.2 Backbone.js Post - google-app-engine

I'm working on a Google App engine web application with Backbone.js and Spring mvc. I have the following backbone.js code:
var PostModel = Backbone.Model.extend({
defaults: {
"title": "",
"postTxt": ""
}
});
var PostCollection = Backbone.Collection.extend({
url: '/createUserPost',
model: PostModel
});
var postcollection = new PostCollection();
function createPost() {
var postview = new PostView();
postcollection.create({ title: $('#title').val(), postTxt: $('#postTxt').val() });
title: $('#title').val('');
post: $('#postTxt').val('');
}
Spring mvc 3.2 code on the backend:
#RequestMapping(value = "/createUserPost", method=RequestMethod.POST)
public #ResponseBody UserPosts createUserPost( #ModelAttribute(value="post") UserPosts post,
#CookieValue(value = "sessionId", defaultValue = "null") String sessionId) {
//my custom method to get user name by querying the datastore
String author = getAuthorFromSessionId(sessionId);
PersistenceManager pm = PMF.get().getPersistenceManager();
try {
post.setAuthor(author);
pm.makePersistent(post);
}
finally {
pm.close();
}
return post;
}
When post button is clicked, The text is added to the HTML correctly and POST request is made to the server and the status code is 200 OK.
Spring controller is not able to read the request parameters. Alternatively, I tried using HttpServletRequest and request.getParameter("title") without autobinding, but value returned was still null.
Data sent in the request:
{title: "kdkldklfd", postTxt: "kkfdlksffkl"}
Response received:
{"title":null,"author":"admin","postTxt":null}

#RequestMapping(value = "/createUserPost", method=RequestMethod.POST,
consumes = "application/json", produces = "application/json")
public #ResponseBody UserPosts createUserPost(#RequestBody UserPosts post) {
The #RequestBody annotation should be used in place of #ModelAttribute to read the Request parameters.

Related

Passing String from angular to Spring using #Requestbody

I am working on a project using angularjs+springboot. Am trying to send email via my application using spring-boot-starter-mail. The message and object of the email are written by the user in a form. what I want to do is to get the message and object values in my RestController using #RequestBody.
the function in my service.js
// send mail
var sendMail = function(id, objet, msg) {
var deferred = $q.defer();
$http.post(urlBase + id, objet, msg).then(
function(response) {
deferred.resolve(response.data);
}, function(errResponse) {
console.error('Error while sending email');
deferred.reject(errResponse);
});
return deferred.promise;
}
the method in my restContoller
#RestController
public class EmailController {
#Autowired
private JavaMailSender javaMailSender;
#Autowired
UtilisateurService service;
#RequestMapping(value = "/users/{id}", method = RequestMethod.POST)
public ResponseEntity<Void> sendMail(#PathVariable("id") int id, #RequestBody String objet,
#RequestBody String msg) {
Utilisateur currentUser = service.findById(id);
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(currentUser.getEmailUtil());
message.setSubject(objet);
message.setText(msg);
javaMailSender.send(message);
return new ResponseEntity<Void>(HttpStatus.OK);
}}
This throws this exception :
Failed to read HTTP message: org.springframework.http.converter.HttpMessageNotReadableException: Required request body is missing: public org.springframework.http.ResponseEntity<java.lang.Void> com.sla.utilisateur.controller.EmailController.sendMail(int,java.lang.String,java.lang.String)
How can I fix it?
thank you,
Your usage of $http.post is not correct. You should have a look at the AngularJS POST documentation. $http.post arguments are the following:
post(url, data, [config]);
AngularJS sends the data by default in JSON. So you should send the request using the following statement (for example):
$http.post(urlBase + id, {subject:objet, body:msg})
And in your controller you should define only one #RequestBody maps for the ease of the example to a Map (You could change it to a POJO. ):
#RequestMapping(value = "/users/{id}", method = RequestMethod.POST)
public ResponseEntity<Void> sendMail(#PathVariable("id") int id, #RequestBody Map<String,String> msg) {
Utilisateur currentUser = service.findById(id);
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(currentUser.getEmailUtil());
message.setSubject(msg.get("subject");
message.setText(msg.get("body"));
javaMailSender.send(message);
return new ResponseEntity<Void>(HttpStatus.OK);
}}

Get a custom header in angular $http from web api controller

I am trying to pass a piece of information along with my content in my HttpResponseMessage like:
string jsonFiles = JsonConvert.SerializeObject(listFiles);
HttpResponseMessage response = new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent(jsonFiles)
};
response.Headers.Add("Key", "Value");
return response;
However in my angular call and response I cannot see the "Key" header in response.config.headers or response.headers. Any idea why?
$http.get("/api/Locker").then(function (response) {
console.log(response);
console.log(response.headers);
console.log(response.config.headers);
});
In my Startup.cs I do have:
app.UseCors(CorsOptions.AllowAll);
app.UseWebApi(config);
As per the documentation response.config returns the config used while sending the request. response.headers is a function. Try using response.headers("Key") and see if it helps.
You have to explicitly add the custom header to your CORS policy, or AngularJS will be unable to read it. Change this:
app.UseCors(CorsOptions.AllowAll);
To this:
var policy = new CorsPolicy
{
AllowAnyHeader = true,
AllowAnyMethod = true,
AllowAnyOrigin = true,
SupportsCredentials = true
};
policy.ExposedHeaders.Add("MyHeader");
app.UseCors(new CorsOptions
{
PolicyProvider= new CorsPolicyProvider
{
PolicyResolver = c => Task.FromResult(policy)
}
});
I am using WebApi version 5.2.3. To export a response header, you can try creating a custom attribute, for example
public class CustomHeadersAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
actionExecutedContext.Response.Headers.Add("Access-Control-Expose-Headers", "<Your Custom Key>");
base.OnActionExecuted(actionExecutedContext);
}
}
Then on your controller or wherever you need it, just add
[CustomHeaders]
public HttpResponseMessage GetMethod() { ... }

Redirect to Identity Server Login page from AngularJs http web api request

I am trying to redirect to Identity Server's default login page when calling an API controller method from Angular's $http service.
My web project and Identity Server are in different projects and have different Startup.cs files.
The web project Statup.cs is as follows
public class Startup
{
public void Configuration(IAppBuilder app)
{
AntiForgeryConfig.UniqueClaimTypeIdentifier = Thinktecture.IdentityServer.Core.Constants.ClaimTypes.Subject;
JwtSecurityTokenHandler.InboundClaimTypeMap = new Dictionary<string, string>();
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies",
});
var openIdConfig = new OpenIdConnectAuthenticationOptions
{
Authority = "https://localhost:44301/identity",
ClientId = "baseballStats",
Scope = "openid profile roles baseballStatsApi",
RedirectUri = "https://localhost:44300/",
ResponseType = "id_token token",
SignInAsAuthenticationType = "Cookies",
UseTokenLifetime = false,
Notifications = new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = async n =>
{
var userInfoClient = new UserInfoClient(
new Uri(n.Options.Authority + "/connect/userinfo"),
n.ProtocolMessage.AccessToken);
var userInfo = await userInfoClient.GetAsync();
// create new identity and set name and role claim type
var nid = new ClaimsIdentity(
n.AuthenticationTicket.Identity.AuthenticationType,
Thinktecture.IdentityServer.Core.Constants.ClaimTypes.GivenName,
Thinktecture.IdentityServer.Core.Constants.ClaimTypes.Role);
userInfo.Claims.ToList().ForEach(c => nid.AddClaim(new Claim(c.Item1, c.Item2)));
// keep the id_token for logout
nid.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));
// add access token for sample API
nid.AddClaim(new Claim("access_token", n.ProtocolMessage.AccessToken));
// keep track of access token expiration
nid.AddClaim(new Claim("expires_at", DateTimeOffset.Now.AddSeconds(int.Parse(n.ProtocolMessage.ExpiresIn)).ToString()));
// add some other app specific claim
nid.AddClaim(new Claim("app_specific", "some data"));
n.AuthenticationTicket = new AuthenticationTicket(
nid,
n.AuthenticationTicket.Properties);
n.Request.Headers.SetValues("Authorization ", new string[] { "Bearer ", n.ProtocolMessage.AccessToken });
}
}
};
app.UseOpenIdConnectAuthentication(openIdConfig);
app.UseResourceAuthorization(new AuthorizationManager());
app.Map("/api", inner =>
{
var bearerTokenOptions = new IdentityServerBearerTokenAuthenticationOptions
{
Authority = "https://localhost:44301/identity",
RequiredScopes = new[] { "baseballStatsApi" }
};
inner.UseIdentityServerBearerTokenAuthentication(bearerTokenOptions);
var config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
inner.UseWebApi(config);
});
}
}
You will notice that the API is secured with bearer token authentication, whereas the rest of the app uses OpenIdConnect.
The Identity Server Startup.cs class is
public class Startup
{
public void Configuration(IAppBuilder app)
{
var policy = new System.Web.Cors.CorsPolicy
{
AllowAnyOrigin = true,
AllowAnyHeader = true,
AllowAnyMethod = true,
SupportsCredentials = true
};
policy.ExposedHeaders.Add("Location");
app.UseCors(new CorsOptions
{
PolicyProvider = new CorsPolicyProvider
{
PolicyResolver = context => Task.FromResult(policy)
}
});
app.Map("/identity", idsrvApp =>
{
idsrvApp.UseIdentityServer(new IdentityServerOptions
{
SiteName = "Embedded IdentityServer",
SigningCertificate = LoadCertificate(),
Factory = InMemoryFactory.Create(
users: Users.Get(),
clients: Clients.Get(),
scopes: Scopes.Get())
});
});
}
X509Certificate2 LoadCertificate()
{
return new X509Certificate2(
string.Format(#"{0}\bin\Configuration\idsrv3test.pfx", AppDomain.CurrentDomain.BaseDirectory), "idsrv3test");
}
}
Notice that I have added a CorsPolicy entry in order to allow the Web App to hopefully redirect to the Login page. In addition, the Cors policy exposes the Location request header, since it contains the url that I would like to redirect to.
The Web Api controller method is secured using the Authorize Attribute, like so
[HttpPost]
[EnableCors(origins: "*", headers: "*", methods: "*")]
[Authorize]
public PlayerData GetFilteredPlayers(PlayerInformationParameters parameters)
{
var playerInformation = composer.Compose<PlayerInformation>().UsingParameters(parameters);
var players = playerInformation.Players
.Select(p => new {
p.NameLast,
p.NameFirst,
p.Nickname,
p.BirthCity,
p.BirthState,
p.BirthCountry,
p.BirthDay,
p.BirthMonth,
p.BirthYear,
p.Weight,
p.Height,
p.College,
p.Bats,
p.Throws,
p.Debut,
p.FinalGame
});
var playerData = new PlayerData { Players = players, Count = playerInformation.Count, Headers = GetHeaders(players) };
return playerData;
}
The angular factory makes a call to $http, as shown below
baseballApp.factory('playerService', function ($http, $q) {
return {
getPlayerList: function (queryParameters) {
var deferred = $q.defer();
$http.post('api/pitchingstats/GetFilteredPlayers', {
skip: queryParameters.skip,
take: queryParameters.take,
orderby: queryParameters.orderby,
sortdirection: queryParameters.sortdirection,
filter: queryParameters.filter
}).success(function (data, status) {
deferred.resolve(data);
}).error(function (data, status) {
deferred.reject(status);
});
return deferred.promise;
}
}});
When this call occurs, the response status is 200, and in the data, the html for the login page is returned.
Moreover, I can see on Chrome's Network tab that the response has a Location header with the url of the Login page. However, if I set up an http interceptor, I only see the Accept header has been passed to the javascript.
Here are the http headers displayed in Chrome's network tab:
The response does not have the Access-Control-Allow-Origin header for some reason.
So I have the following questions:
Is there a way I could get access to the Location header of the response in the angular client code to redirect to it?
How might I be able to get the server to send me a 401 instead of 200 in order to know that there was an authentication error?
Is there a better way to do this, and if so, how?
Thanks for your help!
EDIT:
I have added a custom AuthorizeAttribute to determine what http status code is returned from the filter.
The custom filter code
public class BearerTokenAutorizeAttribute : AuthorizeAttribute
{
private const string AjaxHeaderKey = "X-Requested-With";
private const string AjaxHeaderValue = "XMLHttpRequest";
protected override void HandleUnauthorizedRequest(System.Web.Http.Controllers.HttpActionContext actionContext)
{
var headers = actionContext.Request.Headers;
if(IsAjaxRequest(headers))
{
if (actionContext.RequestContext.Principal.Identity.IsAuthenticated)
actionContext.Response.StatusCode = System.Net.HttpStatusCode.Forbidden;
else
actionContext.Response.StatusCode = System.Net.HttpStatusCode.Unauthorized;
}
base.HandleUnauthorizedRequest(actionContext);
var finalStatus = actionContext.Response.StatusCode;
}
private bool IsAjaxRequest(HttpRequestHeaders requestHeaders)
{
return requestHeaders.Contains(AjaxHeaderKey) && requestHeaders.GetValues(AjaxHeaderKey).FirstOrDefault() == AjaxHeaderValue;
}
I have observed two things from this: first, the X-Requested-With header is not included in the request generated by the $http service on the client side. Moreover, the final http status returned by the base method is 401 - Unauthorized. This implies that the status code is changed somewhere up the chain.
Please don't feel like you have to respond to all the questions. Any help would be greatly appreciated!
You have probably configured the server correctly since you are getting
the login page html as a response to the angular $http call -> it is
supposed to work this way:
angularjs $http
Note that if the response is a redirect, XMLHttpRequest will transparently follow it, meaning that the outcome (success or error) will be determined by the final response status code.
You are getting a 200 OK response since that is the final response as the redirect is instantly followed and it's result resolved as the $http service outcome, also the response headers are of the final response
One way to achieve the desired result - browser redirect to login page:
Instead of redirecting the request server side (from the web project to the Identity Server) the web api controller api/pitchingstats/GetFilteredPlayer could return an error response (401) with a json payload that contains a {redirectUrl: 'login page'} field or a header that could be read as response.headers('x-redirect-url')
then navigate to the specified address using window.location.href = url
Similar logic can often be observed configured in an $httpInterceptors that handles unauthorized access responses and redirects them to the login page - the redirect is managed on the client side

Web API 405 Error with $http.post

I'm receiving a 405 error with a POST request using $http.post. What's weird is that I'm using $http.post in another area of my application and it works just fine.
I'm using AngularJS for client side, and Web API for server side. I've posted all relevant information (apart from my web.config) that I can think of. Is there something very obvious I'm missing here?
code below does not work (throws 405)
Here's the api controller method that I'm trying to hit:
public async Task<IHttpActionResult> LinkLogin(string provider)
{
Account user = await _repo.FindByNameAsync(User.Identity.Name);
if (user == null)
{
return BadRequest("User does not exist!");
}
return new ChallengeResult(provider, null, "auth/Manage/LinkLoginCallback", user.Id);
}
Here's how I'm trying to hit it on the client side:
var _linkLogin = function (provider) {
$http.post(serviceBase + 'auth/Manage/LinkLogin', provider).then(function (response) {
return response;
});
};
CODE BELOW IS CODE THAT WORKS
Api controller function that works:
// POST auth/Authorization/Register
[AllowAnonymous]
[Route("Register")]
public async Task<IHttpActionResult> Register(UserModel userModel)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
IdentityResult result = await _repo.RegisterUser(userModel);
IHttpActionResult errorResult = GetErrorResult(result);
if (errorResult != null)
{
return errorResult;
}
return Ok();
}
Calling it from the client side:
var _saveRegistration = function (registration) {
_logOut();
return $http.post(serviceBase + 'auth/Authorization/register', registration).then(function (response) {
return response;
});
};
Here is my web api configuration:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "AuthenticationApi",
routeTemplate: "auth/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapODataServiceRoute("ODataRoute", "api", GenerateEdmModel());
var jsonFormatter = config.Formatters.OfType<JsonMediaTypeFormatter>().First();
jsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
}
private static IEdmModel GenerateEdmModel()
{
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
return builder.GetEdmModel();
}
}
Now I have tried a number of different solutions posted on the web to no avail, the following are links to things I have tried:
Web api not supporting POST method
Web API Put Request generates an Http 405 Method Not Allowed error
http://blog.dontpaniclabs.com/post/2013/01/23/That-Pesky-Requested-Resource-Does-Not-Support-HTTP-Method-POST-Error-When-Using-MVC-Web-API
I hate answering my own question. If anyone else runs into this issue it's because you're trying to send a simple string value to a web api controller.
I used this solution with success: http://jasonwatmore.com/post/2014/04/18/Post-a-simple-string-value-from-AngularJS-to-NET-Web-API.aspx
If the link is dead, you simple wrap the string value in double quotes in your POST request like so:
$http.post(Config.apiUrl + '/processfile', '"' + fileName + '"');

WPF and MVC4 Web API Internal Server Error 500 on POST

So I'm attempting to attach to a web api method via a WPF service, but get only a 500 error on anything other than a GET.
WPF call:
using (var client = new HttpClient())
{
var user = new MyUser
{
EntityID = Guid.NewGuid(),
FirstName = "WPF",
LastName = "test"
};
var formatter = new JsonMediaTypeFormatter();
HttpContent content = new ObjectContent<MyUser>(user, formatter);
client.BaseAddress = new Uri("http://localhost:19527/api/");
var response = await client.PostAsJsonAsync("MyUser", content);
//.ContinueWith((postTask) => result = (postTask.Result.Content == null) ? "Could not create user" : "User created successully!");
var r = response.StatusCode;
}'
...and the receiving controller:
public HttpResponseMessage Get(string badgeId)
{
return Request.CreateResponse<bool>(HttpStatusCode.OK, (service.UserByBadge(badgeId) != null));
}
public HttpResponseMessage Put(MyUser user)
{
return Request.CreateResponse<bool>(HttpStatusCode.OK, service.UpsertUser(user));
}
public HttpResponseMessage Post(MyUser user)
{
if (service.UpsertUser(user)) return Request.CreateResponse<MyUser>(HttpStatusCode.OK, service.Get<MyUser>(u => u.BadgeID == user.BadgeID));
return Request.CreateResponse<MyUser>(HttpStatusCode.NoContent, null);
}'
The service on the WebApi controller is a GenericRepository, which is working fine, since the Get method returns as expected. It's only when I use Post that I get the error. Debugging the methods throws the break point in the Get, but not in the Post, so I don't think it's ever being called.
Here's the route config:
routes.MapRoute(
name: "Default",
url: "api/{controller}/{action}/{id}",
defaults: new { controller = "{controller}", action = "{action}", id = UrlParameter.Optional }
);
I've tried different examples from other SO posts, but none appear to address this issue specifically. I'm guessing there's something wrong with how I've constructed the Post() method?
================================================================
RESOLUTION: Model being passed was failing property validations. Why this was causing a 500, not certain. But once I solved for this, API method began working.
If anybody has a "why" explanation, would love to know for future reference.

Resources