I have some restfull services and get and getall work correctly for me, but I can't update a user.
Here is my service class:
#Service
public class UsuarioService{
#Autowired
UsuarioRepository usuarioRepository;
public ArrayList<Usuario> obtenerUsuarios(){
return (ArrayList<Usuario>) usuarioRepository.findAll();
}
public Usuario guardarUsuario(Usuario usuario) {
return usuarioRepository.save(usuario);
}
public Usuario modificarUsuario(Long id, Usuario usuario) {
Optional<Usuario> user = usuarioRepository.findById(id);
if(user.isPresent())
{
usuarioRepository.save(usuario);
return usuario;
}
return null;
}
}
and here is my UserController class:
#RestController
#RequestMapping("/usuario")
public class UsuarioController {
#Autowired
UsuarioService usuarioService;
#GetMapping()
public ArrayList<Usuario> obtenerUsuarios(){
return usuarioService.obtenerUsuarios();
}
#PostMapping
public Usuario guardarUsuario(#RequestBody Usuario usuario) {
return this.usuarioService.guardarUsuario(usuario);
}
#PutMapping("{id}")
public Usuario modificarUsuario(#PathParam("id") Long id, #RequestBody Usuario usuario) {;
return this.usuarioService.modificarUsuario(id, usuario);
}
}
Why cant update user with postman?
Your request payload should not be an array it should just be like below. Also change #PathParam("id") to #PathVariable("id")
{
"name" : ""
"email" : ""
}
Looks like your modificarUsuario method is also not correct and should be something like
public Usuario modificarUsuario(Long id, Usuario usuario) {
Optional<Usuario> user = usuarioRepository.findById(id);
if(user.isPresent())
{
user.setName(usuario.getName())
user.setEmail(usuario.getEmail)
return usuarioRepository.save(user);
}
return null;
}
Related
I trying to create web services for shop cart with mongodb and dotnet core web api. My cart collection have information of product. Product must be a multiple array or object and I tried with one solution but I have this error :
An unhandled exception occurred while processing the request.
InvalidOperationException: Unable to resolve service for type 'CartService' while attempting to activate 'CartsController'.
This is looks collection of cart:
{
"_id" : ObjectId("5cab18a057ab66f2536feeb9"),
"Status" : "Inactive",
"Product" : [
{
"_id" : ObjectId("5ca9b27dbec46268305ce427"),
"Quantity" : 1.0,
"Name" : "Samsung",
"Price" : 1000.0
},
{
"_id" : ObjectId("5ca9b27dbec46268305ce427"),
"Quantity" : 2.0,
"Name" : "Samsung",
"Price" : 9999.0
}
],
"modified_on" : "5/4/2019"
}
This is class model:
public class Cart
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
[BsonElement("Status")]
public string Status { get; set; }
[BsonElement("Product")]
public Product Product { get; set; }
[BsonElement("modified_on")]
public DateTime modified_on { get; set; }
}
Cart Service:
public class CartService
{
private readonly IMongoCollection<Cart> _carts;
public CartService(IConfiguration config)
{
var client = new MongoClient(config.GetConnectionString("WebShopDb"));
var database = client.GetDatabase("WebShopDb");
_carts = database.GetCollection<Cart>("Carts");
}
public List<Cart> Get()
{
return _carts.Find(cart => true).ToList();
}
public Cart Get(string id)
{
return _carts.Find<Cart>(cart => cart.Id == id).FirstOrDefault();
}
public Cart Create(Cart cart)
{
_carts.InsertOne(cart);
return cart;
}
public void Update(string id, Cart cartIn)
{
_carts.ReplaceOne(cart => cart.Id == id, cartIn);
}
public void Remove(Cart cartIn)
{
_carts.DeleteOne(cart => cart.Id == cartIn.Id);
}
public void Remove(string id)
{
_carts.DeleteOne(cart => cart.Id == id);
}
}
Cart Controller:
[Route("api/[controller]")]
[ApiController]
public class CartsController : ControllerBase
{
private readonly CartService _cartService;
public CartsController(CartService cartService)
{
_cartService = cartService;
}
[HttpGet]
public ActionResult<List<Cart>> Get()
{
return _cartService.Get();
}
[HttpGet("{id:length(24)}", Name = "GetCart")]
public ActionResult<Cart> Get(string id)
{
var cart = _cartService.Get(id);
if (cart == null)
{
return NotFound();
}
return cart;
}
[HttpPost]
public ActionResult<Cart> Create(Cart cart)
{
_cartService.Create(cart);
return CreatedAtRoute("GetCart", new { id = cart.Id.ToString() }, cart);
}
[HttpPut("{id:length(24)}")]
public IActionResult Update(string id, Cart cartIn)
{
var cart = _cartService.Get(id);
if (cart == null)
{
return NotFound();
}
_cartService.Update(id, cartIn);
return NoContent();
}
[HttpDelete("{id:length(24)}")]
public IActionResult Delete(string id)
{
var cart = _cartService.Get(id);
if (cart == null)
{
return NotFound();
}
_cartService.Remove(cart.Id);
return NoContent();
}
}
You need to register that service in IoC. The best way is the next:
Create interface ICartService with all methods you will have in CartService implementation:
public interface ICartService {
List<Cart> Get();
Cart Get(string id);
Cart Create(Cart cart);
void Update(string id, Cart cartIn);
void Remove(Cart cartIn);
void Remove(string id);
}
implement that interface in CartService.cs:
public class CartService : ICartService
{
private readonly IMongoCollection<Cart> _carts;
Register that service in Startup.cs (since .NET Core has IoC included)
public void ConfigureServices(IServiceCollection services)
{
...
services.AddScoped<ICartService, CartService>();
...
Quick explanation of object types (Scoped, Transient, Singleton):
Transient objects are always different; a new instance is provided to
every controller and every service.
Scoped objects are the same within a request, but different across
different requests.
Singleton objects are the same for every object and every request.
and change CartsController to:
[Route("api/[controller]")]
[ApiController]
public class CartsController : ControllerBase
{
private readonly ICartService _cartService;
public CartsController(ICartService cartService)
{
_cartService = cartService;
}
and it should work.
I have tried connecting to the database following several different tutorials. Each has had their own method for connecting to MongoDb but none of them have shown me how to connect using a username and password. Here is what I am dealing with:
Startup.cs file:
namespace ShortenUrl
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddSingleton<MongoConfig>(Configuration.GetSection("mongo").Get<MongoConfig>()); // Similar To: Configuration.GetSection("MongoConfig:Server").Value;
services.AddSingleton<MongoConnector>(); // options.Database =
services.AddSingleton<Database>(); // Cofiguration.GetSection("MongoConfig:Database").Value;
services.AddTransient<UsersRepository>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
}
namespace ShortenUrl.Services.Mongo
{
public class MongoConnector
{
public MongoConnector(MongoConfig config)
{
Client = new MongoClient(new MongoClientSettings
{
Server = MongoServerAddress.Parse(config.Server),
Credential = MongoCredential.CreateCredential(config.Creds.Db, config.Creds.User, config.Creds.Password),
UseSsl = true,
VerifySslCertificate = false,
SslSettings = new SslSettings
{
CheckCertificateRevocation = false
}
});
Database = Client.GetDatabase(config.Database);
}
public IMongoClient Client { get; }
public IMongoDatabase Database { get; set; }
}
}
And appsettings.json:
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
}
},
"mongo": {
"server": "****************",
"database": "**********",
"creds": {
"db": "**********",
"user": "**********",
"password": "**********"
}
}
This is my Controller with a post method for adding the user permissions and gain access to the database:
public class UsersController : Controller
{
private readonly UsersRepository _repo;
public UsersController(UsersRepository repo)
{
_repo = repo;
}
[HttpPost]
public async Task<IActionResult> Post ([FromBody] string user)
{
await _repo.CreateAsync(user);
return new OkObjectResult(user);
}
}
}
And this is the repository:
public class UsersRepository
{
private readonly Database _db;
public UsersRepository(Database db)
{
_db = db;
}
public async Task<User> CreateAsync(string username)
{
var user = new User { Username = username };
await _db.Users.InsertOneAsync(user);
return user;
}
Update
Model Config:
namespace ShortenUrl.Services.Configs
{
public class MongoCreds
{
public string Db { get; set; }
public string User { get; set; }
public string Password { get; set; }
}
public class MongoConfig
{
public string Server { get; set; }
public string Database { get; set; }
public MongoCreds Creds { get; set; }
}
}
Connector:
public class MongoConnector
{
public MongoConnector(MongoConfig config)
{
Client = new MongoClient(new MongoClientSettings
{
Server = MongoServerAddress.Parse(config.Server),
Credential = MongoCredential.CreateCredential(config.Creds.Db, config.Creds.User, config.Creds.Password),
UseSsl = true,
VerifySslCertificate = false,
SslSettings = new SslSettings
{
CheckCertificateRevocation = false
}
});
Database = Client.GetDatabase(config.Database);
}
public IMongoClient Client { get; }
public IMongoDatabase Database { get; set; }
}
}
Added the route attribute and now it works.
namespace ShortenUrl.Controllers
{
[Route("api/codes")]
public class ShortUrlsController : Controller
{
private readonly ShortUrlRepository _repo;
//private readonly IShortUrlService _service;
public ShortUrlsController(ShortUrlRepository repo /*IShortUrlService service*/)
{
_repo = repo;
//_service = service;
}
[HttpGet("{id}")]
public async Task<IActionResult> Get(string id)
{
var su = await _repo.GetAsync(id);
if (su == null)
return NotFound();
return Ok(su);
}
[HttpPost]
public async Task<IActionResult> Create([FromBody] ShortUrl su)
{
await _repo.CreateAsync(su);
return Ok(su);
}
}
}
More information on routing to controller actions can be found HERE!
We have different Customers and each of them own a Database.
the client send an Request with an HttpHeader DatabaseOwner:Samsung
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Add("DatabaseOwner", "Samsung");
In my ApiController i would like that my Context is based on that Value and connects to the correct Database
public class AuthController : ApiController
{
public CmpContext db { get; set; }
public AuthController(): base()
{
db = new CmpContext(VcsCmpConnectionstring.GetCMPConnectionString(this.Request));
}
// GET api/Auth
public IQueryable<DealerModel> GetDealer()
{
return db.Dealer;
}
public static string GetCMPConnectionString(HttpRequestMessage request)
{
string returnvalue;
if (request.Headers.Contains("DatabaseOwner"))
{
SqlConnectionStringBuilder ConnStringBuilder = new SqlConnectionStringBuilder(ConfigurationManager.ConnectionStrings["CMPContext"].ConnectionString);
ConnStringBuilder.InitialCatalog = "vcs_cmp_" + request.Headers.GetValues("DatabaseOwner").First();
returnvalue = ConnStringBuilder.ToString();
}
else
{
throw new MissingFieldException("Header DatabaseOwneris null");
}
return returnvalue;
}
When i try to get this.Request it is always null
When is Request access able or is there a better way to switch between Databases?
Greetings from Bavaria
Patrick
Request object is only initialized after APIController's Initialize function is called. So you cannot access Request before base.initialize is called. Try doing this:
public class AuthController : ApiController
{
protected override void Initialize(
System.Web.Http.Controllers.HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
db = new CmpContext(
VcsCmpConnectionstring.GetCMPConnectionString(this.Request));
}
}
I'm trying to add HATEOAS links to a JSON resource served by a Spring REST controller.
I see I should use a resource assembler as described at https://github.com/spring-projects/spring-hateoas
The example displays a Person class and a PersonResource class.
I understand the PersonResource class is defined as:
public class PersonResource extends ResourceSupport {
}
What is then the Person class ? Is it a data domain class ?
In my case, I have defined an Admin class that is a REST domain class, and I specified it as having resource support:
public class Admin extends ResourceSupport {
private String firstname;
private String lastname;
private String email;
private String login;
private String password;
private String passwordSalt;
public Admin() {
}
public String getFirstname() {
return this.firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
public String getLastname() {
return this.lastname;
}
public void setLastname(String lastname) {
this.lastname = lastname;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getLogin() {
return this.login;
}
public void setLogin(String login) {
this.login = login;
}
public String getPassword() {
return this.password;
}
public void setPassword(String password) {
this.password = password;
}
public String getPasswordSalt() {
return passwordSalt;
}
public void setPasswordSalt(String passwordSalt) {
this.passwordSalt = passwordSalt;
}
public EventAdmin toEventAdmin() {
EventAdmin eventAdmin = new EventAdmin();
BeanUtils.copyProperties(this, eventAdmin);
return eventAdmin;
}
public static Admin fromEventAdmin(EventAdmin eventAdmin) {
Admin admin = new Admin();
BeanUtils.copyProperties(eventAdmin, admin);
return admin;
}
}
My REST controller sees only this Admin class as it is a REST domain class. It does not know, and should not know, of anything data domain class.
So I wonder how to use the resource assembler support here.
I don't understand why I should have an additional data domain Admin class here.
kind Regards,
Following Mike's answer here is how my controller now looks like:
#RequestMapping(method = RequestMethod.POST, produces = "application/json; charset=utf-8")
#ResponseBody
public ResponseEntity<Admin> add(#RequestBody Admin admin, UriComponentsBuilder builder) {
AdminCreatedEvent adminCreatedEvent = adminService.add(new CreateAdminEvent(admin.toEventAdmin()));
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.add("Content-Type", "application/json; charset=utf-8");
responseHeaders.setLocation(builder.path("/admin/{id}").buildAndExpand(adminCreatedEvent.getAdminId()).toUri());
Admin createdAdmin = adminResourceAssembler.toResource(adminCreatedEvent.getEventAdmin());
ResponseEntity<Admin> responseEntity = new ResponseEntity<Admin>(createdAdmin, responseHeaders, HttpStatus.CREATED);
return responseEntity;
}
Before, instead of using the resource assembler I was doing a:
Admin createdAdmin = Admin.fromEventAdmin(adminCreatedEvent.getEventAdmin());
createdAdmin.add(linkTo(methodOn(AdminController.class).add(createdAdmin, builder)).withSelfRel());
But it was not giving me the resource id in the url.
Your ResourceAssembler implementation needs to know about both the data domain class and the REST domain class, because its job is to convert the former to the latter.
If you want to keep knowledge of your data classes out of your controller, you could make a resource conversion service which would retrieve the data from the repo and use a ResourceAssembler to turn it into resources that the controller can know about.
#Component
public class AdminResourceAssembler extends ResourceAssemblerSupport<Admin, AdminResource> {
public AdminResourceAssembler() {
super(AdminController.class, AdminResource.class);
}
public AdminResource toResource(Admin admin) {
AdminResource adminResource = createResourceWithId(admin.getId(), admin); // adds a "self" link
// TODO: copy properties from admin to adminResource
return adminResource;
}
}
#Service
public class AdminResourceService {
#Inject private AdminRepository adminRepository;
#Inject private AdminResourceAssembler adminResourceAssembler;
#Transactional
public AdminResource findOne(Long adminId) {
Admin admin = adminRepository.findOne(adminId);
AdminResource adminResource = adminResourceAssembler.toResource(admin);
return adminResource;
}
}
#Controller
#RequestMapping("/admins")
public class AdminController {
#Inject private AdminResourceService adminResourceService;
#RequestMapping(value="/{adminId}", method=RequestMethod.GET)
public HttpEntity<AdminResource> findOne(#PathVariable("adminId") Long adminId) {
AdminResource adminResource = adminResourceService.findOne(adminId);
return new ReponseEntity<>(adminResource, HttpStatus.OK);
}
}
In the http request body, the way password string is passed is "pass=1111", however in the bean the way password is defined is ''private String password". Is there a way I can use annotation to handle the difference or I have to always match names?
The Http request is like this
curl -H "Accept:text/html" -H "Content-Type application/x-www-form-urlencoded" -d 'email=test%40gmail.com&pass=1111&passconfirm=1111&name=x+y' "http://localhost:8080/project/register"
Handler method is
#RequestMapping(method = RequestMethod.POST, headers = "content-type=application/x-www-form-urlencoded")
public String register(#ModelAttribute UserAccountBean account) ...
UserAccountBean is
public class UserAccountBean2 {
#NotNull
#Size(min = 1, max = 25)
private String name;
#NotNull
#Size(min = 4, max = 8)
private String password;
#NotNull
private String email;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword()
{
return password;
}
public void setPassword(String password)
{
this.password = password;
}
public String toString() {
return new ToStringCreator(this).append("name", name).append("password", password).toString();
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
Use #RequestParam annotation in #InitBinder annotated method, and set the desired value manually.
UserController
#InitBinder(value="user")
public void bind(WebDataBinder dataBinder, WebRequest webRequest, #RequestParam(value="pass", required=false) String password) {
User user = (User) dataBinder.getTarget();
user.setPassword(password);
}
Is there a way I can use annotation to
handle the difference or I have to
always match names?
AFAIK there is no ready-made annotation in Spring MVC that can resolve your problem; you need custom setup to handle the situation.
WebModelAttribute
#Target({ElementType.METHOD, ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface WebModelAttribute {
String modelAttributeName();
WebParameterMapping[] parameterMappings();
}
WebParameterMapping
#Target({ElementType.METHOD, ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface WebParameterMapping {
String webProperty();
String beanProperty();
}
UserController
#Controller
public class UserController extends AbstractController {
#Override
#InitBinder(value="user")
#WebModelAttribute(modelAttributeName="user", parameterMappings={#WebParameterMapping(webProperty="pass", beanProperty="password")})
protected void bindWebParameters(WebDataBinder dataBinder, WebRequest webRequest, WebParameterResolver mappingResolver) {
super.bindWebParameters(dataBinder, webRequest, mappingResolver);
}
AbstractController
public class AbstractController {
protected void bindWebParameters(WebDataBinder dataBinder, WebRequest webRequest, WebParameterResolver mappingResolver) {
if(mappingResolver != null && dataBinder.getTarget() != null && dataBinder.getObjectName().equals(mappingResolver.getModelAttributeName())) {
String[] allowedFields = mappingResolver.getAllowedFields(dataBinder.getAllowedFields());
String[] disallowedFields = mappingResolver.getDisallowedFields(dataBinder.getDisallowedFields());
dataBinder.setAllowedFields(allowedFields);
dataBinder.setDisallowedFields(disallowedFields);
dataBinder.bind(mappingResolver.getPropertyValues(dataBinder, webRequest));
}
}
}
WebParameterResolver
public class WebParameterResolver {
private String modelAttributeName;
private WebParameterMapping[] parameterMappings;
public WebParameterResolver(String modelAttributeName,
WebParameterMapping[] parameterMappings) {
this.modelAttributeName = modelAttributeName;
this.parameterMappings = parameterMappings;
}
public String getModelAttributeName() {
return modelAttributeName;
}
public String[] getDisallowedFields(String[] existingDisallowedFields) {
List<String> disallowedFields = new ArrayList<String>();
for (WebParameterMapping parameterMapping : parameterMappings) {
disallowedFields.add(parameterMapping.webProperty());
}
if (existingDisallowedFields != null) {
for (String disallowedField : existingDisallowedFields) {
disallowedFields.add(disallowedField);
}
}
return disallowedFields.toArray(new String[disallowedFields.size()]);
}
public String[] getAllowedFields(String[] existingAllowedFields) {
List<String> allowedFields = new ArrayList<String>();
for (WebParameterMapping parameterMapping : parameterMappings) {
allowedFields.add(parameterMapping.beanProperty());
}
if (existingAllowedFields != null) {
for (String allowedField : existingAllowedFields) {
allowedFields.add(allowedField);
}
}
return allowedFields.toArray(new String[allowedFields.size()]);
}
public MutablePropertyValues getPropertyValues(WebDataBinder dataBinder,
WebRequest webRequest) {
MutablePropertyValues propertyValues = new MutablePropertyValues();
for (WebParameterMapping parameterMapping : parameterMappings) {
String[] values = webRequest.getParameterValues(parameterMapping.webProperty());
if (values == null || values.length == 0) {
// do nothing
} else if (values.length == 1) {
propertyValues.add(parameterMapping.beanProperty(), values[0]);
} else {
propertyValues.add(parameterMapping.beanProperty(), values);
}
}
dataBinder.bind(propertyValues);
return propertyValues;
}
}
CustomArgumentResolver
public class CustomArgumentResolver implements WebArgumentResolver {
#Override
public Object resolveArgument(MethodParameter methodParameter, NativeWebRequest webRequest) throws Exception {
if(methodParameter.getParameterType().equals(WebParameterResolver.class)) {
WebModelAttribute webModelAttribute = methodParameter.getMethod().getAnnotation(WebModelAttribute.class);
if(webModelAttribute == null) {
throw new RuntimeException("method must have WebModelAttribute");
}
return new WebParameterResolver(webModelAttribute.modelAttributeName(), webModelAttribute.parameterMappings());
}
return UNRESOLVED;
}
}
beans.xml
<bean id="handlerAdapter" class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="customArgumentResolvers" ref="timetracking.annotations.CustomArgumentResolver"/>
</bean>
<bean name="timetracking.annotations.CustomArgumentResolver"
class="timetracking.annotations.CustomArgumentResolver" />
You can also have a public static void bindWebParameters(...) method in some helper class; so you don't have to extend the AbstractController every time.
You can achieve it with this:
#RequestMapping(method = RequestMethod.POST, headers = "content-type=application/x-www-form-urlencoded")
public String register(#ModelAttribute("userAccountBean") UserAccountBean account) ...
#ModelAttribute("userAccountBean")
public UserAccountBean getUserAccountBean(HttpServletRequest req) {
UserAccountBean uab = new UserAccountBean();
uab.setPassword(req.getParameter("pass"));
return uab;
}
There is no annotation based solution in 3.0.
Just provide additional getPass() setPass(String pass) method and you should be set.