One of the requirements of Suteki Shop is that admin users should be able to create various new entities (categories, products, content etc) before they are published on the public web site. To this end I created a simple interface, IActivatable:
public interface IActivatable { bool IsActive { get; set; } }
As you can see it's got a single property 'IsActive'. Any entity that needs to be active/inactive implements it. So my product class' partial class definition looks like this:
public partial class Product : IOrderable, IActivatable, IUrlNamed { .... }
I'll explain IOrderable and IUrlNamed later, but they do similar things. Each entity has a matching table in the database and each entity that implements IActivatable also has a bit column IsActive. Here are Category and Product:
When I drag these tables onto the LINQ-to-SQL design surface, Product and Category classes are created with IsActive properties. I can make their matching partial classes implement IActivatable and the designer IsActive property satisfies the implementation requirement.
Now I can write a reusable LINQ query extension method for any IActivatable to filter only active items:
public static IQueryable<T> Active<T>(this IQueryable<T> items) where T : IActivatable { return items.Where(item => item.IsActive); }
Now every time I need a list of active items I can just chain the query with Active(),
var products = productRepository.GetAll().Active();
Remember the IOrderable and IUrlNamed interfaces that the Product partial class implemented? They work in exactly the same way. I explained IOrderable in a previous post, it's used to create a generic service to order and re-order lists of entities. IUrlNamed provides a generic way of querying entities given a unique string from the URL. For example, Suteki Shop has nice URLs for products: http://sutekishop.co.uk/product/East_End_Trilby. So there's an extension method:
public static T WithUrlName<T>(this IQueryable<T> items, string urlName) where T : IUrlNamed { T item = items .SingleOrDefault(i => i.UrlName.ToLower() == urlName.ToLower()); if (item == null) throw new ApplicationException("Unknown UrlName '{0}'".With(urlName)); return item; }
That allows us to look up products like this:
public ActionResult Item(string urlName) { Product product = productRepository.GetAll().WithUrlName(urlName); return View("Item", ShopView.Data.WithProduct(product)); }