SaucyRegistrations 1.0.0
dotnet add package SaucyRegistrations --version 1.0.0
NuGet\Install-Package SaucyRegistrations -Version 1.0.0
<PackageReference Include="SaucyRegistrations" Version="1.0.0" />
paket add SaucyRegistrations --version 1.0.0
#r "nuget: SaucyRegistrations, 1.0.0"
// Install SaucyRegistrations as a Cake Addin #addin nuget:?package=SaucyRegistrations&version=1.0.0 // Install SaucyRegistrations as a Cake Tool #tool nuget:?package=SaucyRegistrations&version=1.0.0
Outstanding features / issues
Issue | Link |
---|---|
Support for keyed services | https://github.com/aspeckt-112/SaucyRegistrations/issues/23 |
Allow excluding certain classes in a namespace | https://github.com/aspeckt-112/SaucyRegistrations/issues/24 |
Support for generic classes | https://github.com/aspeckt-112/SaucyRegistrations/issues/33 |
Saucy Registrations
I've worked on a few large enterprise projects that made use of a dependency injection container. On multiple different projects, at multiple different jobs, there existed some code to "automagically" register services with the container following some sort of convention. For example, classes that ended with "Repository" would be registered as a singleton, while classes that ended with "Service" would be registered as transient, etc. This code was always done with reflection, normally a pain to maintain and debug and always seemed to be a bit of a black box.
I wanted to solve the problem for myself, once and for all. I wanted to do it in a way that was quick and easy to understand.
Enter...this.
What is this?
Saucy is an incremental source generator. It provides a few attributes and an enum. Using these, it'll then generate a class within the same assembly that contains all the registrations for your services. You simply call the extension method it generates in your composition root and forget about it.
How do I use this?
See the samples for a working example. Read on if you want to know more, or you're just procrastinating at work, trying to look productive by reading documentation on GitHub.
At it's core, usage is as simple as adding a single attribute to your service class. For example:
[SaucyInclude(ServiceScope.Singleton)]
public class ExampleClass : IExample
{
}
Which is turn, generates the following code:
namespace YOUR_PROJECT.ServiceCollectionExtensions;
public static class YOUR_PROJECTServiceCollectionExtensions
{
public static void AddYOUR_PROJECTServices(IServiceCollection services)
{
services.AddSingleton<IExample, ExampleClass>();
}
}
You can then call this method in your composition root:
var serviceCollection = new ServiceCollection();
serviceCollection.AddYOUR_PROJECTServices();
In essence, that's it.
But this isn't saving me much time?
At this point you may be thinking "This isn't saving me much time, I could have just written the registration code myself". And you'd be right. But the real power of Saucy comes when you have a large project with many services, in many different namespaces. To help you with this, Saucy provides another, more powerful attribute: SaucyIncludeNamespaceWithSuffix
.
SaucyIncludeNamespaceWithSuffix
is an attribute that you apply at the assembly level. It tells Saucy to always register classes in that namespace with a specific suffix. For example:
AssemblyInfo.cs
[assembly: SaucyIncludeNamespaceWithSuffix(nameof(Saucy.Services), ServiceScope.Transient)]
With this, all classes in the Saucy.Services
namespace will be registered as transient. This is particularly useful when you have a large project with many services, and you want to register them all in the same way.
There's no limitation on the amount of attributes you can apply to an assembly, so you can have multiple namespaces with different scopes.
[assembly: SaucyIncludeNamespaceWithSuffix(nameof(Saucy.Services), ServiceScope.Transient)]
[assembly: SaucyIncludeNamespaceWithSuffix(nameof(Saucy.Builders), ServiceScope.Singleton)]
Which would generate the following code:
// <auto-generated by Saucy. DO NOT CHANGE THIS FILE!!! />
using Microsoft.Extensions.DependencyInjection;
namespace Saucy.Console.ServiceCollectionExtensions;
public static class SaucyConsoleServiceCollectionExtensions
{
public static IServiceCollection AddSaucyConsoleServices(this IServiceCollection services)
{
services.AddSingleton<Saucy.Console.Builders.IBuilderInterfaceOne, Saucy.Console.Builders.ABuilder>();
services.AddTransient<Saucy.Console.Services.IService, Saucy.Console.Services.IncludedServiceOne>();
return services;
}
}
Note: services are registered alphabetically by their fully qualified name. A purely arbitrary choice that I made.
If you need to override the default scope for a specific class, you can do so by applying the SaucyInclude
attribute to the class itself. This will override the scope set by the namespace attribute.
Anything else?
Yes! By default, abstract classes won't be registered. I'm open to feedback on this, but from experience, I can count on one hand that I've actually resolved an abstract class from the container. If you need to register an abstract class. However, if you need, you can do so by applying the SaucyRegisterAbstractClass
attribute to the class.
[SaucyInclude(ServiceScope.Transient)]
[SaucyRegisterAbstractClass]
public class SomethingUsingTheAbstractBaseClassAndTheInterface : AbstractRegistrationBaseClass, ISomeInterface
{
}
If you've got a class that implements multiple interfaces, but you only want to register it as one of them, you can apply one (or more) SaucyDoNotRegisterWithInterface
attribute(s) to the class.
[SaucyInclude(ServiceScope.Scoped)]
[SaucyDoNotRegisterWithInterface(nameof(IService))]
public class IncludedServiceWithScopeOverride : IService
{
}
Doing that will register a concrete instance of IncludedServiceWithScopeOverride
with the container, but it won't register it as an IService
.
Support for generics?
Saucy currently supports generics at an interface level, but not at a class level.
There's an open issue here: https://github.com/aspeckt-112/SaucyRegistrations/issues/33
I'm open to feedback, ideas and pull requests. Enjoy using Saucy!
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. |
.NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
.NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
.NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
MonoAndroid | monoandroid was computed. |
MonoMac | monomac was computed. |
MonoTouch | monotouch was computed. |
Tizen | tizen40 was computed. tizen60 was computed. |
Xamarin.iOS | xamarinios was computed. |
Xamarin.Mac | xamarinmac was computed. |
Xamarin.TVOS | xamarintvos was computed. |
Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|