Consider the following project structure for a .NET Core Web project.
MyApp.UI project (MVC) calls MyApp.BusinessRules calls MyApp.ServiceLayer calls MyApp.DbAccess project calls whatever.
In the front-end project (MyApp.UI), I've defined the logger. I would like to get access to this instance of the logger in MyApp.DbAccess project (or any downstream project) without passing it up via parameters from controller actions. I also can't do dependency injection that deep in the structure.
So how does one get access to the logger?
The logger shouldn't be defined in the UI layer. It is a cross-cutting concern hence you should define it in an external library and reference that in all the projects you need logging.
If you only want to log exceptions, you could define a global exception handler, let the exceptions bubble up to the UI layer and log them there. Example here
In the case you really want to pass your Logger all the way down, you could use Action as parameter in methods and pass a lambda with the method call you want invoked downstream.
public class Logger
{
public string Message { get; set; }
public void Log(string message)
{
Message = message;
}
}
public class Business
{
public void DoWork(int id, Action<string> logAction)
{
if (id < 0)
{
logAction("The string is less than zero");
}
}
}
Calling code
Logger logger = new Logger();
Business business = new Business();
business.DoWork(-1, (message) => logger.Log(message));
Related
I am using VS 2017 and .NET Core.
Using Dependency Injection, I would like to register my service at runtime, dynamically. My goal is to write instances of my service that implement the service interface inside of separate assemblies. The servicename/assembly name will then be added to some sort of configuration file (or db table).
My registration code would do something like this:
var ServiceTypeName = LoadServiceAssembly(AssemblyName);
var serviceProvider = new ServiceCollection()
.AddTransient<IDILogger, "ConsoleDILogger">() // <--- Goal
.BuildServiceProvider();
var logger = serviceProvider.GetService(IDILogger);
Clearly, the AddTransient line will not work as such a method does not exist. It does, however, depict the idea. I want to register the type by a string name so that the loader application need not be recompiled everytime I add a new service type.
I cannot seem to find how to do this. Any suggestions would be welcome.
TIA
You could read configured type from the settings, load the required type via reflection and register it in service collection:
// Read from config
var assemblyPath = "...";
var typeName = "...";
var assembly = Assembly.LoadFrom(assemblyPath);
var loggerType = assembly.GetType(typeName);
var serviceProvider = new ServiceCollection()
.AddTransient(typeof(IDILogger), loggerType)
.BuildServiceProvider();
var logger = serviceProvider.GetService<IDILogger>();
Such dynamic approach will not require any recompilation if you add or reconfigure new logger.
That's obviously not possible as is, however, I used something similar to this in a project to avoid having to add each new type to the container:
var assembly = typeof(YourClass).Assembly; // I actually use Assembly.LoadFile with well-known names
var types = assembly.ExportedTypes
// filter types that are unrelated
.Where(x => x.IsClass && x.IsPublic);
foreach (var type in types)
{
// assume that we want to inject any class that implements an interface
// whose name is the type's name prefixed with I
services.AddScoped(type.GetInterface($"I{type.Name}"), type);
}
For your specific case, you could even make this shorter:
var type = assembly.ExportedTypes.First(x => x.Name == runtimeName);
services.AddScoped(typeof(IDILogger), type);
A very genuine question and with references to different answers by users, here's how I have solved in .NET 6
In program.cs added the following
//Register Service Modules to DI
builder.Services.IncludeServiceModule(builder.Configuration);
The called static function contains something like this
public static class ServiceModule
{
public static IServiceCollection IncludeServiceModule(this IServiceCollection services,
IConfiguration configuration)
{
var appServices = System.Reflection.Assembly.Load("FMDeBill.Service").GetTypes().Where(s => s.Name.EndsWith("Service") && s.IsInterface == false).ToList();
foreach (var appService in appServices)
//services.AddTransient(appService.GetInterface($"I{appService.Name}"), appService);
services.Add(new ServiceDescriptor(appService, appService, ServiceLifetime.Scoped));
return services;
}
}
The assembly name is the name of the project/assembly with services. Any service that is not an interface and ends with "Service" such as "CategoryService" is registered dynamically.
Auto-Register Dependency Injected Services in .NET Core
I wrote this method to auto-register all your services and consumer interfaces and classes at runtime for Dependency Injection by the IoC Container in .NET. All you have to do is add your interfaces and/or concrete classes to the enums lists below and the RegisterServices() method will add them for dependency injection in your .NET application. You can then add them to constructors or call them for dependency injection by .NET.
I chose to load services from an enum rather than say a JSON or other configuration file for security reasons. It also reduces dependencies and also locks the applications state, as well as forces development to lock the app to compilation. Developers must modify, add, remove service types and keep them closely coupled to the code. Changing a configuration file is too dangerous!
LET'S BEGIN
You will need to create two files then change the Startup.cs file in .NET.
Create a file called ServiceList.cs in .NET. This one is just a couple enums where you can add your list of types you want registered as services or consumers of services. If you have many classes that inherit from an Interface, just add lists of those in services. But it will accept concrete types, as well. But if you add an interface, the RegisterServices method below will locate all the child classes that implement the interface and register those, as well. The RegisterServices() method will grab them and register all your services with the IoC in .NET for you.
// ADD SERVICES YOU WANT REGISTERED
enum ServicesList
{
ISampleService,
IAnotherService,
AConcreteClassService
}
// ADD CONSUMERS YOU WANT REGISTERED
enum ConsumersList
{
MyClass1,
MyClass2,
ISomeConsumerTypes
}
Create a second class file called RegisterServices.cs. Add the following code. This is the main method that registers all the services listed in the enums above. It is called RegisterServices.cs.
// REGISTER SERVICES
// This will pull all the services you added to the ServicesList.cs
// enum and try and register them with the Services Provider in .NET
static class RegisterServices
{
// You can add the Logger here if you like.
internal static void Start(IServiceCollection services, ILogger logger = null)
{
// Extract out all service enum values into a single list.
List<string> allTypesToAdd = new List<string>();
allTypesToAdd.AddRange(Enum.GetNames(typeof(ServicesList)).ToList());
allTypesToAdd.AddRange(Enum.GetNames(typeof(ConsumersList)).ToList());
// For now I am just getting the active running assembly
Assembly assembly = Assembly.GetExecutingAssembly();
IEnumerable<TypeInfo> assemblyTypes = assembly.DefinedTypes;
List<string> missingEnumTypes = new List<string>();
bool isTypeFound = false;
// Loop through all services in the collection.
// If your service type is not listed, add it.
foreach (string typeToAdd in allTypesToAdd)
{
// Verify the enum type to add to the service collection exists in the application.
isTypeFound = false;
foreach (TypeInfo type in assemblyTypes)
{
if (type.Name == typeToAdd)
{
if (type.IsInterface)
{
// Add the Interface and any concrete classes
// that are implementations of the parent interface.
var childOfInterface = assembly.GetTypes().Where(t => type.AsType().IsAssignableFrom(t));
foreach (Type c in childOfInterface)
{
if (typeToAdd != c.Name)
{
// For now this just assumes you need a request
// scoped service lifetime services. Change as needed.
services.TryAddScoped(type.AsType(), c);
logger?.LogInformation(LogEventIDs.Information_General_ID, "INFORMATION: A new Service Class Was Added: services.TryAddScoped(" + typeToAdd + "," + c.Name + ")");
}
}
} else {
// Only add the concrete class
// For now just use scoped service lifetime
services.TryAddScoped(type.AsType());
logger?.LogInformation(LogEventIDs.Information_General_ID, "INFORMATION: A new Service Class Was Added: services.TryAddScoped(" + typeToAdd + ")");
}
isTypeFound = true;
break;
}
}
// If users added types in the enum lists
// thats not found, flag as a warning!
if (!isTypeFound)
{
missingEnumTypes.Add(typeToAdd);
}
}
// If a bad enum service name was added, log that as a warning.
if (missingEnumTypes.Count > 0)
{
string items = string.Empty;
foreach (string s in missingEnumTypes)
{
if (items != string.Empty) items += " | ";
items += s;
}
logger?.LogWarning(LogEventIDs.Warning_General_ID, "WARNING: These Types/Interfaces/Classes added to Services were not found in the application >>> " + items);
}
}
}
Register Services consumes the enum list of Services and Consumers above.
The last step is to call the method above inside your Startup.cs .NET file in Core. Add RegisterServices.Start() static method call with your ConfigureServices class inside Startup.cs in the root of your .NET Core application. I also add the logger as a parameter but this version just use the services parameter. "services" is whatever the parameter is in your
ConfigureServices method in Startup.cs:
RegisterServices.Start(services);
HOW TO USE DEPENDENCY INJECTION
After you run RegisterServices in your .NET application and Startup.cs calls it, all your services (and child classes derived from interfaces) are now registered!
To call a Service and have it auto-implemented when you instantiate a class in .NET appears to be inconsistent. The IoC Container will auto-inject all constructor services in MVC Controllers, for example, but NOT regular classes. To solve that I recommend you try and inject everything into your controllers, then use the IServiceProvider in regular class constructors to help you auto-inject all other classes with the services they need (see below).
If you are in ASP.NET Core, your best strategy is to ALWAYS add each service to your controller's constructor using interfaces. You can then have full access to every service you need or any service a child object inside the controller might need. But there will be times you have classes you call outside the controllers that inject services but are not auto-injected. So below are some examples of how to do that and still honor the dependency injection model.
Note: If you are an expert at this, please suggest below in comments how I can improve on this idea, as this is the best model I have for now that is simple and easy to use.
// HOW TO USE SERVICES?
// CONTROLLERS (Web Applications)
// Always inject the services you need into the controller's constructor.
// The IoC Container in .NET always auto-injects these objects
// for you and are 100% ready to access. If using ASP.NET, always use the
// constructor of the controller to inject services.
public class HomeController : BaseController
{
private readonly ISampleService _myservice;
public HomeController(ISampleService myservice){
_myservice = myservice;
}
// You can now access your "_myservice" in any action method of the controller
}
// NON-CONTROLLERS and NON-INJECTED CONSTRUCTORS
// If you cant inject the service object into an ASP.NET Controller
// but still need to instantiate the object, your best alternative
// is to inject the ServiceProvider into your Controller or Class
// constructor first. IoC auto-injects the service collection
// so you can now access it to create child objects you can
// tell .NET to auto-inject with their own services when created
// using the registered services in your enum as an example.
public MyClass (IServiceProvider myservice) {
// Here are 3 ways to force the IoC to auto-inject your dependencies
var obj1 = myservice.GetService<SampleService>();
var obj2 = myservice.GetService(SampleService) as ISampleService;
var obj3 = myservice.GetRequiredService(SampleService) as ISampleService;
var obj4 = (SampleService)myservice.GetService(typeof(SampleService));
}
Below is one of the Service Interface types in the enum above and the child classes that got registered which are now available to use as services in the code above after running the RegisterServices call:
// SERVICE INTERFACE
public interface ISampleService
{
void Message(string message);
}
// SERVICE CONCRETE CLASS
class SampleService : ISampleService
{
public void Message(string message)
{
Console.WriteLine($"{message}");
}
}
// SERVICE CONCRETE CLASS
class AnotherSampleService : ISampleService
{
public void Message(string message)
{
Console.WriteLine($"{message}");
}
}
You can use factory to achieve that.
services.AddScoped(provider =>
{
//Resolve some service at runtime.
var aService = provider.GetService<AServiceType>();
//Any synchronous logic here
return new MyDynamicService();
});
The following code shows the flow I’m currently trying to implement within a WCF service. The service on startup calls the Bootstrapper class which uses Unity to register and resolve the required types. The Gateway class contains the public method which then kicks off the main flow of processing a message (there are many more levels to the code than is shown below).
public static class Bootstrapper
{
public static IGateway InitializeGateway()
{
IUnityContainer resolver = new UnityContainer();
resolver.RegisterType<IGateway, Gateway>();
resolver.RegisterType<ITranslator, Translator>();
resolver.RegisterType<IFormatter, IFormatter>();
return resolver.Resolve<IGateway>();
}
}
public class Gateway : IGateway
{
private readonly ITranslator translator;
private readonly IFormatter formatter;
public Gateway(ITranslator translator, IFormatter formatter)
{
this.translator = translator;
this.formatter = formatter;
}
public string ProcessMessage(string requestMessage)
{
// Create a new GUID for use in main flow for logging
Guid messageGuid = Guid.NewGuid();
requestMessage = this.translator.TranslateMessage(requestMessage);
requestMessage = this.formatter.FormatMessage(requestMessage);
return requestMessage;
}
}
Now what I’m trying to achieve is take the GUID (created for each message) and pass this down within the flow of the service such that each class has a reference to it for logging purposes.
I have tried to find a way of using DI and constructor injection but don’t know if this can be done as the GUID is created on receipt of a message by the gateway (after the bootstrapper call). What I’m trying to get away from is passing the GUID into each method as a parameter.
Any suggestions?
Instead of rolling your own solution to this problem with DI, I would recommend you use the thread-static property Trace.CorrelationManager.ActivityId for this purpose.
Take a look at this article on WCF End-To-End Tracing.
Throughout my ASP.net site i need to check if the logged in users belongs to a role or has a field in my "UserInstance" table in my database set to true. To do this i can do the following.
if(Roles.IsUserInRole("Global Admin")
|| uow.UserInstanceService.GetUserInstance(userId,InstanceId).Admin)
{
//Do something
}
However as i am going to be using this code a lot as much of the permissions depend on the logged in user either being a "Global Admin" or a field of my table being true I don't want to write this out constantly.
One solution i have found is to create a method in the "UserInstance" Service which checks for both as seen in the "IsUserAdminOrGlobalAdmin" method.
public class UserInstanceService
{
IRepository<UserInstance> userInstanceRepository;
public UserInstanceService(IRepository<UserInstance> userInstanceRepository)
{
this.userInstanceRepository = userInstanceRepository;
}
public UserInstance GetByUserIdAndInstanceId(Guid userId, int instanceId)
{
return userInstanceRepository.GetSingle(u => u.UserId == userId && u.InstanceId == instanceId);
}
public bool IsUserAdminOrGlobalAdmin(Guid userId,int instanceId)
{
bool valid = false;
if (System.Web.Security.Roles.IsUserInRole("Global Admin"))
valid = true;
if (GetByUserIdAndInstanceId(userId, instanceId).Admin)
valid = true;
return valid;
}
//Removed rest of methods for readability
}
As this is buisness logic I put this method is in my "UserInstanceService" class which interacts with the repository class which contains the entity context. This service class resides in a seperate Model project so i had to add a reference to System.Web.Security and i am not sure if doing this is good practice. One thing i have noticed is that i can not write unit tests for this method as it relies on a user being logged in.
So my question is, is it acceptable to combine HttpContext specific functionality like the Logged in users roles, in a service?
Edit - After reading the answers I have changed my code so a Auth service (in the Web app project) is called which in turn calls the UserInstanceService to something like this.
public class Auth: IAuthService {
public bool IsUserAdminOrGlobalAdmin(Guid userId,int instanceId) {
myEntities entityContext = new myEntities
//RepsitoryBase inherits my IRepository<TEntity> class
UserInstanceService uis = new UserInstanceService(new RepositoryBase<UserInstance>(entityContext));
bool valid = false
if(Roles.IsUserInRole("Global Admin"))
valid = true;
if(uis.GetByUserIdAndInstanceId(userId,instanceId).Admin)
valid = true;
return valid;
}
}
So i could call this in my pages like this
if(Auth.IsUserAdminOrGlobalAdmin(userId,InstanceId)
{
//Do stuff
}
The original answer was written assuming the UserAccess requires the Authentication, but it appears that the Authentication consumes the UserAccess; simply invert the dependencies, but everything else should be usable in about the same manner.
Original answer:
Pull the ASP.NET-specific code into it's own service separate from the repository. Then that service - say, the Auth Service - can be used by any component (such as the UserInstanceService) that needs access to centralized authentication/authorization logic.
Consume the Auth as a dependency per IoC principles, hopefully using some DI to make life easier.
If the Auth service is kept separate it can also be trivially mocked for testing, such as testing what happens when the use is authenticated or not, which entirely avoids the need to setup a full ASP.NET stack for the User service.
In addition, because services (interfaces) and components (classes) are separate, the actualy HTTP-utilizing component can live in a separate project from the service and wired in later - this will avoid pulling in Web dependencies to the Model project.
For example,
// This is the Service Contract and can live in the Model
public class IAuthService {
void AssertCurrentUserIsAdminOrGlobalAdmin();
void AssertIsUserAdminOrGlobalAdmin(Guid userId,int instanceId);
}
// This is the Component, which provides the Service, and is part
// of the Web/HTTP-specific project. It is wired up via IoC/DI from
// the large context of the application.
public class Auth: IAuthService {
public void AssertCurrentUserIsAdminOrGlobalAdmin() {
// This one already knows the applicable HTTP/User Context
}
public void AssertIsUserAdminOrGlobalAdmin(Guid userId,int instanceId) {
// Do whatever
}
}
// This Component is part of the Model
public class UserInstanceService
{
// IoC dependencies
IRepository<UserInstance> userInstanceRepository;
IAuthService authService;
}
You could set the current principal on the thread and use that instead. I think thats most of what ASP.Net does for you as well.
I have been helping a few friends on a project and there is a class that uses Ninject. I am fairly new to C# and I have no idea what that class is doing, which is why I need to understand Ninject. Can anyone explain what Ninject is and when does one use it(with example if possible)? Or if you can point to some links that would be great too.
I tried this question: Ninject tutorials/documentations? but it didn't really help a beginner like me.
Ninject is dependency injector for .NET, practical realisation of pattern Dependency Injection (form of Inversion of Control pattern).
Suppose you have two classes DbRepository and Controller:
class Controller {
private DbRepository _repository;
// ... some methods that uses _repository
}
class DbRepository {
// ... some bussiness logic here ...
}
So, now you have two problems:
You must initialize _repository to use it. You have several options for doing this:
Manually, within the constructor. But what if the constructor of DbRepository changes? You would need to rewrite your Controller class because code it's dependent upon was changed. It's not hard if you have only one Controller, but if you have a couple of classes that have a dependency on your Repository you have a real problem.
You can use a service locator or factory. But now you have a dependency on your service locator. You have a global service locator and all code must use it. How you will you change the behavior of your service locator when you need to use it in one part of your code for activation logic but for something else in another part of your code? There is only one way - passing the service locator through constructors. But with more and more classes you will need to pass it more and more times. Anyway, it's a good thought but in the long run, it's a bad idea.
class Controller {
private DbRepository _repository;
public Controller() {
_repository = GlobalServiceLocator.Get<DbRepository>()
}
// ... some methods that uses _repository
}
You can use dependency injection. Look at the code:
class Controller {
private IRepository _repository;
public Controller(IRepository repository) {
_repository = repository;
}
}
Now when you need your controller you write: ninjectDevKernel.Get<Controller>(); or ninjectTestKernel.Get<Controller>();. You can switch beetween dependency resolvers as fast as you want. See? It's simple, you don't need to write a lot.
You can't create unit tests for it. Your Controller has a dependency on DbRepository and if you want to test some method that uses repository, your code will go to the database and ask it for data. That's slow, very slow. If your code in DbRepository changes, your unit test on Controller will fall. Only integration test must warn you of 'problems' in this case. What you need in unit tests - is to isolate your classes and test only one class in one test (in ideal - only one method). If your DbRepository code fails, you will think that Controller code failed - and that's bad (even if you have tests for DbRepository and Controller - they both will fail and you can start from the wrong place). It takes a lot of time to determine where the error really is. You need to know that class A is ok, and it was class B where something failed.
When you want to replace DbRepository with something else in all your classes, you have to do a lot of work.
You can't easily control the lifetime of DbRepository. An object of this class is created on initialization of Controller and deleted when Controller is deleted. There is no sharing between different instances of the Controller class and there is no sharing between other classes. With Ninject you can simply write:
kernel.Bind<IRepository>().To<DbRepository>().InSingletonScope();
A special feature of dependency injection - agile development! You describe that your controller uses a repository with interface IRepository. You don't need to write DbRepository, you can simply create a MemoryRepository class and develop Controller while another person develops DbRepository. When work on DbRepository is finished, you just rebind in your dependency resolver that default IRepository is now DbRepository. Have a lot of controllers? All of them will now use DbRepository. That's cool.
Read more:
Inversion of control (wiki)
Dependency injection (wiki)
Inversion of Control Containers and the Dependency Injection pattern (Martin Fowler)
Ninject is an Inversion of Control container.
What does it do?
Suppose you have a Car class that depends on a Driver class.
public class Car
{
public Car(IDriver driver)
{
///
}
}
In order to use the Car class you build it like so:
IDriver driver = new Driver();
var car = new Car(driver);
A IoC containter centralizes the knowledge about how to build classes. It is a central repository that knows a few things. For example, it knows that the concrete class that you need to use to build a car is a Driver and not any other IDriver.
For example, if you are developing a MVC application, you can tell Ninject how to build your controllers. You do so by registering which concrete classes satisfy specific interfaces. At run time Ninject will figure out which classes are needed to build the required controller, and all behind the scenes.
// Syntax for binding
Bind<IDriver>().To<Driver>();
This is beneficial because it lets you build systems that are more easily unit testable. Suppose that Driver encapsulates all database access for Car. In a unit test for Car you can do this:
IDriver driver = new TestDriver(); // a fake driver that does not go to the db
var car = new Car(driver);
There are entire frameworks that take care of automatically creating testing classes for you and they are called mocking frameworks.
For more information:
GitHub/Ninject Home
Inversion of Control
Inversion of Control Containers and the Dependency Injection pattern
Mock Object
Other answers are great but I would also like to point out this Implementing Dependency Injection using Ninject article.
This is one of the best articles I ever read which explains Dependency Injection and Ninject with a very elegant example.
Here's the snippet from the article:
Below Interface will be implemented by our (SMSService) and (MockSMSService), basically the new Interface (ISMSService) will expose the same behaviors of both services as the code below:
public interface ISMSService
{
void SendSMS(string phoneNumber, string body);
}
(SMSService) implementation to implement the (ISMSService) interface:
public class SMSService : ISMSService
{
public void SendSMS(string mobileNumber, string body)
{
SendSMSUsingGateway(mobileNumber, body);
}
private void SendSMSUsingGateway(string mobileNumber, string body)
{
/*implementation for sending SMS using gateway*/
Console.WriteLine("Sending SMS using gateway to mobile:
{0}. SMS body: {1}", mobileNumber, body);
}
}
(MockSMSService) with totally different implementation using the same interface:
public class MockSMSService :ISMSService
{
public void SendSMS(string phoneNumber, string body)
{
SaveSMSToFile(phoneNumber,body);
}
private void SaveSMSToFile(string mobileNumber, string body)
{
/*implementation for saving SMS to a file*/
Console.WriteLine("Mocking SMS using file to mobile:
{0}. SMS body: {1}", mobileNumber, body);
}
}
we need to implement a change to our (UIHandler) class constructor to pass the dependency through it, by doing this, the code which uses the (UIHandler) can determine which concrete implementation of (ISMSService) to use:
public class UIHandler
{
private readonly ISMSService _SMSService;
public UIHandler(ISMSService SMSService)
{
_SMSService = SMSService;
}
public void SendConfirmationMsg(string mobileNumber) {
_SMSService.SendSMS(mobileNumber, "Your order has been shipped successfully!");
}
}
Now, we have to create a separate class (NinjectBindings) which inherits from (NinjectModule). This class will be responsible to resolve dependencies at run time, then we’ll override the load event which is used to configure the binding in it. The nice thing about Ninject is that we do not need to change our code in (ISMSService), (SMSService), and (MockSMSService).
public class NinjectBindings : Ninject.Modules.NinjectModule
{
public override void Load()
{
Bind<ISMSService>().To<MockSMSService>();
}
}
Now in UI form code, we’ll use the binding for Ninject which will determine which implementation to use:
class Program
{
static void Main(string[] args)
{
IKernel _Kernal = new StandardKernel();
_Kernal.Load(Assembly.GetExecutingAssembly());
ISMSService _SMSService = _Kernal.Get<ISMSService>();
UIHandler _UIHandler = new UIHandler(_SMSService);
_UIHandler.SendConfirmationMsg("96279544480");
Console.ReadLine();
}
}
Now the code is using the Ninject Kernal to resolve all chain of dependencies, if we want to use the real service (SMSService) in Release mode (on production environment) instead of the mock one, we need to change on the Ninject binding class (NinjectBindings) only to use the right implementation or by using the #if DEBUG directive as below:
public class NinjectBindings : Ninject.Modules.NinjectModule
{
public override void Load()
{
#if DEBUG
Bind<ISMSService>().To<MockSMSService>();
#else
Bind<ISMSService>().To<SMSService>();
#endif
}
}
Now our binding class (NinjectBindings) is living on the top of all our execution code and we can control the configuration easily in once place.
Also, see What is Inversion of Control? some very simple examples are mentioned to understand IoC.
You have to understand the Dependency Injection(DI) first. Notice here,
public interface IService
{
void Serve();
}
public class Service1 : IService
{
public void Serve() {
Console.WriteLine("Service1 Called");
}
}
public class Service2 : IService
{
public void Serve() {
Console.WriteLine("Service2 Called");
}
}
public class Service3 : IService
{
public void Serve() {
Console.WriteLine("Service3 Called");
}
}
public class Client
{
private IService service;
public Client(IService _service) //Constructor injection
{
service = _service;
}
public void ServeMethod() {
service.Serve(); //Notice here, this Serve() method has no idea what to do.
} // runtime will assign the object, that is Ninject
}
class Program
{
static void Main(string[] args)
{
IService s1 = new Service1(); //N.B. Ninject assigns object with interface
Client c1 = new Client(s1);
c1.ServeMethod();
IService s2 = new Service2(); //N.B. Ninject assigns object with interface
c1 = new Client(s2);
c1.ServeMethod();
IService s3 = new Service3(); //N.B. Ninject assigns object with interface
c1 = new Client(s3);
c1.ServeMethod();
Console.ReadKey();
}
}
// Ninject creates object in runtime for interface in runtime in ASP.NET MVC project.
/*
Output:
Service1 Called
Service2 Called
Service3 Called
*/
I have been using the following pattern for my controller actions:
public ActionResult Create(CreateViewModel model) {
if( !ModelState.IsValid ) {
return View(model);
}
var project = new Project {
Name = model.Name,
// ...
};
projectRepository.Add(project);
return RedirectToAction("Index");
}
This works for simple scenarios, but I have had a few situations where a repository is not enough. I created a service layer / class that will handle saving the project and any extra business logic (not normal validations with fluent validation or data annotations).
public class ProjectService : IProjectService {
void AddProject(Project project) {
// do business logic
// ...
repository.Add(project);
}
}
How can my service layer easily communicate with my controller?
These are the types of things I would like to communicate to the controller:
Business Logic / Validation errors
Database Failures (failed to save etc.)
How can I do this without just returning true/false or status codes from the service layer?
Be careful if you choose exceptions, these are expensive. It gives your controller code extra nesting too, depending on how many exceptions may be thrown. You should really only throw an exception for an exceptional condition, not something that should be handled by the normal flow of your application.
I would go with the other route Wouter de Kort suggested, use the return type of the service for a messaging object. You can key a return message object on a simple enum with the various cases the service may encounter. These look better in the controller because you can handle the enum with a switch/case rather than a try/catch.
Update
What a messaging object may look like:
public interface IServiceAbc
{
ServiceResponse InvokeMyService([params]);
}
public enum ResponseScenario
{
Success,
DatabaseFailed,
BusinessRuleViolated,
ValidationRuleViolated
}
public class ServiceResponse
{
public ResponseScenario Scenario { get; internal set; }
public string Message { get; internal set; }
}
If you want to return detailed messages when an error occurs you could always use Exceptions. Maybe define your own with specific details or reuse the ones that are already in the .NET Framework.
If that´s not an option you could always return a wrapper class which could contain more detailed error information and handle that in the Controller.