(1) CSharp Fundamentals
Topics
- Variables and data types
- Control structures:
if
,switch
,for
,while
- Methods and functions
- Classes and objects
- Abstract classes
- Records
- Interfaces
- Generic
- Anonymous Types
Resources:
(2) Object-Oriented Programming (OOP) in C#
Topics
- Encapsulation, inheritance, polymorphism and abstraction
- SOLID principles
- Design patterns (Factory, Singleton, Repository)
- Delegates and events
- Custom exceptions
- Nullable reference types
- Collections and LINQ
Resources
- Paper “Design Principles and Design Patterns” by Robert C. Martin
- Book: “Design Patterns in C#” by Vaskaran Sarcar
- Design Patterns in C# – Refactoring.guru
- C# Clean Code: SOLID Principles – Dev.to
- Object-oriented programming
- Apress/design-patterns-csharp-2e – Github.com
(3) Dotnet and ASP Dotnet Core
Topics
- Lifecycle of a .NET application
- Project structure (
Program.cs
,Startup.cs
, orbuilder
) - Routing and controllers (
Controller
,Route
,HttpGet/Post/etc.
) - Dependency Injection
- Middleware
Resources
(4) Data Access with Entity Framework Core
Topics
- Defining models and relationships
- DbContext and migrations
- CRUD with LINQ and EF
- Fluent API vs Data Annotations
Resources
(5) REST APIs with ASP.NET Core
Topics
- Creating endpoints using
ApiController
- Model validation (
DataAnnotations
) - Filters (
ActionFilter
,ExceptionFilter
) - Swagger (API documentation)
Resources
(6) Security and Authentication
Topics
- JWT Authentication and Authorization
- Identity Framework
- Policies and roles
Resources
- Secure ASP.NET Core with JWT
- Course (YouTube): JWT Authentication in ASP.NET Core
(7) Testing
Topics
- xUnit, NUnit
- Unit testing and integration testing
- Mocking with Moq
Resources
Other Resources
Resources
Getting Started
What’s CSharp?
C# is a high-level, general-purpose programming language developed by Microsoft as part of the .NET framework. It’s an object-oriented language, meaning it uses objects to structure code and data, and is used to build a variety of applications. C# is known for its ease of learning, strong community support, and ability to produce highly performant code.
.NET SDK Instalation
The easiest way to have the .NET SDK installed in your personal computer is to download Visual Studio. You can also use other IDE, but you will need to install the SDK manually.
Hello World
A sample C# program is show here.
// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");
Run the program as below:
$ dotnet run Program.cs
Variables
Normal Declaration:
string firstName = "Someone";
char userOption = 'A';
int gameScore = 123;
float percentage = 12.10;
double portion = 4.556
decimal particlesPerMillion = 123.4567;
bool processedCustomer = true;
Implicitly Typed:
var message = "Hello world!";
Constant
const int ConstNum = 5;
Data Types
There are two different data types in C#:
- Value Types: Directly store the data. Once you assign a value, it holds that data
int
,char
,float
are just a few examples.
- Reference Types: Store a memory address. They point to the address of the value.
string
,class
,array
are commonly used.
Integer
Math operations:
int sum = 7 + 5;
int difference = 7 - 5;
int product = 7 * 5;
int quotient = 7 / 5;
int modulus = 7 % 5;
Console.WriteLine("Sum: " + sum); // Sum: 12
Console.WriteLine("Difference: " + difference); // Difference: 2
Console.WriteLine("Product: " + product); // Product: 35
Console.WriteLine("Quotient: " + quotient); // Quotient: 1
Console.WriteLine($"Modulus: {7 % 5}"); // Modulus: 2
Order of operations
In math, PEMDAS is an acronym that helps students remember the order of operations. The order is:
- Parentheses (whatever is inside the parenthesis is performed first)
- Exponents
- Multiplication and Division (from left to right)
- Addition and Subtraction (from left to right)
Increment and decrement
int value = 1;
value = value + 1;
Console.WriteLine("First increment: " + value); // First increment: 2
value += 1;
Console.WriteLine("Second increment: " + value); // Second increment: 3
value++;
Console.WriteLine("Third increment: " + value); // Third increment: 4
value = value - 1;
Console.WriteLine("First decrement: " + value); // First decrement: 3
value -= 1;
Console.WriteLine("Second decrement: " + value); // Second decrement: 2
value--;
Console.WriteLine("Third decrement: " + value); // Third decrement: 1
String
Combine String using character escape sequences:
// Character escape sequences
Console.WriteLine("Hello\nWorld!");
Console.WriteLine("Hello\tWorld!");
Console.WriteLine("Hello \"World\"!"); // Hello "World"!
Console.WriteLine("c:\\source\\repos"); // c:\source\repos
// Verbatim string literal
Console.WriteLine(@" c:\source\repos
(this is where your code goes)");
// c:\source\repos
// (this is where your code goes)
// Unicode escape character
Console.WriteLine("\u3053\u3093\u306B\u3061\u306F World!"); // こんにちは World!
Combine String using string concatenation:
string firstName = "Bob";
string greeting = "Hello";
string message = greeting + " " + firstName + "!";
Console.WriteLine(message); // Hello Bob!
Combine String using string interpolation:
string firstName = "Bob";
string greeting = "Hello";
Console.WriteLine($"{greeting} {firstName}!"); // Hello Bob!
// Combine verbatim literals and string interpolation
string projectName = "First-Project";
Console.WriteLine($@"C:\Output\{projectName}\Data"); // C:\Output\First-Project\Data
Array
Declaration:
string[] customerIds = new string[3];
string[] customerIds = [ "A123", "B456", "C789" ]; // Introduced in C#12
string[] customerIds = { "A123", "B456", "C789" }; // Older version
Assigning values:
string[] customerIds = new string[3];
customerIds[0] = "C123";
customerIds[1] = "C456";
customerIds[2] = "C789";
Size of the array:
string[] customerIds = [ "A123", "B456", "C789" ];
Console.WriteLine($"There are {customerIds.Length} customers.");
Tuples
Declaration:
var pt = (X: 1, Y: 2);
var slope = (double)pt.Y / (double)pt.X;
Console.WriteLine($"A line from the origin to the point {pt} has a slope of {slope}."); // A line from the origin to the point (1, 2) has a slope of 2.
Methods
No params:
Console.WriteLine("Generating random numbers:");
DisplayRandomNumbers(); // 17 29 46 36 3
void DisplayRandomNumbers()
{
Random random = new Random();
for (int i = 0; i < 5; i++)
{
Console.Write($"{random.Next(1, 100)} ");
}
Console.WriteLine();
}
Using parameters:
CountTo(5);
void CountTo(int max)
{
for (int i = 0; i < max; i++)
{
Console.Write($"{i}, "); // 0, 1, 2, 3, 4
}
}
Optional parameters:
CountTo();
void CountTo(int max = 10)
{
for (int i = 0; i < max; i++)
{
Console.Write($"{i}, "); // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
}
}
Returning values:
int sum = SumTo(5);
Console.Write($"sum: {sum}"); // sum: 15
int CountTo(int max)
{
int result = 0;
for (int i = 1; i <= max; i++)
{
result += i;
}
return result;
}
Stateless
The following code is stateless because it doesn’t require to store any state to work, you just call the static method WriteLine from Console class.
Console.WriteLine("Hello World!");
Stateful
The following code is stateful because it is required to store previous information of the state to calculate next random value.
Random dice = new Random();
int roll = dice.Next(1, 7);
Condition
if-else operator
string color = "black";
if (color == "black")
{
Console.WriteLine("It's black.");
}
else if (color == "white")
{
Console.WriteLine("It's white.");
}
else {
Console.WriteLine("It's other color.");
}
Conditional Operator
int saleAmount = 1001;
int discount = saleAmount > 1000 ? 100 : 50;
Console.WriteLine($"Discount: {discount}");
Scope
bool flag = true;
if (flag)
{
int value = 10;
Console.WriteLine($"Inside the code block: {value}"); // Prints value.
}
Console.WriteLine($"Outside the code block: {value}"); // Gives error because value is declared inside the if code block.
Switch
string fruit = "apple";
switch (fruit)
{
case "apple":
Console.WriteLine($"App will display information for apple.");
break;
case "banana":
Console.WriteLine($"App will display information for banana.");
break;
case "cherry":
Console.WriteLine($"App will display information for cherry.");
break;
default:
Console.WriteLine($"App will not display information about any fruit.");
break;
}
Loop
Foreach
string[] names = { "Rowena", "Robin", "Bao" };
foreach (string name in names)
{
Console.WriteLine(name); // "Rowena", "Robin", "Bao"
}
For
for (int i = 0; i < 10; i++)
{
if (i > 5) {
break;
}
Console.WriteLine(i); // 1, 2, 3, 4, 5
}
Do-While
Random random = new Random();
int current = 0;
do
{
current = random.Next(1, 11);
if (current >= 8) {
continue;
}
Console.WriteLine(current);
} while (current != 7);
While
Random random = new Random();
int current = random.Next(1, 11);
while (current >= 3)
{
Console.WriteLine(current);
current = random.Next(1, 11);
}
Console.WriteLine($"Last number: {current}");
Casting
Casting truncates the value.
decimal myDecimal = 3.14m;
Console.WriteLine($"decimal: {myDecimal}"); // decimal: 3.14
int myInt = (int)myDecimal;
Console.WriteLine($"int: {myInt}"); // int: 3
To String
int first = 5;
int second = 7;
string message = first.ToString() + second.ToString();
Console.WriteLine(message); // 57
Parse
string first = "5";
string second = "7";
int sum = int.Parse(first) + int.Parse(second);
Console.WriteLine(sum); // 12
Parse
string value = "102";
int result = 0;
if (int.TryParse(value, out result))
{
Console.WriteLine($"Measurement: {result}");
}
else
{
Console.WriteLine("Unable to report the measurement.");
}
Convert
Convert rounds the value.
string value1 = "5";
string value2 = "7";
int result = Convert.ToInt32(value1) * Convert.ToInt32(value2);
Console.WriteLine(result); // 35
Sort
string[] pallets = [ "B14", "A11", "B12", "A13" ];
Console.WriteLine("Sorted...");
Array.Sort(pallets);
foreach (var pallet in pallets)
{
Console.WriteLine($"-- {pallet}"); // A11, A13, B12, B14
}
Reverse
string[] pallets = [ "A11", "A13", "B12", "B14" ];
Console.WriteLine("Reversed...");
Array.Reverse(pallets);
foreach (var pallet in pallets)
{
Console.WriteLine($"-- {pallet}"); // B14, B12, A13, A11
}
Clear
string[] pallets = [ "B14", "A11", "B12", "A13" ];
Console.WriteLine("");
Array.Clear(pallets, 0, 2);
Console.WriteLine($"Clearing 2 ... count: {pallets.Length}");
foreach (var pallet in pallets)
{
Console.WriteLine($"-- {pallet}"); // null, null, B12, A13
}
Resize
string[] pallets = ["B14", "A11", "B12", "A13" ];
Console.WriteLine("");
Array.Resize(ref pallets, 6);
Console.WriteLine($"Resizing 6 ... count: {pallets.Length}");
pallets[4] = "C01";
pallets[5] = "C02";
foreach (var pallet in pallets)
{
Console.WriteLine($"-- {pallet}"); // B14, A11, B12, A13, C01, C02
}
Join
char[] valueArray = ['a', 'b', 'c']
Array.Reverse(valueArray);
// string result = new string(valueArray);
string result = String.Join("|", valueArray); // a|b|c
Console.WriteLine(result);
Split
string result = "123|456|789";
string[] items = result.Split('|');
foreach (string item in items)
{
Console.WriteLine(item); // 123, 456, 789
}
Composite Formatting
string first = "Hello";
string second = "World";
string result = string.Format("{0} {1}!", first, second);
Console.WriteLine(result); // Hello World!
Formatting Currency
decimal price = 123.45m;
int discount = 50;
Console.WriteLine($"Price: {price:C} (Save {discount:C})"); // Price: $123.45 (Save $50.00)
Formatting Numbers
decimal measurement = 123456.78912m;
Console.WriteLine($"Measurement: {measurement:N} units"); // Measurement: 123,456.79 units
Console.WriteLine($"Measurement: {measurement:N4} units"); // Measurement: 123,456.7891 units
Formatting Percentage
decimal tax = .36785m;
Console.WriteLine($"Tax rate: {tax:P2}"); // Tax rate: 36.79%
Padding
string input = "Pad this";
Console.WriteLine(input.PadLeft(12)); // " Pad this"
Console.WriteLine(input.PadLeft(12, '*')); // "****Pad this"
Console.WriteLine(input.PadRight(12, '*')); // "Pad this****"
IndexOf
string message = "Find what is (inside the parentheses)";
int openingPosition = message.IndexOf('(');
int closingPosition = message.IndexOf(')');
Console.WriteLine(openingPosition); // 13
Console.WriteLine(closingPosition); // 36
Substring
string message = "What is the value <span>between the tags</span>?";
const string openSpan = "<span>";
const string closeSpan = "</span>";
int openingPosition = message.IndexOf(openSpan);
int closingPosition = message.IndexOf(closeSpan);
openingPosition += openSpan.Length;
int length = closingPosition - openingPosition;
Console.WriteLine(message.Substring(openingPosition, length)); // between the tags
LastIndexOf
string message = "hello there!";
int first_h = message.IndexOf('h');
int last_h = message.LastIndexOf('h');
Console.WriteLine($"For the message: '{message}', the first 'h' is at position {first_h} and the last 'h' is at position {last_h}.");
// For the message: 'hello there!', the first 'h' is at position 0 and the last 'h' is at position 7.
IndexOfAny
string message = "Hello, world!";
char[] charsToFind = { 'a', 'e', 'i' };
int index = message.IndexOfAny(charsToFind);
Console.WriteLine($"Found '{message[index]}' in '{message}' at index: {index}."); // Found 'e' in 'Hello, world!' at index: 1.
Remove
string data = "12345John Smith 5000 3 ";
string updatedData = data.Remove(5, 20);
Console.WriteLine(updatedData); // 123455000 3
Replace
string message = "This--is--ex-amp-le--da-ta";
message = message.Replace("--", " ");
message = message.Replace("-", "");
Console.WriteLine(message); // This is example data
Trim
string greeting = " Hello World! ";
Console.WriteLine($"[{greeting}]"); // " Hello World! "
string trimmedGreeting = greeting.TrimStart();
Console.WriteLine($"[{trimmedGreeting}]"); // "Hello World! "
trimmedGreeting = greeting.TrimEnd();
Console.WriteLine($"[{trimmedGreeting}]"); // " Hello World!"
trimmedGreeting = greeting.Trim();
Console.WriteLine($"[{trimmedGreeting}]"); // "Hello World!"
Contains
string songLyrics = "You say goodbye, and I say hello";
Console.WriteLine(songLyrics.Contains("goodbye")); // True
Console.WriteLine(songLyrics.Contains("greetings")); // False
Declaring Classes
// A type that is defined as a class is a reference type.
//[access modifier] - [class] - [identifier]
public class Customer
{
// Fields, properties, methods and events go here...
}
Creating Objects
Customer object1 = new Customer(); // object1 is a reference to an allocated space that will know where the object exists.
Customer object2; // Reference to null
Customer object3 = new Customer();
Customer object4 = object3; // object4 has the same reference as object3. If any of both instances changes, the other one does as well. Not recommended to do that.
Constructors and initialization
Accept default values
Every .NET type has a default value. Typically, that value is 0 for number types, and null for all reference types. You can rely on that default value when it’s reasonable in your app.
Field initializers
public class Container
{
private int _capacity;
public Container(int capacity) => _capacity = capacity;
}
Primary Constructor
public class Container(int capacity)
{
private int _capacity = capacity;
}
Object initializer
public class Person
{
public required string LastName { get; set; }
public required string FirstName { get; set; }
}
var p1 = new Person(); // Error! Required properties not set
var p2 = new Person() { FirstName = "Grace", LastName = "Hopper" };