Build .net core 6 projects with Azure Devops using yml files

DevOps is a complicated discipline, so when we as developers have to set up a build pipeline in Azure Devops we are sometimes in deep water.

Thats why I made this yml template that I can copy from whenever I need to build a .net 6 project:

trigger:
  - main

pool:
  name: Azure Pipelines
  vmImage: 'windows-2019'

variables:
  buildConfiguration: 'Release'

steps:
- task: UseDotNet@2
  displayName: 'Use .NET Core 6'
  inputs:
    version: 6.0.x

- task: DotNetCoreCLI@2
  displayName: Restore
  inputs:
    command: restore
    projects: '**/*.csproj'

- task: DotNetCoreCLI@2
  displayName: Build
  inputs:
    projects: '**/*.csproj'

- task: DotNetCoreCLI@2
  displayName: Publish
  inputs:
    command: publish
    publishWebProjects: false
    projects: '**/*.csproj'
    arguments: '--configuration release --output $(build.artifactstagingdirectory) /property:PublishWithAspNetCoreTargetManifest=false'
    zipAfterPublish: false

- task: PublishBuildArtifacts@1
  displayName: 'Publish Artifact'
  inputs:
    ArtifactName: myproject

I name the file “azure-pipelines.yml” and place it in the root of my project:

azure-pipelines.yml added to the root of my project

When the pipeline has run it creates an artifact that can be deployed:

My Artifact

SO WHAT DOES EACH SECTION DO?

triggerDetermines which branch will trigger the pipeline. Is used for continuous deployment
poolDetermines the server that it uses as build platform. Use windows-2019, not ubuntu if you wish to build .net core 6 code
variablesSets up build variables
task: UseDotNet@2Tells the build to use .net core 6 when building. If you leave out this step, it will build using .net core 3.1 instead.
DotNetCoreCLI@2/RestoreGets NuGet packages
DotNetCoreCLI@2/BuildBuilds the project
DotNetCoreCLI@2/PublishCopies the files to a folder.
publishWebProjects will determine what to copy. If true, it will copy web projects
zipAfterPublish determines if the out should be zipped.
property:PublishWithAspNetCoreTargetManifest=false will copy your NuGet dll’s along with the dlls of the project
PublishBuildArtifacts@1Creates an artifact that can be used by your release pipeline

This is just an introduction to DevOps. I hope that the file will give you a hint of what to do before panicking if you are given the task of setting up your own build and release pipelines.

Happy DevOps’ing.

MORE TO READ:

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

Sitecore Create Fully Qualified Url

A classic issue in Sitecore: Some code running in one Site Context is creating a link to a page in another context, and the link turns out to be complete garbage.

For example, this code is running in the modules_shell context and is trying to create a link to a page on my main website (the website context):

var site = Sitecore.Sites.SiteContext.Current.Name;
var item = Sitecore.Context.ContentDatabase.GetItem("/sitecore/content/Website/Frontpage/live");
var link = Sitecore.Links.LinkManager.GetItemUrl(item);

// site = modules_shell
// link = /sitecore-modules/shell/Website/Frontpage/Live

That link is definitely not correct, so you try adding some ItemUrlBuilderOptions to the LinkManager.GetItemUrl():

var site = Sitecore.Sites.SiteContext.Current.Name;
var item = Sitecore.Context.ContentDatabase.GetItem("/sitecore/content/Website/Frontpage/live");
Sitecore.Links.ItemUrlBuilderOptions options = new Sitecore.Links.ItemUrlBuilderOptions();
options.Site = Sitecore.Sites.SiteContext.GetSite("website");
options.LanguageEmbedding = Sitecore.Links.LanguageEmbedding.Never;
options.SiteResolving = true;
var link = Sitecore.Links.LinkManager.GetItemUrl(item, options));

// site = modules_shell
// link = /Live

This is much better. The link is now relative to the website base url. But we need the link to be absolute to the base domain.

To achieve this, make sure you have the targetHostName and scheme set in the <sites> setting in your config:

<site name="website" 
 rootPath="/sitecore/content/website" startItem="/frontpage" 
 targetHostName="mywebsite.com" 
 scheme="https"
 ...
 ...
/>

Then use this little nifty method:

private static string Qualify(string relativeUrl, string sitename)
{
  SiteContext sitecontext = SiteContext.GetSite(sitename);
  return Qualify(relativeUrl, sitecontext);
}

private static string Qualify(string relativeUrl, SiteContext sitecontext)
{
  if (!relativeUrl.StartsWith("/"))
    relativeUrl = "/" + relativeUrl;
  return string.Format("{0}://{1}{2}", sitecontext.SiteInfo.Scheme, sitecontext.TargetHostName, relativeUrl);
}

And your output will be fully qualified:

var item = Sitecore.Context.ContentDatabase.GetItem("/sitecore/content/Website/Frontpage/live");
Sitecore.Links.ItemUrlBuilderOptions options = new Sitecore.Links.ItemUrlBuilderOptions();
options.Site = Sitecore.Sites.SiteContext.GetSite("website");
options.LanguageEmbedding = Sitecore.Links.LanguageEmbedding.Never;
options.SiteResolving = true;
var link = Sitecore.Links.LinkManager.GetItemUrl(item, options);
var fullyQualifiedLink = Qualify(link, "website");

// link = /Live
// fullyQualifiedLink = https://mywebsite.com/Live

That’s it. You are now a Sitecore expert. happy coding.

MORE TO READ:

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

C# SQL Connection Using Azure Managed Identity

Azure Managed Identity is Microsoft’s solution to manage credentials for you, eliminating the need to store usernames, passwords, certificates and other secrets in your config files.

Basically, with Managed Identity you establish a trust between a server and a resource. For example, if you have a SQL server and a Web Server, you use managed identity to grant the Web Server access to the SQL server, so you don’t need to keep a username/password to the SQL server in the connectionstring.

Example on a managed Identity

Read here on how to configure managed identity in Azure.

Once the managed identity have been established between the SQL server and the Web Server, you will have a connection string and a managed identity client id. Notice that the connectionstring does not contain a username and a password:

  "ConnectionStrings": {
    "SqlConnection": "Server=tcp:[servername].database.windows.net,1433;Initial Catalog=[databasename]",
    "ClientId": "[guid]"
  },

Now, remember that the managed identity is a trust between the web server and the sql server. If you develop code locally, you still need a username/password in your connectionstring, as it is not possible to establish a trust between a local machine and a Azure Sql Server.

The easiest way to overcome this is to have a local .config file without a client id, and in the code check if the client id is empty. If it’s empty, you connection the old fashioned way, if not, you use the client id to connect.

ENOUGH TALK, SHOW ME THE CODE

This is an example of a connection manager that uses the client id if it exists to connect using Azure Managed Identity:

using System.Data;
using System.Data.SqlClient;
using Azure.Identity;
using Microsoft.Extensions.Configuration;

namespace MyCode
{
    public class ConnectionManager : IConnectionManager
    {
        private readonly IConfiguration _configuration;
        private readonly string _connectionString;

        public ConnectionManager(IConfiguration configuration)
        {
            _configuration = configuration;
            _connectionString = _configuration.GetConnectionString("SqlConnection");
        }

        public IDbConnection CreateConnection()
        {
            SqlConnection? connection;
            string clientId = _configuration.GetConnectionString("ClientId");
			
            // Empty client id means we are running towards a local 
		    // database using username/password. Connect the old
            // fashioned way
            if (string.IsNullOrWhiteSpace(clientId))
              return new SqlConnection(_connectionString);
            
            // Client id is set. Use the Managed Identity Client Id to 
			// establish a connection
			connection = new SqlConnection(_connectionString);
            var credential = new DefaultAzureCredential(new DefaultAzureCredentialOptions { ManagedIdentityClientId = clientId });
            var token = credential.GetToken(new Azure.Core.TokenRequestContext(new[] { "https://database.windows.net/.default" }));
            connection.AccessToken = token.Token;
            return connection;
        }
    }
}

The connectionmanager is injected into your service registration:

services.AddTransient<IConnectionManager, ConnectionManager>();

And the connectionmanager are now ready to be used in your SQL repositories:

namespace MyCode
{
    public class MyRepository 
    {
        private readonly IConnectionManager _connectionManager;

        public BannerRepository(IConnectionManager connectionManager)
        {
            _connectionManager = connectionManager;
        }

        public async Task MockupSQLAsync()
        {
            using var connection = _connectionManager.CreateConnection();
            await connection.ExecuteAsync("some sql", ...);
        }
    }
}

That’s it. You are now an expert in Managed Identity. Happy coding.

MORE TO READ:

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

C# Convert WebP to JPEG in .NET

WebP is a new compressed image format developed by Google. If you want to convert WebP to any other format, you seem to be mostly out of luck if you are using C#.

There is, however, some tools out there. Google have released 2 DLL’s, libwebp_x64.dll and libwebp_x86.dll, but these DLL’s are running native unmanaged code, so to utilize these, we need to run our code on a Windows machine, and we need to wrap the code so it can be used from managed code.

Jose Pinero have done exactly this, wrapped the DLL’s so you can call the functionalities from your C# managed code.

So here is the journey on how to convert a WebP image to a JPEG using C#.

STEP 1: DOWNLOAD libwebp_x64.dll AND libwebp_x86.dll AND WebPWrapper.cs

Go to the following GIT repo and get the WebPWrapper.cs. Don’t forget the 2 Google DLL’s as well, libwebp_x64.dll and libwebp_x86.dll:

https://github.com/JosePineiro/WebP-wrapper

STEP 2: INCLUDE SYSTEM.DRAWING

System.Drawing is the old image library native to Windows. Include the package:

https://www.nuget.org/packages/System.Drawing.Common/

STEP 3: MAKE AN JPEG EXTENSION FOR THE IMAGE CLASS

The C# Image class can easily be extended to create a JPEG image:

using System.Drawing;
using System.Drawing.Imaging;
using Encoder = System.Drawing.Imaging.Encoder;

namespace WebPConverter
{
  public static class ImageExtensions
  {
    public static void SaveJpeg(this Image img, string filePath, long quality)
    {
      var ep = new EncoderParameters(1);
      ep.Param[0] = new EncoderParameter(Encoder.Quality, quality);
      img.Save(filePath, GetEncoder(ImageFormat.Jpeg), ep);
    }

    public static void SaveJpeg(this Image img, Stream stream, long quality)
    {
      var ep = new EncoderParameters(1);
      ep.Param[0] = new EncoderParameter(Encoder.Quality, quality);
      img.Save(stream, GetEncoder(ImageFormat.Jpeg), ep);
    }

    private static ImageCodecInfo GetEncoder(ImageFormat format)
    {
      ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders();
      return codecs.Single(codec => codec.FormatID == format.Guid);
    }
  }
}

STEP 4: CONVERT THE WEBP IMAGE TO JPEG

This simple code will convert a WebP image to a Jpeg image:

using System.Drawing;
using WebPConverter;
using WebPWrapper;

WebP webp = new WebP();
Bitmap bitmap = webp.Load("d:\\1.webp");
bitmap.SaveJpeg("d:\\1.jpeg", 80);

RUNNING THE WebPWrapper.cs AS A SERVICE

Jose Pinero have tied in some GUI related code in the WebPWrapper.cs code, so if you need to run the code as a service, an API, or as a worker process you need to clean up some of his code.

The easiest way to do this is to remove the following line from the WebPWrapper.cs file and handle whatever compiler error is appearing, by deleting the lines that cast an error.

using System.Windows.Forms;

CONVERTING AND UPLOADING TO BLOB STORAGE:

This example is larger, as I am grabbing a WebP image from a website, converting it into a JPEG, and storing that image in a Blob storage:

using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;
using System.Drawing;
using WebPConverter;
using WebPWrapper;

// Grab a WebP image from a Google Website
HttpClient client = new HttpClient();
var response = await client.GetAsync("https://www.gstatic.com/webp/gallery/1.webp");

// Open a connection to a Blob Container
CloudBlobContainer cloudBlobContainer = CloudStorageAccount.Parse(YOUR_CONNECTiONSTRING).CreateCloudBlobClient().GetContainerReference("containerName");

// Give the blob a name and create a blob reference
string blobName = "MediaStorage/test/image1.jpg";
var blobReference = cloudBlobContainer.GetBlockBlobReference(blobName);
blobReference.Properties.ContentType = "image/jpg";

// Convert the WebP image to a JPEG and stream it into the Blob file we created
await using var jpgStream = await ConvertWebPContentToJpegContent(response.Content);
jpgStream.Seek(0, SeekOrigin.Begin);
await blobReference.UploadFromStreamAsync(jpgStream);

async Task<Stream> ConvertWebPContentToJpegContent(HttpContent content)
{
  // Grab the raw WebP image
  var rawWebP = await content.ReadAsByteArrayAsync();
  // Convert the WebP image to a JPEG
  Bitmap bitmap = new WebP().Decode(rawWebP);
  MemoryStream memoryStream = new MemoryStream();
  // Save the JPEG to a Stream that can be saved in a blob storage
  bitmap.SaveJpeg(memoryStream, 80);
  return memoryStream;
}

FINAL NOTES:

I’m not sure why it’s so hard to convert WebP to JPEG using C#, if it’s because WebP is not widely used, or WebP is so superior that no one would ever convert to another format. The fact that the code is tied to a Windows machine can be a deal breaker for some. There are paid products out there that maybe will help you.

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

MORE TO READ:

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

C# Ignoring Namespaces in XML when Deserializing

Namespaces in XML are hard to understand, I’ll admit that. But even I have learned that namespaces in XML documents matter. These elements are not the same:

<item>
    <title>Brian Caos</title>
    <description>A blog on C# coding</description>
    <link>https://briancaos.wordpress.com/</link>
</item>

<item xmlns:g="http://base.google.com/ns/1.0" version="2.0">
    <g:title>Brian Caos</g:title>
    <g:description>A blog on C# coding</g:description>
    <g:link>https://briancaos.wordpress.com/</g:link>
</item>

These 2 set of elements need to be mapped differently when deserialized in C#:

// This will map the <title> element in XML
[XmlElement("title"]
public string Title { get; set; }

// ... and this will map the <g:title> element in XML
[XmlElement("title", Namespace = "http://base.google.com/ns/1.0")]
public string Title { get; set; }

Unfortunately it is extremely common that businesses completely ignore the namespacing rules and send elements with or without namespaces. And we developers have to deal with it.

SO HOW DO WE FIX IT?

There is a class though, the XmlTextReader, that allows to be overwritten to ignore namespaces. Microsoft discourages us from using the class though. But it’s our only chance to ignore the namespace when deserializing.

STEP 1: CREATE A IgnoreNamespaceXmlTextReader CLASS

using System.Xml;

namespace MyCode
{
  public class IgnoreNamespaceXmlTextReader : XmlTextReader
  {
    public IgnoreNamespaceXmlTextReader(TextReader reader) : base(reader) 
    { 
    }

    public override string NamespaceURI => "";
  }
}

STEP 2: DO NOT SPECIFY ANY NAMESPACES IN THE C# MODEL CLASS

This is an example of how to deserialize a simple XML into a simple model class. The model class does not define any namespaces in the class attributes:

using System.Xml.Serialization;

namespace MyCode
{
  [XmlRoot(ElementName = "model")]
  [XmlType("model")]
  public class XmlModel
  {
    [XmlElement("name")]
    public string Name { get; set; }  

    [XmlElement("value")]
    public string Value { get; set; } 
  }
}

STEP 3: USE THE IgnoreNamespaceXmlTextReader WHEN DESERIALIZING XML

Now we can deserialize any XML we receive, namespace or no namespace:

// We can now import 
// an xml without namespaces
string s = @"<model>
               <name>hello</name>
               <value>world</value>
             </model>";

var sr = new StringReader(s);
var xmlSerializer = new XmlSerializer(typeof(XmlModel));
var xmlModel = (XmlModel)xmlSerializer.Deserialize(new IgnoreNamespaceXmlTextReader(sr));
// xmlModel.Name = "hello" and xmlModel.Value = "world"

// ---------------------------------------------------

// And we can also import 
// an xml with namespaces
// because the IgnoreNamespaceXmlTextReader ignores 
// any namespaces in the receiving XML
string s = @"<model xmlns:g=""http://base.google.com/ns/1.0"">
               <g:name>hello</g:name>
               <g:value>world</g:value>
             </model>";

var sr = new StringReader(s);
var xmlSerializer = new XmlSerializer(typeof(XmlModel));
var xmlModel = (XmlModel)xmlSerializer.Deserialize(new IgnoreNamespaceXmlTextReader(sr));
// xmlModel.Name = "hello" and xmlModel.Value = "world"

MORE TO READ:

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

C# List Batch – Braking an IEnumerable into batches with .NET

There are several ways of batching lists and enumerables with C#. Here are 3 ways:

METHOD 1: USING MoreLINQ

MoreLINQ is an extension package for LINQ that implements yet another set of useful LINQ expressions like Shuffle, Pad, and Batch.

Get the MoreLINQ NuGet package here: https://www.nuget.org/packages/morelinq/

METHOD 2: BATHCING USING FOREACH AND YIELD

This implementation looks a lot like the one that MoreLINQ uses. It utilizes a foreach loop, building a new list that is then returned:

using System;
using System.Collections.Generic;
using System.Linq;

namespace MyCode
{
  internal static class EnumerableExtension
  {
    public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> enumerator, int size)
    {
      T[] batch = null;
      var count = 0;

      foreach (var item in enumerator)
      {
        if (batch == null)
          batch = new T[size];

        batch[count++] = item;
        if (count != size)
          continue;

        yield return batch;

        batch = null;
        count = 0;
      }

      if (batch != null && count > 0)
        yield return batch.Take(count).ToArray();
    }
  }
}

METHOD 3: BATCHING USING SKIP() AND TAKE()

This method is a little shorter, and utilizes the Skip() to jump to the next batch, and Take() to grab the batch:

using System;
using System.Collections.Generic;
using System.Linq;

namespace MyCode
{
  internal static class EnumerableExtension
  {
    public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> enumerator, int size)
    {
      var length = enumerator.Count();
      var pos = 0;
      do
      {
        yield return enumerator.Skip(pos).Take(size);
        pos = pos + size; 
      } while (pos < length);
    }
  }
}

HOW TO USE THE BATCH METHODS ABOVE:

You can try it out using this tiny sample:

public class Program
{
	public static void Main()
	{
		// Add 25 numbers to a list 
        List<int> list = new List<int>();
		for (int i=0;i<25;i++)
			list.Add(i);
        // Batch the list into batches of 7
        var batches = list.Batch(7);
		foreach (var batch in batches)
		{
			foreach (var item in batch)
				Console.Write(item + " ");
			Console.WriteLine(" batch end");	
		}			
	}
}

The code above will return the following output:

0 1 2 3 4 5 6  batch end
7 8 9 10 11 12 13  batch end
14 15 16 17 18 19 20  batch end
21 22 23 24  batch end

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

MORE TO READ:

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

Create C# Class from SQL

I hate doing plumbing code. Especially entity model classes that are exact replicas of SQL tables. I do this all the time when using Dapper. So I did some googling and found some SQL statements that you can run in your SQL Server Management Studio and have a table as a C# class in the output. I combined the best ones into this chunk of SQL:

-- This is the schema name
DECLARE @Schema VARCHAR(MAX) = 'Dbo' 
-- This is the table name. Write your own table name here
DECLARE @Table VARCHAR(MAX) = 'TableName' 

DECLARE @result varchar(max) = ''
SET    @result = @result + 'namespace ' + @Schema  + CHAR(13) + '{' + CHAR(13) 
SET    @result = @result + '    public class ' + @Table + CHAR(13) + '    {' + CHAR(13) 
SELECT @result = @result + '        public ' + DataType + ' ' + PropertyName + ' { get; set; } ' + CHAR(13)
FROM (SELECT
    UPPER(left(c.COLUMN_NAME,1))+SUBSTRING(c.COLUMN_NAME,2,LEN(c.COLUMN_NAME)) AS PropertyName,
    CASE c.DATA_TYPE
        WHEN 'bigint'           THEN CASE C.IS_NULLABLE WHEN 'YES' THEN 'long?' ELSE 'long' END
        WHEN 'binary'           THEN 'Byte[]'
        WHEN 'bit'              THEN CASE C.IS_NULLABLE WHEN 'YES' THEN 'bool?' ELSE 'bool' END
        WHEN 'char'             THEN 'string'
        WHEN 'date'             THEN CASE C.IS_NULLABLE WHEN 'YES' THEN 'DateTime?' ELSE 'DateTime' END
        WHEN 'datetime'         THEN CASE C.IS_NULLABLE WHEN 'YES' THEN 'DateTime?' ELSE 'DateTime' END
        WHEN 'datetime2'        THEN CASE C.IS_NULLABLE WHEN 'YES' THEN 'DateTime?' ELSE 'DateTime' END
        WHEN 'datetimeoffset'   THEN CASE C.IS_NULLABLE WHEN 'YES' THEN 'DateTimeOffset?' ELSE 'DateTimeOffset' END
        WHEN 'decimal'          THEN CASE C.IS_NULLABLE WHEN 'YES' THEN 'decimal?' ELSE 'decimal' END
        WHEN 'float'            THEN CASE C.IS_NULLABLE WHEN 'YES' THEN 'double?' ELSE 'double' END
        WHEN 'image'            THEN 'Byte[]'
        WHEN 'int'              THEN CASE C.IS_NULLABLE WHEN 'YES' THEN 'int?' ELSE 'int' END
        WHEN 'money'            THEN CASE C.IS_NULLABLE WHEN 'YES' THEN 'decimal?' ELSE 'decimal' END
        WHEN 'nchar'            THEN 'string'
        WHEN 'ntext'            THEN 'string'
        WHEN 'numeric'          THEN CASE C.IS_NULLABLE WHEN 'YES' THEN 'decimal?' ELSE 'decimal' END
        WHEN 'nvarchar'         THEN 'string'
        WHEN 'real'             THEN CASE C.IS_NULLABLE WHEN 'YES' THEN 'double?' ELSE 'double' END
        WHEN 'smalldatetime'    THEN CASE C.IS_NULLABLE WHEN 'YES' THEN 'DateTime?' ELSE 'DateTime' END
        WHEN 'smallint'         THEN CASE C.IS_NULLABLE WHEN 'YES' THEN 'short?' ELSE 'short' END
        WHEN 'smallmoney'       THEN CASE C.IS_NULLABLE WHEN 'YES' THEN 'decimal?' ELSE 'decimal' END
        WHEN 'text'             THEN 'string'
        WHEN 'time'             THEN CASE C.IS_NULLABLE WHEN 'YES' THEN 'TimeSpan?' ELSE 'TimeSpan' END
        WHEN 'timestamp'        THEN 'Byte[]'
        WHEN 'tinyint'          THEN CASE C.IS_NULLABLE WHEN 'YES' THEN 'Byte?' ELSE 'Byte' END
        WHEN 'uniqueidentifier' THEN CASE C.IS_NULLABLE WHEN 'YES' THEN 'Guid?' ELSE 'Guid' END
        WHEN 'varbinary'        THEN 'Byte[]'
        WHEN 'varchar'          THEN 'string'
        ELSE 'Object'
    END AS DataType, c.ORDINAL_POSITION
    FROM INFORMATION_SCHEMA.COLUMNS c
    WHERE c.TABLE_NAME = @Table
    AND ISNULL(@Schema, c.TABLE_SCHEMA) = c.TABLE_SCHEMA) t
ORDER BY t.ORDINAL_POSITION

SET @result = @result  + '    }' + CHAR(13)
SET @result = @result + '}' 

PRINT @result

HOW TO USE IT:

Replace the @Schema with your schema value (usually Dbo) and the @Table value with the name of the table and run the statement. It will then output the table as a C# model class.

EXAMPLE:

This is my table:

CREATE TABLE [dbo].[Favorites](
	[rowId] [int] IDENTITY(1,1) NOT NULL,
	[userKey] [int] NOT NULL,
	[favoriteId] [nvarchar](255) NULL,
	[lastModified] [datetime] NULL,
	[isDeleted] [bit] NULL,
	[created] [datetime] NULL,
 CONSTRAINT [PK_Favorites] PRIMARY KEY CLUSTERED 
(
	[rowID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

I set the 2 variables in the first SQL statement:

-- This is the schema name
DECLARE @Schema VARCHAR(MAX) = 'Dbo' 
-- This is the table name. Write your own table name here
DECLARE @Table VARCHAR(MAX) = 'Favorites' 

And run it, and voila! The table is now a C# Dto class. It will even uppercase the first letter in the property name for you:

namespace Dbo
{
    public class Favorites
    {
        public int RowId { get; set; } 
        public int UserKey { get; set; } 
        public string FavoriteId { get; set; } 
        public DateTime? LastModified { get; set; } 
        public bool? IsDeleted { get; set; } 
        public DateTime? Created { get; set; } 
    }
}

That’s it. You are now not only a C# expert, but also a SQL shark. Happy plumbing. And coding.

MORE TO READ:

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

C# Thread Safe File Writer and Reader

Previously I wrote the article Write to file from multiple threads async with C# and .NET Core, which explains how to use a ConcurrentQueue to append lines to a text file. And the ConcurrentQueue is great for the fire-and-forget scenarios of log file writing, where you really do not read from the files you write.

But if you are reading the files you are writing, you need some kind of locking mechanism to ensure that the code block reading and writing the file are accessed by one thread at a time.

There are many options including lock, semaphors and ReaderWriterLock. In this article I will use the good old Mutex class.

STEP 1: THE CODE

using System;
using System.Threading;
using System.IO;
					
public class ThreadSafeFileWriter
{
	public string ReadFile(string filePathAndName)
	{
      // This block will be protected area
      using (var mutex = new Mutex(false, filePathAndName.Replace("\\", "")))
      {
        var hasHandle = false;
        try
        {
          // Wait for the muted to be available
          hasHandle = mutex.WaitOne(Timeout.Infinite, false);
          // Do the file read
          if (!File.Exists(filePathAndName))
            return string.Empty;
		  return File.ReadAllText(filePathAndName);
        }
        catch (Exception)
        {
          throw;
        }
        finally
        {
          // Very important! Release the mutex
          // Or the code will be locked forever
          if (hasHandle)
            mutex.ReleaseMutex();
        }
      }
	}
	
    public void WriteFile(string fileContents, string filePathAndName)
    {
      using (var mutex = new Mutex(false, filePathAndName.Replace("\\", "")))
      {
        var hasHandle = false;
        try
        {
          hasHandle = mutex.WaitOne(Timeout.Infinite, false);
          if (File.Exists(filePathAndName))
            return;
		  File.WriteAllText(filePathAndName, fileContents);
        }
        catch (Exception)
        {
          throw;
        }
        finally
        {
          if (hasHandle)
            mutex.ReleaseMutex();
        }
      }
    }	
}

SO WHAT’S UP WITH THE MUTEX?

The Mutex will let threads wait for the mutex handle in a queue until the mutex is released. So if 3 threads will write to the same file, they will wait in line nicely until they are granted access.

It is very important that the code releases the mutex as well, or the code will be locked forever.

Mutexes can be named, like in the example above. This locks the shared resource system wide. So if another process tries to access the same code, that process will also wait in line. Backslashes in mutex names are a reserved character and must be removed.

Taht’s it. You are now a multi-thread expert. Happy coding.

MORE TO READ:

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

C# Log to Application Insights and File from your .NET 6 Application

So why would you ever log to Application Insights AND File log at the same time? Well, if you are hosting your own applications on your own machine, it can be great to have a file version of what’s happened. The file will be updated immediately, whereas Application Insights are sometimes up to 5 minutes behind.
Also, it gives you the opportunity to log at different levels. Application Insights is not cheap, so having Application Insights log only warnings and errors, but the file logging debug can be a money saver.
And, when developing, the file log is far superior to Application Insights.

My colleague Kim Schiøtt whiffed up this extension method to make the configuration easier.

STEP 1: THE NUGET PACKAGES

You need the following packages:

STEP 2: THE CONFIGURATION

This will configure the logging:

"ApplicationInsights": {
    "InstrumentationKey": "[The instrumentation key]"
  },
"Logging": {
    "PathFormat": "[The path and file format used in file logging, e.g.: c:\\log-{Date}.txt]",
    "LogLevel": {
      "Default": "Information"
    },
    "ApplicationInsightsLoggerProvider": {
      "LogLevel": {
        "Default": "Warning"
      }
    }
  }

The “Logging” section configures the different log levels for file and Application Insights.

STEP 3: THE EXTENSION METHOD

This method allows you to add logging easily:

using Microsoft.ApplicationInsights.AspNetCore.Extensions;

namespace MyCode
{
    public static class WebApplicationBuilderExtensions
    {
        public static void AddLogging(this WebApplicationBuilder builder)
        {
            // Add file logging
            builder.Host.ConfigureLogging(logging =>
                {
                    logging.AddFile(builder.Configuration.GetSection("Logging"));
                }
            );
            // Add Application Insights Logging
            var options = new ApplicationInsightsServiceOptions();
            options.InstrumentationKey = builder.Configuration["ApplicationInsights:InstrumentationKey"];
            builder.Services.AddApplicationInsightsTelemetry(options);
        }
    }
}

STEP 4: CALL THE METHOD WHEN BUILDING YOUR APPLICATION

This is an example where I configure my logging:

using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Hosting;

var webApplicationOptions = new WebApplicationOptions();
var builder = WebApplication.CreateBuilder(webApplicationOptions);
builder.AddLogging();
...
...
...
builder.Build();

MORE TO READ:

Posted in .net, .NET Core, c#, General .NET, Microsoft Azure | Tagged , , , | 4 Comments

Get Users from IdentityServer using .NET Core

If you wish to poke around in the IdentityServer users database directly, the API seems a little bit fishy. This is because the direct data access are very low level, and consists of HttpClient extension methods. They are, in fact, so low level that you think that you are doing something wrong. But you are not, this is the way. So here’s the quick guide to getting started.

STEP 1: THE NUGET PACKAGES

Start with these packages:

STEP 2: CREATE AN ACCESSTOKEN REPOSITORY

IdentityServer have 2 types of access: Client access is the one we use and will grant us access to all users, and user access with will grant individual users access to their own data.

You need to know the URL to your IdentityServer, the client ID (usually a neat readable string) and a client secret (usually an unreadable guid-like string):

using IdentityModel.Client;

namespace IdentityServer.Repositories
{
  public class AccessTokenRepository 
  {
    private readonly IHttpClientFactory _httpClientFactory;

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

    public async Task<string> GetClientAccessTokenAsync()
    {
      var tokenRequest = new ClientCredentialsTokenRequest
      {
        Address = $"[IdentityServerUrl]/connect/token",
        ClientId = "[IdentityServer Client ID]",
        ClientSecret = "[IdentityServer Client Secret]"
      };

      var client = _httpClientFactory.CreateClient("HttpClient");
      var response = await client.RequestClientCredentialsTokenAsync(tokenRequest);
      if (response.IsError)
        throw new Exception($"{GetType()}.GetClientAccessToken failed: {response.ErrorDescription} ({response.HttpStatusCode}) ");

      return response.AccessToken;
    }
  }
}

STEP 3: CREATE A USER REPOSITORY

With an Client Access Token in hand, we are allowed to access the IdentityServer database.

using System.Net;
using System.Net.Http.Headers;

namespace IdentityServer.Repositories
{
  public class UserRepository 
  {
    private IHttpClientFactory _httpClientFactory;
    private AccessTokenRepository _accessTokenRepository;

    public UserRepository(IHttpClientFactory httpClientFactory, AccessTokenRepository accessTokenRepository) 
    {
      _httpClientFactory = httpClientFactory;
      _accessTokenRepository = accessTokenRepository;
    }

    public async Task<string?> GetUserAsync(string username)
    {
      var client = _httpClientFactory.CreateClient("HttpClient");
      client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", await _accessTokenRepository.GetClientAccessTokenAsync());

      var responseMessage = await client.GetAsync($"[IdentityServerUrl]/localapi/users/{username}");
      if (responseMessage.StatusCode == HttpStatusCode.NotFound)
        return null;  
      if (responseMessage.StatusCode == HttpStatusCode.Unauthorized)
        throw new Exception("Unauthorized");
      if (!responseMessage.IsSuccessStatusCode)
        throw new Exception($"{responseMessage.StatusCode}");
      var userJson = await responseMessage.Content.ReadAsStringAsync();
      return userJson;
    }
  }
}

The above code will return the bare-bone Json output from IdentityServer. You will need to parse the output accordingly. The reason this code does not do this is that you can design your own user database, so there is not really a standard data format.

STEP 4: USE THE USERREPOSITORY

The code expects an IHttpClientFactory, so we need to hook up such a thing, and we need to hook up the 2 repositories in our dependency injection framework. Hereafter, the repos will be available:

builder.Services.AddHttpClient("HttpClient");
builder.Services.AddSingleton<AccessTokenRepository>();
builder.Services.AddSingleton<UserRepository>();

...
...

// Example on how to use the userRepository:
if (await _userRepository.GetUserAsync(userName) == null)
  return BadRequest();

MORE TO READ:

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