C# Flashcards
(183 cards)
Inversion of Control (IoC) -Principle
Inversion of Control (IoC) is a design principle (although, some people refer to it as a pattern). As the name suggests, it is used to invert different kinds of controls in object-oriented design to achieve loose coupling. Here, controls refer to any additional responsibilities a class has, other than its main responsibility. This include control over the flow of an application, and control over the flow of an object creation or dependent object creation and binding.
IoC is all about inverting the control. To explain this in layman’s terms, suppose you drive a car to your work place. This means you control the car. The IoC principle suggests to invert the control, meaning that instead of driving the car yourself, you hire a cab, where another person will drive the car. Thus, this is called inversion of the control - from you to the cab driver. You don’t have to drive a car yourself and you can let the driver do the driving so that you can focus on your main work.
The IoC principle helps in designing loosely coupled classes which make them testable, maintainable and extensible.
Dependency Inversion Principle -DIP
DIP is one of the SOLID object-oriented principle invented by Robert Martin (a.k.a. Uncle Bob)
1: High-level modules should not depend on low-level modules. Both should depend on the abstraction.
2: Abstractions should not depend on details. Details should depend on abstractions.
A high-level module is a module which depends on other modules. In our example, CustomerBusinessLogic depends on the DataAccess class, so CustomerBusinessLogic is a high-level module and DataAccess is a low-level module. So, as per the first rule of DIP, CustomerBusinessLogic should not depend on the concrete DataAccess class, instead both classes should depend on abstraction.
In English, abstraction means something which is non-concrete. In programming terms, the above CustomerBusinessLogic and DataAccess are concrete classes, meaning we can create objects of them. So, abstraction in programming means to create an interface or an abstract class which is non-concrete. This means we cannot create an object of an interface or an abstract class. As per DIP, CustomerBusinessLogic (high-level module) should not depend on the concrete DataAccess class (low-level module). Both classes should depend on abstractions, meaning both classes should depend on an interface or an abstract class.
Dependency Injection (DI)
Dependency Injection (DI) is a design pattern which implements the IoC principle to invert the creation of dependent objects.
Inversion of Control (IoC) -Principle-Control Over the Flow of a Program
In the console app, the Main() function of the program class controls the flow of a program. It takes the user’s input for the first name and last name. It saves the data, and continues or exits the console, depending upon the user’s input. So here, the flow is controlled through the Main() function.
IoC can be applied to the above program by creating a GUI-based application such as the following windows-based application, wherein the framework will handle the flow of a program by using events.
Inversion of Control (IoC) -Principle-Control Over the Dependent Object Creation
UI-->Service Layer-->Business Logic -->DataAccess In the typical n-tier architecture, the User Interface (UI) uses Service layer to retrieve or save data. The Service layer uses the BusinessLogic class to apply business rules on the data. The BusinessLogic class depends on the DataAccess class which retrieves or saves the data to the underlying database. This is simple n-tier architecture design. Let's focus on the BusinessLogic and DataAccess classes to understand IoC.
The following is an example of BusinessLogic and DataAccess classes for a customer.
public class CustomerBusinessLogic { DataAccess _dataAccess;
public CustomerBusinessLogic() { _dataAccess = new DataAccess(); }
public string GetCustomerName(int id) { return _dataAccess.GetCustomerName(id); } }
public class DataAccess { public DataAccess() { }
public string GetCustomerName(int id) { return "Dummy Customer Name"; // get it from DB in real app } }
As you can see in the above example, the CustomerBusinessLogic class depends on the DataAccess class. It creates an object of the DataAccess class to get the customer data.
Now, let’s understand what’s wrong with the above classes.
In the above example, CustomerBusinessLogic and DataAccess are tightly coupled classes because the CustomerBusinessLogic class includes the reference of the concrete DataAccess class. It also creates an object of DataAccess class and manages the lifetime of the object.
Problems in the above example classes:
1: CustomerBusinessLogic and DataAccess classes are tightly coupled classes. So, changes in the DataAccess class will lead to changes in the CustomerBusinessLogic class. For example, if we add, remove or rename any method in the DataAccess class then we need to change the CustomerBusinessLogic class accordingly.
2: Suppose the customer data comes from different databases or web services and, in the future, we may need to create different classes, so this will lead to changes in the CustomerBusinessLogic class.
3: The CustomerBusinessLogic class creates an object of the DataAccess class using the new keyword. There may be multiple classes which use the DataAccess class and create its objects. So, if you change the name of the class, then you need to find all the places in your source code where you created objects of DataAccess and make the changes throughout the code. This is repetitive code for creating objects of the same class and maintaining their dependencies.
4: Because the CustomerBusinessLogic class creates an object of the concrete DataAccess class, it cannot be tested independently (TDD). The DataAccess class cannot be replaced with a mock class.
To solve all of the above problems and get a loosely coupled design, we can use the IoC and DIP principles together. Remember, IoC is a principle, not a pattern. It just gives high-level design guidelines but does not give implementation details. You are free to implement the IoC principle the way you want.
The following pattern (but not limited) implements the IoC principle.
IoC can be done with Service Locator, Factory,Template Method,Dependency injection,Abstract Factory,
Pattern for IOC
Let’s use the Factory pattern to implement IoC in the above example, as the first step towards attaining loosely coupled classes.
First, create a simple Factory class which returns an object of the DataAccess class as shown below.
Example: DataAccess Factory public class DataAccessFactory { public static DataAccess GetDataAccessObj() { return new DataAccess(); } } Now, use this DataAccessFactory class in the CustomerBusinessLogic class to get an object of DataAccess class.
Example: Use Factory Class to Retrieve Object public class CustomerBusinessLogic {
public CustomerBusinessLogic() { }
public string GetCustomerName(int id) { DataAccess _dataAccess = DataAccessFactory.GetDataAccessObj();
return _dataAccess.GetCustomerName(id); } } As you can see, the CustomerBusinessLogic class uses the DataAccessFactory.GetCustomerDataAccessObj() method to get an object of the DataAccess class instead of creating it using the new keyword. Thus, we have inverted the control of creating an object of a dependent class from the CustomerBusinessLogic class to the DataAccessFactory class.
This is a simple implementation of IoC and the first step towards achieving fully loose coupled design. As mentioned in the previous chapter, we will not achieve complete loosely coupled classes by only using IoC. Along with IoC, we also need to use DIP, Strategy pattern, and DI (Dependency Injection).
loosely coupled classes
In an object-oriented design, classes should be designed in a loosely coupled way. Loosely coupled means changes in one class should not force other classes to change, so the whole application can become maintainable and extensible.
Static Methods
the static methods in C# do not need an object to call them we could call them in another class by ClassName.StaticMethodName();
Abstraction
In English, abstraction means something which is non-concrete. In programming terms, the above CustomerBusinessLogic and DataAccess are concrete classes, meaning we can create objects of them. So, abstraction in programming means to create an interface or an abstract class which is non-concrete. This means we cannot create an object of an interface or an abstract class. As per DIP, CustomerBusinessLogic (high-level module) should not depend on the concrete DataAccess class (low-level module). Both classes should depend on abstractions, meaning both classes should depend on an interface or an abstract class.
Dependency Inversion Principle -DIP - Example
public interface ICustomerDataAccess { string GetCustomerName(int id); }
public class CustomerDataAccess: ICustomerDataAccess { public CustomerDataAccess() { }
public string GetCustomerName(int id) { return "Dummy Customer Name"; } }
public class DataAccessFactory { public static ICustomerDataAccess GetCustomerDataAccessObj() { return new CustomerDataAccess(); } }
public class CustomerBusinessLogic { ICustomerDataAccess _custDataAccess;
public CustomerBusinessLogic() { _custDataAccess = DataAccessFactory.GetCustomerDataAccessObj(); }
public string GetCustomerName(int id) { return _custDataAccess.GetCustomerName(id); } }
The advantages of implementing DIP in the above example is that the CustomerBusinessLogic and CustomerDataAccess classes are loosely coupled classes because CustomerBusinessLogic does not depend on the concrete DataAccess class, instead it includes a reference of the ICustomerDataAccess interface. So now, we can easily use another class which implements ICustomerDataAccess with a different implementation.
Still, we have not achieved fully loosely coupled classes because the CustomerBusinessLogic class includes a factory class to get the reference of ICustomerDataAccess. This is where the Dependency Injection pattern helps us.
Dependency Injection
Dependency Injection (DI) is a design pattern used to implement IoC. It allows the creation of dependent objects outside of a class and provides those objects to a class through different ways. Using DI, we move the creation and binding of the dependent objects outside of the class that depends on them.
The Dependency Injection pattern involves 3 types of classes.
Client Class: The client class (dependent class) is a class which depends on the service class Service Class: The service class (dependency) is a class that provides service to the client class. Injector Class: The injector class injects the service class object into the client class.
As you can see, the injector class creates an object of the service class, and injects that object to a client object. In this way, the DI pattern separates the responsibility of creating an object of the service class out of the client class.
Constructor Dependency Injection example
Example: Constructor Injection public class CustomerBusinessLogic { ICustomerDataAccess _dataAccess;
public CustomerBusinessLogic(ICustomerDataAccess custDataAccess) { _dataAccess = custDataAccess; }
public CustomerBusinessLogic() { _dataAccess = new CustomerDataAccess(); }
public string ProcessCustomerData(int id) { return _dataAccess.GetCustomerName(id); } }
public interface ICustomerDataAccess { string GetCustomerData(int id); }
public class CustomerDataAccess: ICustomerDataAccess { public CustomerDataAccess() { }
public string GetCustomerName(int id) { //get the customer name from the db in real application return "Dummy Customer Name"; } } In the above example, CustomerBusinessLogic includes the constructor with one parameter of type ICustomerDataAccess. Now, the calling class must inject an object of ICustomerDataAccess.
Example: Inject Dependency
public class CustomerService
{
CustomerBusinessLogic _customerBL;
public CustomerService() { _customerBL = new CustomerBusinessLogic(new CustomerDataAccess()); }
public string GetCustomerName(int id) { return _customerBL.GetCustomerName(id); } } As you can see in the above example, the CustomerService class creates and injects the CustomerDataAccess object into the CustomerBusinessLogic class. Thus, the CustomerBusinessLogic class doesn't need to create an object of CustomerDataAccess using the new keyword or using factory class. The calling class (CustomerService) creates and sets the appropriate DataAccess class to the CustomerBusinessLogic class. In this way, the CustomerBusinessLogic and CustomerDataAccess classes become "more" loosely coupled classes.
Defining the services for Dependency injection
The Startup.ConfigureServices method is responsible for defining the services that the app uses, including platform features, such as Entity Framework Core and ASP.NET Core MVC. Initially, the IServiceCollection provided to ConfigureServices has services defined by the framework depending on how the host was configured. It’s not uncommon for an app based on an ASP.NET Core template to have hundreds of services registered by the framework. A small sample of framework-registered services is listed in the following table.
Extension methods
https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods
Constructor Dependency injection into controllers in ASP.NET Core
Constructor Injection
Services are added as a constructor parameter, and the runtime resolves the service from the service container. Services are typically defined using interfaces. For example, consider an app that requires the current time. The following interface exposes the IDateTime service:
C#
Copy public interface IDateTime { DateTime Now { get; } } The following code implements the IDateTime interface:
C#
Copy public class SystemDateTime : IDateTime { public DateTime Now { get { return DateTime.Now; } } } Add the service to the service container:
C#
Copy
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); } For more information on AddSingleton, see DI service lifetimes.
The following code displays a greeting to the user based on the time of day:
C#
Copy public class HomeController : Controller { private readonly IDateTime _dateTime;
public HomeController(IDateTime dateTime) { _dateTime = dateTime; }
public IActionResult Index() { var serverTime = _dateTime.Now; if (serverTime.Hour < 12) { ViewData["Message"] = "It's morning here - Good Morning!"; } else if (serverTime.Hour < 17) { ViewData["Message"] = "It's afternoon here - Good Afternoon!"; } else { ViewData["Message"] = "It's evening here - Good Evening!"; } return View(); } Run the app and a message is displayed based on the time.
Action injection with FromServices with out using Constructor Dependency Injection
Action injection with FromServices
The FromServicesAttribute enables injecting a service directly into an action method without using constructor injection:
C#
Copy
public IActionResult About([FromServices] IDateTime dateTime)
{
ViewData[“Message”] = $”Current server time: {dateTime.Now}”;
return View(); }
Multiple Startup classes for different environments
https://docs.microsoft.com/en-us/aspnet/core/fundamentals/startup?view=aspnetcore-3.1
When the app defines separate Startup classes for different environments (for example, StartupDevelopment), the appropriate Startup class is selected at runtime. The class whose name suffix matches the current environment is prioritized. If the app is run in the Development environment and includes both a Startup class and a StartupDevelopment class, the StartupDevelopment class is used. For more information, see Use multiple environments.
App startup in ASP.NET Core
The Startup class configures services and the app’s request pipeline.
The Startup class ASP.NET Core apps use a Startup class, which is named Startup by convention. The Startup class:
Optionally includes a ConfigureServices method to configure the app’s services. A service is a reusable component that provides app functionality. Services are registered in ConfigureServices and consumed across the app via dependency injection (DI) or ApplicationServices.
Includes a Configure method to create the app’s request processing pipeline.
ConfigureServices and Configure are called by the ASP.NET Core runtime when the app starts:
IHostBuilder in program.cs
The Startup class is specified when the app’s host is built. The Startup class is typically specified by calling the WebHostBuilderExtensions.UseStartup method on the host builder:
C#
Copy
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); } The host provides services that are available to the Startup class constructor. The app adds additional services via ConfigureServices. Both the host and app services are available in Configure and throughout the app.
Only the following service types can be injected into the Startup constructor when using the Generic Host (IHostBuilder):
IWebHostEnvironment
IHostEnvironment
IConfiguration
C#
Copy public class Startup { private readonly IWebHostEnvironment _env;
public Startup(IConfiguration configuration, IWebHostEnvironment env) { Configuration = configuration; _env = env; }
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services) { if (_env.IsDevelopment()) { } else { } } } Most services are not available until the Configure method is called.
ConfigureServices method in startup
The ConfigureServices method is:
Optional.
Called by the host before the Configure method to configure the app’s services.
Where configuration options are set by convention.
The host may configure some services before Startup methods are called. For more information, see The host.
For features that require substantial setup, there are Add{Service} extension methods on IServiceCollection. For example, AddDbContext, AddDefaultIdentity, AddEntityFrameworkStores, and AddRazorPages:
https://docs.microsoft.com/en-us/aspnet/core/fundamentals/?view=aspnetcore-3.1&tabs=windows#host
public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; }
public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { services.AddDbContext(options => options.UseSqlServer( Configuration.GetConnectionString("DefaultConnection"))); services.AddDefaultIdentity( options => options.SignIn.RequireConfirmedAccount = true) .AddEntityFrameworkStores(); services.AddRazorPages(); } Adding services to the service container makes them available within the app and in the Configure method. The services are resolved via dependency injection or from ApplicationServices.
ASP.NET Core Middleware
Middleware is software that’s assembled into an app pipeline to handle requests and responses. Each component:
Chooses whether to pass the request to the next component in the pipeline.
Can perform work before and after the next component in the pipeline.
The Configure method in startup class
The Configure method is used to specify how the app responds to HTTP requests. The request pipeline is configured by adding middleware components to an IApplicationBuilder instance. IApplicationBuilder is available to the Configure method, but it isn’t registered in the service container. Hosting creates an IApplicationBuilder and passes it directly to Configure.
The ASP.NET Core templates configure the pipeline with support for:
Developer Exception Page Exception handler HTTP Strict Transport Security (HSTS) HTTPS redirection Static files ASP.NET Core MVC and Razor Pages C#
Copy public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; }
public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { services.AddRazorPages(); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Error"); app.UseHsts(); } app. UseHttpsRedirection(); app. UseStaticFiles(); app. UseRouting(); app. UseAuthorization();
app.UseEndpoints(endpoints => { endpoints.MapRazorPages(); }); } } The preceding sample is for Razor Pages; the MVC version is similar.
Each Use extension method adds one or more middleware components to the request pipeline. For instance, UseStaticFiles configures middleware to serve static files.
Each middleware component in the request pipeline is responsible for invoking the next component in the pipeline or short-circuiting the chain, if appropriate.
Additional services, such as IWebHostEnvironment, ILoggerFactory, or anything defined in ConfigureServices, can be specified in the Configure method signature. These services are injected if they’re available.
For more information on how to use IApplicationBuilder and the order of middleware processing, see ASP.NET Core Middleware.
Configure services without Startup
To configure services and the request processing pipeline without using a Startup class, call ConfigureServices and Configure convenience methods on the host builder. Multiple calls to ConfigureServices append to one another. If multiple Configure method calls exist, the last Configure call is used.
C#
Copy
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureAppConfiguration((hostingContext, config) => { }) .ConfigureWebHostDefaults(webBuilder => { webBuilder.ConfigureServices(services => { services.AddControllersWithViews(); }) .Configure(app => { var loggerFactory = app.ApplicationServices .GetRequiredService(); var logger = loggerFactory.CreateLogger(); var env = app.ApplicationServices.GetRequiredService(); var config = app.ApplicationServices.GetRequiredService(); logger.LogInformation("Logged in Configure"); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Home/Error"); app.UseHsts(); }
var configValue = config["MyConfigKey"]; }); }); }); }
launchSettings.json
he environment for local machine development can be set in the Properties\launchSettings.json file of the project. Environment values set in launchSettings.json override values set in the system environment.
The following JSON shows three profiles from a launchSettings.json file:
JSON
Copy { "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { "applicationUrl": "http://localhost:54339/", "sslPort": 0 } }, "profiles": { "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_My_Environment": "1", "ASPNETCORE_DETAILEDERRORS": "1", "ASPNETCORE_ENVIRONMENT": "Staging" } }, "EnvironmentsSample": { "commandName": "Project", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Staging" }, "applicationUrl": "http://localhost:54340/" }, "Kestrel Staging": { "commandName": "Project", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_My_Environment": "1", "ASPNETCORE_DETAILEDERRORS": "1", "ASPNETCORE_ENVIRONMENT": "Staging" }, "applicationUrl": "http://localhost:51997/" } } }
Environment in asp.net core
ASP.NET Core configures app behavior based on the runtime environment using an environment variable.
Environments
ASP.NET Core reads the environment variable ASPNETCORE_ENVIRONMENT at app startup and stores the value in IWebHostEnvironment.EnvironmentName. ASPNETCORE_ENVIRONMENT can be set to any value, but three values are provided by the framework:
Development
Staging
Production (default)
C#
Copy public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); }
if (env.IsProduction() || env.IsStaging() || env.IsEnvironment("Staging_2")) { app.UseExceptionHandler("/Error"); } app.UseStaticFiles(); app.UseMvc(); } The preceding code:
Calls UseDeveloperExceptionPage when ASPNETCORE_ENVIRONMENT is set to Development.
Calls UseExceptionHandler when the value of ASPNETCORE_ENVIRONMENT is set one of the following:
Staging
Production
Staging_2
The Environment Tag Helper uses the value of IHostingEnvironment.EnvironmentName to include or exclude markup in the element:
CSHTML
Copy
<div><environment include="Development"></div> <div><environment exclude="Development"></div> <div> <environment include="Staging,Development,Staging_2"> </div>
On Windows and macOS, environment variables and values aren’t case sensitive. Linux environment variables and values are case sensitive by default.