top of page
cromhancahatiters

.Net Real Implementations: How to Use Xamarin, UWP, and Unity for Mobile and Game Development



As with many other concepts in the ASP.NET Core world, expansive documentation exists regarding Globalization & Localization within applications. However, the documentation is in the form of a user's guide and a simple implementation example. Rarely do our final projects end up matching the single-project solution structures that are used as examples. After recently setting up a new project to support localization, I thought I would share a more "real-world" example from the implementation.


In one of my previous posts I have talked about continuous deployment and continuous delivery and why it is important. In a series of posts I want to describe in detail the various patterns that are necessary to successfully implement a fully automated CI/CD pipeline and also provide sample implementations of those patterns.




.Net Real Implementations




Before I start I need to define our boundary conditions. The following descriptions are based on a real implementation for a customer. The starting point is a huge monolithic web application that has been up and running for a few years and has been maintained and improved by several teams using a somewhat manual continuous delivery pipeline. Deployments happen(ed) several times per day into production with zero downtime. The application is written in C# on top of .NET. The web layer consists of a mixture of Web Forms and ASP.NET MVC. The back-end functionality is at least partially exposed via a RESTful API implemented in ASP.NET Web API 2. There is also a mobile client for iOS and Android that consumes the Web API. Due to various reasons that are not discussed here the customer wanted to migrate the monolithic application to a micro service based architecture and at the same time wanted to improve the continuous delivery pipeline into a fully automated continuous deployment pipeline. Over time coherent functionality is and will be identified and isolated in the monolith and extracted into independent micro services. The functionality is not re-implemented from scratch but only refactored until it can be extracted into a micro service. For this reason, at the moment, all application logic will continue to be implemented in C# on .NET. Additionally, at least for the foreseeable future, the application needs to run in a private cloud. At the time when the project started .NET core was not yet released and Windows containers were still in an early alpha state (TP4). Also since the application uses some specialized external libraries that depend on specific Windows functionality running the micro services on top of Mono in Linux was not an option. That means that at this time we cannot use container technology (Docker) for the micro services. But we are making sure that we are ready to embrace this technology as soon as it is released. On the other hand we are free to use Linux and Docker for all supporting services. Samples are MongoDB, Solr, RabbitMQ, etc.


The Data project contains domain and generic (CRUD and event-sourcing) repository implementations, DbContexts, EF Core migrations, entity type configurations (if any), event store implementation (including snapshots), data entity to domain object mappings, and persistence related services (i.e. a database initializer service).


The Shared project contains service implementations for cross-cutting concerns such as user management and authentication, file storage, service bus, localization, application configuration and a password generator.


This package defines the IAsyncEnumerable interface for platforms that don't officially support C# 8.0. This is a really big deal, because it means you can start exposing this type today in your library APIs and know that people can reasonably consume it from earlier Target Framework Monikers (TFMs).


While this does work, potentially there is a problem if we expose one of these types in our public API. Consuming libraries will see Index or Range in the System namespace, but that would clash with their own implementations, or the implementation from supported TFMs.


I'm sure there's more cons, but the reduced functionality is a really big one! If you're used to one of the more full-featured DI containers, then you'll likely want to stick with those. On the other hand, if you're currently only using the built-in container, Scrutor is worth a look to see if it can simplify your code.


The real selling point for Scrutor is its methods to scan your assemblies, and automatically register the types it finds. Scrutor has several variations which allow you to pass in instances of Assembly to scan, to retrieve the list of Assembly instances based on your app's dependencies, or to use the calling or executing assembly. Personally I prefer the methods that allow you to pass a Type, and have Scrutor find all the classes in the assembly that contains the Type. For example:


ML.NET framework is a fairly young framework and is still rapidly developing, so these drawbacks might be resolved soon. You may also find that the available set of algorithms is sufficient for many real-life cases.


Note that all of these implementations also use a public static property Instance as the means of accessing the instance. In all cases, the property could easily be converted to a method, with no impact on thread-safety or performance.


As you can see, this is really is extremely simple - but why is it thread-safe and how lazy is it? Well, static constructors in C# are specified to execute only when an instance of the class is created or a static member is referenced, and to execute only once per AppDomain. Given that this check for the type being newly constructed needs to be executed whatever else happens, it will be faster than adding extra checking as in the previous examples. There are a couple of wrinkles, however:


If you're using .NET 4 (or higher), you can use the System.Lazy type to make the laziness really simple. All you need to do is pass a delegate to the constructor which calls the Singleton constructor - which is done most easily with a lambda expression.


A lot of the reason for this page's existence is people trying to be clever, and thus coming up with the double-checked locking algorithm. There is an attitude of locking being expensive which is common and misguided. I've written a very quick benchmark which just acquires singleton instances in a loop a billion ways, trying different variants. It's not terribly scientific, because in real life you may want to know how fast it is if each iteration actually involved a call into a method fetching the singleton, etc. However, it does show an important point. On my laptop, the slowest solution (by a factor of about 5) is the locking one (solution 2). Is that important? Probably not, when you bear in mind that it still managed to acquire the singleton a billion times in under 40 seconds. (Note: this article was originally written quite a while ago now - I'd expect better performance now.) That means that if you're "only" acquiring the singleton four hundred thousand times per second, the cost of the acquisition is going to be 1% of the performance - so improving it isn't going to do a lot. Now, if you are acquiring the singleton that often - isn't it likely you're using it within a loop? If you care that much about improving the performance a little bit, why not declare a local variable outside the loop, acquire the singleton once and then loop. Bingo, even the slowest implementation becomes easily adequate.


I would be very interested to see a real world application where the difference between using simple locking and using one of the faster solutions actually made a significant performance difference.


Whew! OK, so now we know what the problem is with that implementation. The plain fact is that ASP.NET prefers synchronous methods if the operation is CPU-bound. And this is also true for other scenarios: Console applications, background threads in desktop applications, etc. In fact, the only place we really need an asynchronous calculation is when we call it from the UI thread.


Note how we will not create any concrete implementations of our services at this point. We are more interested in establishing and testing the behavior of our code. That means that concrete service implementations can come later.


So we have managed to test out the two major paths through our code but there are more tests, more assertions we could be doing. For example, we could ensure that the value of the Cart corresponds to what the customer is actually being charged. As well all know in the real world things are more complicated. We might need to update the API code to consider timeouts or errors being thrown from the Shipment service as well as the payment service.


With IoC containers, such as Unity, you can register all the concrete implementations of IMathOperator as named registrations and then when an array of IMathOperator are requested it injects all of the implementations. However this is not possible in the basic IoC in ASP.NET Core.


The IoC container which ships with ASP.NET Core is relatively basic, it allows for registration of implementatons with dependency trees however does not allow for the registration of the same interface with different implementations as Unity would and use them with a requirement of IMathOperator[].


It is possible to implement the above in ASP.NET Core with some juggling but you will require the use of a factory pattern at the point the dependencies are registered. As the strategy requires all of the IMathOperator implementations the factory pattern has to get them all together ready for consumption.


The implementation of this factory relies on another function of the IoC container which is that you do not have to map interface to concrete implementation when you register the type with the IServiceCollection. This factory implementation relies on the concrete implementations.


I mentioned at the beginning this method allows for applying the SOLID principles. The Single Responsibility Principle is applied to all concrete implementations as they have a single function to handle, the strategy is dealing with finding the appropriate implementation and calling it and the Controller calls the method and returns the result not caring about the processing. 2ff7e9595c


0 views0 comments

Recent Posts

See All

download 1 c

Como baixar o software 1C e aprender a linguagem de programação 1C Se você está procurando uma solução de software poderosa e flexível...

コメント


bottom of page