【翻译】 For OData For C# play on RESTier

要获得统一的体验,请转到GitHub Issues询问问题,报告错误并要求功能。本文档适用于当前版本 1.0(第一个 GA)。0.6.0版本文档参考0.6.0版本文档

入门

1.1引言

OData 代表开放数据协议。 它由微软发起,现在是 ISO 和 OASIS 标准。 OData 支持创建和使用 RESTful API,这些 API 允许 Web 客户端使用简单的 HTTP 请求发布和编辑在数据模型中定义并使用 URL 标识的资源。

  RESTier 是一个 RESTful API 开发框架,用于在 .NET 平台上构建标准化的、基于 OData V4 的 RESTful 服务。 可以将其视为Web API OData之上的中间件。 RESTier提供了像WCF Data Services那样引导OData服务的功能,此外,它还支持通过几个简单的步骤添加业务逻辑,具有灵活性和易于自定义的功能,如Web API OData一样。 它还支持添加额外的发布者以支持其他协议和额外的提供者以支持其他数据源。

有关 OData 的更多信息,请参阅以下资源:

OData.org

OASIS 开放数据协议 (OData) 技术委员会

有关OData .Net库的更多信息,请参考OData .Net库文档

有关Web API OData库的更多信息,请参阅Web API OData库文档

1.2引导OData服务

  在 RESTier 0.4.0 之后,创建 OData 服务从未如此简单! 本小节显示了如何在几分钟内使用RESTier创建OData V4端点。 AdventureWorksLT将用作示例数据库,而Entity Framework将用作数据代理。

创建项目和 Web 应用程序

1.打开Visual Studio 2015或Visual Studio2013。如果使用Visual Studio 2013,则屏幕与屏幕截图会略有不同,但是步骤基本相同。

2. 从文件菜单中,单击新建 > 项目。

3. 在新建项目对话框中,单击 C# > Web > ASP.NET Web 应用程序。

4.清除“将Application Insights添加到项目”复选框。

5. 将应用程序命名为 HelloWorld。

6.单击确定。

7.在新建 ASP.NET 项目对话框中,选择空模板。

8. 选中 Web API 复选框。

9.清除云中的主机复选框。

 安装 RESTier 包

1.在“解决方案资源管理器”窗口中,右键单击项目HelloWorld,然后选择“管理NuGet程序包…”。

2.在“ NuGet软件包管理器”窗口中,选中“包括预发行版”复选框。

3. 在旁边的搜索框中键入 Restier,然后按 Enter。

4. 选择 Microsoft.Restier 并单击安装按钮。

 5.在预览对话框中,单击确定按钮。

 6.在许可接受对话框中,单击我接受按钮。

 生成模型类

1.下载AdventureWorksLT2012_Data.mdf导入(localdb)MSSQLLocalDB数据库。

2.在解决方案资源管理器窗口中,右键单击项目HelloWorld下的模型文件夹,然后选择添加>新项目。

3.在“添加新项-HelloWorld”对话框中,单击“ C#”>“数据”>“ ADO.NET实体数据模型”。

4. 命名模型 AdventureWorksLT。

5. 单击添加按钮。

6.在“实体数据模型向导”窗口中,从数据库中选择“代码优先”项。

7.单击下一步按钮。

 8.单击“新建连接”按钮。

9.在“连接属性”对话框中,键入(localdb) MSSQLLocalDB作为“服务器名称”。

10.选择 AdventureWorksLT2012 作为数据库名称。

 11.返回到“实体数据模型向导”窗口后,单击“下一步”按钮。

 12.选中“表”复选框,然后单击“完成”按钮。

 配置 OData 端点

在解决方案资源管理器窗口中,单击 HelloWorld > App_Start > WebApiConfig.cs。 将以下代码替换为WebApiConfig类。

namespace HelloWorld
{
    public static class WebApiConfig
    {
        public async static void Register(HttpConfiguration config)
        {
            // enable query options for all properties
            config.Filter().Expand().Select().OrderBy().MaxTop(null).Count();
            await config.MapRestierRoute<EntityFrameworkApi<AdventureWorksLT>>(
                "AdventureWorksLT",
                "api/AdventureWorksLT",
                new RestierBatchHandler(GlobalConfiguration.DefaultServer));
        }
    }
}

注意:DbApi从版本0.5重命名为EntityFrameworkApi。

  配置“config.Filter().Expand().Select().OrderBy().MaxTop(null).Count();” 在所有属性上启用 filter/expand/select/orderby/count,从 1.0 版本开始,对可以在查询选项中使用的属性有更多更小的粒度控制,并且所有属性默认禁用。 用户可以在 CLR 类中或在模型构建期间添加配置,以配置允许在 filter/expand/select/orderby/count 中使用哪些属性。 有关更多详细信息,请参阅模型绑定文档。

  完成这些步骤之后,您将完成对OData服务端点的引导。 然后,您可以运行项目并启动OData服务。 然后,您可以首先访问 URL http://localhost:<ISS Express port>/api/AdventureWorksLT 以查看所有可用的实体集,并尝试其他基本的 OData CRUD 操作。 例如,您可以尝试使用 $select、$filter、$orderby、$top、$skip 或 $apply 查询字符串参数查询任何实体集。

特征

2.1安全性

认证和授权

REStier现在对安全透明,任何适用于Web APi的安全配置/方法都适用于RESTier。RESTier 中的一项是特殊的,只有一个控制器名为 RESTier 控制器,用户可以实现其他扩展 ODataController 类的附加控制器,为了对所有这些控制器进行一致的身份验证,用户需要在全局级别配置 Authentication 和 Authorization 过滤器 .

Restier 还提供检查每个请求的功能,请参阅第 2.8 和 2.9 节了解更多详细信息。

有关 Web Api 安全性,请参阅 Web Api 安全性文档

注意:Restier 使用异步调用提供者层(实体框架),这意味着默认情况下用于逻辑应用程序的主体不会传递到调用提供者,如果应用程序需要将主体从应用程序传递到提供者层,请参考这个 详细配置的链接

2.2 实体集过滤器

实体集过滤器约定有助于为实体集插入一段过滤逻辑。 这是通过向 Api 类添加 OnFilter[entity type name](IQueryable<T> entityset) 方法来完成的。

1.过滤方法名必须是OnFilter[entity type name],以目标实体类型名结尾。
2.它必须是“ Api”类上的“受保护”方法。
3. 它应该接受一个 IQueryable<T> 参数并返回一个 IQueryable<T> 结果,其中 T 是实体类型。

假设〜/ AdventureWorksLT / Products可以获取所有Product实体,则下面的OnFilterProduct方法将通过检查ProductID来过滤某些Product实体。

namespace AdventureWorksLTSample.Models
{
    public class AdventureWorksApi : EntityFrameworkApi<AdventureWorksContext>
    {
        protected IQueryable<Product> OnFilterProduct(IQueryable<Product> entitySet)
        {
            return entitySet.Where(s => s.ProductID % 3 == 0).AsQueryable();
        }
    }
}

然后将WebApiConfig中的EntityFrameworkApi <AdventureWorksLT>替换为AdventureWorksApi,现在一些测试将显示:

1.〜/ AdventureWorksLT / Products将仅获得其ProductID为3、6、9、12、15,...的Product实体。
2.〜/ AdventureWorksLT / Products([product id])将只能获得其ProductID mod 3结果为零的Product实体。

注意:1.从0.6版本开始,转换名称改为OnFilter[实体类型名称],0.6版本之前名称为OnFilter[实体集名称]

从 0.6 版本开始,过滤器应用于除顶级实体集之外的所有地方,包括导航属性、$expand 中的实体集合、过滤器中的集合等。 有关所有受支持的方案,请参考端到端测试用例TrippinE2EOnFilterTestCases。

所有者可以使用更有意义的过滤器,例如过滤器实体,并且实体所有者是当前请求用户。

2.3提交逻辑

提交逻辑约定允许用户在提交操作之前和之后授权提交操作或插入用户逻辑(例如日志记录)。 通常,提交操作可以是插入实体、删除实体、更新实体或执行 OData 操作。

还支持针对所有实体集使用单个类自定义提交逻辑,有关更多详细信息,请参见2.9节。

授权

用户可以通过将一些受保护的方法放入Api类来控制是否允许对某些实体集或操作执行四个提交操作之一。 方法签名必须与以下示例完全匹配。 方法名称必须符合Can <Insert | Update | Delete | Execute> <EntitySetName | ActionName>。

namespace Microsoft.OData.Service.Sample.Trippin.Api
{
    public class TrippinApi : EntityFrameworkApi<TrippinModel>
    {
        ...
        // 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;
        }
    }
}

插入用户逻辑

 通过将类似的受保护方法放入Api类,用户可以在执行四个提交操作之一之前和之后插入用户逻辑。 方法签名还必须与以下示例完全匹配。 方法名称必须符合 On<Insert|Updat|Delet|Execut><ed|ing><EntitySetName|ActionName> 其中 ing 为提交前,ed 为提交后。

namespace Microsoft.Restier.Samples.Northwind.Models
{
    public class NorthwindApi : EntityFrameworkApi<NorthwindContext>
    {
        ...
        // Gets called before updating an entity from the entity set Products.
        protected void OnUpdatingProducts(Product product)
        {
            WriteLog(DateTime.Now.ToString() + product.ProductID + " is being updated");
        }

        // Gets called after inserting an entity to the entity set Products.
        protected void OnInsertedProducts(Product product)
        {
            WriteLog(DateTime.Now.ToString() + product.ProductID + " has been inserted");
        }
    }
}

2.4 ETag 支持

RESTier从0.6版本开始支持ETag。

为了支持etag,实体clr类必须具有一些标有[ConcurrencyCheck]属性的属性,然后,具有此属性的属性将用于计算etag以支持并发。

etag支持分为两部分,

第一部分是“ @ odata.etag”注释支持,它是响应主体的一部分,当请求是单个实体或实体集合(在集合中)时,将自动为具有ConcurrencyCheck属性的任何实体类型自动添加 在这种情况下,每个实体实例都将带有“ @ odata.etag”注释)。

第二部分是 Etag 头支持,这仅在针对单个实体的操作时支持。 这是行为的摘要。

 为了支持get方法返回etag头,这个配置是必须的,

config.MessageHandlers.Add(new ETagMessageHandler());

用户可以定义自己的消息处理程序来设置响应中的 etag 标头。

对于etag注解和etag header,生成etag的算法可以替换,用户可以自己创建etag handler,然后在config中设置,默认为DefaultODataETagHandler。 设置自定义 etag 处理程序的代码如下,

IETagHandler eTagHandler = new CustomizedETagHandler();
config.SetETagHandler(eTagHandler);

有关端到端示例的详细信息,请参阅端到端测试用例 TrippinE2EEtagTestCases

有了etag支持,可以在并发模式下操作实体。

2.5模型制作

RESTier 支持多种方式来构建 EDM 模型。 用户可以首先从EF提供程序获取初始模型。 然后 RESTier 的 RestierModelExtender 可以使用来自 Api 类中定义的公共属性和方法的附加实体集、单例和操作进一步扩展模型。 本小节主要讨论如何构建初始EDM模型,然后RESTier采纳约定从Api类扩展EDM模型。

建立初始的EDM模型

RestierModelExtender 要求 EDM 类型存在于初始模型中,因为它只负责构建实体集、单例和操作 NOT 类型。 因此,无论如何,用户都需要构建一个预先添加了足够类型的初始EDM模型。 这样做的典型方法是编写一个实现IModelBuilder的自定义模型构建器,并将其注册到Api类。 以下是使用 OData Web API 中的 **ODataConventionModelBuilder** 构建仅包含 Person 类型的初始模型的示例。 Web API OData支持的任何模型构建方法都可以在此处使用,有关更多信息,请参考Web API OData模型构建器文档。

namespace Microsoft.OData.Service.Sample.TrippinInMemory
{
    public class TrippinApi : ApiBase
    {
        protected static new IServiceCollection ConfigureApi(Type apiType, IServiceCollection services)
        {
            services.AddService<IModelBuilder, CustomizedModelBuilder>();
            return ApiBase.ConfigureApi(apiType, services);
        }

        private class CustomizedModelBuilder : IModelBuilder
        {
            public Task<IEdmModel> GetModelAsync(InvocationContext context, CancellationToken cancellationToken)
            {
                var builder = new ODataConventionModelBuilder();
                builder.EntityType<Person>();
                return Task.FromResult(builder.GetEdmModel());
            }
        }
    }
}

如果使用RESTier实体框架提供程序,并且用户没有数据库模式中的其他类型,则不需要自定义模型构建器,甚至不需要Api类,因为提供程序将代替构建模型。 但是提供者在幕后所做的事情是相似的。 对于实体框架提供程序,默认情况下该模型是使用ODataConventionModelBuilder构建的,请参考已使用的转换文档,例如构建器如何识别实体类型的键等等。

从Api类扩展模型

RestierModelExtender将进一步扩展使用Api类中定义的公共属性和方法传入的EDM模型。 请注意,不会考虑在父类中声明的所有属性和方法。

实体集 如果在 Api 类中声明的属性满足以下条件,则会在模型中添加名称为该属性名称的实体集。

  • Has Resource attribute
  • Public
  • Has getter
  • Either static or instance
  • There is no existing entity set with the same name
  • Return type must be IQueryable<T> where T is class type
namespace Microsoft.OData.Service.Sample.Trippin.Api
{
    public class TrippinApi : EntityFrameworkApi<TrippinModel>
    {
        [Resource]
        public IQueryable<Person> PeopleWithFriends
        {
            get { return Context.People.Include("Friends"); }
        }
        ...
    }
}

单例 如果在 Api 类中声明的属性满足以下条件,则会将名称为属性名称的单例添加到模型中。

  • Has Resource attribute
  • Public
  • Has getter
  • Either static or instance
  • There is no existing singleton with the same name
  • Return type must be non-generic class type
namespace Microsoft.OData.Service.Sample.Trippin.Api
{
    public class TrippinApi : EntityFrameworkApi<TrippinModel>
    {
        ...
        [Resource]
        public Person Me { get { return DbContext.People.Find(1); } }
        ...
    }
}

由于实体框架和 OData 规范的一些限制,RESTier 不直接支持单例实体上的 CUD(插入、更新和删除)。 用户需要定义自己的路由来实现这些操作。

导航属性绑定从版本0.5.0开始,RestierModelExtender遵循以下规则在构建实体集和单例之后添加导航属性绑定。

只会为在 RestierModelExtender 中构建的实体集和单例添加绑定。示例:假定由 RESTier 的 EF 提供者构建的实体集已经添加了它们的导航属性绑定。
RestierModelExtender 仅搜索与源导航属性具有相同实体类型的导航源。示例:如果导航属性的类型为 Person 或 Collection(Person),则仅搜索 Person 类型的实体集和单例。
单例导航属性可以绑定到实体集或单例。示例:如果 Person.BestFriend 是单例导航属性,则从 BestFriend 到实体集 People 或单例 Boss 的绑定都是允许的。
集合导航属性只能绑定到实体集。示例:如果Person.Friends是集合导航属性。仅允许从 Friends 绑定到实体集 People。不允许从Friends绑定到单例Boss。
如果实体集或单例之间存在歧义,则不会添加任何绑定。示例:对于单例导航属性Person.BestFriend,如果1)至少有两个实体类型均为Person的实体集(或单例),则不添加绑定。 2)至少存在一个类型为Person的实体集和一个单例。然而,对于集合导航属性 Person.Friends,仅当至少有两个实体集都是 Person 类型时,才会添加绑定。一个类型为Person的实体集和一个单例都不会导致任何歧义,并且将添加一个对实体集的绑定。


如果 RESTier 未添加任何预期的导航属性绑定,用户始终可以通过自定义模型扩展(如下所述)手动添加它。

  • Public
  • Either static or instance
  • There is no existing operation with the same name
namespace Microsoft.OData.Service.Sample.Trippin.Api
{
    public class TrippinApi : EntityFrameworkApi<TrippinModel>
    {
        ...
        // 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", IsBound = true, HasSideEffects = true)]
        public Trip EndTrip(Trip bindingParameter) { ... }
        
        // Function import
        [Operation(Namespace = "Microsoft.OData.Service.Sample.Trippin.Models", EntitySet = "People")]
        public IEnumerable<Person> GetPeopleWithFriendsAtLeast(int n) { ... }
        
        // Bound function
        [Operation(Namespace = "Microsoft.OData.Service.Sample.Trippin.Models", IsBound = true, EntitySet = "People")]
        public Person GetPersonWithMostFriends(IEnumerable<Person> bindingParameter) { ... }
        ...
    }
}

如果有多个实体类型的实体集是定义的结果类型,则需要操作属性的 EntitySet 属性。例如,如果定义了两个实体类型为Person的EntitySet人员和AllPerson,并且该函数返回Person或Person列表,则该函数的Operation属性必须定义EntitySet,或者EntitySet属性是可选的。

函数和动作使用相同的属性,并且如果该方法是动作,则必须指定属性HasSideEffects的值为true,其默认值为false。

从 0.6 版本开始,如果未指定,则操作命名空间将默认与实体类型相同,如果在操作属性中指定了命名空间,则将使用属性中的命名空间。此外,为了支持除实体类型之外的复杂类型/原始类型等绑定到类型的操作,必须将 IsBound 标志设置为 true 以进行绑定操作。

从0.6版开始,操作将自动路由到Api类中定义的方法,不需要其他控制器。有关更多信息,请参阅第3.3节。

操作 如果在 Api 类中声明的方法满足以下条件,则会将名称为方法名称的操作添加到模型中。

自定义模型扩展

如果即使在应用RESTier的约定后用户仍然需要扩展模型,则可以在调用ApiBase.ConfigureApi(apiType,services)之后使用IServiceCollection AddService添加ModelBuilder。

namespace Microsoft.OData.Service.Sample.Trippin.Api
{
    public class TrippinApi : ApiBase
    {
        protected static new IServiceCollection ConfigureApi(Type apiType, IServiceCollection services)
        {
            services = ApiBase.ConfigureApi(apiType, services);

            // Add your custom model extender here.
            services.AddService<IModelBuilder, CustomizedModelBuilder>();
            return services;
        }

        private class CustomizedModelBuilder : IModelBuilder
        {
            public IModelBuilder InnerModelBuilder { get; set; }

            public async Task<IEdmModel> 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;
            }
        }
    }
}

经过以上步骤,最终构建模型的过程将是:

首先调用在ApiBase.ConfigureApi(apiType,services)之前注册的用户的模型构建器。
RESTier的模型构建器包括EF模型构建器,将调用RestierModelExtender。
调用 ApiBase.ConfigureApi(apiType, services) 后注册的用户模型构建器。
如果不首先调用InnerModelBuilder方法,则调用顺序将有所不同。 实际上,此顺序不仅适用于 IModelBuilder,还适用于所有其他服务。

有关RESTier API服务的更多详细信息,请参阅第4.3节。

2.6复合键

复合密钥意味着一个实体具有一个以上的密钥属性。 RESTIer自动支持它,而无需任何其他配置。

要请求具有复合键的实体,URL将类似于〜/ EntitySet(keyName1 = value1,keyName2 = value2)

2.7 键作为段

在调用 MapRestierRoute 方法之前,RESTier 支持使用单行配置将键作为段:

config.SetUrlKeyDelimiter(ODataUrlKeyDelimiter.Slash);

然后请求一个以键为段的实体,URL将类似于〜/ EntitySet / KeyValue

注意:如果实体类型具有复合键,则此实体类型不支持作为段的键。

2.8自定义查询

RESTier 支持自定义查询设置和查询流程逻辑。

1.自定义查询设置

RESTier 支持自定义各种查询设置,如 AllowedLogicalOperators、AllowedQueryOptions、MaxExpansionDepth、MaxAnyAllExpressionDepth 等。 有关设置的完整列表,请参阅课程。

这是一个关于如何自定义 MaxExpansionDepth 从默认值 2 到 3 的示例,这意味着现在允许两级嵌套扩展,请参阅此链接以查看端到端示例,

首先创建一个工厂委托,该委托将创建ODataValidationSettings的新实例,然后通过覆盖Api类中的ConfigureApi方法将其作为服务注册到RESTier Dependency Injection框架中。

      protected static new IServiceCollection ConfigureApi(Type apiType, IServiceCollection services)
        {
            // Add OData Query Settings and valiadtion settings
            Func<IServiceProvider, ODataValidationSettings> validationSettingFactory = (sp) => new ODataValidationSettings
            {
                MaxAnyAllExpressionDepth =3,
                MaxExpansionDepth = 3
            };

            return ApiBase.ConfigureApi(apiType, services)
                .AddSingleton<ODataValidationSettings>(validationSettingFactory);
        }

然后,在应用此自定义之前,默认情况下仅支持通过最多最多一个嵌套的$ expand最多支持两个嵌套的$ expand的$ expand。

2. 自定义查询逻辑

RESTier支持基于约定的内置查询自定义逻辑(请参阅第2.2节),此外,RESTier还具有两个接口IQueryExpressionAuthorizer和IQueryExpressionProcessor,供最终用户进一步自定义查询过程逻辑。

自定义授权逻辑

用户可以使用IQueryExpressionAuthorizer接口定义任何自定义授权逻辑,以查看用户是否获得了指定查询的授权,如果此方法返回false,则相关查询将获得错误代码403(禁止)。

插入自定义过程逻辑有两个步骤,

首先创建一个类 CustomizedAuthorizer 实现 IQueryExpressionAuthorizer,并添加所需的任何流程逻辑。

其次,通过覆盖 Api 类中的 ConfigureApi 方法将其作为服务注册到 RESTier 依赖注入框架中。

        protected static new IServiceCollection ConfigureApi(Type apiType, IServiceCollection services)
        {

            return ApiBase.ConfigureApi(apiType, services)
                .AddService<IQueryExpressionInspector, CustomizedInspector>();
        }
原文地址:https://www.cnblogs.com/JackeyLove/p/14836148.html