Search code examples
.netentity-frameworkasp.net-web-apiodatabreeze

How can I manually execute Breeze filters in Web Api?


I want to use some external server-side logic to modify properties on the results of a query. To do this, I'll need to apply the Breeze query options, modify the result set, and return it.

I know essentially how I can apply OdataQueryOptions to my query, but I don't want to miss out on all the things that BreezeJS does that Web Api's OData doesn't. For example, I want to keep inlineCount.

How can I do this? Is there some way to hook into Breeze's query modifying code?

In case it matters, I'm using Entity Framework 6 and Web Api 2.


Solution

  • Ok, I'm not sure if there's a better way (because this seems like a lot of work for what seems like it would be a common use case), but here's how I solved this.

    I inherited from the QueryHelper class to modify the PostExecuteQuery method to execute a delegate.

    public class ExtendedQueryHelper : QueryHelper
    {
        public ExtendedQueryHelperOptions Options { get; set; }
    
        public ExtendedQueryHelper(ODataQuerySettings querySettings) : base(querySettings)
        {}
    
        public override IEnumerable PostExecuteQuery(IEnumerable queryResult)
        {
            if (Options != null && Options.PostExecuteQueryHandler != null)
            {
                return Options.PostExecuteQueryHandler(queryResult);
            }
    
            return base.PostExecuteQuery(queryResult);
        }
    }
    

    The delegate is defined in a class called ExtendedQueryHelperOptions

    public class ExtendedQueryHelperOptions
    {
        private const string EXTENDED_QUERY_HELPER_OPTIONS_KEY = "EXTENDED_QUERY_HELPER_OPTIONS_KEY";
        public delegate IEnumerable PostExecuteQueryDelegate(IEnumerable queryResult);
    
        public PostExecuteQueryDelegate PostExecuteQueryHandler { get; set; }
    
        public void InjectIntoRequest(HttpRequestMessage request)
        {
            request.Properties.Add(EXTENDED_QUERY_HELPER_OPTIONS_KEY, this);
        }
    
        public static ExtendedQueryHelperOptions GetFromRequest(HttpRequestMessage request)
        {
            object options;
            request.Properties.TryGetValue(EXTENDED_QUERY_HELPER_OPTIONS_KEY, out options);
    
            return (ExtendedQueryHelperOptions)options;
        }
    }
    

    In order to set these options, I had to inherit from BreezeQueryableAttribute and inject these options when the QueryHelper is being created:

    public class ExtendedBreezeQueryableAttribute : BreezeQueryableAttribute
    {
        protected HttpRequestMessage Request { get; set; }
    
        public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)
        {
            Request = actionContext.Request;
            base.OnActionExecuting(actionContext);
        }
    
        protected override QueryHelper NewQueryHelper()
        {
            var queryHelper = new ExtendedQueryHelper(GetODataQuerySettings());
            queryHelper.Options = ExtendedQueryHelperOptions.GetFromRequest(Request);
    
            return queryHelper;
        }
    }
    

    Now I can inject code to be run on the filtered results like this:

    [BreezeController]
    public class BreezeController : BaseController
    {
        //...
    
        [HttpGet]
        [ExtendedBreezeQueryable]
        public IQueryable<Foo> Foos()
        {
            var options = new ExtendedQueryHelperOptions
            {
                PostExecuteQueryHandler = delegate(IEnumerable results) {
                    // This code will be run after the querying has been
                    // applied by Breeze
                    var foos = results.Cast<Foo>().ToList();
    
                    foreach (var foo in foos)
                    {
                        foo.ComputedProperty = ComputeSomething();
                    }
    
                    return foos;
                }
            };
    
            // Inject these options into the Request, so the ExtendedBreezeQueryableAttribute
            // can get to them later
            options.InjectIntoRequest(Request);
            return Db.Foos;
        }
    }