ASP.NET has changed dramatically in the past five years. I’ve had the privilege to work on some projects using the newer web stacks, as well as modernize an old project.
I’d been away from the Microsoft ecosystem from around 2006 until 2011, and after working in dynamic languages (javascript, python, lisp), it took me some time to figure out what kind of code the Microsoft ecosystem was happiest with.
I threw a lot of pasta on the wall, and here are a few that seemed to stick.
Libraries
I had the smallest friction with this combination of libraries:
- Entity Framework (EF): for database access, schema evolution, and ORM
- Ninject: dependency injection (DI) library
- ASP.NET MVC: fully featured web framework for serving user interfaces, with a filter/DI system that provides a lot of flexibility
- ASP.NET WebApi: fully featured web framework for serving JSON/XML APIs, with a filter/DI system that provides a lot of flexibility. Makes a great backend for javascript frameworks
- Moq: mock object library, makes easily fake any interface to minimize test setup/teardown
- LocalDb: Microsoft’s answer to sqlite, a file-based database that’s SQL Server compatible but does not require any server or licensing
- AutoMapper: quickly copy data from one type to another without a lot of repetitive code
- log4net: very flexible logging; I bet it’s one of the oldest active C# open source libraries, I’ve been using it for years
- FluentValidation: input validation library; lets you specify complex validation rules for a class
Everything is available via nuget, except for the LocalDb installer. Some of these are warrant further discussion.
Entity Framework (EF)
Very fully featured data access library. There’s a ton of depth here.
Good
- define db schemas using C# classes mostly through properties and attributes, able to use inheritance to reduce duplication
- specify db schema evolution using tool-assisted explicit migrations; add a new field to one of the DB classes, call
Add-Migration NewField-ticket42
, and most of the work is done, including a natural place to add data fixes - generate SQL scripts of pending migrations (EF stores some metadata in your database) for deployment
- LocalDb support
- linq support that builds crazy but reasonably efficient SQL queries that make it easy to select the minimal amount of data you need. You can select a few properties from your db object without fetching a whole row, do joins, etc
- provides a database cache; repeated requests to fetch a db object against the same db context are free
- transaction support; any operations (including inserting complex relationships) on a db context are pending until you call
SaveChanges
- can work against existing databases, with some care
Bad
- the linq support can be surprising; some things just aren’t allowed and it isn’t always obvious. Thankfully the exceptions are thrown early and have good messages
- it’s easy to accidentally load way more data than you want
- exception messages for some db errors can be a obtuse or require debugging to examine a property on the exception
- really, really, really wants you to run all your DB operations against one instance of the DB context (i.e. per HTTP request). Things get really weird if you try to use a db object between two db context instances
- is happier with updates if you load a db object in full, change the properties, then save
- it can be tricky to sort out how to add a graph of db objects without fetching the whole DB or calling
SaveChanges
multiple times to get autoincrement ids. Totally doable, but easy to screw up - EF’s migrations require your db classes to be free of compiler errors, which leads to putting your db classes in a different DLL from the rest of your application. If you change a db class in a way that breaks the rest of your application, unless the db classes in a different assembly, you have to update your entire application before you can figure out the migration. This leads to other weirdness and tough questions like “which assembly should this DTO/interface go in?”
ASP.NET MVC and ASP.NET WebApi
ASP.NET WebApi and ASP.NET MVC are very similar, and the two are being combined in ASP.NET vNext. They can also work together in one web project, albeit with different namespaces. It’ll be nice when ASP.NET vNext unifies these namespaces.
Good
- explicitly maps URLs and HTTP verbs to an action method on a controller class
- filter system that lets you run code before and after your action
- naming convention-based system to choose templates for controller actions, with helpful error messages telling you what names the framework expected
- automatically serialize/deserialize between HTTP GET/POST/PUT/DELETE data into C# objects, with hooks to customize the process. Ends up acting a lot like method injection
- hooks for how the framework instantiates controllers to let you use a DI library for controller creation
- really likes view models; define all the data you want in a template as a class, create it in your controller action, and pass it to the template. Viewmodels are easy to test
- the ASP.NET MVC template system has nice helpers like
EditorFor
andDisplayFor
to render UI for view models - Since controllers are plain classes, you can
new
them up in tests and pass in different input without running a web server - lots of plugins and helper libraries on nuget
- WebApi.HelpPage: automatic API documentation using reflection and XML doc comments
- FluentValidation.WebApi-Signed: easily run validation rules for incoming data
- FluentValidation.MVC5-Signed: easily run validation rules for incoming data
- many more
Bad
- MVC wants you to group your files by type, not by feature. This makes your template far away from the (usually) single controller that uses it
- no easy way to share templates between different projects
- ASP.NET WebApi and ASP.NET MVC have a lot of the same classes in different namespaces. If you’re using them together it can get confusing if you want a System.Web.Http.Filters.IAuthenticationFilter and accidentally autocomplete the wrong
using
statement and end up with a System.Web.Mvc.Filters.IAuthenticationFilter - visual studio refactoring tools like “Rename” do NOT change your templates
- any code in your templates is technically in a different assembly, so anything in your viewmodel you want to use in a template needs to be
public
- some built-in MVC helpers look at attributes on your viewmodel for how to render, validate, etc. If you follow that approach, then changing a
<label>
requires a recompile and a bunch of “go to definition”. Seems like writing HTML in your templates is easier than using the helpers so you can scatter your UI text across your viewmodels. This is probably fine if you need internationalization or localization, but if you don’t then it just feels like extra hoops
Ninject
Pretty straightforward dependency injection library. You tell it what implementation you want for what interface at application start, and then ask it to create all your objects.
class Foo : IFoo { public Foo(IBar b){} }
// on app start:
var kernel = new Ninject.StandardKernel();
kernel.Bind<IBar>().To<Bar>();
kernel.Bind<IFoo>().To<Foo>();
var foo = kernel.Get<IFoo>(); // new Foo(new Bar())
Good
- easy to use
- makes it practical to write many small, easily testable classes and not have to wire them up by hand
- good error messages
- support contexts so you can say “make one DBContext per HTTP request”
- tons of options
- lots of plugins on nuget:
- ninject.extensions.conventions: automatically register interfaces/implementations
- Ninject.Extensions.Factory: create naive factory classes; useful if you long-lived objects
- Ninject.MockingKernel.Moq: easily use mock objects with ninject for unit testing
- many more
Bad
- depending on what version of ASP.NET MVC/WebApi you’re using, there are different nuget packages and install instructions, take care you’re using the right approach
- “go to definition” gets less useful, since it’ll lead you to the interface, not to what ninject is actually instantiating at runtime
- reduces the cost of many small classes; you get a long chain of “go to definition” to find the class actually doing the work
LocalDb
This is the dev/test database I’ve always wanted.
Good
- can run tests against a full DB. At the beginning of your test run create a new LocalDb file, run all your EF migrations, then run each test case in a DB transaction
- great for running dev sites on your workstation without a ton of setup
Bad
- sometimes the files get in a weird state and you have to change your connection string to get a new file
- I expect some subtle difference between this and a full SQL Server that will bite you if you use proprietary SQL Server features
AutoMapper
I kept going back and forth on this one; sometimes it’s a great time saver, sometimes it’s a huge hassle. Overall I think it’s a win.
Uses reflection to convert code like:
class Whatever {
public Foo Copy(Bar b){
return new Foo{
Name = b.Name,
Title = b.Title,
Message = b.Message
}
}
}
into something like:
class Whatever{
static Whatever(){
Mapper.CreateMap<Bar, Foo>();
}
public Foo Copy(Bar b){ return Mapper.Map<Foo>(b); }
}
When you have a lot of data transfer objects (DTO)s it can be really common to want to copy fields from one type to another.
Good
- reduces annoying boilerplate
- can specify explicit mappings when names don’t match
- can map collections and nested objects
- works with EF and linq to map from your db objects and minimize how much you fetch from the db
- you create mappings at application start, and they are cached from then on so you don’t pay a big reflection penalty
Bad
- is really much happier when the property names match exactly
- gets really finicky about mapping collections from linq
- error messages could be better
- refactorings like “rename” could introduce runtime errors if you don’t have good test coverage
- copying data a lot isn’t a great idea; using more interfaces might eliminate the need
- complex mappings that apply to multiple classes are difficult to reuse
FluentValidation
Specify validation rules with a testable, fluent interface:
class FooValidator : FluentValidation.AbstractValidator<Foo> {
public FooValidator(){
RuleFor(x => x.Name).NotEmpty();
}
}
// validate
new FooValidator().Validate(new Foo())
Good
- easy to test
- plugins to automatically validate deserialized MVC or WebApi requests
- lots of validation rules
- custom validations are straightforward to implement
Bad
- need to pass the right type into
AbstractValidator<T>
, which can lead to messy generic type signatures if you want to re-use validation rules between parent/child classes. Using extension methods to re-use rules is sometimes easier - custom error messages are defined in your validator class, this can be far away from the UI that displays it
One Comment