OData of NET Core Development Log (Open Data Protocol)

  • 2021-11-01 03:01:17
  • OfStack

Brief introduction

OData, or Open Data Protocol, is an open protocol introduced by Microsoft in 2007, aiming at creating and using query and interactive RESTful API in a simple and standard way.

Class library

If you want to use the OData functionality in. NET Core, you need to add the Microsoft. AspNetCore. OData package.


dotnet add package Microsoft.AspNetCore.OData

Prepare model classes


public class Address
{
 public string City { get; set; }
 public string Street { get; set; }
}
public enum Category
{
 Book,
 Magazine,
 EBook
}
public class Press
{
 public int Id { get; set; }
 public string Name { get; set; }
 public string Email { get; set; }
 public Category Category { get; set; }
}
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 Address { get; set; }
 public Press Press { get; set; }
}

Creating an Edm model

OData uses EDM, or Entity, Data, Model, to describe the structure of the data. Add the creation method to the Startup file.


private static IEdmModel GetEdmModel()
{
  var builder = new ODataConventionModelBuilder();
  builder.EntitySet<Book>("Books");
  builder.EntitySet<Press>("Presses");
  return builder.GetEdmModel();
}

Register the OData service

Register the OData service in the ConfigureServices method of the Startup file.


services.AddOData();
services.AddMvc(options =>
  {
    options.EnableEndpointRouting = false;
  }).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

Note here that in. NET Core 2.2, there is already an endpoint by default, so to use the endpoint of OData, you need to disable the default option.

Register an OData endpoint

Also in the Startup file, in its Configure method, change the original registered routing content to register the endpoint of OData.


app.UseMvc(b =>
{
  b.MapODataServiceRoute("odata", "odata", GetEdmModel());
});

Display metadata

After running the program, you can access the address https://localhost: 5001/odata/$metadata, and you can see the metadata of all available models.


<edmx:Edmx xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx" Version="4.0">
  <edmx:DataServices>
    <Schema xmlns="http://docs.oasis-open.org/odata/ns/edm" Namespace="Default">
      <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="Address" Type="Default.Address"/>
        <NavigationProperty Name="Press" Type="Default.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="Default.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>
      <EntityContainer Name="Container">
        <EntitySet Name="Books" EntityType="Default.Book">
          <NavigationPropertyBinding Path="Press" Target="Presses"/>
        </EntitySet>
        <EntitySet Name="Presses" EntityType="Default.Press"/>
      </EntityContainer>
    </Schema>
  </edmx:DataServices>
</edmx:Edmx>

Create an Controller

In this paper, we do not consider the operation of database, so we use hard code to build the necessary model objects.


public class BooksController : ODataController
{
  private static IList<Book> Books {get; set;}
  public BooksController()
  {
    Books = new List<Book>
    {
      new Book
      {
        Id = 1,
        ISBN = "111-0-321-56789-1",
        Title = "Calculus",
        Price = 66.6m,
        Address = new Address
        {
          City = "Shanghai",
          Street = "Beijin Xi Road"
        },
        Press = new Press
        {
          Id = 1,
          Name = "Shanghai Tongji",
          Category = Category.Book
        }
      },
      new Book
      {
        Id = 2,
        ISBN = "222-2-654-00000-2",
        Title = "Linear Algebra",
        Price = 53.2m,
        Address = new Address
        {
          City = "Shanghai",
          Street = "Beijin Dong Road"
        },
        Press = new Press
        {
          Id = 2,
          Name = "Shanghai Fudan",
          Category = Category.EBook
        }
      }      
    };  
  }

  [EnableQuery]
  public IActionResult Get()
  {
    return Ok(Books);
  }

  [EnableQuery]
  public IActionResult Get(int key)
  {
    return Ok(Books.FirstOrDefault(b => b.Id == key));
  }
}

The EnableQuery feature must be added in scenarios that require advanced queries.

Query

After joining Controller, all Book data can be obtained by visiting https://localhost: 5001/odata/Books address.


{
  "@odata.context": "https://localhost:5001/odata/$metadata#Books",
  "value": [
    {
      "Id": 1,
      "ISBN": "111-0-321-56789-1",
      "Title": "Calculus",
      "Author": null,
      "Price": 66.6,
      "Address": {
        "City": "Shanghai",
        "Street": "Beijin Xi Road"
      }
    },
    {
      "Id": 2,
      "ISBN": "222-2-654-00000-2",
      "Title": "Linear Algebra",
      "Author": null,
      "Price": 53.2,
      "Address": {
        "City": "Shanghai",
        "Street": "Beijin Dong Road"
      }
    }
  ]
}

Access the address https://localhost: 5001/odata/Books (1), and you can get Book data with key value of 1.


{
  "@odata.context": "https://localhost:5001/odata/$metadata#Books/$entity",
  "Id": 1,
  "ISBN": "111-0-321-56789-1",
  "Title": "Calculus",
  "Author": null,
  "Price": 66.6,
  "Address": {
    "City": "Shanghai",
    "Street": "Beijin Xi Road"
  }
}

Advanced query

If you want to use the advanced features of OData queries, you can add additional configuration when registering endpoints.


app.UseMvc(b =>
{
  b.Select().Expand().Filter().OrderBy().MaxTop(100).Count();
  b.MapODataServiceRoute("odata", "odata", GetEdmModel());
});

Add the required query content when visiting the website:
https://localhost:5001/odata/Books?$select=Id,Title


public class Address
{
 public string City { get; set; }
 public string Street { get; set; }
}
public enum Category
{
 Book,
 Magazine,
 EBook
}
public class Press
{
 public int Id { get; set; }
 public string Name { get; set; }
 public string Email { get; set; }
 public Category Category { get; set; }
}
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 Address { get; set; }
 public Press Press { get; set; }
}
0

It is also easy if you want to filter data content according to specific conditions:
https://localhost:5001/odata/Books?$filter=Price%20le%2060


public class Address
{
 public string City { get; set; }
 public string Street { get; set; }
}
public enum Category
{
 Book,
 Magazine,
 EBook
}
public class Press
{
 public int Id { get; set; }
 public string Name { get; set; }
 public string Email { get; set; }
 public Category Category { get; set; }
}
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 Address { get; set; }
 public Press Press { get; set; }
}
1

Summarize

It's easy to see that the real charm of OData is its support for those advanced query functions, so when creating RESTful API, consider using OData, which should reduce a lot of unnecessary code work.


Related articles: