This document is for version 0.5.0-beta or earlier.
For document of current released version, refer to Current Version 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 [>=0.4.0]
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.
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 (>=0.4.0)
Authentication (>=0.4.0)
REStier is transparent to security now, any security configurations / methodology working for Web APi will work for RESTier.
RESTier Role Based Security [0.4.0-beta only]
Currently the quality of the RESTier role-based security module is NOT guaranteed and this module will NOT be shipped with regular RESTier releases and will not be part of first GA release. Please use it at your own risk.
For example code, please refer to NorthwindApi.
-
2.2 Entity Set Filters [>=0.4.0]
Entity set filter convention helps plug in a piece of filtering logic for entity set. It is done via adding an
OnFilter[entity set name](IQueryable<T> entityset)
method to theApi
class.1. The filter method name must be OnFilter[entity set name], ending with the target entity set 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 OnFilterProducts 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.
-
2.3 Submit Logic [>=0.4.0]
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.
For version 0.5.0 or newer, customize submit logic with single class for all entity set is 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 Model Building [0.4.0 only]
This section is for version 0.4.0 only, for newer version,refer to following section. RESTier supports various ways to build EDM model. Users may first get an initial model from the EF provider. Then RESTier’s
ConventionBasedApiModelBuilder
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
ConventionBasedApiModelBuilder
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 theConventionModelBuilder
in OData Web API to build an initial model only containing thePerson
type.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.Extend a model from Api class
The
ConventionBasedApiModelBuilder
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.- 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.- 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:
For versions under 0.4.0-beta, users must define an action with
ODataRouteAttribute
in their custom controller to access a singleton. After version 0.4.0-rc, no custom route is required. However 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.4.0-rc, the
ConventionBasedApiModelBuilder
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
ConventionBasedApiModelBuilder
. Example: Entity sets built by the RESTier’s EF provider are assumed to have their navigation property bindings added already. - The
ConventionBasedApiModelBuilder
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 (namespace should be specified if the namespace of the method does not match the model):
Please note that in order to access an operation user must define an action with
ODataRouteAttribute
in his custom controller.Custom model extension
If users have the need to extend the model even after RESTier’s conventions have been applied,
ApiConfiguratorAttribute
can be used. First implement a customApiConfiguratorAttribute
and register a model extender in it. The difference from the previousTrippinApi.ModelBuilder
is that the previous one does NOT need to implementIDelegateHookHandler<IModelBuilder>
which provides it with the capability to call an inner model builder. The previous one itself is responsible for producing an initial model. HoweverTrippinAttribute.TrippinModelExtender
MUST implement this interface and call the inner model builder to at least get a workable model to extend. Notably the built-inConventionBasedApiModelBuilder
andConventionBasedOperationProvider
also follow this pattern.Then apply it to the
Api
class.After the above steps, the final process of building the model will be:
- User’s model builder or RESTier provider’s model builder registered in
CreateApiConfiguration
: produce an initial model In this case:TrippinApi.ModelBuilder
orMicrosoft.Restier.EntityFramework.Model.ModelProducer
. ConventionBasedApiModelBuilder
: extend the model with entity sets and singletons fromApi
classConventionBasedOperationProvider
: extend the model with actions and functions fromApi
class- User’s model extender registered in custom
ApiConfiguratorAttribute
: custom model extension In this case:TrippinAttribute.TrippinModelExtender
.
Actually this order not only applies to the
IModelBuilder
but also all other hook handlers. The typical order for executing a hook handler will be:- Hook handlers registered in
CreateApiConfiguration
: provide an initial result - Hook handlers provided by RESTier conventions: apply RESTier conventions to the result
- Hook handlers registered in custom
ApiConfiguratorAttribute
: user customizations
-
2.5 Model Building [>=0.5.0]
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.- 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.- 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 (namespace should be specified if the namespace of the method does not match the model):
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.
-
In order to access an operation user must define an action with
ODataRouteAttribute
in his custom controller. 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 base.ConfigureApi(services).
After the above steps, the final process of building the model will be:
- User’s model builder registered before base.ConfigureApi(services) is called first.
- RESTier’s model builder includes EF model builder and RestierModelExtender will be called.
- User’s model builder registered after base.ConfigureApi(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 [>=0.5.0]
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 [>=0.5.0]
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 [>=0.5.0]
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 [>=0.5.0]
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 [>=0.5.0]
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 [>=0.5.0]
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.
3. EXTENSIONS
-
3.1 Use temporal types in RESTier Entity Framework [>=0.5.0]
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 [>=0.4.0]
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 [>=0.4.0]
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 Web API controller class action, this is covered in this section.
Currently RESTier can not route an operation request to a method defined in API class for operation model building, user need to define its own controller with ODataRoute attribute for operation route.
Operation includes function (bounded), function import (unbounded), action (bounded), and action(unbounded).
For function and action, the ODataRoute attribute must include namespace information. There is a way to simplify the URL to omit the namespace, user can enable this via call “config.EnableUnqualifiedNameCall(true);” during registering.
For function import and action import, the ODataRoute attribute must NOT include namespace information.
RESTier also supports operation request in batch request, as long as user defines its own controller for operation route.
This is an example on how to define customized controller with ODataRoute attribute for operation.
-
3.4 In-Memory Provider [0.4.0 only]
RESTier supports building an OData service with all-in-memory resources. However currently RESTier has not provided a dedicated in-memory provider (probably part of the future plan) 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.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. -
3.5 In-Memory Provider [>=0.5.0]
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 Hook Handler (0.4.0 only)
The hook handler in RESTier provides a way for setting custom extension points.
Implement a single hook handler
Basically we have the following interface for all hook handlers
For a specific hook handler, we will also provide the corresponding interface. For example, for model builder, we have the following interface hook handler for building up a service model:
To support this hook handler, user could write his custom class and implement such interface.
Implement a chained hook handler
Before 0.3.0-beta2, RESTier supports two kinds of hook (singleton hook and multi-cast hook point handler). For singleton hook, users can specifiy one single hook implementation, and the invoker will call the hook once. For multi-cast hook, users can add the hook implementation in sequence, while the invoker will call all the hooks in certain order (it may vary for different hook.)
But later we introduced the current hook mechanism, which allows the user to maintain the hook chain. This is also something called Matryoshka doll model.
If user wants to support hook hanlder chain, he could choose to implement the following interface besides the IHookHandler.
For example, to support a chain of model producer, we can have the following class:
The InnnerHandler would be auto set to a previous hook handler if present, during the time this hook handler was added.
Set a hook handler
Then we have got the following two APIs for setting hook handlers:
For each Api class instance, it will have a corresponding configuration. And we can set the hook handlers on the ApiConfiguration instance.
-
4.3 RESTier API Service (>=0.5.0)
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.
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.