Real-World Examples
Learn how to use PyNurseInjector in various scenarios with these practical examples.
Basic Web API Example
A simple Web API project demonstrating basic PyNurseInjector setup.
Project Structure
BookStore.Api/
├── Controllers/
│ └── BooksController.cs
├── Services/
│ ├── IBookService.cs
│ └── BookService.cs
├── Repositories/
│ ├── IBookRepository.cs
│ └── BookRepository.cs
├── Models/
│ └── Book.cs
└── Program.cs
Program.cs
using DotNurse.Injector;
using DotNurse.Injector.AspNetCore;
var builder = WebApplication.CreateBuilder(args);
// Enable property injection
builder.Host.UseDotNurseInjector();
// Add services
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// Register all services and repositories
builder.Services.AddServicesFrom("BookStore.Api.Services");
builder.Services.AddServicesFrom("BookStore.Api.Repositories", ServiceLifetime.Scoped);
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
Models/Book.cs
namespace BookStore.Api.Models;
public class Book
{
public int Id { get; set; }
public string Title { get; set; }
public string Author { get; set; }
public string ISBN { get; set; }
public decimal Price { get; set; }
public DateTime PublishedDate { get; set; }
}
Services/IBookService.cs & BookService.cs
namespace BookStore.Api.Services;
public interface IBookService
{
Task<IEnumerable<Book>> GetAllBooksAsync();
Task<Book> GetBookByIdAsync(int id);
Task<Book> CreateBookAsync(Book book);
Task<Book> UpdateBookAsync(int id, Book book);
Task DeleteBookAsync(int id);
}
public class BookService : IBookService
{
[InjectService]
private readonly IBookRepository bookRepository;
[InjectService]
private readonly ILogger<BookService> logger;
public async Task<IEnumerable<Book>> GetAllBooksAsync()
{
logger.LogInformation("Fetching all books");
return await bookRepository.GetAllAsync();
}
public async Task<Book> GetBookByIdAsync(int id)
{
logger.LogInformation("Fetching book with ID: {BookId}", id);
var book = await bookRepository.GetByIdAsync(id);
if (book == null)
{
logger.LogWarning("Book with ID {BookId} not found", id);
throw new KeyNotFoundException($"Book with ID {id} not found");
}
return book;
}
public async Task<Book> CreateBookAsync(Book book)
{
logger.LogInformation("Creating new book: {Title}", book.Title);
return await bookRepository.CreateAsync(book);
}
public async Task<Book> UpdateBookAsync(int id, Book book)
{
logger.LogInformation("Updating book with ID: {BookId}", id);
book.Id = id;
return await bookRepository.UpdateAsync(book);
}
public async Task DeleteBookAsync(int id)
{
logger.LogInformation("Deleting book with ID: {BookId}", id);
await bookRepository.DeleteAsync(id);
}
}
Controllers/BooksController.cs
namespace BookStore.Api.Controllers;
[ApiController]
[Route("api/[controller]")]
public class BooksController : ControllerBase
{
[InjectService]
public IBookService BookService { get; private set; }
[HttpGet]
public async Task<ActionResult<IEnumerable<Book>>> GetBooks()
{
var books = await BookService.GetAllBooksAsync();
return Ok(books);
}
[HttpGet("{id}")]
public async Task<ActionResult<Book>> GetBook(int id)
{
try
{
var book = await BookService.GetBookByIdAsync(id);
return Ok(book);
}
catch (KeyNotFoundException)
{
return NotFound();
}
}
[HttpPost]
public async Task<ActionResult<Book>> CreateBook(Book book)
{
var createdBook = await BookService.CreateBookAsync(book);
return CreatedAtAction(nameof(GetBook), new { id = createdBook.Id }, createdBook);
}
[HttpPut("{id}")]
public async Task<IActionResult> UpdateBook(int id, Book book)
{
try
{
await BookService.UpdateBookAsync(id, book);
return NoContent();
}
catch (KeyNotFoundException)
{
return NotFound();
}
}
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteBook(int id)
{
try
{
await BookService.DeleteBookAsync(id);
return NoContent();
}
catch (KeyNotFoundException)
{
return NotFound();
}
}
}
Repository Pattern Example
Implementing a generic repository pattern with PyNurseInjector.
Generic Repository Interface
namespace MyApp.Core.Repositories;
public interface IRepository<T> where T : class
{
Task<T> GetByIdAsync(int id);
Task<IEnumerable<T>> GetAllAsync();
Task<IEnumerable<T>> FindAsync(Expression<Func<T, bool>> predicate);
Task<T> AddAsync(T entity);
Task UpdateAsync(T entity);
Task DeleteAsync(T entity);
Task<int> SaveChangesAsync();
}
public interface IUserRepository : IRepository<User>
{
Task<User> GetByEmailAsync(string email);
Task<IEnumerable<User>> GetActiveUsersAsync();
}
public interface IProductRepository : IRepository<Product>
{
Task<IEnumerable<Product>> GetByCategoryAsync(string category);
Task<IEnumerable<Product>> GetTopSellingAsync(int count);
}
Base Repository Implementation
namespace MyApp.Infrastructure.Repositories;
public abstract class BaseRepository<T> : IRepository<T> where T : class
{
[InjectService]
protected AppDbContext Context { get; private set; }
[InjectService]
protected ILogger<BaseRepository<T>> Logger { get; private set; }
protected DbSet<T> DbSet => Context.Set<T>();
public virtual async Task<T> GetByIdAsync(int id)
{
return await DbSet.FindAsync(id);
}
public virtual async Task<IEnumerable<T>> GetAllAsync()
{
return await DbSet.ToListAsync();
}
public virtual async Task<IEnumerable<T>> FindAsync(Expression<Func<T, bool>> predicate)
{
return await DbSet.Where(predicate).ToListAsync();
}
public virtual async Task<T> AddAsync(T entity)
{
await DbSet.AddAsync(entity);
return entity;
}
public virtual async Task UpdateAsync(T entity)
{
DbSet.Update(entity);
}
public virtual async Task DeleteAsync(T entity)
{
DbSet.Remove(entity);
}
public async Task<int> SaveChangesAsync()
{
return await Context.SaveChangesAsync();
}
}
Specific Repository Implementations
namespace MyApp.Infrastructure.Repositories;
[RegisterAs(typeof(IUserRepository))]
[ServiceLifeTime(ServiceLifetime.Scoped)]
public class UserRepository : BaseRepository<User>, IUserRepository
{
public async Task<User> GetByEmailAsync(string email)
{
Logger.LogDebug("Getting user by email: {Email}", email);
return await DbSet.FirstOrDefaultAsync(u => u.Email == email);
}
public async Task<IEnumerable<User>> GetActiveUsersAsync()
{
return await DbSet
.Where(u => u.IsActive && !u.IsDeleted)
.OrderBy(u => u.Name)
.ToListAsync();
}
}
[RegisterAs(typeof(IProductRepository))]
[ServiceLifeTime(ServiceLifetime.Scoped)]
public class ProductRepository : BaseRepository<Product>, IProductRepository
{
public async Task<IEnumerable<Product>> GetByCategoryAsync(string category)
{
return await DbSet
.Where(p => p.Category == category && p.IsAvailable)
.ToListAsync();
}
public async Task<IEnumerable<Product>> GetTopSellingAsync(int count)
{
return await DbSet
.OrderByDescending(p => p.SalesCount)
.Take(count)
.ToListAsync();
}
}
Registration
// In Program.cs
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
// Register all repositories that inherit from BaseRepository
builder.Services.AddServicesFrom("MyApp.Infrastructure.Repositories", ServiceLifetime.Scoped, options =>
{
options.ImplementationBase = typeof(BaseRepository<>);
});
Multi-Layer Architecture
Complete example of a multi-layered application using PyNurseInjector.
Solution Structure
MyEcommerce/
├── MyEcommerce.Domain/ # Domain entities and interfaces
│ ├── Entities/
│ ├── Interfaces/
│ └── ValueObjects/
├── MyEcommerce.Application/ # Business logic and DTOs
│ ├── Services/
│ ├── DTOs/
│ └── Mappings/
├── MyEcommerce.Infrastructure/ # Data access and external services
│ ├── Repositories/
│ ├── Services/
│ └── DbContext/
└── MyEcommerce.Api/ # Web API layer
├── Controllers/
├── Middleware/
└── Program.cs
Domain Layer
// MyEcommerce.Domain/Interfaces/IOrderService.cs
namespace MyEcommerce.Domain.Interfaces;
public interface IOrderService
{
Task<Order> CreateOrderAsync(CreateOrderDto orderDto);
Task<Order> GetOrderByIdAsync(int orderId);
Task<bool> ProcessPaymentAsync(int orderId, PaymentDto paymentDto);
Task UpdateOrderStatusAsync(int orderId, OrderStatus status);
}
// MyEcommerce.Domain/Interfaces/IInventoryService.cs
public interface IInventoryService
{
Task<bool> CheckAvailabilityAsync(int productId, int quantity);
Task ReserveStockAsync(int productId, int quantity);
Task ReleaseStockAsync(int productId, int quantity);
}
Application Layer
// MyEcommerce.Application/Services/OrderService.cs
namespace MyEcommerce.Application.Services;
[RegisterAs(typeof(IOrderService))]
[ServiceLifeTime(ServiceLifetime.Scoped)]
public class OrderService : IOrderService
{
[InjectService] private IOrderRepository orderRepository;
[InjectService] private IProductRepository productRepository;
[InjectService] private IInventoryService inventoryService;
[InjectService] private IPaymentService paymentService;
[InjectService] private INotificationService notificationService;
[InjectService] private ILogger<OrderService> logger;
public async Task<Order> CreateOrderAsync(CreateOrderDto orderDto)
{
logger.LogInformation("Creating order for customer {CustomerId}", orderDto.CustomerId);
// Validate products availability
foreach (var item in orderDto.Items)
{
var isAvailable = await inventoryService.CheckAvailabilityAsync(item.ProductId, item.Quantity);
if (!isAvailable)
{
throw new BusinessException($"Product {item.ProductId} is not available");
}
}
// Reserve inventory
foreach (var item in orderDto.Items)
{
await inventoryService.ReserveStockAsync(item.ProductId, item.Quantity);
}
// Create order
var order = new Order
{
CustomerId = orderDto.CustomerId,
OrderDate = DateTime.UtcNow,
Status = OrderStatus.Pending,
Items = orderDto.Items.Select(i => new OrderItem
{
ProductId = i.ProductId,
Quantity = i.Quantity,
Price = i.Price
}).ToList()
};
await orderRepository.AddAsync(order);
await orderRepository.SaveChangesAsync();
// Send notification
await notificationService.SendOrderConfirmationAsync(order);
return order;
}
public async Task<bool> ProcessPaymentAsync(int orderId, PaymentDto paymentDto)
{
var order = await orderRepository.GetByIdAsync(orderId);
if (order == null)
{
throw new NotFoundException($"Order {orderId} not found");
}
var paymentResult = await paymentService.ProcessPaymentAsync(order, paymentDto);
if (paymentResult.Success)
{
order.Status = OrderStatus.Paid;
order.PaymentId = paymentResult.TransactionId;
await orderRepository.UpdateAsync(order);
await orderRepository.SaveChangesAsync();
await notificationService.SendPaymentConfirmationAsync(order);
}
return paymentResult.Success;
}
}
Infrastructure Layer
// MyEcommerce.Infrastructure/Services/EmailNotificationService.cs
namespace MyEcommerce.Infrastructure.Services;
[RegisterAs(typeof(INotificationService))]
[ServiceLifeTime(ServiceLifetime.Singleton)]
public class EmailNotificationService : INotificationService
{
[InjectService] private IEmailSender emailSender;
[InjectService] private ITemplateEngine templateEngine;
[InjectService] private IConfiguration configuration;
public async Task SendOrderConfirmationAsync(Order order)
{
var template = await templateEngine.RenderAsync("OrderConfirmation", order);
var email = new EmailMessage
{
To = order.Customer.Email,
Subject = $"Order Confirmation - #{order.Id}",
Body = template
};
await emailSender.SendAsync(email);
}
public async Task SendPaymentConfirmationAsync(Order order)
{
var template = await templateEngine.RenderAsync("PaymentConfirmation", order);
var email = new EmailMessage
{
To = order.Customer.Email,
Subject = $"Payment Confirmation - Order #{order.Id}",
Body = template
};
await emailSender.SendAsync(email);
}
}
API Layer Configuration
// MyEcommerce.Api/Program.cs
var builder = WebApplication.CreateBuilder(args);
// Enable property injection
builder.Host.UseDotNurseInjector();
// Add services
builder.Services.AddControllers();
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
// Register services from all layers
builder.Services.AddServicesFrom("MyEcommerce.Application.Services", ServiceLifetime.Scoped);
builder.Services.AddServicesFrom("MyEcommerce.Infrastructure.Repositories", ServiceLifetime.Scoped);
builder.Services.AddServicesFrom("MyEcommerce.Infrastructure.Services", ServiceLifetime.Singleton);
// Add AutoMapper
builder.Services.AddAutoMapper(typeof(MappingProfile));
// Add authentication
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
// JWT configuration
});
var app = builder.Build();
// Middleware pipeline
app.UseExceptionHandler("/error");
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
Background Services Example
Using PyNurseInjector with hosted services and background tasks.
Order Processing Background Service
namespace MyApp.Services.Background;
[RegisterAs(typeof(IHostedService))]
[ServiceLifeTime(ServiceLifetime.Singleton)]
public class OrderProcessingService : BackgroundService
{
[InjectService] private IServiceProvider serviceProvider;
[InjectService] private ILogger<OrderProcessingService> logger;
[InjectService] private IConfiguration configuration;
private readonly TimeSpan _period;
public OrderProcessingService()
{
_period = TimeSpan.FromMinutes(5); // Process every 5 minutes
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
logger.LogInformation("Order Processing Service is starting.");
using PeriodicTimer timer = new(_period);
while (!stoppingToken.IsCancellationRequested &&
await timer.WaitForNextTickAsync(stoppingToken))
{
await ProcessPendingOrdersAsync(stoppingToken);
}
}
private async Task ProcessPendingOrdersAsync(CancellationToken cancellationToken)
{
try
{
using var scope = serviceProvider.CreateScope();
// Resolve scoped services
var orderService = scope.ServiceProvider.GetRequiredService<IOrderService>();
var orderRepository = scope.ServiceProvider.GetRequiredService<IOrderRepository>();
var pendingOrders = await orderRepository.GetPendingOrdersAsync();
logger.LogInformation("Processing {Count} pending orders", pendingOrders.Count());
foreach (var order in pendingOrders)
{
if (cancellationToken.IsCancellationRequested)
break;
try
{
await orderService.ProcessOrderAsync(order.Id);
logger.LogInformation("Successfully processed order {OrderId}", order.Id);
}
catch (Exception ex)
{
logger.LogError(ex, "Error processing order {OrderId}", order.Id);
}
}
}
catch (Exception ex)
{
logger.LogError(ex, "Error in order processing service");
}
}
}
Email Queue Service
[RegisterAs(typeof(IHostedService))]
[ServiceLifeTime(ServiceLifetime.Singleton)]
public class EmailQueueService : BackgroundService
{
[InjectService] private IEmailQueue emailQueue;
[InjectService] private IEmailSender emailSender;
[InjectService] private ILogger<EmailQueueService> logger;
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
logger.LogInformation("Email Queue Service is starting.");
while (!stoppingToken.IsCancellationRequested)
{
try
{
var email = await emailQueue.DequeueAsync(stoppingToken);
if (email != null)
{
await SendEmailWithRetryAsync(email, stoppingToken);
}
else
{
// No emails in queue, wait a bit
await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
}
}
catch (OperationCanceledException)
{
// Service is stopping
break;
}
catch (Exception ex)
{
logger.LogError(ex, "Error in email queue service");
await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken);
}
}
}
private async Task SendEmailWithRetryAsync(EmailMessage email, CancellationToken cancellationToken)
{
const int maxRetries = 3;
var retryCount = 0;
while (retryCount < maxRetries)
{
try
{
await emailSender.SendAsync(email);
logger.LogInformation("Email sent successfully to {Recipient}", email.To);
break;
}
catch (Exception ex)
{
retryCount++;
logger.LogWarning(ex, "Failed to send email (attempt {Attempt}/{MaxAttempts})",
retryCount, maxRetries);
if (retryCount >= maxRetries)
{
logger.LogError("Failed to send email after {MaxAttempts} attempts", maxRetries);
// Could move to dead letter queue here
break;
}
await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, retryCount)), cancellationToken);
}
}
}
}
Unit Testing with PyNurseInjector
How to write unit tests for services using PyNurseInjector.
Test Setup
using Microsoft.Extensions.DependencyInjection;
using Xunit;
using Moq;
public class ServiceTestBase : IDisposable
{
protected IServiceProvider ServiceProvider { get; }
protected IServiceCollection Services { get; }
public ServiceTestBase()
{
Services = new ServiceCollection();
// Add logging
Services.AddLogging(builder => builder.AddDebug());
// Register test services
RegisterTestServices();
// Build service provider
ServiceProvider = Services.BuildServiceProvider();
}
protected virtual void RegisterTestServices()
{
// Override in derived classes
}
protected T GetService<T>() where T : notnull
{
return ServiceProvider.GetRequiredService<T>();
}
public void Dispose()
{
if (ServiceProvider is IDisposable disposable)
{
disposable.Dispose();
}
}
}
Testing Services with Mocked Dependencies
public class BookServiceTests : ServiceTestBase
{
private readonly Mock<IBookRepository> _bookRepositoryMock;
private readonly Mock<ILogger<BookService>> _loggerMock;
public BookServiceTests()
{
_bookRepositoryMock = new Mock<IBookRepository>();
_loggerMock = new Mock<ILogger<BookService>>();
}
protected override void RegisterTestServices()
{
// Register mocks
Services.AddSingleton(_bookRepositoryMock.Object);
Services.AddSingleton(_loggerMock.Object);
// Register service under test
Services.AddTransient<BookService>();
// Enable property injection for tests
Services.AddSingleton<IServiceProvider>(provider =>
new DotNurseServiceProvider(provider));
}
[Fact]
public async Task GetBookByIdAsync_WhenBookExists_ReturnsBook()
{
// Arrange
var bookId = 1;
var expectedBook = new Book
{
Id = bookId,
Title = "Test Book",
Author = "Test Author"
};
_bookRepositoryMock
.Setup(x => x.GetByIdAsync(bookId))
.ReturnsAsync(expectedBook);
var bookService = GetService<BookService>();
// Act
var result = await bookService.GetBookByIdAsync(bookId);
// Assert
Assert.NotNull(result);
Assert.Equal(expectedBook.Id, result.Id);
Assert.Equal(expectedBook.Title, result.Title);
_bookRepositoryMock.Verify(x => x.GetByIdAsync(bookId), Times.Once);
}
[Fact]
public async Task GetBookByIdAsync_WhenBookNotFound_ThrowsException()
{
// Arrange
var bookId = 999;
_bookRepositoryMock
.Setup(x => x.GetByIdAsync(bookId))
.ReturnsAsync((Book)null);
var bookService = GetService<BookService>();
// Act & Assert
await Assert.ThrowsAsync<KeyNotFoundException>(
() => bookService.GetBookByIdAsync(bookId));
_loggerMock.Verify(
x => x.Log(
LogLevel.Warning,
It.IsAny<EventId>(),
It.Is<It.IsAnyType>((o, t) => o.ToString().Contains($"Book with ID {bookId} not found")),
It.IsAny<Exception>(),
It.IsAny<Func<It.IsAnyType, Exception, string>>()),
Times.Once);
}
}
Integration Testing
public class BookApiIntegrationTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly WebApplicationFactory<Program> _factory;
private readonly HttpClient _client;
public BookApiIntegrationTests(WebApplicationFactory<Program> factory)
{
_factory = factory.WithWebHostBuilder(builder =>
{
builder.ConfigureServices(services =>
{
// Remove real database
var descriptor = services.SingleOrDefault(
d => d.ServiceType == typeof(DbContextOptions<AppDbContext>));
if (descriptor != null)
{
services.Remove(descriptor);
}
// Add in-memory database for testing
services.AddDbContext<AppDbContext>(options =>
{
options.UseInMemoryDatabase("TestDb");
});
// Ensure PyNurseInjector registrations
services.AddServicesFrom("BookStore.Api.Services");
services.AddServicesFrom("BookStore.Api.Repositories");
});
});
_client = _factory.CreateClient();
}
[Fact]
public async Task GetBooks_ReturnsSuccessAndCorrectContentType()
{
// Act
var response = await _client.GetAsync("/api/books");
// Assert
response.EnsureSuccessStatusCode();
Assert.Equal("application/json; charset=utf-8",
response.Content.Headers.ContentType.ToString());
}
[Fact]
public async Task CreateBook_WithValidData_ReturnsCreatedBook()
{
// Arrange
var newBook = new Book
{
Title = "Integration Test Book",
Author = "Test Author",
ISBN = "123-456-789",
Price = 29.99m
};
var json = JsonSerializer.Serialize(newBook);
var content = new StringContent(json, Encoding.UTF8, "application/json");
// Act
var response = await _client.PostAsync("/api/books", content);
// Assert
response.EnsureSuccessStatusCode();
Assert.Equal(HttpStatusCode.Created, response.StatusCode);
var responseContent = await response.Content.ReadAsStringAsync();
var createdBook = JsonSerializer.Deserialize<Book>(responseContent);
Assert.NotNull(createdBook);
Assert.Equal(newBook.Title, createdBook.Title);
Assert.True(createdBook.Id > 0);
}
}
Advanced Scenarios
Complex use cases and advanced patterns with PyNurseInjector.
Decorator Pattern
// Base service interface
public interface ICacheService
{
Task<T> GetAsync<T>(string key);
Task SetAsync<T>(string key, T value, TimeSpan? expiry = null);
}
// Primary implementation
[RegisterAs(typeof(ICacheService))]
public class RedisCacheService : ICacheService
{
[InjectService] private IConnectionMultiplexer redis;
public async Task<T> GetAsync<T>(string key)
{
var db = redis.GetDatabase();
var value = await db.StringGetAsync(key);
return value.HasValue ? JsonSerializer.Deserialize<T>(value) : default;
}
public async Task SetAsync<T>(string key, T value, TimeSpan? expiry = null)
{
var db = redis.GetDatabase();
var json = JsonSerializer.Serialize(value);
await db.StringSetAsync(key, json, expiry);
}
}
// Decorator with logging
[DontRegister] // Manual registration required for decorators
public class LoggingCacheDecorator : ICacheService
{
private readonly ICacheService _innerCache;
[InjectService] private ILogger<LoggingCacheDecorator> logger;
public LoggingCacheDecorator(ICacheService innerCache)
{
_innerCache = innerCache;
}
public async Task<T> GetAsync<T>(string key)
{
logger.LogDebug("Getting cache value for key: {Key}", key);
var value = await _innerCache.GetAsync<T>(key);
logger.LogDebug("Cache {Result} for key: {Key}",
value != null ? "hit" : "miss", key);
return value;
}
public async Task SetAsync<T>(string key, T value, TimeSpan? expiry = null)
{
logger.LogDebug("Setting cache value for key: {Key}", key);
await _innerCache.SetAsync(key, value, expiry);
}
}
// Registration with decorator
services.AddServicesFrom("MyApp.Services");
services.Decorate<ICacheService, LoggingCacheDecorator>();
Factory Pattern
// Factory interface
public interface IPaymentProcessorFactory
{
IPaymentProcessor GetProcessor(PaymentMethod method);
}
// Payment processor interface
public interface IPaymentProcessor
{
Task<PaymentResult> ProcessPaymentAsync(PaymentRequest request);
PaymentMethod SupportedMethod { get; }
}
// Implementations
[RegisterAs(typeof(IPaymentProcessor))]
public class CreditCardProcessor : IPaymentProcessor
{
[InjectService] private ICreditCardGateway gateway;
public PaymentMethod SupportedMethod => PaymentMethod.CreditCard;
public async Task<PaymentResult> ProcessPaymentAsync(PaymentRequest request)
{
// Process credit card payment
return await gateway.ChargeAsync(request);
}
}
[RegisterAs(typeof(IPaymentProcessor))]
public class PayPalProcessor : IPaymentProcessor
{
[InjectService] private IPayPalGateway gateway;
public PaymentMethod SupportedMethod => PaymentMethod.PayPal;
public async Task<PaymentResult> ProcessPaymentAsync(PaymentRequest request)
{
// Process PayPal payment
return await gateway.ProcessAsync(request);
}
}
// Factory implementation
[RegisterAs(typeof(IPaymentProcessorFactory))]
[ServiceLifeTime(ServiceLifetime.Singleton)]
public class PaymentProcessorFactory : IPaymentProcessorFactory
{
private readonly Dictionary<PaymentMethod, IPaymentProcessor> _processors;
public PaymentProcessorFactory(IEnumerable<IPaymentProcessor> processors)
{
_processors = processors.ToDictionary(p => p.SupportedMethod);
}
public IPaymentProcessor GetProcessor(PaymentMethod method)
{
if (!_processors.TryGetValue(method, out var processor))
{
throw new NotSupportedException($"Payment method {method} is not supported");
}
return processor;
}
}
Multi-Tenant Services
// Tenant context
public interface ITenantContext
{
string TenantId { get; }
string ConnectionString { get; }
}
// Multi-tenant repository
public abstract class MultiTenantRepository<T> : IRepository<T> where T : class
{
[InjectService] private ITenantContext tenantContext;
[InjectService] private IDbContextFactory<AppDbContext> contextFactory;
protected AppDbContext GetDbContext()
{
var optionsBuilder = new DbContextOptionsBuilder<AppDbContext>();
optionsBuilder.UseSqlServer(tenantContext.ConnectionString);
return new AppDbContext(optionsBuilder.Options);
}
public async Task<T> GetByIdAsync(int id)
{
using var context = GetDbContext();
return await context.Set<T>().FindAsync(id);
}
// Other repository methods...
}
// Tenant-specific service
[RegisterAs(typeof(IProductService))]
[ServiceLifeTime(ServiceLifetime.Scoped)]
public class TenantProductService : IProductService
{
[InjectService] private ITenantContext tenantContext;
[InjectService] private IProductRepository productRepository;
[InjectService] private ICacheService cache;
public async Task<IEnumerable<Product>> GetProductsAsync()
{
var cacheKey = $"products_{tenantContext.TenantId}";
var cachedProducts = await cache.GetAsync<IEnumerable<Product>>(cacheKey);
if (cachedProducts != null)
{
return cachedProducts;
}
var products = await productRepository.GetAllAsync();
await cache.SetAsync(cacheKey, products, TimeSpan.FromMinutes(5));
return products;
}
}
Migration Guide
How to migrate existing projects to use PyNurseInjector.
From Manual Registration
Before (Manual Registration)
// Startup.cs - Lots of manual registrations
public void ConfigureServices(IServiceCollection services)
{
// Services
services.AddScoped<IUserService, UserService>();
services.AddScoped<IProductService, ProductService>();
services.AddScoped<IOrderService, OrderService>();
services.AddScoped<IPaymentService, PaymentService>();
services.AddScoped<IShippingService, ShippingService>();
services.AddScoped<IInventoryService, InventoryService>();
services.AddScoped<INotificationService, NotificationService>();
// Repositories
services.AddScoped<IUserRepository, UserRepository>();
services.AddScoped<IProductRepository, ProductRepository>();
services.AddScoped<IOrderRepository, OrderRepository>();
// Singletons
services.AddSingleton<ICacheService, RedisCacheService>();
services.AddSingleton<IConfigurationService, ConfigurationService>();
// ... many more registrations
}
After (PyNurseInjector)
// Program.cs - Clean and simple
builder.Host.UseDotNurseInjector(); // Enable property injection
// Register all services automatically
builder.Services.AddServicesFrom("MyApp.Services", ServiceLifetime.Scoped);
builder.Services.AddServicesFrom("MyApp.Repositories", ServiceLifetime.Scoped);
// Special cases only
builder.Services.AddServicesFrom("MyApp.Services.Cache", ServiceLifetime.Singleton);
From Constructor Injection
Before (Constructor Injection)
public class OrderController : Controller
{
private readonly IOrderService _orderService;
private readonly IUserService _userService;
private readonly IProductService _productService;
private readonly IPaymentService _paymentService;
private readonly IShippingService _shippingService;
private readonly ILogger<OrderController> _logger;
public OrderController(
IOrderService orderService,
IUserService userService,
IProductService productService,
IPaymentService paymentService,
IShippingService shippingService,
ILogger<OrderController> logger)
{
_orderService = orderService;
_userService = userService;
_productService = productService;
_paymentService = paymentService;
_shippingService = shippingService;
_logger = logger;
}
// Methods using the injected services
}
After (Property Injection)
public class OrderController : Controller
{
[InjectService] public IOrderService OrderService { get; private set; }
[InjectService] public IUserService UserService { get; private set; }
[InjectService] public IProductService ProductService { get; private set; }
[InjectService] public IPaymentService PaymentService { get; private set; }
[InjectService] public IShippingService ShippingService { get; private set; }
[InjectService] public ILogger<OrderController> Logger { get; private set; }
// No constructor needed!
// Methods can directly use the properties
}
Step-by-Step Migration
-
Install PyNurseInjector packages
dotnet add package DotNurse.Injector dotnet add package DotNurse.Injector.AspNetCore -
Enable property injection in Program.cs
builder.Host.UseDotNurseInjector(); -
Replace manual registrations with namespace registration
// Remove individual registrations // Add namespace-based registration services.AddServicesFrom("YourApp.Services"); -
Add attributes to special cases
[ServiceLifeTime(ServiceLifetime.Singleton)] public class CacheService : ICacheService { } -
Convert constructors to property injection (optional)
[InjectService] public IMyService MyService { get; private set; } -
Test thoroughly
- Verify all services are registered correctly
- Check service lifetimes
- Ensure property injection works