I got an issue on the url mapping so don't focus on the codes inside my methods too much. This is my Controller:
#Autowired
ChannelService channelService;
#Autowired
RuleService ruleService;
#GetMapping("/config/{channelCode}/view/{ruleCode}")
public String viewTable(#PathVariable String channelCode, #PathVariable String ruleCode, Authentication authentication, ModelMap model) {
UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal();
ChannelDto channelDto = channelService.findByClientIdAndChannelCode(userPrincipal.getUser().getClient().getId(), channelCode);
List<RuleStoreDto> storeDtos = ruleService.findAllCustomRuleStoreByClientIdAndChannelCode(userPrincipal.getUser().getClient().getId(), channelCode);
model.addAttribute("channel", channelDto);
model.addAttribute("stores", storeDtos);
return "configuration/sales_channel_view";
}
#GetMapping("/config/{channelCode}/create")
public String createForm(#PathVariable String channelCode, Authentication authentication, ModelMap model) {
UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal();
ChannelDto channelDto = channelService.findByClientIdAndChannelCode(userPrincipal.getUser().getClient().getId(), channelCode);
model.addAttribute("channel", channelDto);
return "configuration/sales_channel_create";
}
#RequestMapping(value="/config/{channelCode}/create", method=RequestMethod.POST)
public String postForm(
#Valid #ModelAttribute("createForm") RuleStoreDto form, #PathVariable String channelCode, BindingResult result, Authentication authentication, ModelMap model) {
UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal();
ChannelDto channelDto = channelService.findByClientIdAndChannelCode(userPrincipal.getUser().getClient().getId(), form.getChannelCode());
model.addAttribute("channel", channelDto);
try {
form.setClientId(userPrincipal.getUser().getClient().getId());
if(result.hasErrors()) {
model.addAttribute("createForm", form);
return "configuration/sales_channel_create";
}
customRuleService.save(form);
}catch(Exception e) {
model.addAttribute("err", e.getClass().getSimpleName());
model.addAttribute("createForm", form);
return "configuration/sales_channel_create";
}
return "configuration/sales_channel_create";
}
This is a snippet from my sales_channel_view.html for the table view:
<div class="btn-group">
<a class="btn btn-success"
th:href="#{|/config/${channel.channelCode}/create|}">
<i class="fa fa-plus"> Create New</i>
</a>
</div>
This is a snippet from my sales_channel_create.html for the form:
<form id="demo-form2" th:action="|#{/config/${channel.channelCode}/create}|" th:object="${createForm}" method="post" data-parsley-validate class="form-horizontal form-label-left">
//stuff
</form>
So when I'm trying to visit the form (createForm) from the table (viewTable), my form is not fully shown & I got this error:
ERROR org.thymeleaf.TemplateEngine - [THYMELEAF][http-nio-8080-exec-4] Exception processing template "configuration/sales_channel_create": An error happened during template parsing (template: "class path resource [templates/configuration/sales_channel_create.html]")
org.thymeleaf.exceptions.TemplateInputException: An error happened during template parsing (template: "class path resource [templates/configuration/sales_channel_create.html]")
What's wrong with my code?
Without the full exception, I can only speculate.
The pipe symbols (|) here seem wrong:
th:href="#{|/config/${channel.channelCode}/create|}">
th:action="|#{/config/${channel.channelCode}/create}|"
Because they are not part of an valid URL and whould need to be escaped as %7C.
Related
I created a form where the user can update his data account. In this form the user is also able to change the account password, before doing so, I ask him the current password, this is the field:
<div class="form-group">
<label>Current Password</label>
<input class="form-control" id="oldPassword"
asp-for="#Model.ExistingPassword" type="password" />
<div class="invalid-feedback"></div>
</div>
as you can see the oldPassword input bound the property ExistingPassword which is part of the ViewModel of that View and have the following declaration:
[Required, MinLength(6), MaxLength(50), DataType(DataType.Password)]
public string ExistingPassword { get; set; }
when the form is submitted I call the following ajax function:
$.post(url, user, function (response) {
//Some stuff
}).done(function (response) {
alert("Updated executed");
}).fail(function (jqXHR, textStatus, errorThrown) {
alert("Error happened!");
});
the parameter of the function are taken by the form, in particular:
url: $(this).attr('action');
user: $(this).serialize();
the action of the form will call the following controller: User\UpdateUser.
Inside the UpdateUser method I execute the following check:
public async Task<UserProfileViewModel> UpdateUserAsync(UserProfileViewModel updatedUser)
{
if (!await _userManager.CheckPasswordAsync(originalUser, updatedUser.ExistingPassword))
throw new Exception("Invalid password");
essentially, the condition check if the current password is correct, if not, then an exception will raised.
Now, my question with this is: how can I know which type of exception the method has generated?
I need to know which type of exception the method UpdateUser has generated because there are different exceptions in the method.
Suppose the Invalid Password exceptions is raised, I need to display a message inside invalid-feedback div, next to oldPassword, so the user know why the update has failed.
Thanks in advance for any help.
Normally, I recommend not using an exception except in actual exception circumstances, but given the way you've designed this, you have a few options.
I'd suggest creating a custom "UpdateUserException" that you can throw that will include additional information, which can be provided by an enum or just string.
public class UpdateUserException : Exception {
public UpdateUserError ErrorCondition;
public UpdateUserException(UpdateUserError error, string message)
{
ErrorCondition = error;
Message = message;
}
}
then you would throw it
throw new UpdateUserException(UpdateUserError.BadPassword, "Invalid Password");
then you would catch it
try {}
catch (UpdateUserException e)
{
if (e.ErrorCondition == UpdateUserException.BadPassword)
{
// handle your exception.
}
}
Have a look at the UserManager ChangePassword Method.
You can bind the UserManager to use DependencyInjection like this (in Startup.cs)
public async void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
...
app.UseAuthentication();
app.UseMvc();
var scopeFactory = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>();
using (var scope = scopeFactory.CreateScope())
{
UserManager<User> userManager = scope.ServiceProvider.GetRequiredService<UserManager<User>>();
}
}
And then in your Controller s Constructor
private readonly UserManager<User> _userManager;
public AccountController(UserManager<User> userManager)
{
_userManager = userManager;
}
And finally your endpoint:
[HttpPost("ChangePassword")]
public async Task<IActionResult> ChangePassword([FromBody]ChangePasswordRequest changePasswordParams)
{
if (changePasswordParams == null)
return BadRequest($"{nameof(changePasswordParams)} must not be null!");
if (string.IsNullOrWhiteSpace(changePasswordParams.OldPassword) || string.IsNullOrWhiteSpace(changePasswordParams.NewPassword))
return BadRequest("old and new passwords have to be provided, but they both are empty.");
var userId = User.Claims.FirstOrDefault(c => c.Type == "id")?.Value;
var user = await _userManager.FindByIdAsync(userId);
var result = await _userManager.ChangePasswordAsync(user, changePasswordParams.OldPassword, changePasswordParams.NewPassword);
if (result.Succeeded)
return NoContent();
return BadRequest(result.Errors);
}
after that you can handle the errors in a switch statement.
Using Exceptions for handled errors are not recommended since they generally ends up with Internal Server error and actually It is beyond of its purpose.
The best approach would be to send BadRequest as It is stated by #maerlin.
However, If you insist to use Exceptions in your application or your applciation is architected to work in this way. I suggest you to inherit new CustomApplcationException class from ApplicationException and then inherit UpdateUserException and vs. from CustomApplicationException class. After that, I Suggest you to handle your exceptions in ErrorHandlingMiddleware and return HandledExceptions at least with BadRequest (400) status code.
The Example Code would be
public class ExceptionMiddleware
{
private readonly RequestDelegate _next;
private readonly ILoggerManager _logger;
public ExceptionMiddleware(RequestDelegate next, ILoggerManager logger)
{
_logger = logger;
_next = next;
}
public async Task InvokeAsync(HttpContext httpContext)
{
try
{
await _next(httpContext);
}
catch (CustomApplicationException cae)
{
await HandleCustomExceptionAsync(httpContext, cae);
}
catch (Exception ex)
{
_logger.LogError($"Something went wrong: {ex}");
await HandleExceptionAsync(httpContext, ex);
}
}
private static Task HandleExceptionAsync(HttpContext context, Exception exception)
{
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
return context.Response.WriteAsync(new ErrorDetails()
{
StatusCode = context.Response.StatusCode,
Message = "Internal Server Error from the custom middleware."
}.ToString());
}
private static Task HandleCustomExceptionAsync(HttpContext context, Exception exception)
{
context.Response.StatusCode = 400;
return context.Response.WriteAsync(new ErrorDetails()
{
StatusCode = context.Response.StatusCode,
Message = exception.Message
}.ToString());
}
}
then you need to regiter middleware in your Startup.cs
app.UseMiddleware<ExceptionMiddleware>();
please see https://code-maze.com/global-error-handling-aspnetcore/ and http://www.talkingdotnet.com/global-exception-handling-in-aspnet-core-webapi/ for further details.
Not sure what I am doing wrong here but when I DO NOT define the mvc:annotation-driven in my servlet, the value returned from the #PathVariable is not getting displayed in my jsp page and when I do define the annotation-driven, all other links e.g. home gets broken and I get the The specified HTTP method is not allowed for the requested resource (Request method GET not supported).
#Controller("HealthCheckController")
#RequestMapping("/healthCheckSummary")
public class HealthCheckController {
private Log log = LogFactory.getLog(GolSimpleMappingExceptionResolver.class);
private HealthCheckService healthCheckService = null;
private IEappDataAccessFacade dataAccessFacade;
#RequestMapping(value = "/{username}/{password}", method = RequestMethod.GET)
public String getEappStatus(#PathVariable String username, #PathVariable String password, Model model){
String dbConnectivityStatus = getDatabaseConnectivityStatus() ? "online" : "offline";
if (!username.equals("lola") || !password.equals("123")) {
// wrong credentials were provided
log.error("The login credentials in the header are not valid." + username + password);
throw new RuntimeException( "Unable to continue, the login credentials in the header are not valid." + username + password);
}
model.addAttribute("healthCheckSummary", dbConnectivityStatus);
return "healthCheckSummary";
}
public HealthCheckService getHealthCheckService()
{
return healthCheckService;
}
public boolean getDatabaseConnectivityStatus() {
String result = “OK”;
if (result != null) {
return true;
}
return false;
}
}
Oh and in the application context we have defined the
<tx:annotation-driven />
JSP page
<%# page language="java"%>
Welcome ${username} - ${password}
Eapp is currently ${healthCheckSummary}
Two things:
You never put your username and password #PathVariables in the model. Your jsp page has no way of knowing that they even existed as you lose any reference to them after the code leaves getEappStatus. Add the following to your handler method:
model.addAttribute("username", username);
model.addAttribute("password", password);
and see if that works.
You might want to add <mvc:annotation-driven/> just to avoid some surprises when you use some spring-mvc annotations. This isn't strictly necessary, but might save you some head scratching when some #RequestBody doesn't work.
I'm sending a request to server in the following form:
http://localhost:12345/api/controller/par1/par2
The request is correctly resolved to a method like:
[HttpPost]
public void object Post(string par1, string par2)
However, I pass additional data through the request content. How can I retrieve these data?
For the sake of example, let's say, that the request is sent from the form:
<form action="http://localhost:12345/api/controller/par1/par2" method="post">
<input type="hidden" name="data" value="value" />
<input type="submit" name="submit" value="Submit" />
</form>
From answer in this question:
How to get Json Post Values with asp.net webapi
Autoparse using parameter binding; note that the dynamic is made up of JToken, hence the .Value accessor.
public void Post([FromBody]dynamic value) {
var x = value.var1.Value; // JToken
}
Read just like Request.RequestUri.ParseQueryString()[key]
public async Task Post() {
dynamic obj = await Request.Content.ReadAsAsync<JObject>();
var y = obj.var1;
}
Same as #2, just not asynchronously (?) so you can use it in a helper method
private T GetPostParam<T>(string key) {
var p = Request.Content.ReadAsAsync<JObject>();
return (T)Convert.ChangeType(p.Result[key], typeof(T)); // example conversion, could be null...
}
Caveat -- expects media-type application/json in order to trigger JsonMediaTypeFormatter handling.
After spending a good bit of time today trying to wrap my brain around the (significant but powerful) paradigm shift between old ways of processing web form data and how it is done with WebAPI, I thought I'd add my 2 cents to this discussion.
What I wanted to do (which is pretty common for web form processing of a POST) is to be able to grab any of the form values I want, in any order. Say like you can do if you have your data in a System.Collections.Specialized.NameValueCollection. But turns out, in WebAPI, the data from a POST comes back at you as a stream. So you can't directly do that.
But there is a cool little class named FormDataCollection (in System.Net.Http.Formatting) and what it will let you do is iterate through your collection once.
So I wrote a simple utility method that will run through the FormDataCollection once and stick all the values into a NameValueCollection. Once this is done, you can jump all around the data to your hearts content.
So in my ApiController derived class, I have a post method like this:
public void Post(FormDataCollection formData)
{
NameValueCollection valueMap = WebAPIUtils.Convert(formData);
... my code that uses the data in the NameValueCollection
}
The Convert method in my static WebAPIUtils class looks like this:
/// <summary>
/// Copy the values contained in the given FormDataCollection into
/// a NameValueCollection instance.
/// </summary>
/// <param name="formDataCollection">The FormDataCollection instance. (required, but can be empty)</param>
/// <returns>The NameValueCollection. Never returned null, but may be empty.</returns>
public static NameValueCollection Convert(FormDataCollection formDataCollection)
{
Validate.IsNotNull("formDataCollection", formDataCollection);
IEnumerator<KeyValuePair<string, string>> pairs = formDataCollection.GetEnumerator();
NameValueCollection collection = new NameValueCollection();
while (pairs.MoveNext())
{
KeyValuePair<string, string> pair = pairs.Current;
collection.Add(pair.Key, pair.Value);
}
return collection;
}
Hope this helps!
I had a problem with sending a request with multiple parameters.
I've solved it by sending a class, with the old parameters as properties.
<form action="http://localhost:12345/api/controller/method" method="post">
<input type="hidden" name="name1" value="value1" />
<input type="hidden" name="name2" value="value2" />
<input type="submit" name="submit" value="Submit" />
</form>
Model class:
public class Model {
public string Name1 { get; set; }
public string Name2 { get; set; }
}
Controller:
public void method(Model m) {
string name = m.Name1;
}
It is hard to handle multiple parameters on the action directly. The better way to do it is to create a view model class. Then you have a single parameter but the parameter contains multiple data properties.
public class MyParameters
{
public string a { get; set; }
public string b { get; set; }
}
public MyController : ApiController
{
public HttpResponseMessage Get([FromUri] MyParameters parameters) { ... }
}
Then you go to:
http://localhost:12345/api/MyController?a=par1&b=par2
Reference: http://www.asp.net/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api
If you want to use "/par1/par2", you can register an asp routing rule. eg routeTemplate: "API/{controller}/{action}/{a}/{b}".
See http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-in-aspnet-web-api
Try this.
public string Post(FormDataCollection form) {
string par1 = form.Get("par1");
// ...
}
It works for me with webapi 2
None of the answers here worked for me. Using FormDataCollection in the post method seems like the right answer but something about my post request was causing webapi to choke. eventually I made it work by including no parameters in the method call and just manually parsing out the form parameters like this.
public HttpResponseMessage FileUpload() {
System.Web.HttpRequest httpRequest = System.Web.HttpContext.Current.Request;
System.Collections.Specialized.NameValueCollection formData = httpRequest.Form;
int ID = Convert.ToInt32(formData["ID"]);
etc
I found for my use case this was much more useful, hopefully it helps someone else that spent time on this answer applying it
public IDictionary<string, object> GetBodyPropsList()
{
var contentType = Request.Content.Headers.ContentType.MediaType;
var requestParams = Request.Content.ReadAsStringAsync().Result;
if (contentType == "application/json")
{
return Newtonsoft.Json.JsonConvert.DeserializeObject<IDictionary<string, object>>(requestParams);
}
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
Is there a way to handle form post data in a Web Api controller?
The normal approach in ASP.NET Web API is to represent the form as a model so the media type formatter deserializes it. Alternative is to define the actions's parameter as NameValueCollection:
public void Post(NameValueCollection formData)
{
var value = formData["key"];
}
ON WEB API.
[HttpGet]
[Route("api/Get_EXCUTA_PROCEDURE_IBESVNDACMVDD")]
public IHttpActionResult Get(int CodigoPuxada...)
{
string retornoErro = string.Empty;
try
{
//int codigoPuxada = entrada.CodigoPuxada;
SetKeyAtual(CodigoPuxada);
var repo = new ItemBroker_Dim_Canal_BookRepositorio(ConnectionString);
try
{
var dadosRetorno = repo.ExcuteProcedure_Busca_vbc_(CodigoPuxada,...); // method return object (dataset)
return Ok(dadosRetorno);
}
catch
{
throw;
}
}
catch (Exception ex)
{
retornoErro = ex.Message;
if (ex.InnerException != null)
retornoErro = ex.InnerException.ToString();
}
return Ok(retornoErro);
}
Other projet invoke web api...
(USING RESTSHARP DLL)
RestClient clientHttpPost1 = null;
string dadosVbc123 = string.empty;
clientHttpPost1 = new RestSharp.RestClient($"{urlWebApiAvante}Get_EXCUTA_PROCEDURE_IBESVNDACMVDD?CodigoPuxada=...");
RestSharp.RestRequest request2 = new RestSharp.RestRequest(RestSharp.Method.GET);
request2.RequestFormat = RestSharp.DataFormat.Json;
request2.AddHeader("Content-Type", "application/json;charset=utf-8");
string strAux1 = string.Empty;
request2.Timeout = 180000;
RestSharp.IRestResponse response = clientHttpPost1.Execute(request2);
if ((response != null) && response.StatusCode == System.Net.HttpStatusCode.OK)
{
try
{
var dataObjects = response.Content.ToString().Trim();
dadosVbc123 = dataObjects.ToString().Replace("\t", "");
if (dadosVbc123.Trim() == "{\"IBESVNDACMVDD\":[]}")
dadosVbc123 = string.Empty;
}
...
}
// converting JSON to dataset
string val1 = dadosVbc123.Replace("{\"IBESVNDACMVDD\":", "").Replace("}]}", "}]");
DataTable dtVBC123 = (DataTable)Newtonsoft.Json.JsonConvert.DeserializeObject(val1, (typeof(DataTable)));
In Spring MVC I have the following controller:
#RequestMapping(value="adminUsers", method = RequestMethod.POST)
public ModelAndView listAdminUsers(Person newPerson, HttpServletResponse response) throws Exception {
Person person = personService.findPerson(newPerson.getUsername());
if (person == null) {
// Set errorText = "Invalid Person";
// redisplay view
ModelAndView mav = new ModelAndView("adminUsersList");
return mav;
} else {
Roles roles = new Roles();
roles.setPersonCode(person.getPersonCode());
roles.setRoleType("ADMN");
rolesMapper.insert(roles);
return new ModelAndView("redirect:/admin/adminUsers.html");
}
}
With the view:
<form:form method="post" action="${action}" commandName="person" >
<form:label path="username">Add new administrator:</form:label>
<form:input path="username" size="20"/>
<form:errors path="username" />
<input type="submit" value="Submit Changes"/> </form:form>
How would I return an error back to the view, so it is displayed by the <form:errors path="username" /> tag?
This is simlar to How to return error status and validation errors from this Spring MVC controller? except I'm returning a webpage, not a REST object, so the answers there don't apply.
I don't want to use Validator because the above calls the database, it's not simply a check for if username is not empty etc.
Thanks!
Add BindingResult to your method signature. From there, bindingResult.rejectValue("username", "username.notvalid", "Username is not valid");
BrandonV had it. My Controller now looks like:
// Create a new item
#RequestMapping(value="adminUsers", method = RequestMethod.POST)
public ModelAndView listAdminUsers(Person newPerson, BindingResult result, HttpServletResponse response) throws Exception {
Person person = personService.findPerson(newPerson.getUsername());
if (person == null) {
result.rejectValue("username","username.notvalid","Username doesn't exist");
ModelAndView mav = new ModelAndView("adminUsersList"); //This is ugly, it's copy & pasted from above
mav.addObject("adminUsersList", adminService.findAllAdminUsers()); //Ugly...
return mav;
} else {
Roles roles = new Roles();
roles.setPersonCode(person.getPersonCode());
roles.setRoleType("ADMN");
rolesMapper.insert(roles);
return new ModelAndView("redirect:/admin/adminUsers.html");
}
}
You need to change your method logic:
if (result.hasErrors){
return "";
} else {
creating your person object logic goes here!
}
I've a website in asp.net MVC 3, I just put some anti-forgery token, and I don't know why, in one of my forms(not others), I receive this exception:
A required anti-forgery token was not supplied or was invalid.
It appears that in my html code, I've the following token:
<input name="__RequestVerificationToken"
type="hidden" value="0Ll0Io/fX0dR5HXCAroCKTKCBqNn2tmwgcqgYGjln8WVdWOPF2VQEen4wd2UKso1lpIstniXWjdgEE6m0ADgfRhIP25K12Y/ll+PFaYzoQgFAqSfL4XqNYKMrzvAKqmuqXnh3lwBFCYcDXKxRshKVefYelNfWgdFMtf8Ru/dT4qzWw9vU4KQS8eliglpzN9jXu5fekpBOsQGzOhoFHI3Ow=="
disabled="disabled">
Why do I get this disabled attribute?
Here is some of my code:
#using (Html.BeginForm("XXX", "YYY", FormMethod.Post))
{
#Html.AntiForgeryToken()
...
}
and the controller:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult XXX(SessionStore sessionStore, CurrentModel model)
I can't find anything on disabled on google :/(
Here's the code that generates the hidden field:
public HtmlString GetHtml(HttpContextBase httpContext, string salt, string domain, string path)
{
string str = this.GetAntiForgeryTokenAndSetCookie(httpContext, salt, domain, path);
string antiForgeryTokenName = AntiForgeryData.GetAntiForgeryTokenName(null);
TagBuilder builder = new TagBuilder("input");
builder.Attributes["type"] = "hidden";
builder.Attributes["name"] = antiForgeryTokenName;
builder.Attributes["value"] = str;
return new HtmlString(builder.ToString(TagRenderMode.SelfClosing));
}
As you can see there's no trace of any disabled attribute.
So the situation you are describing cannot happen unless:
You are using some custom built helper or custom built ASP.NET MVC
You have used javascript to add this attribute