.NET Core Caching in your API using AddResponseCaching and ResponseCache attribute

Caching the response of an API speeds up the API endpoint because the code that generates the response is not called, but rather fetched from memory. This is especially helpful for API’s where the response is time-consuming to retrieve but the response does not change.

To add a response cache, you need to declare that caching is enabled, then specify the cache parameters for each of your API endpoints.

STEP 1: ENABLE CACHING IN Program.cs

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddResponseCaching();  
var app = builder.Build();
app.UseHttpsRedirection();
// UseCors must be called before UseResponseCaching
// app.UseCors();
app.UseResponseCaching();
app.UseAuthorization();
app.MapControllers();
app.Run();

It is important that you call UseResponseCaching AFTER you have called UseCors.

STEP 2: DEFINE THE CACHE PROPERTIES FOR YOUR API ENDPOINT

To declare the cache properties for an endpoint, use the [ResponseCache] attribute:

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

namespace MyCode.Controllers
{
  [Route("api/[controller]")]
  [ApiController]
  public class MyController : ControllerBase
  {
    public MyController()
    {
    } 

    [HttpGet]
    [ResponseCache(Duration=10)]
    public string? GetCatFact()
    {
      // Pseudocode. Call something that will return a random cat fact
      // Insert your own code logic here:
      CatModel? catFact = _httpRepository.GetAsync<CatModel>("https://catfact.ninja/fact").Result;
      return catFact?.Fact;
    }
  }
}

In the example above, the ResponseCache attribute will cache the response for 10 seconds unconditionally.

The first request will call the underlying code, but the next requests are fetched from the memory cache, until the cache expires after 10 seconds. The response header will now have a max-age of 10, indicating the cache duration:

Response headers now has the cache-control max-age of 10, indicating that this response is cached in 10 seconds.
API Response as seen in Swagger

VARY THE CACHE BY HEADERS OR QUERYSTRING

The your API endpoint response is controlled using querystrings, you should add those as variation:

[HttpGet]
[ResponseCache(Duration=30, VaryByQueryKeys=new[] {"id", "search"})]
public string? GetCatFact([FromQuery] int id, [FromQuery] string search)
{
  // Still pseudo code, add your own logic
  CatModel? catFact = _httpRepository.GetAsync<CatModel>("https://catfact.ninja/fact").Result;
  return catFact?.Fact + id + search;
}

The response will very depending of the value of the querystrings. Use VaryByHeaders to vary by headers.

DEFINING CACHE PROFILES

Instead of defining the cache individually per endpoint, you can create cache profiles that can be reused. Go to the Program.cs and add the profiles to the AddControllers method:

builder.Services.AddControllers(options => 
  {
    options.CacheProfiles.Add("Default", 
      new CacheProfile() { Duration = 10 }
    );
    options.CacheProfiles.Add("Client",
      new CacheProfile() { Location = ResponseCacheLocation.Client, Duration = 10 }
    );
  }
);

Then use the profile name in the API endpoint. You can still decorate the endpoint with VaryByHeader/VaryByQueryKeys:

  [HttpGet]
  [ResponseCache(CacheProfileName="Default")]
  public string? GetCatFact()
  {
    CatModel? catFact = _httpRepository.GetAsync<CatModel>("https://catfact.ninja/fact").Result;
    return catFact?.Fact;
  }

  [HttpGet("2")]
  [ResponseCache(CacheProfileName="Client", VaryByQueryKeys=new[] {"id", "search"})]
  public string? GetCatFact2([FromQuery] int id, [FromQuery] string search)
  {
    CatModel? catFact = _httpRepository.GetAsync<CatModel>("https://catfact.ninja/fact").Result;
    return catFact?.Fact + id + search;
  }

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

MORE TO READ:

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

C# Use HttpClient to GET JSON from API endpoint

So, most API endpoints return JSON anyway, right? So why not make a method that can make a GET call to an API and return the response as an object directly?

It’s actually not that hard:

STEP 1: MAKE A MODEL CLASS

My Catfact API endpoint returns the following JSON:

{
  "fact": "All cats need taurine in their diet to avoid blindness. Cats must also have fat in their diet as they are unable to produce it on their own.",
  "length": 140
}

So my model class is equally simple:

public class CatModel
{
  public string Fact  { get; set; }
  public int Length { get; set; } 
}

STEP 2: MAKE A HTTPCLIENTREPOSITORY

using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;

namespace MyCode
{
  public class HttpClientRepository
  {
    private IHttpClientFactory _httpClientFactory;

    public HttpClientRepository(IHttpClientFactory httpClientFactory)
    {
      _httpClientFactory = httpClientFactory;
    }

    public async Task<T?> GetAsync<T>(string url)
    {
      var client = _httpClientFactory.CreateClient("HttpClient");
      var response = await client.GetAsync(url);
      if (!response.IsSuccessStatusCode)
        throw new Exception($"{response.StatusCode}: {response.ReasonPhrase}");
      var responseObject = await response.Content.ReadFromJsonAsync<T>();
      return responseObject;
    }

  }
}

The method “GetAsync” deserializes the return value from the API endpoint to the model of a Generic Type “T“.

A Generic Type is a placeholder for a specific type that you define later. Using a Generic Type ensures that you can call this method regardless of the model returned, as long as the JSON can be deserialized to that model.

In other words: If your specific type matches the JSON returned, everything is fine.

STEP 3: HOW TO USE IT

In your Program.cs, add an IHttpClient and the HttpClientRepository:

builder.Services.AddHttpClient("httpClient");
builder.Services.AddSingleton<HttpClientRepository>();

To call the method is quite simple. This function returns the cat fact string:

public async Task<string?> GetCatFactAsync()
{
  CatModel? catFact = await _httpRepository.GetAsync<CatModel>("https://catfact.ninja/fact");
  return catFact?.Fact;
}

Not familiar with async coding? Don’t worry, just call the method it in the not-recommended way:

public string? GetCatFact()
{
  CatModel? catFact = _httpRepository.GetAsync<CatModel>("https://catfact.ninja/fact").Result;
  return catFact?.Fact;
}

But do yourself a favor and read up on async coding.

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

MORE TO READ:

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

C# HttpClient and IHttpClientFactory in .net core

The C# HttpClient is the go-to library when you need to GET, PUT, POST or DELETE from an API Endpoint. But one of the issues with HttpClient is that it needs to be instantiated as a singleton. This is a big no-no:

public static async Task<string> Get(string queryString)
{
  using (var httpClient = new HttpClient())
  {
    using (var result = await httpClient.GetAsync($"https://briancaos.wordpress.com"))
	{
	  string content = await result.Content.ReadAsStringAsync();
	  return content;
	}
  }
}

The HttpClient is a shared object, and disposing the HttpClient for each call will lead to socket exhaustion.

SO WHAT TO DO THEN?

The trick is to use a IHttpClientFactory. This will create static instances for you (as well as allowing you to use Polly and other middlewares, but that’s a story for another time).

It’s very easy to use:

STEP 1: INSTANTIATE AN IHttpClientFactory AT STARTUP:

To instantiate an IHttpClientFactory, use builder.Services.AddHttpClient:

var builder = WebApplication.CreateBuilder(webApplicationOptions);
...
builder.Services.AddHttpClient("HttpClient");
...
var app = builder.Build();
await app.RunAsync();

The IHttpClientFactory is named, and you can have several IHttpClientFactory instances, each with their own name. If you don’t know why you should have more then one, then you don’t need more than one.

STEP 2: INJECT THE IHttpClientFactory INTO YOUR CONSTRUCTOR

This is the basics of how your repository should look:

namespace MyCode
{
  public class MyRepository
  {
    private IHttpClientFactory _httpClientFactory;

    public MyRepository(IHttpClientFactory httpClientFactory)
    {
      _httpClientFactory = httpClientFactory;
    }

    public async Task<string> GetAsync(string url)
    {
      var client = _httpClientFactory.CreateClient("HttpClient");

      var response = await client.GetAsync($"{url}");
      if (!response.IsSuccessStatusCode)
        throw new Exception($"{response.StatusCode}: {response.ReasonPhrase}");
      var response = await response.Content.ReadAsStringAsync();
	  return response;
    }
  }
}

The _httpClientFactory.CreateClient(“HttpClient”); will ensure that you reuse the httpclient already created at startup.

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

MORE TO READ:

Posted in .net, .NET Core, c#, General .NET | Tagged , , , , | 1 Comment

C# String Token Replacer

Imagine this classic text where you need to replace tokens with some values:

Welcome {{user}}. Click on this link to confirm your subscription: {{url}}.

Tokens encapsulated in double brackets {{token}} are commonly used to mark tokens to be replaced with real values.

It’s simple to make your own token replacer:

using System.Collections.Generic;

namespace MyCode
{
  public static class TokenReplacer
  {
    public static string ReplaceTokens(this string s, Dictionary<string, string> tokens)
    {
      if (tokens == null)
        return s;

      foreach (var token in tokens)
        s = s.Replace("{{" + token.Key + "}}", token.Value);

      return s;
    }
  }
}

Usage is easy:

var tokenValues = new System.Collections.Generic.Dictionary<string, string>();
tokenValues.Add("user", "briancaos");
tokenValues.Add("url", "https://briancaos.wordpress.com/");
string text = "Welcome {{user}}. Click on this link to confirm your subscription: {{url}}.";
Console.WriteLine(text.ReplaceTokens(tokenValues));

// Output is:
// Welcome briancaos. Click on this link to confirm your subscription: https://briancaos.wordpress.com/.

That’s it. Happy coding.

MORE TO READ:

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

Sitecore The type or namespace name ‘Job’ does not exist in the namespace ‘Sitecore.Jobs’ (are you missing an assembly reference?)

From Sitecore 9.0 to Sitecore 9.2, Sitecore updated the Jobs namespace:

  • Job have become Sitecore.Abstractions.BaseJob abstract class and Sitecore.Jobs.DefaultJob concrete implementation.
  • JobOptions have become Sitecore.Abstractions.BaseJobOptions abstract class and Sitecore.Jobs.DefaultJobOptions.

This resonates with the Sitecore structural changes where Sitecore implements abstractions for most of their code. This change began all the way back in Sitecore 7.5 and makes it possible for the Sitecore development team to perform unit tests.

So you’ll need to change your Jobs code:

GET ALL JOBS:

  public IEnumerable<Sitecore.Abstractions.BaseJob> Jobs
  {
    get
    {
      return Sitecore.Jobs.JobManager.GetJobs().OrderBy(job => job.QueueTime);
    }
  }

GET STATUS OF A JOB:

  protected string GetJobMessages(Sitecore.Jobs.DefaultJob job)
  {
    System.Text.StringBuilder sb = new StringBuilder();
    if (job.Options.ContextUser != null)
      sb.AppendLine("Context User: " + job.Options.ContextUser.Name);
    sb.AppendLine("Priority: " + job.Options.Priority.ToString());
    sb.AppendLine("Messages:");
    foreach (string s in job.Status.Messages)
      sb.AppendLine(s);
    return sb.ToString();
  }

CREATE A NEW JOB:

string jobName = "MyJob"

DefaultJobOptions options = new DefaultJobOptions(jobName, "Update", Sitecore.Context.Site.Name, this, "Update", new object[] { }) { ContextUser = Sitecore.Context.User, Priority = System.Threading.ThreadPriority.Normal };

JobManager.Start(options);

UPDATE STATUS OF JOB:

Sitecore.Abstractions.BaseJob job = JobManager.GetJob(jobName);
job.Status.State = JobState.Running;
job.Status.Total = 42;

MORE TO READ:

Posted in c#, Sitecore 10, Sitecore 9 | Tagged , , , | Leave a comment

C# byte size to string

How do you convert a byte size into a human readable string?

There are many many ways of doing this. I made probably the simplest and most readable version, and wrapped it as an extension method:

public static class LongExtensions
{
  public static string ToSizeString(this long l)
  {
    long KB = 1024;
    long MB = KB * 1024;
    long GB = MB * 1024;
    long TB = GB * 1024;
    double size = l;
    if (l >= TB)
    {
      size = Math.Round((double)l / TB, 2);
      return $"{size} TB";
    }
    else if (l >= GB)
    {
      size = Math.Round((double)l / GB, 2);
      return $"{size} GB";
    }
    else if (l >= MB)
    {
      size = Math.Round((double)l / MB, 2);
      return $"{size} MB";
    }
    else if (l >= KB)
    {
      size = Math.Round((double)l / KB, 2);
      return $"{size} KB";
    }
    else
    {
      return $"{size} Bytes";
    }
  }
}

Please note that this is an extension method of the long datatype. If you use int or another type, you’ll need to make an extension for that as well.

Usage is simple:

public static void Main()
{
  long l = 42069;
  Console.WriteLine(l.ToSizeString());
}

// Output is 41.08 KB

That’s it. Happy coding.

NOTE TO SITECORE USERS

Sitecore used to have a method, GetSizeString in the Sitecore.StringUtil library, but have removed that method from latest versions. You will get the following error:

Compiler Error Message: CS0117: ‘Sitecore.StringUtil’ does not contain a definition for ‘GetSizeString’

MORE TO READ:

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

C# Dapper Trim String Values

I was calling a stored procedure using Dapper, and for reasons beyond my understanding, the stored procedure returned strings as non-trimmed strings:

Strings are not trimmed when returned from my stored procedure
Strings are not trimmed when returned from my stored procedure

I could of course look into the stored procedure, but that would require me to obtain new knowledge of SQL, so instead I quickly jumped on the C# bandwagon. Good for me, because Dapper have thought of this scenario, and implemented a TypeHandler that can be globally applied to all Dapper query results automatically.

STEP 1: CREATE A NEW TYPEHANDLER

using Dapper;
using System.Data;

namespace MyCode
{
  public class TrimStringHandler : SqlMapper.TypeHandler<string>
  {
    public override string Parse(object value)
    {
      return (value as string)?.Trim();
    }

    public override void SetValue(IDbDataParameter parameter, string value)
    {
      parameter.Value = value;
    }
  }
}

This TypeHandler handles values of type string, and will trim the result when returned.

STEP 2: APPLY THE TYPEHANDLER GLOBALLY

We’re used to applying stuff using dependency injection, but in Dapper’s case, it’s just a global setting. Add the following line of code to your Program.cs:

Dapper.SqlMapper.AddTypeHandler(new TrimStringHandler());

And now all strings will be returned in trimmed form:

The strings are now nicely trimmed
Strings are nicely trimmed now

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

MORE TO READ:

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

Parameters and Arguments in .NET Core Command Line Applications

Microsoft have released a new System.CommandLine library to handle parameters and arguments in your .NET Core 6 console applications.

The NuGet Package is currently (March 2nd 2023) in beta, so you need to allow prerelease NuGet packages in your solution.

But here’s how to use it.

STEP 1: THE NUGET PACKAGE

Include the following package:

STEP 2: THE PROGRAM.CS

My code differs slightly from the one Microsoft suggest, as I am dividing my code into application configuration and application business logic. The division allows me to treat my business logic as a class with constructor based dependency injection. This is the bare necessities:

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System.CommandLine;

// ----------------------------------------------------
// My configuration part of the application
// ----------------------------------------------------
var host = Host.CreateDefaultBuilder(args)
    .ConfigureAppConfiguration((hostingContext, config) =>
    {
      config.AddJsonFile("appsettings.json");
	  // Add more config files if needed
    }
    )
    .ConfigureServices((hostingContext, services) =>
    {
	  // The "Application" service is the application
	  // itself. Add all other dependencies hereafter
      services.AddSingleton<Application>();
	  
    })
    .ConfigureLogging((hostingContext, logging) =>
      // Add more logging if needed
	  logging.AddConsole()
    )
  .Build();

// Run the application
var application = host.Services.GetRequiredService<Application>();
await application.ExecuteAsync(args);

// ----------------------------------------------------
// This is the business logic of my application.
// It's here that I define what the application will do
// and which parameters it requires
// ----------------------------------------------------
public class Application
{
  private readonly ILogger<Application> _logger;

  public Application(ILogger<Application> logger)
  {
    _logger = logger;
  }

  // This is where the arguments are defined
  public async Task<int> ExecuteAsync(string[] args)
  {
    var argument1 = new Option<string>(
        name: "--argument1",
        description: "This is my first argument that I allow"
    );

    var argument2 = new Option<int>(
        name: "--argument2",
        description: "This is another argument. This time I only allow an integer"
    );

    var rootCommand = new RootCommand("Commands");
    rootCommand.AddOption(argument1);
    rootCommand.AddOption(argument2);

    rootCommand.SetHandler(async (argument1, argument2) =>
		{
		  await RunApplicationAsync(argument1, argument2);
		},
        argument1, argument2
	);

    return await rootCommand.InvokeAsync(args);
  }

  private async Task RunApplicationAsync(string argument1, int argument2)
  {
	// This is the actual starting point of my business logic
    _logger.LogError("Hello world! {argument1} {argument2}", argument1, argument2);
    await Task.Delay(5000);
  }
}

EXPLANATION:

The first section is the typical setup of dependency injection, logging, etc. In line 31,32 I execute the application business login, which is the “Application” class.

Having a separate “Application” class that handles the business logic allows me to threat my application as any other class and use constructor based dependency injection.

The “ExecuteAsync” in line 49 is the entry point of my business logic, but all it does is setting up the arguments that my application allows. Please note that the argument names argument1 and argument2 are repeated several times, but this is the way.

So the actual application business logic starts at the bottom, in the RunApplicationAsync method (line 75). Because the System.CommandLine was hooked up in the ExecuteAsync method, the arguments can now be passed as arguments to my function.

From that point on, it’s business as usual, and your code will look no different from any other application.

HOW TO CALL YOUR APPLICATION:

Your application now allows arguments:

# Get help. This will not execute the RunApplicationAsync method
./application.exe -?

# Run the application with arguments
./application.exe --argument1 "mystring" --argument2 42

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

MORE TO READ:

Posted in .net, .NET Core, c#, General .NET | Tagged , , , , | 1 Comment

Sitecore TargetInvocationException: Exception has been thrown by the target of an invocation

You are probably upgrading some packages in your Sitecore instance, but instead of success and glory, you are greeted with the following error message:

[TargetInvocationException: Exception has been thrown by the target of an invocation.]
   System.RuntimeTypeHandle.CreateInstance(RuntimeType type, Boolean publicOnly, Boolean noCheck, Boolean& canBeCached, RuntimeMethodHandleInternal& ctor, Boolean& bNeedSecurityCheck) +0
   System.RuntimeType.CreateInstanceSlow(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark) +142
   System.Activator.CreateInstance(Type type, Boolean nonPublic) +107
   System.RuntimeType.CreateInstanceImpl(BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes, StackCrawlMark& stackMark) +1476
   System.Activator.CreateInstance(Type type, BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes) +186
   System.Activator.CreateInstance(Type type, BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture) +28
   System.Web.HttpRuntime.CreateNonPublicInstance(Type type, Object[] args) +82
   System.Web.HttpApplication.BuildIntegratedModuleCollection(List`1 moduleList) +235
   System.Web.HttpApplication.GetModuleCollection(IntPtr appContext) +1103
   System.Web.HttpApplication.RegisterEventSubscriptionsWithIIS(IntPtr appContext, HttpContext context, MethodInfo[] handlers) +122
   System.Web.HttpApplication.InitSpecial(HttpApplicationState state, MethodInfo[] handlers, IntPtr appContext, HttpContext context) +173
   System.Web.HttpApplicationFactory.GetSpecialApplicationInstance(IntPtr appContext, HttpContext context) +255
   System.Web.Hosting.PipelineRuntime.InitializeApplication(IntPtr appContext) +347

[HttpException (0x80004005): Exception has been thrown by the target of an invocation.]
   System.Web.HttpRuntime.FirstRequestInit(HttpContext context) +552
   System.Web.HttpRuntime.EnsureFirstRequestInit(HttpContext context) +122
   System.Web.HttpRuntime.ProcessRequestNotificationPrivate(IIS7WorkerRequest wr, HttpContext context) +737

The real reason for the error is hidden because Counters are enabled. To see the actual reason for your demise, disable counters, by setting Counters.Enabled to false:

<setting name="Counters.Enabled">
    <patch:attribute name="value">true</patch:attribute>
</setting>

That’s it. You can now find the actual reason for the error. Happy debugging.

MORE TO READ:

Posted in Sitecore, Sitecore 10, Sitecore 6, Sitecore 7, Sitecore 8, Sitecore 9 | Tagged , , , | Leave a comment

Could not load file or assembly ‘System.Diagnostics.DiagnosticSource, Version=4.0.4.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51’ or one of its dependencies. The system cannot find the file specified

The following error may occur when upgrading Microsoft ApplicationInsights:

[FileNotFoundException: Could not load file or assembly ‘System.Diagnostics.DiagnosticSource, Version=4.0.4.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51’ or one of its dependencies. The system cannot find the file specified.]
Microsoft.AspNet.TelemetryCorrelation.TelemetryCorrelationHttpModule.Application_BeginRequest(Object sender, EventArgs e) +0
System.Web.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +142
System.Web.<>c__DisplayClass285_0.b__0() +38
System.Web.HttpApplication.ExecuteStepImpl(IExecutionStep step) +11857717
System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +93

Version Information: Microsoft .NET Framework Version:4.0.30319; ASP.NET Version:4.8.4494.0

The error may happen in legacy .NET 4.x applications.

This is a conflict between Microsoft.AspNet.TelemetryCorrelation and Microsoft.ApplicationInsights, The 2 packages have different versions of System.Diagnostics.DiagnosticSource.

  • Microsoft.AspNet.TelemetryCorrelation use v4.0.4.0
  • Microsoft.ApplicationInsights use v5.0.0.0
Same dependency, but different versions

To fix this, add a dependentAssembly to your web.config in the assemblyBinding section:

<dependentAssembly>
  <assemblyIdentity name="System.Diagnostics.DiagnosticSource" publicKeyToken="cc7b13ffcd2ddd51"  culture="neutral"/>
  <bindingRedirect oldVersion="0.0.0.0-5.0.0.0" newVersion="5.0.0.0"/>
</dependentAssembly>

Hope this will help you. Happy coding.

MORE TO READ:

Posted in c#, General .NET, Sitecore 10, Sitecore 7, Sitecore 8, Sitecore 9 | Tagged , , , | Leave a comment