{"id":18526700,"url":"https://github.com/rogusdev/docker_heroku_dotnet","last_synced_at":"2026-01-25T19:01:40.355Z","repository":{"id":142081014,"uuid":"113273458","full_name":"rogusdev/docker_heroku_dotnet","owner":"rogusdev","description":"Demo Docker running Dotnet Core (Nancy) on Heroku via CircleCI with Postgresql and Redis ","archived":false,"fork":false,"pushed_at":"2018-09-09T02:05:45.000Z","size":17,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-10-10T15:40:24.564Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rogusdev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-12-06T05:34:13.000Z","updated_at":"2018-09-09T02:05:47.000Z","dependencies_parsed_at":"2023-07-22T16:15:34.336Z","dependency_job_id":null,"html_url":"https://github.com/rogusdev/docker_heroku_dotnet","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/rogusdev/docker_heroku_dotnet","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rogusdev%2Fdocker_heroku_dotnet","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rogusdev%2Fdocker_heroku_dotnet/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rogusdev%2Fdocker_heroku_dotnet/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rogusdev%2Fdocker_heroku_dotnet/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rogusdev","download_url":"https://codeload.github.com/rogusdev/docker_heroku_dotnet/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rogusdev%2Fdocker_heroku_dotnet/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28757148,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-25T16:32:25.380Z","status":"ssl_error","status_checked_at":"2026-01-25T16:32:09.189Z","response_time":113,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-06T17:52:14.234Z","updated_at":"2026-01-25T19:01:40.330Z","avatar_url":"https://github.com/rogusdev.png","language":null,"readme":"# docker_heroku_dotnet\nDemo how to build a docker dotnet core service deployed to heroku through circleci with postgres and redis attached\n\n- use vagrant to start environment in which to run docker\n- ^ docker is NOT a VM!  (it can and frequently does run IN a VM)\n- use docker image to bootstrap new dotnet core app\n- deploy to heroku\n- deploy using circleci\n\n\n1. Install VirtualBox: https://www.virtualbox.org/wiki/Downloads\n1. Install Vagrant: https://www.vagrantup.com/downloads.html\n\n```\ngit clone git@github.com:rogusdev/docker_heroku_dotnet.git\ncd docker_heroku_dotnet/\ncp ../Vagrantfile ./\ncp ../README.md ./\n\nvagrant up\nchmod 600 .vagrant/machines/default/virtualbox/private_key\nscp -r -i .vagrant/machines/default/virtualbox/private_key -P 2222 ~/.ssh/id_rsa* ubuntu@127.0.0.1:/home/ubuntu/.ssh/\nvagrant ssh\n\n\ncat \u003c\u003c EOF \u003e .env\nPORT=5000\nDATABASE_URL=postgres://postgres:@postgres:5432\n#DATABASE_URL=Host=localhost;Username=postgres;Password=postgres;Database=postgres\nREDIS_URL=redis://:@redis:6379\nEOF\n\ncat \u003c\u003c EOF \u003e .dockerignore\n.DS_Store\n.dockerignore\n.env\n.git\n.gitignore\n.idea\n.vagrant\n.vs\n.vscode\ndocker-compose.yml\nDockerfile\n**/bin/\n**/obj/\nEOF\n\nwget https://raw.githubusercontent.com/github/gitignore/master/VisualStudio.gitignore -O .gitignore\n\ncat \u003c\u003c EOF \u003e\u003e .gitignore\n.DS_Store\n.env\n.idea\n.vagrant\n.vscode\n**/bin/\n**/obj/\nEOF\n\ncat \u003c\u003c EOF \u003e .gitattributes\n# https://stackoverflow.com/questions/170961/whats-the-best-crlf-carriage-return-line-feed-handling-strategy-with-git\n*        text eol=lf\n\n*.cs     text diff=csharp\n*.html   text diff=html\n*.css    text diff=css\n*.js     text diff=js\n*.sql    text diff=sql\n\n*.csproj text merge=union\n*.sln    text merge=union eol=crlf\nEOF\n\ncat \u003c\u003c EOF \u003e app.json\n{\n  \"name\": \"Docker Dotnet Core Heroku\",\n  \"description\": \"An example app.json for container-deploy\",\n  \"image\": \"microsoft/aspnetcore:2.0.3\",\n  \"addons\": [\n    \"heroku-postgresql\"\n  ]\n}\nEOF\n\ncat \u003c\u003c EOF \u003e Procfile\nweb: dotnet App.Web.dll\nEOF\n\n\ncat \u003c\u003c EOF \u003e Dockerfile\n# https://docs.docker.com/engine/examples/dotnetcore/#create-a-dockerfile-for-an-aspnet-core-application\n# https://hub.docker.com/r/microsoft/dotnet/\n# https://hub.docker.com/r/microsoft/aspnetcore-build/\n# https://docs.microsoft.com/en-us/dotnet/core/docker/building-net-docker-images\n# https://docs.microsoft.com/en-us/dotnet/core/deploying/index\n# https://github.com/dotnet/dotnet-docker-samples/tree/master/aspnetapp\nFROM microsoft/aspnetcore-build:2.1.3 AS build-env\nWORKDIR /app\n\nCOPY *.sln .\nCOPY App.Web/*.csproj App.Web/\nCOPY App.Tests/*.csproj App.Tests/\nRUN dotnet restore\n\nCOPY . .\nRUN dotnet publish -c Release -o out \\\n    \u0026\u0026 touch App.Web/out/.env\n\nFROM microsoft/aspnetcore:2.1.3\nWORKDIR /app\nCOPY --from=build-env /app/App.Web/out .\n\n# https://www.ctl.io/developers/blog/post/dockerfile-entrypoint-vs-cmd/\n# Heroku will not accept an empty array CMD! -- must pass empty string inside\n# but it actually just straight up ignores ENTRYPOINT anyway!  grr\nCMD [\"dotnet\", \"App.Web.dll\"]\n#ENTRYPOINT [\"dotnet\", \"FudgyCron.Web.dll\"]\n#CMD [\"\"]\n\n# http://localhost:5000/Content/index.html\nEOF\n\n\ncat \u003c\u003c EOF \u003e docker-compose.yml\n# https://blog.codeship.com/running-rails-development-environment-docker/\n# https://nickjanetakis.com/blog/dockerize-a-rails-5-postgres-redis-sidekiq-action-cable-app-with-docker-compose\nversion: '3'\nservices:\n  postgres:\n    image: postgres:9.6\n    ports:\n     - \"5432:5432\"\n  redis:\n    image: redis:4.0.2\n    ports:\n     - \"6379:6379\"\n  zookeeper:\n    image: zookeeper:3.4\n    ports:\n      - \"2181:2181\"\n  kafka:\n    image: wurstmeister/kafka:0.11.0.1\n    ports:\n      - \"9092:9092\"\n    environment:\n      KAFKA_ADVERTISED_HOST_NAME: localhost\n      KAFKA_CREATE_TOPICS: \"test:1:1,recipes-v1:1:1:compact,triggers-v1:1:1,events-v1:1:1\"\n      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181\n  web:\n    build: .\n    # using local dir as a volume instead of copying means no re-build for changes\n    #  but still need copy in the dockerfile for deployment -- tho it will remove dockerignore files!\n    # FIXME: confirm the dockerignore is ignored for compose -- try docker-compose run app ls -la or something\n    ports:\n      - \"5000:5000\"\n    env_file: \".env\"\n    depends_on:\n      - postgres\n      - redis\nEOF\n\n\ndocker run --rm -it -v /vagrant:/app microsoft/aspnetcore-build:2.1.3 sh -c 'cd /app; bash'\n\n\n# inside the new shell:\n\ndotnet new sln -n App\n\ndotnet new web -f netcoreapp2.1 -n App.Web\ndotnet sln add App.Web/App.Web.csproj\n\ndotnet new xunit -f netcoreapp2.1 -n App.Tests\ndotnet add ./App.Tests package xunit --version 2.3.1  # update the version\ndotnet add ./App.Tests package FakeItEasy --version 4.2.0\ndotnet add ./App.Tests package Nancy.Testing --version 2.0.0-clienteastwood\ndotnet add ./App.Tests reference ./App.Web/App.Web.csproj\ndotnet sln add App.Tests/App.Tests.csproj\n\ndotnet add ./App.Tests package Microsoft.NET.Test.Sdk --version 15.5.0\ndotnet add ./App.Tests package xunit.runner.visualstudio --version 2.3.1 \n\n#sed -i '/PackageReference Include=\"Microsoft.NET.Test.Sdk\"/ d' App.Tests/App.Tests.csproj\n#sed -i 's/\u003cPackageReference Include=\"xunit.runner.visualstudio\" Version=\"2.3.1\" \\/\u003e/\u003cDotNetCliToolReference Include=\"dotnet-xunit\" Version=\"2.3.1\" \\/\u003e/' App.Tests/App.Tests.csproj\n\ndotnet add ./App.Web package DotNetEnv --version 1.1.0\ndotnet add ./App.Web package Npgsql --version 3.2.5\ndotnet add ./App.Web package Dapper --version 1.50.2\ndotnet add ./App.Web package StackExchange.Redis --version 1.2.6\ndotnet add ./App.Web package Confluent.Kafka --version 0.11.3\ndotnet add ./App.Web package Newtonsoft.Json --version 10.0.3\n\ndotnet remove ./App.Web package Microsoft.AspNetCore.All\ndotnet add ./App.Web package Microsoft.AspNetCore.Hosting --version 2.0.0\ndotnet add ./App.Web package Microsoft.AspNetCore.Owin --version 2.0.0\ndotnet add ./App.Web package Microsoft.AspNetCore.Server.Kestrel --version 2.0.0\ndotnet add ./App.Web package Microsoft.Extensions.CommandLineUtils --version 1.1.1\ndotnet add ./App.Web package Nancy --version 2.0.0-clienteastwood\n\n# exit from docker bash\n\n\ncat \u003c\u003c EOF \u003e App.Web/Startup.cs\nusing DotNetEnv;\nusing Microsoft.AspNetCore.Builder;\nusing Nancy.Owin;\n\nnamespace App.Web\n{\n    public class Startup\n    {\n        public void Configure(IApplicationBuilder app)\n        {\n            Env.Load();\n            app.UseOwin(x =\u003e x.UseNancy());\n        }\n    }\n}\nEOF\n\ncat \u003c\u003c EOF \u003e App.Web/Program.cs\nusing System.IO;\nusing Microsoft.AspNetCore.Hosting;\n\nnamespace App.Web\n{\n    public class Program\n    {\n        public static void Main(string[] args)\n        {\n            var port = System.Environment.GetEnvironmentVariable(\"PORT\") ?? \"5000\";\n            var host = new WebHostBuilder()\n                .UseKestrel()\n                .UseUrls($\"http://*:{port}\")\n                .UseContentRoot(Directory.GetCurrentDirectory())\n                .UseStartup\u003cStartup\u003e()\n                .Build();\n\n            host.Run();\n        }\n    }\n}\nEOF\n\ncat \u003c\u003c EOF \u003e App.Web/BaseModule.cs\nusing System;\nusing Nancy;\n\nnamespace App.Web\n{\n    public class BaseModule : NancyModule\n    {\n        public BaseModule() : base(\"/\")\n        {\n            Get(\"/\", args =\u003e \"NancyFX Hello @ \" + DateTime.UtcNow);\n        }\n    }\n}\nEOF\n\ncat \u003c\u003c EOF \u003e App.Web/Bootstrapper.cs\nusing Nancy;\nusing Nancy.Bootstrapper;\nusing Nancy.Configuration;\nusing Nancy.Diagnostics;\nusing Nancy.Json;\nusing Nancy.TinyIoc;\n\nnamespace App.Web\n{\n    public class Bootstrapper : DefaultNancyBootstrapper\n    {\n        protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines)\n        {\n        }\n\n        public override void Configure(INancyEnvironment environment)\n        {\n            environment.Json(retainCasing: true);\n            environment.Diagnostics(true, \"password\");\n            environment.Tracing(enabled: true, displayErrorTraces: true);\n            base.Configure(environment);\n        }\n    }\n}\nEOF\n\n\ndocker-compose build\ndocker-compose up\n\n\ndocker ps -a \u0026\u0026 docker images\n#docker stop postgres:9.6\n#docker rm postgres:9.6\n#docker rmi ...\n\ndocker build -t docker-heroku-dotnet .\n\ndocker run --rm -it --env-file .env -p 0.0.0.0:5000:5000 docker-heroku-dotnet\n\n\nheroku plugins:install heroku-container-registry\nheroku login\nheroku container:login\n\nheroku create rogusdev-docker-heroku-dotnet\n#heroku apps:destroy rogusdev-docker-heroku-dotnet\n# https://rogusdev-docker-heroku-dotnet.herokuapp.com/\n\n# Look at the \"stack\" in \"Info\" on Heroku's settings for this app, under config vars:\n#  https://dashboard.heroku.com/apps/rogusdev-docker-heroku-dotnet/settings\n\nheroku container:push web\n\n\nvagrant halt\nvagrant destroy\n```\n\n\nIf you are not in a vagrant vm, you might need to remove postgres:\n```\n# https://askubuntu.com/questions/32730/how-to-remove-postgres-from-my-installation\ndpkg -l | grep postgres\n\nsudo apt-get --purge remove postgresql postgresql-common postgresql-client-common\n```\n\nIntegrating with CircleCI is possible but actually quite complicated -- these are just stubs I will fill in soon, the links tell the story for now:\n```\n# https://circleci.com/docs/2.0/deployment_integrations/#heroku\ncat \u003c\u003c EOF1 \u003e .circleci/setup-heroku.sh\n#!/bin/bash\nwget https://cli-assets.heroku.com/branches/stable/heroku-linux-amd64.tar.gz\nsudo mkdir -p /usr/local/lib /usr/local/bin\nsudo tar -xvzf heroku-linux-amd64.tar.gz -C /usr/local/lib\nsudo ln -s /usr/local/lib/heroku/bin/heroku /usr/local/bin/heroku\n\ncat \u003e ~/.netrc \u003c\u003c EOF\nmachine api.heroku.com\n  login $HEROKU_LOGIN\n  password $HEROKU_API_KEY\nEOF\n\ncat \u003e\u003e ~/.ssh/config \u003c\u003c EOF\nVerifyHostKeyDNS yes\nStrictHostKeyChecking no\nEOF\nEOF1\n\n# https://circleci.com/docs/2.0/executor-types/#using-docker\ncat \u003c\u003c EOF \u003e .circleci/config.yml\nversion: 2\njobs:\n  build:\n    machine: true\n    steps:\n      - checkout\n      - run:\n          name: Greeting\n          command: echo Hello, world.\n      - run:\n          name: Print the Current Time\n          command: date\nEOF\n```\n\n\nActually leverage the postgres and redis connections:\n```\ncat \u003c\u003c EOF \u003e App.Web/PostgresModule.cs\nusing System;\nusing Dapper;\nusing Nancy;\n\nnamespace App.Web\n{\n    public class PostgresModule : NancyModule\n    {\n        public PostgresModule() : base(\"/postgres\")\n        {\n            // this is very bad REST, I know\n            Get(\"/{key}/{value}\", args =\u003e\n            {\n                var key = args.key.ToString();\n                var value = args.value.ToString();\n\n                var databaseUrlString = Environment.GetEnvironmentVariable(\"DATABASE_URL\");\n                Console.WriteLine($\"DATABASE_URL from ENV: {databaseUrlString}\");\n                // https://devcenter.heroku.com/articles/connecting-to-relational-databases-on-heroku-with-java#using-the-database_url-in-plain-jdbc\n                var dbUri = new Uri(databaseUrlString);\n                var userInfo = dbUri.UserInfo.Split(':');\n                var npgsqlConnString = $\"Host={dbUri.Host};Port={dbUri.Port};Database={dbUri.LocalPath.Substring(1)};Username={userInfo[0]};Password={userInfo[1]}\";\n                Console.WriteLine($\"constructed npgsqlConnString: {npgsqlConnString}\");\n\n                // https://github.com/StackExchange/Dapper\n                // http://dapper-tutorial.net/dapper\n                using (var dbConn = new Npgsql.NpgsqlConnection(npgsqlConnString))\n                {\n                    dbConn.Open();\n                    // # https://stackoverflow.com/questions/8902674/manually-map-column-names-with-class-properties/34536863#34536863\n                    Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true;\n\n                    var now = DateTime.UtcNow;\n                    var id = Guid.NewGuid();\n\n                    // Insert some data\n                    var newThing = new Thing()\n                    {\n                        Id = id,\n                        Name = $\"Hello world {now}: {key} = {value}\",\n                        Enabled = true,\n                        CreatedAt = now.AddMinutes(-10),\n                        UpdatedAt = now.AddMinutes(10),\n                    };\n                    dbConn.Execute(\n                        \"INSERT INTO things\" +\n                        \" (id, name, enabled, created_at, updated_at)\" +\n                        \" VALUES (@Id, @Name, @Enabled, @CreatedAt, @UpdatedAt)\",\n                        newThing\n                    );\n                    Console.WriteLine(\"inserted via dapper: {0}: {1}={2}\", now, key, value);\n\n                    // Retrieve all rows\n                    var things = dbConn.Query\u003cThing\u003e(\"SELECT * FROM things\");\n                    foreach (var thing in things)\n                        Console.WriteLine(thing);\n                    Console.WriteLine(\"read all dapper\");\n\n                    var addedThing = dbConn.QuerySingleOrDefault\u003cThing\u003e(\n                        \"SELECT * FROM things WHERE id = @Id\",\n                        new { Id = id }\n                    );\n                    Console.WriteLine(\"added: {0}\", addedThing);\n                }\n\n                return DateTime.UtcNow.ToString();\n            });\n        }\n\n        private class Thing\n        {\n            public Guid Id { get; set; }\n            public string Name { get; set; }\n            public bool Enabled { get; set; }\n            public DateTime CreatedAt { get; set; }\n            public DateTime UpdatedAt { get; set; }\n\n            public override string ToString()\n            {\n                return $\"{nameof(Id)}: {Id}, {nameof(Name)}: {Name}, {nameof(Enabled)}: {Enabled}, {nameof(CreatedAt)}: {CreatedAt}, {nameof(UpdatedAt)}: {UpdatedAt}\";\n            }\n        }\n    }\n}\nEOF\n\n\ncat \u003c\u003c EOF \u003e App.Web/RedisModule.cs\nusing System;\nusing System.Collections.Generic;\nusing Nancy;\n\nnamespace App.Web\n{\n    public class RedisModule : NancyModule\n    {\n        public RedisModule() : base(\"/redis\")\n        {\n            // this is very bad REST, I know\n            Get(\"/{key}/{value}\", args =\u003e\n            {\n                var key = args.key.ToString();\n                var value = args.value.ToString();\n\n                // https://github.com/redis/redis-rb/blob/master/lib/redis/client.rb#L408\n                // https://msdn.microsoft.com/en-us/library/system.uri(v=vs.110).aspx\n                // https://stackexchange.github.io/StackExchange.Redis/Configuration\n                var redisUrlString = Environment.GetEnvironmentVariable(\"REDIS_URL\");\n                Console.WriteLine($\"REDIS_URL from ENV: {redisUrlString}\");\n                var redisUri = new Uri(redisUrlString);\n                var userInfo = redisUri.UserInfo.Split(':');\n                var redisConnString = $\"{redisUri.Host}:{redisUri.Port},password={userInfo[1]}\";\n                Console.WriteLine($\"constructed redisConnString: {redisConnString}\");\n\n                var redisConn = StackExchange.Redis.ConnectionMultiplexer.Connect(redisConnString);\n                var redisDb = redisConn.GetDatabase();\n                redisDb.StringSet(key, value);\n                var dict = new Dictionary\u003cstring, string\u003e\n                {\n                    {args.key, redisDb.StringGet(key)},\n                    {\"ticks\", DateTime.UtcNow.Ticks.ToString()},\n                };\n\n                // https://github.com/NancyFx/Nancy/wiki/Content-Negotiation\n                var response = Response.AsJson(dict);\n                response.ContentType = \"application/json\";\n                return response;\n            });\n        }\n    }\n}\nEOF\n\n\n# poor man's redis-cli\necho \"KEYS *\" | nc -v localhost 6379\necho \"GET key1\" | nc -v localhost 6379\n\n\nsudo apt-get install -y postgresql-client #redis-tools\n\ncat \u003c\u003c EOF | psql -h localhost -p 5432 -U postgres\nCREATE TABLE things (\n id UUID PRIMARY KEY,\n name VARCHAR (255) NOT NULL,\n enabled BOOLEAN NOT NULL DEFAULT 'f',\n created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n);\nEOF\n\necho \"SELECT * FROM things;\" | psql -h localhost -p 5432 -U postgres\n\n\nheroku plugins:install heroku-redis\n\n# https://elements.heroku.com/addons/heroku-redis\n# https://devcenter.heroku.com/articles/heroku-redis#provisioning-the-add-on\n# must add billing info before you can provision this\nheroku addons:create heroku-redis:hobby-dev -a rogusdev-docker-heroku-dotnet\n\nheroku redis:credentials -a rogusdev-docker-heroku-dotnet\n\n# https://elements.heroku.com/addons/heroku-postgresql\n# https://devcenter.heroku.com/articles/heroku-postgresql#version-support-and-legacy-infrastructure\nheroku addons:create heroku-postgresql:hobby-dev -a rogusdev-docker-heroku-dotnet\n# --version 9.6\n# ^ specifying version gasve me 9.6.1, without specifying it gave me 9.6.4\n# -- trying to specify minor version threw an error about not available\n\nheroku pg:psql -a rogusdev-docker-heroku-dotnet\n\n```\n\n\nAnd now with tests!\n```\nrm App.Tests/UnitTest1.cs\n\ncat \u003c\u003c EOF \u003e App.Tests/BaseModuleTests.cs\nusing System;\nusing App.Web;\nusing Nancy;\nusing Nancy.Testing;\nusing Xunit;\n\nnamespace App.Tests\n{\n    public class BaseModuleTests\n    {\n        private readonly Browser _browser;\n        private readonly DateTime _utcNow;\n        \n        public BaseModuleTests()\n        {\n            _utcNow = DateTime.UtcNow;\n            var timeProvider = new FixedTimeProvider(_utcNow);\n\n            _browser = new Browser(with =\u003e\n            {\n                with.Module\u003cBaseModule\u003e();\n                with.Dependency(timeProvider);\n            });\n        }\n\n        [Fact]\n        public void ShouldAnnounceHello()\n        {\n            var response = _browser.Get(\"/\", with =\u003e {\n                with.HttpRequest();\n            }).Result;\n        \n            Assert.Equal(HttpStatusCode.OK, response.StatusCode);\n            // https://stackoverflow.com/questions/38443123/c-sharp-getting-http-body-using-nancy\n            //Assert.StartsWith(\"NancyFX Hello @ \", response.Body.AsString());\n            Assert.Equal($\"NancyFX Hello @ {_utcNow}\", response.Body.AsString());\n        }\n    }\n}\nEOF\n\ncat \u003c\u003c EOF \u003e App.Tests/PostgresModuleTests.cs\nusing System;\nusing App.Web;\nusing FakeItEasy;\nusing Nancy;\nusing Nancy.Testing;\nusing Xunit;\n\nnamespace App.Tests\n{\n    public class PostgresModuleTests\n    {\n        private readonly Browser _browser;\n        private readonly DateTime _utcNow;\n        private readonly IPostgresDb _postgresDb;\n\n        public PostgresModuleTests()\n        {\n            _postgresDb = A.Fake\u003cIPostgresDb\u003e();\n            _utcNow = DateTime.UtcNow;\n            var timeProvider = new FixedTimeProvider(_utcNow);\n            \n            // http://www.marcusoft.net/2013/01/NancyTesting2.html\n            _browser = new Browser(with =\u003e\n            {\n                with.Module\u003cPostgresModule\u003e();\n                with.Dependency(timeProvider);\n                with.Dependency(_postgresDb);\n            });\n        }\n\n        [Fact]\n        public void ShouldStoreAndRetrieveFromPostgres()\n        {\n            // https://fakeiteasy.readthedocs.io/en/stable/invoking-custom-code/\n            // https://stackoverflow.com/questions/43781237/why-cant-i-capture-a-fakeiteasy-expectation-in-a-variable/43781563#43781563\n            // http://thorarin.net/blog/post/2014/09/18/capturing-method-arguments-on-your-fakes-using-fakeiteasy.aspx\n            Thing newThing = null;\n            A.CallTo(() =\u003e _postgresDb.InsertThing(A\u003cThing\u003e._))\n                .Invokes(call =\u003e newThing = call.GetArgument\u003cThing\u003e(0));\n            A.CallTo(() =\u003e _postgresDb.AllThings()).ReturnsLazily(() =\u003e new[] { newThing });\n            var id = Guid.Empty;\n            A.CallTo(() =\u003e _postgresDb.FindThing(A\u003cGuid\u003e._))\n                .Invokes(call =\u003e id = call.GetArgument\u003cGuid\u003e(0))\n                .ReturnsLazily(() =\u003e newThing);\n\n            var response = _browser.Get(\"/postgres/key1/value1\", with =\u003e {\n                with.HttpRequest();\n            }).Result;\n\n            Assert.Equal(HttpStatusCode.OK, response.StatusCode);\n            // https://github.com/xunit/xunit/blob/master/test/test.xunit.assert/Asserts/StringAssertsTests.cs\n            Assert.Matches(@\"^\\d{2}/\\d{2}/\\d{4} \\d{2}:\\d{2}:\\d{2}$\", response.Body.AsString());\n            \n            Assert.NotEqual(Guid.Empty, id);\n            Assert.Equal(newThing.Id, id);\n            Assert.Equal($\"Hello world {_utcNow}: key1 = value1\", newThing.Name);\n            Assert.True(newThing.Enabled);\n            Assert.Equal(_utcNow.AddMinutes(-10), newThing.CreatedAt);\n            Assert.Equal(_utcNow.AddMinutes(10), newThing.UpdatedAt);\n        }\n    }\n}\nEOF\n\ncat \u003c\u003c EOF \u003e App.Tests/RedisModuleTests.cs\nusing System;\nusing System.Collections.Generic;\nusing App.Web;\nusing FakeItEasy;\nusing Nancy;\nusing Nancy.Json;\nusing Nancy.Testing;\nusing Newtonsoft.Json;\nusing Xunit;\n\nnamespace App.Tests\n{\n    public class RedisModuleTests\n    {\n        private readonly Browser _browser;\n        private readonly DateTime _utcNow;\n        private readonly IRedisDb _redisDb;\n\n        public RedisModuleTests()\n        {\n            _redisDb = A.Fake\u003cIRedisDb\u003e();\n            _utcNow = DateTime.UtcNow;\n            var timeProvider = new FixedTimeProvider(_utcNow);\n\n            _browser = new Browser(with =\u003e\n            {\n                with.Module\u003cRedisModule\u003e();\n                with.Dependency(timeProvider);\n                with.Dependency(_redisDb);\n                // https://github.com/NancyFx/Nancy/pull/1489\n                //with.ApplicationStartup((x, y) =\u003e JsonSettings.RetainCasing = true);\n                //with.Configure(env =\u003e env.Json(retainCasing: true));\n            });\n        }\n\n        [Fact]\n        public void ShouldStoreAndRetrieveFromRedis()\n        {\n            A.CallTo(() =\u003e _redisDb.StringGet(\"key1\")).Returns(\"value1\");\n            \n            var response = _browser.Get(\"/redis/key1/value1\", with =\u003e {\n                with.HttpRequest();\n            }).Result;\n\n            // https://stackoverflow.com/questions/1207731/how-can-i-deserialize-json-to-a-simple-dictionarystring-string-in-asp-net\n            // https://stackoverflow.com/questions/31089347/testing-a-rest-api-in-nancy\n            //var responseDict = response.Body.DeserializeJson\u003cDictionary\u003cstring, string\u003e\u003e();  // empty dict for me\n            var responseDict = JsonConvert.DeserializeObject\u003cDictionary\u003cstring, string\u003e\u003e(response.Body.AsString());\n            var expectedDict = new Dictionary\u003cstring, string\u003e\n            {\n                {\"key1\", \"value1\"},\n                {\"ticks\", _utcNow.Ticks.ToString()},\n            };\n\n            A.CallTo(() =\u003e _redisDb.StringSet(\"key1\", \"value1\")).MustHaveHappened();\n\n            Assert.Equal(HttpStatusCode.OK, response.StatusCode);\n            // https://github.com/xunit/xunit/blob/master/test/test.xunit.assert/Asserts/CollectionAssertsTests.cs\n            //Assert.True(response.Headers.ContainsKey(\"content-type\"));\n            //Assert.Equal(\"application/json\", response.Headers[\"content-type\"]);\n            Assert.Equal(expectedDict, responseDict);\n        }\n    }\n}\nEOF\n\n\n# But we do need to update the implementations to make them more easily testable:\n\ncat \u003c\u003c EOF \u003e App.Web/IProvideTime.cs\nusing System;\n\nnamespace App.Web\n{\n    public interface IProvideTime\n    {\n        DateTime UtcNow { get; }\n    }\n\n    public class SystemTimeProvider : IProvideTime\n    {\n        public DateTime UtcNow =\u003e DateTime.UtcNow;\n    }\n\n    public class FixedTimeProvider : IProvideTime\n    {\n        public DateTime UtcNow { get; private set; }\n\n        public FixedTimeProvider(DateTime dateTime)\n        {\n            UtcNow = dateTime;\n        }\n\n        public void Update(DateTime dateTime)\n        {\n            UtcNow = dateTime;\n        }\n\n        public void Add(TimeSpan timeSpan)\n        {\n            UtcNow += timeSpan;\n        }\n    }\n}\nEOF\n\ncat \u003c\u003c EOF \u003e App.Web/BaseModule.cs\nusing Nancy;\n\nnamespace App.Web\n{\n    public class BaseModule : NancyModule\n    {\n        public BaseModule(IProvideTime timeProvider) : base(\"/\")\n        {\n            Get(\"/\", args =\u003e \"NancyFX Hello @ \" + timeProvider.UtcNow);\n        }\n    }\n}\nEOF\n\ncat \u003c\u003c EOF \u003e App.Web/PostgresModule.cs\nusing System;\nusing System.Collections.Generic;\nusing System.Data;\nusing Dapper;\nusing Nancy;\n\nnamespace App.Web\n{\n    public class PostgresModule : NancyModule\n    {\n        public PostgresModule(IProvideTime timeProvider, IPostgresDb postgresDb) : base(\"/postgres\")\n        {\n            // this is very bad REST, I know\n            Get(\"/{key}/{value}\", args =\u003e\n            {\n                var key = args.key.ToString();\n                var value = args.value.ToString();\n\n                var now = timeProvider.UtcNow;\n                var id = Guid.NewGuid();\n\n                // Insert some data\n                var newThing = new Thing\n                {\n                    Id = id,\n                    Name = $\"Hello world {now}: {key} = {value}\",\n                    Enabled = true,\n                    CreatedAt = now.AddMinutes(-10),\n                    UpdatedAt = now.AddMinutes(10),\n                };\n\n                postgresDb.InsertThing(newThing);\n                Console.WriteLine(\"inserted via dapper: {0}: {1}={2}\", now, key, value);\n\n                var things = postgresDb.AllThings();\n                foreach (var thing in things)\n                    Console.WriteLine(thing);\n                Console.WriteLine(\"read all dapper\");\n\n                var addedThing = postgresDb.FindThing(id);\n                Console.WriteLine(\"added: {0}\", addedThing);\n\n                return timeProvider.UtcNow.ToString();\n            });\n        }\n    }\n\n    public class Thing\n    {\n        public Guid Id { get; set; }\n        public string Name { get; set; }\n        public bool Enabled { get; set; }\n        public DateTime CreatedAt { get; set; }\n        public DateTime UpdatedAt { get; set; }\n\n        public override string ToString()\n        {\n            return $\"{nameof(Id)}: {Id}, {nameof(Name)}: {Name}, {nameof(Enabled)}: {Enabled}, {nameof(CreatedAt)}: {CreatedAt}, {nameof(UpdatedAt)}: {UpdatedAt}\";\n        }\n    }\n\n    public interface IPostgresDb\n    {\n        void InsertThing(Thing thing);\n        IEnumerable\u003cThing\u003e AllThings();\n        Thing FindThing(Guid id);\n    }\n\n    public class PostgresDb : IPostgresDb\n    {\n        private readonly string _npgsqlConnString;\n\n        public PostgresDb()\n        {\n            var databaseUrlString = Environment.GetEnvironmentVariable(\"DATABASE_URL\");\n            Console.WriteLine($\"DATABASE_URL from ENV: {databaseUrlString}\");\n            // https://devcenter.heroku.com/articles/connecting-to-relational-databases-on-heroku-with-java#using-the-database_url-in-plain-jdbc\n            var dbUri = new Uri(databaseUrlString);\n            var userInfo = dbUri.UserInfo.Split(':');\n            _npgsqlConnString = $\"Host={dbUri.Host};Port={dbUri.Port};Database={dbUri.LocalPath.Substring(1)};Username={userInfo[0]};Password={userInfo[1]}\";\n            Console.WriteLine($\"constructed npgsqlConnString: {_npgsqlConnString}\");\n\n            // # https://stackoverflow.com/questions/8902674/manually-map-column-names-with-class-properties/34536863#34536863\n            DefaultTypeMap.MatchNamesWithUnderscores = true;\n        }\n\n        private IDbConnection NewConn()\n        {\n            // http://www.npgsql.org/doc/connection-string-parameters.html#pooling\n            // http://www.npgsql.org/doc/performance.html\n            return new Npgsql.NpgsqlConnection(_npgsqlConnString);\n        }\n\n        public void InsertThing(Thing newThing)\n        {\n            // https://github.com/StackExchange/Dapper\n            // http://dapper-tutorial.net/dapper\n            using (var dbConn = NewConn())\n            {\n                dbConn.Open();\n\n                dbConn.Execute(\n                    \"INSERT INTO things\" +\n                    \" (id, name, enabled, created_at, updated_at)\" +\n                    \" VALUES (@Id, @Name, @Enabled, @CreatedAt, @UpdatedAt)\",\n                    newThing\n                );\n            }\n        }\n\n        public IEnumerable\u003cThing\u003e AllThings()\n        {\n            using (var dbConn = NewConn())\n            {\n                dbConn.Open();\n\n                return dbConn.Query\u003cThing\u003e(\"SELECT * FROM things\");\n            }\n        }\n        \n        public Thing FindThing(Guid id)\n        {\n            using (var dbConn = NewConn())\n            {\n                dbConn.Open();\n\n                return dbConn.QuerySingleOrDefault\u003cThing\u003e(\n                    \"SELECT * FROM things WHERE id = @Id\",\n                    new { Id = id }\n                );\n            }\n        }\n    }\n}\nEOF\n\ncat \u003c\u003c EOF \u003e App.Web/RedisModule.cs\nusing System;\nusing System.Collections.Generic;\nusing Nancy;\nusing StackExchange.Redis;\n\nnamespace App.Web\n{\n    public class RedisModule : NancyModule\n    {\n        public RedisModule(IProvideTime timeProvider, IRedisDb redisDb) : base(\"/redis\")\n        {\n            // this is very bad REST, I know\n            Get(\"/{key}/{value}\", args =\u003e\n            {\n                var key = args.key.ToString();\n                var value = args.value.ToString();\n\n                redisDb.StringSet(key, value);\n                Console.WriteLine(\"redis set: {0}={1}\", key, value);\n\n                var redisValue = redisDb.StringGet(key);\n                Console.WriteLine(\"redis get: {0}\", redisValue);\n\n                var dict = new Dictionary\u003cstring, string\u003e\n                {\n                    {key, redisValue},\n                    {\"ticks\", timeProvider.UtcNow.Ticks.ToString()},\n                };\n\n                // https://github.com/NancyFx/Nancy/wiki/Content-Negotiation\n                var response = Response.AsJson(dict);\n                //response.ContentType = \"application/json\";\n                return response;\n            });\n        }\n    }\n\n    public interface IRedisDb\n    {\n        void StringSet(string key, string value);\n        string StringGet(string key);\n    }\n\n    public class RedisDb : IRedisDb\n    {\n        private readonly Lazy\u003cIDatabase\u003e _redisDb;\n\n        public RedisDb()\n        {\n            // https://github.com/redis/redis-rb/blob/master/lib/redis/client.rb#L408\n            // https://msdn.microsoft.com/en-us/library/system.uri(v=vs.110).aspx\n            // https://stackexchange.github.io/StackExchange.Redis/Configuration\n            var redisUrlString = Environment.GetEnvironmentVariable(\"REDIS_URL\");\n            Console.WriteLine($\"REDIS_URL from ENV: {redisUrlString}\");\n            var redisUri = new Uri(redisUrlString);\n            var userInfo = redisUri.UserInfo.Split(':');\n            var redisConnString = $\"{redisUri.Host}:{redisUri.Port},password={userInfo[1]}\";\n            Console.WriteLine($\"constructed redisConnString: {redisConnString}\");\n\n            _redisDb = new Lazy\u003cIDatabase\u003e(() =\u003e\n                ConnectionMultiplexer.Connect(redisConnString).GetDatabase());\n        }\n\n        public void StringSet(string key, string value)\n        {\n            _redisDb.Value.StringSet(key, value);\n        }\n\n        public string StringGet(string key)\n        {\n            return _redisDb.Value.StringGet(key);\n        }\n    }\n}\nEOF\n\ndotnet clean \u0026\u0026 dotnet test\n```\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frogusdev%2Fdocker_heroku_dotnet","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frogusdev%2Fdocker_heroku_dotnet","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frogusdev%2Fdocker_heroku_dotnet/lists"}