├── .nuget ├── NuGet.exe ├── NuGet.Config ├── packages.config └── NuGet.targets ├── tools └── phantom │ ├── Boo.Lang.dll │ ├── Phantom.exe │ ├── Ionic.Zip.dll │ ├── Rhino.DSL.dll │ ├── Phantom.Core.dll │ ├── Boo.Lang.Parser.dll │ ├── CSScriptLibrary.dll │ ├── Phantom.DSL.Boo.dll │ ├── Boo.Lang.Compiler.dll │ ├── Phantom.DSL.CSharp.dll │ ├── readme.txt │ └── License.html ├── src └── MarkEmbling.PostcodesIO │ ├── icon.png │ ├── Data │ ├── NearestResult.cs │ ├── TerminatedPostcodeData.cs │ ├── OutcodeData.cs │ ├── PostcodeCodesData.cs │ └── PostcodeData.cs │ ├── Exceptions │ ├── PostcodesIOApiException.cs │ └── PostcodesIOEmptyResponseException.cs │ ├── Results │ ├── RawResult.cs │ └── BulkQueryResult.cs │ ├── MarkEmbling.PostcodesIO.csproj │ ├── ReverseGeocodeQuery.cs │ ├── IPostcodesIOClient.cs │ └── PostcodesIOClient.cs ├── tests └── MarkEmbling.PostcodesIO.Tests │ ├── MarkEmbling.PostcodesIO.Tests.csproj │ ├── Integration │ ├── RandomTests.cs │ ├── ValidateTests.cs │ ├── BulkLookupTests.cs │ ├── AutocompleteTests.cs │ ├── QueryTests.cs │ ├── LookupLatLonTests.cs │ ├── BulkLookupLatLonTests.cs │ ├── NearestTests.cs │ └── LookupTests.cs │ └── Unit │ ├── ReverseGeocodeQueryTests.cs │ └── SerializabilityTests.cs ├── LICENSE ├── README.md ├── MarkEmbling.PostcodesIO.sln ├── .gitattributes └── .gitignore /.nuget/NuGet.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markembling/MarkEmbling.PostcodesIO/HEAD/.nuget/NuGet.exe -------------------------------------------------------------------------------- /tools/phantom/Boo.Lang.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markembling/MarkEmbling.PostcodesIO/HEAD/tools/phantom/Boo.Lang.dll -------------------------------------------------------------------------------- /tools/phantom/Phantom.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markembling/MarkEmbling.PostcodesIO/HEAD/tools/phantom/Phantom.exe -------------------------------------------------------------------------------- /tools/phantom/Ionic.Zip.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markembling/MarkEmbling.PostcodesIO/HEAD/tools/phantom/Ionic.Zip.dll -------------------------------------------------------------------------------- /tools/phantom/Rhino.DSL.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markembling/MarkEmbling.PostcodesIO/HEAD/tools/phantom/Rhino.DSL.dll -------------------------------------------------------------------------------- /tools/phantom/Phantom.Core.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markembling/MarkEmbling.PostcodesIO/HEAD/tools/phantom/Phantom.Core.dll -------------------------------------------------------------------------------- /tools/phantom/Boo.Lang.Parser.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markembling/MarkEmbling.PostcodesIO/HEAD/tools/phantom/Boo.Lang.Parser.dll -------------------------------------------------------------------------------- /tools/phantom/CSScriptLibrary.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markembling/MarkEmbling.PostcodesIO/HEAD/tools/phantom/CSScriptLibrary.dll -------------------------------------------------------------------------------- /tools/phantom/Phantom.DSL.Boo.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markembling/MarkEmbling.PostcodesIO/HEAD/tools/phantom/Phantom.DSL.Boo.dll -------------------------------------------------------------------------------- /src/MarkEmbling.PostcodesIO/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markembling/MarkEmbling.PostcodesIO/HEAD/src/MarkEmbling.PostcodesIO/icon.png -------------------------------------------------------------------------------- /tools/phantom/Boo.Lang.Compiler.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markembling/MarkEmbling.PostcodesIO/HEAD/tools/phantom/Boo.Lang.Compiler.dll -------------------------------------------------------------------------------- /tools/phantom/Phantom.DSL.CSharp.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markembling/MarkEmbling.PostcodesIO/HEAD/tools/phantom/Phantom.DSL.CSharp.dll -------------------------------------------------------------------------------- /.nuget/NuGet.Config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/MarkEmbling.PostcodesIO/Data/NearestResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MarkEmbling.PostcodesIO.Data 4 | { 5 | [Serializable] 6 | public class NearestResult : PostcodeData 7 | { 8 | public double Distance { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/MarkEmbling.PostcodesIO/Exceptions/PostcodesIOApiException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MarkEmbling.PostcodesIO.Exceptions 4 | { 5 | public class PostcodesIOApiException : Exception { 6 | public PostcodesIOApiException(Exception innerException) 7 | : base("Error retrieving response. Please check inner exception for details.", innerException) { } 8 | } 9 | } -------------------------------------------------------------------------------- /src/MarkEmbling.PostcodesIO/Results/RawResult.cs: -------------------------------------------------------------------------------- 1 | namespace MarkEmbling.PostcodesIO.Results 2 | { 3 | /// 4 | /// Raw result object returned from the Postcodes.io API 5 | /// 6 | /// Type to deserialise "result" 7 | public class RawResult { 8 | public int Status { get; set; } 9 | public T Result { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /src/MarkEmbling.PostcodesIO/Exceptions/PostcodesIOEmptyResponseException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | 4 | namespace MarkEmbling.PostcodesIO.Exceptions 5 | { 6 | public class PostcodesIOEmptyResponseException : Exception { 7 | public PostcodesIOEmptyResponseException(HttpStatusCode statusCode) 8 | : base(string.Format("No response was provided; HTTP status: {0}", (int)statusCode)) {} 9 | } 10 | } -------------------------------------------------------------------------------- /src/MarkEmbling.PostcodesIO/Data/TerminatedPostcodeData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MarkEmbling.PostcodesIO.Data 4 | { 5 | [Serializable] 6 | public class TerminatedPostcodeData 7 | { 8 | public string Postcode { get; set; } 9 | public int YearTerminated { get; set; } 10 | public int MonthTerminated { get; set; } 11 | public double Longitude { get; set; } 12 | public double Latitude { get; set; } 13 | } 14 | } -------------------------------------------------------------------------------- /src/MarkEmbling.PostcodesIO/Results/BulkQueryResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MarkEmbling.PostcodesIO.Results 4 | { 5 | /// 6 | /// A single query/result pair from a bulk API call 7 | /// 8 | /// Type of the query 9 | /// Result of the query 10 | [Serializable] 11 | public class BulkQueryResult where TResult : class { 12 | public TQuery Query { get; set; } 13 | public TResult Result { get; set; } 14 | } 15 | } -------------------------------------------------------------------------------- /.nuget/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /tests/MarkEmbling.PostcodesIO.Tests/MarkEmbling.PostcodesIO.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/MarkEmbling.PostcodesIO/Data/OutcodeData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace MarkEmbling.PostcodesIO.Data 5 | { 6 | [Serializable] 7 | public class OutcodeData 8 | { 9 | public string Outcode { get; set; } 10 | public int? Eastings { get; set; } 11 | public int? Northings { get; set; } 12 | public List AdminCounty { get; set; } 13 | public List AdminDistrict { get; set; } 14 | public List AdminWard { get; set; } 15 | public double? Longitude { get; set; } 16 | public double? Latitude { get; set; } 17 | public List Country { get; set; } 18 | public List Parish { get; set; } 19 | } 20 | } -------------------------------------------------------------------------------- /src/MarkEmbling.PostcodesIO/Data/PostcodeCodesData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MarkEmbling.PostcodesIO.Data 4 | { 5 | [Serializable] 6 | public class PostcodeCodesData 7 | { 8 | public string AdminDistrict { get; set; } 9 | public string AdminCounty { get; set; } 10 | public string AdminWard { get; set; } 11 | public string Parish { get; set; } 12 | public string CCG { get; set; } 13 | public string CCGId { get; set; } 14 | public string CED { get; set; } 15 | public string NUTS { get; set; } 16 | public string LAU2 { get; set; } 17 | public string LSOA { get; set; } 18 | public string MSOA { get; set; } 19 | public string PFA { get; set; } 20 | } 21 | } -------------------------------------------------------------------------------- /tests/MarkEmbling.PostcodesIO.Tests/Integration/RandomTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using System.Threading.Tasks; 3 | 4 | namespace MarkEmbling.PostcodesIO.Tests.Integration 5 | { 6 | [TestFixture, Explicit("Hits live Postcodes.io API")] 7 | public class RandomTests { 8 | private PostcodesIOClient _client; 9 | 10 | [SetUp] 11 | public void Setup() { 12 | _client = new PostcodesIOClient(); 13 | } 14 | 15 | [Test] 16 | public void Random_returns_a_postcode_result() { 17 | var result = _client.Random(); 18 | Assert.That(result, Is.Not.Null); 19 | Assert.That(string.IsNullOrEmpty(result.Postcode), Is.False); 20 | } 21 | 22 | [Test] 23 | public async Task Random_returns_a_postcode_result_async() 24 | { 25 | var result = await _client.RandomAsync(); 26 | Assert.That(result, Is.Not.Null); 27 | Assert.That(string.IsNullOrEmpty(result.Postcode), Is.False); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/MarkEmbling.PostcodesIO/MarkEmbling.PostcodesIO.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0;net472 5 | Mark Embling 6 | Mark Embling 7 | Library for interacting with the excellent Postcodes.io service. 8 | Copyright © Mark Embling and Contributors 2015-2025 9 | MIT 10 | https://github.com/markembling/MarkEmbling.PostcodesIO 11 | true 12 | 0.0.13 13 | 0.0.13 14 | 0.0.13 15 | icon.png 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | True 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2015-2019 Mark Embling and contributers 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | MarkEmbling.PostcodesIO 2 | ======================= 3 | 4 | .NET library for interacting with the excellent [Postcodes.io][1] service. 5 | 6 | ### Usage 7 | 8 | var client = new PostcodesIOClient(); 9 | var result = client.Lookup("GU1 1AA"); 10 | // result contains properties such as Latitude, Longitude, Region, County and so on... 11 | 12 | Check out the integration tests (`MarkEmbling.PostcodesIO.Tests`) for further examples. 13 | 14 | Do not instantiate a new instance of PostocdesIOClient for each request. If you use a dependency-injection container, register the client as a singleton. 15 | 16 | Supports .NET Framework 4.7.2 and above, and .NET Standard 2.0 onwards. 17 | 18 | ---------- 19 | 20 | This library is still a work-in-progress. More examples and documentation will come soon, along with the missing API methods. 21 | 22 | In the meantime, the package is [available on NuGet][2]: 23 | 24 | PM> Install-Package MarkEmbling.PostcodesIO 25 | 26 | Be aware: the API may fluctuate until it hits v1.0. But I think it's already going down the right path, so we're probably fine for the most part. But as always, use at your own risk. 27 | 28 | ### Contributions 29 | 30 | Feel free to make contributions via a pull request. Please keep the tests current (and add, if necessary). 31 | 32 | 33 | [1]: http://postcodes.io/ 34 | [2]: https://www.nuget.org/packages/MarkEmbling.PostcodesIO/ 35 | -------------------------------------------------------------------------------- /tests/MarkEmbling.PostcodesIO.Tests/Integration/ValidateTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using System.Threading.Tasks; 3 | 4 | namespace MarkEmbling.PostcodesIO.Tests.Integration 5 | { 6 | [TestFixture, Explicit("Hits live Postcodes.io API")] 7 | public class ValidateTests { 8 | private PostcodesIOClient _client; 9 | 10 | [SetUp] 11 | public void Setup() { 12 | _client = new PostcodesIOClient(); 13 | } 14 | 15 | [Test] 16 | public void Validate_returns_true_for_valid_postcode() { 17 | var result = _client.Validate("GU1 1AA"); 18 | Assert.That(result, Is.True); 19 | } 20 | 21 | [Test] 22 | public void Validate_returns_false_for_nonsense_postcode() { 23 | var result = _client.Validate("FAKE_POSTCODE"); 24 | Assert.That(result, Is.False); 25 | } 26 | 27 | [Test] 28 | public async Task Validate_returns_true_for_valid_postcode_async() 29 | { 30 | var result = await _client.ValidateAsync("GU1 1AA"); 31 | Assert.That(result, Is.True); 32 | } 33 | 34 | [Test] 35 | public async Task Validate_returns_false_for_nonsense_postcode_async() 36 | { 37 | var result = await _client.ValidateAsync("FAKE_POSTCODE"); 38 | Assert.That(result, Is.False); 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /tools/phantom/readme.txt: -------------------------------------------------------------------------------- 1 | Phantom is a .NET build system written in C# and Boo. 2 | 3 | For discussion, please use the mailing list: 4 | http://groups.google.com/group/phantom-discuss 5 | 6 | For complete documentation see the Phantom wiki: 7 | http://wiki.github.com/JeremySkinner/Phantom 8 | 9 | Posts about Phantom can be found on my blog: 10 | http://www.jeremyskinner.co.uk 11 | 12 | This project is licensed under the Microsoft Public License. 13 | http://www.microsoft.com/opensource/licenses.mspx 14 | 15 | Example: 16 | 17 | desc "Compiles the solution" 18 | target compile: 19 | msbuild(file: "MySolution.sln", configuration: "release") 20 | 21 | desc "Executes tests" 22 | target test: 23 | nunit(assembly: "path/to/TestAssembly.dll") 24 | 25 | desc "Copies the binaries to the 'build' directory" 26 | target deploy: 27 | rmdir('build') 28 | 29 | with FileList("src/MyApp/bin/release"): 30 | .Include("*.{dll,exe}") 31 | .ForEach def(file): 32 | file.CopyToDirectory("build") 33 | 34 | desc "Creates zip package" 35 | target package: 36 | zip("build", 'build/MyApp.zip') 37 | 38 | 39 | ---------------- 40 | Acknowledgements 41 | ---------------- 42 | Many thanks to the following for their contributions to this project: 43 | 44 | - Andrey Shchekin (http://www.ashmind.com) 45 | - Ben Scheirman (http://flux88.com/) 46 | - Emil Cardell (http://www.unwillingcoder.com) 47 | - Mark Embling (http://www.markembling.info) 48 | - Mikael Henrixon (http://blog.zoolutions.se) -------------------------------------------------------------------------------- /src/MarkEmbling.PostcodesIO/ReverseGeocodeQuery.cs: -------------------------------------------------------------------------------- 1 | namespace MarkEmbling.PostcodesIO 2 | { 3 | public class ReverseGeocodeQuery { 4 | public double Latitude { get; set; } 5 | public double Longitude { get; set; } 6 | 7 | public int? Limit { get; set; } 8 | public int? Radius { get; set; } 9 | public bool? WideSearch { get; set; } 10 | 11 | protected bool Equals(ReverseGeocodeQuery other) { 12 | return Latitude.Equals(other.Latitude) && 13 | Longitude.Equals(other.Longitude) && 14 | Limit == other.Limit && 15 | Radius == other.Radius && 16 | WideSearch.Equals(other.WideSearch); 17 | } 18 | 19 | public override bool Equals(object obj) { 20 | if (ReferenceEquals(null, obj)) return false; 21 | if (ReferenceEquals(this, obj)) return true; 22 | if (obj.GetType() != this.GetType()) return false; 23 | return Equals((ReverseGeocodeQuery) obj); 24 | } 25 | 26 | public override int GetHashCode() { 27 | unchecked { 28 | int hashCode = Latitude.GetHashCode(); 29 | hashCode = (hashCode*397) ^ Longitude.GetHashCode(); 30 | hashCode = (hashCode*397) ^ Limit.GetHashCode(); 31 | hashCode = (hashCode*397) ^ Radius.GetHashCode(); 32 | hashCode = (hashCode*397) ^ WideSearch.GetHashCode(); 33 | return hashCode; 34 | } 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /src/MarkEmbling.PostcodesIO/Data/PostcodeData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace MarkEmbling.PostcodesIO.Data 5 | { 6 | [Serializable] 7 | public class PostcodeData 8 | { 9 | public string Postcode { get; set; } 10 | 11 | [JsonPropertyName("outcode")] 12 | public string OutCode { get; set; } 13 | 14 | [JsonPropertyName("incode")] 15 | public string InCode { get; set; } 16 | 17 | public int Quality { get; set; } 18 | public int? Eastings { get; set; } 19 | public int? Northings { get; set; } 20 | public string Country { get; set; } 21 | 22 | [JsonPropertyName("nhs_ha")] 23 | public string NHSHealthAuthority { get; set; } 24 | 25 | public string AdminCounty { get; set; } 26 | public string AdminDistrict { get; set; } 27 | public string AdminWard { get; set; } 28 | public double? Longitude { get; set; } 29 | public double? Latitude { get; set; } 30 | public string ParliamentaryConstituency { get; set; } 31 | public string EuropeanElectoralRegion { get; set; } 32 | public string PrimaryCareTrust { get; set; } 33 | public string Region { get; set; } 34 | public string Parish { get; set; } 35 | public string LSOA { get; set; } 36 | public string MSOA { get; set; } 37 | public string CED { get; set; } 38 | public string CCG { get; set; } 39 | public string NUTS { get; set; } 40 | 41 | public PostcodeCodesData Codes { get; set; } 42 | } 43 | } -------------------------------------------------------------------------------- /tests/MarkEmbling.PostcodesIO.Tests/Integration/BulkLookupTests.cs: -------------------------------------------------------------------------------- 1 | using MarkEmbling.PostcodesIO.Data; 2 | using MarkEmbling.PostcodesIO.Results; 3 | using NUnit.Framework; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace MarkEmbling.PostcodesIO.Tests.Integration 9 | { 10 | [TestFixture, Explicit("Hits live Postcodes.io API")] 11 | public class BulkLookupTests { 12 | private PostcodesIOClient _client; 13 | 14 | [SetUp] 15 | public void Setup() { 16 | _client = new PostcodesIOClient(); 17 | } 18 | 19 | [Test] 20 | public void BulkLookup_returns_results() 21 | { 22 | var result = _client.BulkLookup(new[]{"GU1 1AA", "GU1 1AB", "GU1 1AD"}).ToList(); 23 | 24 | TestResults(result); 25 | } 26 | 27 | [Test] 28 | public async Task BulkLookup_returns_results_async() 29 | { 30 | var result = (await _client.BulkLookupAsync(new[] { "GU1 1AA", "GU1 1AB", "GU1 1AD" })).ToList(); 31 | 32 | TestResults(result); 33 | } 34 | 35 | private static void TestResults(List> result) 36 | { 37 | Assert.That(result.Count, Is.EqualTo(3)); 38 | // The results come back in no particular order, so we must check for 39 | // existence of both but make no assumptions about the order they 40 | // may have come back in. 41 | Assert.That(result.Any(x => x.Query == "GU1 1AA"), Is.True); 42 | Assert.That(result.Any(x => x.Query == "GU1 1AB"), Is.True); 43 | Assert.That(result.Any(x => x.Query == "GU1 1AD"), Is.True); 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /tests/MarkEmbling.PostcodesIO.Tests/Integration/AutocompleteTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | 5 | namespace MarkEmbling.PostcodesIO.Tests.Integration 6 | { 7 | [TestFixture, Explicit("Hits live Postcodes.io API")] 8 | public class AutocompleteTests { 9 | private PostcodesIOClient _client; 10 | 11 | [SetUp] 12 | public void Setup() { 13 | _client = new PostcodesIOClient(); 14 | } 15 | 16 | [Test] 17 | public void Autocomplete_returns_full_postcodes_for_partial() { 18 | var result = _client.Autocomplete("GU1 1A").ToList(); 19 | Assert.That(result.Any(), Is.True); 20 | Assert.That(result.Contains("GU1 1AA"), Is.True); 21 | } 22 | 23 | [Test] 24 | public void Autocomplete_returns_null_for_uncompletable_postcode() { 25 | var result = _client.Autocomplete("X"); 26 | Assert.That(result, Is.Null); 27 | } 28 | 29 | [Test] 30 | public void Autocomplete_limits_results_when_limit_is_given() { 31 | var result = _client.Autocomplete("GU1 1A", 2).ToList(); 32 | Assert.That(result.Count(), Is.EqualTo(2)); 33 | } 34 | 35 | [Test] 36 | public async Task Autocomplete_returns_full_postcodes_for_partial_async() 37 | { 38 | var result = (await _client.AutocompleteAsync("GU1 1A")).ToList(); 39 | Assert.That(result.Any(), Is.True); 40 | Assert.That(result.Contains("GU1 1AA"), Is.True); 41 | } 42 | 43 | [Test] 44 | public async Task Autocomplete_returns_null_for_uncompletable_postcode_async() 45 | { 46 | var result = await _client.AutocompleteAsync("X"); 47 | Assert.That(result, Is.Null); 48 | } 49 | 50 | [Test] 51 | public async Task Autocomplete_limits_results_when_limit_is_given_async() 52 | { 53 | var result = (await _client.AutocompleteAsync("GU1 1A", 2)).ToList(); 54 | Assert.That(result.Count(), Is.EqualTo(2)); 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /MarkEmbling.PostcodesIO.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.28803.156 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{A6AF1AB0-4126-4674-A05F-E2B4211A5B41}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{744A05F4-1F6B-444F-AEAB-C8B7DD30A132}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MarkEmbling.PostcodesIO", "src\MarkEmbling.PostcodesIO\MarkEmbling.PostcodesIO.csproj", "{2DABCA36-950E-461B-B9DF-60C9D801AA22}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MarkEmbling.PostcodesIO.Tests", "tests\MarkEmbling.PostcodesIO.Tests\MarkEmbling.PostcodesIO.Tests.csproj", "{98161FD9-1577-4042-9721-52D40889D48D}" 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|Any CPU = Debug|Any CPU 17 | Release|Any CPU = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {2DABCA36-950E-461B-B9DF-60C9D801AA22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {2DABCA36-950E-461B-B9DF-60C9D801AA22}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {2DABCA36-950E-461B-B9DF-60C9D801AA22}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {2DABCA36-950E-461B-B9DF-60C9D801AA22}.Release|Any CPU.Build.0 = Release|Any CPU 24 | {98161FD9-1577-4042-9721-52D40889D48D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {98161FD9-1577-4042-9721-52D40889D48D}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {98161FD9-1577-4042-9721-52D40889D48D}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {98161FD9-1577-4042-9721-52D40889D48D}.Release|Any CPU.Build.0 = Release|Any CPU 28 | EndGlobalSection 29 | GlobalSection(SolutionProperties) = preSolution 30 | HideSolutionNode = FALSE 31 | EndGlobalSection 32 | GlobalSection(NestedProjects) = preSolution 33 | {2DABCA36-950E-461B-B9DF-60C9D801AA22} = {A6AF1AB0-4126-4674-A05F-E2B4211A5B41} 34 | {98161FD9-1577-4042-9721-52D40889D48D} = {744A05F4-1F6B-444F-AEAB-C8B7DD30A132} 35 | EndGlobalSection 36 | GlobalSection(ExtensibilityGlobals) = postSolution 37 | SolutionGuid = {66B8796C-56DE-4549-90D7-E5BBE4F7EC95} 38 | EndGlobalSection 39 | EndGlobal 40 | -------------------------------------------------------------------------------- /src/MarkEmbling.PostcodesIO/IPostcodesIOClient.cs: -------------------------------------------------------------------------------- 1 | using MarkEmbling.PostcodesIO.Data; 2 | using MarkEmbling.PostcodesIO.Results; 3 | using System.Collections.Generic; 4 | using System.Threading.Tasks; 5 | 6 | namespace MarkEmbling.PostcodesIO 7 | { 8 | public interface IPostcodesIOClient { 9 | // TODO: documentation 10 | 11 | PostcodeData Lookup(string postcode); 12 | Task LookupAsync(string postcode); 13 | IEnumerable> BulkLookup(IEnumerable postcodes); 14 | Task>> BulkLookupAsync(IEnumerable postcodes); 15 | IEnumerable Query(string q, int? limit = null); 16 | Task> QueryAsync(string q, int? limit = null); 17 | bool Validate(string postcode); 18 | Task ValidateAsync(string postcode); 19 | 20 | IEnumerable LookupLatLon(ReverseGeocodeQuery query); 21 | Task> LookupLatLonAsync(ReverseGeocodeQuery query); 22 | IEnumerable>> BulkLookupLatLon(IEnumerable queries); 23 | Task>>> BulkLookupLatLonAsync(IEnumerable queries); 24 | 25 | IEnumerable Autocomplete(string postcode, int? limit = null); 26 | Task> AutocompleteAsync(string postcode, int? limit = null); 27 | PostcodeData Random(); 28 | Task RandomAsync(); 29 | 30 | IEnumerable Nearest(string postcode, int? limit = null, int? radius = null); 31 | Task> NearestAsync(string postcode, int? limit = null, int? radius = null); 32 | 33 | OutcodeData OutwardCodeLookup(string outcode); 34 | 35 | TerminatedPostcodeData Terminated(string postcode); 36 | Task TerminatedAsync(string postcode); 37 | 38 | /* 39 | IEnumerable OutwardCodeLookupLatLon(ReverseGeocodeQuery query); 40 | IEnumerable NearestOutwardCode(string outcode, int? limit = null, int? radius = null); 41 | */ 42 | } 43 | } -------------------------------------------------------------------------------- /tests/MarkEmbling.PostcodesIO.Tests/Unit/ReverseGeocodeQueryTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | 3 | namespace MarkEmbling.PostcodesIO.Tests.Unit 4 | { 5 | [TestFixture] 6 | public class ReverseGeocodeQueryTests { 7 | [Test] 8 | public void Similar_instances_should_be_considered_equal() { 9 | var q1 = new ReverseGeocodeQuery {Latitude = 51.2452924089757, Longitude = -0.58231794275613}; 10 | var q2 = new ReverseGeocodeQuery {Latitude = 51.2452924089757, Longitude = -0.58231794275613}; 11 | Assert.That(q1, Is.EqualTo(q2)); 12 | } 13 | 14 | [Test] 15 | public void Different_instances_should_not_be_considered_equal() { 16 | var q1 = new ReverseGeocodeQuery {Latitude = 51.2452924089757, Longitude = -0.58231794275613}; 17 | var q2 = new ReverseGeocodeQuery {Latitude = 51.2571984465953, Longitude = -0.567549033067429}; 18 | Assert.That(q2, Is.Not.EqualTo(q1)); 19 | } 20 | 21 | [Test] 22 | public void Similar_queries_but_with_different_limit_should_not_be_considered_equal() { 23 | var q1 = new ReverseGeocodeQuery {Latitude = 51.2452924089757, Longitude = -0.58231794275613}; 24 | var q2 = new ReverseGeocodeQuery {Latitude = 51.2452924089757, Longitude = -0.58231794275613, Limit = 1}; 25 | Assert.That(q1, Is.Not.EqualTo(q2)); 26 | } 27 | 28 | [Test] 29 | public void Similar_queries_but_with_different_radius_should_not_be_considered_equal() { 30 | var q1 = new ReverseGeocodeQuery { Latitude = 51.2452924089757, Longitude = -0.58231794275613 }; 31 | var q2 = new ReverseGeocodeQuery {Latitude = 51.2452924089757, Longitude = -0.58231794275613, Radius = 1}; 32 | Assert.That(q1, Is.Not.EqualTo(q2)); 33 | } 34 | 35 | [Test] 36 | public void Similar_queries_but_with_different_widesearch_should_not_be_considered_equal() { 37 | var q1 = new ReverseGeocodeQuery {Latitude = 51.2452924089757, Longitude = -0.58231794275613}; 38 | var q2 = new ReverseGeocodeQuery { 39 | Latitude = 51.2452924089757, 40 | Longitude = -0.58231794275613, 41 | WideSearch = true 42 | }; 43 | Assert.That(q1, Is.Not.EqualTo(q2)); 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /tests/MarkEmbling.PostcodesIO.Tests/Integration/QueryTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | 5 | namespace MarkEmbling.PostcodesIO.Tests.Integration 6 | { 7 | [TestFixture, Explicit("Hits live Postcodes.io API")] 8 | public class QueryTests { 9 | private PostcodesIOClient _client; 10 | 11 | [SetUp] 12 | public void Setup() { 13 | _client = new PostcodesIOClient(); 14 | } 15 | 16 | [Test] 17 | public void Query_with_full_postcode_returns_matching_result() { 18 | var results = _client.Query("GU1 1AA").ToList(); 19 | 20 | Assert.That(results.Count(), Is.EqualTo(1)); 21 | Assert.That(results.First().Postcode, Is.EqualTo("GU1 1AA")); 22 | } 23 | 24 | [Test] 25 | public void Query_with_partial_postcode_returns_all_matching_results() { 26 | var results = _client.Query("GU1 1").ToList(); 27 | Assert.That(results.Count() > 1, Is.True); 28 | } 29 | 30 | [Test] 31 | public void Query_with_invalid_postcode_returns_nothing() { 32 | var result = _client.Query("X"); 33 | Assert.That(result, Is.Null); 34 | } 35 | 36 | [Test] 37 | public void Query_with_limit_returns_limited_results() { 38 | var results = _client.Query("GU1 1", 2); 39 | Assert.That(results.Count(), Is.EqualTo(2)); 40 | } 41 | 42 | [Test] 43 | public async Task Query_with_full_postcode_returns_matching_result_async() 44 | { 45 | var results = (await _client.QueryAsync("GU1 1AA")).ToList(); 46 | 47 | Assert.That(results.Count(), Is.EqualTo(1)); 48 | Assert.That(results.First().Postcode, Is.EqualTo("GU1 1AA")); 49 | } 50 | 51 | [Test] 52 | public async Task Query_with_partial_postcode_returns_all_matching_results_async() 53 | { 54 | var results = (await _client.QueryAsync("GU1 1")).ToList(); 55 | Assert.That(results.Count() > 1, Is.True); 56 | } 57 | 58 | [Test] 59 | public async Task Query_with_invalid_postcode_returns_nothing_async() 60 | { 61 | var result = await _client.QueryAsync("X"); 62 | Assert.That(result, Is.Null); 63 | } 64 | 65 | [Test] 66 | public async Task Query_with_limit_returns_limited_results_async() 67 | { 68 | var results = await _client.QueryAsync("GU1 1", 2); 69 | Assert.That(results.Count(), Is.EqualTo(2)); 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /tests/MarkEmbling.PostcodesIO.Tests/Integration/LookupLatLonTests.cs: -------------------------------------------------------------------------------- 1 | using MarkEmbling.PostcodesIO.Data; 2 | using NUnit.Framework; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace MarkEmbling.PostcodesIO.Tests.Integration 8 | { 9 | [TestFixture, Explicit("Hits live Postcodes.io API")] 10 | public class LookupLatLonTests { 11 | private PostcodesIOClient _client; 12 | 13 | [SetUp] 14 | public void Setup() { 15 | _client = new PostcodesIOClient(); 16 | } 17 | 18 | [Test] 19 | public void LookupLatLon_simple_query_returns_populated_response() 20 | { 21 | var results = _client.LookupLatLon(new ReverseGeocodeQuery { 22 | Latitude = 51.2452924089757, 23 | Longitude = -0.58231794275613 24 | }).ToList(); 25 | 26 | TestLookupLatLon_simple_query_returns_populated_response(results); 27 | } 28 | 29 | [Test] 30 | public void LookupLatLon_with_limit_returns_only_that_number_of_results() { 31 | var results = _client.LookupLatLon(new ReverseGeocodeQuery { 32 | Latitude = 51.2452924089757, 33 | Longitude = -0.58231794275613, 34 | Limit = 2 35 | }).ToList(); 36 | 37 | Assert.That(results.Count, Is.EqualTo(2)); 38 | } 39 | 40 | [Test] 41 | public async Task LookupLatLon_simple_query_returns_populated_response_async() 42 | { 43 | var results = (await _client.LookupLatLonAsync(new ReverseGeocodeQuery 44 | { 45 | Latitude = 51.2452924089757, 46 | Longitude = -0.58231794275613 47 | })).ToList(); 48 | 49 | TestLookupLatLon_simple_query_returns_populated_response(results); 50 | } 51 | 52 | [Test] 53 | public async Task LookupLatLon_with_limit_returns_only_that_number_of_results_async() 54 | { 55 | var results = (await _client.LookupLatLonAsync(new ReverseGeocodeQuery 56 | { 57 | Latitude = 51.2452924089757, 58 | Longitude = -0.58231794275613, 59 | Limit = 2 60 | })).ToList(); 61 | 62 | Assert.That(results.Count, Is.EqualTo(2)); 63 | } 64 | 65 | // TODO: tests for radius and wideSearch. Probably better as unit tests. 66 | private static void TestLookupLatLon_simple_query_returns_populated_response(List results) 67 | { 68 | Assert.That(results.Any(), Is.True); 69 | //TODO probably a better way of writing this without using Is.True 70 | Assert.That(results.Any(p => p.Postcode == "GU1 1AA"), Is.True); 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /tests/MarkEmbling.PostcodesIO.Tests/Integration/BulkLookupLatLonTests.cs: -------------------------------------------------------------------------------- 1 | using MarkEmbling.PostcodesIO.Data; 2 | using MarkEmbling.PostcodesIO.Results; 3 | using NUnit.Framework; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace MarkEmbling.PostcodesIO.Tests.Integration 9 | { 10 | [TestFixture, Explicit("Hits live Postcodes.io API")] 11 | public class BulkLookupLatLonTests { 12 | private PostcodesIOClient _client; 13 | private ReverseGeocodeQuery[] _lookups; 14 | 15 | [SetUp] 16 | public void Setup() { 17 | _client = new PostcodesIOClient(); 18 | _lookups = [ 19 | new ReverseGeocodeQuery {Latitude = 51.2452924089757, Longitude = -0.58231794275613}, 20 | new ReverseGeocodeQuery {Latitude = 51.2571984465953, Longitude = -0.567549033067429} 21 | ]; 22 | } 23 | 24 | [Test] 25 | public void BulkLookupLatLon_returns_results() { 26 | var results = _client.BulkLookupLatLon(_lookups); 27 | Assert.That(results.Count(), Is.EqualTo(2)); 28 | } 29 | 30 | [Test] 31 | public void BulkLookupLatLon_results_contain_original_queries() { 32 | var results = _client.BulkLookupLatLon(_lookups).ToList(); 33 | 34 | Assert.That(results.Any(x => x.Query.Equals(_lookups[0])), Is.True); 35 | Assert.That(results.Any(x => x.Query.Equals(_lookups[1])), Is.True); 36 | 37 | } 38 | 39 | [Test] 40 | public void BulkLookupLatLon_results_contain_postcode_results() { 41 | var results = _client.BulkLookupLatLon(_lookups).ToList(); 42 | 43 | TestBulkLookupLatLon_results_contain_postcode_results(results); 44 | } 45 | 46 | [Test] 47 | public async Task BulkLookupLatLon_returns_results_async() 48 | { 49 | var results = await _client.BulkLookupLatLonAsync(_lookups); 50 | Assert.That(results.Count(), Is.EqualTo(2)); 51 | } 52 | 53 | [Test] 54 | public async Task BulkLookupLatLon_results_contain_original_queries_async() 55 | { 56 | var results = (await _client.BulkLookupLatLonAsync(_lookups)).ToList(); 57 | 58 | Assert.That(results.Any(x => x.Query.Equals(_lookups[0])), Is.True); 59 | Assert.That(results.Any(x => x.Query.Equals(_lookups[1])), Is.True); 60 | } 61 | 62 | [Test] 63 | public async Task BulkLookupLatLon_results_contain_postcode_results_async() 64 | { 65 | var results = (await _client.BulkLookupLatLonAsync(_lookups)).ToList(); 66 | 67 | TestBulkLookupLatLon_results_contain_postcode_results(results); 68 | } 69 | 70 | private void TestBulkLookupLatLon_results_contain_postcode_results(List>> results) 71 | { 72 | Assert.That(results[0].Result.Any(), Is.True); 73 | Assert.That(results[1].Result.Any(), Is.True); 74 | Assert.That(results.Single(r => r.Query.Equals(_lookups[0])).Result.Exists(p => p.Postcode == "GU1 1AA"), Is.True); 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /tools/phantom/License.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Microsoft Public License (Ms-PL) 5 | 6 | 7 | 8 |

Microsoft Public License (Ms-PL)

9 | 10 |

This license governs use of the accompanying software. If you use the software, you accept this license. If you do not accept the license, do not use the software.

11 | 12 |

The software is governed by the Microsoft Public License, the terms of which 13 | are below; except for the Ruby Standard libraries which are governed by the 14 | Ruby license. We have included a copy of the Ruby license in the same 15 | directory as this file

16 | 17 |

1. Definitions

18 |

The terms “reproduce,” “reproduction,” “derivative works,” and “distribution” have the same meaning here as under U.S. copyright law.

19 |

A “contribution” is the original software, or any additions or changes to the software.

20 |

A “contributor” is any person that distributes its contribution under this license.

21 |

“Licensed patents” are a contributor’s patent claims that read directly on its contribution.

22 |

2. Grant of Rights

23 |

(A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright 24 | license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create.

25 | 26 |

(B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, 27 | royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative 28 | works of the contribution in the software.

29 | 30 |

3. Conditions and Limitations

(A) No Trademark License- This license does not grant you rights to use any contributors’ name, logo, or trademarks.

31 |

(B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, your patent license from such contributor to the software ends automatically.

32 |

(C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution notices that are present in the software.

33 |

(D) If you distribute any portion of the software in source code form, you may do so only under this license by including a complete copy of this license with your distribution. 34 | If you distribute any portion of the software in compiled or object code form, you may only do so under a license that complies with this license.

35 | 36 |

(E) The software is licensed “as-is.” You bear the risk of using it. The contributors give no express warranties, guarantees or conditions. You may have additional 37 | consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the contributors exclude the implied warranties of merchantability, 38 | fitness for a particular purpose and non-infringement.

39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /tests/MarkEmbling.PostcodesIO.Tests/Integration/NearestTests.cs: -------------------------------------------------------------------------------- 1 | using MarkEmbling.PostcodesIO.Data; 2 | using NUnit.Framework; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace MarkEmbling.PostcodesIO.Tests.Integration 7 | { 8 | [TestFixture, Explicit("Hits live Postcodes.io API")] 9 | public class NearestTests 10 | { 11 | private PostcodesIOClient _client; 12 | 13 | [SetUp] 14 | public void Setup() 15 | { 16 | _client = new PostcodesIOClient(); 17 | } 18 | 19 | [Test] 20 | public void Nearest_returns_populated_response() 21 | { 22 | var results = _client.Nearest("GU1 1AA"); 23 | 24 | foreach (var result in results) 25 | { 26 | TestLookup_returns_populated_responseResult(result); 27 | } 28 | } 29 | 30 | [Test] 31 | public async Task Nearest_returns_populated_response_async() 32 | { 33 | var results = await _client.NearestAsync("GU1 1AA"); 34 | 35 | foreach (var result in results) 36 | { 37 | TestLookup_returns_populated_responseResult(result); 38 | } 39 | } 40 | 41 | private static void TestLookup_returns_populated_responseResult(NearestResult result) 42 | { 43 | Assert.That(result.Postcode, Is.Not.Null); 44 | Assert.That(result.Quality, Is.Not.Null); 45 | Assert.That(result.Eastings, Is.Not.Null); 46 | Assert.That(result.Northings, Is.Not.Null); 47 | Assert.That(result.Country, Is.Not.Null); 48 | Assert.That(result.NHSHealthAuthority, Is.Not.Null); 49 | Assert.That(result.Longitude, Is.Not.Null); 50 | Assert.That(result.ParliamentaryConstituency, Is.Not.Null); 51 | Assert.That(result.EuropeanElectoralRegion, Is.Not.Null); 52 | Assert.That(result.PrimaryCareTrust, Is.Not.Null); 53 | Assert.That(result.Region, Is.Not.Null); 54 | Assert.That(result.LSOA, Is.Not.Null); 55 | Assert.That(result.MSOA, Is.Not.Null); 56 | Assert.That(result.NUTS, Is.Not.Null); 57 | Assert.That(result.InCode, Is.Not.Null); 58 | Assert.That(result.OutCode, Is.Not.Null); 59 | Assert.That(result.AdminDistrict, Is.Not.Null); 60 | Assert.That(result.Parish, Is.Not.Null); 61 | Assert.That(result.AdminCounty, Is.Not.Null); 62 | Assert.That(result.AdminWard, Is.Not.Null); 63 | Assert.That(result.CED, Is.Not.Null); 64 | Assert.That(result.CCG, Is.Not.Null); 65 | Assert.That(result.Codes, Is.Not.Null); 66 | 67 | Assert.That(result.Distance, Is.Not.Null); 68 | } 69 | 70 | [Test] 71 | public void Nearest_returns_populated_response_limit() 72 | { 73 | var limit = 20; 74 | var results = _client.Nearest("GU1 1AA", limit); 75 | 76 | Assert.That(results.Count(), Is.EqualTo(limit)); 77 | } 78 | 79 | [Test] 80 | public async Task Nearest_returns_populated_response_limit_async() 81 | { 82 | var limit = 20; 83 | var results = await _client.NearestAsync("GU1 1AA", limit); 84 | 85 | Assert.That(results.Count(), Is.EqualTo(limit)); 86 | } 87 | 88 | //TODO: tests on radius, need to find postcode with other postcodes that are not near 89 | 90 | } 91 | } -------------------------------------------------------------------------------- /tests/MarkEmbling.PostcodesIO.Tests/Unit/SerializabilityTests.cs: -------------------------------------------------------------------------------- 1 | using MarkEmbling.PostcodesIO.Data; 2 | using NUnit.Framework; 3 | using System.Text.Json; 4 | 5 | namespace MarkEmbling.PostcodesIO.Tests.Unit 6 | { 7 | [TestFixture] 8 | public class SerializabilityTests 9 | { 10 | static byte[] Serialize(object o) 11 | { 12 | if (o == null) return null; 13 | return JsonSerializer.SerializeToUtf8Bytes(o); 14 | } 15 | 16 | static T Deserialize(byte[] bytes) 17 | { 18 | if (bytes == null || bytes.Length == 0) return default; 19 | return JsonSerializer.Deserialize(bytes); 20 | } 21 | 22 | [Test] 23 | public void PostcodeData_should_be_serializable() 24 | { 25 | var expected = new PostcodeData 26 | { 27 | AdminCounty = "AdminCounty", 28 | AdminWard = "AdminWard", 29 | AdminDistrict = "AdminDistrict", 30 | CCG = "CCG", 31 | CED = "CED", 32 | Country = "Country", 33 | Eastings = 100000, 34 | Northings = 200000, 35 | EuropeanElectoralRegion = "EuropeanElectoralRegion", 36 | InCode = "InCode", 37 | LSOA = "LSOA", 38 | Latitude = 53.9999, 39 | Longitude = -3.9999, 40 | MSOA = "MSOA", 41 | NHSHealthAuthority = "NHSHealthAuthority", 42 | NUTS = "NUTS", 43 | OutCode = "OutCode", 44 | Parish = "Parish", 45 | ParliamentaryConstituency = "ParliamentaryConstituency", 46 | Postcode = "Postcode", 47 | PrimaryCareTrust = "PrimaryCareTrust", 48 | Quality = 1, 49 | Region = "Region", 50 | Codes = new PostcodeCodesData {Parish = "Parish", CCG = "CCG", AdminDistrict = "AdminDistrict", AdminWard = "AdminWard", AdminCounty = "AdminCounty"} 51 | }; 52 | 53 | var expectedBytes = Serialize(expected); 54 | var actual = Deserialize(expectedBytes); 55 | 56 | Assert.That(actual.Postcode, Is.EqualTo(expected.Postcode)); 57 | Assert.That(actual.Eastings, Is.EqualTo(expected.Eastings)); 58 | Assert.That(actual.Latitude, Is.EqualTo(expected.Latitude)); 59 | Assert.That(actual.Codes.AdminCounty, Is.EqualTo(expected.Codes.AdminCounty)); 60 | } 61 | 62 | [Test] 63 | public void OutcodeData_should_be_serializable() 64 | { 65 | var expected = new OutcodeData 66 | { 67 | AdminCounty = ["AdminCounty"], 68 | AdminDistrict = ["AdminDistrict"], 69 | AdminWard = ["AdminWard"], 70 | Country = ["Country"], 71 | Eastings = 10000, 72 | Latitude = 53.9999, 73 | Longitude = -3.9999, 74 | Northings = 20000, 75 | Outcode = "OUT", 76 | Parish = ["Parish"] 77 | }; 78 | var expectedBytes = Serialize(expected); 79 | var actual = Deserialize(expectedBytes); 80 | 81 | Assert.That(actual.AdminCounty, Is.EqualTo(expected.AdminCounty)); 82 | Assert.That(actual.Eastings, Is.EqualTo(expected.Eastings)); 83 | Assert.That(actual.Latitude, Is.EqualTo(expected.Latitude)); 84 | Assert.That(actual.Outcode, Is.EqualTo(expected.Outcode)); 85 | } 86 | 87 | [Test] 88 | public void TerminatedPostcodeData_should_be_serializable() 89 | { 90 | var expected = new TerminatedPostcodeData 91 | { 92 | Latitude = 53.9999, 93 | Longitude = -3.9999, 94 | MonthTerminated = 1, 95 | Postcode = "Postcode", 96 | YearTerminated = 2000 97 | }; 98 | var expectedBytes = Serialize(expected); 99 | var actual = Deserialize(expectedBytes); 100 | 101 | Assert.That(actual.Latitude, Is.EqualTo(expected.Latitude)); 102 | Assert.That(actual.MonthTerminated, Is.EqualTo(expected.MonthTerminated)); 103 | Assert.That(actual.Postcode, Is.EqualTo(expected.Postcode)); 104 | } 105 | } 106 | } -------------------------------------------------------------------------------- /tests/MarkEmbling.PostcodesIO.Tests/Integration/LookupTests.cs: -------------------------------------------------------------------------------- 1 | using MarkEmbling.PostcodesIO.Data; 2 | using NUnit.Framework; 3 | using System.Collections.Generic; 4 | using System.Threading.Tasks; 5 | 6 | namespace MarkEmbling.PostcodesIO.Tests.Integration 7 | { 8 | [TestFixture, Explicit("Hits live Postcodes.io API")] 9 | public class LookupTests { 10 | private PostcodesIOClient _client; 11 | 12 | [SetUp] 13 | public void Setup() { 14 | _client = new PostcodesIOClient(); 15 | } 16 | 17 | [Test] 18 | public void Lookup_returns_populated_response() 19 | { 20 | var result = _client.Lookup("GU1 1AA"); 21 | 22 | TestLookup_returns_populated_responseResult(result); 23 | } 24 | 25 | [Test] 26 | public void OutwardCode_Lookup_returns_populated_response() 27 | { 28 | var result = _client.OutwardCodeLookup("IP3"); 29 | 30 | TestOutwardCode_Lookup_returns_populated_responseResult(result); 31 | } 32 | 33 | [Test] 34 | public async Task Lookup_returns_populated_response_async() 35 | { 36 | var result = await _client.LookupAsync("GU1 1AA"); 37 | 38 | TestLookup_returns_populated_responseResult(result); 39 | } 40 | 41 | private static void TestLookup_returns_populated_responseResult(PostcodeData result) 42 | { 43 | Assert.That(result.Postcode, Is.EqualTo("GU1 1AA")); 44 | Assert.That(result.Quality, Is.EqualTo(1)); 45 | Assert.That(result.Eastings, Is.EqualTo(499049)); 46 | Assert.That(result.Northings, Is.EqualTo(150522)); 47 | Assert.That(result.Country, Is.EqualTo("England")); 48 | Assert.That(result.NHSHealthAuthority, Is.EqualTo("South East Coast")); 49 | Assert.That(result.Longitude, Is.EqualTo(-0.582332)); 50 | Assert.That(result.Latitude, Is.EqualTo(51.245283)); 51 | Assert.That(result.ParliamentaryConstituency, Is.EqualTo("Guildford")); 52 | Assert.That(result.EuropeanElectoralRegion, Is.EqualTo("South East")); 53 | Assert.That(result.PrimaryCareTrust, Is.EqualTo("Surrey")); 54 | Assert.That(result.Region, Is.EqualTo("South East")); 55 | Assert.That(result.LSOA, Is.EqualTo("Guildford 015A")); 56 | Assert.That(result.MSOA, Is.EqualTo("Guildford 015")); 57 | Assert.That(result.NUTS, Is.EqualTo("Guildford")); 58 | Assert.That(result.InCode, Is.EqualTo("1AA")); 59 | Assert.That(result.OutCode, Is.EqualTo("GU1")); 60 | Assert.That(result.AdminDistrict, Is.EqualTo("Guildford")); 61 | Assert.That(result.Parish, Is.EqualTo("Guildford, unparished area")); 62 | Assert.That(result.AdminCounty, Is.EqualTo("Surrey")); 63 | Assert.That(result.AdminWard, Is.EqualTo("Stoke")); 64 | Assert.That(result.CED, Is.EqualTo("Guildford South West")); 65 | Assert.That(result.CCG, Is.EqualTo("NHS Surrey Heartlands")); 66 | Assert.That(result.Codes, Is.Not.Null); 67 | } 68 | 69 | private static void TestOutwardCode_Lookup_returns_populated_responseResult(OutcodeData result) 70 | { 71 | Assert.That(result.Outcode, Is.EqualTo("IP3")); 72 | Assert.That(result.Northings, Is.EqualTo(242900)); 73 | Assert.That(result.Eastings, Is.EqualTo(618740)); 74 | Assert.That(result.AdminCounty, Is.EqualTo(new List() { "Suffolk" })); 75 | Assert.That(result.AdminDistrict, Is.EqualTo(new List() { "Ipswich", "East Suffolk" })); 76 | Assert.That(result.AdminWard, Is.EqualTo(new List() { "Bixley", "Martlesham & Purdis Farm", "Gainsborough", "Holywells", "Rushmere St Andrew", "Alexandra", "St John's", "Priory Heath" })); 77 | Assert.That(result.Longitude, Is.EqualTo(1.188121282722514)); 78 | Assert.That(result.Latitude, Is.EqualTo(52.041320951570555)); 79 | Assert.That(result.Country, Is.EqualTo(new List() { "England" })); 80 | Assert.That(result.Parish, Is.EqualTo(new List() { "Ipswich, unparished area", "Rushmere St. Andrew", "Purdis Farm" })); 81 | } 82 | 83 | [Test] 84 | public void Lookup_returns_populated_codes_property() 85 | { 86 | var result = _client.Lookup("GU1 1AA").Codes; 87 | 88 | TestLookup_returns_populated_codes_propertyResult(result); 89 | } 90 | 91 | [Test] 92 | public async Task Lookup_returns_populated_codes_property_async() 93 | { 94 | var result = (await _client.LookupAsync("GU1 1AA")).Codes; 95 | 96 | TestLookup_returns_populated_codes_propertyResult(result); 97 | } 98 | 99 | private static void TestLookup_returns_populated_codes_propertyResult(PostcodeCodesData result) 100 | { 101 | Assert.That(result.AdminDistrict, Is.EqualTo("E07000209")); 102 | Assert.That(result.AdminCounty, Is.EqualTo("E10000030")); 103 | Assert.That(result.AdminWard, Is.EqualTo("E05014959")); 104 | Assert.That(result.Parish, Is.EqualTo("E43000138")); 105 | Assert.That(result.CCG, Is.EqualTo("E38000264")); 106 | Assert.That(result.CCGId, Is.EqualTo("92A")); 107 | Assert.That(result.CED, Is.EqualTo("E58001497")); 108 | Assert.That(result.NUTS, Is.EqualTo("TLJ25")); 109 | Assert.That(result.LAU2, Is.EqualTo("E07000209")); 110 | Assert.That(result.LSOA, Is.EqualTo("E01030452")); 111 | Assert.That(result.MSOA, Is.EqualTo("E02006358")); 112 | Assert.That(result.PFA, Is.EqualTo("E23000031")); 113 | } 114 | 115 | [Test] 116 | public void Lookup_correctly_handles_null_geolocation_values() 117 | { 118 | var result = _client.Lookup("JE2 4ST"); 119 | 120 | Assert.That(result.Eastings, Is.Null); 121 | Assert.That(result.Northings, Is.Null); 122 | Assert.That(result.Latitude, Is.Null); 123 | Assert.That(result.Longitude, Is.Null); 124 | } 125 | } 126 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Aa][Rr][Mm]/ 27 | [Aa][Rr][Mm]64/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | 33 | # Visual Studio 2015/2017 cache/options directory 34 | .vs/ 35 | # Uncomment if you have tasks that create the project's static files in wwwroot 36 | #wwwroot/ 37 | 38 | # Visual Studio 2017 auto generated files 39 | Generated\ Files/ 40 | 41 | # MSTest test Results 42 | [Tt]est[Rr]esult*/ 43 | [Bb]uild[Ll]og.* 44 | 45 | # NUNIT 46 | *.VisualState.xml 47 | TestResult.xml 48 | 49 | # Build Results of an ATL Project 50 | [Dd]ebugPS/ 51 | [Rr]eleasePS/ 52 | dlldata.c 53 | 54 | # Benchmark Results 55 | BenchmarkDotNet.Artifacts/ 56 | 57 | # .NET Core 58 | project.lock.json 59 | project.fragment.lock.json 60 | artifacts/ 61 | 62 | # StyleCop 63 | StyleCopReport.xml 64 | 65 | # Files built by Visual Studio 66 | *_i.c 67 | *_p.c 68 | *_h.h 69 | *.ilk 70 | *.meta 71 | *.obj 72 | *.iobj 73 | *.pch 74 | *.pdb 75 | *.ipdb 76 | *.pgc 77 | *.pgd 78 | *.rsp 79 | *.sbr 80 | *.tlb 81 | *.tli 82 | *.tlh 83 | *.tmp 84 | *.tmp_proj 85 | *_wpftmp.csproj 86 | *.log 87 | *.vspscc 88 | *.vssscc 89 | .builds 90 | *.pidb 91 | *.svclog 92 | *.scc 93 | 94 | # Chutzpah Test files 95 | _Chutzpah* 96 | 97 | # Visual C++ cache files 98 | ipch/ 99 | *.aps 100 | *.ncb 101 | *.opendb 102 | *.opensdf 103 | *.sdf 104 | *.cachefile 105 | *.VC.db 106 | *.VC.VC.opendb 107 | 108 | # Visual Studio profiler 109 | *.psess 110 | *.vsp 111 | *.vspx 112 | *.sap 113 | 114 | # Visual Studio Trace Files 115 | *.e2e 116 | 117 | # TFS 2012 Local Workspace 118 | $tf/ 119 | 120 | # Guidance Automation Toolkit 121 | *.gpState 122 | 123 | # ReSharper is a .NET coding add-in 124 | _ReSharper*/ 125 | *.[Rr]e[Ss]harper 126 | *.DotSettings.user 127 | 128 | # JustCode is a .NET coding add-in 129 | .JustCode 130 | 131 | # TeamCity is a build add-in 132 | _TeamCity* 133 | 134 | # DotCover is a Code Coverage Tool 135 | *.dotCover 136 | 137 | # AxoCover is a Code Coverage Tool 138 | .axoCover/* 139 | !.axoCover/settings.json 140 | 141 | # Visual Studio code coverage results 142 | *.coverage 143 | *.coveragexml 144 | 145 | # NCrunch 146 | _NCrunch_* 147 | .*crunch*.local.xml 148 | nCrunchTemp_* 149 | 150 | # MightyMoose 151 | *.mm.* 152 | AutoTest.Net/ 153 | 154 | # Web workbench (sass) 155 | .sass-cache/ 156 | 157 | # Installshield output folder 158 | [Ee]xpress/ 159 | 160 | # DocProject is a documentation generator add-in 161 | DocProject/buildhelp/ 162 | DocProject/Help/*.HxT 163 | DocProject/Help/*.HxC 164 | DocProject/Help/*.hhc 165 | DocProject/Help/*.hhk 166 | DocProject/Help/*.hhp 167 | DocProject/Help/Html2 168 | DocProject/Help/html 169 | 170 | # Click-Once directory 171 | publish/ 172 | 173 | # Publish Web Output 174 | *.[Pp]ublish.xml 175 | *.azurePubxml 176 | # Note: Comment the next line if you want to checkin your web deploy settings, 177 | # but database connection strings (with potential passwords) will be unencrypted 178 | *.pubxml 179 | *.publishproj 180 | 181 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 182 | # checkin your Azure Web App publish settings, but sensitive information contained 183 | # in these scripts will be unencrypted 184 | PublishScripts/ 185 | 186 | # NuGet Packages 187 | *.nupkg 188 | # The packages folder can be ignored because of Package Restore 189 | **/[Pp]ackages/* 190 | # except build/, which is used as an MSBuild target. 191 | !**/[Pp]ackages/build/ 192 | # Uncomment if necessary however generally it will be regenerated when needed 193 | #!**/[Pp]ackages/repositories.config 194 | # NuGet v3's project.json files produces more ignorable files 195 | *.nuget.props 196 | *.nuget.targets 197 | 198 | # Microsoft Azure Build Output 199 | csx/ 200 | *.build.csdef 201 | 202 | # Microsoft Azure Emulator 203 | ecf/ 204 | rcf/ 205 | 206 | # Windows Store app package directories and files 207 | AppPackages/ 208 | BundleArtifacts/ 209 | Package.StoreAssociation.xml 210 | _pkginfo.txt 211 | *.appx 212 | *.appxbundle 213 | *.appxupload 214 | 215 | # Visual Studio cache files 216 | # files ending in .cache can be ignored 217 | *.[Cc]ache 218 | # but keep track of directories ending in .cache 219 | !?*.[Cc]ache/ 220 | 221 | # Others 222 | ClientBin/ 223 | ~$* 224 | *~ 225 | *.dbmdl 226 | *.dbproj.schemaview 227 | *.jfm 228 | *.pfx 229 | *.publishsettings 230 | orleans.codegen.cs 231 | 232 | # Including strong name files can present a security risk 233 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 234 | #*.snk 235 | 236 | # Since there are multiple workflows, uncomment next line to ignore bower_components 237 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 238 | #bower_components/ 239 | 240 | # RIA/Silverlight projects 241 | Generated_Code/ 242 | 243 | # Backup & report files from converting an old project file 244 | # to a newer Visual Studio version. Backup files are not needed, 245 | # because we have git ;-) 246 | _UpgradeReport_Files/ 247 | Backup*/ 248 | UpgradeLog*.XML 249 | UpgradeLog*.htm 250 | ServiceFabricBackup/ 251 | *.rptproj.bak 252 | 253 | # SQL Server files 254 | *.mdf 255 | *.ldf 256 | *.ndf 257 | 258 | # Business Intelligence projects 259 | *.rdl.data 260 | *.bim.layout 261 | *.bim_*.settings 262 | *.rptproj.rsuser 263 | *- Backup*.rdl 264 | 265 | # Microsoft Fakes 266 | FakesAssemblies/ 267 | 268 | # GhostDoc plugin setting file 269 | *.GhostDoc.xml 270 | 271 | # Node.js Tools for Visual Studio 272 | .ntvs_analysis.dat 273 | node_modules/ 274 | 275 | # Visual Studio 6 build log 276 | *.plg 277 | 278 | # Visual Studio 6 workspace options file 279 | *.opt 280 | 281 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 282 | *.vbw 283 | 284 | # Visual Studio LightSwitch build output 285 | **/*.HTMLClient/GeneratedArtifacts 286 | **/*.DesktopClient/GeneratedArtifacts 287 | **/*.DesktopClient/ModelManifest.xml 288 | **/*.Server/GeneratedArtifacts 289 | **/*.Server/ModelManifest.xml 290 | _Pvt_Extensions 291 | 292 | # Paket dependency manager 293 | .paket/paket.exe 294 | paket-files/ 295 | 296 | # FAKE - F# Make 297 | .fake/ 298 | 299 | # CodeRush personal settings 300 | .cr/personal 301 | 302 | # Python Tools for Visual Studio (PTVS) 303 | __pycache__/ 304 | *.pyc 305 | 306 | # Cake - Uncomment if you are using it 307 | # tools/** 308 | # !tools/packages.config 309 | 310 | # Tabs Studio 311 | *.tss 312 | 313 | # Telerik's JustMock configuration file 314 | *.jmconfig 315 | 316 | # BizTalk build output 317 | *.btp.cs 318 | *.btm.cs 319 | *.odx.cs 320 | *.xsd.cs 321 | 322 | # OpenCover UI analysis results 323 | OpenCover/ 324 | 325 | # Azure Stream Analytics local run output 326 | ASALocalRun/ 327 | 328 | # MSBuild Binary and Structured Log 329 | *.binlog 330 | 331 | # NVidia Nsight GPU debugger configuration file 332 | *.nvuser 333 | 334 | # MFractors (Xamarin productivity tool) working folder 335 | .mfractor/ 336 | 337 | # Local History for Visual Studio 338 | .localhistory/ 339 | 340 | # BeatPulse healthcheck temp database 341 | healthchecksdb 342 | 343 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 344 | MigrationBackup/ 345 | -------------------------------------------------------------------------------- /.nuget/NuGet.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(MSBuildProjectDirectory)\..\ 5 | 6 | 7 | false 8 | 9 | 10 | false 11 | 12 | 13 | true 14 | 15 | 16 | false 17 | 18 | 19 | 20 | 21 | 22 | 26 | 27 | 28 | 29 | 30 | $([System.IO.Path]::Combine($(SolutionDir), ".nuget")) 31 | 32 | 33 | 34 | 35 | $(SolutionDir).nuget 36 | 37 | 38 | 39 | $(MSBuildProjectDirectory)\packages.$(MSBuildProjectName.Replace(' ', '_')).config 40 | $(MSBuildProjectDirectory)\packages.$(MSBuildProjectName).config 41 | 42 | 43 | 44 | $(MSBuildProjectDirectory)\packages.config 45 | $(PackagesProjectConfig) 46 | 47 | 48 | 49 | 50 | $(NuGetToolsPath)\NuGet.exe 51 | @(PackageSource) 52 | 53 | "$(NuGetExePath)" 54 | mono --runtime=v4.0.30319 "$(NuGetExePath)" 55 | 56 | $(TargetDir.Trim('\\')) 57 | 58 | -RequireConsent 59 | -NonInteractive 60 | 61 | "$(SolutionDir) " 62 | "$(SolutionDir)" 63 | 64 | 65 | $(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(NonInteractiveSwitch) $(RequireConsentSwitch) -solutionDir $(PaddedSolutionDir) 66 | $(NuGetCommand) pack "$(ProjectPath)" -Properties "Configuration=$(Configuration);Platform=$(Platform)" $(NonInteractiveSwitch) -OutputDirectory "$(PackageOutputDir)" -symbols 67 | 68 | 69 | 70 | RestorePackages; 71 | $(BuildDependsOn); 72 | 73 | 74 | 75 | 76 | $(BuildDependsOn); 77 | BuildPackage; 78 | 79 | 80 | 81 | 82 | 83 | 84 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 99 | 100 | 103 | 104 | 105 | 106 | 108 | 109 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 141 | 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /src/MarkEmbling.PostcodesIO/PostcodesIOClient.cs: -------------------------------------------------------------------------------- 1 | using MarkEmbling.PostcodesIO.Data; 2 | using MarkEmbling.PostcodesIO.Exceptions; 3 | using MarkEmbling.PostcodesIO.Results; 4 | using RestSharp; 5 | using RestSharp.Serializers.Json; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Net; 9 | using System.Text.Json; 10 | using System.Threading.Tasks; 11 | 12 | namespace MarkEmbling.PostcodesIO 13 | { 14 | public class PostcodesIOClient : IPostcodesIOClient { 15 | private readonly string _endpoint; 16 | private readonly string _proxyServerUrl; 17 | private readonly RestClient _client; 18 | 19 | public PostcodesIOClient(string endpoint = "https://api.postcodes.io", string proxyServerUrl = null) { 20 | _endpoint = endpoint; 21 | _proxyServerUrl = proxyServerUrl; 22 | 23 | var clientOptions = new RestClientOptions(_endpoint); 24 | if (!string.IsNullOrEmpty(_proxyServerUrl)) 25 | { 26 | clientOptions.Proxy = new WebProxy(_proxyServerUrl, true) 27 | { 28 | Credentials = CredentialCache.DefaultCredentials 29 | }; 30 | } 31 | _client = new RestClient( 32 | baseUrl: new Uri(_endpoint), 33 | configureSerialization: s => s.UseSystemTextJson(new JsonSerializerOptions { 34 | PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower }) 35 | ); 36 | } 37 | 38 | private T Execute(RestRequest request) where T : new() { 39 | var response = _client.Execute>(request); 40 | 41 | if (response.ErrorException != null) 42 | throw new PostcodesIOApiException(response.ErrorException); 43 | if (response.Data == null) 44 | throw new PostcodesIOEmptyResponseException(response.StatusCode); 45 | 46 | return response.Data.Result; 47 | } 48 | 49 | private async Task ExecuteAsync(RestRequest request) where T : new() 50 | { 51 | var response = await _client.ExecuteAsync>(request).ConfigureAwait(false); 52 | 53 | if (response.ErrorException != null) 54 | throw new PostcodesIOApiException(response.ErrorException); 55 | if (response.Data == null) 56 | throw new PostcodesIOEmptyResponseException(response.StatusCode); 57 | 58 | return response.Data.Result; 59 | } 60 | 61 | public PostcodeData Lookup(string postcode) { 62 | var request = CreateLookupRequest(postcode); 63 | return Execute(request); 64 | } 65 | 66 | public OutcodeData OutwardCodeLookup(string outcode) { 67 | var request = CreateOutwardCodeLookupRequest(outcode); 68 | return Execute(request); 69 | } 70 | 71 | public Task LookupAsync(string postcode) 72 | { 73 | var request = CreateLookupRequest(postcode); 74 | return ExecuteAsync(request); 75 | } 76 | 77 | public IEnumerable> BulkLookup(IEnumerable postcodes) 78 | { 79 | var request = CreateBulkLookupRequest(postcodes); 80 | return Execute>>(request); 81 | } 82 | 83 | public Task>> BulkLookupAsync(IEnumerable postcodes) 84 | { 85 | var request = CreateBulkLookupRequest(postcodes); 86 | return ExecuteAsync>>(request).ContinueWith(t => t.Result as IEnumerable>, TaskContinuationOptions.OnlyOnRanToCompletion); 87 | } 88 | 89 | public IEnumerable Query(string q, int? limit = null) 90 | { 91 | var request = CreateQueryRequest(q, limit); 92 | return Execute>(request); 93 | } 94 | 95 | public Task> QueryAsync(string q, int? limit = null) 96 | { 97 | var request = CreateQueryRequest(q, limit); 98 | return ExecuteAsync>(request).ContinueWith(t => t.Result as IEnumerable, TaskContinuationOptions.OnlyOnRanToCompletion); 99 | } 100 | 101 | public bool Validate(string postcode) 102 | { 103 | var request = CreateValidateRequest(postcode); 104 | return Execute(request); 105 | } 106 | 107 | public Task ValidateAsync(string postcode) 108 | { 109 | var request = CreateValidateRequest(postcode); 110 | return ExecuteAsync(request); 111 | } 112 | 113 | public IEnumerable LookupLatLon(ReverseGeocodeQuery query) 114 | { 115 | var request = CreateLookupLocationRequest(query); 116 | return Execute>(request); 117 | } 118 | 119 | public Task> LookupLatLonAsync(ReverseGeocodeQuery query) 120 | { 121 | var request = CreateLookupLocationRequest(query); 122 | return ExecuteAsync>(request).ContinueWith(t => t.Result as IEnumerable, TaskContinuationOptions.OnlyOnRanToCompletion); 123 | } 124 | 125 | public IEnumerable>> BulkLookupLatLon(IEnumerable queries) 126 | { 127 | var request = CreateBulkLookupLatLon(queries); 128 | return Execute>>>(request); 129 | } 130 | 131 | public Task>>> BulkLookupLatLonAsync(IEnumerable queries) 132 | { 133 | var request = CreateBulkLookupLatLon(queries); 134 | return ExecuteAsync>>>(request).ContinueWith(t => t.Result as IEnumerable>>, TaskContinuationOptions.OnlyOnRanToCompletion); 135 | } 136 | 137 | public IEnumerable Autocomplete(string postcode, int? limit = null) 138 | { 139 | var request = CreateAutocompleteRequest(postcode, limit); 140 | return Execute>(request); 141 | } 142 | 143 | public Task> AutocompleteAsync(string postcode, int? limit = null) 144 | { 145 | var request = CreateAutocompleteRequest(postcode, limit); 146 | return ExecuteAsync>(request).ContinueWith(t => t.Result as IEnumerable, TaskContinuationOptions.OnlyOnRanToCompletion); 147 | } 148 | 149 | public PostcodeData Random() 150 | { 151 | var request = CreateRandomRequest(); 152 | return Execute(request); 153 | } 154 | 155 | public Task RandomAsync() 156 | { 157 | var request = CreateRandomRequest(); 158 | return ExecuteAsync(request); 159 | } 160 | 161 | public IEnumerable Nearest(string postcode, int? limit = null, int? radius = null) 162 | { 163 | var request = CreateNearest(postcode, limit, radius); 164 | return Execute>(request); 165 | } 166 | 167 | public Task> NearestAsync(string postcode, int? limit = null, int? radius = null) 168 | { 169 | var request = CreateNearest(postcode, limit, radius); 170 | return ExecuteAsync>(request).ContinueWith(t => t.Result as IEnumerable, TaskContinuationOptions.OnlyOnRanToCompletion); 171 | } 172 | 173 | public TerminatedPostcodeData Terminated(string postcode) 174 | { 175 | var request = CreateTerminatedRequest(postcode); 176 | return Execute(request); 177 | } 178 | 179 | public Task TerminatedAsync(string postcode) 180 | { 181 | var request = CreateTerminatedRequest(postcode); 182 | return ExecuteAsync(request); 183 | } 184 | 185 | private static RestRequest CreateBulkLookupRequest(IEnumerable postcodes) 186 | { 187 | var request = new RestRequest("postcodes", Method.Post) 188 | { 189 | RequestFormat = DataFormat.Json 190 | }; 191 | request.AddJsonBody(new { postcodes }); 192 | return request; 193 | } 194 | 195 | private static RestRequest CreateQueryRequest(string q, int? limit) 196 | { 197 | var request = new RestRequest("postcodes", Method.Get); 198 | request.AddQueryParameter("q", q); 199 | if (limit.HasValue) request.AddParameter("limit", limit.Value); 200 | return request; 201 | } 202 | 203 | private static RestRequest CreateValidateRequest(string postcode) 204 | { 205 | var request = new RestRequest(string.Format("postcodes/{0}/validate", postcode), Method.Get); 206 | return request; 207 | } 208 | 209 | private static RestRequest CreateLookupLocationRequest(ReverseGeocodeQuery query) 210 | { 211 | var request = new RestRequest("postcodes", Method.Get); 212 | request.AddParameter("lat", query.Latitude); 213 | request.AddParameter("lon", query.Longitude); 214 | if (query.Limit.HasValue) request.AddParameter("limit", query.Limit.Value); 215 | if (query.Radius.HasValue) request.AddParameter("radius", query.Radius.Value); 216 | if (query.WideSearch.HasValue) request.AddParameter("widesearch", query.WideSearch.Value); 217 | return request; 218 | } 219 | 220 | private static RestRequest CreateLookupRequest(string postcode) 221 | { 222 | return new RestRequest(string.Format("postcodes/{0}", postcode), Method.Get); 223 | } 224 | 225 | private static RestRequest CreateOutwardCodeLookupRequest(string outcode) 226 | { 227 | return new RestRequest(string.Format("outcodes/{0}", outcode), Method.Get); 228 | } 229 | 230 | private static RestRequest CreateBulkLookupLatLon(IEnumerable queries) 231 | { 232 | var request = new RestRequest("postcodes", Method.Post); 233 | request.AddJsonBody(new { geolocations = queries }); 234 | return request; 235 | } 236 | 237 | private static RestRequest CreateAutocompleteRequest(string postcode, int? limit) 238 | { 239 | var request = new RestRequest(string.Format("postcodes/{0}/autocomplete", postcode), Method.Get); 240 | if (limit.HasValue) request.AddParameter("limit", limit.Value); 241 | return request; 242 | } 243 | 244 | private static RestRequest CreateRandomRequest() 245 | { 246 | var request = new RestRequest("random/postcodes", Method.Get); 247 | return request; 248 | } 249 | 250 | private static RestRequest CreateNearest(string postcode, int? limit, int? radius) 251 | { 252 | var request = new RestRequest(string.Format("postcodes/{0}/nearest", postcode), Method.Get); 253 | if (limit.HasValue) request.AddParameter("limit", limit.Value); 254 | if (radius.HasValue) request.AddParameter("radius", radius.Value); 255 | return request; 256 | } 257 | 258 | private static RestRequest CreateTerminatedRequest(string postcode) 259 | { 260 | return new RestRequest(string.Format("terminated_postcodes/{0}", postcode), Method.Get); 261 | } 262 | } 263 | } --------------------------------------------------------------------------------