Web API and routes
This section lets you customize the exposure of the entity view.
Authorization
Exposed as API
This property defines whether the controller exposing the entity view will be generated.
When false, the controller is not generated and authorizations will only serve as default values for authorizations on the UI views based on this entity view.
When the entity view is only used for an embedded collection or reference, you can set Exposed as API to false. This is because the entity view will only be used via the parent entity view in which it is embedded.
Internal use only
This property indicates whether the access to the exposed controller is restricted to local incoming addresses.
When true, requests from external servers will not be allowed.
The default value is false.
Display in API explorer
This property indicates whether the entity view REST API endpoinds are displayed in the documentation UI (e.g. Swagger UI).
When false, REST API endpoints are ignored by the API explorer.
The default value is true.
Note
When the InternalUseOnly property is set to true, the DisplayInApiExplorer property is automatically set to false.
Read allowed
When true, the server exposes a Get and a GetAll REST API for reading records (HTTP GET). Additionally, when an Image property is present in the entity view, a GetFile REST API is generated.
When false :
- UI views based on this entity view are not be able to load data on start and you have to write your own data loading code in them
- if
Load on startistrueon one of these UI views, an error is logged during the application generation
Creation allowed
When true, the server exposes a Post and a BulkPost REST API for REST APIs for creating records (HTTP POST).
When false :
- UI views based on this entity view are unable to create new records
- if
Create allowedistrueon one of these UI views, a warning message will be logged during the application generation
Update allowed
When true, the server exposes a 'Put' REST API for updating records (HTTP PUT).
Deletion allowed
When true, the server exposes a 'Delete' REST API for deleting records (HTTP DELETE).
Routes
Root route
You can customize the root route that will be used to build the API routes. By default, it uses the name of the entity view.
The {rootRoute} variable is available to use in your custom routes, it will be replaced by the configured value when generating them.
Open Api model base name
The application exposes a OpenAPI JSON file (swagger.json).
This definition is used by SwaggerUI to visualize the documentation of the API.
It can also be used to generate client code with a tool like NSwagStudio.
This tool generates client classes using names inside the swagger.json file.
To make the code more readable and consistent (particularly when routes have been customized), it is advisable to update the base name for the models.
Warning
Several entity views could share the same root route. To prevent the generation of duplicate elements in the swagger.json file,
the generator renames models and emits a warning.
For example, if entity views CustomerView and CustomerAddressView share the same root route customers, a warning will be emitted
and the swagger will contain models named Customers1XXXModel and Customers2XXXModel.
You can solve this warning by forcing Api model base name on one of the entity views.
Warning
The generator emits a warning even if two entity views on the same entity expose different routes.
This behavior is normal in the current version because the PUT model is also used if a server method has a parameter with the entity view interface as its type.
You can solve this warning by forcing Api model base name on one of the entity views.
Example of a CustomerAddressView entity view whose root route has not been customized
Code using the NSwag-generated client would look like this:
CustomerAddressViewPostModel newItem = new();
CustomerAddressViewGetModel addedItem = await client.CustomerAddressViewPOSTAsync(newItem);
CustomerAddressViewPutModel putItem = ...;
await client.CustomerAddressViewPUTAsync(identifier, putItem);
Note that classes generated for input/output parameters use the exact name of the entity view with a suffix.
Example of a CustomerAddressView entity view with customers-addresses in root route
Code using the NSwag-generated client would look like this:
CustomersAddressesPostModel newItem = new();
CustomersAddressesGetModel addedItem = await client.CustomersAddressesPOSTAsync(newItem);
CustomersAddressesPutModel putItem = ...;
await client.CustomersAddressesPUTAsync(identifier, putItem);
Note that classes generated for input/output parameters use the root route with a suffix.
Example of a CustomerAddressView entity view with customer-addresses in root route and address in Open Api model base name
Code using the NSwag-generated client would look like this:
AddressPostModel newItem = new();
AddressGetModel addedItem = await client.CustomersAddressesPOSTAsync(newItem);
AddressPutModel putItem = ...;
await client.CustomersAddressesPUTAsync(identifier, putItem);
Note that classes generated for input/output parameters use the model base name of the entity view with a suffix.
Custom routes and property binding
All exposed routes, based on the defined authorizations, are listed and can be customized with constant and variable parts.
The following routes require the entity view's key to be provided either in the route itself or in the query string :
- Get
- GetFile
- Put
- Delete
Note
Any key property not present in the route will be retrieved from the query string. To find out more about binding sources in ASP.NET Core controllers, you can read this article.
The {$keys} variable represents the entity view's full key, whatever its shape — simple, composite, primary, or alternate, including an alternate key resolved through navigation to a related entity. It is resolved automatically when generating the routes, so it is the recommended way to reference the key: Neos Studio's Add routes action and the default routes both use it.
You can also reference an individual property directly, in camel case and between braces, to build a route that targets a specific property. The {rootRoute} variable remains available as usual.
Note
Key placeholder casing is normalized at generation, so a placeholder written as {OrderId} or {orderId} both resolve to the same route. camelCase is the canonical form used by the generated routes and the OpenAPI document.
Examples :
{rootRoute}/{$keys}{rootRoute}/{country}/{postalCode}/{name}order/{orderId}
Disabling specific routes
It can sometimes be useful to disable a specific route.
For example, we might not want to expose a GetAll API on an entity view that provides lots of details with embedded references and collections as it can be very costly in terms of performance.
This can be done by unchecking the Exposed as API on a specific route.
Warning
Keep in mind that disabling specific routes may cause UI views based on that entity view to malfunction. We recommend only using this feature on entity views specifically designed for API calls.
Per-route API-surface options
Each route can be configured independently with two options that refine its exposure without removing it. These options do not change whether the endpoint exists (that is controlled by Exposed as API); they refine an existing, working endpoint.
Important
These per-route options — the two flags below and the obsolescence settings further down — apply only to additional routes (routes whose path differs from the technical route). The technical route of each operation type is always generated for the application's own screens and generated client; it lives at the controller path (api/v{version}/<EntityViewName>). It is the official route while no additional route of that type exists (and is shown in the API explorer); as soon as you add an additional route, the technical route stays generated for the front-end but is hidden from the API explorer for readability. It cannot carry these options: authoring a route on its path would produce a second action at the same path (a routing clash), and the technical route's documentation comes from the entity view itself, not from a route. So setting an obsolescence value, Internal use only, Display in API explorer, or documentation on a route that lands on that path is rejected when you save. Configure these on an additional route instead. Note that customizing the entity view's root route relocates the public route to a distinct path (e.g. /users/settings/reports): that relocated route is an additional route and can carry these options. The technical route's exposure is still controllable through the entity view's own settings, and you can disable it entirely with Exposed as API.
- Internal use only : when
true, this route is restricted to local incoming addresses (requests from external servers are rejected). The effective restriction is the entity-view value OR the route value: a route can only add the restriction, never relax it. If the entity view is internal use only, all its routes are internal use only regardless of their own value. - Display in API explorer : when
false, this route is hidden from the documentation UI (e.g. Swagger UI) while still working. The effective visibility is the entity-view value AND the route value: a route can only hide itself, never force itself to show on an entity view that is already hidden from the API explorer.
These three settings are easy to confuse. The table below summarizes how they differ:
| Setting | Endpoint generated ? | Reachable ? | Visible in API explorer ? |
|---|---|---|---|
| Exposed as API | No when false |
No (the endpoint does not exist) | No |
| Internal use only | Yes | Local incoming addresses only | Yes |
| Display in API explorer | Yes | Yes | No when false |
Note
Exposed as API decides whether the controller action is generated at all; the other two only refine a route that is already exposed.
Deprecating a route
A route can be flagged as obsolete. The four obsolescence settings are the same as for other obsolete metadata (see Obsolescence):
- Obsolete : marks the route as obsolete.
- Obsolescence message : explains why the route is obsolete and which route to use instead.
- Error after version : the obsolete route is logged as a warning up to and including this version, and as an error for any strictly later module version.
- Obsolescence URL : an optional link to documentation about the migration.
An obsolete route still works, but the generator emits a [System.Obsolete(...)] attribute on the corresponding controller action, which is reported as deprecated: true in the generated OpenAPI / Swagger documentation. This signals consumers that they should migrate.
Evolving a route without breaking consumers
Because a route already integrated by external consumers must keep working, modifying or removing an existing route is a breaking change. The intended evolution path is therefore to add a new route and deprecate the old one rather than editing or deleting it:
- Add the new route alongside the existing one (for example, add
addons/v2next toaddons). Adding a route is backward compatible. - Mark the old route (
addons) obsolete, with an obsolescence message pointing to the new route, and optionally anError after versionto plan the end of support and an obsolescence URL linking to migration documentation. - Consumers see the old route reported as
deprecatedin Swagger / OpenAPI and migrate to the new one at their own pace.
Note
A route cannot be deleted if it is the last route of its operation type: every operation type always keeps at least one route. Deprecation, not deletion, is the way to retire a route while keeping a replacement in place.
Editing per-route options in Neos Studio
In the Web API panel of the entity view, the routes are listed in a grid. Each row's … (More) menu gives access to:
- Advanced options : the per-route Internal use only and Display in API explorer flags.
- Obsolescence : the obsolescence settings described above.
An obsolete route is shown with a highlighted row in the grid so you can spot deprecated routes at a glance.
Handling API endpoints that return a list with OData
OData specifications are implemented for the API endpoints that retrieve a list of entity views (e.g. GET /OrderListView). There are some important details to note.
$top
The $top parameter is used to specify the maximum number of items to be retrieved.
When this parameter is not defined, only the first 10 items are retrieved.
If the entity view is bound to an entity, this parameter is applied to the SQL query. Otherwise, it is applied in memory after the Retrieving event rule execution.
$skip
The $skip parameter is used to specify how many entities should be skipped before starting to retrieve results. This parameter is often used in conjunction with $top to paginate the results.
If the entity view is bound to an entity, this parameter is applied to the SQL query. Otherwise, it is applied in memory after the Retrieving event rule execution.
$filter
The $filter parameter is used to filter the entities to be retrieve by specifying conditions.
If the entity view is bound to an entity, this parameter is applied to the SQL query. Otherwise, it is applied in memory after the Retrieving event rule execution.
$select
The $select parameter is used to specify which properties of an entity view should be included in the response.
If the entity view is bound to an entity without Retrieved event rule, this parameter is applied to the SQL query. Otherwise, the properties is selected in memory after the Retrieved event rule execution.
In the Retrieving event rule, It is possible to know if the result will be transformed using the boolean property WillTransform available on the arguments.
$expand
The $expand parameter is used to include navigation properties in the response and to control how nested data is shaped.
In Neos, entity view navigation properties are generated as OData navigation properties and are auto-expanded by default. This means that references and collections are returned even when $expand is omitted.
$expand is still useful when you want to:
- apply a nested
$selecton a reference - apply a nested
$filteror$orderbyon a collection
Examples:
GET /api/v1/OrderListView?$expand=customerGET /api/v1/OrderListView?$expand=customer($select=customerId,name)GET /api/v1/OrderListView?$expand=customer($expand=address($select=city,country))GET /api/v1/OrderListView?$expand=orderDetails($select=productId,quantity;$expand=product($select=productId,name))
Interaction with $select
$select applies to the root level only.
When you want to restrict properties on the root entity view and also restrict properties on an expanded navigation property, combine both operators:
- use the top-level
$selectfor root properties - use a nested
$selectinside$expandfor the navigation property
Example:
GET /api/v1/OrderListView?$select=orderId,orderDate,customer&$expand=customer($select=customerId,name)
In this example, orderId and orderDate are selected on OrderListView, while customerId and name are selected on the expanded customer reference.
$apply
The $apply parameter is used to specify data transformation and aggregation operations to be applied. It can be used to aggregate or group data, possibly using a filter.
If the entity view is bound to an entity without Retrieved event rule, this parameter is applied to the SQL query. Otherwise, the transformation is performed in memory after the Retrieved event rule execution.
In the Retrieving event rule, it is possible to know if the result will be transformed using the boolean property WillTransform available in the arguments.
The $apply parameter is applied before all other parameters, which means that other parameters will be applied to the transformed data.
$count
The $count parameter is used to request the total number of items that match the query without returning the items themselves.
If the entity view is bound to an entity, this parameter is applied to the SQL query. Otherwise, it is applied in memory after the Retrieving event rule execution.
You can control the behavior of a count query in the Retrieving event rule when this rule cancels the query execution.
The IsCountQuery property is available in the Retrieving event rule arguments and can be used to know if the query is a count query.
The SetCount method is available in the arguments of the Retrieving event rule and can be used to set the count value only when the query is a count query. If the rule is canceled, the count value returned is the one defined in the rule, otherwise it is the total number of items returned.
Example: The following code sets the count to 42 and cancels the query execution, the OData response will be "42".
if (args.IsCountQuery)
{
args.SetCount(42);
args.SetItems(new List<Order>()); //SetItems cancels the query execution
}
Note
The Retrieved event rule is not executed for count queries.
Embedded collections in PUT REST APIs
Starting from version 2.3, collections are no longer mandatory in PUT API calls. You can pass null or omit the property entirely to avoid modifying the collection.
The table below explains the behavior based on the value provided for a collection in the API request:
| Collection property value in JSON | POST | PUT |
|---|---|---|
| Property not present | No entries are created | No changes are applied (same behavior as null) |
null |
Invalid value (Property must be omitted or explicitly set to an empty array) |
No changes are applied |
[] (empty array) |
No entries are created | All existing entries are deleted |
| Array with data | Data in the array is inserted | Data in the array is inserted/updated Existing entries not present in the array are deleted |
Key Notes:
- For POST:
- Use an empty array
[]or omit the property to create no entries. nullis not a valid value.
- Use an empty array
- For PUT:
- Omitting the property or setting it to
nullpreserves existing data. - An empty array
[]triggers a full deletion of existing entries. - Providing data performs an upsert (update existing entries, add new ones, remove entries not in the array).
- Omitting the property or setting it to