dev asp.net, dotnet_core

Developing projects is fun and a great way to learn new stuff. The things is everything moves so fast, you constantly need to maintain the projects as well. In that spirit, I decided to re-write my old AngularJS DomChk53.

First, I developed an API. This way I won’t have to maintain my AWS Lambda functions. Even though nobody uses them, still opening a public API poses risks and maintenance hassle. Now everybody can run their own system locally using their own AWS accounts.

API Implementation

Full source code of the API can be found in DomChk53 repo under aspnet-api folder. There’s not much benefit in covering the code line by line but I want to talk about some of the stuff that stood out in one way or the other:

CS5001: Program does not contain a static ‘Main’ method” error

I started this API with the intention of deploying it to Docker so right after I created a new project I tested Docker deployment but it kept giving the error above. After some searching I found that the Dockerfile was in the wrong folder! I moved it one level up as suggested in this answer and it worked like a charm!

Reading config values

I appreciated the ease of using configuration files. IConfiguration provider is provided out-of-the-box which handles appsettings.json config file.

So for example I was able to inject it easily:

public TldService(IConfiguration configuration, IFetchHtmlService fetchHtmlService)
{
    this.configuration = configuration;
    this.fetchHtmlService = fetchHtmlService;
}

and use it like this:

var awsDocumentationUrl = configuration["AWS.DocumentationURL"];

Using dependency injection

ASP.NET Core Web API has built-in dependency injection so when I needed to use it all I had to do was register my classes in ConfigureServices method:

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

    services.AddTransient<ITldService, TldService>();
    services.AddTransient<IFetchHtmlService, FetchHtmlService>();
}

Documentation

Adding Swagger documentation was a breeze. I just followed the steps in the documentation and now I have a pretty documentation. More on that in the usage section below.

AWS Implementation

Even though the code will run locally in a Docker container, you still need to setup an IAM user with the appropriate permissions. So make sure the policy below is attached to the user:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "route53domains:CheckDomainAvailability",
            "Resource": "*"
        }
    ]
}

Usage

Nice thing about Swagger is it can used as a full-blown API client as well. So instead of developing a dummy client I just used Swagger UI for simple domain availability enquiries. This is until I develop a better frontend on my own. For now at least it works.

To use Swagger, simply run the Domchk53.API application and change the URL to https://localhost:{port number}/docs, port number being whatever your local web server is running on.

curl usage:

Windows:

curl -i -X POST -H "Content-Type: application/json" -d "{\"domainList\": [\"volkan\"], \"tldList\": [\"com\", \"net\"]}" https://localhost:5001/api/domain

Mac:

curl --insecure -X POST -H "Content-Type: application/json" -d '{"domainList": ["volkan"], "tldList": ["com", "net"]}' https://localhost:5001/api/domain

Example Test data:

This is just a quick reference to be used as template:

{
  "domainList": ["domain1", "domain2"],
  "tldList": ["com", "net"]
}
{ "domainList": ["volkan"], "tldList": ["com", "net"] }

Resources

dev javascript, tdd, unit_testing

As applications get more complex, they come with more dependencies. Mocking helps us isolate the subject under test so that our unit tests can run any external dependencies. In this example, I’d like to show a simple mocking example using Jasmine and Karma in an Angular app.

Case study: Mocking a HTTP dependency

In this example the service I want to test is called TldService. It basically gets a raw HTML from a URL and extracts some data using regex.

It looks something like this

export class TldService {

  constructor(private fetchHtmlService: FetchHtmlService) { }

  getAllSupportedTlds(): string[] {
    const rawHtml = this.fetchHtmlService.getRawHtml();
    const supportedTlds = this.parseHtml(rawHtml);
    return supportedTlds;
  }

  private parseHtml(html: string): string[] {
    const pattern = /some-regex-pattern/g;
    const regEx = new RegExp(pattern);
    let tldList = [];

    let match = regEx.exec(html);
    console.log(match);

    while (match !== null) {
      tldList.push(match[1]);
      match = regEx.exec(html);
    }

    return tldList;
  }
}

This service depends on FetchHtmlService which does the getting HTML part. Nice thing about injecting this dependency is that we can replace it with a fake one while testing. This way we can test it without even having to implement the dependency.

import { TestBed } from '@angular/core/testing';
import { TldService } from './tld.service';
import { FetchHtmlService } from './fetch-html.service';

describe('TldService', () => {
  beforeEach(() => TestBed.configureTestingModule({

  }));

  it('should be created', () => {
    const service: TldService = TestBed.get(TldService);
    expect(service).toBeTruthy();
  });

  it('should parse html and extract TLD list', () => {
    const fetchHtmlService: FetchHtmlService = new FetchHtmlService();
    spyOn(fetchHtmlService, 'getRawHtml').and.returnValue('<a href="registrar-tld-list.html#academy">.academy</a>');
    const service: TldService = new TldService(fetchHtmlService);
    expect(service.getAllSupportedTlds().length).toBe(1);
  });
});

In the 2nd test above we are creating a new FetchHtmlService and overriding the getRawHtml function behaviour by using Jasmine’s spyOn method.

That’s it! Now we don’t need a network connection to make actual calls while testing our code. We can develop and test our service in isolation independent from the dependency!

Resources

devdevops git

When you work on the same repository on multiple computers it’s easy to forget to push code and leave some changes local. To avoid that I decided to develop a tiny project that loops through all the folders and pulls remote changes then commits and pushes all the local changes.

The script itself it very simple. It first pulls the latest and pushes the local changes:

Function Sync-Git {
    git fetch --all
    git pull
    git add .
    git commit -m 'git-sync'
    git push
}

$rootFolder = '/rootFolder'

Write-Host "Changing directory to root folder: $rootFolder"
Set-Location $rootFolder
Get-Location | Write-Host

Get-ChildItem -Directory | ForEach-Object { 
    Set-Location -Path $_ 
    Write-Host $_
    Sync-Git
    Set-Location $rootFolder
}

Write-Host "Done"

Dockerfile is based Microsoft’s Powershell image and it only needs to install Git on it:

FROM mcr.microsoft.com/powershell

RUN apt-get update && apt-get install -y git
RUN git config --global user.email "you@example.com"

RUN echo "    IdentityFile ~/.ssh/id_rsa" >> /etc/ssh/ssh_config
RUN echo "    StrictHostKeyChecking no" >> /etc/ssh/ssh_config

RUN mkdir /home/git-sync
WORKDIR /home/git-sync

COPY ./git-sync.ps1 .

ENTRYPOINT ["pwsh", "git-sync.ps1"]

Build it at the root folder:

docker build . -t {image name}

And run it as:

docker run --rm -d -v /host/path/to/root/git/folder:/rootFolder -v ~/.ssh:/root/.ssh:ro {image name}

Or pull my image from Docker Hub:

docker pull volkanpaksoy/git-sync

Issues

fatal: unable to auto-detect email address

GitHub didn’t allow me to push my changes without an email address so I had to add the command below to Dockerfile:

git config --global user.email "you@example.com"

It doesn’t look good maybe but I don’t care too much about email addresses in commits anyway so I’m fine with it.

“WARNING: UNPROTECTED PRIVATE KEY FILE!” error

This might also be an issue in which case the solution is simple (assuming the name of the private key file is id_rsa):

chmod 600 ~/.ssh/id_rsa 

Bad configuration option

Make sure to have the following options in the config file in your .ssh directory:

Host *
  StrictHostKeyChecking no
  AddKeysToAgent yes
  IgnoreUnknown UseKeychain
  UseKeychain yes
  IdentityFile ~/.ssh/id_rsa

Source Code

Resources