From 121a124f13b97026ee33cb7928e4028794be3ab2 Mon Sep 17 00:00:00 2001 From: Robert McLaws Date: Sat, 4 Jun 2016 20:55:06 -0400 Subject: [PATCH 01/18] Initial documentation checkin Prototyping documentation layout for generating a ReadTheDocs site. --- docs/clients/dot-net.md | 17 ++++++ docs/contribution-guidelines.md | 17 ++++++ docs/extending-restier/temporal-types.md | 69 ++++++++++++++++++++++++ docs/index.md | 17 ++++++ docs/installation.md | 17 ++++++ docs/license.md | 17 ++++++ docs/release-notes/0-4-0-rc.md | 26 +++++++++ docs/server/filters.md | 17 ++++++ docs/server/interceptors.md | 17 ++++++ docs/server/method-authorization.md | 17 ++++++ docs/server/model-building.md | 17 ++++++ mkdocs.yml | 21 ++++++++ 12 files changed, 269 insertions(+) create mode 100644 docs/clients/dot-net.md create mode 100644 docs/contribution-guidelines.md create mode 100644 docs/extending-restier/temporal-types.md create mode 100644 docs/index.md create mode 100644 docs/installation.md create mode 100644 docs/license.md create mode 100644 docs/release-notes/0-4-0-rc.md create mode 100644 docs/server/filters.md create mode 100644 docs/server/interceptors.md create mode 100644 docs/server/method-authorization.md create mode 100644 docs/server/model-building.md create mode 100644 mkdocs.yml diff --git a/docs/clients/dot-net.md b/docs/clients/dot-net.md new file mode 100644 index 00000000..da37213a --- /dev/null +++ b/docs/clients/dot-net.md @@ -0,0 +1,17 @@ +# Welcome to MkDocs + +For full documentation visit [mkdocs.org](http://mkdocs.org). + +## Commands + +* `mkdocs new [dir-name]` - Create a new project. +* `mkdocs serve` - Start the live-reloading docs server. +* `mkdocs build` - Build the documentation site. +* `mkdocs help` - Print this help message. + +## Project layout + + mkdocs.yml # The configuration file. + docs/ + index.md # The documentation homepage. + ... # Other markdown pages, images and other files. diff --git a/docs/contribution-guidelines.md b/docs/contribution-guidelines.md new file mode 100644 index 00000000..da37213a --- /dev/null +++ b/docs/contribution-guidelines.md @@ -0,0 +1,17 @@ +# Welcome to MkDocs + +For full documentation visit [mkdocs.org](http://mkdocs.org). + +## Commands + +* `mkdocs new [dir-name]` - Create a new project. +* `mkdocs serve` - Start the live-reloading docs server. +* `mkdocs build` - Build the documentation site. +* `mkdocs help` - Print this help message. + +## Project layout + + mkdocs.yml # The configuration file. + docs/ + index.md # The documentation homepage. + ... # Other markdown pages, images and other files. diff --git a/docs/extending-restier/temporal-types.md b/docs/extending-restier/temporal-types.md new file mode 100644 index 00000000..87d9c1d9 --- /dev/null +++ b/docs/extending-restier/temporal-types.md @@ -0,0 +1,69 @@ +When using Microsoft.Restier.Providers.EntityFramework, temporal types are now supported. The table below shows how Temporal Types map to SQL Types: + +| EF Type | SQL Type | Edm Type | Need ColumnAttribute? | +|:---------------------:|:------------------:|:------------------:|:---------------------:| +| System.DateTime | DateTime/DateTime2 | Edm.DateTimeOffset | Y | +| System.DateTimeOffset | DateTimeOffset | Edm.DateTimeOffset | N | +| System.DateTime | Date | Edm.Date | Y | +| System.TimeSpan | Time | Edm.TimeOfDay | Y | +| System.TimeSpan | Time | Edm.Duration | N | + +The next sections illustrate how to use use temporal types in various scenarios. + +### Add Edm.DateTimeOffset property +Suppose you have an entity class `Person`, all the following code define `Edm.DateTimeOffset` properties in the EDM model though the underlying SQL types are different (see the value of the `TypeName` property). You can see Column attribute is optional here. + + + using System; + using System.ComponentModel.DataAnnotations.Schema; + + public class Person + { + public DateTime BirthDateTime1 { get; set; } + + [Column(TypeName = "DateTime")] + public DateTime BirthDateTime2 { get; set; } + + [Column(TypeName = "DateTime2")] + public DateTime BirthDateTime3 { get; set; } + + public DateTimeOffset BirthDateTime4 { get; set; } + } + + +### Add Edm.Date property +The following code define an `Edm.Date` property in the EDM model. + + using System; + using System.ComponentModel.DataAnnotations.Schema; + + public class Person + { + [Column(TypeName = "Date")] + public DateTime BirthDate { get; set; } + } + +### Add Edm.Duration property +The following code define an `Edm.Duration` property in the EDM model. + + using System; + using System.ComponentModel.DataAnnotations.Schema; + + public class Person + { + public TimeSpan WorkingHours { get; set; } + } + +### Add Edm.TimeOfDay property +The following code define an `Edm.TimeOfDay` property in the EDM model. Please note that you MUST NOT omit the `ColumnTypeAttribute` on a `TimeSpan` property otherwise it will be recognized as an `Edm.Duration` as described above. + + using System; + using System.ComponentModel.DataAnnotations.Schema; + + public class Person + { + [Column(TypeName = "Time")] + public TimeSpan BirthTime { get; set; } + } + +As before, if you have the need to override `ODataPayloadValueConverter`, please now change to override `RestierPayloadValueConverter` instead in order not to break the payload value conversion specialized for these temporal types. \ No newline at end of file diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..c477e0e2 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,17 @@ +# Welcome to RESTier! + +For full documentation visit [mkdocs.org](http://mkdocs.org). + +## Commands + +* `mkdocs new [dir-name]` - Create a new project. +* `mkdocs serve` - Start the live-reloading docs server. +* `mkdocs build` - Build the documentation site. +* `mkdocs help` - Print this help message. + +## Project layout + + mkdocs.yml # The configuration file. + docs/ + index.md # The documentation homepage. + ... # Other markdown pages, images and other files. diff --git a/docs/installation.md b/docs/installation.md new file mode 100644 index 00000000..da37213a --- /dev/null +++ b/docs/installation.md @@ -0,0 +1,17 @@ +# Welcome to MkDocs + +For full documentation visit [mkdocs.org](http://mkdocs.org). + +## Commands + +* `mkdocs new [dir-name]` - Create a new project. +* `mkdocs serve` - Start the live-reloading docs server. +* `mkdocs build` - Build the documentation site. +* `mkdocs help` - Print this help message. + +## Project layout + + mkdocs.yml # The configuration file. + docs/ + index.md # The documentation homepage. + ... # Other markdown pages, images and other files. diff --git a/docs/license.md b/docs/license.md new file mode 100644 index 00000000..da37213a --- /dev/null +++ b/docs/license.md @@ -0,0 +1,17 @@ +# Welcome to MkDocs + +For full documentation visit [mkdocs.org](http://mkdocs.org). + +## Commands + +* `mkdocs new [dir-name]` - Create a new project. +* `mkdocs serve` - Start the live-reloading docs server. +* `mkdocs build` - Build the documentation site. +* `mkdocs help` - Print this help message. + +## Project layout + + mkdocs.yml # The configuration file. + docs/ + index.md # The documentation homepage. + ... # Other markdown pages, images and other files. diff --git a/docs/release-notes/0-4-0-rc.md b/docs/release-notes/0-4-0-rc.md new file mode 100644 index 00000000..3091ea2b --- /dev/null +++ b/docs/release-notes/0-4-0-rc.md @@ -0,0 +1,26 @@ +## Downloads + + - NuGet: + - [Source (Zip)](https://github.com/OData/RESTier/archive/0.4.0-rc.zip) + +## New Features + + - Unified hook handler mechanism for users to inject hooks, [Tutorial](http://odata.github.io/RESTier/#04-04-Hook-Handler) + - 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](https://github.com/OData/RESTier/issues/136), [#193](https://github.com/OData/RESTier/issues/193), [#234](https://github.com/OData/RESTier/issues/234), [Tutorial](http://odata.github.io/RESTier/#03-05-Controllers) + - Support building entity set, singleton and operation from `Api` (previously `Domain`). Support navigation property binding. Now users can save much time writing code to build model. [#207](https://github.com/OData/RESTier/issues/207), [Tutorial](http://odata.github.io/RESTier/#02-06-Model-building) + - Support in-memory data source provider [#189](https://github.com/OData/RESTier/issues/189) + +## Enhancements + + - Thorough API cleanup, code refactor and concept reduction [#164](https://github.com/OData/RESTier/issues/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](https://github.com/OData/RESTier/issues/191) + - Add a sample service with an in-memory provider [#189](https://github.com/OData/RESTier/issues/189) + - Unified exception-handling process [#24](https://github.com/OData/RESTier/issues/24), [#26](https://github.com/OData/RESTier/issues/26) + - Simplified `MapRestierRoute` now takes an `Api` class instead of a controller class. No custom controller required in simple cases. + - Update project URL in RESTier NuGet packages. + +## Bug Fixes + + - Fix IISExpress instance startup issue in E2E tests [#145](https://github.com/OData/RESTier/issues/145), [#241](https://github.com/OData/RESTier/issues/241) + - Should return 400 if there is any invalid query option [#176](https://github.com/OData/RESTier/issues/176) + - EF7 project bug fixes [#253](https://github.com/OData/RESTier/issues/253), [#254](https://github.com/OData/RESTier/issues/254) \ No newline at end of file diff --git a/docs/server/filters.md b/docs/server/filters.md new file mode 100644 index 00000000..da37213a --- /dev/null +++ b/docs/server/filters.md @@ -0,0 +1,17 @@ +# Welcome to MkDocs + +For full documentation visit [mkdocs.org](http://mkdocs.org). + +## Commands + +* `mkdocs new [dir-name]` - Create a new project. +* `mkdocs serve` - Start the live-reloading docs server. +* `mkdocs build` - Build the documentation site. +* `mkdocs help` - Print this help message. + +## Project layout + + mkdocs.yml # The configuration file. + docs/ + index.md # The documentation homepage. + ... # Other markdown pages, images and other files. diff --git a/docs/server/interceptors.md b/docs/server/interceptors.md new file mode 100644 index 00000000..da37213a --- /dev/null +++ b/docs/server/interceptors.md @@ -0,0 +1,17 @@ +# Welcome to MkDocs + +For full documentation visit [mkdocs.org](http://mkdocs.org). + +## Commands + +* `mkdocs new [dir-name]` - Create a new project. +* `mkdocs serve` - Start the live-reloading docs server. +* `mkdocs build` - Build the documentation site. +* `mkdocs help` - Print this help message. + +## Project layout + + mkdocs.yml # The configuration file. + docs/ + index.md # The documentation homepage. + ... # Other markdown pages, images and other files. diff --git a/docs/server/method-authorization.md b/docs/server/method-authorization.md new file mode 100644 index 00000000..da37213a --- /dev/null +++ b/docs/server/method-authorization.md @@ -0,0 +1,17 @@ +# Welcome to MkDocs + +For full documentation visit [mkdocs.org](http://mkdocs.org). + +## Commands + +* `mkdocs new [dir-name]` - Create a new project. +* `mkdocs serve` - Start the live-reloading docs server. +* `mkdocs build` - Build the documentation site. +* `mkdocs help` - Print this help message. + +## Project layout + + mkdocs.yml # The configuration file. + docs/ + index.md # The documentation homepage. + ... # Other markdown pages, images and other files. diff --git a/docs/server/model-building.md b/docs/server/model-building.md new file mode 100644 index 00000000..da37213a --- /dev/null +++ b/docs/server/model-building.md @@ -0,0 +1,17 @@ +# Welcome to MkDocs + +For full documentation visit [mkdocs.org](http://mkdocs.org). + +## Commands + +* `mkdocs new [dir-name]` - Create a new project. +* `mkdocs serve` - Start the live-reloading docs server. +* `mkdocs build` - Build the documentation site. +* `mkdocs help` - Print this help message. + +## Project layout + + mkdocs.yml # The configuration file. + docs/ + index.md # The documentation homepage. + ... # Other markdown pages, images and other files. diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 00000000..8d50de96 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,21 @@ +site_name: RESTier Documentation +site_description: How to use the RESTier framework for .NET. +theme: readthedocs +pages: +- Home: + - 'Introduction': 'index.md' + - 'Installation': 'installation.md' +- Building the Service: + - 'Entity Set Filters': 'server/filters.md' + - 'Method Authorization': 'server/method-authorization.md' + - 'Interceptors': 'server/interceptors.md' + - 'Model Building': 'server/model-building.md' +- Extending RESTier: + - 'Temporal Types': 'extending-restier/temporal-types.md' +- Building the Client: + - '.NET': 'clients/dot-net.md' +- Release Notes: + - 0.4.0-rc: 'release-notes/0-4-0-rc.md' +- About: + - 'License': 'license.md' + - 'Contributing': 'contribution-guidelines.md' \ No newline at end of file From 250051654cff1a7ad23a9cd99e961fb337251e09 Mon Sep 17 00:00:00 2001 From: Robert McLaws Date: Sat, 4 Jun 2016 23:49:21 -0400 Subject: [PATCH 02/18] Docs checkin #2 --- .../additional-operations.md | 69 +++++++++++++++ docs/extending-restier/in-memory-provider.md | 84 +++++++++++++++++++ docs/extending-restier/temporal-types.md | 2 + docs/vs-highlight.css | 69 +++++++++++++++ mkdocs.yml | 7 +- 5 files changed, 230 insertions(+), 1 deletion(-) create mode 100644 docs/extending-restier/additional-operations.md create mode 100644 docs/extending-restier/in-memory-provider.md create mode 100644 docs/vs-highlight.css diff --git a/docs/extending-restier/additional-operations.md b/docs/extending-restier/additional-operations.md new file mode 100644 index 00000000..413f74da --- /dev/null +++ b/docs/extending-restier/additional-operations.md @@ -0,0 +1,69 @@ +## Additional WebAPI Operations + +RESTier is built on top of ASP.NET Web API, so like our regular OData support, augmenting your service +with additional actions is very simple. + +First, you must add the action to the EDM Model Builder. + +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. + +```cs +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Web.Http; +using System.Web.OData; +using System.Web.OData.Extensions; +using System.Web.OData.Routing; +using Microsoft.OData.Edm.Library; +using Microsoft.OData.Service.Sample.Trippin.Api; +using Microsoft.OData.Service.Sample.Trippin.Models; + +namespace Microsoft.OData.Service.Sample.Trippin.Controllers +{ + public class TrippinController : ODataController + { + private TrippinApi Api + { + get + { + if (api == null) + { + api = new TrippinApi(); + } + + return api; + } + } + ... + // Unbounded action does not need namespace in route attribute + [ODataRoute("ResetDataSource")] + public IHttpActionResult ResetDataSource() + { + // reset the data source; + return StatusCode(HttpStatusCode.NoContent); + } + + [ODataRoute("Trips({key})/Microsoft.OData.Service.Sample.Trippin.Models.EndTrip")] + public IHttpActionResult EndTrip(int key) + { + var trip = DbContext.Trips.SingleOrDefault(t => t.TripId == key); + return Ok(Api.EndTrip(trip)); + } + ... + } +} +``` \ No newline at end of file diff --git a/docs/extending-restier/in-memory-provider.md b/docs/extending-restier/in-memory-provider.md new file mode 100644 index 00000000..2d8347fe --- /dev/null +++ b/docs/extending-restier/in-memory-provider.md @@ -0,0 +1,84 @@ +## In-Memory Data 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](https://github.com/OData/RESTier/tree/apidev/test/ODataEndToEndTests/Microsoft.OData.Service.Sample.TrippinInMemory). +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](http://odata.github.io/RESTier/#01-02-Bootstrap). 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 set `People` to the `Api` class: + + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + using System.Web.OData.Builder; + using Microsoft.OData.Edm; + using Microsoft.Restier.Core; + using Microsoft.Restier.Core.Model; + + namespace Microsoft.OData.Service.Sample.TrippinInMemory + { + public class TrippinApi : ApiBase + { + private static readonly List people = new List + { + ... + }; + + public IQueryable People + { + get { return people.AsQueryable(); } + } + } + } + +### 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 the `ODataConventionModelBuilder` 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 ](http://odata.github.io/WebApi/#02-01-model-builder-abstract)**document for more information. + + namespace Microsoft.OData.Service.Sample.TrippinInMemory + { + public class TrippinApi : ApiBase + { + protected override IServiceCollection ConfigureApi(IServiceCollection services) + { + services.AddService(new ModelBuilder()); + return base.ConfigureApi(services); + } + + private class ModelBuilder : IModelBuilder + { + public Task GetModelAsync(InvocationContext context, CancellationToken cancellationToken) + { + var builder = new ODataConventionModelBuilder(); + builder.EntityType(); + return Task.FromResult(builder.GetEdmModel()); + } + } + } + } + +### 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. + + using System.Web.Http; + using Microsoft.Restier.Publisher.OData.Batch; + + namespace Microsoft.OData.Service.Sample.TrippinInMemory + { + public static class WebApiConfig + { + public static void Register(HttpConfiguration config) + { + config.MapRestierRoute( + "TrippinApi", + "api/Trippin", + new RestierBatchHandler(GlobalConfiguration.DefaultServer)).Wait(); + } + } + } + diff --git a/docs/extending-restier/temporal-types.md b/docs/extending-restier/temporal-types.md index 87d9c1d9..79ad235b 100644 --- a/docs/extending-restier/temporal-types.md +++ b/docs/extending-restier/temporal-types.md @@ -1,3 +1,5 @@ +## Temporal Types + When using Microsoft.Restier.Providers.EntityFramework, temporal types are now supported. The table below shows how Temporal Types map to SQL Types: | EF Type | SQL Type | Edm Type | Need ColumnAttribute? | diff --git a/docs/vs-highlight.css b/docs/vs-highlight.css new file mode 100644 index 00000000..4a07c429 --- /dev/null +++ b/docs/vs-highlight.css @@ -0,0 +1,69 @@ +/* + +Visual Studio-like style based on original C# coloring by Jason Diamond + +*/ +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: white; + color: black; +} + +.hljs-comment, +.hljs-quote, +.hljs-variable { + color: #008000; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-built_in, +.hljs-name, +.hljs-tag { + color: #00f; +} + +.hljs-string, +.hljs-title, +.hljs-section, +.hljs-attribute, +.hljs-literal, +.hljs-template-tag, +.hljs-template-variable, +.hljs-type, +.hljs-addition { + color: #a31515; +} + +.hljs-deletion, +.hljs-selector-attr, +.hljs-selector-pseudo, +.hljs-meta { + color: #2b91af; +} + +.hljs-doctag { + color: #808080; +} + +.hljs-attr { + color: #f00; +} + +.hljs-symbol, +.hljs-bullet, +.hljs-link { + color: #00b0e8; +} + + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} + diff --git a/mkdocs.yml b/mkdocs.yml index 8d50de96..d62da4f2 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -12,10 +12,15 @@ pages: - 'Model Building': 'server/model-building.md' - Extending RESTier: - 'Temporal Types': 'extending-restier/temporal-types.md' + - 'In-Memory Provider': 'extending-restier/in-memory-provider.md' + - 'Additinal Operations': 'extending-restier/additional-operations.md' - Building the Client: - '.NET': 'clients/dot-net.md' - Release Notes: - 0.4.0-rc: 'release-notes/0-4-0-rc.md' - About: - 'License': 'license.md' - - 'Contributing': 'contribution-guidelines.md' \ No newline at end of file + - 'Contributing': 'contribution-guidelines.md' +markdown_extensions: + - toc: + baselevel: "1" \ No newline at end of file From 38e4c551333c4607287d098a19db7ecbc492a8f9 Mon Sep 17 00:00:00 2001 From: Robert McLaws Date: Sun, 5 Jun 2016 00:42:02 -0400 Subject: [PATCH 03/18] #3 --- docs/release-notes/0-3-0-beta1.md | 20 +++++++++++ docs/release-notes/0-3-0-beta2.md | 19 +++++++++++ docs/release-notes/0-5-0-beta.md | 32 +++++++++++++++++ docs/server/filters.md | 41 +++++++++++++++------- docs/server/method-authorization.md | 53 ++++++++++++++++++++++------- mkdocs.yml | 3 ++ 6 files changed, 144 insertions(+), 24 deletions(-) create mode 100644 docs/release-notes/0-3-0-beta1.md create mode 100644 docs/release-notes/0-3-0-beta2.md create mode 100644 docs/release-notes/0-5-0-beta.md diff --git a/docs/release-notes/0-3-0-beta1.md b/docs/release-notes/0-3-0-beta1.md new file mode 100644 index 00000000..964ecb58 --- /dev/null +++ b/docs/release-notes/0-3-0-beta1.md @@ -0,0 +1,20 @@ +## Downloads + + - NuGet: + - [Source (Zip)](https://github.com/OData/RESTier/archive/0.4.0-rc.zip) + +## New Features + + - Complex type support [#96](https://github.com/OData/RESTier/issues/96) + +## Enhancements + + - Northwind service uses script to generate database instead of .mdf/.ldf files. [#77](https://github.com/OData/RESTier/issues/77) + - Add StyleCop and FxCop to build process to ensure code quality. + - TripPin service supports singleton. + - Visual Studio 2015 and MSSQLLocalDB. + - Use xUnit 2.0 as the test framework for RESTier. [#104](https://github.com/OData/RESTier/issues/104) + +## Bug Fixes + + - None in this release. \ No newline at end of file diff --git a/docs/release-notes/0-3-0-beta2.md b/docs/release-notes/0-3-0-beta2.md new file mode 100644 index 00000000..036cf906 --- /dev/null +++ b/docs/release-notes/0-3-0-beta2.md @@ -0,0 +1,19 @@ +## Downloads + + - NuGet: + - [Source (Zip)](https://github.com/OData/RESTier/archive/0.4.0-rc.zip) + +## New Features + + - Support concrete classes that implement IDbSet<> [#159](https://github.com/OData/RESTier/issues/159) by [mkemal](https://github.com/mkemal) + - Support Edm.Date [#138](https://github.com/OData/RESTier/issues/138), [Tutorial](http://odata.github.io/RESTier/#03-04-Date) + +## Enhancements + + - Automatically start TripPin service when running E2E cases [#146](https://github.com/OData/RESTier/issues/146) + - No need to change machine configuration for running tests under Release mode + +## Bug Fixes + + - Fix incorrect status code [#115](https://github.com/OData/RESTier/issues/115) + - Computed annotation should not be added for Identity property [#116](https://github.com/OData/RESTier/issues/116) \ No newline at end of file diff --git a/docs/release-notes/0-5-0-beta.md b/docs/release-notes/0-5-0-beta.md new file mode 100644 index 00000000..a92a3fed --- /dev/null +++ b/docs/release-notes/0-5-0-beta.md @@ -0,0 +1,32 @@ +## Downloads + + - NuGet: + - [Source (Zip)](https://github.com/OData/RESTier/archive/0.4.0-rc.zip) + +## New Features + + - [[Issue](https://github.com/OData/RESTier/issues/150)] [[PR](https://github.com/OData/RESTier/pull/286)] Integrate Microsoft Dependency Injection Framework into RESTier. [Tutorial](http://odata.github.io/RESTier/#04-04-Api-Service). + - [[Issue](https://github.com/OData/RESTier/issues/273)] [[PR](https://github.com/OData/RESTier/pull/278)] Support temporal types in Restier.EF. [Tutorial](http://odata.github.io/RESTier/#03-07-Temporal). + - [[Issue](https://github.com/OData/RESTier/issues/383)] [[PR](https://github.com/OData/RESTier/pull/402)] Adopt Web OData Conversion Model builder as default EF provider model builder. [Tutorial](http://odata.github.io/WebApi/#02-04-convention-model-builder). + - [[Issue](https://github.com/OData/RESTier/issues/360)] [[PR](https://github.com/OData/RESTier/pull/399)] Support $apply in RESTier. [Tutorial](http://docs.oasis-open.org/odata/odata-data-aggregation-ext/v4.0/odata-data-aggregation-ext-v4.0.html). + +## Enhancements + + - The concept of **hook handler** now becomes **API service** after DI integration. + - The interface `IHookHandler` and `IDelegateHookHandler` 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](http://odata.github.io/RESTier/#04-04-Api-Service) for details. + - `AddHookHandler` is now replaced with `AddService` from DI. Please see [Tutorial](http://odata.github.io/RESTier/#04-04-Api-Service) for details. + - `GetHookHandler` is now replaced with `GetApiService` and `GetService` from DI. Please see [Tutorial](http://odata.github.io/RESTier/#04-04-Api-Service) for details. + - All the serializers and `DefaultRestierSerializerProvider` are now public. But we still need to address [#301](https://github.com/OData/RESTier/issues/301) to allow users to override the serializers. + - The interface `IApi` is now removed. Use `ApiBase` instead. We never expect users to directly implement their API classes from `IApi` anyway. The `Context` property in `IApi` now becomes a public property in `ApiBase`. + - Previously the `ApiData` class is very confusing. Now we have given it a more meaningful name `DataSourceStubs` which accurately describes the usage. Along with this change, we also rename `ApiDataReference` to `DataSourceStubReference` accordingly. + - `ApiBase.ApiConfiguration` is renamed to `ApiBase.Configuration` to keep consistent with `ApiBase.Context`. + - The static `Api` class is now separated into two classes `ApiBaseExtensions` and `ApiContextExtensions` to eliminate the ambiguity regarding the previous `Api` class. +## Bug Fixes + + - [[Issue](https://github.com/OData/RESTier/issues/123)] [[PR](https://github.com/OData/RESTier/pull/294)] Fix a bug that prevents using `Edm.Int64` as entity key. + - [[Issue](https://github.com/OData/RESTier/issues/269)] [[PR](https://github.com/OData/RESTier/pull/271)] Fix a bug that `NullReferenceException` is thrown when POST/PATCH/PUT with null property values. + - [[Issue](https://github.com/OData/RESTier/issues/287)] [[PR](https://github.com/OData/RESTier/pull/314)] Fix a bug that $count does not work correctly when there is $expand. + - [[Issue](https://github.com/OData/RESTier/issues/304)] [[PR](https://github.com/OData/RESTier/pull/306)] Fix a bug that `GetModelAsync` is not thread-safe. + - [[Issue](https://github.com/OData/RESTier/issues/304)] [[PR](https://github.com/OData/RESTier/pull/322)] Fix a bug that if `GetModelAsync` takes too long to complete, any subsequent request will fail. + - [[Issue](https://github.com/OData/RESTier/issues/308)] [[PR](https://github.com/OData/RESTier/pull/313)] Fix a bug that `NullReferenceException` is thrown when `ColumnTypeAttribute` does not have a `TypeName` property specified. + - [[Issue](https://github.com/OData/RESTier/issues/309)][[Issue](https://github.com/OData/RESTier/issues/310)][[Issue](https://github.com/OData/RESTier/issues/311)][[Issue](https://github.com/OData/RESTier/issues/312)] [[PR](https://github.com/OData/RESTier/pull/313)] Fix various bugs in the RESTier query pipeline. \ No newline at end of file diff --git a/docs/server/filters.md b/docs/server/filters.md index da37213a..675397c6 100644 --- a/docs/server/filters.md +++ b/docs/server/filters.md @@ -1,17 +1,34 @@ -# Welcome to MkDocs +# Entity Set Filters -For full documentation visit [mkdocs.org](http://mkdocs.org). +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 entityset)` method to the `Api` class. -## Commands +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 parameter and return an IQueryable result where T is the entity type. -* `mkdocs new [dir-name]` - Create a new project. -* `mkdocs serve` - Start the live-reloading docs server. -* `mkdocs build` - Build the documentation site. -* `mkdocs help` - Print this help message. +Supposed that ~/AdventureWorksLT/Products can get all the Product entities, the below OnFilterProducts method will filter some Product entities by checking the ProductID. -## Project layout +```cs +using Microsoft.Restier.Core; +using Microsoft.Restier.Provider.EntityFramework; +using System.Data.Entity; +using System.Linq; +using System.Threading.Tasks; - mkdocs.yml # The configuration file. - docs/ - index.md # The documentation homepage. - ... # Other markdown pages, images and other files. +namespace AdventureWorksLTSample.Models +{ + public class AdventureWorksApi : EntityFrameworkApi + { + protected IQueryable OnFilterProducts(IQueryable entitySet) + { + return entitySet.Where(s => s.ProductID % 3 == 0).AsQueryable(); + } + } +} +``` + +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. \ No newline at end of file diff --git a/docs/server/method-authorization.md b/docs/server/method-authorization.md index da37213a..1691178b 100644 --- a/docs/server/method-authorization.md +++ b/docs/server/method-authorization.md @@ -1,17 +1,46 @@ -# Welcome to MkDocs +# Method Authorization -For full documentation visit [mkdocs.org](http://mkdocs.org). +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. -## Commands +### Convention-Based 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 to `Can{Operation}{TargetName}`. -* `mkdocs new [dir-name]` - Create a new project. -* `mkdocs serve` - Start the live-reloading docs server. -* `mkdocs build` - Build the documentation site. -* `mkdocs help` - Print this help message. +The possible values for {Operation} are: -## Project layout ++ Insert ++ Update ++ Delete ++ Execute - mkdocs.yml # The configuration file. - docs/ - index.md # The documentation homepage. - ... # Other markdown pages, images and other files. +Possible values for {TargetName} are: + ++ *EntitySetName* ++ *ActionName* + +```cs +namespace Microsoft.OData.Service.Sample.Trippin.Api +{ + public class TrippinApi : EntityFrameworkApi + { + ... + // Can delete an entity from the entity set Trips? + protected bool CanDeleteTrips() + { + return false; + } + + // Can execute an action named ResetDataSource? + protected bool CanExecuteResetDataSource() + { + return false; + } + } +} +``` + +### Centralized Authorization +[section 2.9](http://odata.github.io/RESTier/#02-09-Customize-Submit) \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index d62da4f2..3df3a70b 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -17,7 +17,10 @@ pages: - Building the Client: - '.NET': 'clients/dot-net.md' - Release Notes: + - 0.5.0-beta: 'release-notes/0-5-0-beta.md' - 0.4.0-rc: 'release-notes/0-4-0-rc.md' + - 0.3.0-beta2: 'release-notes/0-3-0-beta2.md' + - 0.3.0-beta1: 'release-notes/0-3-0-beta1.md' - About: - 'License': 'license.md' - 'Contributing': 'contribution-guidelines.md' From 2bc5023e4e0265a278bf7a08756cd8e47710b7fe Mon Sep 17 00:00:00 2001 From: Robert McLaws Date: Sun, 5 Jun 2016 00:46:47 -0400 Subject: [PATCH 04/18] GitIgnore to remove doc compile from checkins --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index da1c6032..b8437ffb 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,5 @@ _[Ss]cripts *.pdb *.mdf *.ldf -.vs \ No newline at end of file +.vs +site \ No newline at end of file From 0a75e3c22d4bbf165dd7912e124045f525484457 Mon Sep 17 00:00:00 2001 From: Robert McLaws Date: Sun, 5 Jun 2016 10:17:33 -0400 Subject: [PATCH 05/18] VS Highlight theme --- mkdocs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/mkdocs.yml b/mkdocs.yml index 3df3a70b..5b989091 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -24,6 +24,7 @@ pages: - About: - 'License': 'license.md' - 'Contributing': 'contribution-guidelines.md' +extra_css: [vs-highlight.css] markdown_extensions: - toc: baselevel: "1" \ No newline at end of file From a10812e50d76b966fe5ba9d3c6f160ffe310716e Mon Sep 17 00:00:00 2001 From: Robert McLaws Date: Sun, 5 Jun 2016 11:36:30 -0400 Subject: [PATCH 06/18] Getting Started and Method Authorization --- docs/{installation.md => getting-started.md} | 0 docs/index.md | 47 +++++++++++++----- docs/server/method-authorization.md | 52 ++++++++++++++++---- mkdocs.yml | 2 +- 4 files changed, 79 insertions(+), 22 deletions(-) rename docs/{installation.md => getting-started.md} (100%) diff --git a/docs/installation.md b/docs/getting-started.md similarity index 100% rename from docs/installation.md rename to docs/getting-started.md diff --git a/docs/index.md b/docs/index.md index c477e0e2..95f37aa1 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,17 +1,42 @@ # Welcome to RESTier! -For full documentation visit [mkdocs.org](http://mkdocs.org). +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**](http://odata.github.io/WebApi/). 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. -## Commands +## What is OData? -* `mkdocs new [dir-name]` - Create a new project. -* `mkdocs serve` - Start the live-reloading docs server. -* `mkdocs build` - Build the documentation site. -* `mkdocs help` - Print this help message. +OData stands for the Open Data Protocol. 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. -## Project layout +## A Little History - mkdocs.yml # The configuration file. - docs/ - index.md # The documentation homepage. - ... # Other markdown pages, images and other files. +OData was originally designed by Microsoft to be a considtent framework for exposing Entity Framework objects over HTTP. +It has since been submitted to and ratified by OASIS as an industry standard for building queryable APIs. + +## Getting The Source + + + +## RESTier Contributors + +Special thanks to everyone involved in making RESTier the best API development platform for .NET. The following people +have made various contributions to the codebase: + +- Lewis Cheng [MS] +- Dong Liu [MS] +- Ray Yao [MS] +- Congyong Su [MS] +- Vincent He [MS] +- Layla Liu [MS] +- Challenh [MS] +- Mark Stafford [MS] +- Fan Ouyang [MS] +- Eric Erhardt [MS] +- Kemal M +- Cengiz Ilerler +- Robert McLaws \ No newline at end of file diff --git a/docs/server/method-authorization.md b/docs/server/method-authorization.md index 1691178b..7421b716 100644 --- a/docs/server/method-authorization.md +++ b/docs/server/method-authorization.md @@ -1,13 +1,12 @@ # Method Authorization -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. +Method Authorization allows you to have fine-grain control over how different types of API requests can be executed. -### Convention-Based Authorization + +## Convention-Based 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 to `Can{Operation}{TargetName}`. +method name must conform to the convention `Can{Operation}{TargetName}`. The possible values for {Operation} are: @@ -21,24 +20,57 @@ Possible values for {TargetName} are: + *EntitySetName* + *ActionName* +### Example + +The example below demonstrates how both types of {TargetName} can be used. + +- The first method shows a simple way to prevent *any* user from deleting a particular EntitySet. +- The second example shows how you can integrate role-based security + ```cs namespace Microsoft.OData.Service.Sample.Trippin.Api { + + /// + /// Specifies whether or not a Trip can be deleted through the API. + /// + /// + /// Add the following line in WebApiConfig.cs to register this code: + /// await config.MapRestierRoute("Trippin", "api", new RestierBatchHandler(GlobalConfiguration.DefaultServer)); + /// public class TrippinApi : EntityFrameworkApi { - ... - // Can delete an entity from the entity set Trips? - protected bool CanDeleteTrips() + + /// + /// Specifies whether or not a Trip can be deleted through the API. + /// + protected internal bool CanDeleteTrips() { return false; } + + /// + /// User role-based security to specifies whether or not a Trip can be updated through the API. + /// + protected internal bool CanUpdateTrips() + { + // Use legacy role-based security + return HttpContext.Current.User.IsInRole("admin"); + + // You can also use claims-based security. + // return ClaimsPrincipal.Current.IsInRole("admin"); + } - // Can execute an action named ResetDataSource? - protected bool CanExecuteResetDataSource() + /// + /// Specifies whether or not an action called ResetDataSource can be executed through the API. + /// + protected internal bool CanExecuteResetDataSource() { return false; } + } + } ``` diff --git a/mkdocs.yml b/mkdocs.yml index 5b989091..88ba2c52 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -4,7 +4,7 @@ theme: readthedocs pages: - Home: - 'Introduction': 'index.md' - - 'Installation': 'installation.md' + - 'Getting Started': 'getting-started.md' - Building the Service: - 'Entity Set Filters': 'server/filters.md' - 'Method Authorization': 'server/method-authorization.md' From f1bfc2396086c0e066fd1aa68df49a610ada1c22 Mon Sep 17 00:00:00 2001 From: Robert McLaws Date: Sun, 5 Jun 2016 13:18:28 -0400 Subject: [PATCH 07/18] More docs progress - MethodAuthorization progress - Changes to the supplemental CSS to better format tags. --- docs/index.md | 25 +++--- docs/server/method-authorization.md | 134 +++++++++++++++++++++++----- docs/vs-highlight.css | 11 ++- 3 files changed, 134 insertions(+), 36 deletions(-) diff --git a/docs/index.md b/docs/index.md index 95f37aa1..ffe057eb 100644 --- a/docs/index.md +++ b/docs/index.md @@ -27,16 +27,15 @@ It has since been submitted to and ratified by OASIS as an industry standard for Special thanks to everyone involved in making RESTier the best API development platform for .NET. The following people have made various contributions to the codebase: -- Lewis Cheng [MS] -- Dong Liu [MS] -- Ray Yao [MS] -- Congyong Su [MS] -- Vincent He [MS] -- Layla Liu [MS] -- Challenh [MS] -- Mark Stafford [MS] -- Fan Ouyang [MS] -- Eric Erhardt [MS] -- Kemal M -- Cengiz Ilerler -- Robert McLaws \ No newline at end of file +| Microsoft | External | +|---------------|----------------| +| Lewis Cheng | Cengiz Ilerler | +| Challenh | Kemal M | +| Eric Erhardt | Robert McLaws | +| Vincent He | | +| Dong Liu | | +| Layla Liu | | +| Fan Ouyang | | +| Congyong S | | +| Mark Stafford | | +| Ray Yao | | \ No newline at end of file diff --git a/docs/server/method-authorization.md b/docs/server/method-authorization.md index 7421b716..b8f35306 100644 --- a/docs/server/method-authorization.md +++ b/docs/server/method-authorization.md @@ -1,38 +1,58 @@ # Method Authorization Method Authorization allows you to have fine-grain control over how different types of API requests can be executed. +Since most of RESTier uses built-in convention over repetitive boiler-plate Controllers, you can't just add security attributes +to the controller methods, like you can with Web API. +However, there are two different methods for defining per-request security. One, like the rest of RESTier, is +convention-based, and the other executes before every request, allowing you to centralize your authorization logic. +This allows you to pick the approach that works best for your architecture. + +No matter what approach you chose, the concept is simple. Either technique uses a function that returns boolean. +Return `true`, and processing continues normally. Return `false`, and RESTier returns a 403 Unauthorized to the client. ## Convention-Based 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 +`protected internal` methods into the `Api` class. The method signatures must exactly match the following examples. The method name must conform to the convention `Can{Operation}{TargetName}`. -The possible values for {Operation} are: - -+ Insert -+ Update -+ Delete -+ Execute - -Possible values for {TargetName} are: - -+ *EntitySetName* -+ *ActionName* + + + + + + + + + +
The possible values for {Operation} are:The possible values for {TargetName} are:
+
    +
  • Insert
  • +
  • Update
  • +
  • Delete
  • +
  • Execute
  • +
+
+
    +
  • EntitySetName
  • +
  • ActionName
  • +
+
### Example -The example below demonstrates how both types of {TargetName} can be used. +The example below demonstrates how both types of `{TargetName}` can be used. -- The first method shows a simple way to prevent *any* user from deleting a particular EntitySet. -- The second example shows how you can integrate role-based security +- The first method shows a simple way to prevent *any* user from deleting a particular EntitySet. +- The second method shows how you can integrate role-based security using multiple techniques. +- The third method shows how to prevent execution a custom Action. ```cs namespace Microsoft.OData.Service.Sample.Trippin.Api { /// - /// Specifies whether or not a Trip can be deleted through the API. + /// Customizations to the EntityFrameworkApi for the TripPin service. /// /// /// Add the following line in WebApiConfig.cs to register this code: @@ -42,7 +62,7 @@ namespace Microsoft.OData.Service.Sample.Trippin.Api { /// - /// Specifies whether or not a Trip can be deleted through the API. + /// Specifies whether or not a Trip can be deleted from an EntitySet. /// protected internal bool CanDeleteTrips() { @@ -50,7 +70,7 @@ namespace Microsoft.OData.Service.Sample.Trippin.Api } /// - /// User role-based security to specifies whether or not a Trip can be updated through the API. + /// User role-based security to specifies whether or not a updated Trip can be sent to an EntitySet. /// protected internal bool CanUpdateTrips() { @@ -62,7 +82,7 @@ namespace Microsoft.OData.Service.Sample.Trippin.Api } /// - /// Specifies whether or not an action called ResetDataSource can be executed through the API. + /// Specifies whether or not an Action called ResetDataSource can be executed through the API. /// protected internal bool CanExecuteResetDataSource() { @@ -74,5 +94,77 @@ namespace Microsoft.OData.Service.Sample.Trippin.Api } ``` -### Centralized Authorization -[section 2.9](http://odata.github.io/RESTier/#02-09-Customize-Submit) \ No newline at end of file +## Centralized Authorization + +In addition to the more granular convention-based approach, you can also centralize processing into one location. This is +useful if + +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 the centralized authorization logic. + +- Create a class that implements `IChangeSetItemAuthorizer`. +- Register that class with RESTier through Dependency Injection (DI). + +### Example + +```cs +namespace Microsoft.OData.Service.Sample.Trippin.Api +{ + + /// + /// + /// + public class CustomAuthorizer : IChangeSetItemAuthorizer + { + + // The inner handler will call CanUpdate/Insert/Delete method + private IChangeSetItemProcessor Inner { get; set; } + + /// + /// + /// + public Task AuthorizeAsync(SubmitContext context, ChangeSetItem item, CancellationToken cancellationToken) + { + // TODO: RWM: Provide legitimate samples here, along with parameter documentation. + } + + } + + /// + /// Customizations to the EntityFrameworkApi for the TripPin service. + /// + /// + /// Add the following line in WebApiConfig.cs to register this code: + /// await config.MapRestierRoute("Trippin", "api", new RestierBatchHandler(GlobalConfiguration.DefaultServer)); + /// + public class TrippinApi : EntityFrameworkApi + { + + /// + /// Allows us to leverage DI to inject additional capabilities into RESTier. + /// + protected override IServiceCollection ConfigureApi(IServiceCollection services) + { + return base.ConfigureApi(services) + .AddService(); + } + + } + +} +``` + +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 Inner {get; set;}" in class CustomizedAuthorizer, +then call Inner.Inspect() to call RESTier logic which call Authorize part logic defined in section 2.3. + +## Unit Testing Considerations + +Because both of these methods are de-coupled from the code that interacts with the database, the Authorization +logic is easily testable, without having to fire up the entire Web API + RESTier pipeline. + +### Setting up your Unit Test + +If you don't have a unit test project for your API project already, start by creating one. \ No newline at end of file diff --git a/docs/vs-highlight.css b/docs/vs-highlight.css index 4a07c429..09cbe2e0 100644 --- a/docs/vs-highlight.css +++ b/docs/vs-highlight.css @@ -1,7 +1,5 @@ /* - Visual Studio-like style based on original C# coloring by Jason Diamond - */ .hljs { display: block; @@ -67,3 +65,12 @@ Visual Studio-like style based on original C# coloring by Jason Diamond Date: Sun, 5 Jun 2016 15:05:00 -0400 Subject: [PATCH 08/18] Unit test example for Method Authorization --- docs/server/method-authorization.md | 138 +++++++++++++++++++++++++++- 1 file changed, 133 insertions(+), 5 deletions(-) diff --git a/docs/server/method-authorization.md b/docs/server/method-authorization.md index b8f35306..55a0ada7 100644 --- a/docs/server/method-authorization.md +++ b/docs/server/method-authorization.md @@ -48,6 +48,10 @@ The example below demonstrates how both types of `{TargetName}` can be used. - The third method shows how to prevent execution a custom Action. ```cs +using Microsoft.Restier.Providers.EntityFramework; +using System; +using System.Security.Claims; + namespace Microsoft.OData.Service.Sample.Trippin.Api { @@ -74,11 +78,11 @@ namespace Microsoft.OData.Service.Sample.Trippin.Api /// protected internal bool CanUpdateTrips() { - // Use legacy role-based security - return HttpContext.Current.User.IsInRole("admin"); + // Use claims-based security + return ClaimsPrincipal.Current.IsInRole("admin"); - // You can also use claims-based security. - // return ClaimsPrincipal.Current.IsInRole("admin"); + // You can also use legacy role-based security, though it's harder to test. + //return HttpContext.Current.User.IsInRole("admin"); } /// @@ -110,6 +114,9 @@ There are two steps to plug in the centralized authorization logic. ### Example ```cs +using Microsoft.OData.Core; +using Microsoft.Restier.Providers.EntityFramework; + namespace Microsoft.OData.Service.Sample.Trippin.Api { @@ -156,6 +163,7 @@ namespace Microsoft.OData.Service.Sample.Trippin.Api } ``` +NEEDS CLARIFICATION: 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 Inner {get; set;}" in class CustomizedAuthorizer, then call Inner.Inspect() to call RESTier logic which call Authorize part logic defined in section 2.3. @@ -167,4 +175,124 @@ logic is easily testable, without having to fire up the entire Web API + RESTier ### Setting up your Unit Test -If you don't have a unit test project for your API project already, start by creating one. \ No newline at end of file +If you don't have a unit test project for your API project already, start by creating one. Repeat the process +outlined in "Getting Started" to install the RESTier packages into your Unit Test project. The add the FluentAssertions +package. + +Next, go back to your API. Expand Properties, double-click AssemblyInfo.cs, and add the following line to the very end: +`[assembly: InternalsVisibleTo("{TestProjectAssembly}")]`, making sure you replace {TestProjectAssembly} with the actual +assembly name. This is important, because otherwise the tests won't be able to see the `protected internal` methods the +authorization conventions use. + +### Example + +Given the [Convention-Based Authorization](#convention-based-authorization) example, the tests below should have 100% code + +coverage, and should pass without any required changes. + +```cs +using FluentAssertions; +using Microsoft.OData.Core; +using Microsoft.OData.Service.Sample.Trippin.Api; +using Microsoft.Restier.Providers.EntityFramework; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Security.Claims; + +namespace Trippin.Tests.Api +{ + + /// + /// Test cases for the RESTier Method Authorizers. + /// + [TestClass] + public class TrippinApiTests + { + + #region Trips EntitySet + + /// + /// Tests if the Trips EntitySet is properly configured to reject delete requests. + /// + [TestMethod] + public void TrippinApi_Trips_CanDelete_IsConfigured() + { + var api = new TrippinApi(); + api.CanDeleteTrips.Should().BeFalse(); + } + + /// + /// Tests if the Trips EntitySet is properly configured to accept Admin update requests. + /// + [TestMethod] + public void TrippinApi_Trips_CanUpdate_IsAdmin() + { + var api = new TrippinApi(); + + // We won't be testing HttpContext-related security here, because that requires mocking, + // which is outside the scope of this document. + AuthenticateAsAdmin(); + api.CanUpdateTrips.Should().BeTrue(); + } + + /// + /// Tests if the Trips EntitySet is properly configured to reject non-Admin update requests. + /// + [TestMethod] + public void TrippinApi_Trips_CanUpdate_IsNotAdmin() + { + var api = new TrippinApi(); + // We won't be testing HttpContext-related security here, because that requires mocking, + // which is outside the scope of this document. + AuthenticateAsNonAdmin(); + api.CanUpdateTrips.Should().BeFalse(); + } + + #endregion + + #region Actions + + /// + /// Tests if the Trips EntitySet is properly configured to reject delete requests. + /// + [TestMethod] + public void TrippinApi_CanExecuteResetDataSource_IsConfigured() + { + var api = new TrippinApi(); + api.CanExecuteResetDataSource.Should().BeFalse(); + } + + #endregion + + #region Test Helpers + + /// + /// Sets the Thread.CurrentPrincipal to a test user with an "admin" Role Claim. + /// + internal static void AuthenticateAsAdmin() + { + var claimsCollection = new List + { + new Claim(ClaimTypes.Role, "admin") + }; + var claimsIdentity = new ClaimsIdentity(claimsCollection, "Test User"); + Thread.CurrentPrincipal = new ClaimsPrincipal(claimsIdentity); + } + + /// + /// Sets the Thread.CurrentPrincipal to a test user without an "admin" Role Claim. + /// + internal static void AuthenticateAsNonAdmin() + { + var claimsCollection = new List(); + var claimsIdentity = new ClaimsIdentity(claimsCollection, "Test User"); + Thread.CurrentPrincipal = new ClaimsPrincipal(claimsIdentity); + } + + #endregion + + } + +} + +``` \ No newline at end of file From a6d1d15fd9755235269970c9b6fd43ea7b5443d7 Mon Sep 17 00:00:00 2001 From: Robert McLaws Date: Sun, 5 Jun 2016 17:06:17 -0400 Subject: [PATCH 09/18] More doc updates - Release notes updates - More realistic Filters examples --- docs/release-notes/0-3-0-beta1.md | 4 +- docs/release-notes/0-3-0-beta2.md | 8 ++-- docs/release-notes/0-4-0-rc.md | 4 +- docs/release-notes/0-4-0-rc2.md | 8 ++++ docs/release-notes/0-5-0-beta.md | 4 +- docs/server/filters.md | 58 +++++++++++++++++++++-------- docs/server/method-authorization.md | 9 ++--- mkdocs.yml | 3 +- 8 files changed, 67 insertions(+), 31 deletions(-) create mode 100644 docs/release-notes/0-4-0-rc2.md diff --git a/docs/release-notes/0-3-0-beta1.md b/docs/release-notes/0-3-0-beta1.md index 964ecb58..14512b6c 100644 --- a/docs/release-notes/0-3-0-beta1.md +++ b/docs/release-notes/0-3-0-beta1.md @@ -1,7 +1,7 @@ ## Downloads - - NuGet: - - [Source (Zip)](https://github.com/OData/RESTier/archive/0.4.0-rc.zip) + - NuGet: `Install-Package Microsoft.Restier -Version 0.3.0-beta1 -Pre` [[Website](http://www.nuget.org/packages/Microsoft.Restier/0.3.0-beta1)] + - Source: [[Zip](https://github.com/OData/RESTier/archive/0.3.0-beta1.zip)] [[Tarball](https://github.com/OData/RESTier/archive/0.3.0-beta1.tar.gz)] ## New Features diff --git a/docs/release-notes/0-3-0-beta2.md b/docs/release-notes/0-3-0-beta2.md index 036cf906..6a2d3838 100644 --- a/docs/release-notes/0-3-0-beta2.md +++ b/docs/release-notes/0-3-0-beta2.md @@ -1,12 +1,12 @@ ## Downloads - - NuGet: - - [Source (Zip)](https://github.com/OData/RESTier/archive/0.4.0-rc.zip) + - NuGet: `Install-Package Microsoft.Restier -Version 0.3.0-beta2 -Pre` [[Website](http://www.nuget.org/packages/Microsoft.Restier/0.3.0-beta2)] + - Source: [[Zip](https://github.com/OData/RESTier/archive/0.3.0-beta2.zip)] [[Tarball](https://github.com/OData/RESTier/archive/0.3.0-beta2.tar.gz)] ## New Features - - Support concrete classes that implement IDbSet<> [#159](https://github.com/OData/RESTier/issues/159) by [mkemal](https://github.com/mkemal) - - Support Edm.Date [#138](https://github.com/OData/RESTier/issues/138), [Tutorial](http://odata.github.io/RESTier/#03-04-Date) + - [[Issue](https://github.com/OData/RESTier/issues/126)] [[PR](https://github.com/OData/RESTier/pull/159)] Support concrete classes that implement IDbSet<> by [mkemal](https://github.com/mkemal) + - [[Issue](https://github.com/OData/RESTier/issues/138)] [[PR](https://github.com/OData/RESTier/pull/194)] Support Edm.Date [Tutorial](http://odata.github.io/RESTier/#03-04-Date) ## Enhancements diff --git a/docs/release-notes/0-4-0-rc.md b/docs/release-notes/0-4-0-rc.md index 3091ea2b..1f7afaa1 100644 --- a/docs/release-notes/0-4-0-rc.md +++ b/docs/release-notes/0-4-0-rc.md @@ -1,7 +1,7 @@ ## Downloads - - NuGet: - - [Source (Zip)](https://github.com/OData/RESTier/archive/0.4.0-rc.zip) + - NuGet: `Install-Package Microsoft.Restier -Version 0.4.0-rc -Pre` [[Website](http://www.nuget.org/packages/Microsoft.Restier/0.4.0-rc)] + - Source: [[Zip](https://github.com/OData/RESTier/archive/0.4.0-rc.zip)] [[Tarball](https://github.com/OData/RESTier/archive/0.4.0-rc.tar.gz)] ## New Features diff --git a/docs/release-notes/0-4-0-rc2.md b/docs/release-notes/0-4-0-rc2.md new file mode 100644 index 00000000..212a8ac4 --- /dev/null +++ b/docs/release-notes/0-4-0-rc2.md @@ -0,0 +1,8 @@ +## Downloads + + - NuGet: `Install-Package Microsoft.Restier -Version 0.4.0-rc2 -Pre` [[Website](http://www.nuget.org/packages/Microsoft.Restier/0.4.0-rc2)] + - Source: [[Zip](https://github.com/OData/RESTier/archive/0.4.0-rc2.zip)] [[Tarball](https://github.com/OData/RESTier/archive/0.4.0-rc2.tar.gz)] + +## Bug Fixes + + - Support string as return type or argument of functions [#258](https://github.com/OData/RESTier/issues/258) \ No newline at end of file diff --git a/docs/release-notes/0-5-0-beta.md b/docs/release-notes/0-5-0-beta.md index a92a3fed..c3257ad1 100644 --- a/docs/release-notes/0-5-0-beta.md +++ b/docs/release-notes/0-5-0-beta.md @@ -1,7 +1,7 @@ ## Downloads - - NuGet: - - [Source (Zip)](https://github.com/OData/RESTier/archive/0.4.0-rc.zip) + - NuGet: `Install-Package Microsoft.Restier -Pre` [[Website](http://www.nuget.org/packages/Microsoft.Restier/0.5.0-beta)] + - Source: [[Zip](https://github.com/OData/RESTier/archive/0.5.0-beta.zip)] [[Tarball](https://github.com/OData/RESTier/archive/0.5.0-beta.tar.gz)] ## New Features diff --git a/docs/server/filters.md b/docs/server/filters.md index 675397c6..aee5f327 100644 --- a/docs/server/filters.md +++ b/docs/server/filters.md @@ -1,34 +1,62 @@ -# Entity Set Filters +# EntitySet Filters -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 entityset)` method to the `Api` class. +Have you ever wanted to limit the results of a particular query based on the current user, or maybe you only want +to return results that are marked "active"? -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 parameter and return an IQueryable result where T is the entity type. +EntitySet Filters allow you to consistently control the shape of the results returned from particular EntitySets, +even across navigation properties. -Supposed that ~/AdventureWorksLT/Products can get all the Product entities, the below OnFilterProducts method will filter some Product entities by checking the ProductID. +## Convention-Based Filtering + +Like the rest of RESTier, this is accomplished through a simple convention that +meets the following criteria: + + 1. The filter method name must be `OnFilter{EntitySetName}`, where `{EntitySetName}` is the name the target EntitySet. + 2. It must be a `protected internal` method on the implementing `EntityFrameworkApi` class. + 3. It should accept an IQueryable parameter and return an IQueryable result where T is the Entity type. + +### Example ```cs using Microsoft.Restier.Core; using Microsoft.Restier.Provider.EntityFramework; using System.Data.Entity; using System.Linq; +using System.Security.Claims; using System.Threading.Tasks; -namespace AdventureWorksLTSample.Models +namespace Microsoft.OData.Service.Sample.Trippin.Api { - public class AdventureWorksApi : EntityFrameworkApi + + /// + /// Customizations to the EntityFrameworkApi for the TripPin service. + /// + /// + /// Add the following line in WebApiConfig.cs to register this code: + /// await config.MapRestierRoute("Trippin", "api", new RestierBatchHandler(GlobalConfiguration.DefaultServer)); + /// + public class TrippinApi : EntityFrameworkApi { - protected IQueryable OnFilterProducts(IQueryable entitySet) + + /// + /// Filters queries to the Trips EntitySet to only return Users that have Trips. + /// + protected internal IQueryable OnFilterPeople(IQueryable entitySet) { - return entitySet.Where(s => s.ProductID % 3 == 0).AsQueryable(); + return entitySet.Where(c => c.Trips.Any()).AsQueryable(); } + + /// + /// Filters queries to the Trips EntitySet to only return the current user's Trips. + /// + protected internal IQueryable OnFilterTrips(IQueryable entitySet) + { + return entitySet.Where(c => c.PersonId == ClaimsPrincipal.Current.FindFirst("currentUserId")).AsQueryable(); + } + } + } ``` -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. \ No newline at end of file + ## Centralized Filtering diff --git a/docs/server/method-authorization.md b/docs/server/method-authorization.md index 55a0ada7..f902c681 100644 --- a/docs/server/method-authorization.md +++ b/docs/server/method-authorization.md @@ -179,15 +179,14 @@ If you don't have a unit test project for your API project already, start by cre outlined in "Getting Started" to install the RESTier packages into your Unit Test project. The add the FluentAssertions package. -Next, go back to your API. Expand Properties, double-click AssemblyInfo.cs, and add the following line to the very end: -`[assembly: InternalsVisibleTo("{TestProjectAssembly}")]`, making sure you replace {TestProjectAssembly} with the actual -assembly name. This is important, because otherwise the tests won't be able to see the `protected internal` methods the -authorization conventions use. +Next, go back to your API project. Expand the "Properties" node, double-click AssemblyInfo.cs, and add the following line +to the very end of the file: `[assembly: InternalsVisibleTo("{TestProjectAssembly}")]`, making sure you replace +{TestProjectAssembly} with the actual assembly name. This is important, because otherwise the tests won't be able to see +the `protected internal` methods the authorization conventions use. ### Example Given the [Convention-Based Authorization](#convention-based-authorization) example, the tests below should have 100% code - coverage, and should pass without any required changes. ```cs diff --git a/mkdocs.yml b/mkdocs.yml index 88ba2c52..2d75fc54 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -13,11 +13,12 @@ pages: - Extending RESTier: - 'Temporal Types': 'extending-restier/temporal-types.md' - 'In-Memory Provider': 'extending-restier/in-memory-provider.md' - - 'Additinal Operations': 'extending-restier/additional-operations.md' + - 'Additional Operations': 'extending-restier/additional-operations.md' - Building the Client: - '.NET': 'clients/dot-net.md' - Release Notes: - 0.5.0-beta: 'release-notes/0-5-0-beta.md' + - 0.4.0-rc2: 'release-notes/0-4-0-rc2.md' - 0.4.0-rc: 'release-notes/0-4-0-rc.md' - 0.3.0-beta2: 'release-notes/0-3-0-beta2.md' - 0.3.0-beta1: 'release-notes/0-3-0-beta1.md' From ab7995cb05ca3105fe9074504826732b112d63eb Mon Sep 17 00:00:00 2001 From: Robert McLaws Date: Sun, 5 Jun 2016 17:44:12 -0400 Subject: [PATCH 10/18] Cleaning up the intro --- docs/index.md | 29 ++++++++++++++++++----------- docs/release-notes/0-3-0-beta2.md | 2 +- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/docs/index.md b/docs/index.md index ffe057eb..e03f1db2 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,11 +1,18 @@ # Welcome to RESTier! 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**](http://odata.github.io/WebApi/). 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. +platform. It can be seen as a middle-ware on top of [**Web API OData**](http://odata.github.io/WebApi/). + +RESTier is the spiritual successor to WCF Data Services. Instead of generating endless boilerplate code with the current +Web API + OData toolchain, RESTier helps you boostrap a standardized, queryable HTTP-based REST interface in literally +minutes. And that's just the beginning. + +Like WCF Data Services, RESTier provides simple and straightforward ways to shape queries and intercept submissions +before and after they hit the database. And like Web API + OData, you still have the flexibility to add your own +custom queries and actions with techniques you're already familiar with. + +But RESTier isn't just for OData and the Entity Framework. It also supports adding additional publishers to support other +protocols and additional providers to support other data sources. ## What is OData? @@ -13,14 +20,14 @@ OData stands for the Open Data Protocol. OData enables the creation and consumpt resources, defined in a data model and identified by using URLs, to be published and edited by Web clients using simple HTTP requests. -## A Little History - -OData was originally designed by Microsoft to be a considtent framework for exposing Entity Framework objects over HTTP. -It has since been submitted to and ratified by OASIS as an industry standard for building queryable APIs. - -## Getting The Source +OData was originally designed by Microsoft to be a framework for exposing Entity Framework objects over REST services. +The first concepts shipped as "Project Astoria" in 2007. By 2009, the concept had evolved enough for Microsoft to +announce OData, along with a [larger effort](https://blogs.msdn.microsoft.com/odatateam/2009/11/17/breaking-down-data-silos-the-open-data-protocol-odata/) +to push the format as an insustry standard. +Work on the current version of the protocol (V4) began in April 2012, and was ratified by OASIS as an industry standard in Feb 2014. +You can find out more about OData at [OData.org](http://www.odata.org/). ## RESTier Contributors diff --git a/docs/release-notes/0-3-0-beta2.md b/docs/release-notes/0-3-0-beta2.md index 6a2d3838..96fc3139 100644 --- a/docs/release-notes/0-3-0-beta2.md +++ b/docs/release-notes/0-3-0-beta2.md @@ -5,7 +5,7 @@ ## New Features - - [[Issue](https://github.com/OData/RESTier/issues/126)] [[PR](https://github.com/OData/RESTier/pull/159)] Support concrete classes that implement IDbSet<> by [mkemal](https://github.com/mkemal) + - [[Issue](https://github.com/OData/RESTier/issues/126)] [[PR](https://github.com/OData/RESTier/pull/159)] Support concrete classes that implement IDbSet>T< by [mkemal](https://github.com/mkemal) - [[Issue](https://github.com/OData/RESTier/issues/138)] [[PR](https://github.com/OData/RESTier/pull/194)] Support Edm.Date [Tutorial](http://odata.github.io/RESTier/#03-04-Date) ## Enhancements From 0073bac3d9651cea66091bd9b449f61e8f98d232 Mon Sep 17 00:00:00 2001 From: Robert McLaws Date: Sun, 5 Jun 2016 17:53:56 -0400 Subject: [PATCH 11/18] Selected TOC item CSS fixes --- docs/vs-highlight.css | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/vs-highlight.css b/docs/vs-highlight.css index 09cbe2e0..5d765e49 100644 --- a/docs/vs-highlight.css +++ b/docs/vs-highlight.css @@ -65,12 +65,17 @@ Visual Studio-like style based on original C# coloring by Jason Diamond a { + padding-left: 2.42em; +} +.wy-menu-vertical li.current > ul li a { + padding-left: 3.23em; +} \ No newline at end of file From 55d28d07f3a75d1f1d2b7fb64de60f6cdcfc91c1 Mon Sep 17 00:00:00 2001 From: Robert McLaws Date: Sun, 5 Jun 2016 17:58:46 -0400 Subject: [PATCH 12/18] Some minor intro edits --- docs/index.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/index.md b/docs/index.md index e03f1db2..8b7614c6 100644 --- a/docs/index.md +++ b/docs/index.md @@ -3,11 +3,11 @@ 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**](http://odata.github.io/WebApi/). -RESTier is the spiritual successor to WCF Data Services. Instead of generating endless boilerplate code with the current -Web API + OData toolchain, RESTier helps you boostrap a standardized, queryable HTTP-based REST interface in literally -minutes. And that's just the beginning. +RESTier is the spiritual successor to [WCF Data Services](https://en.wikipedia.org/wiki/WCF_Data_Services). Instead of +generating endless boilerplate code with the current Web API + OData toolchain, RESTier helps you boostrap a standardized, +queryable HTTP-based REST interface in literally minutes. And that's just the beginning. -Like WCF Data Services, RESTier provides simple and straightforward ways to shape queries and intercept submissions +Like WCF Data Services before it, RESTier provides simple and straightforward ways to shape queries and intercept submissions before and after they hit the database. And like Web API + OData, you still have the flexibility to add your own custom queries and actions with techniques you're already familiar with. From 863b46eb18fdc356410ebe8db15b469bfbe95c89 Mon Sep 17 00:00:00 2001 From: Robert McLaws Date: Sun, 5 Jun 2016 18:42:11 -0400 Subject: [PATCH 13/18] Another round of fixes - Temporal Type adjustments - Contribution Guidelines - Method authorization tightening - Interceptors scaffolding --- docs/contribution-guidelines.md | 80 +++++- docs/extending-restier/temporal-types.md | 24 +- docs/server/interceptors.md | 312 ++++++++++++++++++++++- docs/server/method-authorization.md | 6 +- 4 files changed, 386 insertions(+), 36 deletions(-) diff --git a/docs/contribution-guidelines.md b/docs/contribution-guidelines.md index da37213a..634f4058 100644 --- a/docs/contribution-guidelines.md +++ b/docs/contribution-guidelines.md @@ -1,17 +1,73 @@ -# Welcome to MkDocs +# How Can I Contribute? +There are many ways for you to contribute to RESTier. The easiest way is to participate in discussion of +features and issues. You can also contribute by sending pull requests of features or bug fixes to us. +Contribution to the [documentations](http://odata.github.io/RESTier/) is also highly welcomed. -For full documentation visit [mkdocs.org](http://mkdocs.org). +## Discussion +You can participate into discussions and ask questions about RESTier at our +[Github issues](https://github.com/OData/RESTier/issues). -## Commands +## Bug Reports +When reporting a bug at the issue tracker, fill the template of issue. The issue related to other libraries +should not be reported in RESTier library issue tracker, but be reported to other libraries' issue tracker. -* `mkdocs new [dir-name]` - Create a new project. -* `mkdocs serve` - Start the live-reloading docs server. -* `mkdocs build` - Build the documentation site. -* `mkdocs help` - Print this help message. +## Pull Requests +**Pull request is the only way we accept code and document contribution.** Pull request of document, features +and bug fixes are both welcomed. Refer to this [link](https://help.github.com/articles/using-pull-requests/) +to learn details about pull request. Before you send a pull request to us, you need to make sure you've +followed the steps listed below. -## Project layout +### Pick an issue to work on +You should either create or pick an issue on the [issue tracker](https://github.com/OData/RESTier/issues) +before you work on the pull request. After the RESTier team has reviewed this issue and change its label +to "accepting pull request", you can work on the code change. - mkdocs.yml # The configuration file. - docs/ - index.md # The documentation homepage. - ... # Other markdown pages, images and other files. +### Prepare Tools +[Atom](https://atom.io/) with package [atom-beautify](https://atom.io/packages/atom-beautify) and +[markdown-toc](https://atom.io/packages/markdown-toc) is recommended to edit the document. +[MarkdownPad](http://www.markdownpad.com/) can also be used to edit the document.
+Visual Studio 2015 is recommended for code contribution. + +### Steps to create a pull request +These are the recommended steps to create a pull request:
+ +1. Create a forked repository of [https://github.com/OData/RESTier.git](https://github.com/OData/RESTier.git) +2. Clone the forked repository into your local environment +3. Add a git remote to upstream for local repository with command _git remote add upstream +[https://github.com/OData/RESTier.git](https://github.com/OData/RESTier.git)_ +4. Make code changes and add test cases, refer Test specification section for more details about test +5. Test the changed codes with one-click build and test script +6. Commit changed code to local repository with clear message +7. Rebase the code to upstream via command _git pull --rebase upstream master_ and resolve conflicts +if there is any then continue rebase via command _git pull --rebase continue_ +8. Push local commit to the forked repository +9. Create pull request from forked repository Web console via comparing with upstream. +10. Complete a Contributor License Agreement (CLA), refer below section for more details. +11. Pull request will be reviewed by Microsoft OData team +12. Address comments and revise code if necessary +13. Commit the changes to local repository or amend existing commit via command _git commit --amend_ +14. Rebase the code with upstream again via command _git pull --rebase upstream master_ and resolve +conflicts if there is any then continue rebase via command _git pull --rebase continue_ +15. Test the changed codes with one-click build and test script again +16. Push changes to the forked repository and use _--force_ option if existing commit is amended +17. Microsoft OData team will merge the pull request into upstream + +### Test specification +All tests need to be written with xUnit. Here are some rules to follow when you are organizing the +test code: + +- **Project name correspondence** (`X -> X.Tests`). For instance, all the test code of the `Microsoft.Restier.Core` project should be placed in the `Microsoft.Restier.Core.Tests` project. Path and file name correspondence. (`X/Y/Z/A.cs -> X.Tests/Y/Z/ATests.cs`). For example, the test code of the `ConventionBasedApiModelBuilder` class (in the `Microsoft.Restier.Core/Convention/ConventionBasedApiModelBuilder.cs` file) should be placed in the `Microsoft.Restier.Core.Tests/Convention/ConventionBasedApiModelBuilderTests.cs` file. +- **Namespace correspondence** (`X.Tests/Y/Z -> X.Tests.Y.Z`). The namespace of the file should strictly follow the path. For example, the namespace of the `ConventionBasedApiModelBuilderTests.cs` file should be `Microsoft.Restier.Core.Tests.Convention`. +- **Utility classes**. The file for a utility class can be placed at the same level of its user or a shared level that is visible to all its users. But the file name must **NOT** be ended with `Tests` to avoid any confusion. +- **Integration and scenario tests**. Those tests usually involve multiple modules and have some specific scenarios. They should be placed separately in `X.Tests/IntegrationTests` and `X.Tests/ScenarioTests`. There is no hard requirement of the folder structure for those tests. But they should be organized logically and systematically as possible. + +### Complete a Contribution License Agreement (CLA) +You will need to complete a Contributor License Agreement (CLA). Briefly, this agreement testifies +that you are granting us permission to use the submitted change according to the terms of the +project's license, and that the work being submitted is under appropriate copyright. + +Please submit a Contributor License Agreement (CLA) before submitting a pull request. +[Download the agreement](https://github.com/odata/odatacpp/wiki/files/Microsoft Contribution License Agreement.pdf)), +sign, scan, and email it back to [cla@microsoft.com](mailto:cla@microsoft.com). Be sure to include your Github +user name along with the agreement. Only after we have received the signed CLA, we'll review the pull request that +you send. You only need to do this once for contributing to any Microsoft open source projects. \ No newline at end of file diff --git a/docs/extending-restier/temporal-types.md b/docs/extending-restier/temporal-types.md index 79ad235b..f268a39a 100644 --- a/docs/extending-restier/temporal-types.md +++ b/docs/extending-restier/temporal-types.md @@ -1,6 +1,7 @@ -## Temporal Types +# Temporal Types -When using Microsoft.Restier.Providers.EntityFramework, temporal types are now supported. The table below shows how Temporal Types map to SQL Types: +When using the Microsoft.Restier.Providers.EntityFramework provider, temporal types are now supported. The table below +shows how Temporal Types map to SQL Types: | EF Type | SQL Type | Edm Type | Need ColumnAttribute? | |:---------------------:|:------------------:|:------------------:|:---------------------:| @@ -12,8 +13,10 @@ When using Microsoft.Restier.Providers.EntityFramework, temporal types are now s The next sections illustrate how to use use temporal types in various scenarios. -### Add Edm.DateTimeOffset property -Suppose you have an entity class `Person`, all the following code define `Edm.DateTimeOffset` properties in the EDM model though the underlying SQL types are different (see the value of the `TypeName` property). You can see Column attribute is optional here. +## Edm.DateTimeOffset +Suppose you have an entity class `Person`, all the following code define `Edm.DateTimeOffset` properties in the +EDM model though the underlying SQL types are different (see the value of the `TypeName` property). You can see +Column attribute is optional here. using System; @@ -33,7 +36,7 @@ Suppose you have an entity class `Person`, all the following code define `Edm.Da } -### Add Edm.Date property +## Edm.Date The following code define an `Edm.Date` property in the EDM model. using System; @@ -45,7 +48,7 @@ The following code define an `Edm.Date` property in the EDM model. public DateTime BirthDate { get; set; } } -### Add Edm.Duration property +## Edm.Duration The following code define an `Edm.Duration` property in the EDM model. using System; @@ -56,8 +59,9 @@ The following code define an `Edm.Duration` property in the EDM model. public TimeSpan WorkingHours { get; set; } } -### Add Edm.TimeOfDay property -The following code define an `Edm.TimeOfDay` property in the EDM model. Please note that you MUST NOT omit the `ColumnTypeAttribute` on a `TimeSpan` property otherwise it will be recognized as an `Edm.Duration` as described above. +## Edm.TimeOfDay +The following code define an `Edm.TimeOfDay` property in the EDM model. Please note that you MUST NOT omit the +`ColumnTypeAttribute` on a `TimeSpan` property otherwise it will be recognized as an `Edm.Duration` as described above. using System; using System.ComponentModel.DataAnnotations.Schema; @@ -68,4 +72,6 @@ The following code define an `Edm.TimeOfDay` property in the EDM model. Please n public TimeSpan BirthTime { get; set; } } -As before, if you have the need to override `ODataPayloadValueConverter`, please now change to override `RestierPayloadValueConverter` instead in order not to break the payload value conversion specialized for these temporal types. \ No newline at end of file +As before, if you have the need to override `ODataPayloadValueConverter`, please now change to override +`RestierPayloadValueConverter` instead in order not to break the payload value conversion specialized for these +temporal types. \ No newline at end of file diff --git a/docs/server/interceptors.md b/docs/server/interceptors.md index da37213a..938b87e3 100644 --- a/docs/server/interceptors.md +++ b/docs/server/interceptors.md @@ -1,17 +1,305 @@ -# Welcome to MkDocs +# Interceptors -For full documentation visit [mkdocs.org](http://mkdocs.org). +Interceptors allow you to process validation and business logic before *and after* Entities hit the database. For +example, you may need to validate some external business rules before the object is saved, but then after it's saved, +you may need to dump the object to an Azure Storage Queue to get picked up by a WebJob for further processing out-of-band. -## Commands +The way RESTier accomplishes this is virtually identical to the [Method Authorization](/server/method-authorization/) +feature. This means there are once again two different approaches to tackle the task. -* `mkdocs new [dir-name]` - Create a new project. -* `mkdocs serve` - Start the live-reloading docs server. -* `mkdocs build` - Build the documentation site. -* `mkdocs help` - Print this help message. +As before, no matter what approach you chose, the concept is simple. Either technique uses a function that returns boolean. +Return `true`, and processing continues normally. Return `false`, and RESTier returns a 403 Unauthorized to the client. -## Project layout +## Convention-Based Interception +Users can control if one of the four submit operations is allowed on some entity set or action by putting some +`protected internal` methods into the `Api` class. The method name must conform to the convention +`On{{BeforeOperation}/{AfterOperation}}{TargetName}`. - mkdocs.yml # The configuration file. - docs/ - index.md # The documentation homepage. - ... # Other markdown pages, images and other files. + + + + + + + + + + + +
The possible values for {BeforeOperation} are:The possible values for {AfterOperation} are:The possible values for {TargetName} are:
+
    +
  • Inserting
  • +
  • Updating
  • +
  • Deleting
  • +
  • Executing
  • +
+
+
    +
  • Inserted
  • +
  • Updated
  • +
  • Deleted
  • +
  • Executed
  • +
+
+
    +
  • EntitySetName
  • +
  • ActionName
  • +
+
+ +### Example + +The example below demonstrates how both types of `{TargetName}` can be used. + +- The first method shows a simple way to prevent *any* user from deleting a particular EntitySet. +- The second method shows how you can integrate role-based security using multiple techniques. +- The third method shows how to prevent execution a custom Action. + +```cs +using Microsoft.Restier.Providers.EntityFramework; +using System; +using System.Security.Claims; + +namespace Microsoft.OData.Service.Sample.Trippin.Api +{ + + /// + /// Customizations to the EntityFrameworkApi for the TripPin service. + /// + /// + /// Add the following line in WebApiConfig.cs to register this code: + /// await config.MapRestierRoute("Trippin", "api", new RestierBatchHandler(GlobalConfiguration.DefaultServer)); + /// + public class TrippinApi : EntityFrameworkApi + { + + /// + /// Specifies whether or not a Trip can be deleted from an EntitySet. + /// + protected internal bool CanDeleteTrips() + { + return false; + } + + /// + /// User role-based security to specifies whether or not a updated Trip can be sent to an EntitySet. + /// + protected internal bool CanUpdateTrips() + { + // Use claims-based security + return ClaimsPrincipal.Current.IsInRole("admin"); + + // You can also use legacy role-based security, though it's harder to test. + //return HttpContext.Current.User.IsInRole("admin"); + } + + /// + /// Specifies whether or not an Action called ResetDataSource can be executed through the API. + /// + protected internal bool CanExecuteResetDataSource() + { + return false; + } + + } + +} +``` + +## Centralized Interception + +In addition to the more granular convention-based approach, you can also centralize processing into one location. This is +useful if + +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 the centralized authorization logic. + +- Create a class that implements `IChangeSetItemAuthorizer`. +- Register that class with RESTier through Dependency Injection (DI). + +### Example + +```cs +using Microsoft.OData.Core; +using Microsoft.Restier.Providers.EntityFramework; + +namespace Microsoft.OData.Service.Sample.Trippin.Api +{ + + /// + /// + /// + public class CustomAuthorizer : IChangeSetItemAuthorizer + { + + // The inner handler will call CanUpdate/Insert/Delete method + private IChangeSetItemProcessor Inner { get; set; } + + /// + /// + /// + public Task AuthorizeAsync(SubmitContext context, ChangeSetItem item, CancellationToken cancellationToken) + { + // TODO: RWM: Provide legitimate samples here, along with parameter documentation. + } + + } + + /// + /// Customizations to the EntityFrameworkApi for the TripPin service. + /// + /// + /// Add the following line in WebApiConfig.cs to register this code: + /// await config.MapRestierRoute("Trippin", "api", new RestierBatchHandler(GlobalConfiguration.DefaultServer)); + /// + public class TrippinApi : EntityFrameworkApi + { + + /// + /// Allows us to leverage DI to inject additional capabilities into RESTier. + /// + protected override IServiceCollection ConfigureApi(IServiceCollection services) + { + return base.ConfigureApi(services) + .AddService(); + } + + } + +} +``` + +NEEDS CLARIFICATION: +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 Inner {get; set;}" in class CustomizedAuthorizer, +then call Inner.Inspect() to call RESTier logic which call Authorize part logic defined in section 2.3. + +## Unit Testing Considerations + +Because both of these methods are de-coupled from the code that interacts with the database, the Authorization +logic is easily testable, without having to fire up the entire Web API + RESTier pipeline. + +### Setting up your Unit Test + +If you don't have a unit test project for your API project already, start by creating one. Repeat the process +outlined in "Getting Started" to install the RESTier packages into your Unit Test project. The add the FluentAssertions +package. + +Next, go back to your API project. Expand the "Properties" node, double-click AssemblyInfo.cs, and add the following line +to the very end of the file: `[assembly: InternalsVisibleTo("{TestProjectAssembly}")]`, making sure you replace +{TestProjectAssembly} with the actual assembly name. This is important, because otherwise the tests won't be able to see +the `protected internal` methods the authorization conventions use. + +### Example + +Given the [Convention-Based Authorization](#convention-based-authorization) example, the tests below should have 100% code +coverage, and should pass without any required changes. + +```cs +using FluentAssertions; +using Microsoft.OData.Core; +using Microsoft.OData.Service.Sample.Trippin.Api; +using Microsoft.Restier.Providers.EntityFramework; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Security.Claims; + +namespace Trippin.Tests.Api +{ + + /// + /// Test cases for the RESTier Method Authorizers. + /// + [TestClass] + public class TrippinApiTests + { + + #region Trips EntitySet + + /// + /// Tests if the Trips EntitySet is properly configured to reject delete requests. + /// + [TestMethod] + public void TrippinApi_Trips_CanDelete_IsConfigured() + { + var api = new TrippinApi(); + api.CanDeleteTrips.Should().BeFalse(); + } + + /// + /// Tests if the Trips EntitySet is properly configured to accept Admin update requests. + /// + [TestMethod] + public void TrippinApi_Trips_CanUpdate_IsAdmin() + { + var api = new TrippinApi(); + + // We won't be testing HttpContext-related security here, because that requires mocking, + // which is outside the scope of this document. + AuthenticateAsAdmin(); + api.CanUpdateTrips.Should().BeTrue(); + } + + /// + /// Tests if the Trips EntitySet is properly configured to reject non-Admin update requests. + /// + [TestMethod] + public void TrippinApi_Trips_CanUpdate_IsNotAdmin() + { + var api = new TrippinApi(); + // We won't be testing HttpContext-related security here, because that requires mocking, + // which is outside the scope of this document. + AuthenticateAsNonAdmin(); + api.CanUpdateTrips.Should().BeFalse(); + } + + #endregion + + #region Actions + + /// + /// Tests if the Trips EntitySet is properly configured to reject delete requests. + /// + [TestMethod] + public void TrippinApi_CanExecuteResetDataSource_IsConfigured() + { + var api = new TrippinApi(); + api.CanExecuteResetDataSource.Should().BeFalse(); + } + + #endregion + + #region Test Helpers + + /// + /// Sets the Thread.CurrentPrincipal to a test user with an "admin" Role Claim. + /// + internal static void AuthenticateAsAdmin() + { + var claimsCollection = new List + { + new Claim(ClaimTypes.Role, "admin") + }; + var claimsIdentity = new ClaimsIdentity(claimsCollection, "Test User"); + Thread.CurrentPrincipal = new ClaimsPrincipal(claimsIdentity); + } + + /// + /// Sets the Thread.CurrentPrincipal to a test user without an "admin" Role Claim. + /// + internal static void AuthenticateAsNonAdmin() + { + var claimsCollection = new List(); + var claimsIdentity = new ClaimsIdentity(claimsCollection, "Test User"); + Thread.CurrentPrincipal = new ClaimsPrincipal(claimsIdentity); + } + + #endregion + + } + +} + +``` \ No newline at end of file diff --git a/docs/server/method-authorization.md b/docs/server/method-authorization.md index f902c681..d2e8b0ab 100644 --- a/docs/server/method-authorization.md +++ b/docs/server/method-authorization.md @@ -12,9 +12,9 @@ No matter what approach you chose, the concept is simple. Either technique uses Return `true`, and processing continues normally. Return `false`, and RESTier returns a 403 Unauthorized to the client. ## Convention-Based Authorization -Users can control if one of the four submit operations is allowed on some entity set or action by putting some -`protected internal` methods into the `Api` class. The method signatures must exactly match the following examples. The -method name must conform to the convention `Can{Operation}{TargetName}`. +Users can control if one of the four submit operations is allowed on some EntitySet or Action by putting some +`protected internal` methods into the `Api` class. The method name must conform to the convention +`Can{Operation}{TargetName}`. From 6550ea10cdfcaae15a47396bd014b4f38573dc80 Mon Sep 17 00:00:00 2001 From: Robert McLaws Date: Sun, 5 Jun 2016 23:13:04 -0400 Subject: [PATCH 14/18] More server docs updates - Interceptors example - New Method Authorization section on using both techniques at once. --- docs/server/interceptors.md | 28 +++++------- docs/server/method-authorization.md | 71 +++++++++++++++++++++++++---- 2 files changed, 75 insertions(+), 24 deletions(-) diff --git a/docs/server/interceptors.md b/docs/server/interceptors.md index 938b87e3..b738ba9d 100644 --- a/docs/server/interceptors.md +++ b/docs/server/interceptors.md @@ -76,29 +76,25 @@ namespace Microsoft.OData.Service.Sample.Trippin.Api /// /// Specifies whether or not a Trip can be deleted from an EntitySet. /// - protected internal bool CanDeleteTrips() + protected void OnInsertingTrip(Trip trip) { - return false; + Trace.WriteLine($"{DateTime.Now.ToString()}: {trip.TripId} is being Inserted."); + + if (string.IsNullOrWhiteSpace(trip.Description)) + { + throw new ODataException("The Trip Description cannot be blank."); + } } /// - /// User role-based security to specifies whether or not a updated Trip can be sent to an EntitySet. + /// Specifies whether or not a Trip can be deleted from an EntitySet. /// - protected internal bool CanUpdateTrips() + protected void OnInsertedTrip(Trip trip) { - // Use claims-based security - return ClaimsPrincipal.Current.IsInRole("admin"); + Trace.WriteLine($"{DateTime.Now.ToString()}: {trip.tripId} has been Inserted."); - // You can also use legacy role-based security, though it's harder to test. - //return HttpContext.Current.User.IsInRole("admin"); - } - - /// - /// Specifies whether or not an Action called ResetDataSource can be executed through the API. - /// - protected internal bool CanExecuteResetDataSource() - { - return false; + // Pseudocode that represents a real business process. + // EmailManager.SendTripWelcome(trip); } } diff --git a/docs/server/method-authorization.md b/docs/server/method-authorization.md index d2e8b0ab..7013fc55 100644 --- a/docs/server/method-authorization.md +++ b/docs/server/method-authorization.md @@ -121,14 +121,11 @@ namespace Microsoft.OData.Service.Sample.Trippin.Api { /// - /// + /// Provides global ChangeSet Authorization for a RESTier API. /// public class CustomAuthorizer : IChangeSetItemAuthorizer { - // The inner handler will call CanUpdate/Insert/Delete method - private IChangeSetItemProcessor Inner { get; set; } - /// /// /// @@ -163,10 +160,68 @@ namespace Microsoft.OData.Service.Sample.Trippin.Api } ``` -NEEDS CLARIFICATION: -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 Inner {get; set;}" in class CustomizedAuthorizer, -then call Inner.Inspect() to call RESTier logic which call Authorize part logic defined in section 2.3. +## Leveraging Both Techniques + +There may be certain situations where you want to have a global interceptor, and then pass requests off to the individual +convention-based interceptors. For example, if you need to authenticate a Bearer token. The example below shows you +exactly how this type of scenario would work. + +### Example + +```cs +using Microsoft.OData.Core; +using Microsoft.Restier.Providers.EntityFramework; + +namespace Microsoft.OData.Service.Sample.Trippin.Api +{ + + /// + /// Provides global ChangeSet Authorization for a RESTier API. + /// + public class CustomAuthorizer : IChangeSetItemAuthorizer + { + + /// + /// The built-in ChangeSetItemAuthorizer instance that will be set by RESTier. + /// + private IChangeSetItemAuthorizer InnerAuthorizer {get; set;} + + /// + /// + /// + public Task AuthorizeAsync(SubmitContext context, ChangeSetItem item, CancellationToken cancellationToken) + { + // TODO: RWM: Provide legitimate samples here, along with parameter documentation. + + // Hand off processing to the appropriate convention-based function. + await InnerAuthorizer.AuthorizeAsync(context, item, cancellationToken); + } + + } + + /// + /// Customizations to the EntityFrameworkApi for the TripPin service. + /// + /// + /// Add the following line in WebApiConfig.cs to register this code: + /// await config.MapRestierRoute("Trippin", "api", new RestierBatchHandler(GlobalConfiguration.DefaultServer)); + /// + public class TrippinApi : EntityFrameworkApi + { + + /// + /// Allows us to leverage DI to inject additional capabilities into RESTier. + /// + protected override IServiceCollection ConfigureApi(IServiceCollection services) + { + return base.ConfigureApi(services) + .AddService(); + } + + } + +} +``` ## Unit Testing Considerations From 64fabd02207559dc8e62e0a6178dc3c57ee2bf23 Mon Sep 17 00:00:00 2001 From: Robert McLaws Date: Mon, 6 Jun 2016 00:07:26 -0400 Subject: [PATCH 15/18] Talking with Mark --- docs/server/filters.md | 4 +- docs/server/model-building.md | 279 ++++++++++++++++++++++++++++++++-- 2 files changed, 270 insertions(+), 13 deletions(-) diff --git a/docs/server/filters.md b/docs/server/filters.md index aee5f327..3e2a287c 100644 --- a/docs/server/filters.md +++ b/docs/server/filters.md @@ -59,4 +59,6 @@ namespace Microsoft.OData.Service.Sample.Trippin.Api } ``` - ## Centralized Filtering +## Centralized Filtering + +TODO: Pull content from Section 2.8. \ No newline at end of file diff --git a/docs/server/model-building.md b/docs/server/model-building.md index da37213a..d2a8f4e8 100644 --- a/docs/server/model-building.md +++ b/docs/server/model-building.md @@ -1,17 +1,272 @@ -# Welcome to MkDocs +# Building the EDM Model -For full documentation visit [mkdocs.org](http://mkdocs.org). +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 the `Api` class. -## Commands +This subsection mainly talks about how to build an initial EDM model and then the convention RESTier adopts to +extend an EDM model from an `Api` class. -* `mkdocs new [dir-name]` - Create a new project. -* `mkdocs serve` - Start the live-reloading docs server. -* `mkdocs build` - Build the documentation site. -* `mkdocs help` - Print this help message. +## 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 implementing +`IModelBuilder` and register it to the `Api` class. Here is an example using the +[`**ODataConventionModelBuilder**`](http://odata.github.io/WebApi/#02-04-convention-model-builder) in OData Web API +to build an initial model only containing the `Person` type. -## Project layout +Any model building methods supported by Web API OData can be used here, refer to +**[Web API OData Model builder ](http://odata.github.io/WebApi/#02-01-model-builder-abstract)**document for more information. - mkdocs.yml # The configuration file. - docs/ - index.md # The documentation homepage. - ... # Other markdown pages, images and other files. +```cs +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData.Edm; +using Microsoft.Restier.Core; +using Microsoft.Restier.Core.Model; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using System.Web.OData.Builder; + +namespace Microsoft.OData.Service.Sample.TrippinInMemory +{ + + internal class CustomizedModelBuilder : IModelBuilder + { + public Task GetModelAsync(InvocationContext context, CancellationToken cancellationToken) + { + var builder = new ODataConventionModelBuilder(); + builder.EntityType(); + return Task.FromResult(builder.GetEdmModel()); + } + } + + /// + /// + /// + public class TrippinApi : ApiBase + { + + /// + /// + /// + protected override IServiceCollection ConfigureApi(IServiceCollection services) + { + services.AddService(); + return base.ConfigureApi(services); + } + + } + +} +``` + +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**](http://odata.github.io/WebApi/#02-04-convention-model-builder), refer to +[document](http://odata.github.io/WebApi/#02-04-convention-model-builder) 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 the +`Api` 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` where `T` is class type + +Example: + +```cs +using System.Collections.Generic; +using System.Linq; +using Microsoft.Restier.Core.Model; +using Microsoft.Restier.Provider.EntityFramework; +using Microsoft.OData.Service.Sample.Trippin.Models; + +namespace Microsoft.OData.Service.Sample.Trippin.Api +{ + public class TrippinApi : EntityFrameworkApi + { + public IQueryable PeopleWithFriends + { + get { return Context.People.Include("Friends"); } + } + ... + } +} +``` + +**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: + +```cs +using System.Collections.Generic; +using System.Linq; +using Microsoft.Restier.Core.Model; +using Microsoft.Restier.Provider.EntityFramework; +using Microsoft.OData.Service.Sample.Trippin.Models; + +namespace Microsoft.OData.Service.Sample.Trippin.Api +{ + public class TrippinApi : EntityFrameworkApi + { + ... + public Person Me { get { return DbContext.People.Find(1); } } + ... + } +} +``` + +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 is `Person` or `Collection(Person)`, only those entity sets and singletons of type `Person` are searched. + - Singleton navigation properties can be bound to either entity sets or singletons. + **Example:** If `Person.BestFriend` is a singleton navigation property, bindings from `BestFriend` to an entity set `People` or to a singleton `Boss` are all allowed. + - Collection navigation properties can **ONLY** be bound to entity sets. + **Example:** If `Person.Friends` is a collection navigation property. **ONLY** binding from `Friends` to an entity set `People` is allowed. Binding from `Friends` to a singleton `Boss` 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 type `Person`; 2) there is at least one entity set and one singleton both of type `Person`. However for the collection navigation property `Person.Friends`, no binding will be added only if there are at least two entity sets both of type `Person`. One entity set and one singleton both of type `Person` 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): + +```cs +using System.Collections.Generic; +using System.Linq; +using Microsoft.Restier.Core.Model; +using Microsoft.Restier.Provider.EntityFramework; +using Microsoft.OData.Service.Sample.Trippin.Models; + +namespace Microsoft.OData.Service.Sample.Trippin.Api +{ + public class TrippinApi : EntityFrameworkApi + { + ... + // Action import + [Operation(Namespace = "Microsoft.OData.Service.Sample.Trippin.Models", HasSideEffects = true)] + public void CleanUpExpiredTrips() {} + + // Bound action + [Operation(Namespace = "Microsoft.OData.Service.Sample.Trippin.Models", HasSideEffects = true)] + public Trip EndTrip(Trip bindingParameter) { ... } + + // Function import + [Operation(Namespace = "Microsoft.OData.Service.Sample.Trippin.Models", EntitySet = "People")] + public IEnumerable GetPeopleWithFriendsAtLeast(int n) { ... } + + // Bound function + [Operation(Namespace = "Microsoft.OData.Service.Sample.Trippin.Models", EntitySet = "People")] + public Person GetPersonWithMostFriends(IEnumerable bindingParameter) { ... } + ... + } +} +``` + +Note: + +1. 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. + +2. 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. + +3. In order to access an operation user must define an action with `ODataRouteAttribute` in his custom controller. +Refer to [section 3.3](http://odata.github.io/RESTier/#03-03-Operation) 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). + +```cs +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.OData.Edm; +using Microsoft.Restier.Core; +using Microsoft.Restier.Core.Model; +using Microsoft.Restier.Provider.EntityFramework; +using Microsoft.OData.Service.Sample.Trippin.Models; + +namespace Microsoft.OData.Service.Sample.Trippin.Api +{ + public class TrippinAttribute : ApiConfiguratorAttribute + { + protected override IServiceCollection ConfigureApi(IServiceCollection services) + { + services = base.ConfigureApi(services); + // Add your custom model extender here. + services.AddService(); + return services; + } + + private class CustomizedModelBuilder : IModelBuilder + { + public IModelBuilder InnerModelBuilder { get; set; } + + public async Task GetModelAsync(InvocationContext context, CancellationToken cancellationToken) + { + IEdmModel model = null; + + // Call inner model builder to get a model to extend. + if (this.InnerModelBuilder != null) + { + model = await this.InnerModelBuilder.GetModelAsync(context, cancellationToken); + } + + // Do sth to extend the model such as add custom navigation property binding. + + return model; + } + } + } +} +``` + +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](http://odata.github.io/RESTier/#04-03-Api-Service) for more details of RESTier API Service. From 9ac17c8d9d304e365b684e02e0c070129b5f54ac Mon Sep 17 00:00:00 2001 From: Robert McLaws Date: Tue, 7 Jun 2016 22:16:12 -0400 Subject: [PATCH 16/18] Team demo --- docs/index.md | 2 ++ docs/vs-highlight.css | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/index.md b/docs/index.md index 8b7614c6..feb121ea 100644 --- a/docs/index.md +++ b/docs/index.md @@ -14,6 +14,8 @@ custom queries and actions with techniques you're already familiar with. But RESTier isn't just for OData and the Entity Framework. It also supports adding additional publishers to support other protocols and additional providers to support other data sources. +Hi quick change! + ## What is OData? OData stands for the Open Data Protocol. OData enables the creation and consumption of RESTful APIs, which allow diff --git a/docs/vs-highlight.css b/docs/vs-highlight.css index 5d765e49..e94200f2 100644 --- a/docs/vs-highlight.css +++ b/docs/vs-highlight.css @@ -73,9 +73,9 @@ td code { font-size: 100%; } -.wy-menu-vertical li.current > a { +.wy-menu-vertical .subnav li.current > a { padding-left: 2.42em; } -.wy-menu-vertical li.current > ul li a { +.wy-menu-vertical .subnav li.current > ul li a { padding-left: 3.23em; } \ No newline at end of file From e3b27fe5e88460e0d89c72fe13982e1a4f3c638e Mon Sep 17 00:00:00 2001 From: Robert McLaws Date: Tue, 7 Jun 2016 23:13:20 -0400 Subject: [PATCH 17/18] Removing text added during the team demo --- docs/index.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/index.md b/docs/index.md index feb121ea..8b7614c6 100644 --- a/docs/index.md +++ b/docs/index.md @@ -14,8 +14,6 @@ custom queries and actions with techniques you're already familiar with. But RESTier isn't just for OData and the Entity Framework. It also supports adding additional publishers to support other protocols and additional providers to support other data sources. -Hi quick change! - ## What is OData? OData stands for the Open Data Protocol. OData enables the creation and consumption of RESTful APIs, which allow From d41d93acb2ad39659c85646faa766e52b1fbfe33 Mon Sep 17 00:00:00 2001 From: Robert McLaws Date: Mon, 13 Jun 2016 02:17:38 -0400 Subject: [PATCH 18/18] ModelBinding updates - Updates still in progress. - Also removing placeholder content on other pages. --- docs/clients/dot-net.md | 18 +----------- docs/license.md | 18 +----------- docs/server/model-building.md | 53 +++++++++++++++++++---------------- 3 files changed, 31 insertions(+), 58 deletions(-) diff --git a/docs/clients/dot-net.md b/docs/clients/dot-net.md index da37213a..84c3a7de 100644 --- a/docs/clients/dot-net.md +++ b/docs/clients/dot-net.md @@ -1,17 +1 @@ -# Welcome to MkDocs - -For full documentation visit [mkdocs.org](http://mkdocs.org). - -## Commands - -* `mkdocs new [dir-name]` - Create a new project. -* `mkdocs serve` - Start the live-reloading docs server. -* `mkdocs build` - Build the documentation site. -* `mkdocs help` - Print this help message. - -## Project layout - - mkdocs.yml # The configuration file. - docs/ - index.md # The documentation homepage. - ... # Other markdown pages, images and other files. + [THIS IS A PLACEHOLDER FOR FUTURE CONTENT] \ No newline at end of file diff --git a/docs/license.md b/docs/license.md index da37213a..c629fb2b 100644 --- a/docs/license.md +++ b/docs/license.md @@ -1,17 +1 @@ -# Welcome to MkDocs - -For full documentation visit [mkdocs.org](http://mkdocs.org). - -## Commands - -* `mkdocs new [dir-name]` - Create a new project. -* `mkdocs serve` - Start the live-reloading docs server. -* `mkdocs build` - Build the documentation site. -* `mkdocs help` - Print this help message. - -## Project layout - - mkdocs.yml # The configuration file. - docs/ - index.md # The documentation homepage. - ... # Other markdown pages, images and other files. +[THIS IS A PLACEHOLDER FOR FUTURE CONTENT] \ No newline at end of file diff --git a/docs/server/model-building.md b/docs/server/model-building.md index d2a8f4e8..cf92abcb 100644 --- a/docs/server/model-building.md +++ b/docs/server/model-building.md @@ -1,30 +1,38 @@ -# Building the EDM Model +# Customizing the Entity Model -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 the `Api` class. +OData and the Entity Framework are based on the same underlying concept for mapping the idea of an Entity with +its representation in the database. That "mapping" layer is called the Entity Data Model, or EDM for short. -This subsection mainly talks about how to build an initial EDM model and then the convention RESTier adopts to -extend an EDM model from an `Api` class. +Part of the beautiy of RESTier is that, for the majority of API builders, it can construct your EDM for you +*automagically*. But there are times where you have to take charge of the process. And as with many things in RESTier, +the intrepid developers at Microsoft provide you with two ways to do so. -## 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 implementing -`IModelBuilder` and register it to the `Api` class. Here is an example using the -[`**ODataConventionModelBuilder**`](http://odata.github.io/WebApi/#02-04-convention-model-builder) in OData Web API -to build an initial model only containing the `Person` type. +The first method allows you to completely relpace the automagic model construction with your own, in a manner +very similar to Web API OData. -Any model building methods supported by Web API OData can be used here, refer to -**[Web API OData Model builder ](http://odata.github.io/WebApi/#02-01-model-builder-abstract)**document for more information. +The second method lets RESTier do the initial work for you, and then you manipulate the resulting EDM metadata. + +Let's take a look at how each of these methods work. + +## ModelBuilder Takeover + +There are several situations where you are likely going to want to use this approach to create your Model. +For example, if you're migrating from an existing Web API OData v3 or v4 implementation, and needed to +customize that model, you will be able to copy/paste your existing code over, with just a few small changes. +If you're building a new model, but you're using Entity Framework Model First + SQL Views, then you'll +likely need to define a primary key, or omit the View from your service. + +With the Entity Framework provider, the model is built with the +[**ODataConventionModelBuilder**](http://odata.github.io/WebApi/#02-04-convention-model-builder). To +understand how this ModelBuilder works, please take a few minutes and review that documentation. + +# Example ```cs using Microsoft.Extensions.DependencyInjection; using Microsoft.OData.Edm; using Microsoft.Restier.Core; using Microsoft.Restier.Core.Model; -using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -35,7 +43,7 @@ namespace Microsoft.OData.Service.Sample.TrippinInMemory internal class CustomizedModelBuilder : IModelBuilder { - public Task GetModelAsync(InvocationContext context, CancellationToken cancellationToken) + public Task GetModelAsync(ModelContext context, CancellationToken cancellationToken) { var builder = new ODataConventionModelBuilder(); builder.EntityType(); @@ -54,8 +62,8 @@ namespace Microsoft.OData.Service.Sample.TrippinInMemory ///
protected override IServiceCollection ConfigureApi(IServiceCollection services) { - services.AddService(); - return base.ConfigureApi(services); + return base.ConfigureApi(services) + .AddService(); } } @@ -67,10 +75,7 @@ If RESTier entity framework provider is used and user has no additional types ot 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**](http://odata.github.io/WebApi/#02-04-convention-model-builder), refer to -[document](http://odata.github.io/WebApi/#02-04-convention-model-builder) 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 the