简体   繁体   English

ASP .NET MVC 4 WebApi:手动处理OData查询

[英]ASP .NET MVC 4 WebApi: Manually handle OData queries

I have a Web Service made using the WebAPI provided by ASP .NET MVC 4. I know that the layer on top of which WebAPI works automatically handles OData Queries (such as $filter , $top , $skip ), but what if I want to handle the filtering by myself? 我有一个使用ASP .NET MVC 4提供的WebAPI制作的Web服务。我知道WebAPI工作之上的层自动处理OData查询 (例如$filter$top$skip ),但是如果我想要的话自己处理过滤?

I don't simply return data from my database , but I have another layer which adds some properties, makes some conversions etc. So querying ALL of my data, converting them and returning them to the WebAPI class for OData filtering isn't just good enough. 我不是简单地从我的数据库返回数据 ,但我有另一个层添加了一些属性,进行了一些转换等等。因此查询我的所有数据,转换它们并将它们返回到WebAPI类进行OData过滤不仅仅是好的足够。 It's of course terribly slow, and generally a crappy idea. 它当然非常慢,通常是一个糟糕的想法。

So is there a way to propagate the OData query parameters from my WebAPI entry point to the functions I call to get and convert the data? 那么有没有办法将OData查询参数从我的WebAPI入口点传播到我调用的函数来获取和转换数据?

For example, a GET to /api/people?$skip=10&$top=10 would call on the server: 例如,GET到/api/people?$skip=10&$top=10会调用服务器:

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

And in PersonService : 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;
}

I just stumbled across this old post and I'm adding this answer as it's now very easy to handle the OData queries yourself. 我只是偶然发现了这个老帖子,我正在添加这个答案,因为现在很容易自己处理OData查询。 Here's an example: 这是一个例子:

[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; }
}

The query from the URL gets translated into a LINQ expression tree which is then executed against the IQueryable your operation returns. 来自URL的查询被转换为LINQ表达式树,然后针对您的操作返回的IQueryable执行。 You can analyze the expression and provide the results in any way you want. 您可以分析表达式并以您想要的任何方式提供结果。 The downside is that you need to implement IQueryable which is not super easy. 缺点是你需要实现IQueryable,这不是一件容易的事。 Take a look at this blog post series if you're interested: http://blogs.msdn.com/b/vitek/archive/2010/02/25/data-services-expressions-part-1-intro.aspx . 如果您有兴趣,请查看此博客文章系列: http//blogs.msdn.com/b/vitek/archive/2010/02/25/data-services-expressions-part-1-intro.aspx It talks about WCF Data Services, but the filter expressions used by the Web API will be very similar. 它讨论了WCF数据服务,但Web API使用的过滤器表达式将非常相似。

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

Write a custom handler like below: 编写如下的自定义处理程序:

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;
                }
            );
        }
    }

Register in global.asax.cs 在global.asax.cs中注册

Method in application class: 应用类中的方法:

static void Configure(HttpConfiguration config)
 {
     config.MessageHandlers.Add(new CustomHandler()); 
 }

protected void Application_Start()
{
     ....
     .....
     //call the configure method
     Configure(GlobalConfiguration.Configuration);
 }

I did something like this with WCF Data Services and asp.net mvc 3.5 but it was a bit of a kludge. 我使用WCF数据服务和asp.net mvc 3.5做了类似的事情,但它有点像kludge。

The first step is to rewrite the path so that the skip and top options don't get applied twice, once by you and once by the runtime. 第一步是重写路径,以便跳过和顶部选项不会被应用两次,一次由您运行,一次由运行时应用。

I did the rewriting with an HttpModule. 我用HttpModule进行了重写。 In your BeginRequest method you'd have code like this: 在您的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);
    }
}

Then just examine the query string and pluck out the parameters you need. 然后只需检查查询字符串并选择所需的参数。

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

Then split the filter string and parse it into a sql statement or any other db commands. 然后拆分过滤器字符串并将其解析为sql语句或任何其他db命令。 I was using NHibernate in my case. 在我的案例中我使用的是NHibernate。 I was also able to limit what filter commands I supported which made things easier. 我还能够限制我支持的过滤器命令,这使得事情变得更容易。 I didn't do groupings for example. 例如,我没有进行分组。 Mostly just the comparison operators. 主要是比较运算符。

There's a list of filter operators at OData.org . OData.org上有一个过滤器运算符列表。 Split the string by "and" and "or" into individual clauses. 将字符串“and”和“or”拆分为单独的子句。 Split each clause by a space and you should get a 3 element array with the property name in [0] the operator in [1] and the value in [2]. 用空格分割每个子句,你应该得到一个3元素数组,其中[0]中的属性名称为[1]中的运算符,[2]中的值。

The clauses will be in terms of a Person but I'm assuming you'll be able to convert them into something that will pull the right results out of the db. 这些条款将以Person为单位,但我假设您将能够将它们转换为可以从db中获取正确结果的内容。

I ended up abandoning this approach since it won't work for POSTS. 我最终放弃了这种方法,因为它不适用于POSTS。 I had to write my own Linq provider but writing my own filter string parser made that easier to understand. 我必须编写自己的Linq提供程序,但编写自己的过滤器字符串解析器使其更容易理解。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM