ASP.NET Core Web API .NET 8
Source project URL: teddysmithdev/FinShark
API URL: http://localhost:5017/swagger/index.html
Credentials for API: { "username": "admin", "email": "[email protected]", "password": "4dm|N|5tr4t0r" }
- Install Visual Studio Code
- Install Visual Studio (For .NET Core 8 framework only)
- Install SQL Server + SSMS (Replaced with PostgreSql)
brew install postgresql@15
Extensions for Visual Studio Code:
- C#
- C# Dev Kit
- .NET Extension Pack
- .NET Install Tool
- Nuget Gallery
- Prettier
- C# Extension Pack By JosKreativ
dotnet new webapi -o api
Running api
project:
dotnet watch run
Models & One-To-Many
Tools to install:
- Microsoft.EntityFrameworkCore.SqlServer
- Microsoft.EntityFrameworkCore.Tools
- Microsoft.EntityFrameworkCore.Design
- Npgsql.EntityFrameworkCore.PostgreSQL (Added as an extra due to replacement of MS SQL Server)
- Npgsql (Added as an extra due to replacement of MS SQL Server)
Database creation
Database migrations
dotnet ef migrations add init
dotnet ef database update
Initial data on Stock table
INSERT INTO finshark.public."Stocks"("Symbol", "CompanyName", "Purchase", "LastDiv", "Industry", "MarketCap")
VALUES ('TSLA', 'Tesla', 100.00, 2.00, 'Automotive', 547100000000)
, ('MSFT', 'Microsoft', 100.00, 1.20, 'Technology', 3179710000000)
, ('SEB', 'Skandinaviska Enskilda banken', 56.00, 2.10, 'Finance', 316960000000)
, ('Swe', 'Swedbank', 44.00, 1.10, 'Finance', 248020000000)
, ('PLTR', 'Plantir', 23.00, 0, 'Technology', 123456)
GET /api/stocks/ GET /api/stocks/{id}
- Define methods as async
- Wrap return type by Task<>
- Make database calls await and use async methods
For some reasons, EF remove operation is not asynchronious.
Replacing repetetives (abstraction): _context.Stock.FirstOrDefault() -> repo.FindStock()
Initial comment seeding
INSERT INTO public."Comments"("Title", "Content", "CreatedOn", "StockId")
VALUES ('Test comment', 'This is my test comment content', '2024-04-13 22:11:55', 2)
, ('Another test comment', 'This is my another test comment content', '2024-04-13 22:14:00', 2)
, ('Another test comment', 'This is my another test comment content', '2024-04-13 22:14:00', 2)
, ('Test 2', 'Test comment content', '2024-04-10 14:33:21', 1)
, ('Test 3', 'Test 3 comment content', '2024-04-11 11:00:23', 1)
, ('Test 4', 'Test 4 comment content', '2024-04-01 10:03:55', 3)
, ('Test 5', 'Test 5 comment content', '2024-04-11 11:44:22', 3)
, ('Test 6', 'Test 6 comment content', '2024-04-11 12:05:45', 3);
Installing additional packages:
- Newtonsoft.Json
- Microsoft.AspNetCore.Mvc.NewtonsoftJson (For preventing loop when serializing objects)
.AsQueryable() - deffers SQL rendering and allows to do the filtering .ToList() - Renders SQL
Implementation using LINQ:
- .Skip()
- .Take()
var skipNumber = (query.PageNumber - 1) * query.PageSize;
return await stocks.Skip(skipNumber).Take(query.PageSize).ToListAsync();
Installing libraries:
- Microsoft.Extensions.Identity.Core
- Microsoft.AspNetCore.Identity.EntityFrameworkCore
- Microsoft.AspNetCore.Authentication.JwtBearer
Creating user class:
public class AppUser : IdentityUser
{
}
Registering user in DBContext (replace DbContext with IdentityDbContext):
public class ApplicationDatabaseContext : IdentityDbContext<AppUser>
{
}
Registering in Program.cs
builder.Services.AddIdentity<AppUser, IdentityRole>(options=>
{
options.Password.RequireDigit = true;
options.Password.RequireLowercase = true;
options.Password.RequireUppercase = true;
options.Password.RequiredLength = 12;
options.Password.RequireNonAlphanumeric = true;
})
.AddEntityFrameworkStores<ApplicationDatabaseContext>();;
Adding authentication service and schemes
builder.Services.AddAuthentication(options=>
{
options.DefaultAuthenticateScheme =
options.DefaultChallengeScheme =
options.DefaultForbidScheme =
options.DefaultScheme =
options.DefaultSignInScheme =
options.DefaultSignOutScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = builder.Configuration["JWT:Issuer"],
ValidateAudience = true,
ValidAudience = builder.Configuration["JWT:Audience"],
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(builder.Configuration["JWT:SigningKey"])
),
};
});
Adding EF Migrations
dotnet ef migrations add Identity
dotnet ef database update
Adding EF migration to add users and roles
dotnet ef migrations add SeedRole
dotnet ef database update
Claims vs Roles
- Roles are more generic and broad (old school)
- Claims don't require DB and verify fexible (new school)
MS has moved away from Roles
For login two things are used:
- User manager (to find the user)
- Signing manager (to validate password)
Swagger JWT support:
builder.Services.AddSwaggerGen(option =>
{
option.SwaggerDoc("v1", new OpenApiInfo { Title = "Demo API", Version = "v1" });
option.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
In = ParameterLocation.Header,
Description = "Please enter a valid token",
Name = "Authorization",
Type = SecuritySchemeType.Http,
BearerFormat = "JWT",
Scheme = "Bearer"
});
option.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type=ReferenceType.SecurityScheme,
Id="Bearer"
}
},
new string[]{}
}
});
});
Many-to-Many configuration on EF:
builder.Entity<Portfolio>()
.HasOne(u=>u.AppUser)
.WithMany(u=>u.Portfolios)
.HasForeignKey(p=>p.AppUserId);
builder.Entity<Portfolio>()
.HasOne(u=>u.Stock)
.WithMany(u=>u.Portfolios)
.HasForeignKey(p=>p.StockId);
When applying changes to database:
- Need to delete the migrations
- Need to drop the database
- Add EF migration
dotnet ef migrations add PortfolioManyToMany
- Apply changes to database
dotnet ef database update
Model in the model
Adding EF migration
dotnet ef migrations add CommentOneToOne
.Include() .ThenInclude() - for nested includes
nmp install create-react-app
npx create-react-app frontend --template typescript
Snippets used (Extensions): ES7 + React/Redux/React-Native snippets
- Intrinsic elements (e.x.
<div></div>
) -> React.createElement("div") - Value based elements (
<MyComponent></MyComponent>
) -> React.createElement() Running React app:npm start
- function type -> React.FC
- return type -> JSX.Element
Use state provides a getter and setter for you
const[index, setIndex] = useState()
- Using external API: Financial Modeling API
npm install axios --save
npm install --save-dev @types/axios
npm install dotenv --save
Creating seperate API file Creating global type file
Lower level components are dumb components Higher level components are smart
Good architecture: Event goes up - to smarter ones Data flows down to dumb components
Search - Dump component
Restart was required to fetch variables from .env
Iteration .map
npm install uuid
npm install -save-dev @types/uuid
Recreating arrays instead of modifying existing ones!!
const updatedPortfolio = [...portfolioValues, e.target[0].value];
setPortfolioValues(updatedPortfolio);
For deleting items from array we need to have a new instance of array as well. For that filter method can be used which returns a new instance of the array:
const onPortfolioDelete = (e: any) => {
e.preventDefault();
const removed = portfolioValues.filter((value)=>{
return value !== e.target[0].value;
});
setPortfolioValues(removed);
}
A utility-first CSS framework packed with classes like flex, pt-4, text-center and rotate-90 that can be composed to build any design, directly in your markup.
npm install -D tailwindcss
npx taiwindcss init
npm install --save react-router
npm install --save react-router-dom
npm install --save @types/react-router-dom
npm install --save @types/react-router
Used for things outside of react app (e.x. external API)
- ReferentialEquality Mode
useEffect(()=>{
}, [object])
npm install react-icons
TTM - Trailing 12-month revenue Ticker data passes through: Page->Dasboard->Profile/TTM
npm install react-spinners
Skipped
Keep code out of context using services Shareable state we put in Context
npm install react-toastify
Context - global, shareable. Purpose is to share state downstream to components Regular component (Custom hook). Self contained object. Not aimed to be shared
npm install react-hook-form yup @hookform/resolvers
<ProtectedRoute><StockProfile></ProtectedRoute>