【问题标题】:ASP .NET MVC 4 WebApi: Manually handle OData queriesASP .NET MVC 4 WebApi:手动处理 OData 查询
【发布时间】:2012-06-02 14:58:04
【问题描述】:

我有一个使用 ASP .NET MVC 4 提供的 WebAPI 制作的 Web 服务。 我知道 WebAPI 工作的层会自动处理OData Queries(例如$filter$top$skip),但是如果我想自己处理过滤呢?

I don't simply return data from my database,但我还有另一层,它添加了一些属性、进行一些转换等。因此,查询我的所有数据、转换它们并将它们返回到 WebAPI 类以进行 OData 过滤还不够好。这当然非常慢,而且通常是一个糟糕的想法。

那么有没有办法将 OData 查询参数从我的 WebAPI 入口点传播到我调用的函数以获取和转换数据?

例如,对/api/people?$skip=10&$top=10 的 GET 将调用服务器:

public IQueryable<Person> get() {
    return PersonService.get(SomethingAboutCurrentRequest.CurrentOData);
}

PersonService:

public IQueryable<Person> getPeople(var ODataQueries) {
    IQueryable<ServerSidePerson> serverPeople = from p in dbContext.ServerSidePerson select p;
    // Make the OData queries
    // Skip
    serverPeople = serverPeople.Skip(ODataQueries.Skip);
    // Take
    serverPeople = serverPeople.Take(ODataQueries.Take);
    // And so on
    // ...

    // Then, convert them
    IQueryable<Person> people = Converter.convertPersonList(serverPeople);
    return people;
}

【问题讨论】:

    标签: rest odata asp.net-mvc-4 asp.net-web-api


    【解决方案1】:

    我刚刚偶然发现这篇旧帖子,我正在添加这个答案,因为现在自己处理 OData 查询非常容易。这是一个例子:

    [HttpGet]
    [ActionName("Example")]
    public IEnumerable<Poco> GetExample(ODataQueryOptions<Poco> queryOptions)
    {
        var data = new Poco[] { 
            new Poco() { id = 1, name = "one", type = "a" },
            new Poco() { id = 2, name = "two", type = "b" },
            new Poco() { id = 3, name = "three", type = "c" }
        };
    
        var t = new ODataValidationSettings() { MaxTop = 2 };
        queryOptions.Validate(t);
    
        //this is the method to filter using the OData framework
        //var s = new ODataQuerySettings() { PageSize = 1 };
        //var results = queryOptions.ApplyTo(data.AsQueryable(), s) as IEnumerable<Poco>;
    
        //or DIY
        var results = data;
        if (queryOptions.Skip != null) 
            results = results.Skip(queryOptions.Skip.Value);
        if (queryOptions.Top != null)
            results = results.Take(queryOptions.Top.Value);
    
        return results;
    }
    
    public class Poco
    {
        public int id { get; set; }
        public string name { get; set; }
        public string type { get; set; }
    }
    

    【讨论】:

    【解决方案2】:

    来自 URL 的查询被转换为 LINQ 表达式树,然后针对您的操作返回的 IQueryable 执行该树。您可以分析表达式并以您想要的任何方式提供结果。缺点是您需要实现 IQueryable,这并不容易。如果您有兴趣,请查看此博客文章系列:http://blogs.msdn.com/b/vitek/archive/2010/02/25/data-services-expressions-part-1-intro.aspx。它谈到了 WCF 数据服务,但 Web API 使用的过滤器表达式将非常相似。

    【讨论】:

      【解决方案3】:

      使用 Web-api 的一种方法是使用客户消息处理程序 http://www.asp.net/web-api/overview/working-with-http/http-message-handlers

      编写如下自定义处理程序:

      public class CustomHandler : DelegatingHandler
          {
              protected override Task<HttpResponseMessage> SendAsync(
                  HttpRequestMessage request, CancellationToken cancellationToken)
              {
                  return base.SendAsync(request, cancellationToken).ContinueWith(
                      (task) =>
                      {
                          HttpResponseMessage response = task.Result;
                          var persons = response.Content.ReadAsAsync<IQueryable<Person>>().Result;
                          var persons2 = new List<Person>(); //This can be the modified model completely different
                          foreach (var item in persons)
                          {
                              item.Name = "changed"; // here you can change the data
                              //persons2.Add(....); //Depending on the results modify this custom model
                          }
                          //overwrite the response
                          response = new HttpResponseMessage<IEnumerable<Person>>(persons2); 
                          return response;
                      }
                  );
              }
          }
      

      在 global.asax.cs 中注册

      应用类中的方法:

      static void Configure(HttpConfiguration config)
       {
           config.MessageHandlers.Add(new CustomHandler()); 
       }
      
      protected void Application_Start()
      {
           ....
           .....
           //call the configure method
           Configure(GlobalConfiguration.Configuration);
       }
      

      【讨论】:

      • 恐怕这是不可行的,因为数据库类和返回类完全不同,并且(由于某些原因)不可能在自定义处理程序中进行转换。
      • 然后更改响应。上面编辑。 var persons2 = new List&lt;Person&gt;(); //This can be the modified model completely different response = new HttpResponseMessage&lt;IEnumerable&lt;Person&gt;&gt;(persons2);
      • 这里最大的问题是您没有通过过滤来正确使用 IQueryable。相反,您正在枚举完整结果并手动构建新结果集。使用任何底层 linq 提供程序都会失败。
      【解决方案4】:

      我用 WCF 数据服务和 asp.net mvc 3.5 做了类似的事情,但它有点笨拙。

      第一步是重写路径,以便跳过和顶部选项不会被应用两次,一次由您应用,一次由运行时应用。

      我使用 HttpModule 进行了重写。在您的 BeginRequest 方法中,您将拥有如下代码:

      HttpApplication app = (HttpApplication)sender;
      if (HttpContext.Current.Request.Path.Contains(YOUR_SVC))
      {
          if (app.Request.Url.Query.Length > 0)
          {
              //skip questionmark
              string queryString = app.Request.Url.Query.Substring(1) 
                          .Replace("$filter=", "filter=")
                          .Replace("$orderby=", "orderby=")
                          .Replace("$top=", "top=")
                          .Replace("$skip=", "skip=");
      
                      HttpContext.Current.RewritePath(app.Request.Path, "", queryString);
          }
      }
      

      然后只需检查查询字符串并提取您需要的参数。

      if (HttpContext.Current.Request.QueryString["filter"] != null)
          var filter = HttpContext.Current.Request.QueryString["filter"] as string;
      

      然后拆分过滤器字符串并将其解析为 sql 语句或任何其他 db 命令。在我的案例中,我使用的是 NHibernate。我还能够限制我支持的过滤器命令,这使事情变得更容易。例如,我没有进行分组。主要是比较运算符。

      OData.org 上有一个过滤器运算符列表。通过“and”和“or”将字符串拆分为单独的子句。用空格分割每个子句,你应该得到一个 3 元素数组,其中属性名称在 [0] 中,运算符在 [1] 中,值在 [2] 中。

      子句将以 Person 的形式出现,但我假设您能够将它们转换为能够从数据库中提取正确结果的东西。

      我最终放弃了这种方法,因为它不适用于 POSTS。我必须编写自己的 Linq 提供程序,但编写自己的过滤字符串解析器更容易理解。

      【讨论】:

      • 这可能正是我想要的。谢谢!!!有没有办法自动将过滤器应用于 IQueryable 或者我必须手动完成所有操作?
      • 不幸的是手动操作。此方法基本上只是将过滤器转换为字符串数组。在 NH 中,这还不错,因为动态构建查询很容易。
      • HttpContext.Current.RewritePath(app.Request.Path, "", queryString); 似乎不起作用。路径已更改,但ApiController 顶部的层看起来仍在处理旧的$skip$top。你知道为什么吗?
      • 对不起,我没有。这可能是 WebAPI 和 WCF 数据服务之间的差异之一。您可以尝试切换到数据服务。唯一困难的部分是我认为您不需要的自定义 linq 提供程序。我用来解决跳过和顶部问题的另一种(kludgy)方法是将一堆虚拟项目插入到我创建的列表中。然后,当它跳过时,它将从您的真实数据开始。不过,这将无法处理扩展(至少在没有大量工作的情况下不会)。
      猜你喜欢
      • 2018-07-24
      • 1970-01-01
      • 1970-01-01
      • 2016-03-14
      • 2016-06-22
      • 2017-06-18
      • 1970-01-01
      • 2011-03-18
      • 1970-01-01
      相关资源
      最近更新 更多