I'm implementing an ODataController. It's OData V3 for compatibility reasons with Breeze.js:
using System.Web.Http.OData;
public class OffersController : ODataMetadataController
{
...
Somwhere in the middle I want to implement merge/patch as seen in examples:
[AcceptVerbs("PATCH", "MERGE")]
public IHttpActionResult Patch([FromODataUri] int key, Delta<BOOffer> delta)
{
...
For some reason I'm getting the following error:
No MediaTypeFormatter is available to read an object of type 'Delta`1' from content with media type 'application/json'.;
Ok. Delta<> is OData related, I would need an OData formatter for that.
Iterating through the formatters (as on this page), it does not seem to be an OData formatter there:
JsonMediaTypeFormatter
CanReadType: True
CanWriteType: True
Base: BaseJsonMediaTypeFormatter
Media Types: application/json, text/json
XmlMediaTypeFormatter
CanReadType: True
CanWriteType: True
Base: MediaTypeFormatter
Media Types: application/xml, text/xml
FormUrlEncodedMediaTypeFormatter
CanReadType: False
CanWriteType: False
Base: MediaTypeFormatter
Media Types: application/x-www-form-urlencoded
JQueryMvcFormUrlEncodedFormatter
CanReadType: True
CanWriteType: False
Base: FormUrlEncodedMediaTypeFormatter
Media Types: application/x-www-form-urlencoded
Should I register this formatter? Shouldn't it be automatic? If I need to register it manually, how?
If I change the input parameter form Delta<BOOffer> to BOOffer the method gets called, but since only the changed properties are sent, this is not something I can use.
I configure my controller in app_start like this:
System.Web.Http.OData.Builder.ODataConventionModelBuilder builderV3 = new System.Web.Http.OData.Builder.ODataConventionModelBuilder();
var entitySetConfigV3 = builderV3.EntitySet<BOOffer>("Offers");
entitySetConfigV3.EntityType.HasKey(o => o.ID);
config.Routes.MapODataServiceRoute(
routeName: "odata/v3",
routePrefix: "odata/v3",
model: builderV3.GetEdmModel(),
batchHandler: new DefaultODataBatchHandler(GlobalConfiguration.DefaultServer));
The reason for this was referencing both System.Web.Http.OData (odatav3) and System.Web.OData (odatav4) in the project and mixing up references.
the System.Web.Http.OData.Formatter.ODataMediaTypeFormatter is not configured to be able to serialize into System.Web.OData.Delta<T>.
Using to System.Web.Http.OData.Delta<T> worked as intended.
Be careful about referencing different OData versions in one projects.
Related
is there a way to access metadata from controller methods?
For example, I add metadata to a controller class with SetMetadata() - e.g. from a decorator.
I know how to access metadata in a guard. You need to inject reflector and guard.canActivate() has ExecutionContext parameter.
canActivate(context: ExecutionContext): boolean {
metadata: SomeType = this.reflector.get<EnabledFeatures>(SOME_METADATA_KEY, [context.getClass()]);
}
To get metadata I need 2 components: Reflector and ExecutionContext.
I can inject Reflector into controller, but how can I access ExecutionContext from a controller?
Assuming we set some metadata on Controller with #SetMetadata :
#Controller({...})
#SetMetadata('roles', ['admin'])
We can have access to it, by creating our custom param decorator:
export const Roles = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
// get roles metadata from #Controller class
const roles = Reflect.getMetadata('roles', ctx.getClass());
return roles;
},
);
And then we can use it on controller's method :
#Get()
getInfo(#Roles() roles): string {
// roles = ['admin']
//...
}
Some notes
#SetMetadata not a good practice
Usage of #SetMetadata directly is not really a good practice. Prefer to create a specific decorator (for maintenance and readability of code) :
export const SetRoles = (...roles: string[]) => SetMetadata('roles', roles);
...
#Controller({...})
#SetRoles('admin')
export class MyController {...}
Reflect.getMetadata API vs Injector
Even if Reflect.getMetadata is in fact called by Reflector API of NestJS, it could be changed in the future.
So if we want to deal with only public/documented API of NestJS, we can:
use a global guard, which will inject Injector,
get metadata with ExecutionContext
and then set result in Request instance.
An other custom param decorator will retrieve data from Request and return it.
More complicated, but without using direct call to Reflect.getMetadata API.
I have implemented a REST API which is documented via OpenAPI. In detail, the specification is generated from Java source code using springdoc-openapi.
However, I have the need for accepting patches. So, I
/customers/{id}:
patch:
tags:
- Customers
summary: Updates an existing user
description: Updates an existing user
operationId: partialUpdateMergePatchCustomer
parameters:
- name: id
in: path
description: The numeric ID of the customer
required: true
schema:
type: integer
format: int64
requestBody:
content:
application/merge-patch+json:
schema:
type: object
required: true
I have read through the OpenAPI specification/documentation but did not find any information regarding JSON Merge Patch, JSON Patch or the like.
I am experiencing the following issues I'd like to overcome:
There is no clue information of which type of resource is to be patched here. The rest of the API, esp. in SwaggerUI, is well documented and has schema references everywhere. How can I specify the resources schema that actually is to be patched?
Apart from the pure schema reference, in my API, some resources have read-only fields or there may be other restrictions that apply to certain fields that I'd like to document. Is there a better way as doing it in natural language as part of the original schema?
I had the same issue as your issue #1. That is, providing more info to swagger on the JsonPatch RequestBody parameter than the default generated by springdoc-openapi. I'm posting my solution here for others who may run into the same issue.
My solution was to add the following annotation to the RestController patch method:
#PatchMapping(value = "/{id}", consumes = "application/json-patch+json")
#io.swagger.v3.oas.annotations.parameters.RequestBody(content = #Content(array = #ArraySchema(schema = #Schema(implementation = JsonPatchSchema.class))))
public void patch(#PathVariable #Min(0) long id, #RequestBody JsonPatch patch) {
Where JsonPatchSchema contains the requested format:
public class JsonPatchSchema {
#NotBlank
public Op op;
public enum Op {
replace, add, remove, copy, move, test
}
#NotBlank
#Schema(example = "/name")
public String path;
#NotBlank
public String value;
}
Using the #Schema annotation you could provide more detail on what is allowed, see the Schema javadoc. For example the pattern parameter allows you to define a regular expression. I haven't found a way to generate the allowed content unfortunately.
I am developing a rest application.
Some endpoints require a custom header parameter, not related to authorisation. I created a custom annotation using jax-rs NameBinding. Here is an usage example:
#GET
#RequiresBankHeader
public int get(
#HeaderParam("bank")
#Parameter(ref = "#/components/parameters/banks")
String bank) {
return someService.getSomeInformation();
}
There is a provider that intercepts this call and do some routine using the information in the header parameter.
The problem is that I have to repeat '#HeaderParam("bank") #Parameter(ref = "#/components/parameters/banks") String bank' everywhere, just so it appears in Swagger, even though the service classes do not need it. I was able to at least reuse the parameter definition with ref = "#/components/parameters/banks", and declaring it in the OpenAPI.yml file, that Quarkus merges with generated code very nicely.
But I also want to create and interceptor to dynamically add this do the OpenApi definition whenever RequiresBankHeader annotation is present.
Is there a way to do it?
I dont think you can use interceptors to modify the generated Openapi schema output.
If all methods on a given endpoint require some parameter, you can specify it on class level like so:
#Path("/someendpoint")
public class MyEndpoint {
#HeaderParam("bank")
#Parameter(name = "bank")
String bank;
#GET
public Response getAll() {return Response.ok().build()}
#GET
#Path("{id}")
public Response someMethod(#PathParam("id") String id) {return Response.ok().build();}
}
As mentioned by Roberto Cortez, the MP OpenAPI spec provides a programmatic way to contribute metadata to the openapi.yml file.
It is not possible to detect an annotation in the JAX-RS endpoint definition, but it was good enough to automate what I needed. Since all methods that had the RequiresBankHeader return the same Schema, I was able to hack it like this:
public class OpenApiConfigurator implements OASFilter {
#Override
public Operation filterOperation(Operation operation) {
operation.getResponses().getAPIResponses().values().stream().
map(APIResponse::getContent).
filter(Objects::nonNull).
map(Content::getMediaTypes).
flatMap(mediaTypes -> mediaTypes.values().stream()).
map(MediaType::getSchema).
filter(Objects::nonNull).
map(Schema::getRef).
filter(Objects::nonNull).
filter(ref -> ref.contains("the common response schema")).
findAny().
ifPresent(schema -> {
ParameterImpl parameter = new ParameterImpl();
parameter.setRef("#/components/parameters/banks");
operation.addParameter(parameter);
});
return operation;
}
OpenApiConfigurator should be configure in the application properties, using mp.openapi.filter=com.yourcompany.OpenApiConfigurator
I'm having problems defining a function for odata4. The default get would work but I want to require a user parameter so a client set can be determined, other tables are involved so LINQ is required, I also return a DTO instead of the default table info (EF). Below is the code. I get a "Invalid EntitySetPath detected. 'bindingParameter/Client' is not a valid entity set path for procedure 'Default.GetClients'." What am I doing wrong here?
WebApiConfig
public static void Register(HttpConfiguration config)
{
ODataModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<client>("Client").EntityType.HasKey(p => p.int_id);
var function = builder.Function("GetClients");
function.Parameter<string>("user");
function.ReturnsCollectionFromEntitySet<client>("Client");
builder.EntitySet<ClientDTO>("ClientDTO");
config.MapODataServiceRoute(
routeName: "ODataRoute",
routePrefix: null,
model: builder.GetEdmModel());
WebApp.Controller
[ODataRoute("GetClients(user={user})")]
[EnableQuery(PageSize=25)]
public IQueryable<ClientDTO> GetClients([FromODataUri] string user)
{
var clients = (from c in db.clients
join ...
If your OData controller is returning the DTO, the function should look like this:
var function = builder.Function("GetClients");
function.Parameter<string>("user");
function.ReturnsCollectionFromEntitySet<ClientDTO>("Client");
With your current setup, your OData route of GetClients says that it is returning a ClientDTO object, but your WebApiConfig is stating you are returning a Client object.
As the Entity Collection being returned is actually the DTO. The part that shows ("Client") is simply how the OData service will report the name of the object to the project consuming the OData service. For my own personal sanity, I typically include DTO as well so I know when I'm using a DTO and when I'm using a direct entity. So in my own setup i'd return ("ClientDTO"), just a personal preference.
I'm starting to learn Json.NET, but I'm having trouble using its serializer. I have a new MVC4 project with a Web.API service:
public class PTE_TestsController : ApiController {
PTE_TestsRepository _repository = new PTE_TestsRepository();
// GET /api/PTE_Tests/5
public HttpResponseMessage<string> Get(int id) {
try {
PTE_Test test = _repository.GetTest(id);
return new HttpResponseMessage<string>(JsonConvert.SerializeObject(test));
} catch {
return new HttpResponseMessage<string>(HttpStatusCode.NotFound);
}
}
}
JsonConvert.SerializeObject() works as expected and returns a string. My Web.API controller returns that as part of an HttpResponseMessage. The end result, when viewed in Fiddler, is not JSON data, but JSON data being serialized again (I think):
"{\"ID\":1,\"Name\":\"Talar Tilt\",\"TagID\":1,\"PracticeID\":1,
\"SpecificAreaID\":1,\"TypeID\":1,\"VideoID\":1,\"PicID\":1}"
Does someone know how to turn off the default serializer so I can use Json.NET directly? By the way, I'm not using the default serializer because I can't figure out how to make it work with complex objects (PTE_Test will eventually contain members of type List).
Alternately, it will also solve my problem if someone can explain how to use the default serializer with complex objects. MSDN's explanation didn't help me.
Rick Strahl has a blog on that here with a code that works.
As others have pointed out, you need to create a formatter and replace the DataContractSerializer with the JSON.NET serializer. Although, if you're not in a rush for JSON.NET specifically, rumor has it that next beta/rc is going to have support for JSON.NET built in.
Conceptually, however, you're missing part of the magic of WebAPI. With WebAPI you return your object in it's native state (or IQueryable if you want OData support). After your function call finishes the Formatter's take over and convert it into the proper shape based on the client request.
So in your original code, you converted PTE_Test into a JSON string and returned it, at which point the JSON Formatter kicked in and serialized the string. I modified your code as follows:
public class PTE_TestsController : ApiController {
PTE_TestsRepository _repository = new PTE_TestsRepository();
public HttpResponseMessage<PTE_Test> Get(int id) {
try {
PTE_Test test = _repository.GetTest(id);
return new HttpResponseMessage(test);
} catch {
return new HttpResponseMessage<string>(HttpStatusCode.NotFound);
}
}
}
Notice how your function returns PTE_Test instead of string. Assuming the request came in with a request header of Accept = application/json then the JSON formatter will be invoked. If the request had a header of : Accept = text/xml the XML formatter is invoked.
There's a decent article on the topic here. If you're a visual learner, Scott Gu shows some examples using fiddler in this video, starting around 37 minutes. Pedro Reys digs a little deeper into content negotiation here.
The way to do this is to use formatters.
Check out: https://github.com/WebApiContrib/WebAPIContrib/tree/master/src/WebApiContrib.Formatting.JsonNet.
Json.NET support will be in the RC release of Web API.