Base class as model in Web API - asp.net-web-api2

Lets say I have the following class structure:
public abstract class BaseModel
{
public string Name {get;set;}
}
public class ExtendedOne : BaseModel
{
prop string Prop1 {get;set;}
}
public class ExtendedTwo : BaseModel
{
prop string AnotherProp {get;set;}
}
Then I have the follow Web API endpoint:
[Route("api/Test")
public IHttpActionResult Post(BaseModel model)
{
//Do work
}
I want a client to be able to post an instance of each of the extended classes to this endpoint.
When I try this the model is just null.
Is what I'm trying possible?

This isn't working because BaseModel is abstract. WebApi is trying to deserialize the model parameter as a BaseClass instance but it can't be instantiated because it is abstract. Web Api doesn't know anything about the derived classes. It only knows about JSON that it thinks represents a BaseClass instance which, again, is invalid because it is abstract.

Whether it is possible:
This should be possible by the architecture of webapi.
Web api is not really concerned about the class you define in the input. It receives data which is (mostly) in JSON format. If you look at the fiddler request, the name of the class is not sent to the web api. It will be a simple Json which will look like
POST http://localhost/api/Values/SomeMethod HTTP/1.1
User-Agent: Fiddler
Host: localhost:49946
Content-Length: 63
Content-Type: application/json; charset=utf-8
{
name: "Some Name"
}
The media formatters present in the pipeline will see that name property is present and will create an object of BaseModel. They will usually ignore additional properties and hence it should work well for the inherited classes.
Where it could go wrong:
From the code fragment included, we cant get a hint of why it is not working. Probably you need to include more code. However, one possibility is: if you use the derived class and do not set the Name property of the derived class.
Something like
var extended = new ExtendedOne{Prop1="Value1"}// Name is left out
In this case, in case json serializer is configured to omit nulls (Name in this case) the json sent to webapi could be something like
POST http://localhost/api/Values/SomeMethod HTTP/1.1
User-Agent: Fiddler
Host: localhost:49946
Content-Length: 63
Content-Type: application/json; charset=utf-8
{
prop1: "Value1"
}
Since Name is not present, webapi thinks it is something different, does not construct the model and sets model as null. You can detect what is the problem in your case by simply looking at what is being passed in the fiddler.

Related

ASP.NET core - how to pass optional [FromBody] parameter?

How do I pass optional (nullable) [FromBody] parameter in ASP.NET Core (5.0)? If I don't send body in my request I get 415 Unsupported Media Type error. Can this be configured and if so, how to do it on a controller or action, rather than an app level? I presume it has to do something with model validation, but not sure. Thanks.
[HttpGet("[action]")]
public async Task<IActionResult> GetElementsAsync([FromBody] IEnumerable<int> elements = default)
{
var result = await dataService.GetData(elements);
return Ok(result);
}
EDIT: To clarify:
This is typical scenario and it works normally:
But passing empty body is returning 415 right away without even reaching action:
You can find a solution here:
https://github.com/pranavkm/OptionalBodyBinding
From this issue on github:
https://github.com/dotnet/aspnetcore/issues/6878
And from .net Core 5 you can use this one:
public async Task<IActionResult> GetElementsAsync([FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] IEnumerable<int> elements = default)
...
Also needed (from Pawel experience):
services.AddControllers(options =>{options.AllowEmptyInputInBodyModelBinding = true;})
Just add content-type in your request header. Without the content-type:application/json will appear 415 when body is empty.
No changes to your controller. Test in my side is ok.
I created a new asp.net core 5 api project and this is my controller:
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
namespace WebApi_net5.Controllers
{
public class HomeController : ControllerBase
{
[HttpGet("[action]")]
public string GetElementsAsync([FromBody] IEnumerable<int> elements = default)
{
return "value";
}
}
}
With ASP.NET Core 3.1, I could allow nullable optional parameters by implementing Nicola's suggestion:
services.AddControllers(options =>{options.AllowEmptyInputInBodyModelBinding = true;})
I will address some points that were not mentioned here.
To get rid of 415 without sending Content-Type you need to create your custom consumer of the Content-Type
https://stackoverflow.com/a/65813534/2531209
But I would say this is an overkill
If you pass Content-Type: application/json in your request header you will get "Body cannot be empty" (Tested on .NET 6) and only then #Nicola answer comes in handy.
From my tests it looks like modifications to the controller are not needed and only FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow) is enough with nullable type as parameter.
Or you can change nothing in your current code and send a header Content-Type: application/json with a body of {}. This will bypasses all of those errors, but this is not the most optimal solutions for public API's

Is it possible to deserialize RFC3339 full-date field of an request json body to some java date class within msf4j application?

I wonder if it is possible to deserialize RFC3339 full-date field of an request json body to some java date class within msf4j application?
My model looks like:
class Model {
java.util.Date date;
...
}
Service looks like:
#Path("/")
class Service {
#POST
#Path("/models")
#Consumes({"application/json"})
public Response(Model m) {
...
}
}
And when I do post, it returns me : "Error in executing request: POST /models"
AFAIK jackson deserialization doesn't supported by the MSF4J. You can do a feature request issue for jackson support

Is it possible to possible to achieve with Jersey a polymorphic deserialisation of json payloads on POST resources

I'd like a endpoint to accept on a single verb and path a json payload that varies by a tiny bit and that can be mapped to different objects. The variation is usually on the field value like the grant_type in the OAuth world.
I scrapped stackoverflow and google on this, I found this among other
JAX-RS polymorphic POST request: how should I write my JSON?
Polymorphism in JSON, Jersey and Jackson
But those question don't seem to be relevant to me, jackson deserialisation alone works fine for this payloads, but Jersey always refuses to init the servlet with an error that let me thinks there's no way around it.
#Path("parent")
interface Resource {
#POST
#Path("test")
String test(Payload1 payload1);
#POST
#Path("test")
String test(Payload2 payload1);
#Data
#JsonTypeName("payload1")
class Payload1 extends BasePayload {
String a;
}
#Data
#JsonTypeName("payload2")
class Payload2 extends BasePayload {
String b;
}
// #JsonTypeInfo(use= Id.MINIMAL_CLASS, include=As.PROPERTY, property="#class")
#JsonTypeInfo(use= Id.NAME, include=As.PROPERTY, property="#payload")
#JsonSubTypes({
#JsonSubTypes.Type(value = Payload1.class),
#JsonSubTypes.Type(value = Payload2.class)
})
class BasePayload {
}
However I get this message in an exception upon servlet initialisation. (edited message for clarity)
</pre><p><b>root cause</b></p><pre>
org.glassfish.jersey.server.model.ModelValidationException:
Validation of the application resource model has failed during application initialization.
[[FATAL] A resource model has
ambiguous (sub-)resource method for HTTP method POST and input mime-types as defined by
"#Consumes" and "#Produces" annotations
at Java methods
public java.lang.String xxx.Service.test(xxx.Resource$Payload1)
and
public java.lang.String xxx.Service.test(xxx.Resource$Payload2)
at matching regular expression /test.
These two methods produces and consumes exactly the same mime-types and
therefore their invocation as a resource methods will always fail.;
source='org.glassfish.jersey.server.model.RuntimeResource#59b668bf']
Note however that having a endpoint with the parent class of the payload works, but you have to handle the dispatch yourself.
#POST
#Path("test")
String test(BasePayload payload);
I'm using Spring-Boot 1.4 / Jersey 2.23.2 / Jackson 2.8.5
The JAX-RS runtime uses the following for matching requests to resource methods:
URI: Defined in the #Path annotation.
Request method: Defined by a resource method designator such as #GET, #POST, etc).
Media type: Defined in Accept and Content-Type headers, that are matched with the values defined in the #Produces and #Consumes annotations, respectively.
The content of the payload is not taken into account, so it makes your first method definition ambiguous.
As you already figured out, the following approach is the way to go:
#POST
#Path("test")
String test(BasePayload payload);

Content-Type case sensitivity in RESTEasy 3

I am developing a RestEasy client to connect to a 3rd party REST service which has defined its own custom media types. A made up example is
application/vnd.abc.thirdPartyThing-v1+json
Note the uppercase P in thirdParty.
I am using RESTEasy 3.0.11 for my client implementation. At the point where I make a POST call to the service my code looks like
Response response = target.request()
.post(Entity.<ThirdPartyThing>entity(
thing,
"application/vnd.abc.thirdPartyThing-v1+json"));
but RESTEasy sends to the server
Content-Type: application/vnd.abc.thirdpartything-v1+json
This is due to RESTEasy's MediaTypeHeaderDelegate class's toString() method, which lowercases the type and subtype MediaTypeHeaderDelegate. This should be correct, or at least unimportant, as RFC-1341 states that Content-Type values are case-insensitive - RFC-1341
Unfortunately the 3rd party service is checking the Content-Type in a case sensitive manner and so returning a 415 UNSUPPORTED MEDIA TYPE error. I've tested using curl which doesn't alter the content-type value and confirmed that it's a case issue. application/vnd.abc.thirdPartyThing-v1+json works, application/vnd.abc.thirdpartything-v1+json does not.
I'm in the process of raising a ticket, but in the meantime is there any way to override RESTEasy's default behaviour and send Content-Type headers without lowercasing the value?
Thanks for reading.
I could reproduce this behavior with RESTeasy 3.0.6.Final and would not expect it. Maybe you could check their JIRA if this has already been discussed or open an issue. I once had problems on the server side because a 2.x version of RESTeasy was checking the charset attribute of the Content-Type header case-sensitive. This was also changed.
You could solve this problem by a really ugly workaround: Overwrite the header again in a ClientRequestFilter.
public class ContentTypeFilter implements ClientRequestFilter {
private Map<String, String> contentTypes;
public ContentTypeFilter() {
contentTypes = new HashMap<>();
contentTypes.put("text/foo", "text/Foo");
}
#Override
public void filter(ClientRequestContext requestContext) throws IOException {
String contentType = requestContext.getHeaderString("Content-Type");
if (contentTypes.containsKey(contentType)) {
requestContext.getHeaders().putSingle("Content-Type", contentTypes.get(contentType));
}
}
}
Don't forget to register this Filter:
Client client = ClientBuilder.newClient().register(ContentTypeFilter.class);

WCF MesageContract - customizing outgoing SOAP message - multiple bodies

I have to call a Web service that is extremely demanding (unstandard) regarding the SOAP message format that it chooses to process. I have no control over the server side implementation and there is no WSDL available, all I have is an intercepted message attached bellow.
My first thought was WCF+MessageContract, but whatever I do with the last, I can't seem to get the right result. Outgoing messages should look like the one bellow. The most tricky part seems to be multiple body contents ("ProxyInfo" and "PayloadInfo" bellow). Besides that I also can not get WCF to remove "Action" element from SOAP message header. I realize that it is a vital element to WCF, but I doubt that I could persuade Web service to accept it. The reply will probably be another story, but I will cross that bridge when I get to it.
Currently I am considering custom serialization and post-/pre- processing of outgoing/incoming messages. In the worst case I guess I will have to do Web requests as well as serialization manually. Please help, I am getting realy desperate...
<?xml version="1.0" encoding="UTF-8" ?>
<e:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsse="http://docs.oasisopen.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<e:Header>
<ServiceHeader xmlns="http://services/serviceheader" e:actor="http://services/loadbalancer" >
<ServiceLevel>
<Type>HIGH</Type>
<Method>FIFO</Method>
</ServiceLevel>
</ServiceHeader>
</e:Header>
<e:Body>
<ProxyInfo xmlns="http://services/proxyinfo">
<Server>
<Address>proxy1:8080</Address>
<AppId>case_delegator</AppId>
</Server>
</ProxyInfo>
<PayloadInfo xmlns="http://services/payload">
<GetConfirmation>
<CaseId>
<Id>9728DFC889874CC8B1505D91E33FCFCD</Id>
</CaseId>
</GetConfirmation>
</PayloadInfo>
</e:Body>
</e:Envelope>
If you don't want to use Address header you have to use binding without WS-Addressing. In your case use BasicHttpBinding. It will not use WS-Addressing and Action SOAP header but instead it will use SOAPAction HTTP header.
For your message contract try to use something like this:
[DataContract]
public class ServiceHeader
{
...
}
[DataContract]
public class ProxyInfo
{
...
}
[DataContract]
public class PayloadInfo
{
...
}
[MessageContract(IsWrapped = false)]
public class Request
{
[MessageHeader(Namespace="http://services/serviceheader")]
public ServiceHeader ServiceHeader { get; set; }
[MessageBodyMember(Namespace="http://services/proxyinfo")]
public ProxyInfo ProxyInfo { get; set; }
[MessageBodyMember(Namespace="http://services/payload")]
public PayloadInfo PayloadInfo { get; set; }
}
The strange thing is the actor attribute in ServiceHeader. Your message doesn't define namespace for prefix e so the message is not valid XML.