Skip to content

Introduction

Dovetail overview diagram

The goal of Dovetail is to be a simple way to bridge the gap between Configuration driven development and Dovetail driven development. In an ASP.NET Core application it is “pay-for-play” in that you must register all your configurations, services, etc. This is great but it also forces you into cases where if you forget to call an extension method nothing lights up like you expected. Another draw back of using extension methods is that any dependencies you need, to configure services for example, must be included in the method call.

One thing to keep in mind is that ASP.NET Core just got rid of the double container problem, we are not introducing a second container, instead we just implement IServiceProvider using the same dictionary that is provided by IHostBuilder.Properties. Any services that you inject into your joints must exist inside of this dictionary, or be optional.

At a high level Dovetail solves this problem in a very similar way to something like Autofac Modules or other similar assembly scanning solutions. Our goal is to be agnostic at a high level, strictly speaking using MSDI/MSEL/MSC is not required however is currently assumed that .NET Platform Extensions are going to be used. In the future this might be broken out.

You have to make a choice with every library you introduce, and using Dovetail is no different. If it’s not for you, that’s okay! Here are some possible reasons why and why not to use Dovetail.

  • You can create library code that “just works” without having to ensure that the correct extension method was called, with the correct parameters, for every library you add.
  • You can don’t have to worry about service registration extension methods in instead each module can setup it’s own registrations.
  • You can can group bootstrapping logic together to lessen cognitive load.

Everything has it’s positives and negatives. We cannot lie using joints will add time to starting up your application. There is a trade off to be made here and optimizations that can be made to lessen that trade-off.

Fundamentally if you’re making a highly focused, and specialized microservice then using Dovetail may not be for you. You will own the burden to ensuring that configuration is done the same, extension methods are called, and everything else. It is called “pay-to-play” after all.

A quick note on performance. We are now using Source Generators to generate export methods, that return all joints for a given assembly. This allows joint loading to have similar performance characteristics to pure extension methods, it’s still slower but reduces the need to assembly scan.

The xref:Joinery.Dovetail.ICompiledTypeProvider is now generated by source generator at compile time, further reducing the need to assembly scan at runtime.

A joint is nothing but a class that implements the xref:Joinery.Dovetail.Infrastructure.IDovetailJoint interface. Nothing special. By itself xref:Joinery.Dovetail.Infrastructure.IDovetailJoint is just a marker interface used to identify a class that may implement one more joints.

Dovetails are then interfaces that are implemented by the class some examples are:

ad-hoc Dovetails (or delegate Dovetails) are a different kind of joint but written as a delegate, but they mirror the method given by the Dovetail interface. These can be used to during program startup just like ConfigureServices are used today.

Some examples are:

A joint can be exported by using the xref:Joinery.Dovetail.DovetailAttribute (eg. [assembly: Dovetail(typeof(MyDovetail))]). This is important you may want to make joints are not enabled by default but are available to be loaded by users if they want to.

The rules are straight forward.

Creating your own joints is fairly easily, and can be done by using existing joints or by executing it yourself.

A package or application that wants to add a new kind of joint declares it with a <DovetailJoint> item, and a project the SDK does not recognise declares its host with a <DovetailHost> item. Both are listed, with every value they accept, in the reference: DovetailJoint and DovetailHost.