65.9K
CodeProject 正在变化。 阅读更多。
Home

OData ASP.NET Core 2.0 Web API 中的不区分大小写的搜索过滤器作为 OWIN 中间件

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3投票s)

2018 年 2 月 1 日

CPOL

4分钟阅读

viewsIcon

32842

downloadIcon

258

如何替换 Web API 查询字符串以提供不区分大小写的 OData 搜索

引言

Google、Bing 和其他搜索引擎在输入框中不区分大小写地提供搜索结果。OData 服务也大多如此。将数据库排序规则配置为忽略大小写是最有效率的解决方案。如果无法更改数据库(例如,由于缺乏访问权限或数据库是外部服务),则有另一种建议:在执行数据库查询之前,将数据库值转换为小写。

本文不介绍如何从头开始配置 Entity Framework 和 OData 服务,但文章附带了一个可用的 Visual Studio 项目示例。

在本文中,我将展示如何使用 OWIN 中间件在 ASP.NET Core Web API 中修改 OData 过滤器(或任何其他查询字符串)。此中间件附加在管道的开头。此处理的目的是通过 `tolower` 函数扩展数据库查询,以忽略大小写。

所需环境、工具和 NuGet 包

要重现该场景,我们需要以下软件组件。

ASP.NET Core 2.0 Web 应用程序

最新版本的 Visual Studio 2017(15.5.x)提供了创建基于 .NET Standard 2.0 的 Web 应用程序和 Web 服务的模板,因此也支持 .NET Core 2.0 或 .NET Framework 4.6.1。

如果您刚了解 .NET Standard 和 .NET Core 的区别 - .NET Standard 是一个“规范”,而 .NET Core 和 .NET Framework 是该规范的“实现”。

更多详情,请访问 Microsoft 官方关于 .NET Standard 的页面。

安装 Visual Studio 后,创建一个新的“ASP.NET Core 2.0 Web 应用程序”。然后选择“Web API”模板。

Create ASP.NET Core Web API

Entity Framework Core

我非常喜欢 Entity Framework 和 SQLite 的组合。它易于设置,可移植,并且可用于小型甚至中型网站。要使用它,请安装以下 NuGet 包

Microsoft.EntityFrameworkCore.Sqlite
Microsoft.EntityFrameworkCore.Tools

第二个包是创建迁移和数据库文件所必需的。

OData ASP.NET Core

实际上(截至 2018 年 1 月),在 ASP.NET Core 中使用 OData 的唯一方法是使用以下包

Microsoft.AspNetCore.OData

由于这仍是 Beta 版,您应该勾选“包含预发行版”复选框,以在 NuGet 包管理器中显示它。

Include prerelease packages in NuGet Package Manager

配置组件

实体框架

作为 EF 模型,我们使用 `Person` 类。其 `Name` 属性是搜索目标。

public class Person
{
  public int Id { get; set; }
  public string Name { get; set; }
  public DateTime Birthday { get; set; }
}

有关设置 `DbContext` 和 Entity Framework 的进一步配置,请参考附带的示例项目。

值得注意的是,示例项目将其数据库文件引用在“T:”驱动器上,这是一个虚拟驱动器。

为所有团队成员设置一个通用的虚拟驱动器至少有两个原因很有价值

  1. 每个机器上的所有项目配置都指向同一位置。
  2. 位于“我的文档”深处的长路径不再是问题。

您可以通过打开命令行并执行 `subst` 命令来设置一个通用驱动器。

subst.exe t: d:\temp

前提是“d:\temp”目录存在。

OData

根据 OData 版本 4.0 规范,数据过滤是通过 OData 函数 `contains` 实现的。

https://:51192/odata/Persons?$filter=contains(Name,'dom')

上述 HTTP 请求提供了数据库列 `Name` 中包含文本“dom”的所有值。然而,像“Dominik”或“KINGDOM”这样的值将找不到,除非使用 `tolower` OData 函数将其转换为小写。

https://:51192/odata/Persons?$filter=contains(tolower(Name),'dom')

重新创建 `ODataQueryOptions`、修改其 `Filter` 属性或自定义解析查询字符串并重新构建数据库查询是复杂且容易出错的。

因此,我们在其他 OWIN 中间件和操作过滤器开始处理之前,在 OWIN 管道中更新 `HttpContext.Request.QueryString`。在稍后修改查询字符串可能会对 Web 应用程序的稳定性造成风险。

正则表达式

我们将替换查询字符串的以下片段

contains(Name,

用以下片段

contains(tolower(Name),

使用以下正则表达式

Regex replaceToLowerRegex = new Regex(@"contains\((?<columnName>\w+),");
var s = replaceToLowerRegex.Replace(context.Request.QueryString.Value, @"contains(tolower($1),")

如果您需要替换数据库值以及搜索值,请使用以下正则表达式

Regex replaceToLowerRegex =
         new Regex(@"contains\((?<columnName>\w+),.*(?<value>(\'|%27).+(\'|%27))\)");
var s = replaceToLowerRegex.Replace(context.Request.QueryString.Value,
         @"contains(tolower(${columnName}),tolower(${value}))");

它会生成以下查询字符串

https://:51192/odata/Persons?$filter=contains(tolower(Name),tolower('Dom'))

OWIN 中间件

为了提高可读性,我们将正则表达式算法封装在自定义 OWIN 中间件 `ODataQueryStringFixer` 中。

public class ODataQueryStringFixer : IMiddleware
{
  private static readonly Regex ReplaceToLowerRegex =
    new Regex(@"contains\((?<columnName>\w+),");

  public Task InvokeAsync(HttpContext context, RequestDelegate next)
  {
    var input = context.Request.QueryString.Value;
    var replacement = @"contains(tolower($1),";
    context.Request.QueryString =
      new QueryString(ReplaceToLowerRegex.Replace(input, replacement));

    return next(context);
  }
}

然后,我们创建一个用户友好的扩展方法 `UseODataQueryStringFixer` 来将中间件锚定在 OWIN 管道中。

public static class ODataQueryStringFixerExtensions
{
  public static IApplicationBuilder UseODataQueryStringFixer(this IApplicationBuilder app)
  {
    return app.UseMiddleware<ODataQueryStringFixer>();
  }
}

最后一步是更新 `Startup` 类。

启动

在 `ConfigureServices` 方法中,我们需要使用 `services.AddSingleton` 将 `ODataQueryStringFixer` 声明为服务。

public void ConfigureServices(IServiceCollection services)
{
    // the database filename is stored in the appsettings.json
    var connectionString = Configuration.GetConnectionString(nameof(ApplicationDbContext));
    services.AddDbContext<ApplicationDbContext>(optionsAction =>
        optionsAction.UseSqlite(connectionString));

    // declare OWIN middleware as a service
    services.AddSingleton<ODataQueryStringFixer>();

    services.AddOData();

    services.AddMvc();
}

在 `Configure` 方法中,我们的 OWIN 中间件应该在 OData 中间件之前声明。

public void Configure(IApplicationBuilder app,
    IHostingEnvironment env, IServiceProvider serviceProvider)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    // create and populate DB
    serviceProvider.InitializeDb();

    // anchor OWIN middleware in the pipe
    app.UseODataQueryStringFixer();

    // configure OData model
    var builder = new ODataConventionModelBuilder(serviceProvider);
    builder.EntitySet<Person>("Persons").EntityType
        .OrderBy(
            nameof(Person.Name),
            nameof(Person.Birthday))
        .Filter(
            nameof(Person.Name));

    // configure OData routing
    app.UseMvc(routeBuilder =>
        routeBuilder.MapODataServiceRoute("OData", "odata", builder.GetEdmModel()));
}

就是这样。启动应用程序后,我们的 OWIN 中间件将使用 `tolower` OData 函数扩展查询字符串。

关注点

阅读了一些网络文章后,我曾专注于在 `ODataController` 的操作方法中重新创建 `ODataQueryOptions` 或重写 `EnableQueryAttribute`。

然而,以上任何一种方法都不如在请求开始时修改 `HttpContext.Request.QueryString` 那么简单。

我对 ASP.NET Core 2.0 的强大和灵活性印象深刻,就像大约 15 年前我对比 .NET 1.1 时对 .NET 2.0 印象深刻一样……加油,微软!

如果您喜欢这篇文章,请给它打 5 星。如果不喜欢,请在下面的评论中告诉我您的异议。 ;-)

历史

  • 2018 年 1 月 31 日:发布
© . All rights reserved.