4

I have a standard ASP.NET MVC 3 Controller with an action that has following signature: public ActionResult Index(int? page, string sort, string sortDir) My view is using WebGrid so parameters are produced by it automatically.

Next I use Dynamic Expressions API (aka Dynamic LINQ) to convert parameters into query. Example:

var customerSummary = CustomerManager.CustomerRepository.GetQuery()
      .OrderBy(sort + " " + sortDir)
      .Select(c => new CustomerSummaryViewModel()
                  {
                      Id = c.Id,
                      Name = c.Name,
                      IsActive = c.IsActive,
                      OrderCount = c.Orders.Count
                  })
      .Skip(page.Value - 1 * 10) //10 is page size
      .Take(10)
      .ToList();

The Goal

What I would like to do is to use Dynamic Expressions API itself to validate parameters for sorting (and perhaps create a valid lambda). For example, I'd like to use DynamicExpression.Parse() or DynamicExpression.ParseLambda() methods to see if they produce ParseException, in which case I can replace erroneous params with default (e.g. sort by name ascending "Name ASC")...

The Problem

The problem is that IQueryable extensions take only a string If I wanted to use ParseLambda and then feed it to .OrderBy I cannot use direction (it takes in only property name). For example, I can do this:

var se = DynamicExpression.ParseLambda<Customer, string>("Name"); // now I can use  .OrderBy(se) which is same as .OrderBy(c=>c.Name)

but not this

var se = DynamicExpression.ParseLambda<Customer, string>("Name DESC"); 

Recap

I would like to use Dynamic LINQ to 1) validate and 2) build predicates (for sorting) based on action parameters

zam6ak
  • 7,229
  • 11
  • 46
  • 84

1 Answers1

0

I'm not very familiar with Dymaic LINQ but you can do the following:

var customerSummary = CustomerManager.CustomerRepository.GetQuery();

if ("desc".Equals(sortDir, StringComparison.CurrentCultureIgnoreCase))
   customerSummary = customerSummary.OrderByDescending(sort);
else
   customerSummary = customerSummary.OrderBy(sort);

var pageNumber = page.GetValueOrDefault();
if (pageNumber < 1)
   pageNumber = 1;

customerSummary = customerSummary
   .Select(c => new CustomerSummaryViewModel()
      {
         Id = c.Id,
         Name = c.Name,
         IsActive = c.IsActive,
         OrderCount = c.Orders.Count
      })
   .Skip((pageNumber - 1) * 10) 
   .Take(10)
   .ToList();

I done something similar (except i used raw Expressions) in my project but gone even further.

I've created a base ViewModel like this:

class TableViewModel 
{
   public string SortColumn { get; set; }
   public bool IsAsc { get; set; }
   public int? PageNumber { get; set; }
}

And created a helper method that do all the paging/sorting work. Signature is like this:

public static IQueryable<T> TableHelper(this IQueryable<T> source, TableViewModel model) { ... }

And when i need to receive data from my table control and return a requested piece of data, controller action looks like this:

public ActionResult Index(TableViewModel model)
{
   var data = _productRepository.AsQueryable().TableHelper(model);

   ... //Operation on data
}

Common thing that before and after call of the helper you are free to apply any filtering or smth.

It's very convinient.

When i need to extend my ViewModel, i inherit it and add new members to child model.

UPD: If you decide to stay DLINQ, try the following signature - OrderBy("Name", "ascending");

UPD2: If you want to validate your sort parameter, i think, reflection is the only choise. Something like this:

bool doSort = typeof(Product).GetProperty(sort, BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy /*Or whatever flags you need*/) != null

Going this way, you should apply a OrderBy/OrderByDescending logic only if doSort is true. Otherwise, just skip sorting or apply any default logic.

In my oppinion, the more functionality you want from your code, the less DLINQ seems to be suitable for it. Once PropertyInfo is obtained the next reasonable step could be to use it in expression. Once we do it, where would be a place for DLINQ? :)

I agree that the expression and reflection code is very ugly in actions, but moving it outside, for example, to ExtensionMethod, as in my case, or in NonAction mehod of a controller base class, saves your eyes from seeing it:)

ILya
  • 2,670
  • 4
  • 27
  • 40
  • I'd like to use Dynamic LINQ (as pew question). But besides that how do you validate "sort" param in your example? How do you make sure that "sort" is a valid property on your underlaying model? :) – zam6ak Apr 09 '12 at 19:29
  • I have updated the answer (see UPD2). For ordering with DLINQ, haven't you seen my first UPD? Does it work for you? – ILya Apr 10 '12 at 00:00
  • BTW, what would DLINQ do if you pass a wrong field name? Will it throw an exception? – ILya Apr 10 '12 at 00:23
  • Yes Dynamic LINQ has Parse methods that throw ParseException if expression is bad. Your approach using reflection works and I had it working as such before, but my question is *how* to do it with Dynamic LINQ... – zam6ak Apr 10 '12 at 15:12