I'm working an example from Grails in Action, specifically implementing a Service for the first time and shifting the work from the Controller to the Service.
I have a Post object containing a User and String content.
The service code is
class PostService {
boolean transactional= true
Post createPost(String user, String content){
def thisUser = User.findByUserID(user)
if (user){
def post = new Post(content:content)
if (user.save()){
return post
} else
throw new PostException(message: "Invalid post", post:post)
}
throw new PostException(message:"Invalid User ID")
}
}
and the controller code is
def addPost = {
try{
def newPost = postService.createPost(params.id, params.content)
flash.message= "Added new Post: $newPost.content}"
} catch (PostException pe){
flash.message = pe.message
}
redirect(action:'timeline', id:params.id)
}
The way this code is supposed to work is an input is made in the form, which is passed to addPost as a params object. Said params object is then handed off to the Service, where a new Post is to be made and bound to the User.
However, I'm getting the error at user.save(). The specific error message is
No signature of method: java.lang.String.save() is applicable for argument
types: () values: []
If I erase the service connector and implement the service code in the controller as
def user = User.findByUserID(params.id)
if (user) {
def post= new Post(params)
user.addToPosts(post)
if (user.save()){
flash.message= "Sucessfully created Post"
}
else {
user.discard()
flash.message = "Invalid or empty post"
}
} else {
flash.message = "Invalid User ID"
}
redirect(action: 'timeline', id:params.id)
}
the action works. So why am I getting a user.save() error on the service implementation, but not the controller?
user is the String you're passing to the service method. thisUser is the actual User object you're getting that you can call save() on.
Related
I'm facing a behavior in Minimal API that I can't understand.Consider the following simple Minimal API:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.UseExceptionHandler((exceptionApp) =>
{
exceptionApp.Run(async context =>
{
context.Response.ContentType = MediaTypeNames.Text.Plain;
var feature = context.Features.Get<IExceptionHandlerPathFeature>();
if (feature?.Error is BadHttpRequestException ex)
{
context.Response.StatusCode = 400;
var message =
(ex.InnerException is JsonException)
? "The request body is an invalid JSON"
: "Bad Request";
await context.Response.WriteAsync(message);
}
else
{
context.Response.StatusCode = 500;
await context.Response.WriteAsync("There is a problem occured");
}
});
});
app.MapPost("/models", (Model model) => Results.Created(string.Empty, model));
app.Run();
public record Model(int Value, string Title);
When I run the application in the Development environment, and pass an invalid JSON like
{
"value": 1,
"Title": Model #1
}
the custom exception handler is called and I have to control the behavior of the API. But whenever
I run the application in the Production environment, the framework automatically returns a
"bad request" response without letting me control the response.
Could anyone explain this behavior to me? I really need my exception handler to handle invalid input
JSON exceptions.
Thanks
After digging into ASP.Net Core source code for a while, I found that the following line resolves the issue.
builder.Services.Configure<RouteHandlerOptions>(o => o.ThrowOnBadRequest = true);
Let's say I post a workitem to Revit engine using the following code
var response = await _flurlClient.Request("https://developer.api.autodesk.com/da/us-east/v3/workitems")
.PostJsonAsync(new
{
activityId = "ActivityID",
arguments = new
{
rvtFile = new
{
url = storageUrl,
Headers = new
{
Authorization = $"Bearer {accessToken}"
}
},
result = new
{
verb = "post",
url = $"{baseUrl}/api/result"
}
}
})
.ReceiveJson();
The response will contain the Id for this workitem. once the work item completes successfully, Forge calls my API endpoint with the output file. My endpoint is implemented as follows:
[HttpPost("Result")]
public async Task<IActionResult> PostResults()
{
await using (var fs = new FileStream("D://Test//l2.xlsx", FileMode.Create))
{
await Request.Body.CopyToAsync(fs);
}
return Ok();
}
The file is correctly saved but I can't get the associated workitem Id (not as a query parameter nor a header). This causes an issue, let's say I submitted two work items (A and B) when I receive a file how I can tell if it is related to work item A or B.
See answer.
Design automation allows you to specify a variable workitem.id in your output url. When your workitem completes we shall call this url with the variable expanded to the id of that workitem. This dynamic variable path allows you to determine the workitem id associated with that callback.
At your end you will need to implement Attribute Routing.
[HttpPost("Result/{workitemId}")]
public async Task<IActionResult> PostResults(string workitemId)
{
}
You may post the workitem with:
result = new
{
verb = "post",
url = $"{baseUrl}/api/result/$(workitem.id)"
}
I am unable to get a json response from my controller action. The network shows as a post which is correct as I am posting a file to the server, however, needs a JSON response sent back to my view.
public JsonResult Upload(HttpPostedFileBase file, int id)
{
Homes thishomes= _db.Homes.FirstOrDefault(t => t.Id == id);
FileUploader fileupload = new FileUploader();
fileupload.PostIt(file.InputStream);
return Json(new { success = true, response = "File uploaded.", JsonRequestBehavior.AllowGet });
}
JQUERY using Dropzonejs:
Dropzone.options.DropzoneForm = {
paramName: "file",
maxFilesize: 2000,
maxFiles: 28,
dictMaxFilesExceeded: "Custom max files msg",
init: function () {
this.on("success", function () {
alert("Added file");
})
}
};
Can anyone see an this issue?
Try to write [HttpPost] attribute over your action. Also "The network shows as a post which is correct" if its post then you don't need JsonRequestBehavior.AllowGet
when you are returning Json to your request
My application receives a JSON object with two boolean values. I am accessing the JSON data in my controller using the following:
$http.post('url-here', {"token": token })
.success(function(data) {$scope.tokenValidate = data;});
This is the JSON object I receive:
{valid: true, expired: false}
I am able to access this data within my view using the following:
{{ tokenValidate.valid }} or {{ tokenValidate.expired }}
Instead, because of the way I'm creating my view, I'd like to use these boolean values to assign strings to scope variables from within the controller, like this:
if ($scope.tokenValidate.valid) {
$scope.header = "Choose a new password";
} else if ($scope.tokenValidate.expired) {
$scope.header = "This link has expired.";
$scope.mesage = "Enter your email address to receive a new link."
} else {
$scope.header = "This link is invalid."
}
When I do this, the controller fails to read the JSON data correctly.
TypeError: Cannot read property 'valid' of undefined
Any help? My guess is that it has something to with the fact that when I access the JSON from the view, it has already been loaded, but when I try to access it from the controller, $scope.tokenValidate hasn't been assigned yet.
Maybe the values are strings, not bool
if ($scope.tokenValidate.valid=='true') {
$scope.header = "Choose a new password";
} else if ($scope.tokenValidate.expired=='true') {
$scope.header = "This link has expired.";
$scope.mesage = "Enter your email address to receive a new link."
} else {
$scope.header = "This link is invalid."
}
Before you use returned json data, first parse it and then use it.
$http.post('url-here', {"token": token })
.then(function(data) {
var data=JSON.parse(data);
if (data.valid) {
$scope.header = "Choose a new password";
} else if (data.expired) {
$scope.header = "This link has expired.";
$scope.mesage = "Enter your email address to receive a new link."
} else {
$scope.header = "This link is invalid."
}
});
i have the following action method, that returns a partial view _create. but is there a way to pass a Json object such as return Json(new { IsSuccess = "True" }, with the Partial view.
My Action method looks as follow:-
try
{
if (ModelState.IsValid)
{
var v = repository.GetVisit(visitid);
if (!(v.EligableToStart(User.Identity.Name)))
{
return View("NotFound");
}
vlr.VisitID = visitid;
repository.AddVisitLabResult(vlr);
repository.Save();
ViewBag.LabTestID = new SelectList(repository.FindAllLabTest(), "LabTestID", "Description", vlr.LabTestID);
// return Json(new { IsSuccess = "True" }, JsonRequestBehavior.AllowGet);
#ViewBag.status = "Added Succsfully";
return PartialView("_create",vlr) ;
}
}
::-UPDATED-::
what i am trying to do as follow:-
i am calling the action method using ajax.beginform
using (Ajax.BeginForm("CreateAll", "VisitLabResult", new AjaxOptions
{
HttpMethod = "Post",
UpdateTargetId = item.ToString(),
InsertionMode = InsertionMode.Replace,
OnSuccess = string.Format("disableform({0})", Json.Encode(item)),
}))
after successfully receiving the response from the server ,, the Onsuccess script will be executed,,, the script simply disable the form:-
function disableform(id) {
$('#' + id + ' :input').prop("disabled", true);
}
The problem is that the script will always disable the form even is some validation error occurs,, so what i was trying to achieve is to return a JSON with the partial view that indicate if the ModelState.IsValid was valid or not,, and if it was not valid to keep the form enabled to allow the user to correct the validation errors.
BR
You can return ONLY ONE view from action method, if at all you want to pass other information,make use of ViewData or ViewBag
ViewBag.IsSuccess = "True";
Or
ViewData["IsSuccess"] = "True";
No, you can return only the view and pass JSON as the model, or ViewBag (I recommend model.)
Why not simply extend the model that you are already passing to the View adding the property IsSuccess?
ViewBag or ViewData are evil in my opinion. Try to always use a ViewModel when returning data to the view.
In such cases I used following solution:
In your ajax form definition set:
OnComplete = "yourCallback"
Then:
yourCallback = function(response){
var json = response.responseJSON;
if(json.success){
alert('Well done!');
} else {
var form = $('#formId');
form.html(json.html);
form.removeData("validator").removeData("unobtrusiveValidation");
$.validator.unobtrusive.parse(form);
}
}
Your controller should return something like this:
var result = new { success = false, html = helper.Partial("_YourPartial", model) };
return Json(result);
Where helper helps you to add validation to your partial view. (Described here: https://stackoverflow.com/a/4270511/952023)