Spring webflux interact with Mono object - spring

I have a simple CRUD controller. When updating I want merge the updated model with the model that has to be updated, this is what I have at the moment:
#PutMapping("{id}")
public Mono<Map> update(#PathVariable UUID id, #RequestBody Server updatedServer) {
Mono<Server> server = this.serverRepository.findById(id);
// Update the server here.
// server.setName(updatedServer.getName());
// server.setHost(updatedServer.getHost());
server.flatMap(this.serverRepository::save).subscribe();
return Mono.just(Collections.singletonMap("success", true));
}
How can I edit the server variable before the save? When subscribing on the Mono it will be executed after the save.
I know this is a pretty simple question, but I just can't find a way to do it.

I always find the answer just after asking the question...
I just used another flatmap to edit the object:
#PutMapping("{id}")
public Mono<Server> update(#PathVariable UUID id, #RequestBody Server updatedServer) {
Mono<Server> server = this.serverRepository.findById(id);
return server.flatMap(value -> {
value.setName(updatedServer.getName());
value.setHost(updatedServer.getHost());
return Mono.just(value); // Can this be done cleaner?
}).flatMap(this.serverRepository::save);
}
Or when still returning success: true:
#PutMapping("{id}")
public Mono<Map> update(#PathVariable UUID id, #RequestBody Server updatedServer) {
Mono<Server> server = this.serverRepository.findById(id);
return server.flatMap(value -> {
value.setName(updatedServer.getName());
value.setHost(updatedServer.getHost());
return Mono.just(value); // Can this be done cleaner?
}).flatMap(this.serverRepository::save).subscribe();
return Mono.just(Collections.singletonMap("success", true));
}

Related

How to resolve Web API AmbiguousActionException in dotnet core web api?

I have two Get methods. I want to access this by using following urls
https://localhost:44396/api/values/1
https://localhost:44396/api/values/1?status=1
But I am trying to call this I am getting following exception
AmbiguousActionException: Multiple actions matched. The following actions matched route data and had all constraints satisfied:
// GET api/values/5
[HttpGet("{id}")]
public ActionResult<string> Get(SomeEnum id)
{
//somecode
return "value";
}
[HttpGet("{id}")]
public ActionResult<string> Get(SomeEnum id,int status)
{
//somecode
return "value";
}
Is there any way to use routs like this with mutltiple get methods
There is nothing out of the box provided by ASP.NET core to help your case. As suggested in one of the comments, you should make the status parameter as nullable and use it within the action method to decide what next to do. Something like this:
[HttpGet("{id}")]
public ActionResult<string> Get(SomeEnum id,int? status)
{
if(status == null)
{
//perform usual logic which requires only id
}
else
{
//perform logic or call a method which requires both id and status
}
return "value";
}

Update data with Microsoft.AspNet.WebApi

Hey i am having a big trouble updating data in my client side REST application.
I made a Web API controller.
// PUT: api/Contacts/5
[ResponseType(typeof(void))]
public IHttpActionResult PutContact(Contact contact, int id)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (id != contact.ContactId)
{
return BadRequest();
}
_contactService.Update(contact);
return StatusCode(HttpStatusCode.NoContent);
}
And also client side service method:
public async Task<T> PutData<T>(T data, int dataId)
{
HttpResponseMessage resp = await this._client.PutAsJsonAsync(_serviceUrl + "/" + dataId, data);
resp.EnsureSuccessStatusCode();
return await resp.Content.ReadAsAsync<T>();
}
Service URL shows in debug mode that i goes to endpoint:
http://localhost:21855/api/Contacts/8
But it does not even go to breakpoint when i debug my server controller PutContact method.
What i am doint wrong? I need to update the data but i cant, because my client-side application won't even go to servers breakpoint on debug mode!!!
It gives me an error response 405 : Method not allowed
You can't have two different body parameters in the same method.
What you need to do is to set the id parameter to come from the URI and the Contact parameter from the body, like this:
public IHttpActionResult PutContact([FromBody]Contact contact, [FromUri]int id)
{
// method code
}
BTW, I suppose you have a GET method in your controller which looks like this:
public IHttpActionResult GetContact(int id)
{
// method code
return Contact; // pseudo-code
}
The error you getting comes from the fact that the system is not really calling your PUT method but the GET one (the system is ignoring the Contact parameter for the reason I expressed before): calling a GET method with a PUT verb results in a 405 Method Not Allowed exception.

ASP.net 5 Web API Post CreatedAtRoute always returns 500 Internal Server Error

The database works. It does actually insert the new record, but when I use CreatedAtRoute(), I always get a 500 back from the client. Why?
My controller's Get:
[Route("api/[controller]")]
public class IngredientController : Controller
{
private SimpleCookbookDbContext db { get; set; }
public IngredientController(SimpleCookbookDbContext context)
{
db = context;
}
// GET: api/values
[HttpGet]
public async Task<IEnumerable<Ingredient>> Get()
{
return await db.Ingredients.ToListAsync();
}
// GET api/values/5
[HttpGet("{id}", Name = "GetIngredient")]
public async Task<Ingredient> Get(int id)
{
return await db.Ingredients.SingleOrDefaultAsync(i => i.Id == id);
}
[HttpPost]
public async Task<IActionResult> Post([FromBody]Ingredient ingredient)
{
try
{
var res = await IM.CreateAsync(ingredient);
if (!res.Success)
{
return HttpBadRequest(res.Errors);
}
}
catch(Exception)
{
return new HttpStatusCodeResult((int)HttpStatusCode.InternalServerError);
}
return CreatedAtRoute("GetIngredient", new { controller="Ingredient", id = ingredient.Id });
}
}
I tried debugging this. Yes, it would return the HttpBadRequest if the ingredient I'm trying to insert already exists.
I tried putting a breakpoint inside the catch block, and I'm not getting there, so I assume there was no error from the database.
The record does get inserted to the database. I do get to the line return CreatedAtRoute(...); but I get a 500 back. (I set a breakpoint there, too).
Now, I'm using fiddler. My request is this:
POST /api/ingredient HTTP/1.1
Host: localhost:55303
Content-Type: application/json;charset=utf-8
{"id":0, "name": "rosemary", "description": "rosemary"}
I also removed the double quotes on the property names, and I still get the same 500.
I do have camel-casing resolved at the Startup:
services.AddMvc().AddJsonOptions(options => {
options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
});
I think I showed all relevant code. If you need more, please let me know.
UPDATE
CreatedAtRoute has an overload taking in three parameters:
return CreatedAtRoute("GetIngredient", new { controller="Ingredient", id = ingredient.Id }, ingredient);
The last parameter is an object, which you could create dynamically or pass back your entire entity, depending on what you want to expose back.
It's strange how there's a 2-parameter variant that would result in a strange 500 response.

Execute a simple call to a WebAPI Get using RestRequest and a single string parameter

I have the following code in my receiving controller:
[Route("api/StudentUserId/{string}"), HttpGet]
public StudentDto StudentUserId(string userId)
{
StudentModel sm = new StudentModel(userId);
StudentDto dto = sm.ConvertToDto();
return dto;
}
After running this project, I have another project that I use to test the WebAPI controller calls. I use the following code to read a student record form the database using their userId:
protected T SendRequestToReadItemUsingString<T>(string resource, string userId) where T : new()
{
string resourceString = string.Format("{0}/{{userId}}", resource);
RestRequest request = new RestRequest(resourceString, Method.GET);
request.AddUrlSegment("userId", userId);
RestClient restClient = new RestClient(Service.Location);
var response = restClient.Execute<T>(request);
T retVal = response.Data;
return retVal;
}
Comparible code seems to work if I change the userId to an int Id in both the controller and calling code. I can't seem to get it to work with string. If I place a breakpoint in the controller code it never hits it and the calling code just returns a null.
Thanks for your help
Please note that WebApi works based on reflection this means that your curly braces {vars} must match the same name in your methods.
Therefore to match this api/StudentUserId/{string} your method needs to be declare like this:
[Route("api/StudentUserId/{userId}"), HttpGet]
public StudentDto StudentUserId(string userId)
return userId;
}
Where the parameter {string} was replaced by userId.
If you want to read more about this Routing Rules here is similar post on this;
WebApi Routing Configuration

ID in Spring-MVC 2.5 edit form using #Controller

I have a problem with the my Controller code. GET works fine (both empty form + form populated from db), POST works fine only for creating new object, but doesn't work for editing. Part of my #Controller class:
#RequestMapping(value = "/vehicle_save.html", method = RequestMethod.GET)
public String setUpForm(#RequestParam(value="id", required = false) Long id, ModelMap model) {
Vehicle v;
if (id == null) {
v = new Vehicle();
} else {
v = vehicleManager.findVehicle(id);
}
model.addAttribute("vehicle", v);
return "vehicle_save";
}
#RequestMapping(value = "/vehicle_save.html", method = RequestMethod.POST)
public String save(#ModelAttribute("vehicle") Vehicle vehicle, BindingResult result, SessionStatus status) {
vehicleValidator.validate(vehicle, result);
if (result.hasErrors()) {
return "vehicle_save";
}
if(vehicle.getId() == null) {
vehicleManager.createVehicle(vehicle);
} else {
vehicleManager.updateVehicle(vehicle);
}
status.setComplete();
return "redirect:vehicle_list.html";
}
The first method creates a vehicle object (including its ID). But the second method gets the same object without the ID field (set to null).
What could I do: manually set vehicle.setID(id from parameters) and then save it to database. This causes JPAOptimisticLockException + I don't like that solution.
Is there a way to pass my Vehicle object with ID to the second method? BTW, I would like to avoid adding hidden ID field to the JSP.
the example you suggested is using session to store the value. the #SessionAttribute is to bind an existing model object to the session. Look at the source code the class is annotated with #SessionAttributes("pet").Which means your model attribute named "pet" is getting stored in session.Also look at the code in processSubmit method of EditPetForm class
#RequestMapping(method = { RequestMethod.PUT, RequestMethod.POST })
public String processSubmit(#ModelAttribute("pet") Pet pet, BindingResult result, SessionStatus status) {
new PetValidator().validate(pet, result);
if (result.hasErrors()) {
return "pets/form";
}
else {
this.clinic.storePet(pet);
status.setComplete(); //look at its documentation
return "redirect:/owners/" + pet.getOwner().getId();
}
}
I havnt used something like this before.But i guess putting ur id in session is the way
BTW, I would like to avoid adding hidden ID field to the JSP.
This is common solution. What's wrong with it ? You should create hidden input with id.
May be you can try using session, cause you cant store info between two request. But that will be uglier i guess.
Btw, Can you please explain a little why you want to avoid adding hidden fields? I'm little curious

Resources