C# Authorization in .NET Core API’s using Attributes, TypeFilterAttribute and IAuthorizationFilter

So the goal here is to define that a specific API endpoint in my .NET Core API requires an authorization token. It’s not all my endpoints that requires authentication, only those I specify.

To obtain this, I would like to decorate my api method in my controller with an attribute stating that this endpoint requires an authorization token in the request header before you are allowed to call it. Like this:

[HttpGet()]
[AuthTokenRequired]
public async Task<IActionResult> GetProfile()
{
  // Here goes my code...
}

The “AuthTokenRequired” is an attribute defined by me by combining a TypeFilterAttribute with an IAuthorizationFilter. This combination allows me to create a custom TypeFilterAttribute attribute while still using dependency injection in the class implmenting the IAuthorizationFilter interface.

But enough talk, let’s code.

THE SAMPLE CODE:

You need to see the code in it’s entirely to understand what’s happening. There are 2 classes here:

using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc;
using System.Security.Authentication;
using Users.AuthenticationToken;

namespace MyCode
{
  // This is the attribute class. This allows you to 
  // decorate endpoints in your controllers with a
  // [AuthTokenRequired] attribute
  public class AuthTokenRequiredAttribute : TypeFilterAttribute
  {
    public AuthTokenRequiredAttribute() : base(typeof(TokenAuthorizationFilter))
    {
    }
  }

  // This is the actual code that runs the authorization. By splitting
  // the code into 2 classes, you can inject your dependencies into this class  
  [AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = true)]
  public class TokenAuthorizationFilter : Attribute, IAuthorizationFilter
  {
	// Sample private variables, yours will look different
	private readonly SomeSampleRepository _rep;
    private readonly IWebHostEnvironment _environment;

    public TokenAuthorizationFilter(SomeSampleRepository rep, IWebHostEnvironment environment)
    {
      // This is just some sample code. This is added 
	  // to show you that you can now use dependency injection
	  // in this class.
	  // In your code you will add the classed needed to 
	  // do the proper authorization for you.
      _rep = rep; 
      _environment = environment;
    }
  
    public void OnAuthorization(AuthorizationFilterContext context)
    {
      // Here you will add your authorization code. This is just an 
	  // example of the possibilities in this method. Do your
	  // method the way you need it to work.
	  
	  if (_environment.IsDevelopment())
        return;
	  
	  // You can for example retrieve a request header and check the value
      var token = context.HttpContext.Request.Headers["AuthToken"];
      if (string.IsNullOrEmpty(token))
        throw new AuthenticationException("Unauthorized");
      if (token != "my-token")
        throw new AuthenticationException("Unauthorized");
    }
  }
}

So first we define the attribute itself. The class is called AuthTokenRequiredAttribute which will create an [AuthTokenRequired] attribute to be used in the api endpoints in your controller.

The attribute instantiates a new class, TokenAuthorizationFilter, and it is this class that actually does the authorization.

When decorating an API endpoint with the [AuthTokenRequired] attribute, the OnAuthorization method will be called each time before the API endpoint is called.

You must implement your own authorization logic here, I just included some inspiration on how to do it.

My controller class looks like this:

using Microsoft.AspNetCore.Mvc;

namespace MyController
{
  [ApiController]
  [Route("api/[controller]")]
  public class ProfileController : Controller
  {
    [HttpGet()]
    [AuthTokenRequired]
    public async Task<IActionResult> GetProfile()
    {
      try
      {
		// If the [AuthTokenRequired] fails, this
		// code will never be called. A AuthenticationException
		// will be thown before this code is reached
		// Pseudocode: do some code to get the profile and return it
        HttpRequest request = this.Request;
        return Ok(profile);
      }
      catch (Exception ex)
      {
        return BadRequest();
      }
    }
  }
}

That’s it. You are now an authorization expert. Happy coding.

MORE TO READ:

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

Sitecore SOLR Search in multiselect lists fails unless you add the field to the defaultSolrIndexConfiguration config

So I have this multiselect field in Sitecore:

Sitecore Treelist field
Sitecore Treelist Field

The field is a treelist field, but this article works for all multiselect fields, treelist, checklist, multilist, etc.

Treelist field
Treelist field

So how do you return the field as a list of Sitecore ID’s from the SOLR index?

STEP 1: CREATE A MODEL CLASS IN SITECORE

Your model class models the item to be returned from the search. I’m just showing a partial section of the class, yours will have all the other fields defined as well.

  [Serializable]
  public class MyClass
  {
    // define some fields

    // This is our field in question:
    private IEnumerable<ID> _tradeLabels;

    // The field is protected against null references
    // using a private variable. You don't need this
    // if your code allows the value to be null.
    [IndexField("AdvertTradeLabels")]
    [TypeConverter(typeof(IndexFieldEnumerableConverter))]
    public IEnumerable<ID> TradeLabels
    {
      {
        if (_tradeLabels != null)
          return _tradeLabels;

        return Enumerable.Empty<ID>();
      }
      set
      {
        _tradeLabels = value;
      }
    }

    // define more fields
  }

STEP 2: EXTEND THE defaultSolrIndexConfiguration SECTION OF THE Sitecore.ContentSearch.Solr.DefaultIndexConfiguration.config FILE

The field will not automatically be mapped to an array if Sitecore ID’s. You need to define it in the Sitecore.ContentSearch.Solr.DefaultIndexConfiguration.config file.

The field needs to be defined as returning a guidCollection. It does not in fact return a list of GUID’s, but a list of Sitecore ID’s.

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:x="http://www.sitecore.net/xmlconfig/" xmlns:role="http://www.sitecore.net/xmlconfig/role/" xmlns:search="http://www.sitecore.net/xmlconfig/search/" xmlns:exmEnabled="http://www.sitecore.net/xmlconfig/exmEnabled/">
  <sitecore exmEnabled:require="yes" search:require="Solr">
    <contentSearch>
      <indexConfigurations>
        <defaultSolrIndexConfiguration>
          <fieldMap>
            <fieldNames>
              <field fieldName="AdvertTradeLabels" returnType="guidCollection" />
            </fieldNames>
          </fieldMap>
        </defaultSolrIndexConfiguration>
      </indexConfigurations>
    </contentSearch>
  </sitecore>
</configuration>

STEP 3: PERFORM THE SEARCH

Your field will now be part of the search result when doing a search.

public IEnumerable<MyClass> Get(Expression<Func<MyClass, bool>> predicate)
{
  using (IProviderSearchContext context = ContentSearchManager
    .GetIndex("sitecore_master_index")
    .CreateSearchContext(SearchSecurityOptions.DisableSecurityCheck))
  {
    return context.GetQueryable<MyClass>()
      .Where(predicate).ToList();
  }
}

You can also access it if you wish to filter using predicates:

public IEnumerable<MyClass> GetWithTradeLabel(ID tradeLabel)
{
  var hasTradeLabel = PredicateBuilder.True<MyClass>().And(advert => advert.TradeLabels.Contains(tradeLabel));
  return Get(hasTradeLabel);
}

FINAL NOTES:

For an easier approach to SOLR Content Searching in Sitecore, I recommend that you use a base class for all of your SOLR model classes. This makes searching so much easier. Read more here:

Sitecore ContentSearch – Get items from SOLR or Lucene – A base class implementation

MORE TO READ:

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

Run node.js on Windows server using NSSM

Node.js was probably never intended to be run on a windows server. It runs perfectly on Linux, and you can also run it without problems as an Azure App Service.

But what if Node.js has to run on a Windows server? Currently you have 2 options, use IISNode or use NSSM.

NSSM, the Non-Sucking Service Manager allows you to run any application as a Windows Service, including the command line. And since Node.js can be started from the command line, this is my choice in this example.

WHAT YOU NEED TO KNOW BEFORE RUNNING YOUR NODE WEBSITE ON A WINDOWS MACHINE

Running Node.js as a service means that your Node.js will run locally, listening to a port. You therefore need a load balancer to route requests from the internet to the port on the Windows server. The Windows server itself have no routing capabilities. One solution is to install an IIS on the server and use a redirect module to redirect requests to the correct port, but this is not covered in this article. I am so fortunate to have a NGINX load balancer handling the routing for me.

WHAT YOU NEED TO INSTALL FIRST

Of course you need Node.js. Check the version of Node.js and NPM like this:

node -v
npm -v

You also need the latest version of NSSM.

STEP 1: CREATE A START.BAT FILE THAT CAN START NODE.JS

These start.bat files are different from site to site. This is my bat file:

cd dist
npm run start

The .bat file is a replica of what I would have typed in myself in my command line or PowerShell in order to start the website manually.

STEP 2: INSTALL A SERVICE USING NSSM

I have installed my NSSM in the C:\nssm folder on my server. And I have the website in the C:\website folder. Finally, I have a C:\logfiles folder to store logfiles. To setup NSSM, write the following:

C:\Nssm\win64\nssm.exe install Website-Service C:\website\start.bat
C:\Nssm\win64\nssm.exe set Website-Service AppDirectory C:\website\
C:\Nssm\win64\nssm.exe set Website-Service AppStdout D:\logfiles\out.txt 
C:\Nssm\win64\nssm.exe set Website-Service AppStderr D:\logfiles\err.txt

The lines do the following:

  1. Install a service called Website-Service that runs the C:\website\start.bat file
  2. Set the local directory to C:\website\ (do not skip this step, or you might end up having access issues)
  3. Route whatever the command line outputs to the D:\logfiles\out.txt folder
  4. Route errors to the D:\logfiles\err.txt

Node.js is case sensitive, so do not write c:\website, use C:\website.

You will now have a service that you can start either manually or from PowerShell:

Start-Service -Name Website-Service

That’s it. You are now a Node.js DevOps expert. Happy DevOps’ing

MORE TO READ

Posted in Uncategorized | Tagged , , , , | Leave a comment

C# Azure Table Storage QueryAsync, Paging and Filtering

Azure Table Storage is a NoSQL key-value store that can store massive amounts of data in a semi-structured way. Data is stored using a PartitionKey/RowKey keyset and a list of values. So far so good, but how do we query the danm thing?

First things first, have a look at my table storage, a list of famous celebrities:

Azure Table Storage

STEP 1: THE NUGET PACKAGES

You need the following NuGet package to access a Table Storage:

STEP 2: MAP THE TABLE STORAGE TO A MODEL CLASS:

Querying Table Storages are done by creating a model that implements the ITableEntity interface. The interface ensures that the ETag and Timestamp is present in the class:

using Azure;
using Azure.Data.Tables;

namespace MyCode
{
  public class FamousCelebritiesModel : ITableEntity
  {
    public string PartitionKey { get; set; }
    public string RowKey { get; set; }
    public int CelebrityIndex { get; set; }
    public string RealName { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Url { get; set; }
    public ETag ETag { get; set; } = default!;
    public DateTimeOffset? Timestamp { get; set; } = default!;
  }
}

STEP 3: CONNECT TO THE TABLE STORAGE

Connection is easy:

using Azure;
using Azure.Data.Tables;

namespace MyCode
{
  public class FamousCelebritiesRepository
  {
    private TableServiceClient _tableServiceClient;
    private TableClient _tableClient;

    public FamousCelebritiesRepository()
    {
      _tableServiceClient = new TableServiceClient("[your connection string]");
      _tableClient = _tableServiceClient.GetTableClient(tableName: "FamousCelebrities");
    }
	
	// more functions goes here
	// more functions goes here
	// more functions goes here
	// more functions goes here
	// more functions goes here

  }
}

QUERY 1: QUERYASYNC TO GET ALL ITEMS FROM THE TABLE STORAGE

The QueryAsync looks different from what we are used to, as the “await” operator is on the foreach loop:

public async Task<IEnumerable<FamousCelebritiesModel>> GetAllAsync()
{
  IList<FamousCelebritiesModel> modelList = new List<FamousCelebritiesModel>();
  var celebs = _tableClient.QueryAsync<FamousCelebritiesModel>(filter: "", maxPerPage: 10);
  await foreach (var celeb in celebs)
  {
    modelList.Add(celeb);
  }
  return modelList;
}

QUERY 2: QUERYASYNC WITH FILTER

You can apply simple LINQ filters to a Table Storage. This example filters by PartitionKey:

public async Task<IEnumerable<FamousCelebritiesModel>> GetByKnownAsync(string partitionKey)
{   
  IList<FamousCelebritiesModel> modelList = new List<FamousCelebritiesModel>();
  var celebs = _tableClient.QueryAsync<FamousCelebritiesModel>(x => x.PartitionKey == partitionKey, maxPerPage: 10);
  await foreach (var celeb in celebs)
  {
    modelList.Add(celeb);
  }
  return modelList;
}

QUERY 3: PAGINATION

Table Storage with millions of rows must be queried using pagination. Table Storage uses a “continuationtoken” to tell which page is next. Start with an empty token (null) to get the first page, and then use the continuationtoken from that query to get the next page:

public async Task<Tuple<string, IEnumerable<IFamousCelebritiesModel>>?> GetAllAsync(string continuationToken)
{
  IList<FamousCelebritiesModel> modelList = new List<FamousCelebritiesModel>();
  var celebs = _tableClient.QueryAsync<FamousCelebritiesModel>(filter: "", maxPerPage: 10);

  await foreach (var page in celebs.AsPages(continuationToken))
  {
    return Tuple.Create<string, IEnumerable<IFamousCelebritiesModel>>(page.ContinuationToken, page.Values);
  }
  return null;
}

Don’t know how to use a Tuple?

// Get first page:
var page = await GetAllAsync(null);
string continuationToken = page.Item1;
foreach (var item in page.Item2)  
{
  // take items from the list, for example:
  // item.RealName
}

// Get next page:
page = await GetAllAsync(continuationToken);
continuationToken = page.Item1;
foreach (var item in page.Item2)  
{
  // take items from the list, for example:
  // item.RealName
}

That’s it. You are now a Azure Table Storage Expert. Happy coding.

MORE TO READ:

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

Serialization and deserialization of ‘System.IntPtr’ instances are not supported. Path: $.TargetSite.MethodHandle.Value

I added exception handling to my .net core 6 API, and any of my error messages just throws an error 500.

The complete exception is as follows:

System.NotSupportedException: Serialization and deserialization of 'System.IntPtr' instances are not supported. Path: $.TargetSite.MethodHandle.Value.
 ---> System.NotSupportedException: Serialization and deserialization of 'System.IntPtr' instances are not supported.
   at System.Text.Json.Serialization.Converters.UnsupportedTypeConverter`1.Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
   at System.Text.Json.Serialization.JsonConverter`1.TryWrite(Utf8JsonWriter writer, T& value, JsonSerializerOptions options, WriteStack& state)
   at System.Text.Json.Serialization.Metadata.JsonPropertyInfo`1.GetMemberAndWriteJson(Object obj, WriteStack& state, Utf8JsonWriter writer)
   at System.Text.Json.Serialization.Converters.ObjectDefaultConverter`1.OnTryWrite(Utf8JsonWriter writer, T value, JsonSerializerOptions options, WriteStack& state)
   at System.Text.Json.Serialization.JsonConverter`1.TryWrite(Utf8JsonWriter writer, T& value, JsonSerializerOptions options, WriteStack& state)
   at System.Text.Json.Serialization.Metadata.JsonPropertyInfo`1.GetMemberAndWriteJson(Object obj, WriteStack& state, Utf8JsonWriter writer)
   at System.Text.Json.Serialization.Converters.ObjectDefaultConverter`1.OnTryWrite(Utf8JsonWriter writer, T value, JsonSerializerOptions options, WriteStack& state)
   at System.Text.Json.Serialization.JsonConverter`1.TryWrite(Utf8JsonWriter writer, T& value, JsonSerializerOptions options, WriteStack& state)
   at System.Text.Json.Serialization.Metadata.JsonPropertyInfo`1.GetMemberAndWriteJson(Object obj, WriteStack& state, Utf8JsonWriter writer)
   at System.Text.Json.Serialization.Converters.ObjectDefaultConverter`1.OnTryWrite(Utf8JsonWriter writer, T value, JsonSerializerOptions options, WriteStack& state)
   at System.Text.Json.Serialization.JsonConverter`1.TryWrite(Utf8JsonWriter writer, T& value, JsonSerializerOptions options, WriteStack& state)
   at System.Text.Json.Serialization.JsonConverter`1.WriteCore(Utf8JsonWriter writer, T& value, JsonSerializerOptions options, WriteStack& state)

This is my controller. As you can see, it’s supposed to throw an error 400 (BadRequest):

namespace MyController
{
  [ApiController]
  [Route("api/public/v{version:apiVersion}/[controller]")]
  public class MyController : Controller
  {
    [HttpGet("{id}")]
    public async Task<IActionResult> GetData([FromRoute] int id)
    {
      try
      {
        // .. do stuff
        // .. do stuff
        // .. do stuff
      }
      catch (Exception ex)
      {
        return BadRequest(ex);
      }
    }
  }
}

But all it does is throw an error 500:

Any exception is thrown as an error 500, with the same error message.

THE PROBLEM:

It turns out, that the built in System.Text.Json serializer cannot serialize an exception. So when I return the BadRequest(ex), ex cannot be serialized to a JSON text string. And since my API is JSON based, I will get an error.

THE SOLUTION:

Do not return an exception. Instead, return an object than can be serialized, like the ProblemDetails class. I made an extension method for this purpose:

using Microsoft.AspNetCore.Mvc;
using System.ComponentModel.DataAnnotations;
using System.Net;
using System.Security.Authentication;

namespace MyCode
{
  public static class ExceptionExtensions
  {
    public static ProblemDetails ToProblemDetails(this Exception e)
    {
      return new ProblemDetails()
      {
        Status = (int)GetErrorCode(e.InnerException ?? e),
        Title = e.Message
      };
    }

    private static HttpStatusCode GetErrorCode(Exception e)
    {
      switch (e)
      {
        case ValidationException _:
          return HttpStatusCode.BadRequest;
        case FormatException _:
          return HttpStatusCode.BadRequest;
        case AuthenticationException _:
          return HttpStatusCode.Forbidden;
        case NotImplementedException _:
          return HttpStatusCode.NotImplemented;
        default:
          return HttpStatusCode.InternalServerError;
      }
    }

  }
}

With this extension method I can easily convert the Exception to a ProblemDetails class:

namespace MyController
{
  [ApiController]
  [Route("api/public/v{version:apiVersion}/[controller]")]
  public class MyController : Controller
  {
    [HttpGet("{id}")]
    public async Task<IActionResult> GetData([FromRoute] int id)
    {
      try
      {
        // .. do stuff
        // .. do stuff
        // .. do stuff
      }
      catch (Exception ex)
      {
        return BadRequest(ex.ToProblemDetails());
      }
    }
  }
}

And whenever an error occurs, it will return the proper type and message:

Now returning a proper error 400 with a nice title

That’s it. You are now a .net core 6 API exception handling expert. Happy coding.

MORE TO READ:

Posted in .net, .NET Core, c#, General .NET | Tagged , , , , , | 2 Comments

C# .net Swagger API Versioning – Show versions in your Swagger page

So, Swagger does in fact support versioning in the interface. You just need to do it right, and remember to apply all the attributes and methods.

An API with 2 versions, version 7 and version 8

First of all, versioning an API is a standard thing, and will help your API grow in a controlled manner. And Swagger will help you while developing. But let’s see how you can do it:

STEP 1: THE NUGET PACKAGES

To allow versioning, and swagger, add the following NuGet packages:

STEP 2: DECORATE THE CONTROLLERS

When creating the controllers you need to decorate them correctly. If not, Swagger will not discover them:

[ApiController]
[ApiVersion("1.0")]
[ApiExplorerSettings(GroupName = "V1")]
[Route("api/public/v{version:apiVersion}/[controller]")]
public class MyV1Controller : Controller
{
  ...
  ...
  ...
}

[ApiController]
[ApiVersion("2.0")]
[ApiExplorerSettings(GroupName = "V2")]
[Route("api/public/v{version:apiVersion}/[controller]")]
public class MyV2Controller : Controller
{
  ...
  ...
  ...
}

The [ApiVersion] determines the version. Make sure to take a notice if you write “1.0” or “1”. The value will be used later.

The [ApiExplorerSettings] determines where the version will be displayed. Make sure to take a notice if you write “V1”, or “V1.0” or “Version1.0”. The value will be used later.

STEP 3: HOOK UP VERSIONING

In your Program.cs, hook up the API versioning:

builder.Services.AddApiVersioning(
  options =>
  {
    options.ReportApiVersions = true;
    options.Conventions.Controller<MyV1Controller>().HasApiVersion(new Microsoft.AspNetCore.Mvc.ApiVersion(1, 0));
    options.Conventions.Controller<MyV2Controller>().HasApiVersion(new Microsoft.AspNetCore.Mvc.ApiVersion(2, 0));
  }
);

Here I hook up my MyV1Controller to version 1.0 and MyV2Controller to version 2.0.

STEP 4: HOOK UP SWAGGER GENERATION:

In Program.cs, add all versions to the swagger generation:

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
  {
    options.SwaggerDoc("V1", new OpenApiInfo() { Title = "API V1", Version = "V1.0" });
    options.SwaggerDoc("V2", new OpenApiInfo() { Title = "API V2", Version = "V2.0" });
    options.ResolveConflictingActions(apiDescriptions => apiDescriptions.First());
    options.CustomSchemaIds(x => x.FullName);
  }
);

Take a notice of “V1” and “V2“. Those words need to match the attributes in the controller decoration.

STEP 5: HOOK UP SWAGGER VISUALS:

In Program.cs, hook up the swagger ui pages:

if (app.Environment.IsDevelopment())
{
  app.UseSwagger();
  app.UseSwaggerUI(options =>
    {
      options.SwaggerEndpoint($"/swagger/V1/swagger.json", "V1.0");
      options.SwaggerEndpoint($"/swagger/V2/swagger.json", "V2.0");
     }
  );
}

Here I allow swagger to have 2 pages, one for each version of the API.

That’s it. You are now a Swagger and versioning expert. Happy coding.

MORE TO READ:

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

.NET API CORS: Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header.NET API

What to do if you get the following error in the browser when calling a CORS protected .NET Core API endpoint:

Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:4200' is therefore not allowed access. The response had HTTP status code 405.

CHECK 1: ALLOW METHOD “OPTIONS”:

A preflight request is a request that the browser issues to check if the CORS settings are implemented correct.

So if your endpoint is a POST, PUT or DELETE endpoint, and the browser sends a request with type “application/json“, the browser sends 2 requests, first the “OPTIONS” request, followed by a POST, PUT or DELETE.

In the CORS settings of your application, you must therefore also allow OPTIONS:

app.UseCors(builder =>
{
  builder
     .WithOrigins("http://localhost:4200", "https://localhost:4200")
     .SetIsOriginAllowedToAllowWildcardSubdomains()
     .AllowAnyHeader()
     .AllowCredentials()
     .WithMethods("GET", "PUT", "POST", "DELETE", "OPTIONS")
     .SetPreflightMaxAge(TimeSpan.FromSeconds(3600));
}
);

CHECK 2: CORS IS DEFINED TWICE IN YOUR CODE:

Check that you have defined CORS twice.

First add CORS to the WebApplicationBuilder Services:

var builder = WebApplication.CreateBuilder();
...
...
builder.Services.AddCors();

Then when defining the app:

var app = builder.Build();
app.UseCors(builder =>
      {
        builder
              .WithOrigins("http://localhost:4200", "https://localhost:4200")
              .SetIsOriginAllowedToAllowWildcardSubdomains()
              .AllowAnyHeader()
              .AllowCredentials()
              .WithMethods("GET", "PUT", "POST", "DELETE", "OPTIONS")
              .SetPreflightMaxAge(TimeSpan.FromSeconds(3600));

      }
);

CHECK 3: CHECK THE SEQUENCE OF CALLS:

You must define CORS before you map controllers, define routes etc.

var app = builder.Build();

// The first thing in the chain of calls is to define the CORS
app.UseCors(builder =>
      {
        builder
              .WithOrigins("http://localhost:4200", "https://localhost:4200")
              .SetIsOriginAllowedToAllowWildcardSubdomains()
              .AllowAnyHeader()
              .AllowCredentials()
              .WithMethods("GET", "PUT", "POST", "DELETE", "OPTIONS")
              .SetPreflightMaxAge(TimeSpan.FromSeconds(3600));

      }
);

...
...

// After that, I can do mapping, routing, redirection etc...
app.MapControllers();
app.UseRouting();
app.UseHttpsRedirection();

if (app.Environment.IsProduction())
{
  app.UseTokenAuthentication();
}

app.Run();

CHECK 4: YOUR LOAD BALANCER MUST ALLOW “OPTIONS” AS WELL

Do you have a load balancer in front of your API? Make sure that the load balancer allows OPTIONS methods as well.

You are now a Cross-Origin Resource Sharing expert. Happy coding.

MORE TO READ:

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

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