This document is for current release version 1.0 (First GA).
For document of version 0.6.0, refer to Version 0.6.0 document
1. GETTING STARTED
-
1.1 Introduction
OData stands for the Open Data Protocol. It was initiated by Microsoft and is now an ISO and OASIS standard. OData enables the creation and consumption of RESTful APIs, which allow resources, defined in a data model and identified by using URLs, to be published and edited by Web clients using simple HTTP requests.
RESTier is a RESTful API development framework for building standardized, OData V4 based RESTful services on .NET platform. It can be seen as a middle-ware on top of Web API OData. RESTier provides facilities to bootstrap an OData service like what WCF Data Services (which is sunset) does, beside this, it supports to add business logic in several simple steps, has flexibility and easy customization like what Web API OData do. It also supports to add additional publishers to support other protocols and additional providers to support other data sources.
For more information about OData, please refer to the following resources:
OASIS Open Data Protocol (OData) Technical Committee
For more information about OData .Net Library, refer to OData .Net Library document.
For more information about Web API OData Library, refer to Web API OData Library document.
-
1.2 Bootstrap an OData service
After RESTier 0.4.0, creating an OData service has never been easier! This subsection shows how to create an OData V4 endpoint using RESTier in a few minutes. AdventureWorksLT will be used as the sample database and Entity Framework as the data proxy.
Create a project and a web app
1.Open Visual Studio 2015 or Visual Studio 2013. If you use Visual Studio 2013, the screens will be slightly different from the screenshots, but the procedures are essentially the same.
2.From the File menu, click New > Project.
3.In the New Project dialog box, click C# > Web > ASP.NET Web Application.
4.Clear the Add Application Insights to Project check box.
5.Name the application HelloWorld.
6.Click OK.
7.In the New ASP.NET Project dialog box, select the Empty template.
8.Select the Web API check box.
9.Clear the Host in the cloud check box.
Install the RESTier packages
1.In the Solution Explorer window, right click the project HelloWorld and select Manage NuGet Packages….
2.In the NuGet Package Manager window, select the Include prerelease checkbox.
3.Type Restier in the Search Box beside and press Enter.
4.Select Microsoft.Restier and click the Install button.
5.In the Preview dialog box, click the OK button.
6.In the License Acceptance dialog box, click the I Accept button.
Generate the model classes
1.Download AdventureWorksLT2012_Data.mdf and import it into the
(localdb)\MSSQLLocalDB
database.2.In the Solution Explorer window, right click the Models folder under the project HelloWorld and select Add > New Item.
3.In the Add New Item - HelloWorld dialog box, click C# > Data > ADO.NET Entity Data Model.
4.Name the model AdventureWorksLT.
5.Click the Add button.
6.In the Entity Data Model Wizard window, select the item Code First from database.
7.Click the Next button.
8.Click the New Connection button.
9.In the Connection Properties dialog box, type (localdb)\MSSQLLocalDB for Server name.
10.Select AdventureWorksLT2012 for database name.
11.After returning to the Entity Data Model Wizard window, click the Next button.
12.Select the Tables check box and click the Finish button.
Configure the OData Endpoint
In the Solution Explorer window, click HelloWorld > App_Start > WebApiConfig.cs. Replace the
WebApiConfig
class the following code.Note : DbApi was renamed to EntityFrameworkApi from version 0.5.
The configuration “config.Filter().Expand().Select().OrderBy().MaxTop(null).Count();” is enabling filter/expand/select/orderby/count on all properties, starting 1.0 release, there are more smaller granularity control on the properties which can be used in query option, and all properties are disabled to be used by default. User can add configured in CLR class or during model build to configure which properties are allowed to be used in filter/expand/select/orderby/count. Refer to Model bound document for more details.
After these steps, you will have finished bootstrapping an OData service endpoint. You can then Run the project and an OData service is started. Then you can start by accessing the URL
http://localhost:<ISS Express port>/api/AdventureWorksLT
to view all available entity sets, and try with other basic OData CRUD operations. For instance, you may try querying any of the entity sets using the$select
,$filter
,$orderby
,$top
,$skip
or$apply
query string parameters.
2. FEATURES
-
2.1 Security
Authentication and Authorization
REStier is transparent to security now, any security configurations / methodology working for Web APi will work for RESTier.
One item is special in RESTier, there is only one controller named RESTier controller, and user can implement other additional controllers which extends ODataController class, in order to have consistent authentication for all these controllers, user need to configure Authentication and Authorization filter at global level.
Restier also provides capability to inspect each request, refer section 2.8 and 2.9 for more details.
For Web Api security, refer to Web Api Security document.
Note: Restier uses asynchronous call to the provider layer (entity framework), this means by default the principal used to logic the application will not passed to call to provider, if the application need to pass principal from application to provider layer, refer to this link on the detail configuration.
-
2.2 Entity Set Filters
Entity set filter convention helps plug in a piece of filtering logic for entity set. It is done via adding an
OnFilter[entity type name](IQueryable<T> entityset)
method to theApi
class.1. The filter method name must be OnFilter[entity type name], ending with the target entity type name. 2. It must be a **protected** method on the `Api` class. 3. It should accept an IQueryable<T> parameter and return an IQueryable<T> result where T is the entity type.
Supposed that ~/AdventureWorksLT/Products can get all the Product entities, the below OnFilterProduct method will filter some Product entities by checking the ProductID.
Then replace the
EntityFrameworkApi<AdventureWorksLT>
in WebApiConfig withAdventureWorksApi
, Now some testings will show that:1. ~/AdventureWorksLT/Products will only get the Product entities whose ProductID is 3,6,9,12,15,... 2. ~/AdventureWorksLT/Products([product id]) will only be able to get a Product entity whose ProductID mod 3 results a zero.
Note: 1. Starting from version 0.6, the conversion name is changed to OnFilter[entity type name], and before version 0.6, the name is OnFilter[entity set name]
-
Starting from version 0.6, the filter is applied to all places besides the top level entity set which includes navigation properties, collection of entity in $expand, collection in filter and so on. Refer to end to end test case TrippinE2EOnFilterTestCases for all the scenarios supported.
-
More meaningful filter can be adopted like filter entity by the owner and the entity owner is current request user.
-
-
2.3 Submit Logic
Submit logic convention allows users to authorize a submit operation or plug in user logic (such as logging) before and after a submit operation. Usually a submit operation can be inserting an entity, deleting an entity, updating an entity or executing an OData action.
Customize submit logic with single class for all entity set is also supported, refer to section 2.9 for more detail.
Authorization
Users can control if one of the four submit operations is allowed on some entity set or action by putting some protected methods into the
Api
class. The method signatures must exactly match the following examples. The method name must conform toCan<Insert|Update|Delete|Execute><EntitySetName|ActionName>
.Plug in user logic
Users can plug in user logic before and after executing one of the four submit operations by putting similar protected methods into the
Api
class. The method signatures must also exactly match the following examples. The method name must conform toOn<Insert|Updat|Delet|Execut><ed|ing><EntitySetName|ActionName>
whereing
for before submit anded
for after submit. -
2.4 ETag Support
RESTier supports ETag starting from version 0.6.
In order to support etag, the entity clr class must have some properties which are marked with [ConcurrencyCheck] attribute, then the properties with this attribute will be used to calculate the etag for concurrency support purpose.
The etag support is divided into two parts,
First part is the “@odata.etag” annotation support, it is part of response body, and will be auto added for any entity type which has properties with ConcurrencyCheck attribute when the request is a single entity or a collection of entity (in collection case, each entity instance will have “@odata.etag” annotation).
Second part is Etag header support, this is only support when operation is against a single entity. Here are the summary of the behavior.
Operation Header Etag matched? Response Get If-Match No 412(Precondition failed) Yes Return the entity If-None-Match No Return the entity Yes 304(Not modified) Update/Delete No header 428 (Precondition required) If-Match No 412(Precondition failed) Yes Proceed the operation If-None-Match No Proceed the operation Yes 412(Precondition failed) In order to support the get method return the etag header, this configuration is a must,
config.MessageHandlers.Add(new ETagMessageHandler());
User can define his own message handler to set the etag header in the response.
For both etag annotation and etag header, the algorithm to generate the etag can be replaced, user can create his own etag handler, then set in the config, and default is DefaultODataETagHandler. The code to set customize etag handler is as following,
IETagHandler eTagHandler = new CustomizedETagHandler(); config.SetETagHandler(eTagHandler);
For detail end to end examples, refer to end to end test case TrippinE2EEtagTestCases.
With etag support, the entity can be operated in concurrency mode.
-
2.5 Model Building
RESTier supports various ways to build EDM model. Users may first get an initial model from the EF provider. Then RESTier’s
RestierModelExtender
can further extend the model with additional entity sets, singletons and operations from the public properties and methods defined in theApi
class. This subsection mainly talks about how to build an initial EDM model and then the convention RESTier adopts to extend an EDM model from anApi
class.Build an initial EDM model
The
RestierModelExtender
requires EDM types to be present in the initial model because it is only responsible for building entity sets, singletons and operations NOT types. So anyway users need to build an initial EDM model with adequate types added in advance. The typical way to do so is to write a custom model builder implementingIModelBuilder
and register it to theApi
class. Here is an example using the**ODataConventionModelBuilder**
in OData Web API to build an initial model only containing thePerson
type. Any model building methods supported by Web API OData can be used here, refer to Web API OData Model builder document for more information.If RESTier entity framework provider is used and user has no additional types other than those in the database schema, no custom model builder or even the
Api
class is required because the provider will take over to build the model instead. But what the provider does behind the scene is similar. With entity framework provider, the model by default is built with ODataConventionModelBuilder, refer to document on the conversions been used like how the builder identifies keys for entity type and so on.Extend a model from Api class
The
RestierModelExtender
will further extend the EDM model passed in using the public properties and methods defined in theApi
class. Please note that all properties and methods declared in the parent classes are NOT considered.Entity set If a property declared in the
Api
class satisfies the following conditions, an entity set whose name is the property name will be added into the model.- Has Resource attribute
- Public
- Has getter
- Either static or instance
- There is no existing entity set with the same name
- Return type must be
IQueryable<T>
whereT
is class type
Example:
Singleton If a property declared in the
Api
class satisfies the following conditions, a singleton whose name is the property name will be added into the model.- Has Resource attribute
- Public
- Has getter
- Either static or instance
- There is no existing singleton with the same name
- Return type must be non-generic class type
Example:
Due to some limitations from Entity Framework and OData spec, CUD (insertion, update and deletion) on the singleton entity are NOT supported directly by RESTier. Users need to define their own route to achieve these operations.
Navigation property binding Starting from version 0.5.0, the
RestierModelExtender
follows the rules below to add navigation property bindings after entity sets and singletons have been built.- Bindings will ONLY be added for those entity sets and singletons that have been built inside
RestierModelExtender
. Example: Entity sets built by the RESTier’s EF provider are assumed to have their navigation property bindings added already. - The
RestierModelExtender
only searches navigation sources who have the same entity type as the source navigation property. Example: If the type of a navigation property isPerson
orCollection(Person)
, only those entity sets and singletons of typePerson
are searched. - Singleton navigation properties can be bound to either entity sets or singletons.
Example: If
Person.BestFriend
is a singleton navigation property, bindings fromBestFriend
to an entity setPeople
or to a singletonBoss
are all allowed. - Collection navigation properties can ONLY be bound to entity sets.
Example: If
Person.Friends
is a collection navigation property. ONLY binding fromFriends
to an entity setPeople
is allowed. Binding fromFriends
to a singletonBoss
is NOT allowed. - If there is any ambiguity among entity sets or singletons, no binding will be added.
Example: For the singleton navigation property
Person.BestFriend
, no binding will be added if 1) there are at least two entity sets (or singletons) both of typePerson
; 2) there is at least one entity set and one singleton both of typePerson
. However for the collection navigation propertyPerson.Friends
, no binding will be added only if there are at least two entity sets both of typePerson
. One entity set and one singleton both of typePerson
will NOT lead to any ambiguity and one binding to the entity set will be added.
If any expected navigation property binding is not added by RESTier, users can always manually add it through custom model extension (mentioned below).
Operation If a method declared in the
Api
class satisfies the following conditions, an operation whose name is the method name will be added into the model.- Public
- Either static or instance
- There is no existing operation with the same name
Example:
Note:
-
Operation attribute’s EntitySet property is needed if there are more than one entity set of the entity type that is type of result defined. Take an example if two EntitySet People and AllPersons are defined whose entity type is Person, and the function returns Person or List of Person, then the Operation attribute for function must have EntitySet defined, or EntitySet property is optional.
-
Function and Action uses the same attribute, and if the method is an action, must specify property HasSideEffects with value of true whose default value is false.
-
Starting from version 0.6, the operation namespace will be same as the entity type by default if it is not specified, if the namespace is specified in operation attribute, then that namespaces in attribute will be used. Also in order to support operation bound to type like complex type/primitive type besides the entity type, IsBound flag must be set to true for bound operation.
-
Starting from version 0.6, operation will be auto routed to method defined in Api class, no additional controller is needed. Refer to section 3.3 for more information.
Custom model extension
If users have the need to extend the model even after RESTier’s conventions have been applied, user can use IServiceCollection AddService to add a ModelBuilder after calling ApiBase.ConfigureApi(apiType, services).
After the above steps, the final process of building the model will be:
- User’s model builder registered before ApiBase.ConfigureApi(apiType, services) is called first.
- RESTier’s model builder includes EF model builder and RestierModelExtender will be called.
- User’s model builder registered after ApiBase.ConfigureApi(apiType, services) is called.
If InnerModelBuilder method is not called first, then the calling sequence will be different. Actually this order not only applies to the
IModelBuilder
but also all other services.Refer to section 4.3 for more details of RESTier API Service.
-
2.6 Composite Key
Composite key means one entity has more then one attributes for the key. It is automatically supported by RESTIer without any additional configurations.
To request an entity with composite key, the URL will be like
~/EntitySet(keyName1=value1,keyName2=value2)
-
2.7 Key As Segment
RESTier supports key as segment with one single line configuration before calling MapRestierRoute method:
Then request an entity with key as segment, the URL will be like
~/EntitySet/KeyValue
Note : If entity type has composite key, then key as segment is not supported for this entity type.
-
2.8 Customize Query
RESTier supports to customize the query setting and query process logic.
1. Customize Query Setting
RESTier supports to customize kinds of query setting like AllowedLogicalOperators, AllowedQueryOptions, MaxExpansionDepth, MaxAnyAllExpressionDepth and so on. Refer to class for full list of settings.
This is an example on how to customize MaxExpansionDepth from default value 2 to 3 which means allowing two level nested expand now, refer to this link to see the end to end samples,
First create a factory delegate which will create a new instance of ODataValidationSettings, then registering it into RESTier Dependency Injection framework as a service via overriding the ConfigureApi method in your Api class.
Then $expand with supported with max two nested $expand via only max one nested $expand is supported by default before we apply this customization.
2. Customize Query Logic
RESTier supports built in convention based query customized logic (refer to section 2.2), besides this, RESTier has two interfaces IQueryExpressionAuthorizer and IQueryExpressionProcessor for end user to further customize the query process logic.
Customized Authorize Logic
User can use interface IQueryExpressionAuthorizer to define any customize authorize logic to see whether user is authorized for the specified query, if this method returns false, then the related query will get error code 403 (forbidden).
There are two steps to plug in customized process logic,
First create a class CustomizedAuthorizer implement IQueryExpressionAuthorizer, and add any process logic needed.
Second, registering it into RESTier Dependency Injection framework as a service via overriding the ConfigureApi method in your Api class.
In CustomizedAuthorizer, user can decide whether to call the RESTier logic, if user decide to call the RESTier logic, user can defined a property like “private IQueryExpressionAuthorizer InnerAuthorizer {get; set;}” in class CustomizedAuthorizer, then call InnerAuthorizer.Authorize() to call RESTier logic.
Customized Process Logic
User can create class implementing interface IQueryExpressionProcessor to customize the LINQ query expression build process like to replace part of expression, remove part of expression or append part of expression. Then registered the customized class as DI service.The steps to plugin is same as above.
The logic OnFilter[entity set name] is been processed by RESTier default expression processor which add a where clause after entity set. The way to call default logic is same as above.
-
2.9 Customize Submit
RESTier supports built in convention based logic (refer to section 2.3) for submit, besides this, RESTier has three interfaces IChangeSetItemAuthorizer, IChangeSetItemValidator and IChangeSetItemProcessor for end user to customize the logic.
Customized Authorize Logic
User can use interface IChangeSetItemAuthorizer to define any customize authorize logic to see whether user is authorized for the specified submit, if this method return false, then the related query will get error code 403 (forbidden).
There are two steps to plug in customized process logic,
First create a class CustomizedAuthorizer implement IChangeSetItemAuthorizer, and add any process logic needed.
Second, registering it into RESTier Dependency Injection framework as a service via overriding the ConfigureApi method in your Api class.
In CustomizedAuthorizer, user can decide whether to call the RESTier logic, if user decide to call the RESTier logic, user can defined a property like “private IChangeSetItemAuthorizer InnerAuthorizer {get; set;}” in class CustomizedAuthorizer, then call InnerAuthorizer.AuthorizeAsync() to call RESTier logic which call Authorize part logic defined in section 2.3.
Customized Validation Logic
User can use interface IChangeSetItemValidator to customize validation logic for submit, and if validate fails, add a error validation result to validation results, then the request will get 400(bad request) return code, here is a sample customize validation logic,
The steps to plugin the logic is same as above.
Customized Process Logic
User can use interface IChangeSetItemProcessor to customize logic before or after submit, OnProcessingChangeSetItemAsync logic is called before submit and OnProcessedChangeSetItemAsync logic is called after submit, RESTier default logic is defined in section 2.3 plugin user logic part. Default logic can be called via defined a property with type IChangeSetItemProcessor like “private IChangeSetItemProcessor InnerProcessor {get; set;}”, and user call InnerProcessor.OnProcessingChangeSetItemAsync or OnProcessedChangeSetItemAsync to call RESTier logic, if in CustomizedProcessor, there is no such property defined or InnerProcessor is not used, then RESTier logic will not be called.
The steps to plugin the logic is same as above.
-
2.10 Customize Payload Converter
RESTier supports to customize the payload to be read or written (a.k.a serialize and deserialize), user can extend the class RestierPayloadValueConverter to overwrite method ConvertToPayloadValue for payload writing and ConvertFromPayloadValue for payload reading.
This is an example on how to customize a specified string value to add some prefix and write into response, refer to this link to see the end to end samples,
1. Create a class to have the customized converter logic
2. Register customized converter into RESTier Dependency Injection framework as a service via overriding the ConfigureApi method in your Api class.
Then when writting payload for response, any string which has value “Russell” will become “RussellConverter”.
-
2.11 Customize Serializer and Deserializer Provider
RESTier supports to customize serializer and deserializer provider for payload reading and writing, then in the provider, it can return customized serializer or deserializer for specified EdmType to customize the payload reading and writing.
This is an example on how to customize ODataComplexTypeSerializer to customize how complex type payload is serialized for response.
First create a class which extends ODataComplexTypeSerializer, and override method WriteObject.
Second create a class which extends DefaultRestierSerializerProvider, and override method GetODataPayloadSerializer and GetEdmTypeSerializer which will return the customized serializer, and this is sample code,
Third, register customized serializer provider as DI service in the Api ConfigureApi method
With these customized code, the complex result will be serialized in the customized way.
-
2.12 Derived Type Support
RESTier supports derived type starting from version 0.6.
Derived type support does not need any additional configuration, it is supported by default.
Refer to method CURDDerivedEntity in class TrippinE2ETestCases and method DerivedTypeQuery in class TrippinE2EQueryTestCases to see the end to end example.
Note: In a typical IIS configuration, the dot in this URL will cause IIS to return error 404. You can resolve this by adding the following section to your Web.Config file:
<system.webServer> <handlers> <clear/> <add name="ExtensionlessUrlHandler-Integrated-4.0" path="/*" verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" /> </handlers> </system.webServer>
-
2.13 Spatial Type Support
RESTier supports spatial type starting from version 0.6 with some manual effort.
There are two ways to support spatial type, refer to the end to end samples for more detail.
SpatialSample will need few manual effort to build model, but there is one limitation that user can not have the spatial type property in query option.
SpatialSample2 will require to use Edm model builder to build model, have full support of spatial type.
3. EXTENSIONS
-
3.1 Use temporal types in RESTier Entity Framework
Restier.EF now supports various temporal types. Compared to the previous support, the current solution is more consistent and extensible. You can find the detailed type-mapping table among EF type, SQL type and EDM type from the comment in Issue #279. Now almost all the OData scenarios (CRUD) of these temporal types should be well supported by RESTier.
This subsection shows how to use temporal types in Restier.EF.
Add Edm.DateTimeOffset property
Suppose you have an entity class
Person
, all the following code defineEdm.DateTimeOffset
properties in the EDM model though the underlying SQL types are different (see the value of theTypeName
property). You can see Column attribute is optional here.Add Edm.Date property
The following code define an
Edm.Date
property in the EDM model.Add Edm.Duration property
The following code define an
Edm.Duration
property in the EDM model.Add Edm.TimeOfDay property
The following code define an
Edm.TimeOfDay
property in the EDM model. Please note that you MUST NOT omit theColumnTypeAttribute
on aTimeSpan
property otherwise it will be recognized as anEdm.Duration
as described above.As before, if you have the need to override
ODataPayloadValueConverter
, please now change to overrideRestierPayloadValueConverter
instead in order not to break the payload value conversion specialized for these temporal types. -
3.2 Use Controllers in RESTier
RESTier aims to achieve more OData features with less user code. Currently in OData Web API users have to write a controller for each entity set or singleton and a lot of actions in that controller to support various property access. Mostly code among controllers is similar and redundant. Thus
RestierController
(previouslyODataDomainController
) was introduced to serve as the globally unique controller to handle most OData requests. While most is not everything, there are a few scenarios not covered byRestierController
yet. As a result, traditional controllers (ODataController
orApiController
) are still supported in RESTier’s routing convention with higher priority thanRestierController
. With such a flexible design, RESTier can satisfy various user requirements to implement an OData service.OData features supported by RestierController
Now users need not write any controller code any more to enjoy the following OData features provided by
RestierController
:- Query service document
- Query metadata document
- Query entity set
- Query single entity
- Query any property path
- Query entity/value count (by $count)
- Query raw property value (by $value)
- Create an entity
- Fully update an entity
- Partially update an entity
- Delete an entity
A little secret behind query
Users may wonder how RESTier handles all these queries in a generic way in only one controller. Actually
RestierController
will use an internal classRestierQueryBuilder
to go through eachODataPathSegment
and gradually compose a LINQ query. Here is an example. If user sends the following query:The final LINQ query generated will be like (suppose EF is being used):
Use custom controllers
Users may not always want their requests to be processed by
RestierController
. RESTier of course provides several ways to override this.- Convention routing. If user defines a controller (MUST inherit from
ODataController
) with specific name for an entity set (likePeopleController
for the entity setPeople
), all requests to that entity set will be routed to the the user-defined controller instead ofRestierController
. Refer to convention routing document for more details. - Attribute routing.
ODataRouteAttribute
always has the highest priority in routing. Now users are recommended to use attribute routing to implement OData operation and singleton. Refer to attribute routing document for more details.
-
3.3 Operations
Operation includes function (bounded), function import (unbounded), action (bounded), and action(unbounded).
To supports operation, there are two major items, first being able to build model for operation, refer to section 2.5 for more details. Second support to route operation requests to a controller action.
Starting from release 0.6.0, RESTier can auto route an operation request to the method defined in API class which is defined for operation model building, user does NOT need to define its own controller with ODataRoute attribute for operation route.
Refer to end to end test cases for end to end operation support samples.
Note: In a typical IIS configuration, the dot in this URL will cause IIS to return error 404. You can resolve this by adding the following section to your Web.Config file:
<system.webServer> <handlers> <clear/> <add name="ExtensionlessUrlHandler-Integrated-4.0" path="/*" verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" /> </handlers> </system.webServer>
-
3.4 In-Memory Provider
RESTier supports building an OData service with all-in-memory resources. However currently RESTier has not provided a dedicated in-memory provider module so users have to write some service code to bootstrap the initial model with EDM types themselves. There is a sample service with in-memory provider here. This subsection mainly talks about how such a service is created.
First please create an Empty ASP.NET Web API project following the instructions in Section 1.2. Stop BEFORE the Generate the model classes part.
Create the Api class
Create a simple data type
Person
with some properties and “fabricate” some fake data. Then add the first entity setPeople
to theApi
class:Create an initial model
Since the RESTier convention will not produce any EDM type, an initial model with at least the
Person
type needs to be created by service. Here theODataConventionModelBuilder
from OData Web API is used for quick model building. Any model building methods supported by Web API OData can be used here, refer to Web API OData Model builder document for more information.Configure the OData endpoint
Replace the
WebApiConfig
class with the following code. No need to create a custom controller if users don’t have attribute routing.
4. DEEP IN RESTIER
-
4.1 RESTier infrastructure
Restier provides a connection between various data sources and existing clients. The framework contains 4 components: Core, Module, Provider and Publisher:
- The core component provides functionalities for building up domain specific metadata, and logic for data CRUD processing flow. It also includes some extensible interfaces which allows pluggable modules.
- The module component provides the common service elements such as authorization, logging, and conventions that allow users to set up a service more quickly.
- The provider component includes data source adapters which provide functionalities for building up metadata and conduct data exchange with external data sources.
- The publisher component provides functionalities for exposing the domain specific data via a new service interface, which could be understand by existing clients.
-
4.2 RESTier API Service
Users can inject their custom API services into RESTier to extend various functionalities. There is a big progress since 0.4.0. Now the concept of hook handler has become API service in 0.5.0. We have removed the old interfaces
IHookHandler
andIDelegateHookHandler
to adapt to the concept change. Thus the implementation of any custom API service (previously known as hook handler) should also be changed accordingly.All API services registered as one specific type (either a class or an interface) are organized in a consistently chained (or nested) way. Each API service in a chain can choose whether to call the next (or inner) API service. The last API service registered is always invoked first if current service always call inner method first.
As a practical example in RESTier, there is an API service interface called
IModelBuilder
to build or extend an EDM model. By default, RESTier will register two model builders forIModelBuilder
. The model builder from the data provider (e.g.,ModelProducer
in RESTier EF) is always registered first. TheRestierModelExtender
is always registered later. Any custom model builder will be registered sequentially between or before or after the two built-in model builders based on the way been registered. When the API serviceIModelBuilder
is invoked, the outermostModelBuilder
is always invoked first. It first invokes the inner API service which could possibly be the model builder from the data provider or some custom model builder (if any). The custom model builder can choose to extend the model returned from an inner builder, or otherwise it can simply choose not to call the inner one and directly return a new model. The model builder from the data provider is typically innermost and thus has no inner builder to call.This subsection shows how to implement custom API services and inject them into RESTier between two built-in model builders. For before or after, refer to section 2.5 Model Building part. This is common for all other services which allows customization.
Implement an API service
The following sample code is to implement a custom model builder. Please note that if you want to call the inner builder, you need to put a settable property of
IModelBuilder
into your builder class. The accessibility and the name of the property doesn’t matter here. Then try to call the inner builder in the service implementation. If you don’t want to call any inner builder, you can just omit the property and remove the related logic.Register an API service
We need to register
MyModelBuilder
into the API to make it work. You can override theConfigureApi
method in your API class to do so. Here is the sample code. There are also overloads for the two methods that take an existing service instance or a service factory method. By the way, all those methods are fluent API so you can call them in a chained way.In the service implementation, the parameter type
IServiceCollection
is actually a container builder from Microsoft Dependency Injection Framework (DI). You can do whatever applicable to a normal DI container here. It is notable that you can also take advantage of the powerful scope feature in DI here! RESTier will create a new scope for each individual request inApiContext
which enables you to register scoped services whose lifetime is per-request.Please visit https://docs.asp.net/en/latest/fundamentals/dependency-injection.html to grasp some basic understanding about DI before proceeding.
The following example is to register a scoped
MyDbContext
service so that you have a newMyDbContext
instance for each request.You can also make a specific API service singleton, scoped or transient (though not common) by calling
MakeSingleton
,MakeScoped
orMakeTransient
. Here is a sample which is to makeIModelBuilder
scoped.Get an API service
No matter in which way you register an API service of
T
, the only and unified way to get that service out is to use(ApiContext).GetApiService<T>
from RESTier orIServiceProvider.GetService<T>
from DI.
5. CLIENTS
-
5.1 Clients
RESTier helps to build OData service which is RESTful services, so any REST client or http client can be used to send requests and receive responses.
OData team develops a .Net Client which can work with any OData services, refer to “Basic CRUD Operations with OData .Net Client” for details.
OData team also develops two client tools to facilitate OData Client application development, refer to “Using OData Client Code Generator to generate client-side proxy class” and “Using OData Connected Service to generate client-side proxy class” for details.
6. ANNOUNCEMENTS
-
6.1 Release notes for RESTier 0.2.0-preview
Below are the features supported in the RESTier 0.2.0-preview, as well as the limitations of the current version.
Easily build an OData V4 service
Features directly supported
Just create one
ODataDomainController<>
and all of the features below are automatically enabled:- Basic queries for metadata and top level entities.
- System query options
$select
,$expand
,$filter
,$orderby
,$top
,$skip
, and$format
. - Ability to request related entities.
- Create, Update and Delete top-level entities.
- Batch requests.
Leverage attribute routing to fall back to Web API OData for features not directly supported by RESTier
- Request entity references with
$ref
. - Create, Update and Delete entities not on the top-level.
- Modify relationships between entities.
- etc.
Use
EdmModelExtender
to support features currently not directly supported by RESTier.- OData functions.
- OData actions
Rich domain logic
-
Role-based security
You can easily set restrictions for different entity sets. For example, you can provide users with READ permission on some entity sets, and INSPECT (only provides access to $metadata) on others.
-
Imperative views
Customized entity sets which are not in the data model can be easily added. Currently, these entity sets are read-only, and do not support CUD (Create, Update, Delete) operations.
-
Entity set filters
With entity set filters, you can easily set filters before entity data is retrieved. For example, if you want users to only see part of Customers based on their UserID, you can use entity set filters to pre-filter the results.
-
Submit logic
With submit logic, you can add custom business logic that fires during or after a specific operation is performed on an entity set (e.g.,
OnInsertedProducts
).
Limitations
- Only supports OData V4.
- Only supports Entity Framework as data providers.
These are the two primary limitations currently, and we are looking at mitigating them in future releases. In the meanwhile, we’d like to hear your feedback and suggestions on how to improve RESTier.
-
6.2 RESTier now open sourced on GitHub
The source code of RESTier now is open-souced on GitHub, together with the test code and the Northwind Samples.
We have heard a lot of feedback of RESTier and record them directly on GitHub Issues, with the source code open now, developers can explore and play with RESTier more easily. And code contributions, bug reports are warmly welcomed.
-
6.3 Release notes for RESTier 0.3.0-beta1
Features supported in 0.3.0-beta1
- Complex type support #96
Improvements since 0.2.0-pre -
6.4 Release notes for RESTier 0.3.0-beta2
Features supported in 0.3.0-beta2
Bug-fixes since 0.3.0-beta1
Improvements since 0.3.0-beta1- Automatically start TripPin service when running E2E cases #146
- No need to change machine configuration for running tests under Release mode
-
6.5 Release notes for RESTier 0.4.0-rc
Features supported in 0.4.0-rc
- Unified hook handler mechanism for users to inject hooks, Tutorial
- Built-in
RestierController
now handles most CRUD scenarios for users including entity set access, singleton access, entity access, property access with $count/$value, $count query option support. #136, #193, #234, Tutorial - Support building entity set, singleton and operation from
Api
(previouslyDomain
). Support navigation property binding. Now users can save much time writing code to build model. #207, Tutorial - Support in-memory data source provider #189
Bug-fixes since 0.3.0-beta2- Fix IISExpress instance startup issue in E2E tests #145, #241
- Should return 400 if there is any invalid query option #176
- EF7 project bug fixes #253, #254
Improvements since 0.3.0-beta2- Thorough API cleanup, code refactor and concept reduction #164
- The Conventions project was merged into the Core project. Conventions are now enabled by default. The
OnModelExtending
convention was removed due to inconsistency. #191 - Add a sample service with an in-memory provider #189
- Unified exception-handling process #24, #26
- Simplified
MapRestierRoute
now takes anApi
class instead of a controller class. No custom controller required in simple cases. - Update project URL in RESTier NuGet packages.
-
6.6 Release notes for RESTier 0.4.0-rc2
Bug-fixes since 0.4.0-rc
- Support string as return type or argument of functions #258
-
6.7 Release notes for RESTier 0.5.0-beta
New features since 0.4.0-rc2
- [Issue #150] [PR #286] Integrate Microsoft Dependency Injection Framework into RESTier. Tutorial.
- [Issue #273] [PR #278] Support temporal types in Restier.EF. Tutorial.
- [Issue #383] [PR #402] Adopt Web OData Conversion Model builder as default EF provider model builder. Tutorial.
- [Issue #360] [PR #399] Support $apply in RESTier. Tutorial.
-
Bug-fixes since 0.4.0-rc2 - [Issue #123] [PR #294] Fix a bug that prevents using
Edm.Int64
as entity key. - [Issue #269] [PR #271] Fix a bug that
NullReferenceException
is thrown when POST/PATCH/PUT with null property values. - [Issue #287] [PR #314] Fix a bug that $count does not work correctly when there is $expand.
- [Issue #304] [PR #306] Fix a bug that
GetModelAsync
is not thread-safe. - [Issue #304] [PR #322] Fix a bug that if
GetModelAsync
takes too long to complete, any subsequent request will fail. - [Issue #308] [PR #313] Fix a bug that
NullReferenceException
is thrown whenColumnTypeAttribute
does not have aTypeName
property specified. - [Issue #309][Issue #310][Issue #311][Issue #312] [PR #313] Fix various bugs in the RESTier query pipeline.
API changes since 0.4.0-rc2- The concept of hook handler now becomes API service after DI integration.
- The interface
IHookHandler
andIDelegateHookHandler
are removed. The implementation of any custom API service (previously known as hook handler) should also change accordingly. But this should not be big change. Please see Tutorial for details. AddHookHandler
is now replaced withAddService
from DI. Please see Tutorial for details.GetHookHandler
is now replaced withGetApiService
andGetService
from DI. Please see Tutorial for details.- All the serializers and
DefaultRestierSerializerProvider
are now public. But we still need to address #301 to allow users to override the serializers. - The interface
IApi
is now removed. UseApiBase
instead. We never expect users to directly implement their API classes fromIApi
anyway. TheContext
property inIApi
now becomes a public property inApiBase
. - Previously the
ApiData
class is very confusing. Now we have given it a more meaningful nameDataSourceStubs
which accurately describes the usage. Along with this change, we also renameApiDataReference
toDataSourceStubReference
accordingly. ApiBase.ApiConfiguration
is renamed toApiBase.Configuration
to keep consistent withApiBase.Context
.- The static
Api
class is now separated into two classesApiBaseExtensions
andApiContextExtensions
to eliminate the ambiguity regarding the previousApi
class.
-
6.8 Release notes for RESTier 0.6.0
New features since 0.5.0-beta
- [Issue #117] [PR #410] Entity Type inheritance support.
- [Issue #378] [PR #424] Support operation without additional controller.
- [Issue #460] [PR #461] Imprative view support with entity type which is not defined in EF.
- [Issue #443] [PR #448] Support method bound to non-entity type.
- [Issue #414] [PR #422] Support backslash and slash in key value.
- [Issue #22] [PR #458] Add ETag support in RESTier.
- [Issue #479] [PR #484] Add Immutable and Computed Annotation support.
Bug-fixes since 0.5.0-beta
- [Issue #432] [PR #452] More meaningful exception message during model build.
- [Issue #438] [PR #448] Make namespace consistent for entity type / operation / container in model.
- [Issue #413] [PR #454] Auto pop entity type key from entity framework during model builder.
- [Issue #426] [PR #424] Support method return nullable enum.
- [Issue #459] [PR #458] Return 404 if single entity for bound operation does not exist.
- [Issue #288] [PR #465] Return 204 if single navigation property is null.
- [Issue #328] [PR #465] Return 404 when request property of non-exist entity or complex.
- [PR #455] Improve RESTier routing which only fail to entity set controller if there is an action for the request.
-
6.9 Release notes for RESTier 1.0.0
This is the first GA release of RESTier, it is based on newest odata .net library 7.x and newest Web API OData library 6.x.
Now it is more flexibility for user to define the behavior, and lots more customization capability as both ODL and WAO have adopted Dependency Injection. All the service registered in DI container by ODL and WAO can be customized. Refer to ODL and WAO documents for more detail.
Three breaking changes impacts any consumer who implements its own API are,
-
Must create a constructor which accept a IServiceProvider, the IServiceProvider will be set automatically when Api instance is retrieved from DI container.
-
ConfigureApi method is static and has one additional parameter “Type apiType”, and in this method, it must call ApiBase or EntityFrameworkApi
ConfigureApi method to have Api registered as DI service. -
Starting 1.0 release, there are more smaller granularity control on the properties which can be used in query option, and all properties are disabled to be used by default. User can add configured in CLR class or during model build to configure which properties are allowed to be used in filter/expand/select/orderby/count. Refer to Model bound document for more details. User can also use configuration “config.Filter().Expand().Select().OrderBy().MaxTop(null).Count();” to enable filter/expand/select/orderby/count on all properties.
New features since 0.6.0
- Add resource attribute for the properties in Api class
- Now for the properties in API class, it must have Resource attribute to be built as entity set or singleton.
- Make URI resolver as DI service.
- URL resolver will need to be registered as DI service which was set via config before.
- Move ApiBase as DI service and remove ApiContext.
- ApiBase is DI service now and one instance is created for each request. Remove ApiContext to make logic clear.
- Make ApiConfiguration as internal service.
- ApiConfiguration is moved as internal service now, if additional static configuration is needed for Api class, a DI singleton service is recommeded.
- Use WepApi OData formatting attribute and remove RESTier formatting attribute.
- Add support of untyped property
Bug-fixes since 0.6.0
- [Issue #491] [PR #495] Support property with type of byte array as concurrency check properties.
- [Issue #488] [PR #498] Support DataTime whose kind is local.
- [Issue #505] [PR #507] PATCH semantics against complex types is incorrect.
-
7. TOOLING
-
7.1 Restier Scaffolding
Introduction
This tool is used to modify the config class to simplifies the process of building the OData service with EF by Restier(>=0.4.0-rc) in visual studio. The scaffolding item will appear in the scaffolding list by right click on any folder in project and select “Add” -> “New Scaffolded Item”
Install Visual Studio Extension of Scaffolding
The installer of Restier scaffolding can be downloaded from Visual Studio Gallery: Restier Scaffolding. Double click vsix to install, the extension supports the VS2013 and VS2015, now.
Using Scaffolding Tool
Here is the process of building an OData V4 endpoint using RESTier. With scaffolding tool, you only need to “Create a project and a web app”, then “Generate the model classes”. The project will looks like:
- Right click the APP_Start folder->Add->New Scaffolded items
- Select “Microsoft OData Restier Config” under Common\Web API node
- Select the “Data context class” needed and “WebApi config class” which will be modified to add the code as following:
- Click “Change”. Scaffolding tool will add the code in “WebApiConfig.cs”. And add Restier assembly as reference
- Reopen the “WebApiConfig.cs” to view the code added:
- Rebuld the project and start:
Notice: The alpha version of tool may contain an issue: during the step 5 and 6, visual studio may need to be restarted.
8. OTHERS
-
8.1 Sample Services
Refer to sample service github for end to end sample service.
The source code also contains end to end service for end to end test purpose.
All the sample services can be run with visual studio 2015.
-
8.2 How to Debug
If you want to debug OData Lib, WebAPI, Restier source, open
DEBUG
->Options and Settings
in VS, configure below things inGeneral
tab:- Uncheck
Enable Just My Code (Managed only)
. - Uncheck
Enable .NET Framework source stepping
. - UnCheck
Require source files to exactly match the original version
. - Check
Enable source server support
.
Setup your symbol source in
Symbols
tab:- Check
Microsoft Symbol Servers
. - Add location: http://srv.symbolsource.org/pdb/Public (For preview/public releases in nuget.org).
- Add location: http://srv.symbolsource.org/pdb/MyGet (For nightly build, and preview releases in myget.org).
- Set the cache symbols directory in your, the path should be as short as it can be.
Turn on the CLR first change exception to do a quick debug, open
DEBUG
->Exceptions
in VS, check theCommon Language Runtime Exceptions
.RESTier also exposes a configuration which will return the whole exception stack trace if there is any exception thrown on the server side for the request, it is disabled by default, it can be enabled via call “config.SetUseVerboseErrors(true);” during routes register.
- Uncheck