Friday 1 April 2022

GraphQL in Asp.Net Core - Creating a flexible API

More and more .NET Developers have heard about GraphQL. This started as an in-house project in Facebook 2012 to provide a flexible way of sending customized data to mobile clients. Giving the clients the possible to query after tailored data meant sending less data over the wire to the mobiles with less bandwidth. As cell phones moves over to 5G networks, the issue means less and less (in urban areas with good base station coverage), however we should of course seek to always optimize our data transfer as pure bandwidth usage is always a valued thing to optimize. And added dimension is the less cost of creating APIs as we can tailor our data needs. Instead of creating methods for either returning lookup ids and then querying after entire data objects, we can project only the data we need to retrieve to present data on the mobile clients in a meaningful way. Whatever makes your boat rock for showing interest in GraphQL, this article will discuss how you can get started with GraphQL in Asp.Net Core. I have prepared a demo here:
 
  https://github.com/toreaurstadboss/AspNetCore-GraphQLDemo
 
The demo repository shows a list of the tallest mountains in the municipialites in Norway. Norway is a land of mountains and it is always to know which mountain is the very tallest in the municipiality you are visiting! (I enjoy mountain climbing and hiking now and then in my spare time). The demo page shows a text area where you can customize the data to load here. Of course we can only load the data provided for us. We can also use the Ui playground for GraphQL added for us here too:
First off, we need to grab some Nuget packages for GraphQL. We will be using Asp.Net Core 3.1. in this article.
 
        <PackageReference Include="GraphQL" Version="2.4.0" />
	<PackageReference Include="GraphQL.Server.Transports.AspNetCore" Version="3.4.0" />
	<PackageReference Include="GraphQL.Server.Transports.WebSockets" Version="3.4.0" />
	<PackageReference Include="GraphQL.Server.Ui.Playground" Version="3.5.0-alpha0046" />  
 
Then we need to specify in our Startup class the needed setup.
 
Startup.cs
using AspNetCore_GraphQLDemo.GraphQL; using AspNetCore_GraphQLDemo.GraphQL.Messaging; using Data; using Data.Repositories; using GraphQL; using GraphQL.Server; using GraphQL.Server.Ui.Playground; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Diagnostics; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.WebSockets; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Newtonsoft.Json; namespace AspNetCore_GraphQLDemo { public class Startup { private readonly IWebHostEnvironment _env; public Startup(IConfiguration configuration, IWebHostEnvironment env) { _env = env; Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { // If using IIS: services.Configure<IISServerOptions>(options => { options.AllowSynchronousIO = true; }); services.AddControllersWithViews(); services.AddHttpContextAccessor(); services.AddRazorPages().AddRazorRuntimeCompilation(); services.AddDbContext<MountainDbContext>(options => { options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")); }); services.AddScoped<IMountainRepository, MountainRepository>(); services.AddScoped<IDependencyResolver>(s => new FuncDependencyResolver(s.GetRequiredService)); services.AddScoped<MountainSchema>(); services.AddSingleton<MountainMessageService>(); services.AddSingleton<MountainDetailsDisplayedMessageService>(); services.AddGraphQL(x => { x.EnableMetrics = true; x.ExposeExceptions = _env.IsDevelopment(); x.SetFieldMiddleware = true; }).AddGraphTypes(ServiceLifetime.Scoped) .AddUserContextBuilder(httpContext => httpContext.User) .AddDataLoader() .AddWebSockets(); services.AddCors(options => { options.AddPolicy(name: "MyAllowSpecificOrigins", builder => { builder.AllowAnyOrigin().AllowAnyMethod(); }); }); } //static IEnumerable<Type> GetGraphQlTypes() //{ // return typeof(Startup).Assembly // .GetTypes() // .Where(x => !x.IsAbstract && // (typeof(IObjectGraphType).IsAssignableFrom(x) || // typeof(IInputObjectGraphType).IsAssignableFrom(x))); //} // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); app.UseBrowserLink(); } app.UseExceptionHandler(errorApp => { errorApp.Run(async context => { context.Response.Redirect("/Error"); context.Response.StatusCode = 500; var exceptionHandlerPathFeature = context.Features.Get<IExceptionHandlerPathFeature>(); var exception = exceptionHandlerPathFeature.Error; var result = JsonConvert.SerializeObject(new { error = exception.Message }); context.Response.ContentType = "application/json"; await context.Response.WriteAsync(result); }); }); app.UseStaticFiles(); app.UseRouting(); app.UseCors("MyAllowSpecificOrigins"); app.UseWebSockets(); app.UseGraphQLWebSockets<MountainSchema>("/graphql"); //app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapDefaultControllerRoute(); }); app.UseGraphQL<MountainSchema>(); if (env.IsDevelopment()) { app.UseGraphQLPlayground(new GraphQLPlaygroundOptions { }); } } } }
In ConfigureServices method above we register the schema for our GraphQL method.
 
 
    services.AddScoped<MountainSchema>(); 
 
 
We also add GraphQL itself and setup also web sockets (which are needed for GraphQL).
 
       services.AddGraphQL(x =>
                {
                    x.EnableMetrics = true; x.ExposeExceptions = _env.IsDevelopment(); x.SetFieldMiddleware = true; }).AddGraphTypes(ServiceLifetime.Scoped)
            .AddUserContextBuilder(httpContext => httpContext.User)
            .AddDataLoader()
            .AddWebSockets(); 
 
Just as a side note, you want to add Cors also:
 
     services.AddCors(options =>
            {
                options.AddPolicy(name: "MyAllowSpecificOrigins",
                    builder =>
                    {
                        builder.AllowAnyOrigin().AllowAnyMethod();
                    });
            });
 
Inside Configure method we add also the following to enable GraphQL:
 
           app.UseCors("MyAllowSpecificOrigins");

            app.UseWebSockets();

            app.UseGraphQLWebSockets<MountainSchema>("/graphql");

            //app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapDefaultControllerRoute();
            });

            app.UseGraphQL<MountainSchema>();
            if (env.IsDevelopment())
            {
                app.UseGraphQLPlayground(new GraphQLPlaygroundOptions
                {
                    
                });
            }
        }  
 
Our Mountainschema looks like this:
MountainSchema.cs
using AspNetCore_GraphQLDemo.GraphQL.Types; using AspNetCore_GraphQLDemo.GraphQL.Types.Directives; using GraphQL; using GraphQL.Instrumentation; using GraphQL.Types; namespace AspNetCore_GraphQLDemo.GraphQL { public class MountainSchema : Schema { public MountainSchema(IDependencyResolver resolver) : base(resolver) { Query = resolver.Resolve<MountainQuery>(); Mutation = resolver.Resolve<MountainMutation>(); Subscription = resolver.Resolve<MountainSubscription>(); RegisterDirective(new LowercaseDirective()); RegisterDirective(new OrderbyDirective()); var builder = new FieldMiddlewareBuilder(); builder.Use<LowercaseFieldsMiddleware>(); builder.ApplyTo(this); builder.Use(next => { return context => { return next(context).ContinueWith(x => { var c = context; var result = x.Result; result = OrderbyQuery.OrderIfNecessary(context, result); return result; }); }; }); builder.ApplyTo(this); //builder.Use<CustomGraphQlExecutor<MountainSchema>>(); //builder.ApplyTo(this); } } }
We pass in a IDependencyResolver (dependency!) into the constructor and resolve the classes we desire (we inherit from Schema class). We wire up our schema here to the Query, Mutation and Subscription we desire and register directives. Here is how the Query property is set:
 
MountainQuery.cs
using AspNetCore_GraphQLDemo.GraphQL.Types; using Data; using Data.Repositories; using GraphQL.Types; namespace AspNetCore_GraphQLDemo.GraphQL { public class MountainQuery : ObjectGraphType { public MountainQuery(IMountainRepository mountainRepository) { Field<ListGraphType<MountainType>>("mountains", resolve: context => mountainRepository.GetAll() ); FieldAsync<MountainType>("mountain", arguments: new QueryArguments(new QueryArgument<NonNullGraphType<MountainIdInputType>> {Name = "id"}), resolve: async context => { var mountain = context.GetArgument<MountainInfo>("id"); var mountainFromDb = await mountainRepository.GetById(mountain.Id); return mountainFromDb; }); //FieldAsync<MountainType>("selectmountain", // arguments: new QueryArguments(new QueryArgument(typeof(int)) { Name = "id" }), // resolve: async context => // { // var mountain = context.GetArgument<MountainInfo>("id"); // var mountainFromDb = await mountainRepository.GetById(mountain.Id); // return mountainFromDb; // }); //sadly, we need to inherit from IGraphType and cannot just have simple scalar arguments in GraphQL.Net.. } } }
As you can see, we can define multiple queries. We inherit from ObjectGraphType and pass in a IMountainRepository. This is an interface for your repository, which fetches data via Entity Framework Core and you can then load data into GraphQL from the local database (The DEMO uses Sql Server (SQLEXPRESS)) via EF Core in a simple manner by only providing the repo via dependency injection. We define via the methods Field and FieldAsync our methods (note the use of string constants as a string value we can use in GraphQL queries of ours that resides in the Schema) and the resolve lambda tells how data is to be fetched. We can specify arguments also. The "mountain" FieldAsync method also accepts arguments via the
arguments lambda and this allows us parameterized access to our data. Over to the Subscription property. It looks like this:
 
using AspNetCore_GraphQLDemo.GraphQL.Messaging;
using AspNetCore_GraphQLDemo.GraphQL.Types;
using GraphQL.Resolvers;
using GraphQL.Types;

namespace AspNetCore_GraphQLDemo.GraphQL
{
    public class MountainSubscription : ObjectGraphType
    {
        public MountainSubscription(MountainDetailsDisplayedMessageService mountainDetailsDisplayedMessageService)
        {
            Name = "Subscription";
            AddField(new EventStreamFieldType
            {
                Name = "detailsDisplayed",
                Type = typeof(MountainDetailsMessageType),
                Resolver = new FuncFieldResolver<MountainDetailsMessage>(c => c.Source as MountainDetailsMessage),
                Subscriber = new EventStreamResolver<MountainDetailsMessage>(c => mountainDetailsDisplayedMessageService.GetMessages())
            });
        }
    }
}
 
 
Here we inherit from ObjectGraphType (as we did for Query) and we use the MountainDetailsDisplayedMessageService. This was added as a (concrete class) singleton in the Startup.cs file. The message service uses RxJs serverside to handle the Pub-sub pattern of the subscriber. We are using System.Reactive.Subjects here.
 
MountainSubscription.cs
using System; using System.Reactive.Linq; using System.Reactive.Subjects; namespace AspNetCore_GraphQLDemo.GraphQL.Messaging { public class MountainDetailsDisplayedMessageService { private readonly ISubject<MountainDetailsMessage> _messageStream = new ReplaySubject<MountainDetailsMessage>(1); public MountainDetailsMessage AddMountainDetailsMessage(int id) { var message = new MountainDetailsMessage { Id = id }; _messageStream.OnNext(message); return message; } public IObservable<MountainDetailsMessage> GetMessages() { return _messageStream.AsObservable(); } } }
The mutation looks like this:
MountainMutation.cs
using AspNetCore_GraphQLDemo.GraphQL.Messaging; using AspNetCore_GraphQLDemo.GraphQL.Types; using Data; using Data.Repositories; using GraphQL.Types; namespace AspNetCore_GraphQLDemo.GraphQL { public class MountainMutation : ObjectGraphType { public MountainMutation(IMountainRepository mountainRepository, MountainMessageService mountainMessageService) { FieldAsync<MountainType>("createMountain", arguments: new QueryArguments( new QueryArgument<NonNullGraphType<MountainInputType>> {Name = "mountain"}), resolve: async context => { var mountain = context.GetArgument<MountainInfo>("mountain"); await mountainRepository.AddMountain(mountain); mountainMessageService.AddMountainAddedMessage(mountain); return mountain; }); FieldAsync<MountainType>("removeMountain", arguments: new QueryArguments( new QueryArgument<NonNullGraphType<MountainIdInputType>> { Name = "id" }), resolve: async context => { var mountain = context.GetArgument<MountainInfo>("id"); await mountainRepository.RemoveMountain(mountain.Id); return mountain; }); } } }
We can create a mountain like this in GraphQL Query:
 
 mutation {
  createMountain(mountain: {
   county: "Svalbard"
  muncipiality: "Svalbard"
  officialName: "Newtontoppen"
  referencePoint: "Isbjønn på toppen"
  comments: "Husk rask snøskuter",
  metresAboveSeaLevel: "1713",
  primaryFactor: "1713"
  }) {    
    id
  }
} 
  
 
And we can remove a mountain (don't we all?) like this:
 

# Write your query or mutation here
mutation {
  removeMountain(id: {
    id: 370
  }) { id }
}
  
 
If you clone the repo you will find more source code concerning directives such as lowercase and sorting. As you saw in MountainSchema I use the FieldMiddlewareBuilder to do the sorting as this needs to tap into the pipeline more of GraphQL.Net. We also need some more code - for the client side of course. The client side code relies on Apollo Client lib like this:
 
index.cshtml
<script src="https://unpkg.com/apollo-client-browser@1.7.0"></script>
The libman.json file (the similar file to package.json when it comes to specifying client-side libraries in .net core mvc solutions) of the demo solution looks like this I have used looks like this:
 
libman.json
{ "version": "1.0", "defaultProvider": "cdnjs", "libraries": [ { "library": "twitter-bootstrap@4.2.1", "destination": "wwwroot/lib/bootstrap", "files": [ "js/bootstrap.bundle.js", "css/bootstrap.min.css" ] }, { "library": "jquery@3.3.1", "destination": "wwwroot/lib/jquery", "files": [ "jquery.min.js" ] }, { "provider": "unpkg", "library": "font-awesome@4.7.0", "destination": "wwwroot/lib/font-awesome/" }, { "provider": "unpkg", "library": "toastr@2.1.4", "destination": "wwwroot/lib/toastr/" } ] }
We then need some client side code to load data from GraphQL server of ours.
 
  <script>

    function LoadGraphQLDataIntoUi(result) {

        var tableBody = $("#mountainsTableBody");
        tableBody.empty();

        var tableHeaderRow = $("#mountainsTableHeaderRow");
        tableHeaderRow.empty();

        var rowIndex = 0;

        result.data.mountains.forEach(mountain => {

            if (rowIndex == 0) {
                Object.keys(mountain).forEach(key => {
                    if (key === '__typename') {
                        return;
                    }
                    tableHeaderRow.append(`<th>${key}</th>`);
                });;
            }

            tableBody.append('<tr>');

            Object.keys(mountain).forEach(key => {
                if (key === '__typename') {
                    return;
                }
                if (key === 'id') {
                    tableBody.append(`<td><a href='/home/mountaindetails/?id=${mountain[key]}'><i class='fa fa-arrow-right'></i></a> ${mountain[key]}</td>`);
                    return;
                }
                tableBody.append(`<td>${mountain[key]}</td>`);

            });

            tableBody.append('</tr>');

            rowIndex++;

        });

        toastr.success('Loaded GraphQL data from server into the UI successfully.');


    }

    $("#btnConnect").click(function () {
        ConnectDemo();

    });


    $("#btnLoadData").click(function () {
        var gqlQueryContents = $("#GraphQLQuery").val();
        LoadGraphQLData(gqlQueryContents, LoadGraphQLDataIntoUi);
        toastr.info('Retrieving data from API using GraphQL.');
    });

    $(document).ready(function () {

        console.log('loading');

        var initialQuery = `
                {
                    mountains {
                        id
                        fylke: county
                        kommune: muncipiality
                        hoydeOverHavet: calculatedMetresAboveSeaLevel
                        offisieltNavn: officialName
                        primaerfaktor: calculatedPrimaryFactor
                        referansePunkt: referencePoint
                    }
                }`;

        $("#GraphQLQuery").val(initialQuery);

    });

</script>
 
 
And then a method using Apollo client lib to load the data:
 
 /**
 * Loads GraphQL data specified by query expression and passes the 'result' array to the callBackFunction
 * callBackFunction should be Js method (function) that accept one parameter, preferably called result, which is an object
 * that contains a result.data object.
 */
function LoadGraphQLData(gqlQuery, callBackFunction) {

    var apolloClient = new Apollo.lib.ApolloClient({
        networkInterface: Apollo.lib.createNetworkInterface({
            uri: 'http://localhost:2542/graphql',
            transportBatching: true,
        }), connectToDevTools: true
    });
    var query = Apollo.gql(gqlQuery);

    apolloClient.query({
        query: query,
        variables: {}
    }).then(result => {
        callBackFunction(result);
    }).catch(error => {
        //debugger
        toastr.error(error, 'GraphQL loading failed');
    });
}
 

Saturday 19 March 2022

Using C# 9 language features in .NET Framework and .NET Standard projects

C# 7.0 came out in March 2017 and Microsoft has published other frameworks later, such as .NET Core and .NET 5 plus .NET 6. If you are working with a .NET Framework based solution (or .NET Standard 2.0), you can actually get support for C# 8 and C# 9 language version - enabling you to utilize more of C# language features. The following steps can be used to enable C# language 9 in for example .NET Framework 4.8 (tested and verified that I could use records (a C# 9 language feature).
  • Specify in the .csproj file(s) that you want to use <LangVersion> element set to 9.0
  • Consider using a file called Directory.Build.props (at the root level of your solution) (Case sensitive on Linux) with this shared setting to enabled C# 9.0 version in all projects.
  • Using C# 9 language version also requires you to include a small file in each project listed below, call it IsExternalInitPatch.cs for example.
File IsExternalInitPatch.cs should include this :

  
namespace System.Runtime.CompilerServices
{
    internal static class IsExternalInit { }
}
  


Now you can start playing around with C# 9 in a .NET Framework 4.8 solution for example, which earlier has been limited to C# 7.1 and no later language version features of C#.

namespace SomeAcme.SomeProduct.Common.Test
{
    /// <summary>
    /// This is just a test of csharp 9 for SomeAcme.SomeProduct
    /// Note that Directory.Build.props in this branch uses LangVersion set to 9.0 and we need the file IsExternalInit.cs in every project
    /// </summary>
    /// <remarks>
    /// See these two urls: 
    /// https://btburnett.com/csharp/2020/12/11/csharp-9-records-and-init-only-setters-without-dotnet5.html
    /// https://blog.ndepend.com/using-c9-record-and-init-property-in-your-net-framework-4-x-net-standard-and-net-core-projects/
    /// </remarks>
    [TestFixture]
    public class TestOutCsharpNine
    {

        public record Operasjon (DateTime StartTid, bool ErElektiv, string PasientNavn);


        [Test]
        public void Test_Records_ChsharpNine_And_Deconstruction_And_Discardable_Variables()
        {
            var op = new Operasjon(DateTime.Today.AddHours(8).AddMinutes(15), true, "Bjarne Brøndbo");
            (_, _, string pasientNavn) = op;
            pasientNavn.Should().Be(op.PasientNavn);
        }

        [Test]
        public void Test_Init_Only_Props()
        {
            var op = new OperasjonWithInitOnlyProps
            {
                ErElektiv = true,
                PasientNavn = "Thomas Brøndbo"
            };
            // op.PasientNavn = "foo"; 
            //uncommenting line above should demonstrate init only property giving compiller error if trying to mutate or alter this property
            op.PasientNavn.Should().Contain("Brøndbo");
        }

        [DataContract]
        public class OperasjonWithInitOnlyProps
        {
            [DataMember]
            public string PasientNavn { get; init; }
            [DataMember]
            public bool ErElektiv { get; init; }
        }
    }
}


The CSharp compiler sets up default the CSharp language features according to these rules: The compiler determines a default based on these rules:
Target framework	version	C# language version default
.NET	6.x	C# 10
.NET	5.x	C# 9.0
.NET Core	3.x	C# 8.0
.NET Core	2.x	C# 7.3
.NET Standard	2.1	C# 8.0
.NET Standard	2.0	C# 7.3
.NET Standard	1.x	C# 7.3
.NET Framework	all	C# 7.3
So .NET Framework and .NET Standard based solution has not gotten per default any modernization of C# sharp features since March 2017 (five years ago), but we can with some small modification still use C# 9.0 which came out 1.5 years ago. Of course, this C# language version is meant to be used with .NET 5, so do not expect everything to be supported on it. However, chances are high that much of C# 8 and C# 9 language features could be handy to use in many .NET Framework and .NET Standard based projects. For example, records with their support for immutability is definately a big new thing in C# compared to what is avilable in C# language version 8 or earlier. Lastly, you must also consider how to build C# 9 language features (which assumes .NET 5 available) on a build server. For Team City for example, you must install .NET 5 SDK on the build agent.
Also, most likely you have for example a MS Build step in Team City, so you should use MS Build 16 (VS 2019 Build tools) and install the build tools for VS 2019 on the build agent from this url or google for Build Tools for VS 2019: https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=BuildTools&rel=16&src=myvs&utm_medium=microsoft&utm_source=my.visualstudio.com&utm_campaign=download&utm_content=vs+buildtools+2019 For Azure Devops, choose the VS 2022 agent. I still had to add a "Use NET Core" task and choose 'Package to install' set to 'SDK (contains runtime)', the YAML looks like this:

steps:
- task: UseDotNet@2
  displayName: 'Use .NET Core sdk 5.0.100'
  inputs:
    version: 5.0.100
    includePreviewVersions: true

Also note this - albeit you might have .NET Framework 4.8 in a project, your config file like app.config might have this :
 
  


<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
    </startup>
	<appSettings>
 
 
The supportedRuntime might force you to in a specific project to have a LangVersion set to a lower value anyways. So you might need for example to have LangVersion set to 7.1 in one project and default to LangVersion 9.0. To sum up :
  • .NET Framework and .NET Standard can still use C# language version 8 or 9. You need to do the adjustments I mentioned in this article.
  • C# language version 10 is only supported by .NET 6. To use this language version you have to upgrade framework..
  • Also - test out new language features in one project first and use the basic features first. If you use advanced language features of C# language version 8 or 9 you might consider some glitches.. However, you should get a compiler warning for most errors you encounter.
  • Don't forget that your build agent must be able to build the solution too. So you can use VS 2022 hosted agent and consider also the USE NET Core Sdk task I mentioned here if you build in Azure Devops. If you use a self-hosted agent, like Team City on-premises build agent, you need to install the newest VS 2019 SDK / Build Tools to ensure that you have the C# langversion.
In the Developer command prompt on the build agent you can run this command
 
  csc -langversion:? 
 
This should output the langversions of C# your build agent supports. It also works on a developer PC (use the VS 2019 command prompt). As I noted, C# 10 is only supported in .NET 6. We might have a future situation where C# 11 is still supported in a .NET 6 solution - I am not sure what Microsoft is planning here. But for other and earlier frameworks, it looks like C# 9 is the end of the road of language versions - we have to upgrade to .NET 6 to utilize newer language features (or consider dragging in Nuget compiler packages ..)

Saturday 12 March 2022

Added NinUtilsNorway - shows modulo 11 algorithm for verifying Norwegian personal identifier numbers

I added a library to Nuget called NinUtilsNorway. Nin is an acronym for Norwegian Identifier Number. It is handy for verifying that Norwegian personal identifiers - or PID - are correct. It supports addional formats of Personal Identifiers Numbers (fnr - fødselsnummere) and these kinds of PID / NIN are supported:
  • Fnr (ordinary PIDs / NINs)
  • D-number - handed out to those working in Norway in a temporary period of some months or years - 'guest workers'
  • H-number - "Nødnummer" - given to tourists, unidentified people et cetera - those on temporary visit to Norway on visa e.g
  • DUF-number - given to asylum seekers by UDI
  • FH-number - a variant of H-number with relaxed formatting - supports more numbers
Install it using these commands : .NET Framework 4.6.2 - at least .NET Framework 4.7.1 recommended : Install-Package NinUtilsNorway -Version 1.1.0 .NET Core and .NET 5 and .NET 6 : dotnet add package NinUtilsNorway --version 1.1.0 In the future, in 2032, a new standard - also called PID - will replace todays format. New citizens (newborns etc) will get new NIN / PID fødselsnummere. But people born before 2032 will default keep their
NIN / PID /fnr. We can still read out age and gender from these fnr using the existing modulo-11 algorithm - actually both the age and the gender are readily resolved without checking the two last controls digits via a modulo 11 algorithm. They follow rules establised. You can browse the source code on my Github repo to see how we resolve information from NIN / PID. The Github repo is here: Github repo Here you can browse the source code of NinUtils. The lib is written in netstandard 2.0 so you can use .NET Framework 4.7+ (theoretically 4.6.2 can also be supported) and .NET Core or .NET 5 and .NET 6. A sample client in .NET 6 Console app is:
 
 
using static NinUtilsNorway.NinUtilsNorway;


// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");

Console.WriteLine("Enter your fnr: ");
string fnr =  Console.ReadLine();

bool isValid = IsValidNin(fnr);
NinUtilsNorway.Gender gender = GetGender(fnr);

Console.WriteLine($"The fnr {fnr} is valid? {isValid} Gender of fnr is: {gender}");

 
This page shows information how the last two numbers of a fnr is used to verify the validity of a fnr: http://www.fnrinfo.no/Teknisk/KontrollsifferSjekk.aspx In short we calculate the last two control digits k1 and k2 and check that both numbers are divisible by 11 like this - C# code: Note - for a fnr with digits d1, d2, .. d11 we calculate mathematically the sum of each digit by multiplying these weights: k1 : weights are {3,7,6,1,8,9,4,5,2,1} k2 : weights are { 5, 4, 3, 2, 7, 6, 5, 4, 3, 2, 1 } Check the link I gave above, it has a very easy example. The modulo-11 algorithm is very similar to what is used in Norwegian banks for KID - customer identifier in checques / billings to have another example.
 
 

   /// >summary<
        /// Calculates validity of Nin according to modulo 11 algorithm. 
        /// >/summary<
        /// >param name="nin"<>/param<
        /// >returns<>/returns<
        /// >remarks<>see href="http://www.fnrinfo.no/Teknisk/KontrollsifferSjekk.aspx"
        /// Example of a Modulo-11 algorithm mathematical basis is shown here: 
        /// >see href="http://www.pgrocer.net/Cis51/mod11.html"/<
        /// >/remarks<
        public static bool IsValidNin(string nin)
        {
            nin = nin?.Trim();
            if (nin?.Length != 11)
            {
                return false;
            }

            if (!long.TryParse(nin, out var _))
            {
                return false;
            }

            if (IsDNumber(nin))
            {
                nin = (byte.Parse(nin[0].ToString()) - 4).ToString() + nin.Skip(1);
            }

            int k1 = 0, k2 = 0; //weighted sums be 
            int[] k1_weights = new int[] { 3, 7, 6, 1, 8, 9, 4, 5, 2, 1 };
            foreach (var item in nin.Select((digit, index) =< (digit, index)))
            {
                if (item.index == 10)
                {
                    break; //only considering first 10 digits of nin
                }
                k1 += int.Parse(item.digit.ToString()) * k1_weights[item.index];
            }
            if (k1 % 11 != 0)
            {
                return false; //k1 must be divisible by 11!
            }
            int[] k2_weights = new int[] { 5, 4, 3, 2, 7, 6, 5, 4, 3, 2, 1 };
            foreach (var item in nin.Select((digit, index) =< (digit, index)))
            {
                k2 += int.Parse(item.digit.ToString()) * k2_weights[item.index];
            }
            if (k2 % 11 != 0)
            {
                return false;
            }

            return true; //k1 and k2 is now known to be both divisible with 11
        }


 
The Readme of the Nuget is added below:
 
 
 NinUtilsNorway
Summary
Util methods for Nin (National identifier number) in Norway

Note : Nin standards will be replaced by PID standard in 2032. Nin will be kept, but new Nins handed out will follow PID standard.

The following types of Nin Numbers exists, basically five types of which ordinary Nin and D-number are the most typical. They all consist of 11 digits of which the last two are control digits. (usually Modulo 11 algorithm is used):

Ordinary Nin (fødselsnummer)
D-number (temporary given to foreign workers, may span multiple years)
Help numbers H-numbers (tourists, infants, unconcious people, unidentified people, etc)
FH help numbers FH-numbers (similar to H-numbers)
DUF-number (given to asylum seekers by UDI)
About change of Nin into PID standard as of 2032 : https://www.skatteetaten.no/en/deling/opplysninger/folkeregisteropplysninger/pid/ Sample test persons can be retrieved from here which was helpful in building the util methods. https://skatteetaten.github.io/folkeregisteret-api-dokumentasjon/test-for-konsumenter

See useful list of definitions here: https://www.ehelse.no/standardisering/standarder/identifikatorer-for-personer And DUF-numbers (UDI) : https://www.udi.no/ord-og-begreper/duf-nummer/ Note - Gender calculation will not necessarily be possible after 2032, as you are not guaranteed that Nin will contain correct gender information when PID is introduced. People will keep their Nin as before, but the semantic of Gender where the ninth digit (last of the three digits of 'individual number' is even number means = FEMALE and odd number = MALE is halted after 2032. Newborns and new Nin (PID) will be gender-less, i.e. you cannot read gender out of Nin handed out after 2032.

NinUtilsNorway.NinUtilsNorway.GetGender(System.String)"
        <summary>
        Resolves gender from nin. Rule is that the first six digits are the birth date
        DDMMYYYY followed by 3 'individual digits' (individnummer) and finally two
        control digits (kontrollsiffer). The third digit of 'individual digits' are the 
        indicator for gender. If even number, female individual, if odd number, male individual.
        </summary>
        <param name="nin"></param>
        <returns></returns>
        <remarks>Documentation about Norwegian Nin structure is here<see href="https://www.skatteetaten.no/person/folkeregister/fodsel-og-navnevalg/barn-fodt-i-norge/fodselsnummer/"/></remarks>
    </member>
NinUtilsNorway.NinUtilsNorway.IsDufNumber(System.String,NinUtilsNorway.IDateTimeNowProvider)
        <summary>
        Checks if this is a DUF-number. These numbers are given by UDI 
        The check is only checking it is a number with 12 digits. The number must also 
        reside in UDI data systems, which this method do not check.
        </summary>
        <returns></returns>
        <remarks>Se notes from eHelse here: <see href="https://www.ehelse.no/standardisering/standarder/identifikatorer-for-personer#DUF-nummer"/></remarks>
    </member>
NinUtilsNorway.NinUtilsNorway.IsHelpNumber(System.String,System.Boolean)
        <summary>
        Checks if this is a help number H-number. The default convention for H-number is that it we add 
        the number 4 to the third digit 
        </summary>
        <param name="useEightNineConvention">Use special convention that if the first digit is 8 or 9, it signals 
        a Help Number. Note - this usually designates a FH-help number instead</param>
        <returns></returns>
    </member>
NinUtilsNorway.NinUtilsNorway.IsFHNumber(System.String)
        <summary>
        FH numbers are developed by KITH as a proposal established as a standard 18.01.2010. It is similar to Nin 
        fødselsnumre with 11 digits, and the first digit is 8 or 9. The numbers in position 2 - 9 are generated
        as random numbers. This standard conceals also gender, birthdate or which order the number is provided.
        The algorithms allows about 200 million numbers minus 17% of these due to incorrect control digits (last two digits). 
        Examples of people getting a FH-number are tourists, newborn (infants), unconcious people not identified, 
        unidentified people or similar reasons that a fødselsnummer Nin or D-Number is not available. 
        </summary>
        <param name="number"></param>
        <returns></returns>
    </member>
M:NinUtilsNorway.NinUtilsNorway.IsDNumber(System.String)
        <summary>
        Returns true if a person is having a D-number. A d-number is given to foreign workers in 
        Norway as a temporary identifier during their work period. It is similar to a ordinary Nin (fødselsnummer), but 
        for the first digit in the nin, we add 4. This gives 4,5,6,7 as possible digits for the first digits.
        A lot of other characteristics of D-number are similar to ordinary Nin, including the two control digits follow same rules.
        </summary>
        <param name="nin"></param>
        <returns></returns>
    </member>
NinUtilsNorway.NinUtilsNorway.GetAge(System.String,NinUtilsNorway.IDateTimeNowProvider)
        <summary>
        Calculates age from Nin
        </summary>
        <param name="nin"></param>
        <param name="nowTimeProvider">Provide an implementation to override now time. 
        Useful for mocking</param>
        <returns></returns>
        <remarks>About individual numbers - the 7-9 digits of Nin - and rules of centuries. 
        See explanation here: <see href="https://no.wikipedia.org/wiki/F%C3%B8dselsnummer" /></remarks>
NinUtilsNorway.NinutilsNorway.GetControlDigitsForNin(string nin)
     <summary>
    /// Nin are composed of two control digits at the end. We can calculate these digits. 
    /// Usage: pass in the first NINE digits of the Nin. The last two digits will then be calculated. 
    /// For given first nine digits of we calculate the control digits, last two digits of the nin
    //  Pass in the first nine digits. 11 - (the weighted sum modulo 11) is then returned for first control digit
    //  k1. And the second control digit 2 is similarly calculated, but include the first control digit also as a 
    //  self correcting mechanism.
    /// </summary>
    /// <param name="nin"></param>
    /// <returns></returns>
    </summary>
NinUtilsNorway.NinUtilsNorway.IsValidNin(string nin)
    /// <summary>
    /// Calculates validity of Nin according to modulo 11 algorithm. 
    /// </summary>
    /// <param name="nin"></param>
    /// <returns></returns>
    /// <remarks><see href="http://www.fnrinfo.no/Teknisk/KontrollsifferSjekk.aspx"
    /// Example of a Modulo-11 algorithm mathematical basis is shown here: 
    /// <see href="http://www.pgrocer.net/Cis51/mod11.html"/>
    /// </remarks>
    /// </summary>
Finally, note about testing. See :

https://skatteetaten.github.io/folkeregisteret-api-dokumentasjon/test-for-konsumenter/

For test data.

Also note that you can implement IDateTimeNowProvider to statically set "today date" for predicatable results while testing.