diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..0626272
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,37 @@
+*.swp
+*.*~
+project.lock.json
+.DS_Store
+*.pyc
+nupkg/
+
+# Visual Studio Code
+.vscode
+
+# Rider
+.idea
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+build/
+bld/
+[Bb]in/
+[Oo]bj/
+[Oo]ut/
+msbuild.log
+msbuild.err
+msbuild.wrn
+
+# Visual Studio 2015
+.vs/
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..e035aa6
--- /dev/null
+++ b/README.md
@@ -0,0 +1,11 @@
+# ThaiNationalIDCard.NET
+
+ThaiNationalIDCard.NET is wrapper classes for .NET based on the [PCSC](https://github.com/danm-de/pcsc-sharp) that provides access to the Thai National ID Card (Smart Card). The wrapper is written with .NET Standard version that are available on all .NET implementations.
+
+# Example
+* Console Application
+* Web API
+
+# Credits
+* [ThaiNationalIDCard](https://github.com/chakphanu/ThaiNationalIDCard)
+* [PC/SC wrapper classes for .NET](https://github.com/danm-de/pcsc-sharp)
\ No newline at end of file
diff --git a/ThaiNationalIDCard.NET.Example.ConsoleApp/Program.cs b/ThaiNationalIDCard.NET.Example.ConsoleApp/Program.cs
new file mode 100644
index 0000000..23bc7ea
--- /dev/null
+++ b/ThaiNationalIDCard.NET.Example.ConsoleApp/Program.cs
@@ -0,0 +1,34 @@
+using System;
+using ThaiNationalIDCard.NET.Models;
+
+namespace ThaiNationalIDCard.NET.Example.ConsoleApp
+{
+ class Program
+ {
+ static void Main(string[] args)
+ {
+ try
+ {
+ ThaiNationalIDCardReader cardReader = new ThaiNationalIDCardReader();
+ PersonalPhoto personalPhoto = cardReader.GetPersonalPhoto();
+
+ Console.WriteLine($"CitizenID: {personalPhoto.CitizenID}");
+ Console.WriteLine($"ThaiPersonalInfo: {personalPhoto.ThaiPersonalInfo}");
+ Console.WriteLine($"EnglishPersonalInfo: {personalPhoto.EnglishPersonalInfo}");
+ Console.WriteLine($"Sex: {personalPhoto.Sex}");
+ Console.WriteLine($"AddressInfo: {personalPhoto.AddressInfo}");
+ Console.WriteLine($"IssueDate: {personalPhoto.IssueDate}");
+ Console.WriteLine($"ExpireDate: {personalPhoto.ExpireDate}");
+ Console.WriteLine($"Issuer: {personalPhoto.Issuer}");
+ Console.WriteLine($"Photo: {personalPhoto.Photo}");
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine(e);
+ }
+
+ Console.WriteLine("Please any key to exit...");
+ Console.ReadKey(true);
+ }
+ }
+}
diff --git a/ThaiNationalIDCard.NET.Example.ConsoleApp/ThaiNationalIDCard.NET.Example.ConsoleApp.csproj b/ThaiNationalIDCard.NET.Example.ConsoleApp/ThaiNationalIDCard.NET.Example.ConsoleApp.csproj
new file mode 100644
index 0000000..64c3385
--- /dev/null
+++ b/ThaiNationalIDCard.NET.Example.ConsoleApp/ThaiNationalIDCard.NET.Example.ConsoleApp.csproj
@@ -0,0 +1,12 @@
+
+
+
+ Exe
+ netcoreapp3.1
+
+
+
+
+
+
+
diff --git a/ThaiNationalIDCard.NET.Example.WebApi/Controllers/ReadersController.cs b/ThaiNationalIDCard.NET.Example.WebApi/Controllers/ReadersController.cs
new file mode 100644
index 0000000..921814b
--- /dev/null
+++ b/ThaiNationalIDCard.NET.Example.WebApi/Controllers/ReadersController.cs
@@ -0,0 +1,36 @@
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using System;
+using ThaiNationalIDCard.NET.Models;
+
+namespace ThaiNationalIDCard.NET.Example.WebApi.Controllers
+{
+ [Route("api/[controller]")]
+ [ApiController]
+ public class ReadersController : ControllerBase
+ {
+ private readonly ThaiNationalIDCardReader cardReader;
+
+ public ReadersController(ThaiNationalIDCardReader cardReader)
+ {
+ this.cardReader = cardReader;
+ }
+
+ [HttpGet("Read")]
+ [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(PersonalPhoto))]
+ [ProducesResponseType(StatusCodes.Status400BadRequest)]
+ public IActionResult Read()
+ {
+ try
+ {
+ PersonalPhoto personalPhoto = cardReader.GetPersonalPhoto();
+
+ return Ok(personalPhoto);
+ }
+ catch (Exception e)
+ {
+ return BadRequest(e);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ThaiNationalIDCard.NET.Example.WebApi/Program.cs b/ThaiNationalIDCard.NET.Example.WebApi/Program.cs
new file mode 100644
index 0000000..b491573
--- /dev/null
+++ b/ThaiNationalIDCard.NET.Example.WebApi/Program.cs
@@ -0,0 +1,20 @@
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Hosting;
+
+namespace ThaiNationalIDCard.NET.Example.WebApi
+{
+ public class Program
+ {
+ public static void Main(string[] args)
+ {
+ CreateHostBuilder(args).Build().Run();
+ }
+
+ public static IHostBuilder CreateHostBuilder(string[] args) =>
+ Host.CreateDefaultBuilder(args)
+ .ConfigureWebHostDefaults(webBuilder =>
+ {
+ webBuilder.UseStartup();
+ });
+ }
+}
diff --git a/ThaiNationalIDCard.NET.Example.WebApi/Properties/launchSettings.json b/ThaiNationalIDCard.NET.Example.WebApi/Properties/launchSettings.json
new file mode 100644
index 0000000..080bb32
--- /dev/null
+++ b/ThaiNationalIDCard.NET.Example.WebApi/Properties/launchSettings.json
@@ -0,0 +1,30 @@
+{
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:55527",
+ "sslPort": 0
+ }
+ },
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "profiles": {
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "launchUrl": "api/readers/read",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "ThaiNationalIDCard.Example.WebApi": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "launchUrl": "weatherforecast",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "applicationUrl": "http://localhost:5000"
+ }
+ }
+}
\ No newline at end of file
diff --git a/ThaiNationalIDCard.NET.Example.WebApi/Startup.cs b/ThaiNationalIDCard.NET.Example.WebApi/Startup.cs
new file mode 100644
index 0000000..d8e1c7b
--- /dev/null
+++ b/ThaiNationalIDCard.NET.Example.WebApi/Startup.cs
@@ -0,0 +1,42 @@
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+
+namespace ThaiNationalIDCard.NET.Example.WebApi
+{
+ public class Startup
+ {
+ public IConfiguration Configuration { get; }
+
+ public Startup(IConfiguration configuration)
+ {
+ Configuration = configuration;
+ }
+
+ public void ConfigureServices(IServiceCollection services)
+ {
+ services.AddControllers();
+
+ services.AddScoped();
+ }
+
+ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
+ {
+ if (env.IsDevelopment())
+ {
+ app.UseDeveloperExceptionPage();
+ }
+
+ app.UseRouting();
+
+ app.UseAuthorization();
+
+ app.UseEndpoints(endpoints =>
+ {
+ endpoints.MapControllers();
+ });
+ }
+ }
+}
diff --git a/ThaiNationalIDCard.NET.Example.WebApi/ThaiNationalIDCard.NET.Example.WebApi.csproj b/ThaiNationalIDCard.NET.Example.WebApi/ThaiNationalIDCard.NET.Example.WebApi.csproj
new file mode 100644
index 0000000..3a1e758
--- /dev/null
+++ b/ThaiNationalIDCard.NET.Example.WebApi/ThaiNationalIDCard.NET.Example.WebApi.csproj
@@ -0,0 +1,18 @@
+
+
+
+ netcoreapp3.1
+ Pichid Detson
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ThaiNationalIDCard.NET.Example.WebApi/appsettings.Development.json b/ThaiNationalIDCard.NET.Example.WebApi/appsettings.Development.json
new file mode 100644
index 0000000..8983e0f
--- /dev/null
+++ b/ThaiNationalIDCard.NET.Example.WebApi/appsettings.Development.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft": "Warning",
+ "Microsoft.Hosting.Lifetime": "Information"
+ }
+ }
+}
diff --git a/ThaiNationalIDCard.NET.Example.WebApi/appsettings.json b/ThaiNationalIDCard.NET.Example.WebApi/appsettings.json
new file mode 100644
index 0000000..d9d9a9b
--- /dev/null
+++ b/ThaiNationalIDCard.NET.Example.WebApi/appsettings.json
@@ -0,0 +1,10 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft": "Warning",
+ "Microsoft.Hosting.Lifetime": "Information"
+ }
+ },
+ "AllowedHosts": "*"
+}
diff --git a/ThaiNationalIDCard.NET.sln b/ThaiNationalIDCard.NET.sln
new file mode 100644
index 0000000..2f4d4bf
--- /dev/null
+++ b/ThaiNationalIDCard.NET.sln
@@ -0,0 +1,37 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29613.14
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThaiNationalIDCard.NET", "ThaiNationalIDCard.NET\ThaiNationalIDCard.NET.csproj", "{754802D9-509B-4FE1-B9BB-8F0838B07AA7}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThaiNationalIDCard.NET.Example.ConsoleApp", "ThaiNationalIDCard.NET.Example.ConsoleApp\ThaiNationalIDCard.NET.Example.ConsoleApp.csproj", "{94485686-CAF7-43FB-B425-DD9F13EFAFF5}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThaiNationalIDCard.NET.Example.WebApi", "ThaiNationalIDCard.NET.Example.WebApi\ThaiNationalIDCard.NET.Example.WebApi.csproj", "{EC52B1CF-6467-4506-A7BC-CAD49E9F202F}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {754802D9-509B-4FE1-B9BB-8F0838B07AA7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {754802D9-509B-4FE1-B9BB-8F0838B07AA7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {754802D9-509B-4FE1-B9BB-8F0838B07AA7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {754802D9-509B-4FE1-B9BB-8F0838B07AA7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {94485686-CAF7-43FB-B425-DD9F13EFAFF5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {94485686-CAF7-43FB-B425-DD9F13EFAFF5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {94485686-CAF7-43FB-B425-DD9F13EFAFF5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {94485686-CAF7-43FB-B425-DD9F13EFAFF5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {EC52B1CF-6467-4506-A7BC-CAD49E9F202F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EC52B1CF-6467-4506-A7BC-CAD49E9F202F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EC52B1CF-6467-4506-A7BC-CAD49E9F202F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {EC52B1CF-6467-4506-A7BC-CAD49E9F202F}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {61BA88EB-152B-47AA-970F-5E6FDF7BA682}
+ EndGlobalSection
+EndGlobal
diff --git a/ThaiNationalIDCard.NET/Interfaces/IThaiNationalIDCardAPDUCommand.cs b/ThaiNationalIDCard.NET/Interfaces/IThaiNationalIDCardAPDUCommand.cs
new file mode 100644
index 0000000..5c9171f
--- /dev/null
+++ b/ThaiNationalIDCard.NET/Interfaces/IThaiNationalIDCardAPDUCommand.cs
@@ -0,0 +1,15 @@
+namespace ThaiNationalIDCard.NET.Interfaces
+{
+ public interface IThaiNationalIDCardAPDUCommand
+ {
+ byte[] GetResponse();
+ byte[] Select(byte[] command);
+ byte[] MinistryOfInteriorAppletCommand { get; }
+ byte[] CitizenIDCommand { get; }
+ byte[] PersonalInfoCommand { get; }
+ byte[] AddressInfoCommand { get; }
+ byte[] CardIssueExpireCommand { get; }
+ byte[] CardIssuerCommand { get; }
+ byte[][] PhotoCommand { get; }
+ }
+}
diff --git a/ThaiNationalIDCard.NET/Models/AddressInfo.cs b/ThaiNationalIDCard.NET/Models/AddressInfo.cs
new file mode 100644
index 0000000..6327504
--- /dev/null
+++ b/ThaiNationalIDCard.NET/Models/AddressInfo.cs
@@ -0,0 +1,51 @@
+namespace ThaiNationalIDCard.NET.Models
+{
+ public class AddressInfo
+ {
+ public string HouseNo { get; set; }
+ public string VillageNo { get; set; }
+ public string Lane { get; set; }
+ public string Road { get; set; }
+ public string SubDistrict { get; set; }
+ public string District { get; set; }
+ public string Province { get; set; }
+
+ public AddressInfo(string addressInfo)
+ {
+ string[] infos = addressInfo.Split('#');
+
+ HouseNo = infos[0].Trim();
+ VillageNo = infos[1].Trim();
+ Lane = infos[2].Trim();
+ Road = infos[3].Trim();
+ SubDistrict = infos[5].Trim();
+ District = infos[6].Trim();
+ Province = infos[7].Trim();
+ }
+
+ public override string ToString()
+ {
+ string address = HouseNo;
+
+ if (!string.IsNullOrEmpty(VillageNo))
+ address += string.Format($" ม.{VillageNo}");
+
+ if (!string.IsNullOrEmpty(Lane))
+ address += string.Format($" ซ.{Lane}");
+
+ if (!string.IsNullOrEmpty(Road))
+ address += string.Format($" ถ.{Road}");
+
+ if (!string.IsNullOrEmpty(SubDistrict))
+ address += string.Format($" ต.{SubDistrict}");
+
+ if (!string.IsNullOrEmpty(District))
+ address += string.Format($" อ.{District}");
+
+ if (!string.IsNullOrEmpty(Province))
+ address += string.Format($" จ.{Province}");
+
+ return address;
+ }
+ }
+}
diff --git a/ThaiNationalIDCard.NET/Models/Personal.cs b/ThaiNationalIDCard.NET/Models/Personal.cs
new file mode 100644
index 0000000..e037996
--- /dev/null
+++ b/ThaiNationalIDCard.NET/Models/Personal.cs
@@ -0,0 +1,16 @@
+using System;
+
+namespace ThaiNationalIDCard.NET.Models
+{
+ public class Personal
+ {
+ public string CitizenID { get; set; }
+ public PersonalInfo ThaiPersonalInfo { get; set; }
+ public PersonalInfo EnglishPersonalInfo { get; set; }
+ public string Sex { get; set; }
+ public AddressInfo AddressInfo { get; set; }
+ public DateTime IssueDate { get; set; }
+ public DateTime ExpireDate { get; set; }
+ public string Issuer { get; set; }
+ }
+}
diff --git a/ThaiNationalIDCard.NET/Models/PersonalInfo.cs b/ThaiNationalIDCard.NET/Models/PersonalInfo.cs
new file mode 100644
index 0000000..db022fa
--- /dev/null
+++ b/ThaiNationalIDCard.NET/Models/PersonalInfo.cs
@@ -0,0 +1,25 @@
+namespace ThaiNationalIDCard.NET.Models
+{
+ public class PersonalInfo
+ {
+ public string Prefix { get; set; }
+ public string FirstName { get; set; }
+ public string MiddleName { get; set; }
+ public string LastName { get; set; }
+
+ public PersonalInfo(string personalInfo)
+ {
+ string[] infos = personalInfo.Split('#');
+
+ Prefix = infos[0].Trim();
+ FirstName = infos[1].Trim();
+ MiddleName = infos[2].Trim();
+ LastName = infos[3].Trim();
+ }
+
+ public override string ToString()
+ {
+ return string.Format($"{Prefix}{FirstName} {LastName}");
+ }
+ }
+}
diff --git a/ThaiNationalIDCard.NET/Models/PersonalPhoto.cs b/ThaiNationalIDCard.NET/Models/PersonalPhoto.cs
new file mode 100644
index 0000000..5cac0bc
--- /dev/null
+++ b/ThaiNationalIDCard.NET/Models/PersonalPhoto.cs
@@ -0,0 +1,19 @@
+namespace ThaiNationalIDCard.NET.Models
+{
+ public class PersonalPhoto : Personal
+ {
+ public string Photo { get; set; }
+
+ public PersonalPhoto(Personal personal)
+ {
+ CitizenID = personal.CitizenID;
+ ThaiPersonalInfo = personal.ThaiPersonalInfo;
+ EnglishPersonalInfo = personal.EnglishPersonalInfo;
+ Sex = personal.Sex;
+ AddressInfo = personal.AddressInfo;
+ IssueDate = personal.IssueDate;
+ ExpireDate = personal.ExpireDate;
+ Issuer = personal.Issuer;
+ }
+ }
+}
diff --git a/ThaiNationalIDCard.NET/Models/ThaiNationalIDCardAPDUCommand.cs b/ThaiNationalIDCard.NET/Models/ThaiNationalIDCardAPDUCommand.cs
new file mode 100644
index 0000000..9564b3f
--- /dev/null
+++ b/ThaiNationalIDCard.NET/Models/ThaiNationalIDCardAPDUCommand.cs
@@ -0,0 +1,53 @@
+using System.Linq;
+using ThaiNationalIDCard.NET.Interfaces;
+
+namespace ThaiNationalIDCard.NET.Models
+{
+ public abstract class ThaiNationalIDCardAPDUCommand : IThaiNationalIDCardAPDUCommand
+ {
+ public byte[] MinistryOfInteriorAppletCommand => new byte[] { 0xA0, 0X00, 0x00, 0x00, 0x54, 0x48, 0x00, 0x01 };
+
+ public byte[] CitizenIDCommand => new byte[] { 0x80, 0xb0, 0x00, 0x04, 0x02, 0x00, 0x0d };
+
+ public byte[] PersonalInfoCommand => new byte[] { 0x80, 0xb0, 0x00, 0x11, 0x02, 0x00, 0xd1 };
+
+ public byte[] AddressInfoCommand => new byte[] { 0x80, 0xb0, 0x15, 0x79, 0x02, 0x00, 0x64 };
+
+ public byte[] CardIssueExpireCommand => new byte[] { 0x80, 0xb0, 0x01, 0x67, 0x02, 0x00, 0x12 };
+
+ public byte[] CardIssuerCommand => new byte[] { 0x80, 0xb0, 0x00, 0xf6, 0x02, 0x00, 0x64 };
+
+ public byte[][] PhotoCommand => new byte[][]
+ {
+ new byte[]{ 0x80, 0xB0, 0x01, 0x7B, 0x02, 0x00, 0xFF },
+ new byte[]{ 0x80, 0xB0, 0x02, 0x7A, 0x02, 0x00, 0xFF },
+ new byte[]{ 0x80, 0xB0, 0x03, 0x79, 0x02, 0x00, 0xFF },
+ new byte[]{ 0x80, 0xB0, 0x04, 0x78, 0x02, 0x00, 0xFF },
+ new byte[]{ 0x80, 0xB0, 0x05, 0x77, 0x02, 0x00, 0xFF },
+ new byte[]{ 0x80, 0xB0, 0x06, 0x76, 0x02, 0x00, 0xFF },
+ new byte[]{ 0x80, 0xB0, 0x07, 0x75, 0x02, 0x00, 0xFF },
+ new byte[]{ 0x80, 0xB0, 0x08, 0x74, 0x02, 0x00, 0xFF },
+ new byte[]{ 0x80, 0xB0, 0x09, 0x73, 0x02, 0x00, 0xFF },
+ new byte[]{ 0x80, 0xB0, 0x0A, 0x72, 0x02, 0x00, 0xFF },
+ new byte[]{ 0x80, 0xB0, 0x0B, 0x71, 0x02, 0x00, 0xFF },
+ new byte[]{ 0x80, 0xB0, 0x0C, 0x70, 0x02, 0x00, 0xFF },
+ new byte[]{ 0x80, 0xB0, 0x0D, 0x6F, 0x02, 0x00, 0xFF },
+ new byte[]{ 0x80, 0xB0, 0x0E, 0x6E, 0x02, 0x00, 0xFF },
+ new byte[]{ 0x80, 0xB0, 0x0F, 0x6D, 0x02, 0x00, 0xFF },
+ new byte[]{ 0x80, 0xB0, 0x10, 0x6C, 0x02, 0x00, 0xFF },
+ new byte[]{ 0x80, 0xB0, 0x11, 0x6B, 0x02, 0x00, 0xFF },
+ new byte[]{ 0x80, 0xB0, 0x12, 0x6A, 0x02, 0x00, 0xFF },
+ new byte[]{ 0x80, 0xB0, 0x13, 0x69, 0x02, 0x00, 0xFF },
+ new byte[]{ 0x80, 0xB0, 0x14, 0x68, 0x02, 0x00, 0xFF },
+ };
+
+ public abstract byte[] GetResponse();
+
+ public byte[] Select(byte[] command)
+ {
+ byte[] selectCommand = new byte[] { 0x00, 0xA4, 0x04, 0x00, (byte)command.Length };
+
+ return selectCommand.Concat(command).ToArray();
+ }
+ }
+}
diff --git a/ThaiNationalIDCard.NET/Models/ThaiNationalIDCardAPDUCommandType01.cs b/ThaiNationalIDCard.NET/Models/ThaiNationalIDCardAPDUCommandType01.cs
new file mode 100644
index 0000000..b80de73
--- /dev/null
+++ b/ThaiNationalIDCard.NET/Models/ThaiNationalIDCardAPDUCommandType01.cs
@@ -0,0 +1,10 @@
+namespace ThaiNationalIDCard.NET.Models
+{
+ public class ThaiNationalIDCardAPDUCommandType01 : ThaiNationalIDCardAPDUCommand
+ {
+ public override byte[] GetResponse()
+ {
+ return new byte[] { 0x00, 0xc0, 0x00, 0x01 };
+ }
+ }
+}
diff --git a/ThaiNationalIDCard.NET/Models/ThaiNationalIDCardAPDUCommandType02.cs b/ThaiNationalIDCard.NET/Models/ThaiNationalIDCardAPDUCommandType02.cs
new file mode 100644
index 0000000..694f508
--- /dev/null
+++ b/ThaiNationalIDCard.NET/Models/ThaiNationalIDCardAPDUCommandType02.cs
@@ -0,0 +1,10 @@
+namespace ThaiNationalIDCard.NET.Models
+{
+ class ThaiNationalIDCardAPDUCommandType02 : ThaiNationalIDCardAPDUCommand
+ {
+ public override byte[] GetResponse()
+ {
+ return new byte[] { 0x00, 0xc0, 0x00, 0x00 };
+ }
+ }
+}
diff --git a/ThaiNationalIDCard.NET/ThaiNationalIDCard.NET.csproj b/ThaiNationalIDCard.NET/ThaiNationalIDCard.NET.csproj
new file mode 100644
index 0000000..be259c8
--- /dev/null
+++ b/ThaiNationalIDCard.NET/ThaiNationalIDCard.NET.csproj
@@ -0,0 +1,20 @@
+
+
+
+ netstandard2.0
+ Pichid Detson
+ ThaiNationalIDCard.NET
+ Thai National ID Card Reader
+ true
+ Thai National ID Card Reader for .NET
+ https://github.com/bencomtech/ThaiNationalIDCard.NET
+ https://github.com/bencomtech/ThaiNationalIDCard.NET
+
+
+
+
+
+
+
+
+
diff --git a/ThaiNationalIDCard.NET/ThaiNationalIDCardReader.cs b/ThaiNationalIDCard.NET/ThaiNationalIDCardReader.cs
new file mode 100644
index 0000000..add5cee
--- /dev/null
+++ b/ThaiNationalIDCard.NET/ThaiNationalIDCardReader.cs
@@ -0,0 +1,240 @@
+using PCSC;
+using PCSC.Exceptions;
+using PCSC.Iso7816;
+using PCSC.Utils;
+using System;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using ThaiNationalIDCard.NET.Interfaces;
+using ThaiNationalIDCard.NET.Models;
+
+namespace ThaiNationalIDCard.NET
+{
+ public class ThaiNationalIDCardReader
+ {
+ private readonly ISCardContext context;
+ private readonly ISCardReader reader;
+
+ private SCardError error;
+ private IntPtr intPtr;
+ private IThaiNationalIDCardAPDUCommand apdu;
+
+ public ThaiNationalIDCardReader()
+ {
+ context = ContextFactory.Instance.Establish(SCardScope.System);
+ reader = new SCardReader(context);
+ apdu = new ThaiNationalIDCardAPDUCommandType02();
+
+ Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
+ }
+
+ public Personal GetPersonal()
+ {
+ try
+ {
+ Open();
+
+ return GetPersonalInfo();
+ }
+ catch (Exception e)
+ {
+ throw e;
+ }
+ finally
+ {
+ Close();
+ }
+ }
+
+ public PersonalPhoto GetPersonalPhoto()
+ {
+ MemoryStream stream = new MemoryStream();
+
+ try
+ {
+ Open();
+
+ Personal personal = GetPersonalInfo();
+ PersonalPhoto personalPhoto = new PersonalPhoto(personal);
+
+ byte[][] photoCommand = apdu.PhotoCommand;
+ byte[] responseBuffer;
+
+ for (int i = 0; i < photoCommand.Length; i++)
+ {
+ responseBuffer = SendCommand(photoCommand[i]);
+ stream.Write(responseBuffer, 0, responseBuffer.Length);
+ }
+
+ stream.Seek(0, SeekOrigin.Begin);
+
+ personalPhoto.Photo = string.Format($"data:image/jpeg;base64,{Convert.ToBase64String(stream.ToArray())}");
+
+ return personalPhoto;
+ }
+ catch (Exception e)
+ {
+ throw e;
+ }
+ finally
+ {
+ stream.Dispose();
+
+ Close();
+ }
+ }
+
+ private Personal GetPersonalInfo()
+ {
+ try
+ {
+ Personal personal = new Personal();
+
+ personal.CitizenID = GetUTF8FromAsciiBytes(SendCommand(apdu.CitizenIDCommand));
+
+ string personalInfo = GetUTF8FromAsciiBytes(SendCommand(apdu.PersonalInfoCommand));
+ string thaiPersonalInfo = personalInfo.Substring(0, 100);
+ string englishPersonalInfo = personalInfo.Substring(100, 100);
+
+ personal.ThaiPersonalInfo = new PersonalInfo(thaiPersonalInfo);
+ personal.EnglishPersonalInfo = new PersonalInfo(englishPersonalInfo);
+ personal.Sex = personalInfo.Substring(208, 1);
+
+ string addressInfo = GetUTF8FromAsciiBytes(SendCommand(apdu.AddressInfoCommand));
+
+ personal.AddressInfo = new AddressInfo(addressInfo);
+
+ string cardIssueExpire = GetUTF8FromAsciiBytes(SendCommand(apdu.CardIssueExpireCommand));
+
+ personal.IssueDate = new DateTime(Convert.ToInt32(cardIssueExpire.Substring(0, 4)) - 543
+ , Convert.ToInt32(cardIssueExpire.Substring(4, 2))
+ , Convert.ToInt32(cardIssueExpire.Substring(6, 2))
+ );
+ personal.ExpireDate = new DateTime(Convert.ToInt32(cardIssueExpire.Substring(8, 4)) - 543
+ , Convert.ToInt32(cardIssueExpire.Substring(12, 2))
+ , Convert.ToInt32(cardIssueExpire.Substring(14, 2))
+ );
+ personal.Issuer = GetUTF8FromAsciiBytes(SendCommand(apdu.CardIssuerCommand));
+
+ return personal;
+ }
+ catch (Exception e)
+ {
+ throw e;
+ }
+ }
+
+ private string GetUTF8FromAsciiBytes(byte[] asciiBytes)
+ {
+ byte[] utf8 = Encoding.Convert(
+ Encoding.GetEncoding("TIS-620"),
+ Encoding.UTF8,
+ asciiBytes
+ );
+
+ return Encoding.UTF8.GetString(utf8);
+ }
+
+ private byte[] SendCommand(byte[] command)
+ {
+ byte[] responseBuffer = new byte[256];
+
+ error = reader.Transmit(intPtr, command, ref responseBuffer);
+ HandleError();
+
+ ResponseApdu responseApdu = new ResponseApdu(responseBuffer, IsoCase.Case2Short, reader.ActiveProtocol);
+
+ if (responseApdu.SW1.Equals((byte)SW1Code.NormalDataResponse))
+ {
+ command = apdu.GetResponse().Concat(new byte[] { responseApdu.SW2 }).ToArray();
+ responseBuffer = new byte[258];
+
+ error = reader.Transmit(intPtr, command, ref responseBuffer);
+ HandleError();
+
+ if (responseBuffer.Length - responseApdu.SW2 == 2)
+ {
+ return responseBuffer.Take(responseBuffer.Length - 2).ToArray();
+ }
+ }
+
+ return responseBuffer;
+ }
+
+ private void Close()
+ {
+ reader.Disconnect(SCardReaderDisposition.Leave);
+ context.Release();
+ }
+
+ private void Open()
+ {
+ try
+ {
+ Thread.Sleep(1500);
+
+ string[] szReaders = context.GetReaders();
+ if (szReaders.Length <= 0)
+ throw new PCSCException(SCardError.NoReadersAvailable, "Could not find any Smartcard reader.");
+
+ error = reader.Connect(szReaders[0], SCardShareMode.Shared, SCardProtocol.T0 | SCardProtocol.T1);
+ HandleError();
+
+ intPtr = new IntPtr();
+ switch (reader.ActiveProtocol)
+ {
+ case SCardProtocol.T0:
+ intPtr = SCardPCI.T0;
+ break;
+ case SCardProtocol.T1:
+ intPtr = SCardPCI.T1;
+ break;
+ case SCardProtocol.Raw:
+ intPtr = SCardPCI.Raw;
+ break;
+ default:
+ throw new PCSCException(SCardError.ProtocolMismatch, "Protocol not supported: " + reader.ActiveProtocol.ToString());
+ }
+
+ error = reader.Status(out string[] readerNames, out SCardState state, out SCardProtocol protocol, out byte[] atrs);
+ HandleError();
+
+ if (atrs == null || atrs.Length < 2)
+ throw new PCSCException(SCardError.InvalidAtr);
+
+ if (atrs[0] == 0x3B && atrs[1] == 0x67)
+ apdu = new ThaiNationalIDCardAPDUCommandType01();
+
+ if (!SelectApplet())
+ throw new Exception("SmartCard not support (Can't select Ministry of Interior Applet).");
+ }
+ catch (PCSCException e)
+ {
+ throw e;
+ }
+ }
+
+ private bool SelectApplet()
+ {
+ byte[] command = apdu.Select(apdu.MinistryOfInteriorAppletCommand);
+ byte[] responseBuffer = new byte[256];
+
+ error = reader.Transmit(intPtr, command, ref responseBuffer);
+ HandleError();
+
+ ResponseApdu responseApdu = new ResponseApdu(responseBuffer, IsoCase.Case2Short, reader.ActiveProtocol);
+
+ return responseApdu.SW1.Equals((byte)SW1Code.NormalDataResponse) || responseApdu.SW1.Equals((byte)SW1Code.Normal);
+ }
+
+ private void HandleError()
+ {
+ if (error == SCardError.Success)
+ return;
+
+ throw new PCSCException(error, SCardHelper.StringifyError(error));
+ }
+ }
+}
diff --git a/cleanup-obj-bin.ps1 b/cleanup-obj-bin.ps1
new file mode 100644
index 0000000..3ba7e3e
--- /dev/null
+++ b/cleanup-obj-bin.ps1
@@ -0,0 +1 @@
+Get-ChildItem .\ -include bin,obj -Recurse | foreach ($_) { remove-item $_.fullname -Force -Recurse }
\ No newline at end of file