Blog

Posts written in September 2020.

  • Another day, another ASP.NET Core error... This time relating to JSON not being parsable. Like the error I posted yesterday, this was another strange one as it only occurred within an Azure environment.

    Let me start by showing the file compilation error:

    Application '/LM/W3SVC/144182150/ROOT' with physical root 'D:\home\site\wwwroot\' hit unexpected managed exception, exception code = '0xe0434352'. First 30KB characters of captured stdout and stderr logs:
    Unhandled exception. System.FormatException: Could not parse the JSON file.
     ---> System.Text.Json.JsonReaderException: '0x00' is an invalid start of a value. LineNumber: 0 | BytePositionInLine: 0.
       at System.Text.Json.ThrowHelper.ThrowJsonReaderException(Utf8JsonReader& json, ExceptionResource resource, Byte nextByte, ReadOnlySpan`1 bytes)
       at System.Text.Json.Utf8JsonReader.ConsumeValue(Byte marker)
       at System.Text.Json.Utf8JsonReader.ReadFirstToken(Byte first)
       at System.Text.Json.Utf8JsonReader.ReadSingleSegment()
       at System.Text.Json.Utf8JsonReader.Read()
       at System.Text.Json.JsonDocument.Parse(ReadOnlySpan`1 utf8JsonSpan, Utf8JsonReader reader, MetadataDb& database, StackRowStack& stack)
       at System.Text.Json.JsonDocument.Parse(ReadOnlyMemory`1 utf8Json, JsonReaderOptions readerOptions, Byte[] extraRentedBytes)
       at System.Text.Json.JsonDocument.Parse(ReadOnlyMemory`1 json, JsonDocumentOptions options)
       at System.Text.Json.JsonDocument.Parse(String json, JsonDocumentOptions options)
       at Microsoft.Extensions.Configuration.Json.JsonConfigurationFileParser.ParseStream(Stream input)
       at Microsoft.Extensions.Configuration.Json.JsonConfigurationFileParser.Parse(Stream input)
       at Microsoft.Extensions.Configuration.Json.JsonConfigurationProvider.Load(Stream stream)
       --- End of inner exception stack trace ---
       at Microsoft.Extensions.Configuration.Json.JsonConfigurationProvider.Load(Stream stream)
       at Microsoft.Extensions.Configuration.FileConfigurationProvider.Load(Boolean reload)
    --- End of stack trace from previous location where exception was thrown ---
       at Microsoft.Extensions.Configuration.FileConfigurationProvider.HandleException(ExceptionDispatchInfo info)
       at Microsoft.Extensions.Configuration.FileConfigurationProvider.Load(Boolean reload)
       at Microsoft.Extensions.Configuration.FileConfigurationProvider.Load()
       at Microsoft.Extensions.Configuration.ConfigurationRoot..ctor(IList`1 providers)
       at Microsoft.Extensions.Configuration.ConfigurationBuilder.Build()
       at Microsoft.Extensions.Logging.AzureAppServices.SiteConfigurationProvider.GetAzureLoggingConfiguration(IWebAppContext context)
       at Microsoft.Extensions.Logging.AzureAppServicesLoggerFactoryExtensions.AddAzureWebAppDiagnostics(ILoggingBuilder builder, IWebAppContext context)
       at Microsoft.Extensions.Logging.AzureAppServicesLoggerFactoryExtensions.AddAzureWebAppDiagnostics(ILoggingBuilder builder)
       at Microsoft.AspNetCore.Hosting.AppServicesWebHostBuilderExtensions.<>c.<UseAzureAppServices>b__0_0(ILoggingBuilder builder)
       at Microsoft.Extensions.DependencyInjection.LoggingServiceCollectionExtensions.AddLogging(IServiceCollection services, Action`1 configure)
       at Microsoft.AspNetCore.Hosting.WebHostBuilderExtensions.<>c__DisplayClass8_0.<ConfigureLogging>b__0(IServiceCollection collection)
       at Microsoft.AspNetCore.Hosting.HostingStartupWebHostBuilder.<>c__DisplayClass6_0.<ConfigureServices>b__0(WebHostBuilderContext context, IServiceCollection services)
       at Microsoft.AspNetCore.Hosting.HostingStartupWebHostBuilder.ConfigureServices(WebHostBuilderContext context, IServiceCollection services)
       at Microsoft.AspNetCore.Hosting.GenericWebHostBuilder.<.ctor>b__5_2(HostBuilderContext context, IServiceCollection services)
       at Microsoft.Extensions.Hosting.HostBuilder.CreateServiceProvider()
       at Microsoft.Extensions.Hosting.HostBuilder.Build()
       at Site.Web.Program.Main(String[] args) in C:\Development\surinder-main-website\Site.Web\Program.cs:line 11
    
    Process Id: 2588.
    File Version: 13.1.20169.6. Description: IIS ASP.NET Core Module V2 Request Handler. Commit: 62c098bc170f50feca15916e81cb7f321ffc52ff
    

    The application was not consuming any form of JSON as part of its main functionality. The only JSON being used were three variations of appsettings.json - each one for development, staging and production. So this had to be the source of the issue. The error message also confirmed this as Program.cs was referenced and it’s at this point where the application startup code is run.

    My first thought was I must have forgotten a comma or missing a closing quote for one of my values. After running the JSON through a validator, it passed with flying colours.

    Solution

    After some investigation, the issue was caused by incorrect encoding of the file. All the appsettings.json files were set to "UTF-8" and as a result, possibly causing some metadata to be added stopping the application from reading the files. Once this was changed to "UTF-8-BOM" through Notepad++ everything worked fine.

  • You gotta love .NET core compilation errors! They provide the most ambiguous error messages known to man. I have noticed the error message and accompanying error code could be caused by a multitude of factors. This error is no different so I’ll make my contribution, hoping this may help someone else.

    The error in question occurred really randomly whilst deploying a minor HTML update to a .NET Core site I was hosting within an Azure Web App. It couldn’t have been a simpler release - change to some markup in a View. When the site loaded, I was greeted with the following error:

    Failed to start application '/LM/W3SVC/####/ROOT', ErrorCode '0x8007023e’.
    

    I was able to get some further information about the error from the Event Log:

    Application 'D:\home\site\wwwroot\' failed to start. Exception message:
    Executable was not found at 'D:\home\site\wwwroot\%LAUNCHER_PATH%.exe'
    Process Id: 10848.
    File Version: 13.1.19331.0. Description: IIS ASP.NET Core Module V2.
    

    The error could only be reproduced on Azure and not within my local development and staging environments. I created a new deployment slot to check if somehow my existing slot got corrupted. Unfortunately, this made no difference. The strange this is, the application was working completely fine up until this release. It's still unknown to me what could have happened for this error to occur all of a sudden.

    Solution

    It would seem that no one else on the planet experienced this issue when Googling the error message and error code. After a lot of fumbling around, the fix ended up being relatively straight-forward. The detail provided by the Event Log pointed me in the right direction and the clue was in the %LAUNCHER_PATH% placeholder. The %LAUNCHER_PATH% placeholder is set in the web.config and this is normally replaced when the application is run in Visual Studio or IIS.

    In Azure, both %LAUNCHER_PATH% and %LAUNCHER_ARGS% variables need to be explicitly set. The following line in the web.config needs to be changed from:

    <aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="false" startupTimeLimit="3600" requestTimeout="23:00:00" hostingModel="InProcess">
    

    To:

    <aspNetCore processPath=".\Site.Web.exe" arguments="" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="false" startupTimeLimit="3600" requestTimeout="23:00:00" hostingModel="InProcess">
    

    The processPath is now pointing to the executable generated by the project. In this case, "Site.Web.exe". Also, since no arguments are being parsed in my build, the arguments attribute is left empty. When you push up your next release, the error should be rectified.

    As a side note, there was one thing recommended to me by Azure support regarding my publish settings in Visual Studio. It was recommended that I should set the deployment mode from "Framework-Dependent" to "Self-Contained". This will ensure the application will always run in its current framework version on the off-chance framework changes happen at an Azure level.

  • The Kentico Kontent ASP.NET Core boilerplate contains a CustomContentLinkUrlResolver class that allows all links within your content to be transformed into a custom URL path based on the content-type a link is referencing. The out-of-the-box boilerplate solution works for most scenarios. But there will be times when links cannot be resolved in such a simplistic fashion, especially if your project is using dynamic page routing.

    What we need to do is make a small tweak to the CustomContentLinkUrlResolver class so we can use Kontent’s DeliveryClient object, which in turn allows us to query the API and carry out a complex ruleset for resolving URL’s.

    To give a frame of reference, the out-of-the-box CustomContentLinkUrlResolver class contains the following code:

    public class CustomContentLinkUrlResolver : IContentLinkUrlResolver
    {
        /// <summary>
        /// Resolves the link URL.
        /// </summary>
        /// <param name="link">The link.</param>
        /// <returns>A relative URL to the page where the content is displayed</returns>
        public string ResolveLinkUrl(ContentLink link)
        {
            return $"/{link.UrlSlug}";
        }
    
        /// <summary>
        /// Resolves the broken link URL.
        /// </summary>
        /// <returns>A relative URL to the site's 404 page</returns>
        public string ResolveBrokenLinkUrl()
        {
            // Resolves URLs to unavailable content items
            return "/404";
        }
    }
    

    This will be changed to:

    public class CustomContentLinkUrlResolver : IContentLinkUrlResolver
    {
        IDeliveryClient deliveryClient;
        public CustomContentLinkUrlResolver(DeliveryOptions deliveryOptions)
        {
            deliveryClient = DeliveryClientBuilder.WithProjectId(deliveryOptions.ProjectId).Build();
        }
    
        /// <summary>
        /// Resolves the link URL.
        /// </summary>
        /// <param name="link">The link.</param>
        /// <returns>A relative URL to the page where the content is displayed</returns>
        public string ResolveLinkUrl(ContentLink link)
        {                
            switch (link.ContentTypeCodename)
            {
                case Home.Codename:
                    return "/";
                case BlogListing.Codename:
                    return "/Blog";
                case BlogPost.Codename:
                    return $"/Blog/{link.UrlSlug}";
                case NewsArticle.Codename:
                    // A simplistic example of the Delivery Client in use to resolve a link...
                    NewsArticle newsArticle = Task.Run(async () => await deliveryClient.GetItemsAsync<NewsArticle>(
                                                                                new EqualsFilter("system.id", link.Id),
                                                                                new ElementsParameter("url"),
                                                                                new LimitParameter(1)
                                                                            )).Result?.Items.FirstOrDefault();
    
                    if (!string.IsNullOrEmpty(newsArticle?.Url))
                        return newsArticle.Url;
                    else
                        return ResolveBrokenLinkUrl();
                default:
                    return $"/{link.UrlSlug}"; 
            }
        }
    
        /// <summary>
        /// Resolves the broken link URL.
        /// </summary>
        /// <returns>A relative URL to the site's 404 page</returns>
        public string ResolveBrokenLinkUrl()
        {
            // Resolves URLs to unavailable content items
            return "/404";
        }
    }
    

    In the updated code, we are using DeliveryClientBuilder.WithProjectId() method to create a new instance of the DeliveryClient object, which can then be used if a link needs to resolve a News Article content type. You have may have also noticed the class is now accepting a DeliveryOptions object as its parameter. This object is populated on startup with Kontent’s core settings from the appsettings.json file. All we’re interested in is retrieving the Project ID.

    A small update to the Startup.cs file will also need to be carried out where the CustomContentLinkUrlResolver class is referenced.

    public void ConfigureServices(IServiceCollection services)
    {
        ...
    
        var deliveryOptions = new DeliveryOptions();
        Configuration.GetSection(nameof(DeliveryOptions)).Bind(deliveryOptions);
    
        IDeliveryClient BuildBaseClient(IServiceProvider sp) => DeliveryClientBuilder
            .WithOptions(_ => deliveryOptions)
            .WithTypeProvider(new CustomTypeProvider())
            .WithContentLinkUrlResolver(new CustomContentLinkUrlResolver(deliveryOptions)) // Line to update.
            .Build();
    
        ...
    }
    

    I should highlight at this point the changes that have been illustrated above have been made on an older version of the Kentico Kontent boilerplate. But the same approach applies. The only thing I’ve noticed that normally changes between boilerplate revisions is the Startup.cs file. The DeliveryOptions class is still in use, but you may have to make a small tweak to ascertain its values.

  • I’ll be the first to admit that I very rarely (if at all!) assign a nice pretty share image to any post that gets shared on social networks. Maybe it’s because I hardly post what I write to social media in the first place! :-) Nevertheless, this isn’t the right attitude. If I am really going to do this, then the whole process needs to be quick and render a share image that sets the tone before that will hopefully entice a potential reader to click on my post.

    I started delving into how my favourite developer site, dev.to, manages to create these really simple text-based share images dynamically. They have a pretty good setup as they’ve somehow managed to generate a share image that contains relevant post related information perfectly, such as:

    • Post title
    • Date
    • Author
    • Related Tech Stack Icons

    For those who are nosey as I and want to know how dev.to undertakes such functionality, they have kindly written the following post - How dev.to dynamically generates social images.

    Since my website is built using the Gatsby framework, I prefer to use a local process to dynamically generate a social image without the need to rely on another third-party service. What's the point in using a third-party service to do everything for you when it’s more fun to build something yourself!

    I had envisaged implementing a process that will allow me to pass in the URL of my blog posts to a script, which in turn will render a social image containing basic information about a blog post.

    Intro Into Puppeteer

    Whilst doing some Googling, one tool kept cropping up in different forms and uses - Puppeteer. Puppeteer is a Node.js library maintained by Google Chrome’s development team and enables us to control any Chrome Dev-Tools based browser through scripts. These scripts can programmatically execute a variety of actions that you would generally do in a browser.

    To give you a bit of an insight into the actions Puppeteer can carry out, check out this Github repo. Here you can see Puppeteer is a tool for testing, scraping and automating tasks on web pages. It’s a very useful tool. The only part I spent most of my time understanding was its webpage screenshot feature.

    To use Puppeteer, you will first need to install the library package in which two options are available:

    • Puppeteer Core
    • Puppeteer

    Puppeteer Core is the more lighter-weight package that can interact with any Dev-Tool based browser you already have installed.

    npm install puppeteer-core
    

    You then have the full package that also installs the most recent version of Chromium within the node_modules directory of your project.

    npm install puppeteer
    

    I opted for the full package just to ensure I have the most compatible version of Chromium for running Puppeteer.

    Puppeteer Webpage Screenshot Script

    Now that we have Puppeteer installed, I wrote a script and added it to the root of my Gatsby site. The script carries out the following:

    • Accepts a single argument containing the URL of a webpage. This will be the page containing information about my blog post in a share format - all will become clear in the next section.
    • Approximately screenshot a cropped version of the webpage. In this case 840px x 420px - the exact size of my share image.
    • Use the page name in the URL as the image file name.
    • Store the screenshot in my "Social Share” media directory.
    const puppeteer = require('puppeteer');
    
    // If an argument is not provided containing a website URL, end the task.
    if (process.argv.length !== 3) {
      console.log("Please provide a single argument containing a website URL.");
      return;
    }
    
    const pageUrl = process.argv[2];
    
    const options = {
        path: `./static/media/Blog/Social Share/${pageUrl.substring(pageUrl.lastIndexOf('/') + 1)}.jpg`,
        fullPage: false,
        clip: {
          x: 0,
          y: 0,
          width: 840,
          height: 420
        }
      };
      
      (async () => {
        const browser = await puppeteer.launch({headless: false});
        const page = await browser.newPage()
        await page.setViewport({ width: 1280, height: 800, deviceScaleFactor: 1.5 })
        await page.goto(pageUrl)
        await page.screenshot(options)
        await browser.close()
      })(); 
    

    The script can be run as so:

    node puppeteer-screenshot.js http://localhost:8000/socialcard/Blog/2020/07/25/Using-Instagram-API-To-Output-Profile-Photos-In-ASPNET-2020-Edition
    

    I made an addition to my Gatsby project that generated a social share page for every blog post where the URL path was prefixed with /socialcard. These share pages will only be generated when in development mode.

    Social Share Page

    Now that we have our Puppeteer script, all that needs to be accomplished is to create a nice looking visual for Puppeteer to convert into an image. I wanted some form of automation where blog post information was automatically populated.

    I’m starting off with a very simple layout taking some inspiration from dev.to and outputting the following information:

    • Title
    • Date
    • Tags
    • Read time

    Working with HTML and CSS isn’t exactly my forte. Luckily for me, I just needed to do enough to make the share image look presentable.

    Social Card Page

    You can view the HTML and CSS on JSFiddle. Feel free to update and make it better! If you do make any improvements, update the JSFiddle and let me know!

    Next Steps

    I plan on adding some additional functionality allowing a blog post teaser image (if one is added) to be used as a background and make things look a little more interesting. At the moment the share image is very plain. As you can tell, I keep things really simple as design isn’t my strongest area. :-)

    If all goes to plan, when I share this post to Twitter you should see my newly generated share image.