Table of Contents

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 start is true on 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 allowed is true on 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:

  1. Add the new route alongside the existing one (for example, add addons/v2 next to addons). Adding a route is backward compatible.
  2. Mark the old route (addons) obsolete, with an obsolescence message pointing to the new route, and optionally an Error after version to plan the end of support and an obsolescence URL linking to migration documentation.
  3. Consumers see the old route reported as deprecated in 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.

See OData documentation.

$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.

See OData documentation.

$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.

See OData documentation.

$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.

See OData documentation.

$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 $select on a reference
  • apply a nested $filter or $orderby on a collection

Examples:

  • GET /api/v1/OrderListView?$expand=customer
  • GET /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 $select for root properties
  • use a nested $select inside $expand for 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.

See OData documentation.

$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.

See OData documentation.

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.
    • null is not a valid value.
  • For PUT:
    • Omitting the property or setting it to null preserves 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).