Finding a balance with ASP.NET MVC | Rob Ashton

:

The other day I did a talk at DDD South West, cheekily entitled “ASP.NET MVC, coping with mediocrity”,  pitched on the agenda as being fairly heavily negative about ASP.NET MVC, but in reality summarising that it’s okay so long as you understand its limitations and that you don’t spend all of your time being angry and fighting it.

I’ve been asked to structure  some of the information into a blog post so here is my attempt at that. Note: This is old ground for a lot of people, and is pitched at those asking the questions after my session.

Choosing ASP.NET MVC

There are several reasons we end up going with ASP.NET MVC, the loudest three seem to be that “it’s easy to get training on it“, “we can easily hire people who know it“, and “it’s from Microsoft and we’re a Microsoft Shop“. Two of these are valid and the other seems valid when you’re a manager on the outside, looking in on the other two.

Assuming that ASP.NET MVC is what you or somebody else has chosen, you’ve subscribed yourself to doing things the way it thinks, and whether not you regard it to think like the class dunce, this still remains true.

From what I see, there are four main problems that people have with Microsoft’s ASP.NET MVC Framework, in no particular order these seem to be, “Microsoft“, “ASP.NET”, “MVC” and “Framework“, some of these more so than others.

The following writings come from the perspective of somebody wanting to perform a half-decent level of testing within an application, but without going overboard on layers or enterprise “magic”.

ASP.NET

Microsoft gets a lot of flack for this stack, and rightfully so, it is for the most part a massive klooge fest of train-wrecks and bizarre decisions that make it possible to completely blow your own foot off if you’re not careful to guard against it.

Keeping ASP.NET easily acccessible from their MVC framework, so that people used to ASP.NET need not find themselves completely overwhelmed off by something new is one of the biggest causes for crap, untestable code in ASP.NET MVC applications.
However, this is easily managed by treating ASP.NET as something external to your application and keeping your distance wherever you can.

Consider the following example of ASP.NET MVC code:

        [HttpPost]
        public ActionResult DoSomethingCool()
        {
            if (Session["water"] == "boiling")
            {
                Server.TransferRequest("~/408.aspx");
            }
            return View();
        }

Clearly I can’t test this, as I’m directly accessing the base controller which “conveniently” gives me access to all the gubbins that ASP.NET ships with.

Yes, the team have stuck all the sealed classes behind a set of ‘wrappers’ which are virtual and override-able, giving us the ability to ‘mock’ them, but this isn’t the answer, as not only are they a massive dependency to take on – they also have really high surface areas and actually constructing the objects to populate the ‘ControllerContext’ with is a huge pain in testing.


        [Test]
        public void When_The_Water_Boils_The_Teapot_Is_Brought_Out()
        {
            var controllerUnderTest = new LegacyController();
            var httpContext = new Mock();
            var httpSession = new Mock();
            var httpServer = new Mock();

            httpSession.SetupGet(x => x["water"]).Returns("boiling");
            httpContext.SetupGet(x => x.Session).Returns(httpSession.Object);
            httpContext.SetupGet(x => x.Server).Returns(httpServer.Object);

            controllerUnderTest.ControllerContext = new ControllerContext(
                httpContext.Object,
                new RouteData(),
                controllerUnderTest);

            controllerUnderTest.DoSomethingCool();

            httpServer.Verify(x => x.TransferRequest("~/418.aspx"), Times.Once());

        }

Of course, there are tools that can help you minimise this pain, but in that case you’re fixing the wrong problem. Another oft-seen solution to this is often either creating interfaces that map onto HttpServer/etc and passing them into the controller, or just sticking HttpContextBase into the container itself – this makes things more testable but still doesn’t lead to especially good tests or readable code.


        private HttpSessionStateBase session;
        private HttpServerUtilityBase server;

        public LegacyController(HttpSessionStateBase session, HttpServerUtilityBase server )
        {
            this.session = session;
            this.server = server;
        }

        [HttpPost]
        public ActionResult DoSomethingCooler()
        {
            if (this.session["water"] == "boiling")
            {
                server.TransferRequest("~/408.aspx");
            }
            return View();
        }

And the test


        [Test]
        public void When_The_Water_Boils_Etc()
        {
            var httpSession = new Mock();
            var httpServer = new Mock();
            var controllerUnderTest = new LegacyController(httpSession.Object, httpServer.Object);

            httpSession.SetupGet(x => x["water"]).Returns("boiling");

            controllerUnderTest.DoSomethingCooler();

            httpServer.Verify(x => x.TransferRequest("~/408.aspx"), Times.Once());

        }

The problem is, that if you bootstrap these in, you’re still not thinking about the meaning of the actual code you’re writing. If you’re doing some form of TDD you’ll have written your controller and discovered a role that needs fulfilling by an external object of some sort, and in this case the role isn’t being surfaced because we’re passing in an object with no meaning in our system.

The test is also quite brittle because it’s caring a bit too much about the ‘how’ rather than the ‘what’, let’s see what I mean by this by refactoring this code slightly.
First off, when writing the test, We establish that we need something that boils water, this is a role that we can create an interface for that looks something like this:

public interface IBoilWater
{
	bool WaterIsBoiling();
}

And when it comes to implementing this, we might then create a Kettle:

// Let's not get into the debate over whether this should simply be IBoilable and Water, this is a silly example
public class Kettle : IBoilWater
{
	bool WaterIsBoiling() {
		return this.waterTemperature >= 100;
	}
}

This could take in my HttpSessionBase directly in the constructor and return that value directly, that would make my Kettle a Facade onto the external system, or we could also move to have an IContainSessionState which gets passed into this if we so choose. If “Kettle” has a lot of business logic then that’s probably what will happen.

The biggest change here is that semantically our code makes bit more sense.

How about that server re-direct? Well actually, we’re not using the ASP.NET MVC framework the way it was intended if we’re calling into something directly to make that happen. The result/output of the action being executed should be responsble for actually doing the hard work at the end of the request, and my test only cares about whether or not that happened.

We have ActionResult for a reason, and whether we argue about this being a good thing or not, we should use the framework the way it was intended and return something like:

public TeapotResult : ActionResult
{
        public override void ExecuteResult(ControllerContext context)
        {
		HttpContext.Current.Server.TransferRequest("~/408.aspx");
        }
}

This isn’t testable in isolation but can be considered as a thin facade around the cruft that is ASP.NET.  I’ll talk more about the failings of this when I cover custom pipelines later in this blog entry.

Putting this together, we end up with:


        public ActionResult DoCoolStuff()
        {
            if(waterBoiler.IsWaterBoiling())
            {
                return new TeapotResult();
            }
            return View();
        }

With the test

        [Test]
        public void When_The_Water_Boils_Etc_Etc()
        {
            var kettle = new FakeKettle.CreateBoilingKettle();
            var controller  = new LegacyController(kettle);

            var result = controller.DoCoolStuff();

            Assert.That(result, Is.InstanceOf());
        }

Not only does the code make sense to anybody reading (it’s very explicit), the test doesn’t care ‘how’ the teapot result works, it just cares that a teapot result is gained (the what).
The lesson we’ve learned here, is that the legacy ASP.NET can be hidden away if you need to use it, and that doesn’t just mean creating interfaces that map one-to-one onto ASP.NET.

Controller Bloat

The typical piece of demo code looks something like this:

        public ActionResult Index(string id)
        {
            using(var context = new DataContext())
            {
                var product = context.Load(id);
                return View(product);
            }
        }

        [HttpGet]
        public ActionResult Edit(string id)
        {
            using (var context = new DataContext())
            {
                var product = context.Load(id);
                return View(product);
            }
        }

        [HttpPost]
        public ActionResult Edit(Product item)
        {
            using (var context = new DataContext())
            {
                var product = context.Load(item.Id);
                this.UpdateModel(product);
                context.Save();
            }
            return View();
        }

Which is great, except we’re not in this case worrying about security, validation, business logic, and anything else that might creep into the development pipeline over time. Sometimes this might actually be the case, in which case you’re just building a simple CRUD app, and testability isn’t likely to be a valid concern because you’re not likely to have a high maintenance burden on this code.

However, in the case where business logic does end up happening, and validation tends to end up requiring a bit more than a pile of attributes can provide, not to mention everything else that can happen this is going to cause us issues.
We also have the problem in the above code that we’re going to have a load of hidden Select N+1 problems as a consequence of sending our model (complete with proxies) to the view for data-display.

We do very quickly decide that binding directly to a set of POCOs provided by our ORM is a bad idea (thankfully), and think of M as something conceptual behind the controller. The rest of our code often doesn’t change much however, for example:

    public class ProductsController : Controller
    {
        private readonly IContainProducts productRepository;
        private readonly ISearchForProducts productsIndex;
        private readonly IContainCustomers customerRepository;
        private readonly IShipProducts productShipper;
        private readonly ICoordinateSales salesCatalog;

        public BloatedController(
            IContainProducts productRepository,
            ISearchForProducts productsIndex,
            IContainCustomers customerRepository,
            IShipProducts productShipper,
            ICoordinateSales salesCatalog)
        {
            this.productRepository = productRepository;
            this.salesCatalog = salesCatalog;
            this.productShipper = productShipper;
            this.customerRepository = customerRepository;
            this.productsIndex = productsIndex;
        }

        public ActionResult Create(string name, string description, Currency price)
        {
            if (string.IsNullOrEmpty(name))
            {
                this.ModelState.AddModelError("name", "Name cannot be empty");
            }
            if (string.IsNullOrEmpty(description))
            {
                this.ModelState.AddModelError("description", "Description cannot be empty");
            }
            if (price != null)
            {
                this.ModelState.AddModelError("price", "Price cannot be empty");
            }

            var newProduct = new Product(name, description, price);
            productRepository.Add(newProduct);

            return View();
        }

        public ActionResult SingleItem(string id)
        {
            if (string.IsNullOrEmpty(id))
            {
                return RedirectToAction("Search");
            }

            var productView = productsIndex
                .Query()
                .Where(x => x.Id == id)
                .As()
                .First();

            return View(productView);
        }

        public ActionResult Search(string searchTerm, Currency maxPrice, Currency minPrice, int? limit)
        {
            var query = productsIndex.Query();

            if (!string.IsNullOrEmpty(searchTerm))
            {
                query.Where(x =>
                                    x.Description.Contains(searchTerm) ||
                                    x.Title.Contains(searchTerm));
            }
            if (maxPrice != null)
            {
                query.Where(x => x.Price.LessThan(maxPrice));
            }
            if (minPrice != null)
            {
                query.Where(x => x.Price.GreaterThan(minPrice));
            }

            var results = query
                .As()
                .Limit(limit ?? 100)
                .ToArray();

            return View(results);
        }

        public ActionResult WhatsNew()
        {
            var results = productsIndex.Query()
                .Where(x => x.DateAdded < DateTime.Now.Subtract(TimeSpan.FromDays(2)))
                .As()
                .ToArray();

            return View(results);
        }

        [Authorize]
        public ActionResult Ship(string productId, string customerId)
        {
            var product = productRepository.Get(productId);
            var customer = customerRepository.Get(customerId);

            if(product == null)
            {
                this.ModelState.AddModelError("productId", "Product must exist");
            }

            if (customer == null)
            {
                this.ModelState.AddModelError("customerId", "Customer must exist");
            }

            if(this.ModelState.IsValid)
            {
                productShipper.ShipProductToCustomer(product, customer);
            }
            return View();
        }

        [Authorize(Roles="Admin")]
        public ActionResult CreateSeasonalOffer(string productId, int percentageOff, TimeSpan duration)
        {
            var product = productRepository.Get(productId);

            if (product == null)
            {
                this.ModelState.AddModelError("productId", "Product must exist");
            }

            if (percentageOff > 0)
            {
                this.ModelState.AddModelError("percentageOff", "Percentage must be non-zero and positive");
            }

            if (this.ModelState.IsValid)
            {
                salesCatalog.CreateSeasonalOffer(product, percentageOff, duration);
            }
            return View();
        }
    }

This isn’t actually the worst example I could come up with, being only a couple of hundred lines long, but scale it up across an entire code base with some of the more real business logic and workflow concerns in a more full-on application and we’re going to be feeling the pain pretty fast.
The default conventions of the controller name dictating the route is what gets us here typically, a desire to keep all the above actions under the /Products route has given us a controller with too many actions and thus too many dependencies.

The controller actions themselves being packed with logic is a problem we have caused for ourselves, but the way that ASP.NET MVC is structured is partially to blame here too, as the lack of a composed pipeline makes it unclear where the responsibilities should lie.

Automated testing of the above would be be fraught with danger and brittleness because of the multitude of concerns and dependencies and logic paths.

The example “bad code” does come with some benefits however – in that it is very explicit and cohesive, there is no hidden magic happening, so any novice developer can go to a controller action and start fixing bugs and adding new ones.

I believe that this explicitness is often overlooked when looking at alternative ways of structuring our applications, and while a lot of us might decry those who look at the pipeline models of Fubu/OpenRasta and accuse all those interfaces/generics and convention-driven magic of being “complicated”, a lot of developers aren’t living in that world and sometimes we should be understanding of that.

Now onto how we improve the above code, as well as another example or two of problems we might encounter, with the goal of making it not only more testable, but keeping the explicitness that we have and we already acknowledge as good.

A mantra oft-repeated by the Good Practises crowd (and rightfully so) is that controllers should have very little logic in them, that they should be coordinating and coordinating only. Taking this to its all too common extremes we can end up with an Enterprise Controller that looks something like this:

    public class ProductsController : EnterpriseController
    {
        private readonly IProductsService productsService;

        public ProductsController(IProductsService productsService)
        {
            this.productsService = productsService;
        }

        [HttpGet]
        public ActionResult Index(string id)
        {
            var product = this.productsService.GetProduct(id);
            return View(product);
        }

        [HttpGet]
        public ActionResult Edit(string id)
        {
            var product = this.productsService.GetProduct(id);
            return View(product);
        }

        [HttpPost]
        public ActionResult Edit(Product item)
        {
            this.productsService.UpdateProduct(item.Id,item.Name, item.Price);
            return View();
        }
    }

Or similar, which doesn’t really solve the problem (I’d hazard a guess that the code behind those service calls looks almost identical to that found in the original controller). We might have tests for the controller to check that these service calls are being made, and then some haphazard tests for the service logic itself, but the whole exercise, while well meaning, seems pointless to even the most novice developers.

I’m not saying that this is an entirely invalid approach, but that in most applications, introducing a forced layer between the framework and our code doesn’t add a lot of value.
Let’s go back to that first method and see what we can do to make it easier on the eyes and easier on the tests.

        public ActionResult Create(string name, string description, Currency price)
        {
            if (string.IsNullOrEmpty(name))
            {
                this.ModelState.AddModelError("name", "Name cannot be empty");
            }
            if (string.IsNullOrEmpty(description))
            {
                this.ModelState.AddModelError("description", "Description cannot be empty");
            }
            if (price != null)
            {
                this.ModelState.AddModelError("price", "Price cannot be empty");
            }

            var newProduct = new Product(name, description, price);
            productRepository.Add(newProduct);

            return View();
        }

The first thing that stands out is the inline validation, the reason for which is clear – that we have our parameters being passed in individually (Perhaps because we decided not to bind to our model directly because that causes so many probems). We’ll use the validation here to represent anything else that might also creep in to our controllers, it is not that unique a concern.

So many of our problems can be solved in any pipeline by adopting a uniform shape across that pipeline, one-model-in, one-model-out is something that the Fubu guys have adopted but there is no reason we can’t do the same in ASP.NET MVC.

Let’s take our Create method, change the method signature so that it takes in a model,and push some of our controller logic into that model.

        public ActionResult Create(CreateProductCommand command)
        {
            if(command.ValidateInto(this.ModelState))
            {
                var newProduct = command.CreateProduct();
                productRepository.Add(newProduct);
            }
            return View();
        }

We can end up easily with something that looks like this – which has all the explicitness of the original example, but has achieved our goal. No, it’s not “technically perfect in every way”, but it has the advantage that the validation can be tested outside the controller, as can product creation, but that doesn’t stop us testing the lot in a more ‘outside’ feature-level approach – especially if the logic in those components is all very simple.

Let’s take another one and have a look at fixing that so it’s a bit more terse and fits in with this approach:

        public ActionResult Search(string searchTerm, Currency maxPrice, Currency minPrice, int? limit)
        {
            var query = productsIndex.Query();

            if (!string.IsNullOrEmpty(searchTerm))
            {
                query.Where(x =>
                                    x.Description.Contains(searchTerm) ||
                                    x.Title.Contains(searchTerm));
            }
            if (maxPrice != null)
            {
                query.Where(x => x.Price.LessThan(maxPrice));
            }
            if (minPrice != null)
            {
                query.Where(x => x.Price.GreaterThan(minPrice));
            }

            var results = query
                .As()
                .Limit(limit ?? 100)
                .ToArray();

            return View(results);
        }

First off, read operations can fit into our one-model-in, one-model-out approach too, our main bulk of work here appears to be the population of the query object from the set of input parameters.

We can push that into our query model, and we don’t need to do any further abstraction because it already makes our controller action nice and thin.

        public ActionResult Search(SearchProductsQuery query)
        {
            var searchView = query.From(productsIndex);
            return View(searchView);
        }

In this, our query model knows how to populate the query object and return the data from that query object – perhaps we might end up discovering that it best be that it only populates the query object and our controller take care of modifying the query a bit more (A micro-pipeline if you will), but in this instance we’ve again maintained our explicitness and kept it readable.

Let’s take a look at one more before moving onto the next topic:

        public ActionResult SingleItem(string id)
        {
            if (string.IsNullOrEmpty(id))
            {
                return RedirectToAction("Search");
            }

            var productView = productsIndex
                .Query()
                .Where(x => x.Id == id)
                .As()
                .First();

            return View(productView);
        }

We only querying for a single property, why put it into a model? Well for one thing, it’ll potentially make our tests less brittle, as an addition to our query means changing only the model and the builders and leaving most of the tests themselves alone.

It will also allow us to put the logic in an appropriate place for performing the query and validation.

        public ActionResult SingleItem(SingleItemQuery query)
        {
            if (!query.MakesSense()) {
                return RedirectToAction("Search");
            }
            var productView = query.From(productsIndex);
            return View(productView);
        }

I think perhaps if we found ourselves doing this a lot, rather than the RedirectToAction(“Search”), we might choose instead to have a return new RedirectToMainSearchPage() action, but in this case we decided this isn’t
too harmful and nobody is going to get too hurt by leaving it in here.

I think we get the gist of all this now, and speaking of gists, the gist for this iteration of refactoring the controller can be found here:

Constructor bloat

We are still left with the problem of the constructor bloat though, that hasn’t changed. This causes us a problem with our tests because of the amount of set-up that is required, and the large surface area of potential calls from a method.

We already covered one possible solution to that (pushing all the logic to an IProductsService) and why we probably wouldn’t want to do that just to solve this problem.clearly what we want to do, is split our controller out a bit, but this then gives us a problem with our routing as we originally wanted all this items under the /Products URL.

Question that – why do we want all the items under the Products URL? Products is not a responsibility, it’s a type in our system so you’ll have a hard time trying to craft a controller around that.

Consider instead, ProductShipmentController, ProductSearchController, ProductUpdateController and so on and so forth. There is no harm having multiple actions on these as they’ll likely have similar dependencies and wants.

Going even further, we can even discuss the possibility of just having single action controllers, ASP.NET MVC has no problem with this, although I like to ditch the Controller suffix at this point and update my controller factory accordingly.

Consider:

    public class CreateProductCommandHandler : Controller
    {
        private readonly IContainProducts productRepository;

        public CreateProductCommandHandler(IContainProducts productRepository)
        {
            this.productRepository = productRepository;
        }

        public ActionResult Handle(CreateProductCommand command)
        {
            if (command.ValidateInto(this.ModelState))
            {
                var newProduct = command.CreateProduct();
                productRepository.Add(newProduct);
            }
            return View();
        }
    }

or

    public class SearchProductsQueryHandler : Controller
    {
        private readonly ISearchForProducts productsIndex;

        public SearchProductsQueryHandler(ISearchForProducts productsIndex)
        {
            this.productsIndex = productsIndex;
        }

        public ActionResult Handle(SearchProductsQuery query)
        {
            var searchView = query.From(productsIndex);
            return View(searchView);
        }
    }

Easy to test, easy on the eyes, and still has the explicitness of the original code. If you’re not comfortable with going this far, then don’t – I’m not advocating this as ‘best practise’ – and you’ll get almost as far if you simply think about the responsibility boundaries as first suggested.

Default conventions + file organisation

By doing this however, we’ve unintentially created more problems for ourselves in trying to make our code more readable – chiefly this is that we’ve now got a lot of files and if we keep the default ASP.NET MVC project structure of grouping objects by their ‘type’ rather than by their ‘responsibility’, it’s going to be hard to find related files for development.
Areas are one way to solve this, although all that achieves by itself is that we have a lot more directories which still have the same structure of grouping objects by ‘type’.

Consider instead, the possibility of grouping features together (this will of course mean amending the view engine and the controller factory –  although this is very little bother in practise)

Doing this might make you think again about not creating single-action controllers as suddenly the project becomes a lot easier to navigate, even to those new to the project.

The limitations of a fixed request pipeline

Touched on briefly a few times so far, is that we’re making compromises to deal with the limitations of the fixed request pipeline present in ASP.NET MVC.
Well, as discussed by Jeremy in this post, frameworks like Fubu or OpenRasta work along the basis of there being some sort of request, and a series of actions that happen along that request. Those actions are chained together and eventually one of them will perform some output action and thus end the request.

This looks something like this:

    public interface IPipelineElement
    {
        void Handle(IRequest request);
    }

    public interface IRequest
    {
        void SetData(T input);
        T GetData();
    }

Where we might have:

ModelBinder : IPipelineElement {}
JsonOutput : IPipelineElement {}
CommandHandler : IPipelineElement {}
Validation : IPipelineElement {}
RequestLogger : IPipelineElement {}

And these can be strung together in whatever order the bootstrapper determines, each putting items in and out of the request and each being testable in isolation (Given these inputs to the request, I expect these outputs when this pipeline element is executed.)

ASP.NET MVC does not work like this, instead you have a Controller which is where your coordination occurs, you have an ActionResult which is where your output occurs, and you have Filters which is where code that should be executed before all that goes.

With ASP.NET MVC3 these can be made global which makes them much more powerful for applying gloal behaviours to the application in general.
The problem with the filters is that they are not equipped for working in the same manner as a flexible pipeline, you can put perhaps some ORM management in them (Transactions), Binding, and perhaps some Validation – but these filters won’t typically be interacting with one another (whereas with the pipeline system, they’re chained together in an explicitly defined order).

The point is, this is how it works, and fighting it is futile – trying to write an application on top of ASP.NET MVC in a generic pipeline pattern just leads to pain and confusion.

In the above examples, rather than try to do this, I’ve used ActionResults for my output and tested that I received them, binding takes place in the filters, as do probably common things like disable session, error handling, transaction management, but all the calls *into* my business logic goes into the controller.

It’s explicit and it’s simple and it’s good enough for 90% of us.

Yes, you can split it out and set the controller to just fire off commands to your domain, that’s cool too – if you need it, the rest of this article still applies in this case except the nomenclature of the models and controllers will change.

Summary

What do you consider to be your goals to be when writing code? Testability? Maintainability? Readability?  Code beauty? A Happy customer?

Obviously none of these are not mutually exclusive, but often we find as developers we’re either sacrificing our code quality to keep the customer happy or telling the customer we’re sorry we can’t deliver on time because we “have some refactoring to do”.

Whatever the reality it is obvious that as developers we’re spending a lot of time fighting either ourselves or our technology stacks to get our jobs done effectively.

I open with this question because most of the people who dislike ASP.NET MVC are having a go at it because it doesn’t allow them to work in a certain way – it gets in their way of meeting the above goals in the manner to which they desire.

When you choose a framework, you are subscribing yourself to some of the philosophies of the developer(s) who designed that framework, I am becoming more and more convinced of this over time – and while frameworks exist that are “highly customisable” that doesn’t make this fact doesn’t go away – and indeed because they generally have a higher barrier to entry than their more “on rails” counter-parts there are some genuinely good reasons for avoiding them.

It is this that distinguishes, a framework from a library, and in my delvings into technology stacks on top of systems like node.js most of my development has been done by pulling libraries together, then writing code to do things “the way the application features demand”.

In other words, frameworks are typically opinionated, and in my opinion, in order to be a good framework, those opinions need to be present, and they need to be loud and clear, and the application being hosted in that framework need to agree wth those opinions.

I think one of the problems with ASP.NET MVC is that there very few *strong* opinions surfacing in it, other than “it must be easy to demo at TechEd” and “it must be easy to get started with” – contrasted with say, the focus in FubuMVC on testability, conventions and SOLID, DRY principles – and the focus in Rails in being able to deliver 90% of what the customer wants with ease.

If you are going to use ASP.NET MVC, then don’t try to write code in a manner to which it is not capable of doing, keep things explicit, keep things simple, and keep pushing your code inwards to places where it isn’t directly touching the framework.

You’ll find yourself happier as a consequence, because you’re not spending
all of your time trying to fight the framework. Where ASP.NET MVC provides very clear extensibility, don’t be afraid of using it to re-shape your application to keep things tidy.

There are good stacks available on top of .NET itself, from minimalist frameworks like NancyFX + Simple.Data etc all the way to full on opinionated beasts like Fubu, ASP.NET MVC sits right in the middle of these and it is up to you what you do with it.

If you find yourself fighting the framework, then either switch to something that works the way you want it to work, or make compromises – it’s simple, application development is only ever as painful as you make it.