Add theme switching to Angular Material

// styles.scss
$dark-theme: mat.define-dark-theme(
    vars.$primaryPalette, 
    vars.$accentPalette, 
    vars.$warnPalette);

$light-theme: mat.define-light-theme(
    vars.$primaryPalette,
    vars.$accentPalette,
    vars.$warnPalette);

.light-theme {
  @include mat.all-component-themes($light-theme);
}

.dark-theme {
  @include mat.all-component-themes($dark-theme);
}

// app.component.ts
constructor(
  private _settingsService: SettingsService,
   @Inject(DOCUMENT) private _document: Document) { }

  ngOnInit(): void {
    this._settingsService.theme$.subscribe(theme => {

      if (theme === Themes.DARK) {
        this._setDarkTheme();
      } else {
        this._setLightTheme();
      }
    });
  }

  private _setDarkTheme(): void {
    this._document.documentElement.classList.add(Themes.DARK);
    this._document.documentElement.classList.remove(Themes.LIGHT);
  }

  private _setLightTheme(): void {
    this._document.documentElement.classList.add(Themes.LIGHT);
    this._document.documentElement.classList.remove(Themes.DARK);
  }

This is just a summary, so some of the more trivial parts are omitted.

ref: https://octoperf.com/blog/2021/01/08/angular-material-multiple-themes

Configuration Extension Examples

AutoMapper Configuration

using AutoMapper;
using Christopher.Snay.Sample.Configuration.AutoMapperProfiles;
using Christopher.Snay.Sample.Configuration.AutoMapperProfiles.Dtos;
using Microsoft.Extensions.DependencyInjection;

namespace Christopher.Snay.Sample.Configuration
{
	public static class AutoMapperConfiguration
	{
		public static void AddAutoMapperConfiguration(this IServiceCollection services)
		{
			services.AddSingleton(new MapperConfiguration(config =>
			{
				config.AddProfile<SampleMapperProfile1>();
				config.AddProfile<SampleMapperProfile2>();		
			}).CreateMapper());
		}
	}
}

CORS Configuration

using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;

namespace Christopher.Snay.Sample.Configuration
{
	public static class CorsConfiguration
	{
		public static IApplicationBuilder UseCorsConfiguration(this IApplicationBuilder app)
		{
			return app.UseCors("CorsPolicy");
		}

		public static IServiceCollection AddCorsConfiguration(this IServiceCollection services)
		{
			var origins = new List<string>
			{
				"http://localhost:4200",
				
				"http://sample-server",
			};

			return services.AddCors(options =>
			{
				options.AddPolicy("CorsPolicy", builder =>
				{
					builder
						.WithOrigins(origins.ToArray())
						.AllowAnyMethod()
						.AllowAnyHeader()
						.AllowCredentials();
				});
			});
		}
	}
}

HttpClientFactory Configuration

using System.Net.Http.Headers;
using System.Net.Mime;
using Christopher.Snay.Sample.Services.RetailerServices;
using Microsoft.Extensions.DependencyInjection;

namespace Christopher.Snay.Sample.Configuration
{
	public static class HttpConfiguration
	{
		public static void AddHttpClientConfiguration(this IServiceCollection services)
		{
			services.AddHttpClient(nameof(ISampleService1), x => x.BaseAddress
				= new Uri("https://www.api.sample.com/search"));

			services.AddHttpClient(nameof(ISampleService2), x => x.BaseAddress
				= new Uri("https://www.api.sample2.com/search"));

			services.AddHttpClient(nameof(ISampleService3), x =>
			{
				x.BaseAddress = new Uri("https://www.api.sample3.com/search");
				x.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(new ProductHeaderValue("Mozilla", "5.0")));
				x.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(MediaTypeNames.Application.Json));
				x.DefaultRequestHeaders.AcceptEncoding.Add(new StringWithQualityHeaderValue("gzip"));
				x.DefaultRequestHeaders.Host = "www.sample.com";
				x.DefaultRequestHeaders.Connection.Add("keep-alive");
				x.DefaultRequestHeaders.Add("cookie", "Bearer ABC123");
			}
		}
	}
}

Scrape rendered HTML with .NET6 C#

using HtmlAgilityPack;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.PhantomJS;

namespace Christopher.Snay.Sample.Services.Scrapers
{
	internal class ChromeScraper : IChromeScraper
	{
		public HtmlDocument ScrapeHtml(Uri url)
		{
			return ScrapeHtml(url.ToString());
		}

		public HtmlDocument ScrapeHtml(string url)
		{
			HtmlDocument doc = new();

			using (PhantomJSDriverService driverService = PhantomJSDriverService.CreateDefaultService())
			{
				driverService.HideCommandPromptWindow = true;
				driverService.LoadImages = false;
				driverService.IgnoreSslErrors = true;

				driverService.Start();

				using IWebDriver driver = new ChromeDriver();

				driver.Navigate().GoToUrl(url);

				Thread.Sleep(3000);

				doc.LoadHtml(driver.PageSource);
			}

			return doc;
		}
	}

	public interface IChromeScraper
	{
		HtmlDocument ScrapeHtml(string url);
		HtmlDocument ScrapeHtml(Uri url);
	}
}

Requirements – The following executables must be in /bin

  • phantonjs.exe
  • chrome.exe
  • chromedriver.exe
  • + the chrome portable binaries directory, currently named \107.0.5304.88
<!-- To copy directly to bin without being placed in a sub-folder -->
<ItemGroup>
	<ContentWithTargetPath Include="Assets\phantomjs.exe">
		<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
		<TargetPath>phantomjs.exe</TargetPath>
	</ContentWithTargetPath>
	<ContentWithTargetPath Include="Assets\chrome.exe">
		<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
		<TargetPath>chrome.exe</TargetPath>
	</ContentWithTargetPath>
	<ContentWithTargetPath Include="Assets\chromedriver.exe">
		<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
		<TargetPath>chromedriver.exe</TargetPath>
	</ContentWithTargetPath>

	<None Include="Assets\phantomjs.exe" />
	<None Include="Assets\chrome.exe" />
	<None Include="Assets\chromedriver.exe" />
</ItemGroup>

I’ve used Chrome portable to avoid having to install Chrome. If Chrome is installed, the chrome.exe steps can probably be skipped.

A Better Generic Repository for Entity Framework

using System.Linq.Expressions;
using Christopher.Snay.Sample.DataAccess.Database;
using Microsoft.EntityFrameworkCore;

namespace Christopher.Snay.Sample.DataAccess.Repositories
{
	internal sealed class Repository<TEntity> : IRepository<TEntity> where TEntity : class
	{
		private readonly SampleDbContext _db;

		public Repository(SampleDbContext db)
		{
			_db = db;
		}

		public TEntity? Get(int id)
		{
			return _db.Set<TEntity>().Find(id);
		}

		public IEnumerable<TEntity> All()
		{
			return _db.Set<TEntity>().AsEnumerable();
		}

		public IEnumerable<TEntity> All<TChild>(
			Expression<Func<TEntity, TChild>> includePredicate)
		{
			return _db.Set<TEntity>().Include(includePredicate).AsEnumerable();
		}

		public IEnumerable<TEntity> All<TChild, TGrandChild>(
			Expression<Func<TEntity, TChild>> includePredicate,
			Expression<Func<TChild, TGrandChild>> thenIncludePredicate)
		{
			return _db.Set<TEntity>().Include(includePredicate)
				.ThenInclude(thenIncludePredicate).AsEnumerable();
		}

		public TEntity? Select(Expression<Func<TEntity, bool>> predicate)
		{
			return _db.Set<TEntity>().FirstOrDefault(predicate);
		}

		public TEntity? Select<TChild>(Expression<Func<TEntity, bool>> predicate,
			Expression<Func<TEntity, TChild>> includePredicate)
		{
			return _db.Set<TEntity>().Include(includePredicate).FirstOrDefault(predicate);
		}

		public TEntity? Select<TChild, TGrandChild>(
			Expression<Func<TEntity, bool>> predicate,
			Expression<Func<TEntity, TChild>> includePredicate,
			Expression<Func<TChild, TGrandChild>> thenIncludePredicate)
		{
			return _db.Set<TEntity>().Include(includePredicate)
				.ThenInclude(thenIncludePredicate).FirstOrDefault(predicate);
		}

		public IEnumerable<TEntity> SelecyMany(Expression<Func<TEntity, bool>> predicate)
		{
			return _db.Set<TEntity>().Where(predicate).AsEnumerable();
		}

		public IEnumerable<TEntity> SelecyMany<TChild>(
			Expression<Func<TEntity, bool>> predicate,
			Expression<Func<TEntity, TChild>> includePredicate)
		{
			return _db.Set<TEntity>().Include(includePredicate).Where(predicate).AsEnumerable();
		}

		public IEnumerable<TEntity> SelecyMany<TChild, TGrandChild>(
			Expression<Func<TEntity, bool>> predicate,
			Expression<Func<TEntity, TChild>> includePredicate,
			Expression<Func<TChild, TGrandChild>> thenIncludePredicate)
		{
			return _db.Set<TEntity>().Include(includePredicate)
				.ThenInclude(thenIncludePredicate).Where(predicate).AsEnumerable();
		}

		public void Insert(TEntity entity)
		{
			_db.Set<TEntity>().Add(entity);
		}

		public void Insert(List<TEntity> entities)
		{
			_db.Set<TEntity>().AddRange(entities);
		}

		public void Update(TEntity entity)
		{
			_db.Set<TEntity>().Update(entity);
		}

		public void Update(List<TEntity> entities)
		{
			_db.Set<TEntity>().UpdateRange(entities);
		}

		public void Delete(TEntity entity)
		{
			_db.Set<TEntity>().Remove(entity);
		}

		public void Delete(List<TEntity> entities)
		{
			_db.Set<TEntity>().RemoveRange(entities);
		}

		public int SaveChanges()
		{
			return _db.SaveChanges();
		}
	}

	public interface IRepository<TEntity> where TEntity : class
	{
		TEntity? Get(int id);

		IEnumerable<TEntity> All();
		IEnumerable<TEntity> All<TChild>(Expression<Func<TEntity, TChild>> includePredicate);
		IEnumerable<TEntity> All<TChild, TGrandChild>(
			Expression<Func<TEntity, TChild>> includePredicate,
			Expression<Func<TChild, TGrandChild>> thenIncludePredicate);

		TEntity? Select(Expression<Func<TEntity, bool>> predicate);
		TEntity? Select<TChild>(
			Expression<Func<TEntity, bool>> predicate,
			Expression<Func<TEntity, TChild>> includePredicate);
		TEntity? Select<TChild, TGrandChild>(
			Expression<Func<TEntity, bool>> predicate,
			Expression<Func<TEntity, TChild>> includePredicate,
			Expression<Func<TChild, TGrandChild>> thenIncludePredicate);

		IEnumerable<TEntity> SelecyMany(Expression<Func<TEntity, bool>> predicate);
		IEnumerable<TEntity> SelecyMany<TChild>(
			Expression<Func<TEntity, bool>> predicate,
			Expression<Func<TEntity, TChild>> includePredicate);
		IEnumerable<TEntity> SelecyMany<TChild, TGrandChild>(
			Expression<Func<TEntity, bool>> predicate,
			Expression<Func<TEntity, TChild>> includePredicate,
			Expression<Func<TChild, TGrandChild>> thenIncludePredicate);

		void Insert(TEntity entity);
		void Insert(List<TEntity> entities);

		void Update(TEntity entity);
		void Update(List<TEntity> entities);

		void Delete(TEntity entity);
		void Delete(List<TEntity> entities);

		int SaveChanges();
	}
}

Serialize object/model to XML

string xml;
using (var writer = new StringWriter())
{
    var serializer = new XmlSerializer(typeof(MyModel));
    serializer.Serialize(writer, myModel);
    xml = writer.ToString();
}

Set default value of a GUID field in Entity Framework Sqlite vs MsSql


protected override void OnModelCreating(ModelBuilder modelBuilder)
{
   modelBuidler.Entity<MyEntity>(entity => 
      if (_settings.UseSql) 
      {
         entity.Property(e => e.Id)
            .HasColumnName("id")
            .HasDefaultValueSql("(newid())");
      }
      else if (_settings.UseSqlite)
      {
         entity.Property(e => e.Id)
            .HasColumnName("id")
            .HasDefaultValue(Guid.NewGuid());
      }
      ....
}

HTTPS redirect on web server

create .htaccess file at public_html root

# WHERE domain = the domain
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{SERVER_PORT} 80
RewriteCond %{HTTP_HOST} ^(www\.)?domain\.com
RewriteRule ^(.*)$ https://www.domain.com [R,L]
RewriteBase /
</IfModule>

Angular 404 or 500 on refresh

create .htaccess file at public_html root

# WHERE directory = the sub-directory AND domain = the domain
RewriteRule ^directory https://www.domain.com [L,R=301]

Prevent IIS From Sleeping

    • Open IIS Manager
    • Application Pools > Right-click the App Pool > Advanced Settings
    • Set the following values:
      • (General) Start Mode = AlwaysRunning
      • (Process Model) Idle Time-out = 0
      • (Process Model) Load User Profile = False
      • (Recycling) Regular Time Interval = 0
      • (Recycling) Specific Times – clear all values from the TimeSpan[] Array
    • Click OK

Convert a C# model into a list of SqlParameters

Assumes properties are decorated with [Column("column_name")] attributes.

public static List<SqlParameter> ToSqlParameters<T>(this T obj)
{
  var result = new List<SqlParameter>();
  var properties = obj.GetType().GetProperties();
    
  properties.ToList().ForEach(propertyInfo => 
  {
    var column = propertyInfo.GetCustomAttribute<ColumnAttribute>();
    var notMapped = propertyInfo.GetCustomAttribute<NotMappedAttribute>();

    // see previous post for this method "IsNullOrDefault"
    if (column != null && notMapped = null && !propertyInfo.IsNullOrDefault(obj)) 
    {
      var fieldValue = propertyInfo.GetValue(obj, default);
      var sqlParam = new SqlParameter($"@{column.Name}", fieldValue);

      result.Add(sqlParam);
    }
  });

  return result;
}

Convert a DataTable into a List of field/value pairs

public static List<Dictionary<string, dynamic>> ToDict(this DataTable dt) 
{
    var tableDict = new List<Dictionary<string, dynamic>>();

    foreach (DataRow row in dt.Rows)
    {
        var rowDict = new Dictionary<string, dynamic>();

        foreach (DataColumn col in dt.Columns) 
        {
            var fieldValue = row[col].Equals(DBNull.Value)
                ? null
                : row[col];

            rowDict.Add(col.ColumnName, fieldValue);
        }

        tableDict.Add(rowDict);
    }

    return tableDict;
}