I'm trying to use OData V6.6.0 $search functionality. I initially had version 5.2 which I updated to version 6.6.0. However, I still am not able to use $search. When I use it in url
odata/Prescribers?$inlinecount=allpages&$top=20&$search=TEST';
I get this error message 'The query parameter '$search' is not supported.'
Do I need to do something else in order to add $search functionality. Btw, I'm also using EnableQuery in my Controller like this.
[EnableQuery(AllowedQueryOptions = System.Web.Http.OData.Query.AllowedQueryOptions.All)]
public IQueryable<ContactList> Get()
{
return dbContext.ContactsList.AsQueryable();
}
I figured it out. If substringof is used in the filter then it returns the records of value containing the string. I used $filter with 'or' operator to check multiple columns.
'&$filter=substringof(\'' + key + '\', NPI)' +
'or substringof(\'' + key + '\', Zip)' +
'or substringof(\'' + key + '\', PrescriberName)';
This displayed all the records that contains the string in the filter.
Although this is kind of old, I wanted to give you my take of an implementation of a "$search to $filter" mapping based on an AllowSearchAttribute on the model. It is implemented as an attribute derived from EnableQueryAttribute
. The "magic" is to take the $search
part of the query, convert it to a $filter
clause, and remove it from the parameters before further processing is done by the infrastructure. This is achieved by replacing request.Properties[HttpPropertyKeys.RequestQueryNameValuePairsKey]
with a changed array. This is the cache, from which later processing will fetch the query fields (see request.GetQueryNameValuePairs
).
public class EnableQueryWithSearchAttribute : EnableQueryAttribute
{
public Type ModelType { get; set; }
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
var request = actionExecutedContext.Request;
var query = request.GetQueryNameValuePairs().ToList();
var searchParam = query.FirstOrDefault(q => q.Key == "$search");
if (!String.IsNullOrWhiteSpace(searchParam.Key))
{
if (!String.IsNullOrWhiteSpace(searchParam.Value))
{
string filterString = null;
var filter = query.FirstOrDefault(q => q.Key == "$filter");
if (!String.IsNullOrWhiteSpace(filter.Key))
{
filterString = filter.Value;
}
var odataSearchValue = searchParam.Value.Replace("'", "''");
var props = this.ModelType
.GetProperties()
.Where(pi => pi.GetCustomAttribute<AllowSearchAttribute>() != null)
.Select(pi => pi.Name)
.ToList();
if (props.Count > 0)
{
if (!String.IsNullOrWhiteSpace(filterString))
{
filterString = "(" + filterString + ") and ";
}
filterString += "(contains(" +
String.Join(", '" + odataSearchValue + "') or contains(", props) + ", '" +
odataSearchValue + "'))";
}
request.Properties[HttpPropertyKeys.RequestQueryNameValuePairsKey] =
query.Where(q => q.Key != "$search" && q.Key != "$filter").Concat(new[] { new KeyValuePair<string, string>("$filter", filterString) }).ToArray();
}
else
{
request.Properties[HttpPropertyKeys.RequestQueryNameValuePairsKey] =
query.Where(q => q.Key != "$search").ToArray();
}
}
base.OnActionExecuted(actionExecutedContext);
}
}
The AllowSearchAttribute is a descriptive attribute with no extra info.
[AttributeUsage(AttributeTargets.Property)]
public class AllowSearchAttribute : Attribute { }
On the model class, you can specify which properties should be searched, by applying the [AllowSearch]
attribute to the desired properties. In the example, only Title
and Description
will be searched:
public class MyModel
{
public int Id { get; set; }
[AllowSearch]
public string Title { get; set; }
[AllowSearch]
public string Description { get; set; }
public string SomethingNotSearchable { get; set; }
}
And on the method where you typically apply the [EnableQuery]
attribute, you will use the derived one and specify the model type:
[EnableQueryWithSearch(EnsureStableOrdering = true, ModelType = typeof(MyModel))]
[HttpGet]
public IQueryable<MyModel> Get()
{
// your code here.
}
You can now call your OData endpoint with a $search parameter, which will be mapped to a corresponding $filter parameter internally. The current implementation always uses a contains(...)
operation, but you could easily enhance the implementation with custom [AllowSearch] parameters, which will issue other operations.
Hope this helps someone. I couldn't find any other implementation of this sort anywhere, so I thought I'd share.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With