Last active
February 11, 2020 21:04
-
-
Save WillSams/606939daa0b0701af0d10da51da1f4fd to your computer and use it in GitHub Desktop.
Simple VS Code C# Solution Generation Example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
echo "===============================================================" | |
echo " This script can be executed under Linux (Debian based) or " | |
echo " Windows (using MSys2/Git Bash) " | |
echo " Also, Sqlite3 will be installed if already not availabe. " | |
echo | |
echo " To ensure this script works, run this command on this file: " | |
echo " chmod +x generate.sh " | |
echo "===============================================================" | |
echo | |
set -o nounset # Treat unset variables are errors | |
SCRIPTVERSION="2020.02.11" | |
SCRIPTNAME="generate.sh" | |
SCRIPTFULLNAME="$0" | |
SOLUTION="MySolution" | |
FILEPATH="$HOME/Projects/$SOLUTION" | |
TYPE="console" | |
FRAMEWORK="netcoreapp3.1" | |
echoerror() { printf "\033[1;31m * ERROR\033[0m: %s\\n" "$@" 1>&2; } | |
usage() { | |
cat << EOT | |
Usage : ${SCRIPTNAME} [options] | |
Options: | |
-h Display this message | |
-v Display script version | |
-s Project name you want to create. Default is MySolution. | |
-f Physical file path of the solution. Default is $HOME\Projects\$SOLUTION | |
-t Type of application. Default is console, see 'dotnet new' for other types. | |
EOT | |
} # ---------- end of function usage ---------- | |
while getopts :hv:s:f:t: opt | |
do | |
case "${opt}" in | |
h ) usage; exit 0 ;; | |
v ) echo "$0 -- Version $SCRIPTVERSION"; exit 0 ;; | |
s ) SOLUTION="$OPTARG" ;; | |
f ) FILEPATH="$OPTARG" ;; | |
t ) TYPE="$OPTARG" ;; | |
\?) echo | |
echoerror "Option does not exist : $OPTARG" | |
usage | |
exit 1 | |
;; | |
esac # --- end of case --- | |
done | |
shift $((OPTIND-1)) | |
echo "Begin. Solution is '$SOLUTION', the type is '$TYPE', and the physical file path is set to '$FILEPATH'." | |
echo | |
read -n 1 -s -r -p "Press any key to continue or Ctrl-C to quit now." | |
if [ -d $FILEPATH ]; then | |
echo "Deleting existing $FILEPATH path." | |
rm -rf $FILEPATH | |
fi | |
mkdir -p $FILEPATH/bin $FILEPATH/docs $FILEPATH/src $FILEPATH/tools | |
mkdir -p $FILEPATH/resources/database $FILEPATH/resources/input $FILEPATH/resources/logs $FILEPATH/resources/sql | |
cd $FILEPATH | |
dotnet new sln | |
INFRASTRUCTURE=$SOLUTION.Infrastructure | |
MODELS=$SOLUTION.Models | |
DATA=$SOLUTION.Data | |
DI=$SOLUTION.DI | |
HANDLERS=$SOLUTION.DomainEventHandlers | |
# Create database #################################################################################### | |
echo "BEGIN TRANSACTION; | |
DROP TABLE IF EXISTS 'Todos'; | |
CREATE TABLE IF NOT EXISTS 'Todos' ( | |
'Id' TEXT NOT NULL, | |
'LastUpdated' TEXT NOT NULL, | |
'Title' TEXT, | |
'Description' REAL NOT NULL, | |
'IsCompleted' TEXT NOT NULL CHECK (IsCompleted IN ('TRUE','FALSE')), | |
CONSTRAINT 'PK_Todos' PRIMARY KEY('Id') | |
); | |
INSERT INTO 'Todos' ('Id','LastUpdated','Title','Description','IsCompleted') | |
VALUES ('9d6611de-1539-45cd-b7a2-9881ba4df5c0','2020-01-21 10:07:51.0496404','Tutor Fernando Powell','Improve on current grade, 92.0','FALSE'), | |
('93c68f13-db68-4a12-9910-6fbdceb6ae6a','2020-01-21 10:07:51.1076361','Tutor William Norton','Improve on current grade, 70.0','FALSE'), | |
('989a0f16-4fcb-49d9-8719-9b9106cc6a15','2020-01-21 10:07:51.1113244','Tutor Dalton Boone','Improve on current grade, 88.0','FALSE'), | |
('f43f9657-5681-4c43-be09-e37f72b4c24a','2020-01-21 10:07:51.1140553','Tutor Edwardo French','Improve on current grade, 52.0','FALSE'), | |
('537b3dc0-3423-4841-8116-a329a223e85d','2020-01-21 10:07:51.1170017','Tutor Antoinette Santiago','Improve on current grade, 82.0','FALSE'), | |
('bb9db179-37e9-4f5c-a555-ed3645b71698','2020-01-21 10:07:51.1270069','Tutor Rena Hayden','Improve on current grade, 99.0','FALSE'), | |
('6748009c-c8c9-46de-8595-e2ea1ed29038','2020-01-21 10:07:51.1299003','Tutor Tyson Best','Improve on current grade, 75.0','FALSE'), | |
('660f2c37-70d3-4fca-a031-78316d99a0a3','2020-01-21 10:07:51.1326988','Tutor Eliza Tyler','Improve on current grade, 62.0','FALSE'), | |
('5f6a4db2-e0dc-4c69-9e0a-7f54fa161aa7','2020-01-21 10:07:51.1413264','Tutor Edith Adkins','Improve on current grade, 0.0','FALSE'), | |
('a8d27e85-6ca3-48b5-afde-81fe09aa8db2','2020-01-21 10:07:51.1448345','Tutor Charlotte Villanueva','Improve on current grade, 95.0','FALSE'), | |
('308f22a1-60c3-4d55-8205-4b3a8abf2215','2020-01-21 10:07:51.1492721','Tutor Tammy Cunningham','Improve on current grade, 99.9','FALSE'), | |
('b667ddf8-4e43-40b6-bed5-b5609a9bb6f7','2020-01-21 10:07:51.1524565','Tutor Marcia Dunlap','Improve on current grade, 43.2','FALSE'), | |
('45691d12-68b5-453e-8786-acdb8f0b6790','2020-01-21 10:07:51.1557428','Tutor Emma Savage','Improve on current grade, 65.4','FALSE'), | |
('f091fa0e-7f27-4486-83ea-49051984c105','2020-01-21 10:07:51.1716161','Tutor Janna Fleming','Improve on current grade, 88.5','FALSE'), | |
('8a45fe33-9a47-4610-ae1e-e1cdaf111525','2020-01-21 10:07:51.1814061','Tutor Emilio Rangel','Improve on current grade, 92.1','FALSE'), | |
('1c1b00bb-7ab4-4b14-8745-b5aa73dbb69c','2020-01-21 10:07:51.1845012','Tutor Martha Mcclure','Improve on current grade, 94.6','FALSE'), | |
('fba455cd-7819-4bf2-90ef-274cfbf0fea0','2020-01-21 10:07:51.1875831','Tutor Jason Vang','Improve on current grade, 75.3','FALSE'), | |
('5b7a64bb-f923-4453-a8c1-ebce8418cf4e','2020-01-21 10:07:51.1907791','Tutor Rosa Chavez','Improve on current grade, 78.9','FALSE'), | |
('8d27f9e6-af04-4dd6-9f49-2a8d668520d4','2020-01-21 10:07:51.1940694','Tutor Hayden Boyle','Improve on current grade, 89.1','FALSE'), | |
('bd8e3745-7ae6-43b0-a800-6bdefd813103','2020-01-21 10:07:51.197185','Tutor Margarita Marks','Improve on current grade, 79.5','FALSE'), | |
('a0b3d26e-7393-40e5-8acb-7eba317c5326','2020-01-21 10:07:51.2016468','Tutor Aileen Yoder','Improve on current grade, 83.0','FALSE'), | |
('d776761b-9313-4eb0-9f22-b01a1c301f1b','2020-01-21 10:07:51.2103955','Tutor Maryanne Anderson','Improve on current grade, 66.0','FALSE'), | |
('385f59d3-cc52-4a89-ba97-b1de63c333a7','2020-01-21 10:07:51.2136877','Tutor Benton Donaldson','Improve on current grade, 52.0','FALSE'), | |
('82a00485-c227-4af3-869c-a5064d3fa949','2020-01-21 10:07:51.2222792','Tutor Myrtle Ingram','Improve on current grade, 98.0','FALSE'), | |
('c4a3548b-9a74-4d8e-8086-77a69e9dc4fe','2020-01-21 10:07:51.225423','Tutor Lina Beck','Improve on current grade, 95.0','FALSE'), | |
('3105c3e7-68de-40c1-89d5-c799f58c9dd8','2020-01-21 10:07:51.2284409','Tutor Alisa Roy','Improve on current grade, 93.0','FALSE'), | |
('47fc9bce-3d0a-4ec4-9856-7a935e4f6b53','2020-01-21 10:07:51.2316102','Tutor Faye Cervantes','Improve on current grade, 84.0','FALSE'), | |
('ff08d778-d5d5-43be-bce8-36f2765e601a','2020-01-21 10:07:51.2346994','Tutor Brooks Villarreal','Improve on current grade, 82.0','FALSE'), | |
('fc84df87-4408-4c71-99c2-8dc7078394f7','2020-01-21 10:07:51.2379045','Tutor Jarrett Henderson','Improve on current grade, 75.0','FALSE'), | |
('fb1a91e2-f508-4f4d-b349-a8ef9a06868a','2020-01-21 10:07:51.2411096','Tutor Darlene Mcconnell','Improve on current grade, 73.0','FALSE'); | |
COMMIT;" >> $FILEPATH/resources/sql/$SOLUTION.sql | |
if [ "$(expr substr $(uname -s) 1 5)" == "Linux" ] && [ ! -f /usr/bin/sqlite3 ]; then | |
sudo bash -c "apt-get install sqlite3 libsqlite3-dev" | |
else | |
cd $FILEPATH/tools | |
curl -O https://www.sqlite.org/2020/sqlite-tools-win32-x86-3310100.zip | |
unzip sqlite-tools-win32-x86-3310100.zip && rm sqlite-tools-win32-x86-3310100.zip | |
cd $FILEPATH/tools/sqlite-tools-win32-x86-3310100 | |
fi | |
ENVIRONS=(Development Test Production) | |
for environ in ${ENVIRONS[*]} | |
do | |
sqlite3 $FILEPATH/resources/database/$SOLUTION-$environ.db < $FILEPATH/resources/sql/$SOLUTION.sql | |
done | |
# Add Infrastructure project ############################################################################## | |
cd $FILEPATH/src | |
dotnet new classlib -n $INFRASTRUCTURE -f $FRAMEWORK | |
cd $INFRASTRUCTURE && rm Class1.cs | |
mkdir -p Configuration Domain Domain/Events Extensions Logging Helpers | |
dotnet add package log4net | |
dotnet add package Newtonsoft.Json | |
dotnet add package System.Configuration.ConfigurationManager | |
dotnet add package System.Data.DataSetExtensions | |
echo "using System; | |
using System.Data; | |
using Newtonsoft.Json; | |
namespace $INFRASTRUCTURE.Helpers { | |
public class JsonHelpers { | |
public static T JsonAsList<T>(string jsonString) { | |
object value = JsonConvert.DeserializeObject<T>(jsonString); | |
return (T)Convert.ChangeType(value, typeof(T)); | |
} | |
public static DataTable JsonAsDataTable(string jsonString) { | |
return JsonConvert.DeserializeObject<DataTable>(jsonString); | |
} | |
} | |
}" >> Helpers/JsonHelpers.cs | |
echo 'using System; | |
using System.IO; | |
using System.Text; | |
using System.Linq; | |
using System.Data; | |
using System.Collections.Generic; | |
using System.Reflection; | |
using System.Text.RegularExpressions; | |
using Newtonsoft.Json; | |
using '$INFRASTRUCTURE'.Logging; | |
namespace '$INFRASTRUCTURE'.Helpers { | |
public class FileHelpers { | |
public static string GetAbsoluteFilePath(string fileName, string path) { | |
path = GetAbsoluteFilePath(path); | |
return String.Format(fileName, path); | |
} | |
public static string GetAbsoluteFilePath(string path) { | |
var rootPath = Assembly.GetExecutingAssembly().Location.Split("src")[0]; | |
return Path.Combine(rootPath, path); | |
} | |
public static DirectoryInfo GetAbsoluteFileDirectoryInfo(string path) { | |
path = GetAbsoluteFilePath(path); | |
return new DirectoryInfo(path); | |
} | |
public static bool DoesFileExist(DirectoryInfo source, string fileName) { | |
var file = GetNonHiddenFiles(source).Where(f => f.Name == fileName).FirstOrDefault(); | |
return file == null ? false : true; | |
} | |
public static bool DoesFileExist(string path, string fileName) { | |
var source = new DirectoryInfo(path); | |
var file = GetNonHiddenFiles(source).Where(f => f.Name == fileName).FirstOrDefault(); | |
return file == null ? false : true; | |
} | |
public static FileInfo[] GetNonHiddenFiles(DirectoryInfo dirInfo) { | |
return dirInfo.GetFiles().Where(f => (f.Attributes & FileAttributes.Hidden) == 0).ToArray(); | |
} | |
} | |
}' >> Helpers/FileHelpers.cs | |
echo "namespace $INFRASTRUCTURE.Logging { | |
public interface ILogger { void Log(string message); } | |
}" >> Logging/ILogger.cs | |
echo 'using System; | |
using System.Reflection; | |
using System.IO; | |
using log4net; | |
using log4net.Config; | |
using log4net.Repository.Hierarchy; | |
using '$INFRASTRUCTURE'.Configuration; | |
using '$INFRASTRUCTURE'.Helpers; | |
namespace '$INFRASTRUCTURE'.Logging { | |
public sealed class Log4NetAdapter : ILogger { | |
readonly ILog _log; | |
IApplicationSettings Settings { get { return ApplicationSettingsFactory.GetSettings(); }} | |
readonly Hierarchy _logRepo = | |
(Hierarchy) LogManager.GetRepository(Assembly.GetExecutingAssembly()); | |
public Log4NetAdapter() { | |
this.Configure(); | |
this._log = LogManager.GetLogger(this._logRepo.Name, this.Settings.LoggerName); | |
} | |
private void Configure() { | |
var logFile = FileHelpers.GetAbsoluteFilePath(this.Settings.LogFileName); | |
GlobalContext.Properties["LogFileName"] = logFile; | |
XmlConfigurator.Configure(this._logRepo, new FileInfo("App.config")); | |
} | |
public void Log(string message) { this._log.Info(message); } | |
} | |
}' >> Logging/Log4NetAdapter.cs | |
echo "namespace $INFRASTRUCTURE.Logging { | |
public sealed class LoggingFactory { | |
static ILogger _logger; | |
public static void InitializeLoggingFactory(ILogger logger) { | |
_logger = logger; | |
} | |
public static ILogger GetLogger() { return _logger; } | |
} | |
}" >> Logging/LoggingFactory.cs | |
echo "using System; | |
using System.Collections.Generic; | |
namespace $INFRASTRUCTURE.Extensions { | |
public static class IEnumerableExtensions { | |
public static void ForEach<T>(this IEnumerable<T> items, Action<T> action) { | |
foreach(var item in items) action(item); | |
} | |
} | |
}" >> Extensions/IEnumerableExtensions.cs | |
echo "namespace $INFRASTRUCTURE.Domain { | |
public sealed class BusinessRule { | |
public string Property { get; set; } | |
public string Rule { get; set; } | |
public BusinessRule(string property, string rule) { | |
this.Property = property; | |
this.Rule = rule; | |
} | |
} | |
}" >> Domain/BusinessRule.cs | |
echo "using System; | |
using System.Collections.Generic; | |
namespace $INFRASTRUCTURE.Domain { | |
public abstract class EntityBase<TId> { | |
readonly IList<BusinessRule> _rules = new List<BusinessRule>(); | |
public virtual TId Id { get; set; } | |
public virtual DateTime LastUpdated { get; set; } | |
protected abstract void Validate(); | |
public IEnumerable<BusinessRule> GetBrokenRules() { | |
this._rules.Clear(); | |
this.Validate(); | |
return this._rules; | |
} | |
protected void AddBrokenRule(BusinessRule rule) { this._rules.Add(rule); } | |
} | |
}" >> Domain/EntityBase.cs | |
echo "namespace $INFRASTRUCTURE.Domain { | |
public interface IAggregateRoot {} | |
}" >> IAggregateRoot.cs | |
echo "using System; | |
using System.Collections.Generic; | |
using System.Linq.Expressions; | |
using System.Threading.Tasks; | |
namespace $INFRASTRUCTURE.Domain { | |
public interface IReadOnlyRepository<T, TId> where T : IAggregateRoot { | |
Task<T> FindById(TId id); | |
Task<IList<T>> FindBy(Expression<Func<T, bool>> predicate); | |
Task<IList<T>> FindAll(); | |
} | |
}" >> IReadOnlyRepository.cs | |
echo "using System.Collections.Generic; | |
using System.Threading.Tasks; | |
namespace $INFRASTRUCTURE.Domain { | |
public interface IRepository<T, TId> : IReadOnlyRepository<T, TId> where T : IAggregateRoot { | |
Task Save(T entity); | |
Task Add(T entity); | |
Task Add(IList<T> entities); | |
Task Remove(T entity); | |
} | |
}" >> IRepository.cs | |
echo "using System; | |
namespace $INFRASTRUCTURE.Domain { | |
public interface IUnitOfWork<TConnection> : IDisposable { | |
TConnection Connection { get; } | |
void Commit(); | |
} | |
}" >> IUnitOfWork.cs | |
echo "using $INFRASTRUCTURE.Extensions; | |
namespace $INFRASTRUCTURE.Domain.Events { | |
public static class DomainEvents { | |
public static IDomainEventHandlerFactory DomainEventHandlerFactory { get; set; } | |
public static void Raise<T>(T domainEvent) where T : IDomainEvent { | |
DomainEventHandlerFactory.GetDomainEventHandlersFor(domainEvent) | |
.ForEach(h => h.Handle(domainEvent)); | |
} | |
} | |
}" >> Domain/Events/DomainEvents.cs | |
echo "namespace $INFRASTRUCTURE.Domain.Events { | |
public interface IDomainEvent { } | |
}" >> Domain/Events/IDomainEvent.cs | |
echo "namespace $INFRASTRUCTURE.Domain.Events { | |
public interface IDomainEventHandler<T> where T : IDomainEvent { | |
void Handle(T domainEvent); | |
} | |
}" >> Domain/Events/IDomainEventHandler.cs | |
echo "using System.Collections.Generic; | |
namespace $INFRASTRUCTURE.Domain.Events { | |
public interface IDomainEventHandlerFactory { | |
IEnumerable<IDomainEventHandler<T>> GetDomainEventHandlersFor<T>(T domainEvent) where T : IDomainEvent; | |
} | |
}" >> Domain/Events/IDomainEventHandlerFactory.cs | |
echo "namespace $INFRASTRUCTURE.Configuration { | |
public interface IApplicationSettings { | |
string LogFileName { get; } | |
string LoggerName { get; } | |
string InputFolder { get; } | |
string OutputFolder { get; } | |
string Database { get; } | |
} | |
}" >> Configuration/IApplicationSettings.cs | |
echo 'using System.Configuration; | |
using System.Reflection; | |
namespace '$INFRASTRUCTURE'.Configuration { | |
public sealed class ApplicationSettings : IApplicationSettings { | |
readonly AppSettingsSection _settings = | |
ConfigurationManager.OpenExeConfiguration((Assembly.GetExecutingAssembly()).Location).AppSettings; | |
public string LogFileName { get { return GetValue("LogFileName"); } } | |
public string LoggerName { get { return GetValue("LoggerName"); } } | |
public string InputFolder { get { return GetValue("InputFolder"); } } | |
public string OutputFolder { get { return GetValue("OutputFolder"); } } | |
public string Database { get { return GetValue("Database"); } } | |
private string GetValue(string property) { return this._settings.Settings[property].Value; } | |
} | |
}' >> Configuration/ApplicationSettings.cs | |
echo "namespace $INFRASTRUCTURE.Configuration { | |
public sealed class ApplicationSettingsFactory { | |
private static IApplicationSettings _settings; | |
public static void InitializeApplicationSettingsFactory(IApplicationSettings settings){ | |
_settings = settings; | |
} | |
public static IApplicationSettings GetSettings() { return _settings; } | |
} | |
}" >> Configuration/ApplicationSettingsFactory.cs | |
echo '<'?'xml version="1.0"?> | |
<configuration> | |
<configSections> | |
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net"/> | |
</configSections> | |
<log4net> | |
<logger name="'$INFRASTRUCTURE'.Logger"> | |
<level value="INFO"/> | |
<appender-ref ref="RollingLogFileAppender"/> | |
</logger> | |
<appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender"> | |
<file type="log4net.Util.PatternString" value="%property{LogFileName}"/> | |
<appendToFile value="true"/> | |
<rollingStyle value="Size"/> | |
<maxSizeRollBackups value="10"/> | |
<maximumFileSize value="10MB"/> | |
<staticLogFileName value="true"/> | |
<layout type="log4net.Layout.PatternLayout"> | |
<conversionPattern value="%-5p %d %5rms %-22.22c{1} %-18.18M - %m%n"/> | |
</layout> | |
</appender> | |
</log4net> | |
<appSettings> | |
<add key="LogFileName" value="resources/logs/log.txt" /> | |
<add key="LoggerName" value="'$INFRASTRUCTURE'.Logger" /> | |
<add key="Database" value="Data Source={0}resources/database/'$SOLUTION'-{1}.db" /> | |
<add key="InputFolder" value="resources/input" /> | |
<add key="OutputFolder" value="resources/output" /> | |
</appSettings> | |
</configuration>' >> App.config | |
sed -e "/<\/PropertyGroup>/r"<( | |
echo "<ItemGroup> | |
<None Update=\"App.config\" CopyToOutputDirectory=\"PreserveNewest\" /> | |
</ItemGroup>" | |
) -i -- $INFRASTRUCTURE.csproj | |
cd $FILEPATH && dotnet sln add src/$INFRASTRUCTURE/$INFRASTRUCTURE.csproj | |
# Add Models project ###################################################################################### | |
cd $FILEPATH/src | |
dotnet new classlib -n $MODELS -f $FRAMEWORK | |
cd $MODELS && rm Class1.cs | |
mkdir -p BusinessRules Events | |
echo 'using '$INFRASTRUCTURE'.Domain; | |
namespace '$MODELS'.BusinessRules { | |
public sealed class TodoBusinessRules { | |
public static readonly BusinessRule TitleRequired = | |
new BusinessRule("Todo", "Title is required."); | |
public static readonly BusinessRule DescriptionRequired = | |
new BusinessRule("Todo", "A description is required."); | |
} | |
}' >> BusinessRules/TodoBusinessRules.cs | |
echo "using $INFRASTRUCTURE.Domain.Events; | |
namespace $MODELS.Events { | |
public interface IRecordChangedEvent<TEntity> : IDomainEvent { | |
TEntity Entity { get; set; } | |
} | |
}" >> Events/IRecordChangedEvent.cs | |
echo "namespace $MODELS.Events { | |
public sealed class TodoAddedEvent : IRecordChangedEvent<Todo> { | |
public TodoAddedEvent(Todo entity) { this.Entity = entity; } | |
public Todo Entity { get; set; } | |
} | |
}" >> TodoAddedEvent.cs | |
echo "namespace $MODELS.Events { | |
public sealed class TodoDeletedEvent : IRecordChangedEvent<Todo> { | |
public TodoDeletedEvent(Todo entity) { this.Entity = entity; } | |
public Todo Entity { get; set; } | |
} | |
}" >> TodoDeletedEvent.cs | |
echo "using System; | |
using $INFRASTRUCTURE.Domain; | |
namespace $MODELS { | |
public interface IModelAttribute<T> : IAggregateRoot { | |
T Id { get; set; } | |
DateTime LastUpdated { get; set; } | |
} | |
}" >> IModelAttribute.cs | |
echo "using System; | |
using $INFRASTRUCTURE.Domain; | |
namespace $MODELS { | |
public interface ITodoRepository : IRepository<Todo, string> { } | |
}" >> ITodoRepository.cs | |
echo "using System; | |
using Newtonsoft.Json; | |
using $INFRASTRUCTURE.Domain; | |
using $MODELS.BusinessRules; | |
namespace $MODELS { | |
public class Todo : EntityBase<string>, IModelAttribute<string> { | |
public Todo() { this.Id = Guid.NewGuid().ToString(); } | |
[JsonProperty(NullValueHandling=NullValueHandling.Ignore)] | |
public string Title{ get; set; } = String.Empty; | |
[JsonProperty(NullValueHandling=NullValueHandling.Ignore)] | |
public string Description { get; set; } = String.Empty; | |
public bool IsCompleted { get; set; } = false; | |
protected override void Validate() { | |
if(String.IsNullOrEmpty(this.Title)) | |
base.AddBrokenRule(TodoBusinessRules.TitleRequired); | |
if(String.IsNullOrEmpty(this.Description)) | |
base.AddBrokenRule(TodoBusinessRules.DescriptionRequired); | |
} | |
} | |
}" >> Todo.cs | |
echo "using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using $INFRASTRUCTURE.Helpers; | |
using $INFRASTRUCTURE.Configuration; | |
namespace $MODELS { | |
public sealed class DataSeeder { | |
public static IList<Todo> GetTodos() { | |
var todos = new List<Todo>(); | |
return todos; | |
} | |
} | |
}" >> DataSeeder.cs | |
dotnet add reference ../$INFRASTRUCTURE/$INFRASTRUCTURE.csproj | |
cd $FILEPATH && dotnet sln add src/$MODELS/$MODELS.csproj | |
# Add Data project ######################################################################################## | |
cd $FILEPATH/src | |
dotnet new classlib -n $DATA -f $FRAMEWORK | |
cd $DATA && rm Class1.cs | |
mkdir -p Repositories | |
dotnet add package Dapper | |
dotnet add package Dapper.Contrib | |
dotnet add package pluralize.net.core | |
dotnet add package System.Data.Sqlite | |
dotnet add reference ../$INFRASTRUCTURE/$INFRASTRUCTURE.csproj | |
dotnet add reference ../$MODELS/$MODELS.csproj | |
echo 'using System; | |
using System.Collections.Generic; | |
using System.Data; | |
using System.Data.Common; | |
using System.Linq; | |
using System.Linq.Expressions; | |
using System.Threading.Tasks; | |
using Dapper; | |
using Pluralize.NET.Core; | |
using '$INFRASTRUCTURE'.Domain; | |
using '$INFRASTRUCTURE'.Logging; | |
using '$MODELS'; | |
namespace '$DATA'.Repositories { | |
public abstract class Repository<T, TEntityKey, TConnection> | |
where T : class, IAggregateRoot, IModelAttribute<TEntityKey> | |
where TConnection : DbConnection { | |
readonly IUnitOfWork<TConnection> _uow; | |
readonly string _tableName = new Pluralizer().Pluralize(typeof(T).Name); | |
public Repository(IUnitOfWork<TConnection> uow) { this._uow = uow; } | |
public async Task Add(T entity){ | |
await Task.Run(() => { | |
entity.LastUpdated = DateTime.Now; | |
try { | |
var existing = this.FindById(entity.Id).Result; | |
if (existing != null) this.Save(entity).Wait(); | |
else { | |
var query = this.GetInsertQuery(); | |
var paramsList = this.BuildParameters(entity); | |
this.ExecuteDataManipulation(query, paramsList); | |
} | |
} catch (Exception ex) { | |
LoggingFactory.GetLogger().Log($@"When attempting to add {entity}: | |
Message: {ex.Message} | |
StackTrace: {ex.StackTrace}"); | |
} | |
}); | |
} | |
public async Task Add(IList<T> entities) { | |
foreach(var entity in entities) await this.Add(entity); | |
} | |
public async Task Remove(T entity) { | |
var paramsList = new DynamicParameters(); | |
paramsList.Add("@Id", entity.Id); | |
await Task.FromResult( | |
this.ExecuteDataManipulation($"delete from {this._tableName} where Id = @Id", paramsList) | |
); | |
} | |
public async Task<T> FindById(TEntityKey id) { | |
var paramsList = new DynamicParameters(); | |
paramsList.Add("@Id", id); | |
var results = this.GetModelData($"select * from {this._tableName} where Id = @Id", paramsList); | |
if (results.Count > 0) return await Task.FromResult(results[0]); | |
else return null; | |
} | |
public async Task<IList<T>> FindBy(Expression<Func<T, bool>> expression) { | |
return await Task.FromResult( | |
this.FindAll().Result.Where(expression.Compile()).ToList() | |
); | |
} | |
public async Task<IList<T>> FindAll() { | |
return await Task.FromResult( | |
this.GetModelData($"select * from {this._tableName}") | |
); | |
} | |
public async Task Save(T entity) { | |
await Task.Run(() => { | |
var query = this.GetUpdateQuery(entity.Id); | |
var paramsList = this.BuildParameters(entity); | |
this.ExecuteDataManipulation(query, paramsList); | |
}); | |
} | |
private bool ExecuteDataManipulation(string query, DynamicParameters parameters) { | |
bool hasChanges = false; | |
var affected = this._uow.Connection.Execute(query, parameters, commandType:CommandType.Text); | |
hasChanges = affected > 0; | |
if (hasChanges) this._uow.Commit(); | |
return hasChanges; | |
} | |
// | |
IList<T> GetModelData(string query) { return this.GetModelData(query, null); } | |
IList<T> GetModelData(string query, DynamicParameters parameters) { | |
return this._uow.Connection.Query<T>(query, parameters, commandType:CommandType.Text).ToList(); | |
} | |
string GetInsertQuery() { | |
string query = string.Empty; | |
var columns = string.Join(",", GetTableColumns()); | |
var placeholders = new List<string>(); | |
foreach(var placeholder in GetTableColumns()) { | |
placeholders.Add(placeholder.Insert(0, "@")); | |
} | |
var values = string.Join(",", placeholders); | |
return $"insert into {this._tableName} ({columns}) values ({values})"; | |
} | |
string GetUpdateQuery(TEntityKey id) { | |
string placeholders = string.Empty; | |
var columns = this.GetTableColumns(); | |
foreach (var column in columns) { | |
placeholders += String.Format("{0} = @{0}", column); | |
if (column != columns.Last()) placeholders += ", "; | |
} | |
return $"update {this._tableName} set {placeholders} where Id = {id}"; | |
} | |
DynamicParameters BuildParameters(T entity) { | |
var paramsList = new DynamicParameters(); | |
var columns = this.GetTableColumns(); | |
foreach (var column in columns) { | |
paramsList.Add($"@{column}", this.GetPropValue(entity, column)); | |
} | |
return paramsList; | |
} | |
string[] GetTableColumns() { | |
var members = typeof(T).GetProperties(); | |
members = members.Where(x => x.Name.ToLower() == "id" | |
|| x.Name.ToLower() == "lastupdated" | |
|| (x.GetAccessors()[0].IsFinal || !x.GetAccessors()[0].IsVirtual)).ToArray(); | |
return members.Select(m => m.Name).ToArray(); | |
} | |
object GetPropValue(object src, string propName) { | |
return src.GetType().GetProperty(propName).GetValue(src, null); | |
} | |
} | |
}' >> Repositories/Repository.cs | |
echo "using System.Data.Common; | |
using System.Threading.Tasks; | |
using $MODELS; | |
using $MODELS.Events; | |
using $INFRASTRUCTURE.Domain; | |
using $INFRASTRUCTURE.Domain.Events; | |
namespace $DATA.Repositories { | |
public sealed class TodoRepository<TConnection> : Repository<Todo, string, TConnection>, ITodoRepository | |
where TConnection : DbConnection { | |
public TodoRepository(IUnitOfWork<TConnection> uow) : base(uow) { } | |
public new async Task Add(Todo todo) { | |
await Task.Run(() => { | |
DomainEvents.Raise(new TodoAddedEvent(todo) { }); | |
base.Add(todo).Wait(); | |
}); | |
} | |
public new async Task Remove(Todo todo) { | |
await Task.Run(() => { | |
DomainEvents.Raise(new TodoDeletedEvent(todo) { }); | |
base.Remove(todo).Wait(); | |
}); | |
} | |
} | |
}" >> Repositories/TodoRepository.cs | |
echo "using System; | |
using System.Collections.Generic; | |
using System.Data.Common; | |
using System.Linq; | |
using Dapper; | |
using $MODELS; | |
using $INFRASTRUCTURE.Configuration; | |
using $INFRASTRUCTURE.Helpers; | |
namespace ACA.Code.Exercise.Repository { | |
public sealed class DbSeeder { | |
private void SeedDatabase() { | |
} | |
} | |
}" >> DapperSeeder.cs | |
echo 'using System; | |
using System.Data.Common; | |
using '$INFRASTRUCTURE'.Domain; | |
using '$INFRASTRUCTURE'.Logging; | |
using '$INFRASTRUCTURE'.Configuration; | |
using '$INFRASTRUCTURE'.Helpers; | |
namespace '$DATA' { | |
public sealed class DapperUnitOfWork<TConnection,TTransaction> | |
: IUnitOfWork<TConnection> where TConnection : DbConnection, new() | |
where TTransaction : DbTransaction { | |
public DapperUnitOfWork() { | |
var env = System.Environment.GetEnvironmentVariable("'$SOLUTION'_ENVIRONMENT"); | |
env = !String.IsNullOrEmpty(env) ? env : "Development"; | |
this.Connection = new TConnection(); | |
this.Connection.ConnectionString = String.Format(this.Settings.Database, FileHelpers.GetAbsoluteFilePath(""), env); | |
this.Connection.Open(); | |
this.Transaction = (TTransaction) this.Connection.BeginTransaction(); | |
} | |
IApplicationSettings Settings { | |
get { | |
if (ApplicationSettingsFactory.GetSettings() == null) | |
ApplicationSettingsFactory.InitializeApplicationSettingsFactory(new ApplicationSettings()); | |
return ApplicationSettingsFactory.GetSettings(); | |
} | |
} | |
public TConnection Connection { get; set; } | |
TTransaction Transaction { get; set; } | |
public void Commit() { | |
try { | |
if (this.Transaction == null) | |
this.Transaction = (TTransaction) this.Connection.BeginTransaction(); | |
this.Transaction.Commit(); | |
} catch(DbException ex) { | |
this.Transaction.Rollback(); | |
LoggingFactory.GetLogger().Log($@"Transaction rolled back, | |
Message: {ex.Message} | |
StackTrace: {ex.StackTrace}"); | |
} finally { this.Transaction = null; } | |
} | |
public void Dispose() { | |
if (this.Transaction != null) { | |
this.Transaction.Rollback(); | |
this.Transaction = null; | |
} | |
if (this.Connection != null) { | |
this.Connection.Close(); | |
this.Connection = null; | |
} | |
} | |
} | |
}' >> DapperUnitOfWork.cs | |
cd $FILEPATH && dotnet sln add src/$DATA/$DATA.csproj | |
# Add DomainEventHandlers project ######################################################################### | |
cd $FILEPATH/src | |
dotnet new classlib -n $HANDLERS -f $FRAMEWORK | |
cd $HANDLERS && rm Class1.cs | |
echo 'using System; | |
using '$MODELS'.Events; | |
using '$INFRASTRUCTURE'.Domain.Events; | |
using '$INFRASTRUCTURE'.Logging; | |
namespace '$HANDLERS' { | |
public sealed class TodoAddedHandler : IDomainEventHandler<TodoAddedEvent> { | |
public void Handle(TodoAddedEvent domainEvent) { | |
var todo = domainEvent.Entity; | |
LoggingFactory.GetLogger().Log($@"Addition -> Todo at {DateTime.Now.ToLongDateString()} | |
Title: {todo.Title}, | |
Description: {todo.Description}."); | |
} | |
} | |
}' >> TodoAddedHandler.cs | |
echo 'using System; | |
using '$MODELS'.Events; | |
using '$INFRASTRUCTURE'.Domain.Events; | |
using '$INFRASTRUCTURE'.Logging; | |
namespace '$HANDLERS' { | |
public sealed class TodoDeletedHandler : IDomainEventHandler<TodoDeletedEvent> { | |
public void Handle(TodoDeletedEvent domainEvent) { | |
var todo = domainEvent.Entity; | |
LoggingFactory.GetLogger().Log($@"Deletion -> Todo at {DateTime.Now.ToLongDateString()} | |
Title: {todo.Title}, | |
Description: {todo.Description}."); | |
} | |
} | |
}' >> TodoDeletedHandler.cs | |
dotnet add reference ../$INFRASTRUCTURE/$INFRASTRUCTURE.csproj | |
dotnet add reference ../$MODELS/$MODELS.csproj | |
cd $FILEPATH && dotnet sln add src/$HANDLERS/$HANDLERS.csproj | |
# Add DI project ########################################################################################## | |
cd $FILEPATH/src | |
dotnet new classlib -n $DI -f $FRAMEWORK | |
cd $DI && rm Class1.cs | |
dotnet add package Autofac | |
dotnet add package System.Data.Sqlite | |
dotnet add reference ../$INFRASTRUCTURE/$INFRASTRUCTURE.csproj | |
dotnet add reference ../$MODELS/$MODELS.csproj | |
dotnet add reference ../$DATA/$DATA.csproj | |
dotnet add reference ../$HANDLERS/$HANDLERS.csproj | |
echo "using System.Data.SQLite; | |
using Autofac; | |
using $INFRASTRUCTURE.Configuration; | |
using $INFRASTRUCTURE.Logging; | |
using $INFRASTRUCTURE.Domain.Events; | |
using $SOLUTION.DomainEventHandlers; | |
using $SOLUTION.Models; | |
using $SOLUTION.Models.Events; | |
using $DATA.Repositories; | |
namespace $SOLUTION.DI { | |
public sealed class AutofacModule : Autofac.Module { | |
protected override void Load(ContainerBuilder builder){ | |
builder.RegisterType<ApplicationSettings>().As<IApplicationSettings>(); | |
builder.RegisterType<Log4NetAdapter>().As<ILogger>(); | |
//builder.RegisterType<SQLiteConnection>().AsImplementedInterfaces(); | |
builder.RegisterType<TodoRepository<SQLiteConnection>>().As<ITodoRepository>(); | |
builder.RegisterType<TodoAddedHandler>().As<IDomainEventHandler<TodoAddedEvent>>(); | |
builder.RegisterType<TodoDeletedHandler>().As<IDomainEventHandler<TodoDeletedEvent>>(); | |
builder.RegisterType<AutofacDomainEventHandlerFactory>().As<IDomainEventHandlerFactory>() | |
.WithParameter(new TypedParameter(typeof(IComponentContext), this)); | |
} | |
} | |
}" >> AutofaceModule.cs | |
echo "using System.Collections.Generic; | |
using Autofac; | |
using $SOLUTION.Infrastructure.Domain.Events; | |
namespace $SOLUTION.DI { | |
public sealed class AutofacDomainEventHandlerFactory : IDomainEventHandlerFactory { | |
readonly IComponentContext _context; | |
public AutofacDomainEventHandlerFactory(IComponentContext context) { | |
this._context = context; | |
} | |
public IEnumerable<IDomainEventHandler<T>> GetDomainEventHandlersFor<T>(T domainEvent) where T : IDomainEvent { | |
return this._context.Resolve<IEnumerable<IDomainEventHandler<T>>>(); | |
} | |
} | |
}" >> AutofacDomainEventHandlerFactory.cs | |
cd $FILEPATH && dotnet sln add src/$DI/$DI.csproj | |
# Add main project based on type ################################################################################### | |
cd $FILEPATH/src | |
dotnet new $TYPE -n $SOLUTION.App -f $FRAMEWORK | |
cd $SOLUTION.App | |
if [ $TYPE == "console" ]; then | |
rm Program.cs | |
dotnet add reference ../$SOLUTION.DI/$SOLUTION.DI.csproj | |
echo 'using System; | |
using System.Data.SQLite; | |
using System.IO; | |
using System.Reflection; | |
using Autofac; | |
using '$DI'; | |
using '$INFRASTRUCTURE'.Domain; | |
using '$INFRASTRUCTURE'.Domain.Events; | |
using '$INFRASTRUCTURE'.Logging; | |
using '$INFRASTRUCTURE'.Configuration; | |
using '$DATA'; | |
namespace '$SOLUTION'.App | |
{ | |
class Program | |
{ | |
static IContainer _container; | |
static void Main(string[] args) | |
{ | |
Environment.SetEnvironmentVariable("'$SOLUTION'_ENVIRONMENT","Development"); | |
BuildContainer(); | |
InitSettings(); // note: this needs to happend before the logger :) | |
InitLogger(); | |
InitDomainEventHandlerFactory(); | |
_container.Resolve<Application>().Run(); | |
} | |
string ExecutionPath => Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); | |
string ProjectRootPath => Assembly.GetExecutingAssembly().Location.Split("src")[0]; | |
static TEntity Resolve<TEntity>() => _container.Resolve<TEntity>(); | |
static void BuildContainer() { | |
var builder = new ContainerBuilder(); | |
builder.RegisterModule<DI.AutofacModule>(); | |
builder.RegisterType<Application>(); | |
builder.RegisterType<DapperUnitOfWork<SQLiteConnection,SQLiteTransaction>>().As<IUnitOfWork<SQLiteConnection>>().InstancePerLifetimeScope(); | |
_container = builder.Build(); | |
} | |
static void InitLogger() { | |
var logger = Resolve<ILogger>(); | |
LoggingFactory.InitializeLoggingFactory(logger); | |
} | |
static void InitSettings() { | |
var settings = Resolve<IApplicationSettings>(); | |
ApplicationSettingsFactory.InitializeApplicationSettingsFactory(settings); | |
} | |
static void InitDomainEventHandlerFactory() { | |
DomainEvents.DomainEventHandlerFactory = | |
new AutofacDomainEventHandlerFactory(_container); | |
} | |
} | |
}' >> Program.cs | |
echo 'using System; | |
using '$INFRASTRUCTURE'.Configuration; | |
using '$INFRASTRUCTURE'.Logging; | |
using '$MODELS'; | |
namespace '$SOLUTION'.App { | |
public class Application { | |
IApplicationSettings Settings => ApplicationSettingsFactory.GetSettings(); | |
ILogger Logger => LoggingFactory.GetLogger(); | |
readonly ITodoRepository _todoRepository; | |
public Application(ITodoRepository todoRepository) { | |
this._todoRepository = todoRepository; | |
} | |
public void Run() { | |
foreach(var todo in this._todoRepository.FindAll().Result) { | |
try { | |
Console.WriteLine($"Title: {todo.Title}, Description: {todo.Description}, IsCompleted: {todo.IsCompleted}"); | |
} catch (Exception ex) { | |
Logger.Log($@"When attempting to add {todo}: | |
Message: {ex.Message} | |
StackTrace: {ex.StackTrace}"); | |
} | |
} | |
} | |
} | |
}' >> Application.cs | |
elif [ $TYPE == "webapi" ]; then | |
dotnet package add Microsoft.AspNetCore.App | |
dotnet add reference ../$SOLUTION.DI/$SOLUTION.DI.csproj | |
echo "todo...finish implementation for webapi" | |
fi | |
cd $FILEPATH && dotnet sln add src/$SOLUTION.App/$SOLUTION.App.csproj | |
# Add specs project ################################################################################### | |
cd $FILEPATH/src | |
dotnet new mstest -n $SOLUTION.Specs -f $FRAMEWORK | |
cd $SOLUTION.Specs | |
rm UnitTest1.cs | |
dotnet add package Moq | |
dotnet add package FluentAssertions | |
dotnet add reference ../$SOLUTION.App/$SOLUTION.App.csproj | |
cd $FILEPATH && dotnet sln add src/$SOLUTION.Specs/$SOLUTION.Specs.csproj | |
dotnet restore | |
dotnet run --project src/$SOLUTION.App --framework $FRAMEWORK | |
echo "Complete." |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment