Friday, March 23, 2012

How to Parameterized Query Interceptor in WCF Data Service


By default WCF Data Service do not support Parametrized QueryInterceptor in some rare scenarios feel that Parameterized Query Interceptor could have been a great solution. However we can leverage custom parameters to archive the same effect.


Solution 
Unfortunately there is no direct support of providing parameters in QueryInterceptor but there is possibility to add custom support. belwo is step by step solution

Actually Data Service class provide a base member OperationContext that contains information about Absolute URL is being passed all you need to do is Intercept this Absolute URI and use it in your Query Interceptor. High level steps are below.
Add two new data member in your data service class
DataServiceOperationContext OpsContext;
Dictionary<string, string> QueryParameters;
Add an override ‘OnStartProcessingRequest
‘ to Initialized OpsContext
protected override void OnStartProcessingRequest(ProcessRequestArgs args)
        {
            OpsContext = args.OperationContext;
            QueryParameters = GetQueryParameterBag(OpsContext);
            base.OnStartProcessingRequest(args);
        }
Implement a function to populate QueryParameters from incoming URL
Dictionary<string, string> GetQueryParameterBag(DataServiceOperationContext serviceOperationContext)
……….<See code details Belwo>
Implement a Parameter Verification function
[Optional]
void VerifyEmployeesInterceptorParameters(Dictionary<string, string> QueryParams)

Implement your interceptor that calls Parameter Verification Function and consume QueryParameters collection
[QueryInterceptor("vEmployees")]
        public Expression<Func<vEmployee, bool>> FilterEmplyee()
        {
            VerifyEmployeesInterceptorParameters(this.QueryParameters);

            string FirstName = QueryParameters["FirstName"];
            return o => (o.FirstName.StartsWith(FirstName));
        }

Once above steps are applied you can use URL Query parameters to bind with Query Interceptors i.e.
http://localhost:2545/WcfDataService1.svc/vEmployees?FirstName=Rober 
How does it works

Limitation of solution  
This solution will not work if entity paging is enabled i.e. custom parameters will not be forwarded to next page url , this is a data service limitation please refer to MSDN thread "Custom Query Options and Server Driven Paging" for more details

Complete Data Service Class File
Note :- Code Attached the the very bottom suggest standard approach i.e. putting Parameter collection object and parameter verification separate function but can be placed in Query interceptor itself  if there is no reuse required.
//-----------------------------------------------------------------------
// <copyright file="ServerHelpers.cs" company="None">
// None
// </copyright>
//-----------------------------------------------------------------------
namespace SampleDataService
{
    using System;
    using System.Collections.Generic;
    using System.Data.Services;
    using System.Data.Services.Common;
    using System.Linq;
    using System.ServiceModel.Web;
    using System.Web;
    using System.Linq.Expressions;



    [System.ServiceModel.ServiceBehavior(IncludeExceptionDetailInFaults = true)]
    public class WCFDataService : DataService<NORTHWNDEntities>
    {
        DataServiceOperationContext OpsContext;
        Dictionary<string, string> QueryParameters;

        // This method is called only once to initialize service-wide policies.
        public static void InitializeService(DataServiceConfiguration config)
        {
            // TODO: set rules to indicate which entity sets and service operations are visible, updatable, etc.
            // Examples:
            config.UseVerboseErrors = true;
            config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);
            config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
        }

        Dictionary<string, string> GetQueryParameterBag(DataServiceOperationContext serviceOperationContext)
        {
            Dictionary<string, string> ParameterBag = null;
            string query = serviceOperationContext.AbsoluteRequestUri.Query;
            if (!string.IsNullOrEmpty(query))
            {
                ParameterBag = new Dictionary<string, string>();
                query = query.Remove(0, 1);
                string[] param = query.Split('&');

                string paramName;
                string paramValue;

                foreach (var item in param)
                {
                    //Skip OData supported parameters [see Ronald`s Comment]
                    if(!item.StartWith("$") 
                    {
                       paramName = item.Substring(0, item.IndexOf("="));
                       paramValue = item.Substring(item.IndexOf("=") + 1);
                       ParameterBag.Add(paramName, paramValue);
                    }
                }
            }
            return ParameterBag;
        }

        protected override void OnStartProcessingRequest(ProcessRequestArgs args)
        {
            OpsContext = args.OperationContext;
            QueryParameters = GetQueryParameterBag(OpsContext);
            base.OnStartProcessingRequest(args);
        }
      
        void VerifyEmployeesInterceptorParameters(Dictionary<string, string> QueryParams)
        {
            if (QueryParams==null||!QueryParams.Keys.Contains("FirstName"))
            {
                throw new ArgumentException("Expecting parameter 'FirstName' but not found");
            }
        }

        [QueryInterceptor("Employees")]
        public Expression<Func<Employee, bool>> FilterEmplyee()
        {
            VerifyEmployeesInterceptorParameters(this.QueryParameters);

            string FirstName = QueryParameters["FirstName"];
            return o => (o.FirstName.StartsWith(FirstName));
        }
    }
}


3 comments:

  1. Nice post. You might want to wrap your loop that builds the parameter dictionary "if (item[0] != '$')" to exclude the standard oData parameters from the list. Also, I am providing an alternate method for passing parameters from the client below. this took me a while to find so it might help other readers.

    TraderList = new DataServiceCollection();
    var dataService = ServiceLocator.Current.GetInstance();
    var query = dataService.Traders.Expand("Trader")
    .AddQueryOption("traderType","pm")
    .OrderBy(o => o.ScreenName);
    TraderList.LoadAsync(query);

    ReplyDelete
  2. Hi Ronald
    Thanks for your feedback , updated the post with your input
    Ashwini

    ReplyDelete
  3. Custom software development specialists explore exactly of the company are on they need from their IT system in order to operate to their optimum.

    ReplyDelete