open changes

This commit is contained in:
Arne Moerman
2024-12-15 19:08:25 +01:00
parent 586b3558ae
commit ab68df2184
145 changed files with 7428 additions and 0 deletions

View File

@@ -0,0 +1,195 @@
using Wishlist.Models;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
namespace Wishlist.Data;
public class ApplicationDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, string,
ApplicationUserClaim, ApplicationUserRole,
ApplicationUserLogin, ApplicationRoleClaim, ApplicationUserToken>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public DbSet<BlogPost> BlogPosts => Set<BlogPost>();
public DbSet<ApplicationUser> ApplicationUsers => Set<ApplicationUser>();
public DbSet<ApplicationRole> ApplicationRoles => Set<ApplicationRole>();
public DbSet<ApplicationUserClaim> ApplicationUserClaims => Set<ApplicationUserClaim>();
public DbSet<ApplicationUserRole> ApplicationUserRoles => Set<ApplicationUserRole>();
public DbSet<ApplicationUserLogin> ApplicationUserLogins => Set<ApplicationUserLogin>();
public DbSet<ApplicationRoleClaim> ApplicationRoleClaims => Set<ApplicationRoleClaim>();
public DbSet<ApplicationUserToken> ApplicationUserTokens => Set<ApplicationUserToken>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
//models
modelBuilder.Entity<ApplicationUser>(b =>
{
// Primary key
b.HasKey(u => u.Id);
// Indexes for "normalized" username and email, to allow efficient lookups
b.HasIndex(u => u.NormalizedUserName).HasName("UserNameIndex").IsUnique();
b.HasIndex(u => u.NormalizedEmail).HasName("EmailIndex");
// Maps to the AspNetUsers table
b.ToTable("AspNetUsers");
// A concurrency token for use with the optimistic concurrency checking
b.Property(u => u.ConcurrencyStamp).IsConcurrencyToken();
// Limit the size of columns to use efficient database types
b.Property(u => u.UserName).HasMaxLength(256);
b.Property(u => u.NormalizedUserName).HasMaxLength(256);
b.Property(u => u.Email).HasMaxLength(256);
b.Property(u => u.NormalizedEmail).HasMaxLength(256);
// The relationships between User and other entity types
// Note that these relationships are configured with no navigation properties
// Each User can have many UserClaims
b.HasMany<ApplicationUserClaim>().WithOne().HasForeignKey(uc => uc.UserId).IsRequired();
// Each User can have many UserLogins
b.HasMany<ApplicationUserLogin>().WithOne().HasForeignKey(ul => ul.UserId).IsRequired();
// Each User can have many UserTokens
b.HasMany<ApplicationUserToken>().WithOne().HasForeignKey(ut => ut.UserId).IsRequired();
// Each User can have many entries in the UserRole join table
b.HasMany<ApplicationUserRole>().WithOne().HasForeignKey(ur => ur.UserId).IsRequired();
});
modelBuilder.Entity<ApplicationUserClaim>(b =>
{
// Primary key
b.HasKey(uc => uc.Id);
// Maps to the AspNetUserClaims table
b.ToTable("AspNetUserClaims");
});
modelBuilder.Entity<ApplicationUserLogin>(b =>
{
// Composite primary key consisting of the LoginProvider and the key to use
// with that provider
b.HasKey(l => new { l.LoginProvider, l.ProviderKey });
// Limit the size of the composite key columns due to common DB restrictions
b.Property(l => l.LoginProvider).HasMaxLength(128);
b.Property(l => l.ProviderKey).HasMaxLength(128);
// Maps to the AspNetUserLogins table
b.ToTable("AspNetUserLogins");
});
modelBuilder.Entity<ApplicationUserToken>(b =>
{
// Composite primary key consisting of the UserId, LoginProvider and Name
b.HasKey(t => new { t.UserId, t.LoginProvider, t.Name });
// Limit the size of the composite key columns due to common DB restrictions
//b.Property(t => t.LoginProvider).HasMaxLength(maxKeyLength);
//b.Property(t => t.Name).HasMaxLength(maxKeyLength);
// Maps to the AspNetUserTokens table
b.ToTable("AspNetUserTokens");
});
modelBuilder.Entity<ApplicationRole>(b =>
{
// Primary key
b.HasKey(r => r.Id);
// Index for "normalized" role name to allow efficient lookups
b.HasIndex(r => r.NormalizedName).HasName("RoleNameIndex").IsUnique();
// Maps to the AspNetRoles table
b.ToTable("AspNetRoles");
// A concurrency token for use with the optimistic concurrency checking
b.Property(r => r.ConcurrencyStamp).IsConcurrencyToken();
// Limit the size of columns to use efficient database types
b.Property(u => u.Id).HasMaxLength(50);
b.Property(u => u.Name).HasMaxLength(256);
b.Property(u => u.NormalizedName).HasMaxLength(256);
// The relationships between Role and other entity types
// Note that these relationships are configured with no navigation properties
// Each Role can have many entries in the UserRole join table
b.HasMany<ApplicationUserRole>().WithOne().HasForeignKey(ur => ur.RoleId).IsRequired();
// Each Role can have many associated RoleClaims
b.HasMany<ApplicationRoleClaim>().WithOne().HasForeignKey(rc => rc.RoleId).IsRequired();
});
modelBuilder.Entity<ApplicationRoleClaim>(b =>
{
// Primary key
b.HasKey(rc => rc.Id);
// Maps to the AspNetRoleClaims table
b.ToTable("AspNetRoleClaims");
});
modelBuilder.Entity<ApplicationUserRole>(b =>
{
// Primary key
b.HasKey(r => new { r.UserId, r.RoleId });
// Maps to the AspNetUserRoles table
b.ToTable("AspNetUserRoles");
});
// navigation props
modelBuilder.Entity<ApplicationUser>(b =>
{
// Each User can have many UserClaims
b.HasMany(e => e.Claims)
.WithOne(e => e.User)
.HasForeignKey(uc => uc.UserId)
.IsRequired();
// Each User can have many UserLogins
b.HasMany(e => e.Logins)
.WithOne(e => e.User)
.HasForeignKey(ul => ul.UserId)
.IsRequired();
// Each User can have many UserTokens
b.HasMany(e => e.Tokens)
.WithOne(e => e.User)
.HasForeignKey(ut => ut.UserId)
.IsRequired();
// Each User can have many entries in the UserRole join table
b.HasMany(e => e.UserRoles)
.WithOne(e => e.User)
.HasForeignKey(ur => ur.UserId)
.IsRequired();
});
modelBuilder.Entity<ApplicationRole>(b =>
{
// Each Role can have many entries in the UserRole join table
b.HasMany(e => e.UserRoles)
.WithOne(e => e.Role)
.HasForeignKey(ur => ur.RoleId)
.IsRequired();
// Each Role can have many associated RoleClaims
b.HasMany(e => e.RoleClaims)
.WithOne(e => e.Role)
.HasForeignKey(rc => rc.RoleId)
.IsRequired();
});
}
}

View File

@@ -0,0 +1,14 @@
using Dapper;
namespace Wishlist.Data.DB
{
public class DatabaseModel : Database<DatabaseModel>
{
public Table<User> Users { get; set; }
public Table<Wishlist> Wishlists { get; set; }
public Table<WishlistItem> WishlistItems { get; set; }
public Table<UserMayViewWishList> UsersMayViewWishList { get; set; }
public Table<UserMarkedAsGettingItem> UsersMarkedAsGettingItem { get; set; }
public Table<Friend> Friends { get; set; }
}
}

View File

@@ -0,0 +1,10 @@
namespace Wishlist.Data.DB
{
public class Friend
{
public int Id { get; set; }
public int UserId { get; set; }
public int FriendId { get; set; }
public FriendStatus Status { get; set; }
}
}

View File

@@ -0,0 +1,94 @@
using FluentMigrator;
namespace Wishlist.Data.DB.Migrations
{
[Migration(2024_06_30__20_15)]
public class _20240630_2015_InitialTables : Migration
{
public override void Up()
{
Create.Table("Users")
.WithColumn("Id").AsInt32().PrimaryKey().Identity()
.WithColumn("Username").AsString(50).NotNullable()
.WithColumn("Name").AsString(255).NotNullable()
.WithColumn("GivenName").AsString(255).NotNullable()
.WithColumn("Surname").AsString(255).NotNullable()
.WithColumn("Email").AsString(255).NotNullable()
.WithColumn("Password").AsString().NotNullable()
.WithColumn("ProjectRolesName").AsString(50).NotNullable()
.WithColumn("LoginType").AsInt32().NotNullable();
Create.Table("Wishlists")
.WithColumn("Id").AsInt32().PrimaryKey().Identity()
.WithColumn("Name").AsString(50).NotNullable()
.WithColumn("Description").AsString().NotNullable()
.WithColumn("UserId").AsInt32().NotNullable()
.WithColumn("IsPublic").AsBoolean().NotNullable();
Create.ForeignKey("FK_Wishlists_Users_UserId")
.FromTable("Wishlists").ForeignColumn("UserId")
.ToTable("Users").PrimaryColumn("Id");
Create.Table("WishlistItems")
.WithColumn("Id").AsInt32().PrimaryKey().Identity()
.WithColumn("WishlistId").AsInt32().NotNullable()
.WithColumn("Name").AsString(50).NotNullable()
.WithColumn("Description").AsString().Nullable()
.WithColumn("Price").AsDecimal().NotNullable()
.WithColumn("Link").AsString().Nullable();
Create.ForeignKey("FK_WishlistItems_Wishlists_WishlistId")
.FromTable("WishlistItems").ForeignColumn("WishlistId")
.ToTable("Wishlists").PrimaryColumn("Id");
Create.Table("UserMayViewWishList")
.WithColumn("Id").AsInt32().PrimaryKey().Identity()
.WithColumn("UserId").AsInt32().NotNullable()
.WithColumn("WishlistId").AsInt32().NotNullable();
Create.ForeignKey("FK_UserMayViewWishList_Users_UserId")
.FromTable("UserMayViewWishList").ForeignColumn("UserId")
.ToTable("Users").PrimaryColumn("Id");
Create.ForeignKey("FK_UserMayViewWishList_Wishlists_WishlistId")
.FromTable("UserMayViewWishList").ForeignColumn("WishlistId")
.ToTable("Wishlists").PrimaryColumn("Id");
Create.Table("UserMarkedAsGettingItem")
.WithColumn("Id").AsInt32().PrimaryKey().Identity()
.WithColumn("UserId").AsInt32().NotNullable()
.WithColumn("WishlistItemId").AsInt32().NotNullable();
Create.ForeignKey("FK_UserMarkedAsGettingItem_Users_UserId")
.FromTable("UserMarkedAsGettingItem").ForeignColumn("UserId")
.ToTable("Users").PrimaryColumn("Id");
Create.ForeignKey("FK_UserMarkedAsGettingItem_WishlistItems_WishlistItemId")
.FromTable("UserMarkedAsGettingItem").ForeignColumn("WishlistItemId")
.ToTable("WishlistItems").PrimaryColumn("Id");
Create.Table("Friends")
.WithColumn("Id").AsInt32().PrimaryKey().Identity()
.WithColumn("UserId").AsInt32().NotNullable()
.WithColumn("FriendUserId").AsInt32().NotNullable();
Create.ForeignKey("FK_Friends_Users_UserId")
.FromTable("Friends").ForeignColumn("UserId")
.ToTable("Users").PrimaryColumn("Id");
Create.ForeignKey("FK_Friends_Users_FriendUserId")
.FromTable("Friends").ForeignColumn("FriendUserId")
.ToTable("Users").PrimaryColumn("Id");
}
public override void Down()
{
Delete.ForeignKey("FK_Friends_Users_FriendUserId");
Delete.ForeignKey("FK_Friends_Users_UserId");
Delete.Table("Friends");
Delete.ForeignKey("FK_UserMarkedAsGettingItem_WishlistItems_WishlistItemId");
Delete.ForeignKey("FK_UserMarkedAsGettingItem_Users_UserId");
Delete.Table("UserMarkedAsGettingItem");
Delete.ForeignKey("FK_UserMayViewWishList_Wishlists_WishlistId");
Delete.ForeignKey("FK_UserMayViewWishList_Users_UserId");
Delete.Table("UserMayViewWishList");
Delete.ForeignKey("FK_WishlistItems_Wishlists_WishlistId");
Delete.Table("WishlistItems");
Delete.ForeignKey("FK_Wishlists_Users_UserId");
Delete.Table("Wishlists");
Delete.Table("Users");
}
}
}

15
wishlist/Data/DB/User.cs Normal file
View File

@@ -0,0 +1,15 @@
namespace Wishlist.Data.DB
{
public class User
{
public int Id { get; set; }
public string? UserName { get; set; }
public string? Name { get; set; }
public string? GivenName { get; set; }
public string? Surname { get; set; }
public string? Password { get; set; }
public string? Email { get; set; }
public string? ProjectRolesName { get; set; }
public LoginType LoginType { get; set; }
}
}

View File

@@ -0,0 +1,11 @@
namespace Wishlist.Data.DB
{
public class UserMarkedAsGettingItem
{
public int Id { get; set; }
public int UserId { get; set; }
public User User { get; set; }
public int WishlistItemId { get; set; }
public WishlistItem WishlistItem { get; set; }
}
}

View File

@@ -0,0 +1,11 @@
namespace Wishlist.Data.DB
{
public class UserMayViewWishList
{
public int Id { get; set; }
public int UserId { get; set; }
public User User { get; set; }
public int WishlistId { get; set; }
public Wishlist Wishlist { get; set; }
}
}

View File

@@ -0,0 +1,12 @@
namespace Wishlist.Data.DB
{
public class Wishlist
{
public int Id { get; set; }
public string? Name { get; set; }
public string? Description { get; set; }
public int UserId { get; set; }
public User User { get; set; }
public bool IsPublic { get; set; }
}
}

View File

@@ -0,0 +1,13 @@
namespace Wishlist.Data.DB
{
public class WishlistItem
{
public int Id { get; set; }
public int WishlistId { get; set; }
public Wishlist Wishlist { get; set; }
public string? Name { get; set; }
public string? Description { get; set; }
public decimal Price { get; set; }
public string? Link { get; set; }
}
}

View File

@@ -0,0 +1,50 @@
using Microsoft.AspNetCore.Identity.UI.Services;
using System.Net.Mail;
using System.Text;
namespace Wishlist.Data;
public class EmailSender : IEmailSender
{
private ILogger<EmailSender> _logger;
public EmailSender(ILogger<EmailSender> logger)
{
_logger = logger;
}
public Task SendEmailAsync(string email, string subject, string htmlMessage)
{
_logger.LogInformation("Email sender started.");
string host = "wishlist.arnemoerman.be";
string from = "wishlist@arnemoerman.be";
int port = 25;
bool enableSsl = false;
MailMessage message = new(from, email, subject, htmlMessage)
{
HeadersEncoding = Encoding.UTF8,
SubjectEncoding = Encoding.UTF8,
BodyEncoding = Encoding.UTF8,
IsBodyHtml = true
};
using (var smtp = new SmtpClient(host))
{
smtp.Port = port;
smtp.EnableSsl = enableSsl;
try
{
smtp.Send(message);
}
catch (Exception ex)
{
_logger.LogCritical(ex, "Sending Email Error");
}
}
return Task.CompletedTask;
}
}

View File

@@ -0,0 +1,10 @@
namespace Wishlist.Data
{
public enum FriendStatus
{
None = 0,
Pending = 1,
Accepted = 2,
Rejected = 3
}
}

View File

@@ -0,0 +1,13 @@
namespace Wishlist.Data
{
[Flags]
public enum LoginType
{
None = 0,
Email = 1,
Google = 2,
Facebook = 4,
Microsoft = 8,
//Twitter = 16
}
}

View File

@@ -0,0 +1,59 @@
namespace Wishlist.Data;
public static class ProjectRoles
{
public static ProjectRole UserRole => new("User", "USER", "User Role");
public static ProjectRole AdminRole => new("Admin", "ADMIN", "Admin Role");
public static ProjectRole[] Roles => [UserRole, AdminRole];
}
public static class ProjectPolicies
{
public static ProjectPolicy BlogPostCreateClaimPolicy => new(nameof(BlogPostCreateClaimPolicy), [ProjectClaims.BlogPostCreate]);
public static ProjectPolicy BlogPostReadClaimPolicy => new(nameof(BlogPostReadClaimPolicy), [ProjectClaims.BlogPostRead]);
public static ProjectPolicy BlogPostUpdateClaimPolicy => new(nameof(BlogPostUpdateClaimPolicy), [ProjectClaims.BlogPostUpdate]);
public static ProjectPolicy BlogPostDeleteClaimPolicy => new(nameof(BlogPostDeleteClaimPolicy), [ProjectClaims.BlogPostDelete]);
public static ProjectPolicy WishlistViewPolicy => new(nameof(WishlistViewPolicy), [ProjectClaims.WishlistView]);
public static ProjectPolicy WishlistCreatePolicy => new(nameof(WishlistCreatePolicy), [ProjectClaims.WishlistCreate]);
public static ProjectPolicy WishlistMarkAsBuyingPolicy => new(nameof(WishlistMarkAsBuyingPolicy), [ProjectClaims.WishlistMarkAsBuying]);
}
public static class ProjectClaims
{
public static ProjectClaim BlogPostCreate => new("BlogPost", "Create");
public static ProjectClaim BlogPostRead => new("BlogPost", "Read");
public static ProjectClaim BlogPostUpdate => new("BlogPost", "Update");
public static ProjectClaim BlogPostDelete => new("BlogPost", "Delete");
public static ProjectClaim WishlistView => new("Wishlist", "View");
public static ProjectClaim WishlistCreate => new("Wishlist", "Create");
public static ProjectClaim WishlistMarkAsBuying => new("Wishlist", "MarkAsBuying");
public static List<ProjectClaim> GetBlogPostClaims => [BlogPostCreate, BlogPostRead, BlogPostUpdate, BlogPostDelete];
public static List<ProjectClaim> GetWishlistClaims => [WishlistView, WishlistCreate, WishlistMarkAsBuying];
public static List<List<ProjectClaim>> GetAllClaims = [GetBlogPostClaims, GetWishlistClaims];
}
public class ProjectPolicy(string name, ProjectClaim[] requiredClaims)
{
public string Name { get; set; } = name;
public ProjectClaim[] RequiredClaims { get; set; } = requiredClaims;
public ProjectRole[] RequiredRoles { get; set; } = [];
}
public class ProjectClaim(string type, string value)
{
public string Type { get; set; } = type;
public string Value { get; set; } = value;
}
public class ProjectRole(string roleName, string normalizedName, string description)
{
public string RoleName { get; set; } = roleName;
public string NormalizedName { get; set; } = normalizedName;
public string Description { get; set; } = description;
}

163
wishlist/Data/SeedData.cs Normal file
View File

@@ -0,0 +1,163 @@
using Wishlist.Models;
using Microsoft.AspNetCore.Identity;
namespace Wishlist.Data;
public class SeedData
{
private readonly ApplicationDbContext _context;
private readonly UserManager<ApplicationUser> _userManager;
private readonly IUserStore<ApplicationUser> _userStore;
private readonly IUserEmailStore<ApplicationUser> _emailStore;
public SeedData(ApplicationDbContext context,
UserManager<ApplicationUser> userManager,
IUserStore<ApplicationUser> userStore)
{
_context = context;
_userManager = userManager;
_userStore = userStore;
_emailStore = GetEmailStore();
}
public async Task CreateInitialData()
{
foreach (var projectRole in ProjectRoles.Roles)
{
var applicationRole = new ApplicationRole { Id = Guid.NewGuid().ToString(), Name = projectRole.RoleName, NormalizedName = projectRole.NormalizedName, Description = projectRole.Description };
_context.Roles.Add(applicationRole);
}
await _context.SaveChangesAsync();
var adminRole = _context.Roles.First(x => x.Name == ProjectRoles.AdminRole.RoleName);
foreach (var claims in ProjectClaims.GetAllClaims)
foreach (var claim in claims)
_context.RoleClaims.Add(new ApplicationRoleClaim { RoleId = adminRole.Id, ClaimType = claim.Type, ClaimValue = claim.Value });
await _context.SaveChangesAsync();
var userRole = _context.Roles.First(x => x.Name == ProjectRoles.UserRole.RoleName);
_context.RoleClaims.Add(new ApplicationRoleClaim { RoleId = userRole.Id, ClaimType = ProjectClaims.BlogPostRead.Type, ClaimValue = ProjectClaims.BlogPostRead.Value });
await _context.SaveChangesAsync();
var admin = Activator.CreateInstance<ApplicationUser>();
admin.Name = "AdminG AdminS";
admin.GivenName = "AdminG";
admin.Surname = "AdminS";
string adminEmail = "admin@admin.com";
string adminPass = "123456";
await _userStore.SetUserNameAsync(admin, adminEmail, CancellationToken.None);
await _emailStore.SetEmailAsync(admin, adminEmail, CancellationToken.None);
var resultAdmin = await _userManager.CreateAsync(admin, adminPass);
if (!resultAdmin.Succeeded) throw new Exception($"Error on Adding Admin user. Errors:{string.Join(",", resultAdmin.Errors)}");
foreach (var role in ProjectRoles.Roles)
await _userManager.AddToRoleAsync(admin, role.RoleName);
await _context.SaveChangesAsync();
var user = Activator.CreateInstance<ApplicationUser>();
user.Name = "UserG UserS";
user.GivenName = "UserG";
user.Surname = "UserS";
string userEmail = "user@user.com";
string userPass = "123456";
await _userStore.SetUserNameAsync(user, userEmail, CancellationToken.None);
await _emailStore.SetEmailAsync(user, userEmail, CancellationToken.None);
var resultUser = await _userManager.CreateAsync(user, userPass);
if (!resultUser.Succeeded) throw new Exception($"Error on Adding User user. Errors:{string.Join(",", resultUser.Errors)}");
await _userManager.AddToRoleAsync(user, ProjectRoles.UserRole.RoleName);
// add BlogPosts
var posts = GetAllBlogPosts();
await _context.BlogPosts.AddRangeAsync(posts);
await _context.SaveChangesAsync();
}
private IUserEmailStore<ApplicationUser> GetEmailStore()
{
if (!_userManager.SupportsUserEmail)
{
throw new NotSupportedException("The default UI requires a user store with email support.");
}
return (IUserEmailStore<ApplicationUser>)_userStore;
}
private static IEnumerable<BlogPost> GetAllBlogPosts()
{
List<BlogPost> posts = [];
for (int i = 0; i < 50; i++)
{
BlogPost post = new() { Id = i + 1, Title = titles[i], Content = contents[i % 10] };
posts.Add(post);
}
return posts;
}
private static readonly string[] titles = {
"Introduction to Object-Oriented Programming",
"Mastering Data Structures and Algorithms",
"Building Web Applications with ASP.NET",
"Creating Mobile Apps with Xamarin",
"Exploring Artificial Intelligence and Machine Learning",
"Understanding Functional Programming Concepts",
"Developing Games with Unity",
"Securing Web Applications from Cyber Attacks",
"Optimizing Code Performance for Better Efficiency",
"Implementing Design Patterns in Software Development",
"Testing and Debugging Strategies for Reliable Software",
"Working with Databases and SQL",
"Building Responsive User Interfaces with HTML and CSS",
"Exploring Cloud Computing and Serverless Architecture",
"Developing Cross-Platform Applications with React Native",
"Introduction to Internet of Things (IoT)",
"Creating Scalable Microservices with Docker and Kubernetes",
"Understanding Network Protocols and TCP/IP",
"Building RESTful APIs with Node.js and Express",
"Exploring Big Data Analytics and Apache Hadoop",
"Mastering Version Control with Git and GitHub",
"Developing Desktop Applications with WPF",
"Securing Mobile Applications from Malicious Attacks",
"Optimizing Database Performance with Indexing",
"Implementing Continuous Integration and Deployment",
"Testing Mobile Apps on Different Platforms",
"Working with NoSQL Databases like MongoDB",
"Building Progressive Web Apps with React",
"Exploring Quantum Computing and Quantum Algorithms",
"Introduction to Cybersecurity and Ethical Hacking",
"Creating Chatbots with Natural Language Processing",
"Understanding Software Development Life Cycle",
"Developing Augmented Reality (AR) Applications",
"Securing Web APIs with OAuth and JWT",
"Optimizing Front-End Performance for Better User Experience",
"Implementing Machine Learning Models with TensorFlow",
"Testing Web Applications for Cross-Browser Compatibility",
"Working with Blockchain Technology and Smart Contracts",
"Building Real-Time Applications with SignalR",
"Exploring Cryptography and Encryption Techniques",
"Introduction to Agile Software Development",
"Creating Voice User Interfaces with Amazon Alexa",
"Understanding Web Accessibility and Inclusive Design",
"Developing Natural Language Processing Applications",
"Securing Cloud Infrastructure and Services",
"Optimizing Backend Performance for Scalability",
"Implementing Continuous Monitoring and Alerting",
"Testing APIs with Postman and Swagger",
"Working with Data Visualization Libraries like D3.js",
"Building E-commerce Applications with Shopify",
"Exploring Robotic Process Automation (RPA)",
"Introduction to DevOps and CI/CD Pipelines"
};
private static readonly string[] contents = new string[]
{
"Lorem ipsum dolor sit amet, consectetur t.",
"Sed ut perspiciatis unde omnis iste natuccusantium doloremque laudantium.",
"Nemo enim ipsam voluptatem quia voluptas aut fugit.",
"Quis autem vel eum iure reprehenderit quesse quam nihil molestiae consequatur.",
"At vero eos et accusamus et iusto odio d.",
"Similique sunt in culpa qui officia de.",
"Et harum quidem rerum facilis est et expio.",
"Nam libero tempore, cum soluta nobis est.",
"Omnis voluptas assumenda est, omnis dolo",
"Temporibus autem quibusdam et aut offic"
};
}