Jackson and unmarshalling Spring HATEOAS references - spring-mvc

I have a Spring Boot REST application which also include dependencies to spring-boot-starter-data-jpa and spring-hateoas.
My issue is want to convert a JSON payload being posted to my server.
The payload contains data to create a new Product with a relation to a existing VAT Rate.
This relation is modeled using a JPA #ManyToOne like:
#NotNull
#ManyToOne(fetch = FetchType.EAGER)
private VatRate vatRate;
When I post something like:
{
"description": "bla",
"price": 99.99,
"vat_rate": {
"id": 1
}
Jackson automatically converts I can persist a new Product with a relation to the given VAT Rate.
However as I'm using HATEOAS I think I should use something like:
{
"description": "bla",
"price": 99.99,
"vat_rate": {
"id": "http://somehost/vat-rates/1"
}
But this one fails as Jackson cannot convert it.
Is there a easy solution for this? As HATEOAS is not a solid requirement in my case, if this is not possible easily it will put a to high burden to use HATEOAS.
Next to that if in first example I provide a unknown vat_rate id Boot gives me back a 500.
Reason is that Jackson references/creates a VAT Rate which does not exists and when trying to store the Product a FK violation happens resulting in the 500 due to a org.hibernate.exception.ConstraintViolationException.
I think it would be better to return a 400 pointing out that vat_rate does not exists.
But probably I have to check the existence of the VAT Rate manually before saving the Product.

In this situation, I like to use a separate DTO that's passed into my controller methods, rather than trying to reuse the JPA entity. In this scenario I'd create a ProductInput class with three properties:
class ProductInput {
private String description;
private String price;
private URI vatRate;
}
The vatRate property is a URI as it's referencing an existing VAT rate resource and, when you're using hypermedia, a URI is a resource's id.
In your controller you then need to use this URI to look up a VatRate instance. You can use UriTemplate to extract the id from the URI and then use the id to look it up. Something like this:
String idString = new UriTemplate(/vat-rates/{id})
.match(productInput.getVateRate().toASCIIString()).get("id");
long id = Long.valueOf(idString);
VatRate vatRate = vatRateRepository.findById(id);
if (vatRate == null) {
// Return an appropriate error response, probably a 400
// …
} else {
// Create and save the product
// …
}
I've got a very basic app that uses Spring HATEOAS on Github that might provide some useful further reading.

Related

Delete WebApi FromURI binding

I am trying to create a .NET5 WebApi delete method in a controller class where this method receives several "ids" that will be used for deleting some entities.
I realized when building the delete request on the client side that specifying a content does not make sense. So I was guided to pass ids on the Uri, hence the use of the "FromUri" attribute:
// DELETE: api/ProductionOrders/5
[HttpDelete("ProductionOrders")]
public IActionResult DeleteProductionOrder([System.Web.Http.FromUri]int[] ids)
{
//code
}
If this is a reasonable approach, is there a better way to build this Uri from the client-side? Imagine instead of an array of ints I had a complex type. How can I serialized this and put into the Uri?
For this example I end up building up a URI like this:
http://localhost:51081/api/ProductionOrders?ids=25563&ids=25533
Personally, if I have to pass a List or a complex type I would map values from the Body via JSON. The DELETE allow using body. And then just decorate your param with [FromBody] attribute.
Despite some recommendations not to use the message body for DELETE requests, this approach may be appropriate in certain use cases.
This allows better extensibility in case you need to change how the data is coming.
In your case with ids I’d create new class like this:
public class RequestEntity {
[JsonPropertyName("Ids")]
public List<int> Ids { get; set; }
}
And then when calling this method, send the Body along with the request.
{
"Ids": [25392, 254839, 25563]
}
In a future you can pass complex objects just by changing what is send to server and implement complex logic.

Web API .net core Attribute routing with FromQuery

I have below code implemented Web API (.net Framework 4.5.2). When I make a call "http://localhost:3000/123" - It fetches user details whose id is 123.
If I make "http://localhost:3000/Class1/?status=Active" - It fetches user details who belong to Class 1 and status as active. Same I converted to .net core and eventhough I mentioned FromQuery, call always goes to ":http://localhost:3000/123"
public class UserController : Controller
{
private Repository repository;
[HttpGet("{id}")]
public object Get(string id)
{
return repository.GetUser(id) ?? NotFound();
}
[HttpGet("{group}")]
public object Get(string group, Status status)
{
// Get the User list from the group and whose status is active
}
}
Please let me know how to resolve this without changing Route Parameter.
Simply, you have two conflicting routes here. There's no way for the framework to know which to route to, so it's just going to take the first one. As #Nkosi indicated, if there's some kind of constraint you can put on the param, that will help. You may not be able to restrict to just ints, but perhaps there's a particular regex, for example, that would only match one or the other. You can see your options for constraining route params in the relevant docs.
If there's no clear constraint you can apply that will not also match the other, then you're mostly out of luck here. You can simply change one of the routes to be more explicit, e.g. [HttpGet("group/{group}")]. If the route absolutely must be the same, your only other option is to have one action handle both cases, branching your code depending on some factor.
[HttpGet("{id}")]
public object Get(string id, Status? status = null)
{
if (status.HasValue)
{
// Note: treat `id` as `group` here.
// Get the User list from the group and whose status is active
}
else
{
return repository.GetUser(id) ?? NotFound();
}
}
That may not be the best approach (branching on presence of status), but it's just an example. You'd need to decide what would work best here.

Filtering Out Properties in an ASP.NET Core API

I want to serve up the following JSON in my API:
{
"id": 1
"name": "Muhammad Rehan Saeed",
"phone": "123456789",
"address": {
"address": "Main Street",
"postCode": "AB1 2CD"
}
}
I want to give the client the ability to filter out properties they are not interested in. So that the following URL returns a subset of the JSON:
`/api/contact/1?include=name,address.postcode
{
"name": "Muhammad Rehan Saeed",
"address": {
"postCode": "AB1 2CD"
}
}
What is the best way to implement this feature in ASP.NET Core so that:
The solution could be applied globally or on a single controller or action like a filter.
If the solution uses reflection, then there must also be a way to optimize a particular controller action by giving it some code to manually filter out properties for performance reasons.
It should support JSON but would be nice to support other serialization formats like XML.
I found this solution which uses a custom JSON.Net ContractResolver. A contract resolver could be applied globally by adding it to the default contract resolver used by ASP.Net Core or manually to a single action like this code sample but not to a controller. Also, this is a JSON specific implementation.
You can use dynamic with ExpandoObject to create a dynamic object containing the properties you need. ExpandoObject is what a dynamic keyword uses under the hood, which allows adding and removing properties/methods dynamically at runtime.
[HttpGet("test")]
public IActionResult Test()
{
dynamic person = new System.Dynamic.ExpandoObject();
var personDictionary = (IDictionary<string, object>)person;
personDictionary.Add("Name", "Muhammad Rehan Saeed");
dynamic address = new System.Dynamic.ExpandoObject();
var addressDictionary = (IDictionary<string, object>)address;
addressDictionary.Add("PostCode", "AB1 2CD");
personDictionary.Add("Address", address);
return Json(person);
}
This results in
{"Name":"Muhammad Rehan Saeed","Address":{"PostCode":"AB1 2CD"}}
You'd just need to create a service/converter or something similar that will use reflection to loop through your input type and only carry over the properties you specify.

How can I add multiple Get actions with different input params when working RESTFUL?

I'm trying to figure out whats the best way to have multiple Get actions in a REST controller.
I would like to do something like this:
Get By Id:
public ResponseType Get(Guid id)
{
// implementation
}
Get By Enum Type:
public ResponseType Get(EnumType type)
{
// implementation
}
Get By Other Enum Type:
public ResponseType Get(OtherEnumType otherType)
{
// implementation
}
etc..
Now when I do something like that, I get the next error message:
Multiple actions were found that match the request
I understand why I get the message and I was thinking how is the best way to do something like that (I want to stick with REST).
I know I can add a route like this:
routeTemplate: "api/{controller}/{action}/{id}"
But then I would need to change the action names and the urls - And this seems like a workaround when we are talking about rest.
Another thing I thought was to create multiple controllers with one Get - But that seems even wronger.
The third workaround was to handle one Get action with an input param that will have the state:
public ResponseType Get(ReqeustObj obj)
{
switch(obj.RequestType)
{
case RequestType.GetById:
// etc...
}
}
Anyway, I would like to know whats the best way to do something like that in REST (WebApi).
As you now, when Web API needs to choose an action, if you don't specify the action name in the route, it looks for actions whose name starts with the method name, GET in this case. So in your case, it will find multiple methods.
But it also try to match the parameters. So, if you include the parameters as part of the url (route parameters) or the query string, the action selector will be able to choose one of the available methods.
If you don't specify a parameter or specify the id in the url (or even in the query string) it should invoke the first overload. If you add the parameter name of the second action in the query string like this: ?type=VALUE it should choose the corresponding overload, and so on.
The question is that the parameter names must be different, or it will not be able to choose one or the other among all the overloads.
For example, if you use the urls in the comments in your browser, you'll see how the right method is chosen:
public class TestController : ApiController
{
// GET api/Test
public string Get()
{
return "without params";
}
// GET api/Test/5
public string Get(int id)
{
return "id";
}
// GET api/Test?key=5
public string Get(string key)
{
return "Key";
}
// GET api/Test?id2=5
public string Get2(int id2)
{
return "id2";
}
}
NOTE: you can also use route constraints to invoke differet methods without using query string parameters, but defining different route parameter names with different constraints. For example you could add a constraint for id accepting only numbers "\d+" and then a second route which accepts "key" for all other cases. In this way you can avoid using the query string

BreezeJs Fails to Load Metadata for EF Code First Schema with Inherited Class

Just started trying out Breeze today, long time EF user - think I found a bug in Breeze, but I may be doing something wrong - want to know which it is:
I have a simple hierarchy in EF Code First:
// For testimonials about the product line in general
public class Testimonial
{
public int Id { get; set; }
public string Text { get; set; }
}
// For testimonials specific to a single product
[Table("ProductTestimonial")]
public class ProductTestimonial : Testimonial
{
public Product Product { get; set; }
}
So just to be clear there's 2 tables here, Testimonial and ProductTestimonial, both have a PK of Id, and a Text field, and one of them also has an FK off to Product. This is just a simple way of implementing Table Per Type.
So I setup the BreezeController via WebApi:
[BreezeController]
public class EFController : ApiController
{
private readonly EFContextProvider<EfDb> _db = new EFContextProvider<EfDb>();
[HttpGet]
public string Metadata()
{
return _db.Metadata();
}
And I go to load it in breeze.js:
var manager = new breeze.EntityManager('/api/ef');
manager.executeQuery(breeze.EntityQuery.from('Product');
Kablamo. Exception. It says:
Unable to get value of the property 'propertyRef': object is null or undefined
At:
function convertFromODataEntityType(... {
. . .
var keyNamesOnServer = toArray(odataEntityType.key.propertyRef)...
Where odataEntityType.name == 'ProductTestimonial', and .key sure enough is undefined.
Which is true. Picking things apart, when I call executeQuery(), Breeze hits the Metadata call on the WebApi, which I verified calls and returns successfully. The massive JSON string returned from there includes:
{
"name": "ProductTestimonial",
"baseType": "Self.Testimonial",
"navigationProperty": {
"name": "Product",
"relationship": "Self.ProductTestimonial_Product",
"fromRole": "ProductTestimonial_Product_Source",
"toRole": "ProductTestimonial_Product_Target"
}
},
{
"name": "Testimonial",
"key": {
"propertyRef": {
"name": "Id"
}
},
So it would appear the basic issue is that the Metadata is accurately portraying ProductTestimonial as an inherited class, whose key is defined elsewhere, but Breeze.js - if I'm understanding it correctly - is naively just checking the .key property without considering superclasses. But, I could be wrong since I'm so new to Breeze.
Addendum:
I don't think it's relevant here but in case it comes up, yes I do have an IQueryable as well on the WebApi Controller:
[HttpGet]
public IQueryable<Product> Products()
{
return _db.Context.Products;
}
Also, I recognize a potential workaround here is probably to discard TPT and make full classes for every Entity with no inheritance, but I'm really slimming my example down here - there's a lot of inheritance throughout the EF model that has to stay. If it's inheritance or Breeze, Breeze is getting the axe.
Edit: As of v 1.3.1 Breeze now DOES support inheritance.
Inheritance is coming but we don't have a fixed date just yet. Please vote for the feature on the Breeze User Voice. We take these suggestions very seriously.
I don't think manager.fetchEntityByKey works with inheritance - can any of the Breeze guys confirm whether this is correct? It seems to be picking up the inherited fields from my base class, but not the fields from my derived class. I can get the full object by using entityQuery but then I have to name every field. When I do that I'm still getting parse errors on ko bindings.

Resources