.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 遵循的国际标准:

  • 核心标准
### 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 标准
### HTTP 标准
- **RFC 7231**: HTTP/1.1 Semantics and Content
- **RFC 7230-7237**: HTTP 协议系列标准
### URI 标准
- **RFC 3986**: Uniform Resource Identifier (URI): Generic Syntax
  • 数据格式标准
- **ECMA-404**: The JSON Data Interchange Format
- **RFC 7493**: The I-JSON Message Format
  • 其他相关标准
- **Atom Publishing Protocol (AtomPub)**: RFC 5023
- **OData Extension for Data Aggregation**: OASIS 标准扩展

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


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

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


实现方式

.csproj 项目添加 nuget 相关包:

<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 相关包。

目录结构

完整目录结构如下:

├─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 模型
  • 业务领域层中间转换模型

这种架构模式的优势:

  • 关注点分离:每层职责明确
  • 可测试性:各层可以独立进行单元测试
  • 可维护性:修改某一层不会(最小化)影响其他层
  • 可扩展性:可以轻松添加新的业务逻辑或数据源
  • 复用性:服务和仓储可以在多个控制器中复用
详细示例
  • 创建数据库表实体模型
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 对象
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 处理
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

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 的转换。

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 配置是关键,更多信息请查看相关资料,篇幅有限不再详述。
相关资料:

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

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

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 查询参数显示
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 特定类型的序列化问题
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]

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 代码示例:
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();

启动项目

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

swagger-odata

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

  • http://localhost:5108/odata/

odata

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

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

Odata-demo


总结

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ChaITSimpleLove

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值