Jump to content

Using Microsoft Dependency Injection in RAGE:MP C-Sharp


trolovecro

Recommended Posts

 

Using Microsoft Dependency Injection in RAGE:MP

I used # instead of dot, cause security reasons of page.

Intro

  • After some time of researching how to implement dependency injection in RAGE MP C sharp script, I finally found correct way to use it.I didnt find any good solution on Discord or forum, so I wanted to share with you this knowledge, so you dont need to waste your time if you choose to use it in your project. I will not upload it on github but I will try to explain every part of code to details. If you guys have any question, ask I will help back.

 

Concept of Dependency Injection

  • Concept of dependency injection may be little bit complicated for beginners so I will paste some documents from WEB so you get basic idea of using this. If you understand concept skip this part.
  • In object-oriented programming (OOP) software design, dependency injection (DI) is the process of supplying a resource that a given piece of code requires. The required resource, which is often a component of the application itself, is called a dependency. 
  • Dependency injection can be useful when working with large applications because it relieves various code modules from the task of instantiating references to resources and allows dependencies -- even mock dependencies --  to be swapped out easily, which can make unit testing easier. -https://www.techtarget.com/searchapparchitecture/definition/dependency-injection

 

  • Like I sad for beginners it may be complicated, I copied this text just so you know basics of DI. What is DI and why its useful.
  • In my example I will have script with WorldWeather service implemented inside DI in different ways. In couple of examples I will show you what is Transient, Scoped, Singleton services in DI concept.

 

  Script structure

  • ServiceContainer#cs
  • Starting#cs
  • Services - FOLDER
    • App#cs
    • App2#cs
    • WorldWeather#cs

Add this elements to your empty project. Be careful, all Services must be inside folder ( It doesn't matter how folder is called ), while Starting#cs, ServiceContainer#cs should be in ROOT folder of your project.

Just like this on image...

spacer.png

 

ServiceContainer.cs - file

Create ServiceContainer#cs class and copy next code inside:

 

Spoiler

This is ServiceContainer. Its static class that configure our dependency injection. 
We have static void function Init. This is core function which we will call in Startup.cs class and its used for configuring DI. Also I have ConfigureServices function that return our DI services and LoadConfiguration function that return IConfiguration for loading configurations from appsettings.json file.

Lets focus on ConfigureServices function. Here I added one Singleton and one Scoped service.

Singleton service is used for having one instance in entire application. In RAGE:MP scripting case, when you start your server until shutdown of your server. Same instance of that class will be kept. This option is great for loading configurations just like in this case where I am loading it from 'appsettings.json' file. In appsettings file you keep all your options for application - like connection strings for database or some other parameters, and it would be great to share this to entire application. Later I will show you what would be if I choose to use WorldWeather as Singleton.

Scoped service is used for having same instance in entire scope, but NOT OUTSIDE of scope. ( Scope is part of your code just like function or class ). We will start with using WorldWeather service as Scoped service. This is why we have App.cs and App2.cs .In this example App is one scope and App2 is another scope

Transient service is not used in code below, but I will make some example with WorldWeather service. In short terms, Transient service will have diffrent instances per request. So every time you need transient service, DI will create another instance of it. 

 

    public static class ServicesContainer
    {
        public static ServiceProvider serviceProvider;
        public static IServiceScopeFactory serviceScopeFactory;
        public static void Init()
        {
            var services = ConfigureServices();
            serviceProvider = services.BuildServiceProvider();
            serviceScopeFactory = serviceProvider.GetService<IServiceScopeFactory>();

        }
        public static IServiceCollection ConfigureServices()
        {
            IServiceCollection services = new ServiceCollection();
            var config = LoadConfiguration();
            services.AddSingleton(config);
            services.AddScoped<WorldWeather>();
            return services;
        }
        public static IConfiguration LoadConfiguration()
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);

            return builder.Build();
        }
    }

 

Starting.cs - file

Create Starting#cs class and copy next code inside.

    public class Starting : Script
    {
        public Starting() : base()
        {
            ServicesContainer.Init();
        }
    }

WorldWeather.cs

Create WorldWeather#cs class inside Services folder and copy next code inside.

Spoiler

In WolrdWeather class we are creating 2 properties. One is _Id and another one is CurrentWeather. Just so we can see diffrent instances on next examples. In constructor we are declaring this 2 variables and in GetWeather() function we are returning string with _Id and CurrentWeather.

 

    public class WorldWeather : Script
    {

        public Guid _Id { get; set; }
        private string CurrentWeather { get; set; } = string.Empty;

        private string[] Weathers = { "\x1b[92m Sunny", "\x1b[95m Rainy", "\x1b[96m Windy", "\x1b[94m Freezing cold", "\x1b[91m Boiling hot", "Cloudy" };

        public WorldWeather()
        {
            _Id = Guid.NewGuid();
            Random rnd = new Random();
            CurrentWeather = Weathers[rnd.Next(Weathers.Length)];
        }
        public string GetWeather()
        {
            return $"\x1b[39m Weather Service - with ID: {_Id} is saying... Outside is {CurrentWeather}";
        }

    }

 

App.cs - file

Create App#cs class inside Services folder and copy next code inside.

Spoiler

In App.cs we are declaring 2 variables. scopeFactory and _scope .ScopeFactory is used to create scope. And _scope is variable where we will store class scope. In class constructor we are declaring our _scope and with help of IDisposable Interface we will dispose scope using Dispose() function. In this way we are telling our DI what is lifetime of our scoped services. 

On server event "OnPlayerConnect" We will call WorldWeather service 2 times. One will be stored in variable "Weather" and other one in "Weather2", just so we can see what will be if we more then one time some service is called inside scope.

    public class App : Script, IDisposable
    {
        private IServiceScopeFactory scopeFactory = ServicesContainer.serviceScopeFactory;
        private IServiceScope _scope;
        
        public App()
        {
           _scope = scopeFactory.CreateScope();
        }

        public void Dispose()
        {
            _scope.Dispose();
        }

        [ServerEvent(Event.PlayerConnected)]
        public void OnPlayerConnected(Player player)
        {
            var Weather = _scope.ServiceProvider.GetService<WorldWeather>();
            var Weather2 = _scope.ServiceProvider.GetService<WorldWeather>();
            NAPI.Util.ConsoleOutput(Weather.GetWeather());
            NAPI.Util.ConsoleOutput(Weather2.GetWeather());
        }
    }

 

App2.cs - file

Create App2#cs class inside Services folder and copy next code inside. Code is same as App#cs but different class and constructor name.

    public class App2 : Script, IDisposable
    {
        private IServiceScopeFactory scopeFactory = ServicesContainer.serviceScopeFactory;
        private IServiceScope _scope;

        public App2()
        {
            _scope = scopeFactory.CreateScope();
        }

        public void Dispose()
        {
            _scope.Dispose();
        }

        [ServerEvent(Event.PlayerConnected)]
        public void OnPlayerConnected(Player player)
        {
            var Weather = _scope.ServiceProvider.GetService<WorldWeather>();
            var Weather2 = _scope.ServiceProvider.GetService<WorldWeather>();
            NAPI.Util.ConsoleOutput(Weather.GetWeather());
            NAPI.Util.ConsoleOutput(Weather2.GetWeather());
        }
    }

 

Result - WorldWeather is Scoped service in DI

  • In this example I have scoped my WorldWeather service in dependency injection. We can se how 2 instances of WorldWeather is created. This is because we have created 2 scopes (App and App2). Every scope is creating one instance of WorldWeather and printing 2 times weather. Also with ID we can see only 2 different IDs.

spacer.png

 

Result - WorldWeather is Transient service in DI

  • In this example we can see that not even 1 message is same. This is why every time we call WorldWeather service, DI will create new instance of it, no matter in which scope it is... 

spacer.png

 

Result - World Weather is Singleton service in DI

  • In this example we can se how every single message is the same, IDs and weathers are same in all 4 messages. This is because our DI created just one instance of WorldWeather service and every time we call it, DI will return same instance. 

spacer.png

Conclusion

  • We saw how different types of services are acting in our script. We have scoped which return same instance inside the scope, Transient which return different instance per service request and Singleton which returns same instance every per service request.
var Weather = _scope.ServiceProvider.GetService<WorldWeather>();
  • This is called injection, where we inject our class. In ASP.NET applications ... We are injecting services with constructor, but in case of RAGE:MP its not possible ( or maybe is, but I didnt find easy and working way)
  • Dependency injection is great for managing your code and building your script, also for testing entire application or easy switching between services. In my tutorial I didnt use Interfaces ( wanted to keep it simple ). This is not 100% proper way of implementing DI in applications.
  • Dont use DI if you find it complicated, first google more information about this concept, and use my example for implementing it to your script.
  • Dont forget that ServiceContainer#cs and Staring#cs should be in root component without any other classes. This is because when you start rage mp server there is not correct way of loading classes, it is loaded randomly so it can happen that script will throw you exception. 
  • Every time you create new service which you want to use in DI, dont forget to add it in ServiceContainer as Singleton,Transient or Scoped service.
  • All your NuGet packages should be same version ( List of dependencies is below ).

Needed Dependencies for DI

Microsoft Extensions Configuration Json (3.1.32)

Microsoft Extensions Dependency Injection (3.1.32)

Microsoft Extensions Dependency Injection Abstractions (3.1.32)

 

 

 

Edited by trolovecro
  • Like 1
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...