0

I'm trying to inject and use a service directly in Startup.cs in my Blazor application (MVVM). When I try to call a method from that service, I receive the same error message as when you failed to register the service properly:

Errormessage

I am doing this to read a value from the localstorage to set the language in startup.cs. for further information, see:
https://learn.microsoft.com/en-us/aspnet/core/blazor/globalization-localization?view=aspnetcore-6.0&pivots=server#dynamically-set-the-culture-by-user-preference-1

This is how I added the service in Startup.cs:

Injecting:

public class Startup
    {
        private IStorageService storageService;

        public Startup(IConfiguration configuration, IStorageService storageService)
        {
            this.storageService = storageService;
            Configuration = configuration;
        }
    
        public IConfiguration Configuration { get; }

RegisterLocation:

        /// <summary>
        /// This method gets called by the runtime. Use this method to add services to the container.
        /// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        /// </summary>
        /// <param name="services"></param>
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddScoped<IStorageService, StorageService>();

            var circuitRetentionPeriod = Configuration.GetValue("DisconnectedCircuitRetentionPeriodInMinutes", 3);

            services.AddRazorPages();
            services.AddServerSideBlazor()
                .AddCircuitOptions(a => a.DisconnectedCircuitRetentionPeriod = System.TimeSpan.FromMinutes(circuitRetentionPeriod))
                .AddHubOptions(hub => hub.MaximumReceiveMessageSize = 100 * 128 * 128); // 100 MB

            // Add basic logging functionality
            services.AddLogging(loggingBuilder =>
            {
                // Clear all potential logging providers
                loggingBuilder.ClearProviders();

                // Add NLog which will output the log to a log file
                loggingBuilder.AddNLog();

                // Log output to the Visual Studio Debug output
                loggingBuilder.AddDebug();
            });

            services.AddScoped<CircuitHandler>((sp) => new CircuitHandlerService(sp.GetRequiredService<ILogger<CircuitHandlerService>>(), sp.GetRequiredService<CircuitsCounterService>()));
            services.AddSingleton<CircuitsCounterService>();

            services.AddScoped<CorrelationProvider>();

            services.AddAppBaseBlazorServices();
            services.AddAppBaseApplicationServices();

            services.AddApplicationServices();
            services.AddBlazorServices();
            services.AddForeignServices();
        }

Registration

UseService:

        /// <summary>
        /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        /// </summary>
        /// <param name="app"></param>
        /// <param name="env"></param>
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();

            var language = storageService.Load<LanguageDTO>(StorageKeys.Language);

            app.UseRouting();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapBlazorHub();
                endpoints.MapFallbackToPage("/_Host");
            });
        }
    }

Implementation of StorageService.Load()

        public StorageService(ILocalStorageService storage, ILogger<StorageService> logger)
        {
            this.logger = logger is null ? new NullLogger<StorageService>() : logger;

            this.storage = storage;
        }
    
        public async Task<T> Load<T>(string key)
            where T : class, new()
        {
            try
            {
                var jsonString = await storage.GetItemAsStringAsync(key);

                if (string.IsNullOrEmpty(jsonString))
                {
                    logger.LogTrace("Local storage item with key '{}' not found. Return new object.", key);
                    return new T();
                }

                if (!jsonString.StartsWith("{"))
                {
                    // No matter if the data is encrypted try to decrypt the value and swallow if the data was not encrypted
                    try { jsonString = StringCipher.Decrypt(jsonString, PASSPHRASE); }
                    catch (FormatException) { }
                }

                if (logger.IsEnabled(LogLevel.Trace))
                {
                    var jDoc = JsonDocument.Parse(jsonString, new JsonDocumentOptions { AllowTrailingCommas = true });
                    jsonString = System.Text.Json.JsonSerializer.Serialize(jDoc, new JsonSerializerOptions { WriteIndented = true });
                    logger.LogTrace("Load value from local storage with key '{key}': {jsonString}", key, jsonString);
                }

                // Deserialize the object
                var result = JsonConvert.DeserializeObject<T>(jsonString);

                if (result is null)
                    return new T();

                else
                    return result;

            }
            catch (JSDisconnectedException)
            {
                // This exception es expected when the application try to load something but the Blazor circuit is closed i.e. when the page is refreshed with f5
                return new T();
            }

I also tried to register the service at the top of ConfigureServices, in case the previous location is too late.

Furthermore, I already found out on the internet and ChatGPT, that this should be totally possible. But can't find out what I am doing wrong.

Aka3D
  • 1
  • 4
  • Post code as a text please – Dani Apr 05 '23 at 08:13
  • define a static method? – Tiny Wang Apr 05 '23 at 08:23
  • You need to inject the service into the configure method i don't see that. And also please post that as text. – Ralf Apr 05 '23 at 08:23
  • @Dani Posted the code as text – Aka3D Apr 05 '23 at 08:24
  • `public class StorageService {public static string GetLanguage(){ return "en-us";}}` does this what you want ? https://i.stack.imgur.com/Jyhtl.png – Tiny Wang Apr 05 '23 at 08:27
  • @Ralf Like this? public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IStorageService storageService) {} This did not wored for me, as I received still the same exception. I already posted the code as text. – Aka3D Apr 05 '23 at 08:30
  • You can not resolve a service that is registered into the DI container before building the container. – Radu Hatos Apr 05 '23 at 08:31
  • @TinyWang Yes excactly what I need, does it have to be static? – Aka3D Apr 05 '23 at 08:36
  • 1
    @RaduHatos Please see this blog: https://nodogmablog.bryanhogan.net/2018/05/using-dependency-injection-with-startup-in-asp-net-core/ Is this only for the use in the whole application without injecting or as well as using in Startup.cs – Aka3D Apr 05 '23 at 08:39
  • @Aka3D yes, have to be static so that you can use it directly in anywhere. – Tiny Wang Apr 05 '23 at 08:45
  • by the way, in my humble opinion if we don't need to change the language value dynamically, we can certainly set the language in the environment variable and read it in startup, so that the language application used will depend on which server it host, when the app is host in a server for English speaking area, we can set the environement variable as en-us.. – Tiny Wang Apr 05 '23 at 08:53
  • @TinyWang I only have one host in a server for every language, by setting the language in startup.cs: app.UseRequestLocalization($"{language}"); it can show the application in the selected language on every user and not setting the language for EVERY user globaly, am I right? – Aka3D Apr 05 '23 at 09:13
  • yes, each user has their own language by selected, won't affect globally. – Tiny Wang Apr 05 '23 at 09:25
  • Nice to know. I also posted the Implementation of the load-method from my StorageService. Hope this helps to find a solution ^^ – Aka3D Apr 05 '23 at 09:40

2 Answers2

0

Your requirement is calling a service to get the language so that you can set it for your blazor app. Since the language is defined in a local storage, I'm afraid we can write a static method so that we can get the value directly in Startup.cs.

var language = StorageService.GetLanguage();

public class StorageService
{
    public static string GetLanguage()
    {
        return "en-us";
    }
}

If we need to dynamically set the culture by user preference, we need to follow this section. following the document and this is the test result.

enter image description here

Tiny Wang
  • 10,423
  • 1
  • 11
  • 29
  • Thanks for the Answer, keep in mind that the user should be able to select the language in a select and the value should be saved in the localstorage by a service. I already have such a service in my application which does exactly that. Now It should load the persisted value somehow to Startup.cs. – Aka3D Apr 05 '23 at 09:22
  • okay... then what I post can't do it. it doesn't allow user to change the language. Your requirement should follow [this section](https://learn.microsoft.com/en-us/aspnet/core/blazor/globalization-localization?view=aspnetcore-6.0&pivots=server#dynamically-set-the-culture-by-user-preference-1)... – Tiny Wang Apr 05 '23 at 09:27
  • Yes, but these aren't options I can use. My application is Cookie-free and would like to keep that. Also, these options require Controllers from MVC, as I mentioned in my question, my application is written with MVVM. For me, it's okay when it's not dynamically and need a restart of the application. These settings you only set once or twice in your life in an application. I just need to load value from that service to startup.cs – Aka3D Apr 05 '23 at 09:38
  • ok, then I think it's ok to have a static method, but you can set a default value in advance. AFAIK, you already had a method to store the language and read the local storage. Then you can run the app once and user choose the language, then when the app restart, the value can be read....anyway, it's better not to take restart into consideration. – Tiny Wang Apr 05 '23 at 10:00
  • we can hard code the language value and can also dynamically set the value. since you said your app is cookie-free so that you can't follow official document to set up culture dynamic, I'm afraid you can only try to "hard code" it. But just like we know, startup will only execute when the app is starting, and it's not a good idea to restart the app to make some changes take effect, we may consider to reload the page upon the user selected language which should have method like the Controller shared in the document. and if can't have a controller, you need to find some thing to replace it. – Tiny Wang Apr 05 '23 at 10:12
  • You do not have to worry about restarting. I already have the functionality to let the application restart. But how does your solution with hard-coding work? How can you read the value and hard code it in Startup.cs? – Aka3D Apr 05 '23 at 11:11
  • `let the application restart` like you know if your restart the app, all your users would be affected.. I'm sorry that I didn't explain it well. If we hard code the culture in startup file, then all the users of your application would be influenced so we have to read a variable, which for different users the vaule may be different. – Tiny Wang Apr 06 '23 at 09:57
0

You can try something like this ?

startup.cs

public class Startup
{
    public IConfiguration Configuration { get; }
    private  IMyService _myService;
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }
    public void ConfigureServices(IServiceCollection services)
    {

        services.AddControllers();
        services.AddSwaggerGen(c =>
        {
            c.SwaggerDoc("v1", new OpenApiInfo { Title = "serviceInStartup", Version = "v1" });
        });
        services.AddSingleton<IMyService, MyService>();
        SetService();
    }
    public void SetService()
    {
        MyService myService = new MyService();
        _myService = myService;
    }
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        _myService.ReturnMeString();
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
            app.UseSwagger();
            app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "serviceInStartup v1"));
        }

        app.UseHttpsRedirection();

        app.UseRouting();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
        
    }
}

If your service injects other services:

public class Startup
{
    public IConfiguration Configuration { get; }
    private static IMyService _myService;
    public Startup(IConfiguration configuration )
    {
        Configuration = configuration;
    }
    public void ConfigureServices(IServiceCollection services)
    {

        services.AddControllers();
        services.AddSwaggerGen(c =>
        {
            c.SwaggerDoc("v1", new OpenApiInfo { Title = "serviceInStartup", Version = "v1" });
        });
        services.AddSingleton<IMySecondService, MySecondService>();
        services.AddSingleton<IMyService, MyService>();
        SetService(services.BuildServiceProvider());
    }
    public void SetService(IServiceProvider provider)
    {
        var mySecondService = provider.GetService<IMySecondService>();
        MyService myService = new MyService(mySecondService);
        _myService = myService;
    }
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        _myService.ReturnMeString();
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
            app.UseSwagger();
            app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "serviceInStartup v1"));
        }

        app.UseHttpsRedirection();

        app.UseRouting();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
        
    }
}
ghostl33t
  • 1
  • 2
  • let's post code in text.... or you may got down vote from others... – Tiny Wang Apr 05 '23 at 08:56
  • That means not using DI but a second instance of a singleton? – Ralf Apr 05 '23 at 09:27
  • Good approach, sadly my Service has two Injections in the constructor of two other services. How do I handle with this issue? – Aka3D Apr 05 '23 at 09:51
  • @Ralf Yes, if you know how to do with DI, can you explain, please? – ghostl33t Apr 05 '23 at 09:58
  • @Aka3D You can add parameter 'IServiceProvider' to the method SetService, Then get services from the provider, something like this: ConfigureServices : `services.AddScoped();` `public void SetService(IServiceProvider provider) { var mySecondService = provider.GetService(); MyService myService = new MyService(mySecondService); _myService = myService; }` Method call : `SetService(services.BuildServiceProvider());` – ghostl33t Apr 05 '23 at 10:49
  • @ghostl33t Thanks, I've tested it. But when I try to run it I receive the following error: `JavaScript interop calls cannot be issued at this time. This is because the component is being statically rendered. When prerendering is enabled, JavaScript interop calls can only be performed during the OnAfterRenderAsync lifecycle method.` This exception is thrown at `try { var jsonString = await storage.GetItemAsStringAsync(key); ... }` You can see the full implantation in my question called "Implementation of StorageService.Load()" – Aka3D Apr 05 '23 at 11:40
  • @Aka3D That is the different problem now. This can help you I guess: https://stackoverflow.com/questions/61438796/javascript-interop-error-when-calling-javascript-from-oninitializedasync-blazor https://github.com/Blazored/SessionStorage – ghostl33t Apr 05 '23 at 12:26
  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Apr 09 '23 at 18:09