HttpClient retry mechanism with .NET Core, Polly and IHttpClientFactory

A lot of HttpClient errors are temporary and is caused by server overload, temporary nerwork timeouts and generic gliches in the Matrix. These scenarios can be dealt with using a retry pattern. In .NET Core, the most common retry library is the Polly library:

Polly is a .NET resilience and transient-fault-handling library that allows developers to express policies such as Retry, Circuit Breaker, Timeout, Bulkhead Isolation, and Fallback in a fluent and thread-safe manner. From version 6.0.1, Polly targets .NET Standard 1.1 and 2.0+.
http://www.thepollyproject.org/

Polly makes it relatively easy to implement a retry pattern, as long as you use the IHttpClient and IHttpClientFactory.

But enugh talk, lets code.

STEP 1: THE NUGET PACKAGES

You need (at least) the following NuGet Packages:

  • Polly
  • Microsoft.Extensions.Http.Polly

STEP 2: CONFIGURE SERVICES IN STARTUP.CS

In the services configuration, you need to add a IHttpClientFactory and attach a PolicyHandler to the factory:

//ConfigureServices()  - Startup.cs
services.AddHttpClient("HttpClient").AddPolicyHandler(GetRetryPolicy());

private static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
  return HttpPolicyExtensions
    // Handle HttpRequestExceptions, 408 and 5xx status codes
    .HandleTransientHttpError()
    // Handle 404 not found
	.OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.NotFound)
    // Handle 401 Unauthorized
	.OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.Unauthorized)
    // What to do if any of the above erros occur:
	// Retry 3 times, each time wait 1,2 and 4 seconds before retrying.
	.WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
}

STEP 3: USE THE IHttpClientFactory IN THE CALLING CLASS

The IHttpClientFactory can be injected using constructor injection. The cool part of the Polly implementation is that your HttpClient code does not contain any special retry-code, just the usual Get or Post calls:

namespace MyCode
{
  public class MyClass
  {
    private readonly IHttpClientFactory _clientFactory;

    public MyClass(IHttpClientFactory clientFactory)
    {
      _clientFactory = clientFactory;
    }

    public async Task<int> PostMessage(string postData)
    {
      var httpClient = _clientFactory.CreateClient("HttpClient");

      using (var content = new StringContent(postData, Encoding.UTF8, "application/json"))
      {
        var result = await httpClient.PostAsync($"{url}", content);
        // The call was a success
        if (result.StatusCode == HttpStatusCode.Accepted)
        {
          return result.StatusCode;
        }
        // The call was not a success, do something
        else
        {
          // Do something
          return result.StatusCode;
        }
      }
    }
  }
}

The httpClient.PostAsync() will retry the post call automatically if any of the conditions described in the GetRetryPolicy() occurs. It will only return after the call is either successful or the retry count is met.

MORE TO READ:

Posted in .NET Core, General .NET | Tagged , , | Leave a comment

.NET Core Worker Services with Application Insights and Serilog

The .NET Core Worker service is yet another tool in the .NET toolbox. They are perfect for background processing like reading from a queue or making health checks. They are cross-platform (of course) and they can run on docker containers, and even run on a locally hosted machine as a Windows service.

It took me quite some time to implement Application Insights and file logging (Serilog) in my worker service, so I thought I might write it down, in case I need it another time.

THE NUGET PACKAGES

First caveat was when I realized that you need a special Microsoft.ApplicationInsights.WorkerService NuGet package before Application Insights will work. Also, if you wish to run the worker service as a Windows service you need the Microsoft.Extensions.Hosting.WindowsServices package.

The packages I needed was:

Microsoft.ApplicationInsights.AspNetCore
Microsoft.ApplicationInsights.WorkerService
Microsoft.Extensions.Hosting
Microsoft.Extensions.Hosting.WindowsServices
Serilog
Serilog.AspNetCore
Serilog.Extensions.Hosting
Serilog.Extensions.Logging
Serilog.Settings.Configuration
Serilog.Sinks.Console
System.Configuration.ConfigurationManager

THE APPSETTINGS.JSON CONFIGURATION

The configuration is pretty straight forward. Only caveat is that Serilog is not configured under “Logging” but under it’s own namespace:

{
  "ApplicationInsights": {
    "InstrumentationKey": "the-appinsights-guid"
  },
  "Serilog": {
    "WriteTo": [
      {
        "Name": "File",
        "Args": {
          "path": "log_.txt",
          "rollingInterval": "Day"
        }
      }
    ]
  },
  "Logging": {
    "ApplicationInsights": {
      "LogLevel": {
        "Default": "Information",
        "Microsoft": "Warning"
      }
    },
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Information",
      "Microsoft.Hosting.Lifetime": "Warning"
    }
  }
}

THE PROGRAM.CS

The examples online was too complex for me, so I simplified it and came up with this:

namespace MyWorkerService
{
  public class Program
  {
    public static void Main(string[] args)
    {
      // This will run the worker service
      CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args)
    {
      var host = Host.CreateDefaultBuilder(args);
      // Add this line to be able to run as a windows service
      // https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/windows-service?view=aspnetcore-3.1&tabs=visual-studio
      host.UseWindowsService();
      // This will add the configuration to the application.
      // It will allow you to inject the configuration as 
      // IConfiguration configuration in your constructors 
      host.ConfigureAppConfiguration(
            (hostContext, config) =>
            {
              config.SetBasePath(Directory.GetCurrentDirectory());
              config.AddJsonFile("appsettings.json", false, true);
              config.AddCommandLine(args);
            }
      );
      // This will configure logging. It reads the log settings from 
      // the appsettings.json configuration file, and adds serilog,
      // allowing the application to write logs to file
      host.ConfigureLogging(
            loggingBuilder =>
            {
              var configuration = new ConfigurationBuilder()
                 .AddJsonFile("appsettings.json")
                 .Build();
              var logger = new LoggerConfiguration()
                  .ReadFrom.Configuration(configuration)
                  .CreateLogger();
              loggingBuilder.AddSerilog(logger, dispose: true);
            }
      );
      // The AddHostedServer adds the worker service to the application.
      // The AddApplicationInsightsTelemetryWorkerService is very important. Without this,
      // the Application Insights logging will not work.
      host.ConfigureServices((hostContext, services) =>
      {
        services.AddHostedService<Worker>();
        services.AddApplicationInsightsTelemetryWorkerService();
      });

      return host;
    }
  }
}

The worker itself is the “Worker” class and it looks like this:

using System.Threading;
using System.Threading.Tasks;
using Microsoft.ApplicationInsights;
using Microsoft.ApplicationInsights.DataContracts;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Configuration;

namespace MyWorkerService
{
  public class Worker : BackgroundService
  {
    private readonly ILogger<Worker> _logger;
    private readonly TelemetryClient _telemetryClient;

    public Worker(ILogger<Worker> logger,
      TelemetryClient telemetryClient,
      IConfiguration configuration)
    {
      _logger = logger;
      _telemetryClient = telemetryClient;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
      while (!stoppingToken.IsCancellationRequested)
      {
        using (_telemetryClient.StartOperation<RequestTelemetry>("Execute Async"))
        {
          _logger.LogInformation("ExecuteAsync Started");
          DoWork();
        }
        await Task.Delay(1000, stoppingToken);
      }
    }
  }
}

The DoWork() is where you would do actual work.

MORE TO READ:

Posted in .net, .NET Core, c# | Tagged , , , | Leave a comment

System.DllNotFoundException: Unable to load DLL ‘sni.dll’ or one of its dependencies: The specified module could n ot be found. (0x8007007E)

This message happens when deploying my .NET Core 3.1 application to production (when compiling the code to an .exe file), but not when running the application locally.

It turns out, that Dapper is missing a reference to System.Data.SqlClient. Adding the System.Data.SqlClient NuGet package to the project solves the issue:

System.Data.SqlClient

System.Data.SqlClient

The full error message is:

Unhandled exception. Unhandled exception. System.TypeInitializationException: The type initializer for ‘System.Data.SqlClient.TdsParser’ threw an exception.
—> System.TypeInitializationException: The type initializer for ‘System.Data.SqlClient.SNILoadHandle’ threw an exception.
—> System.DllNotFoundException: Unable to load DLL ‘sni.dll’ or one of its dependencies: The specified module could not be found. (0x8007007E)
at System.Data.SqlClient.SNINativeMethodWrapper.SNIInitialize(IntPtr pmo)
at System.Data.SqlClient.SNINativeMethodWrapper.SNIInitialize()
at System.Data.SqlClient.SNILoadHandle..ctor()
at System.Data.SqlClient.SNILoadHandle..cctor()
— End of inner exception stack trace —
at System.Data.SqlClient.TdsParser..cctor()
— End of inner exception stack trace —
at System.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, UInt32 waitForMultipleObject
sTimeout, Boolean allowCreate, Boolean onlyOneCheckConnection, DbConnectionOptions userOptions, DbConnectionInternal& co
nnection)
at System.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, TaskCompletionSource`1 retry
, DbConnectionOptions userOptions, DbConnectionInternal& connection)
at System.Data.ProviderBase.DbConnectionFactory.TryGetConnection(DbConnection owningConnection, TaskCompletionSource`
1 retry, DbConnectionOptions userOptions, DbConnectionInternal oldConnection, DbConnectionInternal& connection)
at System.Data.ProviderBase.DbConnectionInternal.TryOpenConnectionInternal(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource`1 retry, DbConnectionOptions userOptions)
at System.Data.ProviderBase.DbConnectionClosed.TryOpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource`1 retry, DbConnectionOptions userOptions) at System.Data.SqlClient.SqlConnection.TryOpen(TaskCompletionSource`1 retry)
at System.Data.SqlClient.SqlConnection.Open()
at Dapper.SqlMapper.QueryImpl[T](IDbConnection cnn, CommandDefinition command, Type effectiveType)+MoveNext() in C:\projects\dapper\Dapper\SqlMapper.cs:line 1079
at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
at Dapper.SqlMapper.Query[T](IDbConnection cnn, String sql, Object param, IDbTransaction transaction, Boolean buffered, Nullable`1 commandTimeout, Nullable`1 commandType) in C:\projects\dapper\Dapper\SqlMapper.cs:line 721

MORE TO READ:

Posted in .net, .NET Core, c# | Tagged , , | Leave a comment

Sync Sitecore content to external database

The legal department of the client requested that we sync parts of the Sitecore tree to an external, system-versioned temporal table, so that we have a full audit trail of changes to these items including before-and-after values.

System-Versioned Table

Example of a System-Versioned Table. When looking into the History you can see a full audit trail of the Sitecore items

Fear not, this is what Sitecore does best. All we need to do is to create a SQL server database with temporal tables, create upsert (a combined insert and update statement) and delete Stored Procedures, and then hook into the Sitecore item:created, item:renamed, item:saved, item:moved and item:deleted events.

STEP 1: THE BASIC DATABASE PATTERN

First we need to create a table for the data. Maybe you like to create your tables in a different matter, and that is OK, but this is the minimum required fields needed to perform a data audit:

CREATE TABLE [dbo].[MyItem]
(
    [SitecoreItemID] UNIQUEIDENTIFIER NOT NULL PRIMARY KEY,
	...
	... here you define the fields from the Sitecore template
	...
    [Created] DATETIME NOT NULL DEFAULT GetDate(), 
    [Updated] DATETIME NULL, 
    [Deleted] DATETIME NULL, 
    [IsDeleted] BIT NOT NULL, 
    [SitecoreUserName] NVARCHAR(255) NOT NULL,
    [SysStartTime] DATETIME2 GENERATED ALWAYS AS ROW START NOT NULL,
    [SysEndTime] DATETIME2 GENERATED ALWAYS AS ROW END NOT NULL,
    PERIOD FOR SYSTEM_TIME (SysStartTime,SysEndTime)
    )
WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.MyItem));
  • [SitecoreItemID]
    Is the key value is the Sitecore item id. Some like to have an auto-incrementing integer as key, and that’s also fine, as long as you at least have the Sitecore item ID in the database.
  • [Created], [Updated], [Deleted]
    Timestamps are used to determine when database operations are carried out
  • [IsDeleted]
    Instead of deleting items, we mark them as deleted. The [Deleted] timestamp will tell us when the item was deleted.
  • [SitecoreUsername]
    Is the name of the Sitecore editor creating/updating/deleting the item
  • [SysStartTime],[SysEndTime]
    Is used by SQL to handle the system-versioning.
  • Remember to add all the fields from your Sitecore template that needs to be synchronized.

Also note that in my example, I do not store the item hierarchy, item languages or item versions.

STEP 2: UPSERT AND DELETE STORED PROCEDURES:

The basic UPSERT Stored procedure is as follows:

  • Select from database with [SitecoreItemID]
  • If found: Update
    Update with values from the Sitecore template
    Set [Updated] to current datetime
    Set [SitecoreUserName] with the Sitecore username
  • If not found: Insert
    Insert values from the Sitecore template
    Set [SitecoreUserName] with the Sitecore username

The basic DELETE Stored procedure is as follows:

  • Set [IsDeleted] to TRUE
    Set [SitecoreUserName] with the Sitecore username
    Set [Deleted] with the current datetime.

STEP 3: HOOK INTO SITECORE 

Now for the fun part, the Sitecore code.

We need to hook into the following Sitecore events:

  • item:created: Call the UPSERT stored procedure
  • item:renamed:Call the UPSERT stored procedure
  • item:saved: Call the UPSERT stored procedure
  • item:moved: Call the UPSERT stored procedure
  • item:deleted: Call the DELETE stored procedure

My code will only sync one language and always the latest version. If you need to sync more languages or versions, remember to add those as key values to your database.

First the code. This is a pseudo-code example, outlining what you need to implement.

using System;
using Sitecore.Data.Items;
using Sitecore.Events;

namespace MyCode
{
  public class SyncItems
  {
    public void OnItemChanged(object sender, EventArgs args)
    {
      Item changedItem = Event.ExtractParameter(args, 0) as Item;
      if (changedItem == null)
        return;
      // We only synchronize one language and only the latest version:
      if (changedItem.Language != Language.Parse("en"))
        return false;
      if (!changedItem.Versions.IsLatestVersion())
        return false;
        
      // If changed item is of the template we wish to synchronize:
      // Call the upsert stored procedure
    }

    public void OnItemMoved(object sender, EventArgs args)
    {
      Item movedItem = Event.ExtractParameter(args, 0) as Item;
      if (movedItem == null)
        return;
      // We only synchronize one language and only the latest version:
      if (movedItem.Language != Language.Parse("en"))
        return false;
      if (!movedItem.Versions.IsLatestVersion())
        return false;

      // If moved item is of the template we wish to synchronize:
      // Call the upsert stored procedure
    }
    
    public void OnItemDeleted(object sender, EventArgs args)
    {
      Item deletedItem = Event.ExtractParameter(args, 0) as Item;
      if (deletedItem == null)
        return;
      // We only synchronize one language and only the latest version:
      if (deletedItem.Language != Language.Parse("en"))
        return false;
      if (!deletedItem.Versions.IsLatestVersion())
        return false;

      // If deleted item is of the template we wish to synchronize:
      // Call the delete stored procedure
    }
}   

Then the Sitecore configuration:

<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:role="http://www.sitecore.net/xmlconfig/role/" xmlns:env="http://www.sitecore.net/xmlconfig/env/">
  <sitecore>
    <events>
      <event name="item:created">
        <handler type="MyCode.SyncItems, MyCode" method="OnItemChanged"/>
      </event>
      <event name="item:renamed">
        <handler type="MyCode.SyncItems, MyCode" method="OnItemChanged"/>
      </event>
      <event name="item:saved">
        <handler type="MyCode.SyncItems, MyCode" method="OnItemChanged"/>
      </event>
      <event name="item:moved">
        <handler type="MyCode.SyncItems, MyCode" method="OnItemMoved"/>
      </event>
      <event name="item:deleted">
        <handler type="MyCode.SyncItems, MyCode" method="OnItemDeleted"/>
      </event>
    <events>
  </sitecore>
</configuration>

THINGS TO CONSIDER:

Should you use a synchronous or asynchronous update method?

If you call the stored procedures synchronously, all code will wait for and answer from the  SQL server. If you a asynchronous approach, you don’t catch exceptions from the database. It is up to you how critical errors are. There are coding patterns that will give you a more foul proof approach such as database retry mechanisms, or implementing a queue in between Sitecore and the database. You decide what is best.

What happens if you move an item with children?

If you are synchronizing items with children, and you stored the hierarchy of items in the database, you will need to make a cascading update on those children. Sitecore will not throw individual events for the children, only the root item that is moved.

What happens if users restore items from the recycle bin?

This is a situation that is currently undetermined, as there is no event attached to the recycle bin restore.

Are there any benefits? Why don’t just use Sitecore versioning and workflows?

Workflows are designed to enforce business rules upon editors, and implies a very stringent way of working. Workflows discourages casual editing and is often abandoned for that very reason. You should decide if a simple workflow would work for you.

MORE TO READ:

Posted in .net, c#, Sitecore 5, Sitecore 6, Sitecore 7, Sitecore 8, Sitecore 9 | Tagged , , , , , , | Leave a comment

C# Get expiry timestamp from JWT token

JWT tokens (or Json Web Tokens) are an open-standard the defines a way to transmit information between 2 parties in a secure manner. Identity Server 4 uses JWT as a security token.

These tokens have an expiry timestamp, and if you handle the tokens yourself, you need to read the token expiry and refresh the token if the token is expired.

Microsoft have made a brilliant library, System.IdentityModel.Tokens.Jwt to handle JWT tokens, but the package does also have a lot of dependencies that were incompatible with my application, so I chose to use JWT.Net instead, as this package does not have any dependencies at all.

THE ANATOMY OF A JWT TOKEN:

Json Web Token Anatomy

Json Web Token Anatomy

A JWT token consists of a header, a payload and a signature. It is in the payload that you find the expiry timestamp in the “exp” field. The timestamp is the stupid UNIX timestamp format, but fear not, .NET knows how to convert the timestamp to a real DateTime.

STEP 1: CREATE A PAYLOAD MODEL CLASS

JWT.Net is not as powerful as System.IdentityModel.Tokens.Jwt, so you need to create a model class of the payload section. The class, however, is very simple:

namespace MyCode
{
  public class JwtToken
  {
    public long exp { get; set; }
  }
}

STEP2: USE JWT.Net TO GET THE EXPIRY FROM THE TOKEN PAYLOAD

Final step is to take the JWT Token string and decode it to the JwtToken class, then convert the UNIX timestamp to a local time:

using System;
using JWT;
using JWT.Algorithms;
using JWT.Serializers;

namespace MyCode
{
  public class JWTService
  {
    private IJsonSerializer _serializer = new JsonNetSerializer();
    private IDateTimeProvider _provider = new UtcDateTimeProvider();
    private IBase64UrlEncoder _urlEncoder = new JwtBase64UrlEncoder();
    private IJwtAlgorithm _algorithm = new HMACSHA256Algorithm();

    public DateTime GetExpiryTimestamp(string accessToken)
    {
      try
      {
        IJwtValidator _validator = new JwtValidator(_serializer, _provider);
        IJwtDecoder decoder = new JwtDecoder(_serializer, _validator, _urlEncoder, _algorithm);
        var token = decoder.DecodeToObject<JwtToken>(accessToken);
        DateTimeOffset dateTimeOffset = DateTimeOffset.FromUnixTimeSeconds(token.exp);
        return dateTimeOffset.LocalDateTime;
      }
      catch (TokenExpiredException)
      {
        return DateTime.MinValue;
      }
      catch (SignatureVerificationException)
      {
        return DateTime.MinValue;
      }
      catch (Exception ex)
      {
        // ... remember to handle the generic exception ...
        return DateTime.MinValue;
      }
    }
  }
}

That’s it. You are now a security expert. Happy coding.

FUNNY FINAL NOTE:

The term “JWT Token” is a redundant acronym syndrome, or RAS-syndrome. It is the use of the last word of the acronym in conjunction with the abbreviated form. It’s like saying “PIN number” or “PDF format”. In reality, when saying “JWT Token”, you are really saying “json web token token” :).

MORE TO READ:

Posted in .net, .NET Core, c#, General .NET, Microsoft Azure | Tagged , , , , | Leave a comment

Application Insights not working in .NET Core Console Application – Remember to flush the TelemetryClient

My .NET Core console application did not write to my Application Insights instance. Well, it did, but only sometimes.

Writing to Application Insights is done asynchronously, usually every 30 seconds or 500 items. This means that you need to give your application some time to flush the cache before closing down. This can be done by configuring a ProcessExit method. This method is called when your Main() function exists.

This is a template where I initialize my TelemetryClient, and then flushes and waits 5 seconds for the cache to flush before the application is exited:

using System;
using System.Threading;
using Microsoft.ApplicationInsights;
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.Extensions.DependencyInjection;

namespace MyCode
{
  internal class Program
  {
    public static void Main(string[] args)
    {
      // Initialize Service Collection
      var serviceCollection = new ServiceCollection();
      ConfigureServices(serviceCollection);

      // Initialize Service Provider
      var serviceProvider = serviceCollection.BuildServiceProvider();

      // Handling finalizing when process is ended
      AppDomain.CurrentDomain.ProcessExit += (s, e) => FinalizeApplication(serviceProvider);

      // Run Application
      // ... do run ...
    }

    private static void FinalizeApplication(ServiceProvider serviceProvider)
    {
      // Give TelemetryClient 5 seconds to flush it's content to Application Insights
      serviceProvider.GetService<TelemetryClient>().Flush();
      Thread.Sleep(5000);
    }

    private static void ConfigureServices(IServiceCollection serviceCollection)
    {
      // Add Application Insights
      var telemetryConfiguration = TelemetryConfiguration.CreateDefault();
      telemetryConfiguration.InstrumentationKey = "add-your-own-key";
      var telemetryClient = new TelemetryClient(telemetryConfiguration);
      serviceCollection.AddSingleton(telemetryClient);
    }

  }
}

HOW IT WORKS:

My .NET Core 3.1 console application is started by the Main() function. It starts by initializing the service collection. The only thing I do is adding a TelemetryClient to the collection, but this is where you would all all of your services.

The AppDomain.CurrentDomain.ProcessExit is configured to call the “FinalizeApplication” method, and this method will now be called whenever the Main() function exits.

The FinalizeApplication will then flush the TelemetryClient, and waits 5 seconds for the flush to do it’s magic.

Without the 5 second wait, I risk that the Flush is not done flushing before the thread is discontinued, hence the wait. For some reason, 5 seconds seems to be the magic threshold for a flush to do it’s thing.

MORE TO READ:

Posted in .NET Core, Microsoft Azure | Tagged , , | Leave a comment

Azure App Configuration – Getting the connection string from appsettings.json

Azure App Configuration is a services that provides a central place to manage application settings, and it provides cool features like feature flags and auto refresh of settings when they are changed.

Azure App Configuration

Azure App Configuration

To access Azure App Configuration from your .NET Core application, you include the preview NuGet Package:

Microsoft.Extensions.Configuration.AzureAppConfiguration

PLEASE NOTE: As per 2020-02-14, Azure App Configuration is still in beta. You must check the “include prerelease” button in Visual Studio to see the NuGet package.

To connect to the Azure App Configuration you need a connection string.

Most of the guides and help pages expects you get the connection string from an environment variable, or that you create a web host application and can get the connection string from a web config file.

But if you would like to use Azure App Configuration together with a local appsettings.json file, there is no help available.

The challenge is that the connectionstring to the Azure App Configuration is not available before you read and build a IConfiguration reading from the local appsettings.json. So you need some way to split the calls to ConfigurationBuilder and then merge the 2 together.

THE SCENARIO:

Let’s assume that you have a local appsettings.json file and that file includes the connectionstring (and maybe even some more settings that are not to be tampered with by those with access to the Azure App Configurations):

{
  "Logging": {
    "FileLogPath": "e:\somewhere\log_.txt"
  },
  "ConnectionStrings": {
    "AzureAppConfiguration": "Endpoint=https://xxxxxxxx.azconfig.io;Id=xxxxxxxx;Secret=xxxxxxxx",
  }
}

THE SOLUTION:

You then need to merge these settings with the settings from Azure App Configuration.

var localConfig = new ConfigurationBuilder()
 .SetBasePath(Directory.GetCurrentDirectory())
 .AddJsonFile("appsettings.json", false, true)
 .Build();

var configuration = new ConfigurationBuilder()
 .AddConfiguration(localConfig)
 .AddAzureAppConfiguration(
    options =>
    {
      options.Connect(configuration.GetConnectionString("AzureAppConfiguration"));
    }
  )
 .Build();

The localConfig variable contains the settings from appsettings.json. This configuration is then merged with the Azure App Configuration into the configuration variable, which is then the final IConfiguration that will be used in the application.

MORE TO READ:

Posted in .NET Core, c#, Microsoft Azure | Tagged , , , | Leave a comment

Command line parameters in .NET Core Console Applications

When implementing .NET Core console applications, you can extract the command line parameters from the void Main(string[] args) method, but it can be a little tedious to do so. To help with the parsing, Microsoft used to maintain the Microsoft.Extensions.CommandLineUtils, but this library have not been in development since 2017, so this is not a good option.

Thankfully, Nate McMaster forked the Microsoft project and created the CommandLineUtils, so developers like you and I don’t have to concern us with parsing of parameters, creating help pages, documentation and so on.

First you need to install the NuGet package:

McMaster.Extensions.CommandLineUtils

This is a base template of how your Program.cs main entry point will look like when using CommandLineUtils:

using McMaster.Extensions.CommandLineUtils;

namespace MyApplication
{
  [Command(Name = "The application name", Description = "The application description")]
  [HelpOption("-?")]
  public class Program
  {
    static void Main(string[] args) => CommandLineApplication.Execute<Program>(args);

    [Argument(0, Description = "The first argument")]
    private string MyArgument { get; }

    [Option("-o|--option", Description = "An example parameter")]
    private bool MyOption { get; }
    
    private void OnExecute()
    {
      // Initialize all the things...
      // Configure logging, application insights, ...
      // Run your application
    }
  }
}

EXPLANATION: THE Main(string[] args) METHOD:

This function is now pointing to the “OnExecute()” function instead. Treat theOnExecute() method as you would treat the Main(string[] args) method and initialize the servicecollection, logging, dependencies as you would normally do it.

EXPLANATION: ATTRIBUTE [Argument(…..)] VS [Option(……)]

CommandLineUtils treat command line parameters differently.

The [Argument] attribute determines parameters without any prefix. They also have a fixed position in the list of parameters.

The the example above, the MyArgument MUST be the first parameter when the application is called. This is determined by the value zero (0) in the attribute:

[Argument(0, Description = "The first argument")]

The value of MyArgument is set only by the first parameter:

./myapplication.exe firstparameter

Here, the value of MyArgument will be “firstparameter“.

The [Option] attribute does not have a fixed position, but a prefix instead. The boolean value of MyOption will be FALSE if -o or -option is NOT set, but true if -o or -option is set:

./myapplication.exe -o

Here, the value of MyOption is TRUE.

Options can have string parameters as well:

[Option("-o2|--option2", Description = "Another Option")]
private string MyOption2 { get; } = "Hello World";

The value of MyOption2 is “Hello world” unless you set the value in a parameter:

./myapplication.exe -o2 AnotherValue

Here, the value of MyOption2 will be “AnotherValue“.

The library have tons of more options, from automatic help pages, prompts for yes/no/passwords, optional/required parameters and so on. Dig into the library and see for yourself. Happy coding.

MORE TO READ:

Posted in .NET Core, c# | Tagged , | Leave a comment

Sitecore Create/Read/Update/Delete/Copy/Move/Rename item – the beginners guide

New Sitecorians often ask the most trivial questions, and I am happy to answer them. This question popped up lately: How do you perform basic CRUD operations on your Sitecore items? Well, it’s easy:

READ ITEM (GET ITEM):

// Get item from content database:
Item item = Sitecore.Context.ContentDatabase.GetItem("/sitecore/content/home");
// Get item from ID:
Item item = Sitecore.Context.ContentDatabase.GetItem(new Sitecore.Data.ID("{9464f2c9-8490-40e9-a95b-17f8a5128da6}");
// Get item from named database
Item item = Sitecore.Configuration.Factory.GetDatabase("master").GetItem("/sitecore/content/home");

CREATE ITEM:

// You need to have a root item to create item under:
Item item = Sitecore.Context.ContentDatabase.GetItem("/sitecore/content/home");
// You also need a template to create the item from:
TemplateID template = new TemplateID(new ID("{434b38a2-b929-4a89-bbc8-a6b66281e014}"));
// Then you can create a new item:
Item newItem = item.Add("new item", template);
// If you wish to create an item based on a branch:
BranchId branch = new BranchId(new ID("{4f254169-7666-4c2e-8021-a05026d5a2e2}"));
Item newItem = item.Add("new item", branch);

UPDATE ITEM:

// You need to have an item to update:
// Remember to always update items in the MASTER database,
// NOT the WEB Database:
Item item = Factory.GetDatabase("master").GetItem("/sitecore/content/home");
// You then set the item in editing mode
item.Editing.BeginEdit();
try
{ 
  // Change the contents of the fields to update
  item.Fields["field"].Value = "new value";
  // End edit writes the updates to the database:
  item.Editing.EndEdit();
}
catch (Exception ex)
{
  // in case of an exception, you do not really
  // need to cancel editing, but it is good 
  // manners and it indicates that you know
  // what the code is doing
  item.Editing.CancelEdit();
}

DELETE ITEM:

// You need to have an item to delete:
// Remember to always update items in the MASTER database,
// NOT the WEB Database:
Item item = Factory.GetDatabase("master").GetItem("/sitecore/content/home");
// Remember that deleting one item also delete the children.
// The item.Recycle() moves the item to the recycle bin, whilst the 
// item.Delete() permanently deletes the item:
item.Recycle();

COPY ITEM:

// You need to have a source destination:
Item destinationItem = Factory.GetDatabase("master").GetItem("/sitecore/content/home");
// You also need an item to copy:
Item sourceItem = Factory.GetDatabase("master").GetItem("/sitecore/content/sourceitem");
// Then you can copy:
sourceItem.CopyTo(destinationItem, "new name");

MOVE ITEM:

// You need to have a source destination:
Item destinationItem = Factory.GetDatabase("master").GetItem("/sitecore/content/home");
// You also need an item to move:
Item sourceItem = Factory.GetDatabase("master").GetItem("/sitecore/content/sourceitem");
// Then you can copy:
sourceItem.MoveTo(destinationItem);

RENAME ITEM:

// You need to have an item to rename:
Item item = Factory.GetDatabase("master").GetItem("/sitecore/content/home");
item.Editing.BeginEdit();
item.Name = "new name";
item.Editing.EndEdit();

MORE TO READ:

Posted in .net, c#, General .NET, Sitecore 5, Sitecore 6, Sitecore 7, Sitecore 8, Sitecore 9 | Tagged , | 1 Comment

Sitecore use item:deleting event to avoid deletion of items

The Sitecore extendable model allows you to build business rules into your Sitecore solution. This is an example where I disallow deletion of certain items when a certain business rule is true.

This pattern could be implemented using a field or template validator, or by using the Sitecore workflow,  but it would require you to remember to add the validator to the correct template, and also requires that no one tampers with your template settings.

Sitecore have an item:deleting event that is fired before an item is deleted. The arguments include a Result.Cancel property that can be set to false, thus stopping the deletion of your item. So the implementation looks like this:

using System;
using Sitecore.Data.Items;
using Sitecore.Events;
using Sitecore.Web.UI.Sheer;

namespace MyProject
{
  public class OnItemDeletingValidator
  {
    public void OnItemDeleting(object sender, EventArgs args)
    {
      // get the item that is being moved
      Item item = Event.ExtractParameter(args, 0) as Item;
      
      if (item == null)
        return;

      if (item.Database.Name != "master")
        return;

      // Only run the rule on a certain template.
      // Replace the "MyTemplate" with the name of your own
      // template
      if (item.TemplateName != "MyTemplate")
        return;

      // Check to see if the business rule is true or false
      // If true, do nothing.
      // Implemement your own business rule here
      if (BusinessRule(item))
        return;
      
      // Alert the user and cancel the delete
      SheerResponse.Alert("You cannot delete this item because the business rule is false");
      ((SitecoreEventArgs)args).Result.Cancel = true;
    }
  }
}

The code above is just a pseudo code example. You need to implement your own business rule, and determine your own template.

Also remember to assign the code to the event using configuration:

<event name="item:deleting">
  <handler type="MyProject.OnItemDeletingValidator, MyProject" method="OnItemDeleting"/>
</event>

MORE TO READ:

Posted in .net, c#, Sitecore 6, Sitecore 7, Sitecore 8, Sitecore 9 | Tagged , , | 1 Comment