什么是OData
开放数据协议(Open Data Protocol,简称OData)是一种描述如何创建和访问Restful服务的OASIS标准。该标准由微软发起 [1] ,前三个版本1.0、2.0、3.0都是微软开放标准,遵循微软开放规范承诺书(Microsoft Open Specification Promise)。第四个版本4.0于2014年3月17日在OASIS投票通过成为开放工业标准
上面是百度的答案,我们可以很清楚的知道这是一个协议
使用OData开发
以.NetCoreAPI为例,创建一个API项目
添加ASP.NET Core OData
和Microsoft.EntityFrameworkCore.InMemory
Nuget程序包
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<UserSecretsId>0d1b65c3-336b-4511-be78-68c012362630</UserSecretsId>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OData" Version="7.4.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="5.0.5" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.10.9" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.6.3" />
</ItemGroup>
</Project>
ASP.NET Core OData
程序包是为了更方便的去开发而引入的
Microsoft.EntityFrameworkCore.InMemory
则方便数据引入
添加Model类
// Book
public class Book
{
public int Id { get; set; }
public string ISBN { get; set; }
public string Title { get; set; }
public string Author { get; set; }
public decimal Price { get; set; }
public Address Location { get; set; }
public Press Press { get; set; }
}
// Press
public class Press
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public Category Category { get; set; }
}
// Category
public enum Category
{
Book,
Magazine,
EBook
}
// Address
public class Address
{
public string City { get; set; }
public string Street { get; set; }
}
在这里:
- Book,Press将作为实体类型
- Address将用作“复杂”类型。
- Category将用作枚举类型。
建立EDM(Entity Data Model)模型
OData基于实体数据模型(EDM)来描述数据结构,在ASP.Net Core OData中,可以轻松的基于上述模型建立,在Startup
类末尾加一个私有方法
private static IEdmModel GetEdmModel()
{
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Book>("Books");
builder.EntitySet<Press>("Presses");
return builder.GetEdmModel();
}
在这里我们定义了两个名为Books和Presses 的实体集
通过依赖注入注册服务
ASP.NET Core OData需要预先注册一些服务才能提供其功能,该库提供了一个名为AddOData()
的扩展方法,以通过内置的依赖项注入来注册所需要的OData服务,因此,修改如下代码到ConfigureServices中
public class Startup
{
// ...
public void ConfigureServices(IServiceCollection services)
{
services.AddOData();
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "WebApplication1", Version = "v1" });
});
}
}
注册OData端点
我们需要添加OData路由来注册OData端点,并调用GetEdmModel()
绑定EDM模型到端点
public class Startup
{
// ...
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "WebApplication1 v1"));
}
app.UseEndpoints(endpoints =>
{
endpoints.MapODataRoute("odata", "odata", GetEdmModel());
});
}
}
8.0版本无需在Configure中注册:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<BookStoreContext>(opt => opt.UseInMemoryDatabase("BookLists"));
services.AddControllers();
services.AddOData(opt => opt.AddModel("odata", GetEdmModel()));
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
private static IEdmModel GetEdmModel()
{
// …
}
}
查询元数据
OData服务已经准备就绪,可以运行,并且提供基本的功能,例如查询元数据(EDM的XML表示),构建并允许项目,我们任何客户端工具发出请求:
GET http://localhost:5000/odata/$metadata
然后,可以获取如下xml所示的元数据
<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx">
<edmx:DataServices>
<Schema Namespace="BookStore.Models" xmlns="http://docs.oasis-open.org/odata/ns/edm">
<EntityType Name="Book">
<Key>
<PropertyRef Name="Id" />
</Key>
<Property Name="Id" Type="Edm.Int32" Nullable="false" />
<Property Name="ISBN" Type="Edm.String" />
<Property Name="Title" Type="Edm.String" />
<Property Name="Author" Type="Edm.String" />
<Property Name="Price" Type="Edm.Decimal" Nullable="false" />
<Property Name="Location" Type="BookStore.Models.Address" />
<NavigationProperty Name="Press" Type="BookStore.Models.Press" />
</EntityType>
<EntityType Name="Press">
<Key>
<PropertyRef Name="Id" />
</Key>
<Property Name="Id" Type="Edm.Int32" Nullable="false" />
<Property Name="Name" Type="Edm.String" />
<Property Name="Email" Type="Edm.String" />
<Property Name="Category" Type="BookStore.Models.Category" Nullable="false" />
</EntityType>
<ComplexType Name="Address">
<Property Name="City" Type="Edm.String" />
<Property Name="Street" Type="Edm.String" />
</ComplexType>
<EnumType Name="Category">
<Member Name="Book" Value="0" />
<Member Name="Magazine" Value="1" />
<Member Name="EBook" Value="2" />
</EnumType>
</Schema>
<Schema Namespace="Default" xmlns="http://docs.oasis-open.org/odata/ns/edm">
<EntityContainer Name="Container">
<EntitySet Name="Books" EntityType="BookStore.Models.Book">
<NavigationPropertyBinding Path="Press" Target="Presses" />
</EntitySet>
<EntitySet Name="Presses" EntityType="BookStore.Models.Press" />
</EntityContainer>
</Schema>
</edmx:DataServices>
</edmx:Edmx>
创建数据库上下文
现在,可以添加更多实际的功能,首先将数据库上下文引入Web程序,在Models文件夹中创建BookStoreContext
类
public class BookStoreContext : DbContext
{
public BookStoreContext(DbContextOptions<BookStoreContext> options)
: base(options)
{
}
public DbSet<Book> Books { get; set; }
public DbSet<Press> Presses { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Book>().OwnsOne(c => c.Location);
}
}
OnModelCreating
中的代码将Address映射为复杂类型
注册数据库上下文
通过依赖注入在服务中注册数据库上下文,求改Startup类中的ConfigureServices方法
public class Startup
{
// ...
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<BookStoreContext>(opt => opt.UseInMemoryDatabase("BookLists"));
services.AddOData();
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "WebApplication1", Version = "v1" });
});
}
}
模型数据
为了简单起见,我们简单生成一些内容
public static class DataSource
{
private static IList<Book> _books { get; set; }
public static IList<Book> GetBooks()
{
if (_books != null)
{
return _books;
}
_books = new List<Book>();
// book #1
Book book = new Book
{
Id = 1,
ISBN = "978-0-321-87758-1",
Title = "Essential C#5.0",
Author = "Mark Michaelis",
Price = 59.99m,
Location = new Address { City = "Redmond", Street = "156TH AVE NE" },
Press = new Press
{
Id = 1,
Name = "Addison-Wesley",
Category = Category.Book
}
};
_books.Add(book);
// book #2
book = new Book
{
Id = 2,
ISBN = "063-6-920-02371-5",
Title = "Enterprise Games",
Author = "Michael Hugos",
Price = 49.99m,
Location = new Address { City = "Bellevue", Street = "Main ST" },
Press = new Press
{
Id = 2,
Name = "O'Reilly",
Category = Category.EBook,
}
};
_books.Add(book);
return _books;
}
}
操作资源
构建Controller
创建一个Controller命名为BooksController
[ApiController]
[Route("[controller]")]
public class BooksController : ODataController
{
private BookStoreContext _db;
public BooksController(BookStoreContext context)
{
_db = context;
if (context.Books.Count() == 0)
{
foreach (var b in DataSource.GetBooks())
{
context.Books.Add(b);
context.Presses.Add(b.Press);
}
context.SaveChanges();
}
}
[EnableQuery]
public IActionResult Get()
{
return Ok(_db.Books);
}
[EnableQuery]
public IActionResult Get([FromODataUri] int key)
{
return Ok(_db.Books.FirstOrDefault(c => c.Id == key));
}
}
在上面定义的两个Action中:
- Get()返回所有内容
- Get(int Key)返回指定ID内容
检索资源
我们访问:GET http://localhost:5000/odata/Books 响应结果为:
{
"@odata.context": "http://localhost:5000/odata/$metadata#Books",
"value": [
{
"Id": 1,
"ISBN": "978-0-321-87758-1",
"Title": "Essential C#5.0",
"Author": "Mark Michaelis",
"Price": 59.99,
"Location": {
"City": "Redmond",
"Street": "156TH AVE NE"
}
},
{
"Id": 2,
"ISBN": "063-6-920-02371-5",
"Title": "Enterprise Games",
"Author": "Michael Hugos",
"Price": 49.99,
"Location": {
"City": "Bellevue",
"Street": "Main ST"
}
}
]
}
尝试发出:GET http://localhost:5000/odata/Books(2) 获取ID为2的书籍
创建资源
添加下列代码到Controller
中
[EnableQuery]
public IActionResult Post([FromBody] Book book)
{
_db.Books.Add(book);
_db.SaveChanges();
return Created(book);
}
发出如下请求创建Book数据
*POST* http://localhost:5000/odata/Books
Content-Type: application/json
Content:
{
"Id":3,"ISBN":"82-917-7192-5","Title":"Hary Potter","Author":"J. K. Rowling",
"Price":199.99,
"Location":{
"City":"Shanghai",
"Street":"Zhongshan RD"
}
}
查询资源
需要在Startup.cs中添加如下代码启用所有OData查询选项,例如$filter,$orderby,$expand等。
public class Startup
{
// ...
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "WebApplication1 v1"));
}
//...
app.UseEndpoints(endpoints =>
{
endpoints.Select().Expand().Filter().OrderBy().MaxTop(100).Count();
endpoints.MapODataRoute("odata", "odata", GetEdmModel());
});
}
}
基于$filter的示例:
GET http://localhost:5000/odata/Books?$filter=Price le 50
响应内容为:
{
"@odata.context": "https://localhost:5001/odata/$metadata#Books",
"value": [
{
"Id": 2,
"ISBN": "063-6-920-02371-5",
"Title": "Enterprise Games",
"Author": "Michael Hugos",
"Price": 49.99,
"Location": {
"City": "Bellevue",
"Street": "Main ST"
}
}
]
}
它还支持更为复杂的查询选项,例如:
GET http://localhost:5000/odata/Books?$filter=Price le 50&$expand=Press($select=Name)&$select=ISBN
响应结果为:
{
"@odata.context": "http://localhost:5000/odata/$metadata#Books(ISBN,Press(Name))",
"value": [
{
"ISBN": "063-6-920-02371-5",
"Press": {
"Name": "O'Reilly"
}
}
]
}
更多的查询方式可以在官方网站查看,这些都是基于OData协议进行的
如果在Swagger中显示OData路由
使用NuGet安装OData.Swagger
程序包
打开Startup.cs文件,在ConfigureService方法中添加如下服务
services.AddOdataSwaggerSupport();
重新启动程序即可
如果你的控制器是继承自ODataController
,则需要添加[ApiExplorerSettings(IgnoreApi = false)]
[ApiController]
[ApiExplorerSettings(IgnoreApi = false)]
[Route("[controller]")]
public class BooksController : ODataController
{
//...
}
本文参考:
OData参考:https://devblogs.microsoft.com/odata/asp-net-core-odata-now-available/
OData参考:https://devblogs.microsoft.com/odata/asp-net-odata-8-0-preview-for-net-5/
在Swagger中显示OData参考:https://github.com/KishorNaik/Sol_OData_Swagger_Support
OData官网:https://www.odata.org/