SeanMcCammon C# .Net core 3 Software Developer

A singleton class is a design pattern where only one instance of a class can exist in a program at any given time. This means that every time an object is created from that class, it returns the same instance. Singleton classes are often used when we want to ensure that there is only one instance of a class in the application, such as a configuration class that should be used throughout the program.

On the other hand, Dependency Injection is a design pattern where the dependencies of an object are injected into it by an external entity, rather than the object creating them itself. In other words, instead of an object creating its own dependencies, it is provided with the dependencies from the outside. This makes the object more modular and testable since it can be easily replaced with a mock object for testing purposes.

Dependency Injection is often used in large-scale applications where the complexity of the code is high, and there is a need to reduce the coupling between different parts of the application. It also allows for greater flexibility in the application's design, making it easier to modify and extend.

In summary, the main difference between a Singleton class and Dependency Injection is that Singleton ensures that only one instance of a class exists in the program, while Dependency Injection is a way of providing an object with its dependencies from the outside. They can be used together, but they serve different purposes in software design.

What is a singleton class?

A Singleton class is a design pattern where only one instance of a class can exist in a program at any given time. This means that every time an object is created from that class, it returns the same instance.

Here is an example of a Singleton class in Java:

public class SingletonClass {

    private static SingletonClass instance = null; // Private class variable to store the instance

    

    // Private constructor to prevent instantiation of the class from outside

    private SingletonClass() {

        // Optional initialization code

    }

    

    // Public method to get the instance of the class

    public static SingletonClass getInstance() {

        if (instance == null) {

            instance = new SingletonClass();

        }

        return instance;

    }

    

    // Public method to demonstrate the use of the class

    public void sayHello() {

        System.out.println("Hello, I am a Singleton Class!");

    }

}

In this example, we define a class called SingletonClass with a private static class variable instance to store the instance of the class. The constructor method is made private to prevent instantiation of the class from outside. The getInstance method is used to get the instance of the class and creates one if it doesn't exist yet. The sayHello method is just a simple method to demonstrate the use of the class.

To use the Singleton class, we can call the getInstance method to get the instance of the class, and then use it to call the sayHello method, like this:

SingletonClass instance1 = SingletonClass.getInstance();
SingletonClass instance2 = SingletonClass.getInstance();

System.out.println(instance1 == instance2); // Output: true

instance1.sayHello(); // Output: Hello, I am a Singleton Class!
instance2.sayHello(); // Output: Hello, I am a Singleton Class!

In this example, we create two instances of the SingletonClass using the getInstance method, but we only get one instance because the class is a Singleton. The sayHello method is called on both instances to demonstrate the use of the class.

What is a dependency injected class?

A Dependency Injected class is a class that has its dependencies injected from outside, rather than creating them internally. In other words, the dependencies are passed to the class from outside, rather than being created by the class itself. This allows for greater flexibility and modularity in the design of the software, as well as easier testing.

Dependency Injection is a design pattern where the dependencies of a class are passed to it through its constructor, methods or setters, instead of being created inside the class itself. This helps to decouple the class from its dependencies and makes it easier to replace or modify them without affecting the behavior of the class.

In a Dependency Injected class, the dependencies are often defined as interfaces or abstract classes, which allows for multiple implementations to be used interchangeably. This is known as Inversion of Control (IoC), where the control of the program's flow is shifted from the class itself to an external entity that manages the dependencies.

For example, consider a class Client that depends on a class Service. Instead of creating an instance of the Service class inside the Client class, the Service instance is passed to the Client constructor, like this:

public class Client {

    private final Service service; // Dependency

    

    // Constructor injection

    public Client(Service service) {

        this.service = service;

    }

    

    // Method to demonstrate the use of the class

    public void doSomething() {

        service.doSomething();

    }

}

In this example, we define a class called Client that has a dependency on another class called Service. The Service class is not defined here but is assumed to exist. The dependency is injected through the constructor of the Client class. The doSomething method of the Client class calls a method of the Service class to demonstrate the use of the dependency.

To use the Client class, we need to create an instance of the Service class and pass it to the constructor of the Client class, like this:

Service service = new Service();
Client client = new Client(service);

client.doSomething();

In this example, we create an instance of the Service class and pass it to the constructor of the Client class. The doSomething method of the Client class is called to demonstrate the use of the dependency.

Dependency Injection allows for greater flexibility in the design of the software since the dependencies can be easily replaced with mock objects for testing purposes, and the code is less tightly coupled. This makes it easier to modify and extend the software over time.

In conclusion

Many would think or choose to give examples of a singleton and dependency injection to be similar, even the same using a singleton class as an example to explain dependency injection. However, we can see that they are two completely different patterns.

It's true that there is only ever one object of the type for each, but that is where the similarities end. 

Singleton pattern is used when we need to ensure that only one instance of a class is created throughout the application's lifecycle and that it is easily accessible by other parts of the application. Here are some scenarios where a Singleton pattern might be useful:

  1. Configuration settings: A Singleton class can be used to store application configuration settings that need to be accessed frequently from different parts of the application.
  2. Database connection: A Singleton class can be used to manage a single connection to a database, which can be shared across different parts of the application.
  3. Logging: A Singleton class can be used to manage application logging, which can be accessed from different parts of the application.
  4. Caching: A Singleton class can be used to manage a cache of frequently accessed data, which can be shared across different parts of the application.
  5. Thread pool: A Singleton class can be used to manage a pool of threads that can be reused across different parts of the application.

In general, the Singleton pattern is useful in situations where we need to ensure that there is only one instance of a particular class and that it is easily accessible by other parts of the application. However, it should be used judiciously, as overuse of the Singleton pattern can lead to a complex and inflexible design.

However, looking at the above list - dependency injected classes could also fit all of the above points. So, in a large complex system, it can be said that the dependency injection pattern should be used for modern systems and that a singleton pattern is not really required in today's system.

The other benefit of using dependency injection above a singleton is you can change the class injected. What I mean by that is, maybe you start with a logger class you like. You get most of what you need out of it. However, you find a new, better logger class that gives you more output. You can inject that new class without having to change much if any code. Where you do need to change any code it's very minimal.

That means less work to do when you make these types of changes, especially if it's over a large code base.

So, as I go through the process of interviewing for new roles I find myself starting to challenge myself with interview questions I find online.

This particular one is taken from LeetCode which is an excellent site that contains great content to both learn and test that knowledge with their interview questions as well as mocks.

The question

So, the question is called Add Two Numbers, but you know as it's an interview type question it's not that simple.

By the way, if you want to check this question out on the LeetCode site and try doing it for yourself - you can do so by clicking this link Add Two Numbers.

So, to paraphrase the question. Basically, you get two values passed in using linked list in reverse order.

So, the number 362 for example would get passed in as:

[2] => [6] => [3]

So, 2 numbers get passed in this format. You need to add the two numbers together and then return the result as a linked list in the same format.

My answer

So the way I approached this was to add each number in turn, and I will come to that - but first...

First answer iteration

My first approach was to multiple each value. So the first value in the head of the list was multiplied by 1. Then I added the second value multiplied by 10 and the third multiplied by 100. To make sure I had no limit, the multiplier was multiplied by 10 each time - so the 4th would have been 1000 and so on.

I did this for both values, then on the reverse I divided using modular to get the remainder for each individual value.

This was long winded and although I could get it to work - it was prone to issues and errors.

Second answer iteration

So, brushing this aside I started on my second and final answer. Now, from the first iteration, I knew dividing by 10would give me some answers I needed. Such as the carry-over.

The carry-over would be something like 9x9=18 - 10 is carried.

Another thing I though is, that, instead of working out the number and then doing calculations - I could do for each individual value in the list.

So, the values for the heads of both list1 and list2 could be added together. Then we can divide by 10 to get the carry-over value and a modulus to work out to get the value to add to the 3rd list.

This value gets added to a new list node and it iterates through the list.

But, instead of me continuing to talk - why don't I just give you the working code to go through. This following code is the code I wrote and tested in LeetCode.

public ListNode AddTwoNumbers(ListNode l1, ListNode l2) {
        ListNode root = new ListNode(0);;
        ListNode l3 = root;
        
        int carry = 0;
        while (l1 != null || l2 != null)
        {         
            int l1_val = (l1 != null) ? l1.val : 0;
            int l2_val = (l2 != null) ? l2.val : 0;
            
            int totalVal = l1_val + l2_val + carry;
            
            carry = totalVal/10;
            int finalVal = totalVal%10;
            
            ListNode new_node = new ListNode(finalVal);
            
            
            if (l1 != null) l1 = l1.next;
            if (l2 != null) l2 = l2.next;
            
            l3.next = new_node;
            
            l3 = l3.next;
        }
        
        if (carry > 0)
        {
            ListNode carryNode = new ListNode(carry);
            l3.next = carryNode;
            l3 = l3.next;
        }
        
        return root.next;
    }

Hopefully, going through this will give you an idea of how to answer any similar question you may get in an interview. Also, definitely check out LeetCode (I'm not affiliated by the way) as it is a fantastic place to get many more questions like this to test yourself on.

The ability to validate values passed into an ASP.NET application at the model level is a powerful thing.

You can validate if the correct type of value has been passed in before you even get to processing it. For example, using something such as :

[Key]
int Id {get; set;};

[Required]
string Name {get; set;}

[Required]
[EmailAddress]
string EmailAddress {get; set;}

Looking at the above we can see 3 fields. The first is the Key - this is used as the key value for the model. 

Then we have a string for name. This is a required field, so will validate if it is input and if the value is a string.

The third value is also required, but also has another valuation for EmailAddress. This will validate if the string is in the format for an email - such as sean@seanmccammon-com.stackstaging.com.

Limiting the length

Using these valiations can save you a lot of time. You can use validation techniques such as Regex to build more complex string validation (something for a future post). 

What you can also do, and the subject of this article, is validate not just on the type - but also the size.

For example, you may need to put a maximum length for a string such as name. You may want to limit the maximum size of the name to 80 characters. 

To do this you could do the following

[Required]
[StringLength(80)]
string name {get; set;}

The validator [StringLength(80)] on this string tells the system that the maximum length for the string is 80 characters. If a string input is greater than this - then the validation fails.

Setting minimum and maximum values for a string

What if you want to set the validation to a string that must have a minimum of 3 characters, but a maximum of 80.

This can also be done in a similar way using the StringLength command.

[Required]
[StringLength(80, MinimumLength = 6)]
string name {get; set;}

When a string is input; using the above it validates that the string is between 6 and 80 characters long.

Taking this one further - and using the StringLength validator to it's fullest - you can setup the error message the user will see if validation fails.

[Required]
[StringLength(80, MinimumLength = 6, ErrorMessage = "Name length much be between 6 and 80 characters in length.")]
string name {get; set;}

Adding this ErrorMessage gives the user a better idea of why the input will have failed. You can be more specific in these error messages - such as giving the format of the value that should be input - if you're using Regex to validate specific string formats.

In the case of StringLength we can le the user know that the string they have input for name is either too long or too short if it fails validation.

StringLength, MinimumLength and MaximumLength to validate length

Using the StringLength command along with MinimumLength and MaximumLength values means you can easily validate input in the model - to ensure it fits with the size of input you need.

Using StringLength in your model will illuminate the need to do some of the validation in your controller (although redundant validation will be needed if it's done in an API).

The Unsafe Keyword

What is the unsafe keyword in C#? When should you use it? Should you use it?

It’s not one of the most common used keywords in your generally C# application but I wanted to talk about it in this article. It could be the answer to your current problem - or you may have just gone through some code, found it and wondered what it is used for.

What is unsafe keyword in C#?

Unsafe is a C# programming language keyword that is used to denote a section of code that is not managed by the .Net Framework Common Language Runtime (CLR). Unsafe is used to declare a block of code or member where some or all of the code that is considered to be unmanaged code.

Microsoft’s pages refer to the unsafe keyword as: denotes an unsafe context, which is required for any operation involving pointers.

Coming from a background in C++, having any keyword that you have to use to denote pointers is strange; though the user of pointers is not a strange context. Pointers are a common things in C++; but for C# - pointers are generally, in most cases at least, never used.

In fact, I would suspect many of those developing in C# don’t even realize that there is some pointer functionality available.

Can we use pointers in C#?

To maintain security and type safety, C# does not support pointer arithmetic: at least not by default. Using the unsafe keyword; you can define an unsafe context in code in which pointers can be used.

Using pointers in C# is where we start to use unmanaged code: code that is outside the context of the .Net Framework CLR.

C# unsafe struct

If we can use pointers in C#, does this mean we can define an unsafe struct. You can use the unsafe keyword when defining a struct to define the entire struct as unsafe in context. This will allow you to use pointers in your struct.

The following example uses unsafe to define two pointers in the struct. This example shows how to define a double linked list structure in C#.

C#
public unsafe struct LList
{
    public int Value;
    public LList* next;
    public LList* previous;
}

Using the unsafe keyword it is completely possible to create an unsafe struct in this manner. In this example above then entire struct is classified as unsafe - however, this could be re-written as follows:

C#
public struct LList
{
    public int Value;
    public unsafe LList* next;
    public unsafe LList* previous;
}

In this example, only the field declarations for left and right fall into the unsafe context.

C# unsafe methods

In this article we’ve shown that you can use the unsafe keyword to define an unsafe field/variable and an unsafe struct - so can we use them to define an unsafe method?

Unsafe is a keyword that is added to modify a field or block of code to define it as unsafe context. As this is true; it can be added to a method to mark all code in that method as unsafe (similar to how we used it for struct).

The following would define a method where all the code is in the unsafe context.

C#
{ ...
    public unsafe LList* findValue(LList* in, int value)
    {
        // Do your stuff here
        LList *output = null;
        while(in->next != null)
        {
            if(in->value == value)
            {
                output = in;
                break;
            }
            
            // Step through the list
            in = in->next;
        }
        
        return output;
    }
}

Using the unsafe keyword as a modifier to the method declaration tells the compiler that the entire content of the method falls into the unsafe context.

Unsafe block of code

Instead of creating an entire method that falls into the unsafe context, you can create an unsafe block of code.

C#
Static void main()
{
    int valueToFind = 10;
    unsafe 
    {
        LList *aList = setupList(); // Method used to setup values in the list
        
        findValue(aList, valueToFind);
    }
}

This method uses an unsafe block of code for when it needs to create a pointer to some list in memory and then pass that to the unsafe method findValue.

One great reference you may want to check out is a page over at Microsoft on Unsafe code. This gives more details on the unsafe keyword and what you can do with it.

Should we use the unsafe keyword in C#

We have the unsafe keyword available to us in C# but should we use it?

My personal thought is this: for most coding problems the answer is no. Pointers and pointer arithmetic were left out of C#, and many other languages, on purpose I believe.

Having access directly to memory causes problems when misused. I’ve personally seen code in C/C++ that has access memory that had been previous reallocated or was out of scope.

This caused issues with the applications and could result in major problems.

Using managed code; not having access directly to memory; means less of these issues. Not accessing unallocated memory. Not trying to get values or update values causing unknown results.

That said, there are some valid reasons I could see for using unsafe pointers. When you create a string in C# its set to a value and is immutable. If you wanted to change some part of that string; say from “king” to “sing”; then you have to in C# you would first assign “king” to the string object.

C#
string value = "king";


Then when you wanted to change it to “sing”, you would assign it that value

C#
value = “sing”;

However, this is not changing the value in memory of the variable. Stepping through the process. First, a string object value is created and assigned the value “king”

Next, we assign the value “sing” to the value object. However, because it is immutable it doesn’t change the value in the memory location it is holding the value “king” in; it creates a whole new string object and assigns the value “sing” to it.

Using the unsafe keyword you could create a pointer, in theory, to the value object memory and change the value held in that memory location instead of creating a new string object.

That said, this is where the coding issues could come in place. You could try and assign a value that is too big for that memory to hold (say a 5 character string instead of 4) and you would get undefined problems.

It is far better to not us unsafe in this situation; continue with managed code and know that you are not potentially adding problems. However, you may find that reading from memory directly using pointers could be quicker than managed object methods.

However, I would still say - do not use the unsafe keyword unless you know what you are doing and face an issue that can only be answered by directly accessing memory through pointers.

I've previously talked about hashing a password for IdentitySever4 in C#. This works well when you want to store your password with added salt to a database or similar store that could be read.

In this previous article I talked about how this could be added to code that was bulk transferring users from one, older, user table to the new AspNetUsers in IdentityServer4. I also mentioned that when doing this you could also create a Random Password Generator to create initial passwords for those users.

In this article I am going to list a code example that does just this. It allows you to specify inputs to increase the character set to choose from. It also allows you to specify a size you would like that password to be (I would suggest at least 10 characters for security).

There are improvements you could make to this code: for example adding spaces, and making sure there are no more than 1 or 2 character that are consecutive (although in my tests is mostly random for each character in password). You could also add some validation to the password - something I may add examples for.

The random password generator C# code example

Let us start with the C# code example

First, lets define the random password generation code 

using System;

namespace PasswordGenerator
{
  class RandomPasswordGenerator
{

   const string LOWER_CASE = "abcdefghijklmnopqursuvwxyz";
   const string UPPER_CAES = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
   const string NUMBERS = "123456789";
   const string SPECIALS = @"!@£$%^&*()#€";


    public string GeneratePassword(bool useLowercase, bool useUppercase, bool useNumbers, bool useSpecial,
        int passwordSize)
    {
        char[] _password = new char[passwordSize];
        string charSet = ""; // Initialise to blank
        System.Random _random = new Random();
        int counter;

        // Build up the character set to choose from
        if (useLowercase) charSet += LOWER_CASE;

        if (useUppercase) charSet += UPPER_CAES;

        if (useNumbers) charSet += NUMBERS;

        if (useSpecial) charSet += SPECIALS;

        for (counter = 0; counter < passwordSize; counter++)
        {
            _password[counter] = charSet[_random.Next(charSet.Length - 1)];
        }

        return String.Join(null, _password);
    }
  }
}

Breaking down this code. To begin with we create and set up some constant strings. These strings contain all the lowercase characters, uppercase characters, numbers and special characters. These are what we will be using for the password generation.

Next we define the GeneratePassword methods. We need to pass in parameters that define how we will generate the password.

In the case of this methods we pass in either True or False to say if using lowercase, uppercase, special or number characters to generate the password. The final input is the size of the password we want to generate.

Next we set up a few variables and objects we are going to be using through the method including the character set, and password string, a randomiser and a counter.

The next few lines simply adds characters to the character set to be used for the random password: depending on the input values to the method.

The final stage is to loop a number of times equal to the size of the password required and to pull out a random character from the character set, which is then added to the password.

This then gets returned to the calling method.

An example of how to call the password generator method

To call this class and generate a random password, you need to call with the following

static void Main(string[] args)
{
  string password;

  RandomPasswordGenerator randomPasswordGenerator = new RandomPasswordGenerator();

  password = randomPasswordGenerator.GeneratePassword(true, true, true, true, 16);
  Console.WriteLine(password);
}

You can see in this case we pass in true for all values and have a password size of 16.

Adding spaces to the password

If you wanted to be able to include spaces in your random password: you could add the following code.

First we need to add the new constant for space.

const string SPACE_CHARACTER = " ";

Next, we need to change the method to allow True/False to add spaces to the password.

public string GeneratePassword(bool useLowercase, bool useUppercase, bool useNumbers, bool useSpecial, bool addSpace, int passwordSize)

Finally we just need to add the extra True/False value to the method call.

password = randomPasswordGenerator.GeneratePassword(true, true, true, true, 16);

Ways to extend this functionality

There are more ways you could extend the functionality of this object. You could add a new method to ValidatePassword using Regex to check if it has a member of each of the characters you chose for the password.

You could add a method to check for same character next to each other (Hint: It could look like this) 

const int MAXIMUM_IDENTICAL_CONSECUTIVE_CHARS = 2
bool moreThanTwoIdenticalInARow =
 characterPosition > MAXIMUM_IDENTICAL_CONSECUTIVE_CHARS
 && _password[counter] == _password[counter - 1]
 && _password[counter - 1] == _password[cunter - 2];

if (moreThanTwoIdenticalInARow)
{
  characterPosition--;
}

These are just a few extras to improve the passwords you generate.

This code will give you random passwords

Using this method you will be able to generate a random password for a number of users. You could have this as a random password generator for when someone signs up. You could use it when doing something like a bulk user transfer.

What ever you need a random password generator for - this will hopefully suite your needs.

Recently we started working on a new IdentityServer4 project - something to give a new facelift and security to an existing product usin C# and .Net Core 3.

The idea was to create a portal for users to log in through, then forward on to the existing products.

The plus side of this: using the latest .Net Core 3 technologies, adding extra security through the IdentityServer4 and future proof.

The negative: IdentityServer4 used it’s own users table in our Microsoft SQL Server DB.

We already had a number of users that had created account in the old product; so the minimum we wanted to do was to transfer the login details without having to manually recreate them through the Identity Server interface.

The first real issue

The first real issue is the hashing algorithm. The existing user table had hashed passwords and a salt value. We were unable to see what the original password was and so could not reproduce.

To add to that, the salt used for these was generated through a different, PHP based, algorithm. We could not simply copy the passwords and salts into the new table.

This added a second issue; IdentityServer4 had its own password hashing algorithm - so we needed to use this to create the hashed password when we copied/created the users in the copy.

The C# .Net Core3 algorithm I used

After a little digging I round out a little more about the PasswordHasher object. It sounded like the tools for the job. After a little more digging I came up with the following code which I used to hash passwords for my existing users after transferring to the new tables.

IdentityUser _identityUser = new IdentityUser(); 
Guid _guid = new GUID(); String _password;
While Loop() 
{ 
    Read existing table(...) 

    _guid = Guid.NewGuid(); 
    _identityUser.UserName = ReadUserName; 
    _identityUser.Email = ReadEmail; 
    _identityUser.Id = guid.ToString(); 
    _identityUser.NormalisedEmail = Email To Upper;
    var _hasher = new PasswordHasher<IdentityUser>();
    _password = _hasher.HashPassword(_identityUser, @“password”);

    Save to new table(...);
} 

In the above code: you create a new identity user, guid and string for the password. 

Next you create your own method for reading the details from the existing table. Add the appropriate details from this into the identity user structure.

Next you create a object of type PasswordHasher. Finally, pass in the user and password text and you get the hashed password out as a string.

Save all this to the AspNetUsers table (the one used by Identity Server as default) and you’ve will have now created a user that you can use to log into your Identity Server project.

This will allow you to build a small application to bulk copy existing users into the new user login - though they may need to change their password on first login.

As part of this tool you may create a random password generation code then save that out to a log file so that each user has an independent password. 

Check out a future post where I will detail the random password generator code I have used in my project.

If you’re anything like me: when you wanted to find out more about ASP.NET you went to the internet.

May be you went to an online site that has training courses such as Udemy. Maybe you searched and found some tutorial pages. 

Me personally, I did both and after trying to follow some of these I found information that had gone out of date.

starting wit information I leant for Route

When I was trying to add a custom route to a page/method - I followed a course which told me to add it to RouteConfig.cs.

Configure the new route for my custom page and inputs in using MapRoute().

With a default .Net Core MVC build in Visual Studio 2019 this file was not in the project.

Examining the default C# files; in Startup.cs the endpoints are mapped using MapControllerRoute().

This worked well but did not give me the flexibility I was after. 

What I really wanted was to have a custom route defined for specific methods in my class that managed a single page.

For example: if I have a page called Movie. I wanted a page that maybe called Random. I then may want a page that displays Movie Titles or Authors.

Using the MapControllerRoute was starting to get a little clunky (using a technical term there) and did not give me the flexibility I needed. So, I decided to use Route in the controller.

Custom routes in the controller

Using this method of adding custom routes allows you to add a route per method for you controller.

So, as an example

Route(“Movie/Author/{name}”)]
Public IActionResult ByAuthor(string name) {...} </code></p>

The above allows us to go to a page named Http://our_site/Movie/Random

And also a page where we pass in a parameter Http://out_site/Movie/Author/Sean

In the above, Sean is passed is as a parameter and goes into the name parameter. We can then search our list of movies for the author name and output content found.

Both these methods would exist in the controller - which maybe named MovieController.cs

The best way to route in ASP.NET code

This, in my opinion, is the best way too route to pages in your ASP.NET code. Each method has its own custom route defined which includes any parameters. 

You don’t need to add any code for the route into your startup.cs file; as it’s all contained in the controller file.

I wanted to start this post with a quick caveat. At the time of writing this post: the .Net Core3 is currently at Preview 8.

As we grow closer to the September release date I hope that many of the code and feature I talk about in my upcoming blog posts will remain the same.

However, I may have to come back to some of them in the future to fine tune.

That said, let us begin

Why write about .Net Core 3?

So, if you look at my previous posts on this site: most are technical. None of them really go into too many specifics apart from any coding examples.

I’m now on a journey myself: a journey of learning about .Net Core 3. A journey that is a discovery of all the new features - but also older features that have not changed.

A journey that will see me work with and code new products. A journey that will also see me learning more about ASP.NET as this is something I have not used commercially.

So, as I travel along this learning journey I thought I would share with you all the things I learn. 

The initial posts maybe more suited to people who are starting to learn C# or .Net Core 2/3 - but I hope to get more technical as the posts go on.

So who maybe interested in these posts?

As I said above. My original though is, that, many of the people interested in these posts will be those on a learning journey.

However, it may be that, you may who already has knowledge of .Net Core 2 and want to see what has changed: see what has changed from my perspective and journey.

These are the main persons who I would think this blog of interest. Though I expect it could be useful to other groups. Whoever is reading this post and any others: welcome to my blog and I hope to interact with you.

More posts to come

So, this is really just an introduction to this new series of posts. I wanted to just say hi and detail a little of what I expect to write.

I do have more posts to write. Posts that will start to delve into a little more detail: such at [Route] for custom routes above methods.

So if you think this is something you will enjoy or find useful for you to read; keep popping back for the latest updates.

See you soon in the next and first post of this series.

linkedin facebook pinterest youtube rss twitter instagram facebook-blank rss-blank linkedin-blank pinterest youtube twitter instagram