.NET9 使用 OData 协议项目实战

.NET 中 ODate 协议介绍

OData(Open Data Protocol) 是一个开放的 Web 协议,用于查询和更新数据。在 .NET 生态系统中,OData 被广泛支持和使用。

主要特性
1. 统一的数据访问方式
  • 提供标准化的查询语法
  • 支持 CRUD 操作
  • 支持元数据描述
2. 查询能力

标准查询选项支持:

  • 过滤数据($filter
  • 排序($orderby
  • 分页($top, $skip
  • 投影,选择字段($select
  • 扩展关联数据($expand

.NET 9 增强功能:

  • 性能改进:查询执行和序列化性能优化
  • 更好的路由集成:与 ASP.NET Core 路由系统更紧密集成
  • 端点(Endpoint)路由支持:完全支持现代端点路由范式
3. 格式支持
  • JSON 格式(默认,推荐)
  • XML 格式
  • Atom 格式
版本支持

.NET 支持多个 OData 版本:

  • OData v4(当前最新版本,推荐使用)
  • OData v3(较旧版本)
安全考虑
  • 支持授权和认证
  • 查询复杂度限制
  • 防止拒绝服务攻击的机制
最佳实践
  1. 正确设置查询限制(SetMaxTop
  2. 使用 EnableQuery 特性控制查询行为
  3. 实现适当的错误处理
  4. 考虑使用 DTO 避免暴露内部模型
  5. 启用适当的缓存策略
优势
  1. 标准化:遵循开放标准,便于与其他系统集成
  2. 灵活性:客户端可以构建复杂的查询
  3. 性能优化 :支持服务端(SSEef core 服务端评估)分页和投影字段选择(CSEef core 客户端评估)
  4. 工具支持Visual Studio 等工具提供良好支持

OData 遵循的国际标准:

  • 核心标准
bash 复制代码
### OASIS 标准
- **OData Version 4.0**: 由 OASIS 组织发布的开放标准
- **OData JSON Format Version 4.0**: 定义 JSON 格式的数据交换规范
- **OData Common Schema Definition Language (CSDL) Version 4.0**: 实体数据模型定义语言

### ISO/IEC 标准
- **ISO/IEC 20802-1:2016**: Information technology - Open Data Protocol (OData) - Part 1: Core
- **ISO/IEC 20802-2:2016**: Information technology - Open Data Protocol (OData) - Part 2: URL Conventions
- **ISO/IEC 20802-3:2016**: Information technology - Open Data Protocol (OData) - Part 3: Common Schema Definition Language (CSDL)
  • 相关 Web 标准
bash 复制代码
### HTTP 标准
- **RFC 7231**: HTTP/1.1 Semantics and Content
- **RFC 7230-7237**: HTTP 协议系列标准
### URI 标准
- **RFC 3986**: Uniform Resource Identifier (URI): Generic Syntax
  • 数据格式标准
bash 复制代码
- **ECMA-404**: The JSON Data Interchange Format
- **RFC 7493**: The I-JSON Message Format
  • 其他相关标准
bash 复制代码
- **Atom Publishing Protocol (AtomPub)**: RFC 5023
- **OData Extension for Data Aggregation**: OASIS 标准扩展

这些标准确保了 OData 协议在全球范围内的互操作性和标准化实施。


使用场景
  • 构建 RESTful API
  • 数据分析和报表系统
  • 移动应用后端服务
  • 微服务间的数据交互

·OData 协议为 .NET 开发者提供了一种强大而标准化的方式来构建数据服务 API


实现方式

.csproj 项目添加 nuget 相关包:

bash 复制代码
<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net9.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.7" />
    <PackageReference Include="Microsoft.AspNetCore.OData" Version="9.3.2" />
    <PackageReference Include="Microsoft.OData.ModelBuilder" Version="2.0.0" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="9.0.7" />
    <PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.3" />
  </ItemGroup>

</Project>

说明:此处使用 EF Core 内存模式,模拟 DB 数据库,并添加 OData 相关包。

目录结构

完整目录结构如下:

text 复制代码
├─Controllers
├─Datatabase
│  ├─Entities
│  ├─Mappers
│  └─Repositories
├─Models
│  └─Dtos
├─Properties
└─Services
架构层次说明

这种实现保持了清晰的分层架构:

Controller 层:ProductsController
  • 负责处理 HTTP 请求和响应
  • 负责请求 DTO 的数据验证
  • 使用 OData 特性进行查询支持
  • 依赖服务层进行业务处理
Database 层:
  • 数据库相关的核心数据访问层
  • 包含实体、映射和仓储模式的实现
  • Database/Entities
    • 存放数据实体类,包含数据结构定义,通常对应数据库表结构
    • 表示业务领域中的核心数据对象
  • Database/Mappers
    • 数据映射器,负责 Entity(实体对象)与领域 DTO 之间的转换
  • Database/Repositories
    • 仓储模式实现,提供数据访问的抽象接口
    • 封装数据访问逻辑,提供 CRUD 操作
Service 层:ProductService
  • 实现业务逻辑
  • 协调多个仓储操作
  • 处理业务规则和验证
  • 依赖仓储层进行数据访问
Models 层:领域模型
  • 包含数据结构定义
  • 领域 DTO 模型
  • 业务领域层中间转换模型

这种架构模式的优势:

  • 关注点分离:每层职责明确
  • 可测试性:各层可以独立进行单元测试
  • 可维护性:修改某一层不会(最小化)影响其他层
  • 可扩展性:可以轻松添加新的业务逻辑或数据源
  • 复用性:服务和仓储可以在多个控制器中复用
详细示例
  • 创建数据库表实体模型
csharp 复制代码
namespace ODataDemo.Database.Entities;

/// <summary>
/// 分类领域实体
/// </summary>
public class Category
{
    public required string Id { get; set; }
    public required string Name { get; set; }
    public required string Description { get; set; }
    public DateTime CreatedDate { get; set; }
    public bool IsActive { get; set; }

    // 导航属性
    public ICollection<Product> Products { get; set; } = [];

    // 领域方法
    public void Activate() => IsActive = true;

    public void Deactivate()
    {
        IsActive = false;
        foreach (var product in Products)
        {
            product.UpdateStock(-product.StockQuantity); // 清空库存
        }
    }

    public bool CanDelete() => !Products.Any(p => p.IsInStock());
}

/// <summary>
/// 产品领域实体
/// </summary>
public sealed class Product
{
    public required string Id { get; set; }
    public required string Name { get; set; }
    public required string Description { get; set; }
    public decimal Price { get; set; }
    public int StockQuantity { get; set; }
    public DateTime CreatedDate { get; set; }
    public DateTime UpdatedDate { get; set; }

    // 导航属性
    public string CategoryId { get; set; } = string.Empty;
    public Category? Category { get; set; }

    // 领域方法
    public void UpdateStock(int quantity)
    {
        if (quantity < 0 && Math.Abs(quantity) > StockQuantity)
        {
            throw new InvalidOperationException("库存不足");
        }
        StockQuantity += quantity;
        UpdatedDate = DateTime.UtcNow;
    }

    public bool IsInStock() => StockQuantity > 0;

    public void ApplyDiscount(decimal discountPercentage)
    {
        if (discountPercentage < 0 || discountPercentage > 100)
        {
            throw new ArgumentException("折扣百分比必须在0-100之间");
        }
        Price = Price * (1 - discountPercentage / 100);
        UpdatedDate = DateTime.UtcNow;
    }
}
  • 创建 DTO 对象
csharp 复制代码
using System.Text.Json.Serialization;

namespace ODataDemo.Models.Dtos;

//#######################################
// 分类数据传输对象,用于 OData API
//#######################################

public class CategoryRequstDto
{
    [JsonPropertyName("name")]
    public required string Name { get; set; }

    [JsonPropertyName("description")]
    public required string Description { get; set; }

    [JsonPropertyName("is_active")]
    public bool IsActive { get; set; }
}

public sealed class CategoryResponeDto : CategoryRequstDto
{
    [JsonPropertyName("id")]
    public required string Id { get; set; }

    [JsonPropertyName("created_date")]
    public DateTime CreatedDate { get; set; }

    [JsonPropertyName("product_count")]
    public int ProductCount { get; set; }
}

//############################################
// 产品数据传输对象,用于 OData API
//############################################

public class ProductRequstDto
{
    [JsonPropertyName("name")]
    public required string Name { get; set; }

    [JsonPropertyName("description")]
    public required string Description { get; set; }

    [JsonPropertyName("price")]
    public decimal Price { get; set; }

    [JsonPropertyName("stock_quantity")]
    public int StockQuantity { get; set; }

    [JsonPropertyName("category_id")]
    public string CategoryId { get; set; } = string.Empty;

    [JsonPropertyName("category_name")]
    public string CategoryName { get; set; } = string.Empty;

    [JsonPropertyName("is_in_stock")]
    public bool IsInStock { get; set; }
}

public sealed class ProductResponeDto : ProductRequstDto
{
    [JsonPropertyName("id")]
    public required string Id { get; set; }

    [JsonPropertyName("created_date")]
    public DateTime CreatedDate { get; set; }

    [JsonPropertyName("updated_date")]
    public DateTime UpdatedDate { get; set; }
}
  • Entity 映射 DTO 处理
csharp 复制代码
using ODataDemo.Database.Entities;
using ODataDemo.Models.Dtos;

namespace ODataDemo.Database.Mappers;

public static class CategoryMapper
{
    public static Category From(this CategoryRequstDto dto)
    {
        return new Category()
        {
            Id = Guid.CreateVersion7().ToString(),
            Name = dto.Name,
            Description = dto.Description,
            CreatedDate = DateTime.UtcNow,
            IsActive = dto.IsActive,
        };
    }

    public static CategoryResponeDto ToModel(this Category entity)
    {
        return new CategoryResponeDto
        {
            Id = entity.Id,
            Name = entity.Name,
            Description = entity.Description,
            IsActive = entity.IsActive,
            CreatedDate = entity.CreatedDate,
            ProductCount = entity.Products.Count,
        };
    }
}

public static class ProductMapper
{
    public static Product From(this ProductRequstDto dto)
    {
        return new Product()
        {
            Id = Guid.CreateVersion7().ToString(),
            Name = dto.Name,
            Description = dto.Description,
            Price = dto.Price,
            StockQuantity = dto.StockQuantity,
            CreatedDate = DateTime.UtcNow,
            UpdatedDate = DateTime.UtcNow,
            CategoryId = dto.CategoryId
        };
    }

    public static ProductResponeDto ToModel(this Product entity)
    {
        return new ProductResponeDto
        {
            Id = entity.Id,
            Name = entity.Name,
            Description = entity.Description,
            Price = entity.Price,
            StockQuantity = entity.StockQuantity,
            CreatedDate = entity.CreatedDate,
            UpdatedDate = entity.UpdatedDate,
            CategoryId = entity.CategoryId,
            CategoryName = entity.Category?.Name ?? string.Empty,
            IsInStock = entity.IsInStock()
        };
    }
}
  • 定义仓储规范

说明:在仓储层,只出现数据库表对应的实体对象 Entity

csharp 复制代码
using ODataDemo.Database.Entities;

namespace ODataDemo.Database.Repositories;

public interface IDataRepository
{
    #region Product
    IQueryable<Product> GetProducts();
    Task<Product?> GetProductByIdAsync(string id);
    Task<Product> AddProductAsync(Product product);
    Task<Product> UpdateProductAsync(Product product);
    Task DeleteProductAsync(string id);
    Task<bool> ExistsProductAsync(string id);
    #endregion

    #region Category
    IQueryable<Category> GetCategorys();
    Task<Category?> GetCategoryByIdAsync(string id);
    Task<Category> AddCategoryAsync(Category category);
    Task<Category> UpdateCategoryAsync(Category category);
    Task DeleteCategoryAsync(string id);
    Task<bool> ExistsCategoryAsync(string id);
    #endregion
}
  • 定义服务规范

说明:服务层只出现 DTO 对象,在实现内部处理 EntityDTO 的转换。

csharp 复制代码
using ODataDemo.Models.Dtos;

namespace ODataDemo.Services;

public interface ICategoryService
{
    IQueryable<CategoryResponeDto> GetAllCategories();
    Task<CategoryResponeDto?> GetCategoryByIdAsync(string id);
    Task<CategoryResponeDto> CreateCategoryAsync(CategoryRequstDto category);
    Task<CategoryResponeDto> UpdateCategoryAsync(string id, CategoryRequstDto category);
    Task DeleteCategoryAsync(string id);
    Task ActivateCategoryAsync(string id);
    Task DeactivateCategoryAsync(string id);
}

public interface IProductService
{
    IQueryable<ProductResponeDto> GetProducts();
    Task<ProductResponeDto?> GetProductByIdAsync(string id);
    Task<ProductResponeDto> CreateProductAsync(ProductRequstDto dto);
    Task<ProductResponeDto> UpdateProductAsync(string id, ProductRequstDto dto);
    Task DeleteProductAsync(string id);
    Task ApplyDiscountAsync(string productId, decimal discountPercentage);
    Task UpdateStockAsync(string productId, int quantity);
}
  • 配置 EDM 模型

说明:EDM 配置是关键,更多信息请查看相关资料,篇幅有限不再详述。

相关资料:

csharp 复制代码
using Microsoft.OData.Edm;
using Microsoft.OData.ModelBuilder;
using ODataDemo.Models.Dtos;

namespace ODataDemo.Database;

public static class EdmModelConfig
{
    // 配置 EDM 模型
    public static IEdmModel GetEdmModel()
    {
        var builder = new ODataConventionModelBuilder();

        // 只注册 DTO 类型
        var productDto = builder.EntityType<ProductResponeDto>();
        productDto.HasKey(p => p.Id);
        productDto.Property(p => p.Name);
        productDto.Property(p => p.Description);
        productDto.Property(p => p.Price);
        productDto.Property(p => p.StockQuantity);
        productDto.Property(p => p.CategoryId);
        productDto.Property(p => p.CreatedDate);
        productDto.Property(p => p.IsInStock);
        productDto.Property(p => p.CreatedDate);
        productDto.Property(p => p.UpdatedDate);

        var categoryDto = builder.EntityType<CategoryResponeDto>();
        categoryDto.HasKey(c => c.Id);
        categoryDto.Property(c => c.Name);
        categoryDto.Property(c => c.Description);
        categoryDto.Property(p => p.IsActive);
        categoryDto.Property(p => p.CreatedDate);
        categoryDto.Property(p => p.ProductCount);

        // 使用 DTO 创建实体集
        builder.EntitySet<ProductResponeDto>("Products");
        builder.EntitySet<CategoryResponeDto>("Categories");

        return builder.GetEdmModel();
    }
}
  • 配置 AppDbContext

说明:在此处添加一些初始化的种子数据。

csharp 复制代码
using Microsoft.EntityFrameworkCore;
using ODataDemo.Database.Entities;

namespace ODataDemo.Database;

public class AppDbContext(DbContextOptions<AppDbContext> options) : DbContext(options)
{
    public DbSet<Product> Products { get; set; }
    public DbSet<Category> Categories { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        string id1 = Guid.CreateVersion7().ToString();
        string id2 = Guid.CreateVersion7().ToString();
        string id3 = Guid.CreateVersion7().ToString();

        // Seed data
        modelBuilder.Entity<Category>().HasData(
            new Category { Id = id1, Name = "Electronics", Description = "Electronic devices" },
            new Category { Id = id2, Name = "Books", Description = "Books and literature" },
            new Category { Id = id3.ToString(), Name = "Clothing", Description = "Apparel and accessories" }
        );

        modelBuilder.Entity<Product>().HasData(
            new Product { Id = Guid.CreateVersion7().ToString(), Name = "Laptop", Price = 1200.00m, StockQuantity = 50, CategoryId = id1, Description = "High-performance laptop" },
            new Product { Id = Guid.CreateVersion7().ToString(), Name = "Mouse", Price = 25.00m, StockQuantity = 100, CategoryId = id1, Description = "Wireless mouse" },
            new Product { Id = Guid.CreateVersion7().ToString(), Name = "Keyboard", Price = 75.00m, StockQuantity = 75, CategoryId = id1, Description = "Mechanical keyboard" },
            new Product { Id = Guid.CreateVersion7().ToString(), Name = "C# Programming Guide", Price = 45.00m, StockQuantity = 30, CategoryId = id2, Description = "Comprehensive C# guide" },
            new Product { Id = Guid.CreateVersion7().ToString(), Name = "T-Shirt", Price = 20.00m, StockQuantity = 200, CategoryId = id3, Description = "Cotton t-shirt" }
        );

        base.OnModelCreating(modelBuilder);
    }
}

说明:添加两个 SwaggerOData 相关的处理

  • 支持 OData 查询参数显示
csharp 复制代码
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using System.Reflection;

namespace ODataDemo.Filters;

public class SwaggerDefaultValues : IOperationFilter
{
    public void Apply(OpenApiOperation operation, OperationFilterContext context)
    {
        var apiDescription = context.ApiDescription;

        // 检查是否标记为过时
        var isDeprecated = context.MethodInfo.GetCustomAttribute<ObsoleteAttribute>() != null;
        if (isDeprecated)
        {
            operation.Deprecated = true;
        }

        // 添加默认响应
        if (operation.Parameters == null)
        {
            operation.Parameters = [];
        }

        // 为每个参数添加默认值和描述
        foreach (var parameter in operation.Parameters)
        {
            var description = apiDescription.ParameterDescriptions
                .First(p => p.Name == parameter.Name);

            parameter.Description ??= description.ModelMetadata?.Description;

            if (parameter.Schema.Default == null &&
                description.DefaultValue != null &&
                description.DefaultValue is not DBNull &&
                description.ModelMetadata?.ModelType != null)
            {
                var json = System.Text.Json.JsonSerializer.Serialize(description.DefaultValue);
                parameter.Schema.Default = OpenApiAnyFactory.CreateFromJson(json);
            }

            parameter.Required |= description.IsRequired;
        }

        // 为 OData 操作添加通用参数说明
        if (context.ApiDescription.HttpMethod == "GET")
        {
            AddODataQueryParameters(operation);
        }
    }

    private void AddODataQueryParameters(OpenApiOperation operation)
    {
        var odataParameters = new List<(string name, string description)>
        {
            ("$filter", "Filters the results based on a Boolean condition"),
            ("$orderby", "Sorts the results"),
            ("$top", "Returns only the first n results"),
            ("$skip", "Skips the first n results"),
            ("$select", "Selects which properties to include in the response"),
            ("$expand", "Expands related entities inline"),
            ("$count", "Includes a count of the total number of items")
        };

        foreach (var (name, description) in odataParameters)
        {
            if (!operation.Parameters.Any(p => p.Name == name))
            {
                operation.Parameters.Add(new OpenApiParameter
                {
                    Name = name,
                    In = ParameterLocation.Query,
                    Description = description,
                    Schema = new OpenApiSchema
                    {
                        Type = "string"
                    },
                    Required = false
                });
            }
        }
    }
}
  • 处理 OData 特定类型的序列化问题
csharp 复制代码
using Microsoft.AspNetCore.OData.Deltas;
using Microsoft.AspNetCore.OData.Results;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;

namespace ODataDemo.Filters;

public class ODataSchemaFilter : ISchemaFilter
{
    public void Apply(OpenApiSchema schema, SchemaFilterContext context)
    {
        // 处理 OData 特定类型的序列化问题
        if (context.Type.IsGenericType &&
            (context.Type.GetGenericTypeDefinition() == typeof(SingleResult<>) ||
             context.Type.GetGenericTypeDefinition() == typeof(Delta<>)))
        {
            // 对于 SingleResult<T> 和 Delta<T>,使用泛型参数类型
            var genericType = context.Type.GetGenericArguments()[0];
            // 可以根据需要自定义 schema
        }
    }
}

  • 控制器实现

注意:控制器继承 ODataController,没有 [ApiController][Route] 特性

//[Route("api/[controller]")]

//[ApiController]

csharp 复制代码
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.OData.Deltas;
using Microsoft.AspNetCore.OData.Formatter;
using Microsoft.AspNetCore.OData.Query;
using Microsoft.AspNetCore.OData.Results;
using Microsoft.AspNetCore.OData.Routing.Controllers;
using ODataDemo.Models.Dtos;
using ODataDemo.Services;

namespace ODataDemo.Controllers;

public class ProductsController(IProductService productService) : ODataController
{
    // GET /odata/Products
    [EnableQuery]
    public IActionResult Get()
    {
        return Ok(productService.GetProducts());
    }

    // GET /odata/Products(1) - 使用标准命名和路由
    [EnableQuery]
    public SingleResult<ProductResponeDto> Get([FromODataUri] string key)
    {
        var result = productService.GetProducts().Where(p => p.Id == key);
        return SingleResult.Create(result);
    }

    // POST /odata/Products
    public async Task<IActionResult> Post([FromBody] ProductRequstDto dto)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        var result = await productService.CreateProductAsync(dto);
        return Created(result);
    }

    // PUT /odata/Products(1)
    public async Task<IActionResult> Put([FromRoute] string key, [FromBody] ProductRequstDto dto)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        var result = await productService.UpdateProductAsync(key, dto);
        return Updated(result);
    }

    // PATCH /odata/Products(1)
    public async Task<IActionResult> Patch([FromRoute] string key, [FromBody] Delta<ProductRequstDto> delta)
    {
        var product = await productService.GetProductByIdAsync(key);
        if (product == null)
        {
            return NotFound();
        }

        delta.Patch(product);
        var result = await productService.UpdateProductAsync(key, product);
        return Updated(result);
    }

    // DELETE /odata/Products(1)
    public async Task<IActionResult> Delete([FromRoute] string key)
    {
        await productService.DeleteProductAsync(key);
        return NoContent();
    }
}
  • Program.cs 代码示例:
csharp 复制代码
using Microsoft.AspNetCore.OData;
using Microsoft.EntityFrameworkCore;
using Microsoft.OpenApi.Models;
using ODataDemo.Data.Repositories;
using ODataDemo.Database;
using ODataDemo.Database.Repositories;
using ODataDemo.Filters;
using ODataDemo.Services;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

// 添加 API 探索器(必需)
builder.Services.AddEndpointsApiExplorer();
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
//builder.Services.AddOpenApi();

// 添加 Swagger 生成器(必需)
builder.Services.AddSwaggerGen(options =>
{
    options.SwaggerDoc("v1", new OpenApiInfo
    {
        Version = "v1",
        Title = "OData API",
        Description = "An ASP.NET Core OData API"
    });

    // 支持 OData 查询参数显示
    options.OperationFilter<SwaggerDefaultValues>();

    // 处理 OData 类型序列化问题
    options.SchemaFilter<ODataSchemaFilter>();
    options.CustomSchemaIds(type => type.ToString());
});

// 添加控制器和 OData 服务
builder.Services.AddControllers()
    .AddOData(options =>
    {
        // 启用分页相关查询选项
        options.Select().Filter().OrderBy().Expand().Count()
               .SetMaxTop(100); // 设置每页最大记录数

        // 使用 Convention-based 路由
        options.AddRouteComponents("odata", EdmModelConfig.GetEdmModel());

        // 关键配置:启用大小写不敏感
        options.RouteOptions.EnablePropertyNameCaseInsensitive = true;
        options.RouteOptions.EnableControllerNameCaseInsensitive = true;
        options.RouteOptions.EnableActionNameCaseInsensitive = true;
    });

// 添加数据库上下文
builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseInMemoryDatabase("ODataDemo"));

// 注册db仓储服务
builder.Services.AddScoped<IDataRepository, DataRepository>();
// 注册业务服务
builder.Services.AddScoped<ICategoryService, CategoryService>();
builder.Services.AddScoped<IProductService, ProductService>();

var app = builder.Build();

// 初始化数据库
using (var scope = app.Services.CreateScope())
{
    var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();
    context.Database.EnsureCreated();
}

// Configure the HTTP request pipeline.
// 启用路由
app.UseRouting();

if (app.Environment.IsDevelopment())
{
    // 添加错误处理中间件
    app.Use(async (context, next) =>
    {
        try
        {
            await next();
        }
        catch (Exception ex)
        {
            if (context.Request.Path.StartsWithSegments("/swagger"))
            {
                Console.WriteLine("Swagger Error:");
            }
            
            Console.WriteLine($"Stack Trace: {ex.StackTrace}");
            await context.Response.WriteAsync(ex.Message);
        }
    });

    // 使用 swagger
    app.UseSwagger();
    app.UseSwaggerUI(options =>
    {
        options.SwaggerEndpoint("/swagger/v1/swagger.json", "OData API v1");
        options.RoutePrefix = "swagger";
    });
}

app.UseAuthorization();
app.MapControllers();

await app.RunAsync();

启动项目

启动项目,显示如下页面,其中包含两个接口,分别是产品和分类,另外还有一个源数据信息接口。

使用 Apipost 工具,访问源数据接口:

  • http://localhost:5108/odata/

其他接口类似,此处就不再详述。

最后附上完整示例截图,感兴趣的小伙伴欢迎点赞关注哟。


总结

ASP.NET Core WebAPIOData 的集成提供了一种标准化、高性能的方式来构建数据驱动的 REST API,大大简化了复杂查询接口的开发工作。

可以增强扩展统一的数据响应格式,比如 ApiResponse,统一的异常数据格式等。

相关推荐
杨DaB2 天前
【SpringBoot】Swagger 接口工具
java·spring boot·后端·restful·swagger
楽码7 天前
在RestFul接口应用Hmac算法
后端·算法·restful
许野平17 天前
Rust 同步方式访问 REST API 的完整指南
java·网络·rust·restful
ArabySide20 天前
【ASP.NET Core】探讨注入EF Core的DbContext在HTTP请求中的生命周期
后端·http·asp.net·asp.net core·efcore
程序猿阿伟22 天前
《不只是接口:GraphQL与RESTful的本质差异》
前端·restful·graphql
杨DaB23 天前
【SpringMVC】MVC中Controller的配置 、RestFul的使用、页面重定向和转发
java·笔记·后端·学习·spring·mvc·restful
程序员编程指南25 天前
Qt 网络编程进阶:RESTful API 调用
c语言·网络·c++·qt·restful
饭勺oO1 个月前
从WebApi迁移到Minimal API?有了这个神器,小白也能10分钟搞定!
asp.net core·minimalapi
鼠鼠我捏,要死了捏1 个月前
Spring Boot中REST与gRPC并存架构设计与性能优化实践指南
springboot·restful·grpc