Filter/Search using Multiple Fields - ASP.NET MVC
I recommend you separate concerns and use an approach that the code in your controller be like this, simple, beautiful and extensible:
public ActionResult Index(ProductSearchModel searchModel)
{
var business = new ProductBusinessLogic();
var model = business.GetProducts(searchModel);
return View(model);
}
Benefits:
- You can put anything you need in your
ProductSearchModel
based on your requirements. - You can write any logic in
GetProducts
based on requirements. There is no limitation. - If you add a new field or option to search, your action and controller will remain untouched.
- If the logic of your search changes, your action and controller will remain untouched.
- You can reuse logic of search wherever you need to search on products, in controllers or even in other business logic.
- Having such
ProductSearchModel
, you can use it as model ofProductSearch
partial view and you can applyDataAnnotations
to it to enhance the model validation and help UI to render it usingDisplay
or other attributes. - You can add other business logic related to your product in that business logic class.
- Following this way you can have a more organized application.
Sample Implementation:
Suppose you have a Product
class:
public class Product
{
public int Id { get; set; }
public int Price { get; set; }
public string Name { get; set; }
}
You can create a ProductSearchModel
class and put some fields you want to search based on them:
public class ProductSearchModel
{
public int? Id { get; set; }
public int? PriceFrom { get; set; }
public int? PriceTo { get; set; }
public string Name { get; set; }
}
Then you can put your search logic in ProductBusinessLogic
class this way:
public class ProductBusinessLogic
{
private YourDbContext Context;
public ProductBusinessLogic()
{
Context = new YourDbContext();
}
public IQueryable<Product> GetProducts(ProductSearchModel searchModel)
{
var result = Context.Products.AsQueryable();
if (searchModel != null)
{
if (searchModel.Id.HasValue)
result = result.Where(x => x.Id == searchModel.Id);
if (!string.IsNullOrEmpty(searchModel.Name))
result = result.Where(x => x.Name.Contains(searchModel.Name));
if (searchModel.PriceFrom.HasValue)
result = result.Where(x => x.Price >= searchModel.PriceFrom);
if (searchModel.PriceTo.HasValue)
result = result.Where(x => x.Price <= searchModel.PriceTo);
}
return result;
}
}
Then in your ProductController
you can use this way:
public ActionResult Index(ProductSearchModel searchModel)
{
var business = new ProductBusinessLogic();
var model = business.GetProducts(searchModel);
return View(model);
}
Important Note:
In a real world implementation, please consider implementing a suitable Dispose
pattern for your business class to dispose db context when needed. For more information take a look at Implementing a Dispose method or Dispose Pattern.
Conditional filtering
.ToList()
, .First()
, .Count()
and a few other methods execute the final LINQ query. But before it is executed you can apply filters just like that:
var stocks = context.Stocks.AsQueryable();
if (batchNumber != null) stocks = stocks.Where(s => s.Number = batchNumber);
if (name != null) stocks = stocks.Where(s => s.Name.StartsWith(name));
var result = stocks.ToList(); // execute query
WhereIf LINQ Extension
Simple WhereIf
can significantly simplify code:
var result = db.Stocks
.WhereIf(batchNumber != null, s => s.Number == batchNumber)
.WhereIf(name != null, s => s.Name.StartsWith(name))
.ToList();
WhereIf implementation. It's a simple extension method for IQueryable
:
public static class CollectionExtensions
{
public static IQueryable<TSource> WhereIf<TSource>(
this IQueryable<TSource> source,
bool condition,
Expression<Func<TSource, bool>> predicate)
{
if (condition)
return source.Where(predicate);
else
return source;
}
}
Non-WhereIf LINQ way (Recommended)
WhereIf
provides more declarative way, if you don't want to use extensions you can just filter like that:
var result = context.Stocks
.Where(batchNumber == null || stock.Number == batchNumber)
.Where(name == null || s => s.Name.StartsWith(name))
.ToList();
It gives an exact same effect as WhereIf
and it will work faster as runtime will need to build just one ExpressionTree instead of building multiple trees and merging them.