Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • Andy Cox 31 posts 182 karma points
    Nov 28, 2017 @ 16:06
    Andy Cox
    0

    Global settings and dependency injection

    Hi All,

    I have a global settings document type in the root that contains various site wide configuration. I've setup Autofac dependency injection to make this node available to my custom services.

    I am experiencing some strange problems though and was wondering if there were any problems with this approach? The problem I am experiencing is that some of the properties on the injected global settings node are null, causing null reference exceptions.

    Here's a sample of some of the code:

    Autofac config:

    builder.Register(ctx => {
        var umbracoHelper = new UmbracoHelper(UmbracoContext.Current);
    
        var globalSettings = umbracoHelper.TypedContentSingleAtXPath($"//{GlobalSettings.ModelTypeAlias}");
    
        if (globalSettings == null)
        {
            throw new ApplicationException("The global settings for the application were not found.");
        }
    
        return globalSettings as GlobalSettings;
    })
    .As<GlobalSettings>()
    .InstancePerRequest();
    

    Service example:

        public SampleService(GlobalSettings globalSettings)
        {
            this.globalSettings = globalSettings;
        }
    
        ...
    
        public void DoStuff()
        {
            var googleMapsApiKey = this.globalSettings.GoogleMapsApiKey; // null
        }
    

    I am using the SampleService class in an ContentService_Saving event handler (ultimately to query the Google Maps API for Geolocation information).

    What is strange is that when I debug the code it works fine! And I use the global settings else where in the code without problems.

    Has anyone setup a settings node in a similar fashion? If so, have you managed to use dependency injection without issues?

    Thanks for your help,

    Andy

  • Laurent Lequenne 123 posts 248 karma points
    Nov 29, 2017 @ 10:15
    Laurent Lequenne
    0

    You should post your class sample. More likely it is at the time your ApplicationHandler is instantiated that there is no umbracoContext available => So no UmbracoHelper either :-)

    So you should manually retrieve your globalSettings in the ApplicationStarted

  • Andy Cox 31 posts 182 karma points
    Nov 29, 2017 @ 11:26
    Andy Cox
    0

    Hi Laurent,

    Thanks for your reply. I thought that the issue with the context would most likely be the cause of the problem. How do you mean manually retrieve the globalSettings? Are you talking about loading from the IContentService? I'd thought about that but I was hoping to use the strongly typed models builder model to access my properties.

    Here is the relevant section from the ApplicationHandler:

        private void ContentService_Saving(IContentService sender, SaveEventArgs<IContent> args)
        {
            foreach (var node in args.SavedEntities)
            {
                if (node.ContentType.Alias == Stockist.ModelTypeAlias)
                {
                    var stockistLocatorService = this.GetStockistLocatorService();
    
                    var postcode = node.GetValue<string>("stockistPostcode");
    
                    var task = SystemTask.Run(() => stockistLocatorService.GetCoordinatesByPostcode(postcode));
                    var coords = task.Result;
    
                    if (coords != null)
                    {
                        node.SetValue("stockistLatitude", coords.Latitude);
                        node.SetValue("stockistLongitude", coords.Longitude);
                    }
                    else
                    {
                        var message = new EventMessage(
                            "Stockist",
                            "Unable to get the location details for this stockist. Please try again later.",
                            EventMessageType.Error);
    
                        args.CancelOperation(message);
                    }
                }
            }
        }
    
        /// <summary>
        /// Gets the stockist locator service.
        /// </summary>
        /// <returns>The stockist locator service.</returns>
        private IStockistLocatorService GetStockistLocatorService()
        {
            return DependencyResolver.Current.GetService<IStockistLocatorService>();
        }
    

    The IStockistLocatorService has the GlobalSettings injected as a contructor dependency, which is where I'm getting the problem.

    Thanks for your help.

    Andy

  • Laurent Lequenne 123 posts 248 karma points
    Nov 29, 2017 @ 15:30
    Laurent Lequenne
    0

    No, I was expecting that you injected the constructor of your application handler with the GlobalSettings.

    But it is not the case apparently, I got some issues also with services running in the ApplicationHandlers where that context is not always available.

    Maybe you should convert your registration of GlobalSettings in a class, and retrieve the globalSettings only on demand from your stockService

  • Andy Cox 31 posts 182 karma points
    Nov 29, 2017 @ 16:33
    Andy Cox
    0

    Hi Laurent,

    This is what I've done but I don't like the solution really. I am currently injecting the IContentService into my IStockistLocatorService and using a GetGlobalSettings method to load the settings node:

    /// <summary>
    /// Gets the global settings.
    /// </summary>
    /// <returns>The global settings.</returns>
    private IContent GetGlobalSettings()
    {
        if (this.globalSettings == null)
        {
            var configuration = this.contentService
                .GetRootContent()
                .FirstOrDefault(x => x.ContentType.Alias.Equals(Configuration.ModelTypeAlias));
    
            if (configuration == null)
            {
                throw new ApplicationException("The configuration node for the application was not found.");
            }
    
            var globalSettings = this.contentService
                .GetDescendants(configuration)
                .FirstOrDefault(x => x.ContentType.Alias.Equals(GlobalSettings.ModelTypeAlias));
    
            if (globalSettings == null)
            {
                throw new ApplicationException("The global settings for the application were not found.");
            }
    
            this.globalSettings = globalSettings;
        }
    
        return this.globalSettings;
    }
    

    Once I've got the IContent I can access the properties using:

    var globalSettings = this.GetGlobalSettings();
    
    globalSettings.GetValue<string>("googleMapsKey");
    

    I'll stick with this solution for now but I'd still be interested to hear anyone else's experiences.

    Thanks for your help.

    Andy

  • Laurent Lequenne 123 posts 248 karma points
    Nov 29, 2017 @ 17:36
    Laurent Lequenne
    1

    You can create a GlobalSettings class, where you inject the IContentService. The constructor reads your globalsetting content.

    a GET  property GoogleMapsKey => reads the right property.

    In the IStockService, you inject the GlobalSettings. And you access the googlemapskey through your property

  • Andy Cox 31 posts 182 karma points
    Nov 30, 2017 @ 08:30
    Andy Cox
    0

    Hi Laurent,

    Yeah, I think this is the way i'll go, when I have the time! I'll create a configuration class which contains my various settings document types, which can then be injected where needed.

    Thanks again for your input.

    Andy

Please Sign in or register to post replies

Write your reply to:

Draft