I’m seeing a disturbing trend in vNext and need some edification because I’m failing to see the big picture of where we are being taken as a community. Why has vNext abandoned basic OOP paradigm like interfaces in lieu of what I can only describe as "convention based programming"?
Perhaps I’m just unlucky and the types of classes I’ve started with are one-off unicorns but in the short three days I’ve been working with vNext I’ve come across two classes that seem to have abandoned basic programming constructs that leaves me mystified.
The first is a middleware class. Middleware in vNext are designed to work without implementing any interface nor inheriting from any common base class but magically get invoked in the vNext pipeline through an Invoke
method. When did we decide that interface contracts are pointless? This just makes no sense to me at all. If there is a third party that is going to call into my middleware then there should be a contract that states I’m going to implement XX. It expresses intent and also provides compile time checks. Without a contract there is nothing preventing said third party from completely changing the signature of the method it expects and only at run-time do you know something went wrong.. Not to mention the poor developers, me and my kin, staring at the code unable to decipher what should be implemented and the sequence of life-cycle events that get me from point A
to point B
.
public class SampleMiddleware // complete lack of interface implementation or base class inheritance
{
private readonly RequestDelegate _next;
public SampleMiddleware(RequestDelegate next)
{
_next = next;
}
// yet a magical method that somehow gets invoked in a pipeline.. wth..
public async Task Invoke(HttpContext context)
{
context.Response.ContentType = "text/html";
await context.Response.WriteAsync("SampleMidlleware fired!");
}
}
The second is a web application that is self-hosted in a console application. When you attempt to run the command to start the self-hosting (dnx . web
) only during runtime do you get the error that you forgot a magical Startup
class. Again because I lack the terminology and I can only describe this as "convention based programming". In OWIN
we at least had the OwinStartupAttribute
that could be applied at the assembly level. Now we are left with, well nothing.. Nothing in my code states that my intent is to define a startup class that can be invoked by a third party program.
System.InvalidOperationException: A type named 'StartupDevelopment' or 'Startup' could not be found in assembly 'ConsoleApp1'.
Guess what happens when you follow the "tip" from the exception above?
System.InvalidOperationException: A method named 'ConfigureDevelopment' or 'Configure' in the type 'ConsoleApp1.Startup' could not be found.
Programming by failure, yeah baby! No problem this will only quadruple my estimate of how long it will take to get this application shipped.. Oh, and can you guess what interface the Startup
class implements? That’s right, nothing, nada, zip. You smell that? I smell smoke..
ConsoleApp1
public class Program
{
public void Main(string[] args)
{
var config = new Configuration()
.AddJsonFile("config.json")
.AddCommandLine(args);
Console.WriteLine(config.Get("message"));
foreach (var arg in args)
{
Console.WriteLine(arg);
}
Console.ReadLine();
}
}
Startup.cs
public class Startup
{
public IConfiguration Configuration { get; set; }
public Startup(IHostingEnvironment env)
{
// Setup configuration sources.
Configuration = new Configuration()
.AddJsonFile("config.json")
.AddEnvironmentVariables();
}
}
project.json
"commands": {
"ConsoleApp1": "ConsoleApp1",
"web": "Microsoft.AspNet.Hosting --server Microsoft.AspNet.Server.WebListener --server.urls http://localhost:5000/"
}
Any of you "old timers" recall global.asax
and its use of convention based application events? Application_Error(object send, EventArgs e)
You smell that? Who wants to poor through articles and documentation because they can’t remember the "convention"? It’s like they’ve declared, "Screw OOP, screw compile-time checking, screw Visual Studio and its advanced intellisense, let them eat documentation."
Please edify me on this madness…
I think so far you have been the victim of a few "one-off unicorns". The whole Microsoft.AspNet.Hosting
package seems to be filled with them. However, much of this is thanks to method-level dependency injection, which by its very nature can't adhere to a contract. And you're right, this is following the same trend that ASP.Net has been going in a while, for multiple platforms: Follow a convention to minimize configuration, or configure it yourself. The thing is, that's actually how many frameworks are going, so you can "get up and running" with as little code as possible.
As an example, the "web" command allows you to specify your startup assembly on the command line using --app
or from a default Microsoft.AspNet.Hosting.ini config file using Hosting:Application
. The fact that you don't have to construct your own WebHostBuilder
is due to the conventions-based-programming, but you can certainly start with your own Program.cs and build it yourself if you prefer.
Middleware classes are yet another example of method-level injection - many parameters can be added directly from the application's IServiceProvider
. I don't think I can add anything helpful to what Tugberk said in the blog post; it's a good read.
I know you didn't ask about controllers, but Actions in Controllers are similar; you're not going to find an interface on them in any of the popular MVC frameworks. Instead, the parameters are dependency-injected in using model binding. Currently, the MVC6 team allows a [FromServices]
decoration to inject request-level dependencies in via Model Binding, but I have heard tales that they're considering removing it. I hope they set it up to inject more consistently directly from the service provider so we can use it in our own frameworks.
I honestly wish there were a good way to reference a "method group" (as the compiler calls it) without knowing the parameters so that we could do more method-level dependency-injection and less adherence to specific interfaces, myself. I also wish there was a good way to declare an interface that it had a method, and who cares what the arguments are.
public interface IMiddleware
{
// No syntax for this
Task Invoke(... any);
}
// No syntax for this either, unless you know in advance what the parameters will be
await serviceProvider.InjectAndInvoke(middleware.Invoke);
If C# were to add this to the spec, then we could definitely have the interfaces you're looking for.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With