I’ve been doing a ton of work with ASP.NET MVC lately. Which of course has me thinking how I design my code structure since MVC is all about separation of concerns. I really like the idea of POCO (Plain Old Clr Objects) because it allows easily changing your access tier without having to rewrite a lot of your existing code. After looking through Kigg’s source code I was given a fantastic idea bout how to handle this… just user interfaces. So for this blog post I’ll demo how they do it so you don’t have to try to follow the IoC madness they implement.
To start off we need to create our projects. I’m going to name this project IEntityDemo (original, I know). I’ll be creating 2 other projects. Both of which will be class library projects one which will contain our interface contracts and one that will contain the LinqToSql implementation. The names of these projects will be IEntityDemo, IEntityDemo.Infrastructure (contracts, abstractions), and IEntityDemo.Repository.LinqToSql (implementation).
Here are some interfaces to get us started (in the IEntityDemo.Infrastructure project)
namespace IEntityDemo.Model
{
public interface ICategory
{
int ID { get; set; }
string Name { get; set; }
}
}
namespace IEntityDemo.Model
{
public interface IProduct
{
int ID { get; set; }
string Name { get; set; }
decimal Price { get; set; }
ICategory Category { get; }
}
}
These are our 2 entities we’ll be using and next is our repository contracts.
namespace IEntityDemo.Repository
{
public interface IRepository<TEntity>
{
void Add(TEntity entity);
void Delete(TEntity entity);
ICollection<TEntity> GetAll();
}
}
namespace IEntityDemo.Repository
{
public interface IProductRepository : IRepository<IProduct>
{
IProduct GetByID(int id);
}
}
namespace IEntityDemo.Repository
{
public interface ICategoryRepository : IRepository<ICategory>
{
ICategory GetByID(int id);
}
}
So far, simple. Of course you can feel free to modify this as you see fit. Next our gold nugget of DDD UnitOfWork. Now I’m by no means saying I’m a DDD expert but I do try to use some of the ideas from the use of DDD and the idea of a unit of work is one of them.
namespace IEntityDemo.Repository
{
public interface IUnitOfWork : IDisposable
{
void Commit();
}
}
Next was the database layout, and I created it as so.
Just 2 tables with a basic FK. And the LINQ to SQL layer…
The only thing really left to do is our implementation using LINQ to SQL. Normally I’m very strict about naming conventions but for this to work without jumping through hoops, I decided to add sql_ prefix to my tables. Next in line is the Repository implementation. One thing you have to remember to do is to setup your namespaces in your LINQ to SQL model.
public class Repository<TAbstract, TConcrete> : IRepository<TAbstract>
where TConcrete : class
{
public Repository(IEntityDemoDataContext db)
{
DB = db;
}
protected internal IEntityDemoDataContext DB { get; private set; }
public void Add(TAbstract entity)
{
DB.GetTable<TConcrete>().InsertOnSubmit(entity as TConcrete);
}
public void Delete(TAbstract entity)
{
DB.GetTable<TConcrete>().DeleteOnSubmit(entity as TConcrete);
}
public ICollection<TAbstract> GetAll()
{
return DB.GetTable<TConcrete>().Cast<TAbstract>().ToList();
}
protected internal IQueryable<TConcrete> GetTable()
{
return DB.GetTable<TConcrete>();
}
}
}
Of course you should abstract out the data context using a common interface but to keep this short I’m not. Do as I say, not as I do!
Now it’s time for our Model implementation.
namespace IEntityDemo.Model
{
public partial class sql_Category : ICategory
{
}
}
namespace IEntityDemo.Model
{
public partial class sql_Product : IProduct
{
public ICategory Category
{
get { return sql_Category; }
}
}
}
Pretty slick, now we have concrete implementations of our repositories.
namespace IEntityDemo.Repository.LinqToSql
{
public class CategoryRepository
: Repository<ICategory, sql_Category>, ICategoryRepository
{
// TODO : Add IoC
public CategoryRepository()
: this(new IEntityDemoDataContext())
{
}
public CategoryRepository(IEntityDemoDataContext db)
: base(db)
{
}
public ICategory GetByID(int id)
{
return GetTable().SingleOrDefault(c => c.ID == id);
}
}
}
namespace IEntityDemo.Repository.LinqToSql
{
public class ProductRepository
: Repository<IProduct, sql_Product>, IProductRepository
{
// TODO : Add IoC
public ProductRepository()
: this(new IEntityDemoDataContext())
{
}
public ProductRepository(IEntityDemoDataContext db)
: base(db)
{
}
public IProduct GetByID(int id)
{
return GetTable().SingleOrDefault(p => p.ID == id);
}
}
}
This blog post is running pretty long so I’ve decided I’m going to turn this into a multi-part series. To achieve this I’m leaving IoC/DI out for this one so as bad as this looks – it’s temporary.
Now it’s time to test out our efforts.
namespace IEntityDemo.Controllers
{
[HandleError]
public class HomeController : Controller
{
public HomeController()
: this(new CategoryRepository(), new ProductRepository())
{
}
public HomeController(ICategoryRepository categoryRepo,
IProductRepository productRepo)
{
CategoryRepo = categoryRepo;
ProductRepo = productRepo;
}
private ICategoryRepository CategoryRepo { get; set; }
private IProductRepository ProductRepo { get; set; }
public ActionResult Index()
{
ViewData["Title"] = "Home Page";
ViewData["Message"] = "Welcome to ASP.NET MVC!";
var products = ProductRepo.GetAll();
return View(products);
}
public ActionResult About()
{
ViewData["Title"] = "About Page";
return View();
}
}
}
Again, this is temporary. On that token let’s write some quick and dirty view code.
<% foreach (var product in ViewData.Model as
IEnumerable<IEntityDemo.Model.IProduct>)
{
%><div><%= product.Category.Name %>:
<%= product.Name %>
<%= product.Price.ToString("C") %></div><%
} %>
Mmm, I loves me some spaghetti code.
Now I know I some of you are thinking why would I go through all of that trouble just to write terrible code? Well I realized this blog post was becoming overstuffed with info and I needed to turn.
I come in peace. This is a post to continue my series about my opinions and thoughts on the issue. If you haven’t I suggest you go back and read Part 1 and Part 2.
So I’ve been working on a MVC application for a friend of mine and his business. When I had the meeting with them I originally thought that for the purpose of a retail storefront I would go with ADO.NET EF to give it a really fair trial in the eyes of the jury.
So I started off making some POCO objects so that if I decided to change things up I could do so without having to re-do my whole DAL/BLL. Shortly after I finished the database setup and the original POCO creation I noticed ADO.NET EF doesn’t support POCO with any ease… fannnnntastic. After an hour or so of playing with my Rubiks cube I went back to coding, using ADO.NET EF’s code generated objects. I started to stub out the controllers and write code for adding/removing entities. It came time to deal with a Product/Category relationship and I realized ADO.NET EF had hidden the CategoryID field on the Product object. After some digging around I found out you can expose it with some pain and anguish but you can’t even use it to query with. Mot only that, since I couldn’t even use the CategoryID property on the Product object I decided I would just query for the Category and set the Category property on the Product object. Guess what – if you have a Product Repository and a Category Repository, you can’t query a Category from one and use it in an object queried from another context.
Here I am, with some code generated objects that I can’t access direct properties of. I decided to persevere and continue because I really did want to get ADO.NET EF a fair fight. Knowing I couldn’t access FK properties on objects nor could I set them without having them come from the same context I thought I would try out Stored Procedures… only to find out I couldn’t map them in ADO.NET EF.
At this point I decided to stop, I had wasted almost an entire day moving backwards with productivity for something that should’ve been relatively easy. They say ADO.NET EF is conceptual mapping which in my opinion should be it would be easier to map things the way you want to without having to bend over backwards, WRONG. I’ve read that the LINQ to SQL guys have merged with the ADO.NET EF team to bring over some of the features (read: requirements) LINQ to SQL supports. Hopefully this turns out well.
I really hope those LINQ to SQL guys help out the ADO.NET team because right now ADO.NET EF was a waste of my time. When v2 comes out if half of these problems still exist I think I’ll be moving to Ruby and Ruby on Rails. There is a large base of .NET developers moving to the RoR platform and tons of support with books and articles. We can only hope with that with the new light ASP.NET MVC has shed on ASP.NET that Microsoft will seize this opportunity to show they can do their consumers right.
Sonu Kapoor the owner of http://dotnetslackers.com wrote an initial version of Kigg, a Digg clone using ASP.NET MVC. I happened to check back at the source code after I noticed that http://www.dotnetshoutout.com looked exactly like what I remember Kigg to be. After looking at their codeplex page I noticed they have updated the source to work with ASP.NET MVC Beta.
The folks developing Kigg have really created a project I can get behind and recommend those working on, or looking into ASP.NET MVC take a peak at. It’s a great place to get started and show that you can create solid, flexible code using the ASP.NET MVC platform. They include a lot of code coverage with their unit tests. I highly suggest everyone go check it out.
So I was roaming around the interwebs today during my lunch break trying to find an interesting product from Microsoft called Oslo. Now before you run off and go google it allow me to save you some headache and tell you it’s not just one “thing.” Oslo allows you to customize or create dynamic languages to create data models.
After watching a few sessions about Oslo and rummaging through Microsoft’s site for it I still felt baffled by it and after discussing this matter back and forth on twitter with Scott Hansleman, I decided.
If you can’t describe your product in one tweet, you need to rethink it.