Jakiś czas temu zastanawiałem się nad OData. Potrzebowałem przekazać z aplikacji do WebAPI jakiś prosty filtr. OData wydało mi się trochę przerostem formy nad treścią. Więc stworzyłem własny przerost formy nad treścią :D
Rozwiązanie jest proste. Stworzyłem na początek klasę QueryFilter wraz z pochodnymi:
public enum OrderDirection
{
NoOrder,
Ascending,
Descending
}
public class OrderInfo
{
/// <summary>
/// Direction of order. NoOrder means that no ordering is done.
/// </summary>
public OrderDirection Direction { get; set; }
/// <summary>
/// Ordering field
/// </summary>
public string FieldName { get; set; }
}
/// <summary>
/// Use this class to filter through WebAPI.
/// </summary>
/// <typeparam name="T">Model that is going to be filtered</typeparam>
public class QueryFilter<T> where T: class
{
/// <summary>
/// "Where" condition. This is lambda expression.
/// </summary>
public Expression<Func<T, bool>> Where { get; set; }
/// <summary>
/// Order by
/// </summary>
public OrderInfo OrderBy { get; set; }
/// <summary>
/// How many records should be skipped. Mainly for pagination
/// </summary>
public int Skip { get; set; }
/// <summary>
/// How many records should be retrieved. Mainly for pagination
/// </summary>
public int Max { get; set; }
public QueryFilter()
{
OrderBy = new OrderInfo();
OrderBy.Direction = OrderDirection.NoOrder;
}
public static QueryFilter<T> FromDto(QueryFilterDto dto, Expression<Func<T, bool>> where)
{
QueryFilter<T> result = new QueryFilter<T>();
result.Max = dto.Max;
result.OrderBy = dto.OrderBy;
result.Skip = dto.Skip;
result.Where = where;
return result;
}
public void CreateWhere(Expression<Func<T, bool>> where)
{
Where = where;
}
public void CreateOrder(OrderDirection direction, string fieldName)
{
OrderBy.Direction = direction;
OrderBy.FieldName = fieldName;
}
}
/// <summary>
/// This is helper class that is send between client and WebApi. Use generic QueryFilter instead
/// </summary>
public class QueryFilterDto
{
public string ModelName { get; set; }
public string Where { get; set; }
public OrderInfo OrderBy { get; set; }
public int Skip { get; set; }
public int Max { get; set; }
}
Następnie wysyłam to JSonem do WebApi.
Po stronie WebApi najważniejsze jest to:
Expression<Func<Order, bool>> where = await Utils.ExpressionFromStr<Order>(filterDto.Where);
ExpressionFromStr wygląda tak:
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;
public async static Task<Expression<Func<T, bool>>> ExpressionFromStr<T>(string expressionStr)
{
var options = ScriptOptions.Default.AddReferences(typeof(T).Assembly);
return await CSharpScript.EvaluateAsync<Expression<Func<T, bool>>>(expressionStr, options);
}
Oczywiście wymaga to Roslyn. Na szczęście te usingi można prosto pobrać z NuGeta.
Mając takie lambda expression można już przekazać je do jakiegoś ORMa albo LinqToSql.