I updated some data access code to wrap some operations in a TransactionScope. The operations are async methods running some Dapper execute statements to write data to a SQL Server database. Something like:
public async Task InserData(SourceData sourceData)
{
using (var transactionScope = new
TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
using (IDbConnection connection = new
SqlConnection(this.ConnectionString))
{
connection.Open();
await InsertSomeData(sourceData.Registers, connection);
await InsertMoreData(sourceData.Deposits, connection);
transactionScope.Complete();
}
}
}
Anyway, I wire up a unit test to this method and it fails with this message:
Result Message: Test method ExtractSourceDataTest.CanStart threw exception: System.InvalidOperationException: A TransactionScope must be disposed on the same thread that it was created.
As usual, Google to the rescue. I found a nice blog post that explains the issue, https://particular.net/blog/transactionscope-and-async-await-be-one-with-the-flow. Basically, TransactionScope was not made to operate asynchronously across threads, but there is a work around for that. Microsoft released a fix, TransactionScopeAsyncFlowOption.Enabled. I went from a zero
using (var transactionScope = new TransactionScope())
to a hero
using (var transactionScope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
Now, if this would have been turned on by default I wouldn’t of had this little problem… talking to you Microsoft. I’m sure there is some backward compatibility issue or other quirk that makes default enable difficult, but I’m ranting anyway.
Conclusion
This is awesome, but I basically just enabled a distributed transaction and that scares me. You do not know the trouble I have seen with distributed transactions. Hopefully, its not that bad since we are distributing across processes on the same machine and not over the network, but scary none the least.
It is the dream of every web developer to build blazing fast and high-performance web applications but this is not easy to accomplish unless we implement some performance optimizations. Web pages have evolved from static HTML pages to complex and responsive pages with a lot of dynamic contents and plugins which require a large number of CSS and JavaScript files to be downloaded to the clients. To improve the initial page request load time, we normally apply two performance techniques called bundling and minification.
Builder Design Pattern allows us to separate the construction of a complex object from its representation so that we can use the same construction code to produce different types and representations of an object. It is used to construct a complex object step by step and return the complete object after the final step.
Create an ASP.NET Core MVC Web Application and inside Models folder create the following Product class.
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
Create the folder Services and add an interface IProductService with a single GetProducts method.
public interface IProductService
{
List<Product> GetProducts();
}
Next, create a class with the name AmazonProductService and Implement IProductService interface on this class. For this tutorial, I am not using any backend repository or service to load products from the database so to keep things simple, Let’s just return some hard-coded products from the GetProducts method as follows:
public class AmazonProductService : IProductService
{
public List<Product> GetProducts()
{
return new List<Product>()
{
new Product() { Id = 1001, Name = "Apple AirPods Pro", Price = 249.00m },
new Product() { Id = 1002, Name = "Sony Noise Cancelling Headphones", Price = 199.00m },
new Product() { Id = 1003, Name = "Acer Aspire 5 Slim Laptop", Price = 346.00m }
};
}
}
Next, we need to register our service in Startup class as follows:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IProductService, AmazonProductService>();
}
}
Next, we need to inject our service into the controller. We can inject services inside the constructor of the controller as shown below:
public class HomeController : Controller
{
private readonly IProductService _productService;
public HomeController(IProductService productService)
{
_productService = productService;
}
public IActionResult Index()
{
var products = _productService.GetProducts();
return View(products);
}
}
Finally, we can display products in our Index.cshtml razor view file as follows:
Run the application and you should be able to see all the products returned from AmazonProductService. This is because at runtime when our Home Controller has requested the instance of the class implementing IProductService, the dependency injection framework resolved it to the AmazonProductService registered in Startup.cs class.
Let’s say your application requirements change and you suddenly decided that the products should load from Ebay instead of Amazon. You can create another class EbayProductService that is implementing the same IProductService interface and has its own implementation of GetProducts method.
public class EbayProductService : IProductService
{
public List<Product> GetProducts()
{
return new List<Product>()
{
new Product() { Id = 2001, Name = "Apple iPhone XS Max", Price = 660.00m },
new Product() { Id = 2002, Name = "Apple iPhone 7", Price = 134.00m },
new Product() { Id = 2003, Name = "Sony Cyber Shot Camera", Price = 109.00m }
};
}
}
You don’t have to change a single line of code in your application. You just have to register EbayProductService in Startup.cs file and you are done.
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IProductService, EbayProductService>();
}
}
Controllers and Views that have a dependency on IProductService will automatically start displaying Ebay Products instead of Amazon products.
Dynamically Register Services in DI Container
Let’s say you are in a situation where you want to use two different services based on the environment. You want to test Amazon service when you are in the development environment but you want to use Ebay service in the production environment. You can easily achieve this by injecting the IWebHostEnvironment inside the constructor of Startup class and then you can register services dynamically as shown below.
public class Startup
{
private IWebHostEnvironment _env;
public Startup(IWebHostEnvironment env)
{
_env = env;
}
public void ConfigureServices(IServiceCollection services)
{
if (_env.IsProduction())
{
services.AddTransient<IProductService, EbayProductService>();
}
else
{
services.AddTransient<IProductService, AmazonProductService>();
}
}
}
Reference
This article has good explanation of dependency injection in general.