This post will use Microsoft’s Inversion of Control (IoC) Unity container to generate HTML email messages based on Razor templates.
We would like to build a service as displayed below that consists of many small components. The service will save an order, generate a HTML email based on a template and send an email.
The problem is how can we decouple our app from concrete implementations and reduce the number of class dependencies?
The solution is to use the dependency injection (DI) pattern that implements inversion of control for resolving dependencies.
Here is an example where the OrderService class is responsible for obtaining its own references to its dependencies.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | public class OrderService { private readonly OrderRepository _repository; private readonly EmailService _emailService; public OrderService() { _repository = new OrderRepository(); _emailService = new EmailService( new TemplateLocator( "C:\templates" ), new RazaorRenderingService(), new SmtpMailClient()); } public void Create(OrderModel order) { if (order == null ) throw new ArgumentNullException( "order" ); _repository.Save(order); _emailService.Send(order.Customer.Email, "Order Created" , order); } } |
The OrderService class has many dependencies on other concrete classes, which makes it hard to swap out behaviour and to test components in isolation. For example, the RazorRendingService cannot be replaced with an XmlRenderingService without modifying the OrderService class.
Let's get started with a DI solution in 4 easy steps.
Download Source CodeAccording to the dependency inversion SOLID principle, our classes should depend on abstractions instead of concretions. We can achieve this goal by defining interfaces/contracts for each component as shown below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | public interface IOrderService { void Create(OrderModel order); } public interface IOrderRepository { void Save(OrderModel order); } public interface IEmailService { void Send<T>( string to, string subject, T body); } public interface IRenderingService { string Render<T>( string templatePath, T model); } public interface ITemplateLocator { string Locate<T>(T model); } public interface IMailClient { void Send( string to, string subject, string body); } |
Move the dependencies of each class to the constructor as shown below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 | public class OrderService : IOrderService { private readonly IOrderRepository _repository; private readonly IEmailService _emailService; public OrderService(IOrderRepository repository, IEmailService emailService) { if (repository == null ) throw new ArgumentNullException( "repository" ); if (emailService == null ) throw new ArgumentNullException( "emailService" ); _repository = repository; _emailService = emailService; } public void Create(OrderModel order) { if (order == null ) throw new ArgumentNullException( "order" ); _repository.Save(order); _emailService.Send(order.Customer.Email, "Order Created" , order); } } public class OrderRepository : IOrderRepository { public void Save(OrderModel order) { if (order == null ) throw new ArgumentNullException( "order" ); Console.WriteLine( "Saving order: {0}" , order.Description); order.Id = 123; // Create an Id } } public class EmailService : IEmailService { private readonly ITemplateLocator _templateFinder; private readonly IRenderingService _renderService; private readonly IMailClient _mailClient; public EmailService(ITemplateLocator templateFinder, IRenderingService renderService, IMailClient mailClient) { if (templateFinder == null ) throw new ArgumentNullException( "templateFinder" ); if (renderService == null ) throw new ArgumentNullException( "renderService" ); if (mailClient == null ) throw new ArgumentNullException( "mailClient" ); _templateFinder = templateFinder; _renderService = renderService; _mailClient = mailClient; } public void Send<T>( string to, string subject, T body) { var template = _templateFinder.Locate(body); var htmlBody = _renderingService.Render(template, body); _mailClient.Send(to, subject, htmlBody); } } public class TemplateLocator : ITemplateLocator { private readonly string _basePath; public TemplateLocator( string basePath) { if (basePath == null ) throw new ArgumentNullException( "basePath" ); _basePath = basePath; } public string Locate<T>(T model) { var templateName = string .Format( "{0}.cshtml" , model.GetType().Name); return Path.Combine(_basePath, templateName); } } public class RazaorRenderingService : IRenderingService { public string Render<T>( string templatePath, T model) { using ( var templateService = new TemplateService()) { return templateService.Parse(File.ReadAllText(templatePath), model, null , null ); } } } public class SmtpMailClient : IMailClient { public void Send( string to, string subject, string body) { using ( var mesage = new MailMessage()) { mesage.Subject = subject; mesage.Body = body; mesage.IsBodyHtml = true ; mesage.To.Add( new MailAddress(to)); using ( var client = new SmtpClient()) { client.Send(mesage); } } } } |
Let's wire it all up using a DI container as shown below.
1 2 3 4 5 6 7 8 9 10 11 | var container = new UnityContainer(); container.RegisterType<IOrderRepository, OrderRepository>(); container.RegisterType<IEmailService, EmailService>(); container.RegisterType<IRenderingService, RazaorRenderingService>(); container.RegisterType<IMailClient, SmtpMailClient>(); container.RegisterType<IOrderService, OrderService>(); var baseTemplatePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "EmailTemplates" ); container.RegisterType<ITemplateLocator, TemplateLocator>( new InjectionConstructor(baseTemplatePath)); |
The razor template is shown below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | @model Orders.Interfaces.Models.OrderModel <!DOCTYPE html> < head > < meta charset = "utf-8" /> < title >Order</ title > </ head > < body > < p >Hi @Model.Customer.Name,</ p > < p >Thanks for placing the order.</ p > < p >Your order number is: @Model.Id</ p > </ body > </ html > |
The App.config is configured to write the emails to C:\temp.
Let’s create an order and get the OrderService to process it.
The email output is shown below.
In this post we have described the process of using the DI pattern to provide inversion of control for resolving dependencies with Unity.
The advantages of DI is:联系客服