Make Solution Template Ready - NeoSOFT-Technologies/rest-dot-net-core GitHub Wiki

Description

When you create a new .NET Core project with dotnet new command it is possible to select from a list of predefined templates. This is a very handy feature, but out-of-the-box templates are not really convenient, additional changes are required afterwards to make the project fit for its purpose. In a situation where many projects with similar structures are created, such as many micro-services, a custom template is very helpful. Users can define a custom template and easily create new projects out of it. In order to convert our solution to template, we need to make some changes to our existing solution.

Steps to make solution template-ready

  • Change solution, project and folder names to simpler ones like NeoCA

Solution, Projects and Folders name

  • Hide the .vs folder if not hidden already

.vs hidden

  • Remove user secrets and drive connection strings from appsettings
  • Change names of context classes and connection strings in all the appsettings files

Context classes

"ConnectionStrings": {
    "ApplicationConnectionString": "",
    "IdentityConnectionString": "",
    "HealthCheckConnectionString": ""
  }
  • Remove dbProvider from appsettings
  • Remove dbProvider switch case from HealthCheckExtensions, IdentityExtensions, PersistenceExtensions and DbFixture
  • Add if else in .csproj for database related packages if not present
  1. NeoCA.Api.csproj
		<!--#if (Database == "MSSQL")-->
		<PackageReference Include="AspNetCore.HealthChecks.SqlServer" Version="3.1.1" />
		<PackageReference Include="AspNetCore.HealthChecks.UI.SqlServer.Storage" Version="3.1.2" />
		<!--#endif-->
		<!--#if (Database == "PGSQL")-->
		<PackageReference Include="AspNetCore.HealthChecks.NpgSql" Version="3.1.1" />
		<PackageReference Include="AspNetCore.HealthChecks.UI.PostgreSQL.Storage" Version="3.1.2" />
		<!--#endif-->
		<!--#if (Database == "MySQL")-->
		<PackageReference Include="AspNetCore.HealthChecks.MySql" Version="3.1.1" />
		<PackageReference Include="AspNetCore.HealthChecks.UI.MySql.Storage" Version="3.1.2" />
		<!--#endif-->

		<!--#if (Database == "SQLite")-->
		<PackageReference Include="AspNetCore.HealthChecks.Sqlite" Version="3.1.1" />
		<PackageReference Include="AspNetCore.HealthChecks.UI.SQLite.Storage" Version="3.1.1" />
		<!--#endif-->
  1. NeoCA.Identity.csproj
                <!--#if (Database == "MSSQL")-->
		<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.10" />
		<!--#endif-->
		<!--#if (Database == "PGSQL")-->
		<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="3.1.1" />
		<!--#endif-->
		<!--#if (Database == "MySQL")-->
		<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="3.1.1" />
		<!--#endif-->
		<!--#if (Database == "SQLite")-->
		<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.2" />
		<!--#endif-->
  1. NeoCA.Persistence.csproj
                <!--#if (Database == "MSSQL")-->
		<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.10" />
		<!--#endif-->
		<!--#if (Database == "PGSQL")-->
		<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="3.1.1" />
		<!--#endif-->
		<!--#if (Database == "MySQL")-->
		<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="3.1.1" />
		<!--#endif-->
		<!--#if (Database == "SQLite")-->
		<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.2" />
		<!--#endif-->
  • Add if else in HealthCheckExtensions, IdentityExtensions, PersistenceServiceRegistration and DbFixture for database
  1. HealthCheckExtensions.cs
services.AddHealthChecks()
                //#if (Database == "MSSQL")
                .AddSqlServer(configuration["ConnectionStrings:IdentityConnectionString"], tags: new[] {
                    "db",
                    "all"})
                //#endif
                //#if (Database == "PGSQL")
                .AddNpgSql(configuration["ConnectionStrings:IdentityConnectionString"], tags: new[] {
                    "db",
                    "all"})
                //#endif
                //#if (Database == "MySQL")
                .AddMySql(configuration["ConnectionStrings:IdentityConnectionString"], tags: new[] {
                    "db",
                    "all"})
                //#endif
                //#if (Database == "SQLite")
                .AddSqlite(configuration["ConnectionStrings:IdentityConnectionString"], tags: new[] {
                    "db",
                    "all"})
                //#endif
                .AddUrlGroup(new Uri(configuration["API:WeatertherInfo"]), tags: new[] {
                    "testdemoUrl",
                    "all"
                });
            //#if (Database == "MSSQL")
            services.AddHealthChecksUI(opt =>
            {
                opt.SetEvaluationTimeInSeconds(15); //time in seconds between check
                opt.MaximumHistoryEntriesPerEndpoint(60); //maximum history of checks
                opt.SetApiMaxActiveRequests(1); //api requests concurrency
                opt.AddHealthCheckEndpoint("API", "/healthz"); //map health check api
            }).AddSqlServerStorage(configuration["ConnectionStrings:HealthCheckConnectionString"]);
            //#endif
            //#if (Database == "PGSQL")
            services.AddHealthChecksUI(opt =>
            {
                opt.SetEvaluationTimeInSeconds(15); //time in seconds between check
                opt.MaximumHistoryEntriesPerEndpoint(60); //maximum history of checks
                opt.SetApiMaxActiveRequests(1); //api requests concurrency
                opt.AddHealthCheckEndpoint("API", "/healthz"); //map health check api
            }).AddPostgreSqlStorage(configuration["ConnectionStrings:HealthCheckConnectionString"]);
            //#endif
            //#if (Database == "MySQL")
            services.AddHealthChecksUI(opt =>
            {
                opt.SetEvaluationTimeInSeconds(15); //time in seconds between check
                opt.MaximumHistoryEntriesPerEndpoint(60); //maximum history of checks
                opt.SetApiMaxActiveRequests(1); //api requests concurrency
                opt.AddHealthCheckEndpoint("API", "/healthz"); //map health check api
            }).AddMySqlStorage(configuration["ConnectionStrings:HealthCheckConnectionString"]);
            //#endif
            //#if (Database == "SQLite")
            services.AddHealthChecksUI(opt =>
            {
                opt.SetEvaluationTimeInSeconds(15); //time in seconds between check
                opt.MaximumHistoryEntriesPerEndpoint(60); //maximum history of checks
                opt.SetApiMaxActiveRequests(1); //api requests concurrency
                opt.AddHealthCheckEndpoint("API", "/healthz"); //map health check api
            }).AddSqliteStorage(configuration["ConnectionStrings:HealthCheckConnectionString"]);
            //#endif
            return services;
  1. IdentityExtensions.cs
            //#if (Database == "MSSQL")
            services.AddDbContext<IdentityDbContext>(
                options => options.UseSqlServer(configuration.GetConnectionString("IdentityConnectionString"),
                b => b.MigrationsAssembly(typeof(IdentityDbContext).Assembly.FullName)));
            //#endif
            //#if (Database == "PGSQL")
            services.AddDbContext<IdentityDbContext>(
                options => options.UseNpgsql(configuration.GetConnectionString("IdentityConnectionString"),
                b => b.MigrationsAssembly(typeof(IdentityDbContext).Assembly.FullName)));
            //#endif
            //#if (Database == "MySQL")
            services.AddDbContext<IdentityDbContext>(
                options => options.UseMySql(configuration.GetConnectionString("IdentityConnectionString"),
                b => b.MigrationsAssembly(typeof(IdentityDbContext).Assembly.FullName)));
            //#endif
            //#if (Database == "SQLite")
            services.AddDbContext<IdentityDbContext>(
                options => options.UseSqlite(configuration.GetConnectionString("IdentityConnectionString"),
                b => b.MigrationsAssembly(typeof(IdentityDbContext).Assembly.FullName)));
            //#endif
  1. PersistenceServiceRegistration.cs
            //#if (Database == "MSSQL")
            services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(configuration.GetConnectionString("ApplicationConnectionString")));
            //#endif
            //#if (Database == "PGSQL")
            services.AddDbContext<ApplicationDbContext>(options =>
                options.UseNpgsql(configuration.GetConnectionString("ApplicationConnectionString")));
            //#endif
            //#if (Database == "MySQL")
            services.AddDbContext<ApplicationDbContext>(options =>
                options.UseMySql(configuration.GetConnectionString("ApplicationConnectionString")));
            //#endif
            //#if (Database == "SQLite")
            services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlite(configuration.GetConnectionString("ApplicationConnectionString")));
            //#endif
  1. DbFixture.cs
            //#if (Database == "MSSQL")
            ApplicationConnString = $"Server=localhost,1433;Database={ApplicationDbName};User=sa;Password=2@LaiNw)PDvs^t>L!Ybt]6H^%h3U>M";
            IdentityConnString = $"Server=localhost,1433;Database={IdentityDbName};User=sa;Password=2@LaiNw)PDvs^t>L!Ybt]6H^%h3U>M";
            HealthCheckConnString = $"Server=localhost,1433;Database={HealthCheckDbName};User=sa;Password=2@LaiNw)PDvs^t>L!Ybt]6H^%h3U>M";
            applicationBuilder.UseSqlServer(ApplicationConnString);
            identityBuilder.UseSqlServer(IdentityConnString);
            //#endif
            //#if (Database == "PGSQL")
            ApplicationConnString = $"Server=localhost;Port=5432;Database={ApplicationDbName};User Id=root;Password=root;";
            IdentityConnString = $"Server=localhost;Port=5432;Database={IdentityDbName};User Id=root;Password=root;";
            HealthCheckConnString = $"Server=localhost;Port=5432;Database={HealthCheckDbName};User Id=root;Password=root;";
            applicationBuilder.UseNpgsql(ApplicationConnString);
            identityBuilder.UseNpgsql(IdentityConnString);
            //#endif
            //#if (Database == "MySQL")
            ApplicationConnString = $"Server=localhost;Port=3306;Database={ApplicationDbName};Userid=root;Password=root;";
            IdentityConnString = $"Server=localhost;Port=3306;Database={IdentityDbName};Userid=root;Password=root;";
            HealthCheckConnString = $"Server=localhost;Port=3306;Database={HealthCheckDbName};Userid=root;Password=root;";
            applicationBuilder.UseMySql(ApplicationConnString);
            identityBuilder.UseMySql(IdentityConnString);
            //#endif
            //#if (Database == "SQLite")
			ApplicationConnString = $"Data Source=..//..//..//db//{ApplicationDbName}";
			IdentityConnString = $"Data Source=..//..//..//db//{IdentityDbName}";
			HealthCheckConnString = $"Data Source=..//..//..//db//{HealthCheckDbName}";
            applicationBuilder.UseSqlite(ApplicationConnString);
            identityBuilder.UseSqlite(IdentityConnString);
            //#endif
  • Delete UI Project if present
  • Delete bin, obj folders from all the projects
  • Add if else in docker-compose if not present
  ##if (Database == "MSSQL")
  mssql:
    image: "mcr.microsoft.com/mssql/server"
    ports:
      - "1433:1433"
    environment:
        SA_PASSWORD: "2@LaiNw)PDvs^t>L!Ybt]6H^%h3U>M"
        ACCEPT_EULA: "Y"
    networks:
      - clean-network
  ##endif
  ##if (Database == "PGSQL")
  postgres:
    image: postgres
    environment:
      POSTGRES_USER: root
      POSTGRES_PASSWORD: root
    ports:
      - "5432:5432"
    networks:
      - clean-network
  ##endif
  ##if (Database == "MySQL")
  mysql:
    image: mysql
    environment:
      MYSQL_ROOT_PASSWORD: root
    ports:
      - '3306:3306'
    networks:
      - clean-network
  ##endif
  ##if (Database == "SQLite")
  sqlite:
    image: nouchka/sqlite3:latest
    stdin_open: true
    tty: true
    networks:
      - clean-network
    volumes:
      - ./test/NeoCA.API.IntegrationTests/./db:/root/db/
  ##endif
  • Add if else in sln to add/remove projects if needed. Keep GUIDs from your sln file as they are. Also, add if else wherever you find GUIDs(right hand side ones) for the following projects.
//#if (Communication == "REST")
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "API", "API", "{153F204B-20E6-46A6-BB73-BAD48B580CC4}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NeoCA.Api", "src\API\NeoCA.Api\NeoCA.Api.csproj", "{17B7B793-4B9F-4040-8722-6BF5E8ED6FFB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NeoCA.API.IntegrationTests", "test\NeoCA.API.IntegrationTests\NeoCA.API.IntegrationTests.csproj", "{93AE446B-39C0-49BE-BA11-85293B15C929}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NeoCA.API.UnitTests", "test\NeoCA.API.UnitTests\NeoCA.API.UnitTests.csproj", "{6559162B-B5AE-499B-9850-9D556E7B54C8}"
EndProject
//#endif
//#if (Communication == "gRPC")
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "gRPC", "gRPC", "{DF730924-E57B-4551-8F91-C8AA5A4274A7}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NeoCA.gRPC", "src\gRPC\NeoCA.gRPC\NeoCA.gRPC.csproj", "{C6B70E88-F7FB-47C1-8A78-E5064DEED58F}"
//#endif
  • Change the last step in Integration Testing.yml workflow to following
 - name: Test
       run: dotnet test --filter FullyQualifiedName~IntegrationTests --no-build --verbosity normal

Reference project

https://github.com/NeoSOFT-Technologies/netcore3-1-template/tree/main/Content