Skip to content

Instantly share code, notes, and snippets.

@WillSams
Last active February 11, 2020 21:04
Show Gist options
  • Save WillSams/606939daa0b0701af0d10da51da1f4fd to your computer and use it in GitHub Desktop.
Save WillSams/606939daa0b0701af0d10da51da1f4fd to your computer and use it in GitHub Desktop.
Simple VS Code C# Solution Generation Example
#!/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