diff --git a/BritishHorseRacingApi.Tests/ApiClientTests.cs b/BritishHorseRacingApi.Tests/ApiClientTests.cs
new file mode 100644
index 0000000..dd68271
--- /dev/null
+++ b/BritishHorseRacingApi.Tests/ApiClientTests.cs
@@ -0,0 +1,133 @@
+using System;
+using System.Net.Http;
+using System.Threading.Tasks;
+using System.Linq;
+using Xunit;
+using SuccessfulCodeTools.LowLevelHttpClient;
+
+namespace BritishHorseRacingApi.Tests
+{
+ public class ApiClientTests
+ {
+ [Fact]
+ public async Task GetToken()
+ {
+ //Arrange
+ var client = new ApiClient();
+ //Act
+ var token = await client.GetNewToken();
+ //Assert
+ Assert.False(String.IsNullOrWhiteSpace(token));
+ }
+ private const string _userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36";
+
+ [Fact]
+ public async Task Test()
+ {
+ //Arrange
+ var client = new CustomHttpClient(13718);
+ var message = new CustomHttpRequestMessage(HttpMethod.Get, "http://www.britishhorseracing.com/feeds/v1/token/request");
+ message.Headers.Add("Accept", "application/json, text/plain, */*");
+ message.Headers.Add("User-Agent", _userAgent);
+ message.Headers.Add("Host", "www.britishhorseracing.com");
+ message.Headers.Add("Connection", "close");
+ message.Headers.Add("accept-encoding", "gzip, deflate");
+ var result = await client.SendAsync(message);
+ Assert.NotNull(result);
+ }
+
+ [Fact]
+ public async Task GetRaceResult()
+ {
+ //Arrange
+ var client = new ApiClient();
+ var token = await client.GetNewToken();
+ //Act
+ var result = await client.GetRaceResults(2018, 42638, 0);
+ //Assert
+ Assert.Equal(6, result.Total);
+ Assert.Equal(6, result.Data.Length);
+ Assert.Equal(42638, result.Data[0].RaceId);
+ }
+
+ [Fact]
+ public async Task GetFixtures()
+ {
+ //Arrange
+ var client = new ApiClient();
+ var token = await client.GetNewToken();
+ //Act
+ var result = await client.GetFixtures(2019, 2);
+ //Assert
+ Assert.Equal(111, result.Total);
+ Assert.Equal(10, result.Data.Length);
+ Assert.Equal(18142, result.Data[0].FixtureId);
+ }
+
+ [Fact]
+ public async Task GetAllFixturesForDay()
+ {
+ //Arrange
+ var client = new ApiClient();
+ var token = await client.GetNewToken();
+ //Act
+ var results = await client.GetAllFixturesForDay(2019, 2, 28);
+ //Assert
+ Assert.True(results.All(e => e.FixtureDate.Day == 28));
+ }
+
+ [Fact]
+ public async Task GetAllRacesForDay()
+ {
+ var client = new ApiClient();
+ var token = await client.GetNewToken();
+ var results = await client.GetAllRacesForDay(2019, 2, 28);
+ Assert.True(results.Count > 0);
+ }
+
+ [Fact]
+ public async Task GetRaceRunners()
+ {
+ var client = new ApiClient();
+ var token = await client.GetNewToken();
+ var results = await client.GetRaceRunners(2020, 50144, 0);
+ Assert.Equal(13, results.Data.Length);
+ }
+
+ [Fact]
+ public async Task GetAllRaceResultsForDay()
+ {
+ var client = new ApiClient();
+ var token = await client.GetNewToken();
+ var results = await client.GetAllRaceResultsForDay(2020, 2, 23);
+ Assert.True(results.Count > 0);
+ }
+
+ [Fact]
+ public async Task GetRaces()
+ {
+ //Arrange
+ var client = new ApiClient();
+ var token = await client.GetNewToken();
+ //Act
+ var result = await client.GetRaces(2019, 18188);
+ //Arrange
+ Assert.Equal(6, result.Total);
+ Assert.Equal(6, result.Data.Length);
+ Assert.Equal(46136, result.Data[0].RaceId);
+ }
+
+ [Fact]
+ public async Task GetRaceCourse()
+ {
+ //Arrange
+ var client = new ApiClient();
+ var token = await client.GetNewToken();
+ //Act
+ var result = await client.GetRacecourse(18);
+ //Arrange
+ Assert.Equal(18, result.CourseId);
+
+ }
+ }
+}
diff --git a/BritishHorseRacingApi.Tests/BritishHorseRacingApi.Tests.csproj b/BritishHorseRacingApi.Tests/BritishHorseRacingApi.Tests.csproj
new file mode 100644
index 0000000..3501b94
--- /dev/null
+++ b/BritishHorseRacingApi.Tests/BritishHorseRacingApi.Tests.csproj
@@ -0,0 +1,15 @@
+
+
+
+ netstandard2.0
+
+
+
+
+
+
+
+
+
+
+
diff --git a/BritishHorseRacingApi.Tests/DataConverterHelperTests.cs b/BritishHorseRacingApi.Tests/DataConverterHelperTests.cs
new file mode 100644
index 0000000..3f4919b
--- /dev/null
+++ b/BritishHorseRacingApi.Tests/DataConverterHelperTests.cs
@@ -0,0 +1,39 @@
+using BritishHorseRacingApi.Helpers;
+using System;
+using Xunit;
+
+namespace BritishHorseRacingApi.Tests
+{
+ public class DataConverterHelperTests
+ {
+ [Theory()]
+ [InlineData("1m 24.73s", 1, 24, 73)]
+ [InlineData("5m 42s", 5, 42, 0)]
+ [InlineData("7m 2.9s", 7, 2, 9)]
+ public void TestStopWatchStringConversion(string time, int mins, int seconds, int milliseconds)
+ {
+ //Arrange
+ //Act
+ var totalMilliseconds = DataConverterHelper.StopWatchStringToMilliseconds(time);
+ //Assert
+ var timeSpan = TimeSpan.FromMilliseconds((double)totalMilliseconds);
+ Assert.Equal(mins, timeSpan.Minutes);
+ Assert.Equal(seconds, timeSpan.Seconds);
+ Assert.Equal(milliseconds, timeSpan.Milliseconds);
+ }
+
+ [Theory()]
+ [InlineData("1m 1f 104y", 2084)]
+ [InlineData("1f 104y", 324)]
+ [InlineData("104y", 104)]
+ public void TestDistanceStringToYardsConversion(string distance, int expectedResult)
+ {
+ //Arrange
+ //Act
+ var result = DataConverterHelper.DistanceStringToYards(distance);
+ //Assert
+ Assert.True(result.HasValue);
+ Assert.Equal(expectedResult, result.Value);
+ }
+ }
+}
diff --git a/BritishHorseRacingApi.sln b/BritishHorseRacingApi.sln
new file mode 100644
index 0000000..992b6d2
--- /dev/null
+++ b/BritishHorseRacingApi.sln
@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29728.190
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BritishHorseRacingApi", "BritishHorseRacingApi\BritishHorseRacingApi.csproj", "{A8C8795B-EDA8-4AB5-9B85-3DA8E883CBD6}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BritishHorseRacingApi.Tests", "BritishHorseRacingApi.Tests\BritishHorseRacingApi.Tests.csproj", "{64B65FA2-BE79-40E5-810D-70C13514A791}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {A8C8795B-EDA8-4AB5-9B85-3DA8E883CBD6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A8C8795B-EDA8-4AB5-9B85-3DA8E883CBD6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A8C8795B-EDA8-4AB5-9B85-3DA8E883CBD6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A8C8795B-EDA8-4AB5-9B85-3DA8E883CBD6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {64B65FA2-BE79-40E5-810D-70C13514A791}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {64B65FA2-BE79-40E5-810D-70C13514A791}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {64B65FA2-BE79-40E5-810D-70C13514A791}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {64B65FA2-BE79-40E5-810D-70C13514A791}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {7724F108-F427-47FD-87ED-81C538932C80}
+ EndGlobalSection
+EndGlobal
diff --git a/BritishHorseRacingApi/ApiClient.cs b/BritishHorseRacingApi/ApiClient.cs
new file mode 100644
index 0000000..8b0e804
--- /dev/null
+++ b/BritishHorseRacingApi/ApiClient.cs
@@ -0,0 +1,285 @@
+using BritishHorseRacingApi.Models;
+using Newtonsoft.Json.Linq;
+using SuccessfulCodeTools;
+using SuccessfulCodeTools.LowLevelHttpClient;
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Http;
+using System.Threading.Tasks;
+using System.Web;
+
+namespace BritishHorseRacingApi
+{
+ public class ApiClient : BaseApiClient
+ {
+ private const int _waitTimeAfterFail = 10;
+ private const string _userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.109 Safari/537.36";
+
+ public ApiClient(TimeSpan? delay = null) : base(delay) { }
+
+ public static class Endpoints
+ {
+ public const string Endpoint = "https://www.britishhorseracing.com/feeds";
+ private const string V1Endpoint = Endpoint + "/v1";
+ private const string V3Endpoint = Endpoint + "/v3";
+ public const string Fixtures = V3Endpoint + "/fixtures";
+ public const string Races = Fixtures + "/{0}/{1}/races";
+ public const string Runners = Races + "/races/{0}/{1}/{2}/entries";
+ public const string RaceResults = V3Endpoint + "/races/{0}/{1}/{2}/results";
+ public const string Racecourses = V1Endpoint + "/racecourses";
+ public const string Jockeys = V3Endpoint + "/jockeys";
+ public const string Animals = V3Endpoint + "/racehorses/{0}";
+ }
+
+ public virtual string CurrentToken { get; set; }
+
+ public async Task GetNewToken()
+ {
+ await DelayBetweenRequests();
+ var message = CreateTokenMessage();
+ var response = await _client.SendAsync(message);
+ if (!response.IsSuccessStatusCode)
+ {
+ throw new Exception("Token not retrieved");
+ }
+
+ var content = response.Content;
+ while (content.StartsWith("<"))
+ {
+ await Task.Delay(TimeSpan.FromMinutes(_waitTimeAfterFail));
+ //_client.OutgoingPort++;
+ message = CreateTokenMessage();
+ response = await _client.SendAsync(message);
+ content = response.Content;
+ }
+
+ var tokenResponse = content.DeserializeJson();
+ CurrentToken = tokenResponse.Token;
+
+ return tokenResponse.Token;
+ }
+
+ private static CustomHttpRequestMessage CreateTokenMessage()
+ {
+ var message = new CustomHttpRequestMessage(HttpMethod.Get, "http://www.britishhorseracing.com/feeds/v1/token/request");
+ message.Headers.Add("Accept", "application/json, text/plain, */*");
+ message.Headers.Add("Accept-Language", "en-US,en;q=0.9");
+ message.Headers.Add("User-Agent", _userAgent);
+ message.Headers.Add("Host", "www.britishhorseracing.com");
+ message.Headers.Add("cache-control", "no-cache");
+ message.Headers.Add("Postman-Token", "887efa5f-fe8e-4ce5-bfc1-810789691391");
+ message.Headers.Add("Connection", "close");
+
+ message.Headers.Add("accept-encoding", "gzip, deflate");
+ return message;
+ }
+
+ public async Task> GetAllRacesForDay(int year, int month, int day)
+ {
+ var fixtures = await GetAllFixturesForDay(year, month, day);
+ var races = new List();
+ foreach(var fixture in fixtures)
+ {
+ var page = await GetRaces(year, fixture.FixtureId);
+ races.AddRange(page.Data);
+ }
+
+ return races;
+ }
+
+ public async Task> GetAllRaceResultsForDay(int year, int month, int day)
+ {
+ var races = await GetAllRacesForDay(year, month, day);
+ var runnerResults = new List();
+ foreach (var race in races)
+ {
+ var page = await GetRaceResults(year, race.RaceId, race.DivisionSequence);
+ runnerResults.AddRange(page.Data);
+ }
+ return runnerResults;
+ }
+
+ public async Task> GetJockeys(int page = 1, int perPage = 2)
+ {
+ var uriBuilder = new UriBuilder(Endpoints.Jockeys)
+ {
+ Port = -1
+ };
+ var query = HttpUtility.ParseQueryString(uriBuilder.Query);
+ query["page"] = page.ToString();
+ query["per_page"] = perPage.ToString();
+ uriBuilder.Query = query.ToString();
+
+ var results = await ExecuteRequestForPagedResults(uriBuilder.ToString());
+ return results;
+ }
+
+ public async Task GetJockey(int id)
+ {
+ var result = await ExecuteRequest>(Endpoints.Jockeys + $"/{id}", (token) => {
+ return !(token["data"] is JArray);
+ });
+
+ if(result == null)
+ {
+ return null;
+ }
+
+ return result.Data;
+ }
+
+ public async Task> GetRaceResults(int year, int raceId, int divisionSequence = 0)
+ {
+ var requestUrl = String.Format(Endpoints.RaceResults, year, raceId, divisionSequence);
+ return await ExecuteRequestForPagedResults(requestUrl);
+ }
+
+ public async Task> GetRaceRunners(int year, int raceId, int divisionSequence = 0)
+ {
+ var requestUrl = String.Format(Endpoints.Runners, year, raceId, divisionSequence);
+ return await ExecuteRequestForPagedResults(requestUrl);
+ }
+
+ public async Task> GetRacecourses()
+ {
+ return await ExecuteRequestForPagedResults(Endpoints.Racecourses);
+ }
+
+ public async Task GetRacecourse(int id)
+ {
+ var result = await ExecuteRequest>(Endpoints.Racecourses + "/" + id);
+ return result.Data;
+ }
+
+ public async Task> GetRaces(int year, int fixtureId)
+ {
+ var finalUrl = String.Format(Endpoints.Races, year, fixtureId);
+ return await ExecuteRequestForPagedResults(finalUrl);
+ }
+
+ public async Task GetAnimal(int id)
+ {
+ var endpoint = String.Format(Endpoints.Animals, id);
+ var result = await ExecuteRequest>(endpoint, (token) => !(token["data"] is JArray));
+
+ if (result == null)
+ {
+ return null;
+ }
+
+ return result.Data;
+ }
+
+ public async Task> GetAllFixturesForDay(int year, int month, int day)
+ {
+ var fixtures = new List();
+ var pages = 1;
+
+ for(var i = 1; i <= pages; i++)
+ {
+ var resultsPage = await GetFixtures(year, month, i);
+
+ foreach(var item in resultsPage.Data)
+ {
+ //Items returned day descending, skip until relevant day
+ if (item.FixtureDate.Day > day)
+ continue;
+
+ //If we hit another day dont bother getting more
+ if (item.FixtureDate.Day < day)
+ return fixtures;
+
+ fixtures.Add(item);
+ }
+
+ if (resultsPage.CurrentPage == resultsPage.LastPage)
+ return fixtures;
+
+ pages++;
+ }
+
+ return fixtures;
+ }
+
+ public async Task> GetFixtures(int? year = null, int? month = null, int page = 1, int perPage = 10, string order = "desc", bool? resultsAvailable = true)
+ {
+ var uriBuilder = new UriBuilder(Endpoints.Fixtures);
+ var query = HttpUtility.ParseQueryString(uriBuilder.Query);
+ query["page"] = page.ToString();
+ query["per_page"] = perPage.ToString();
+ query["order"] = order;
+ if (year != null)
+ {
+ query["year"] = year.ToString();
+ }
+
+ if(month != null)
+ {
+ query["month"] = month.ToString();
+ }
+
+ if(resultsAvailable != null)
+ {
+ query["resultAvailable"] = resultsAvailable.Value.ToString();
+ }
+
+ uriBuilder.Query = query.ToString();
+ var finalUrl = uriBuilder.ToString();
+ return await ExecuteRequestForPagedResults(finalUrl);
+ }
+
+ private Task> ExecuteRequestForPagedResults(string endpoint)
+ {
+ return ExecuteRequest>(endpoint);
+ }
+
+ private async Task ExecuteRequest(string endpoint, Func preDeserilizationValidation = null)
+ {
+ var response = await SendRequest(endpoint);
+
+ if(response.StatusCode == HttpStatusCode.Unauthorized)
+ {
+ await GetNewToken();
+ response = await SendRequest(endpoint);
+ }
+
+ var content = response.Content;
+ while (content.StartsWith("<"))
+ {
+ await Task.Delay(TimeSpan.FromMinutes(_waitTimeAfterFail));
+ //_client.OutgoingPort++;
+ await GetNewToken();
+ response = await SendRequest(endpoint);
+ content = response.Content;
+ }
+
+ if (!response.IsSuccessStatusCode)
+ {
+ throw new Exception($"Unexpected error: {content}" );
+ }
+
+ if(preDeserilizationValidation != null && !preDeserilizationValidation(JToken.Parse(content)))
+ {
+ return default(T);
+ }
+
+ var deserializedResponse = content.DeserializeJson();
+ return deserializedResponse;
+ }
+
+ protected override async Task PresendRequestAlterations(CustomHttpRequestMessage message)
+ {
+ message.Headers.Add("User-Agent", _userAgent);
+ if (CurrentToken == null)
+ {
+ await GetNewToken();
+ }
+ //message.Headers.TryAddWithoutValidation("Authorization", CurrentToken);
+ message.Headers.Add("Authorization", CurrentToken);
+ message.Headers.Add("Accept", "application/json");
+ message.Headers.Add("accept-encoding", "gzip, deflate");
+ message.Headers.Add("Accept-Language", "en-US,en;q=0.9");
+ }
+ }
+}
diff --git a/BritishHorseRacingApi/BaseApiClient.cs b/BritishHorseRacingApi/BaseApiClient.cs
new file mode 100644
index 0000000..f37aae5
--- /dev/null
+++ b/BritishHorseRacingApi/BaseApiClient.cs
@@ -0,0 +1,55 @@
+using SuccessfulCodeTools.LowLevelHttpClient;
+using System;
+using System.Net;
+using System.Net.Http;
+using System.Threading.Tasks;
+
+namespace BritishHorseRacingApi
+{
+ public abstract class BaseApiClient
+ {
+ private const int _startPort = 55948;
+ protected readonly CustomHttpClient _client = new CustomHttpClient(_startPort);
+ private readonly TimeSpan? _delay;
+ private DateTime _lastRequest = DateTime.UtcNow;
+
+ public BaseApiClient(TimeSpan? delay = null)
+ {
+ _delay = delay;
+ _client.DefaultHeaders.Add("Cookie", "bha-ie-test=true; D_SID=188.172.158.138:JT/OEx87nl5ScspTQk38UsDfuIz94DbCf5CYsIC7WpU; _ga=GA1.2.578018316.1549102711; __utmz=36595059.1549187117.8.2.utmcsr=google|utmccn=(organic)|utmcmd=organic|utmctr=(not%20provided); __utma=36595059.578018316.1549102711.1550365414.1551216842.16; __utmc=36595059; D_IID=80C3F85C-C90F-31DF-8830-AF181B9C7A2E; D_UID=33DA8B87-0FB7-3D76-9DA6-A650196A2B16; D_ZID=68EA5585-B6A0-34F7-939A-A0C1B40036B3; D_ZUID=0E647F3A-6570-32D2-9673-8B49FFEBE647; D_HID=EBE66F1F-FA2C-3373-8163-110008C50265");
+ ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
+ }
+
+ protected async Task DelayBetweenRequests()
+ {
+ if (!_delay.HasValue)
+ {
+ _lastRequest = DateTime.UtcNow;
+ return;
+ }
+
+ var timeSinceLastRequest = DateTime.UtcNow - _lastRequest;
+ var timeToWait = _delay.Value - timeSinceLastRequest;
+ if (timeToWait.TotalMilliseconds > 0)
+ {
+ await Task.Delay(timeToWait);
+ }
+ _lastRequest = DateTime.UtcNow;
+ }
+
+ protected async Task SendRequest(string endpoint)
+ {
+ await DelayBetweenRequests();
+
+ var message = new CustomHttpRequestMessage(HttpMethod.Get, endpoint);
+ message.Headers.Add("Accept", "application/json, text/plain, */*");
+
+ await PresendRequestAlterations(message);
+
+ var response = await _client.SendAsync(message);
+ return response;
+ }
+
+ protected abstract Task PresendRequestAlterations(CustomHttpRequestMessage message);
+ }
+}
diff --git a/BritishHorseRacingApi/BritishHorseRacingApi.csproj b/BritishHorseRacingApi/BritishHorseRacingApi.csproj
new file mode 100644
index 0000000..d2f7546
--- /dev/null
+++ b/BritishHorseRacingApi/BritishHorseRacingApi.csproj
@@ -0,0 +1,11 @@
+
+
+
+ netstandard2.0
+
+
+
+
+
+
+
diff --git a/BritishHorseRacingApi/FalseOrObjectConverter.cs b/BritishHorseRacingApi/FalseOrObjectConverter.cs
new file mode 100644
index 0000000..7e84b2c
--- /dev/null
+++ b/BritishHorseRacingApi/FalseOrObjectConverter.cs
@@ -0,0 +1,28 @@
+using Newtonsoft.Json;
+using System;
+
+namespace BritishHorseRacingApi
+{
+ public class FalseOrObjectConverter : JsonConverter
+ {
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ writer.WriteValue(((bool)value) ? 1 : 0);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ if(reader.Value != null)
+ {
+ return null;
+ }
+
+ return serializer.Deserialize(reader, objectType);
+ }
+
+ public override bool CanConvert(Type objectType)
+ {
+ return true;
+ }
+ }
+}
diff --git a/BritishHorseRacingApi/Helpers/DataConverterHelper.cs b/BritishHorseRacingApi/Helpers/DataConverterHelper.cs
new file mode 100644
index 0000000..b8b23a0
--- /dev/null
+++ b/BritishHorseRacingApi/Helpers/DataConverterHelper.cs
@@ -0,0 +1,66 @@
+using System;
+using System.Text;
+using System.Text.RegularExpressions;
+
+namespace BritishHorseRacingApi.Helpers
+{
+ public static class DataConverterHelper
+ {
+ private static Regex _stopwatchTimeRegex = new Regex(@"^(?'minutes'[0-9]*(?=m))?m? ?(?'seconds'[0-9\.]*)s ?(?:\(\+)?(?'lossDifference'[0-9\.]*)(?:s\))?", RegexOptions.Compiled);
+ public static int? StopWatchStringToMilliseconds(string stopwatchValue)
+ {
+ var match = _stopwatchTimeRegex.Match(stopwatchValue);
+ var minutes = GetComponent(match, "minutes", 0);
+ var secondsAndMilliseconds = GetComponent(match, "seconds", "0").Split('.');
+
+ var seconds = int.Parse(secondsAndMilliseconds[0]);
+ var milliseconds = secondsAndMilliseconds.Length > 1 ? int.Parse(secondsAndMilliseconds[1]) : 0;
+
+ var timespan = new TimeSpan(0, 0, minutes, seconds, milliseconds);
+
+ return (int)timespan.TotalMilliseconds;
+ }
+
+ private static Regex _distanceExtractorRegex = new Regex(BuildExtractComponentsRegex(
+ ("miles", "m"),
+ ("furlongs", "f"),
+ ("yards", "y")), RegexOptions.Compiled);
+
+ public static int? DistanceStringToYards(string distance)
+ {
+ if (String.IsNullOrWhiteSpace(distance))
+ return null;
+
+ var match = _distanceExtractorRegex.Match(distance);
+ var miles = GetComponent(match, "miles", 0);
+ var furlongs = GetComponent(match, "furlongs", 0);
+ var yards = GetComponent(match, "yards", 0);
+ var totalYards = (miles * 1760) + (furlongs * 220) + yards;
+
+ return totalYards;
+ }
+
+ private static string BuildExtractComponentsRegex(params (string label, string indicator)[] pairs)
+ {
+ var sb = new StringBuilder();
+ Array.ForEach(pairs, e => sb.AppendFormat(@"(?'{0}'[0-9\.]*(?={1}))?{1}? ?", e.label, e.indicator));
+ return sb.ToString();
+ }
+
+ private static T GetComponent(Match match, string component, T defaultValue = default)
+ {
+ var group = match.Groups[component];
+ if (String.IsNullOrWhiteSpace(group.Value))
+ return defaultValue;
+
+ return (T)Convert.ChangeType(group.Value, typeof(T));
+ }
+
+ public static int StopwatchStringDifferenceToMilliseconds(string timeDifferenceString)
+ {
+ var match = _stopwatchTimeRegex.Match(timeDifferenceString);
+ var lossDifference = GetComponent(match, "lossDifference", 0);
+ return (int)TimeSpan.FromSeconds(lossDifference).TotalMilliseconds;
+ }
+ }
+}
diff --git a/BritishHorseRacingApi/Models/Animal.cs b/BritishHorseRacingApi/Models/Animal.cs
new file mode 100644
index 0000000..b7dba80
--- /dev/null
+++ b/BritishHorseRacingApi/Models/Animal.cs
@@ -0,0 +1,27 @@
+using System;
+
+namespace BritishHorseRacingApi.Models
+{
+ public class Animal
+ {
+ public int Id { get; set; }
+ public string Name { get; set; }
+ public DateTime DateOfBirth { get; set; }
+ public DateTime? DeathDate { get; set; }
+ public string DeathNote { get; set; }
+ public string Moniker { get; set; }
+ public string Sex { get; set; }
+ public string TrainerName { get; set; }
+ public int? TrainerId { get; set; }
+ public string OwnerName { get; set; }
+ public int? OwnerId { get; set; }
+ public decimal? RatingAwt { get; set; }
+ public decimal? RatingChase { get; set; }
+ public decimal? RatingFlat { get; set; }
+ public decimal? RatingHurdle { get; set; }
+ public string NameWithNoCountry { get; set; }
+ public PerformanceDetail[] PerformanceDetails { get; set; }
+ public Lineage Lineage { get; set; }
+ }
+
+}
diff --git a/BritishHorseRacingApi/Models/Career.cs b/BritishHorseRacingApi/Models/Career.cs
new file mode 100644
index 0000000..1ea6aad
--- /dev/null
+++ b/BritishHorseRacingApi/Models/Career.cs
@@ -0,0 +1,11 @@
+namespace BritishHorseRacingApi.Models
+{
+ public partial class Career
+ {
+ public int Wins { get; set; }
+ public int Rides { get; set; }
+ public decimal WinsToRides { get; set; }
+ public int GroupListedWins { get; set; }
+ public decimal PrizeMoney { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/BritishHorseRacingApi/Models/Country.cs b/BritishHorseRacingApi/Models/Country.cs
new file mode 100644
index 0000000..9bb55cf
--- /dev/null
+++ b/BritishHorseRacingApi/Models/Country.cs
@@ -0,0 +1,5 @@
+namespace BritishHorseRacingApi.Models
+{
+ public enum Country { Uk };
+
+}
diff --git a/BritishHorseRacingApi/Models/Fixture.cs b/BritishHorseRacingApi/Models/Fixture.cs
new file mode 100644
index 0000000..3ed2dce
--- /dev/null
+++ b/BritishHorseRacingApi/Models/Fixture.cs
@@ -0,0 +1,39 @@
+using System;
+
+namespace BritishHorseRacingApi.Models
+{
+ public class Fixture
+ {
+ public int FixtureId { get; set; }
+ public int FixtureYear { get; set; }
+ public DateTime FixtureDate { get; set; }
+ public string Distance { get; set; }
+ public string CourseName { get; set; }
+ public DateTime? FirstRace { get; set; }
+ public bool ResultsAvailable { get; set; }
+ public bool RacecardAvailable { get; set; }
+ public bool EntriesAvailable { get; set; }
+ public int BlackTypeRaces { get; set; }
+ public int BcsEvent { get; set; }
+ public int NumberOfRaces { get; set; }
+ public string FixtureName { get; set; }
+ public FixtureType FixtureType { get; set; }
+ public int MeetingId { get; set; }
+ public RacingTrackType RacingTrackType { get; set; }
+ public RacePlanningCode RacePlanningCode { get; set; }
+ public int CourseId { get; set; }
+ public string TicketsLink { get; set; }
+ public int? MajorEvent { get; set; }
+ public string Going { get; set; }
+ public string Weather { get; set; }
+ public string Other { get; set; }
+ public string Stalls { get; set; }
+ public string Rail { get; set; }
+ public int AbandonedReasonCode { get; set; }
+ public FixtureSession FixtureSession { get; set; }
+ public string GoingUpdatedDate { get; set; }
+ public string GoingUpdatedTime { get; set; }
+ public Official[] Officials { get; set; }
+ public NonRunner[] NonRunners { get; set; }
+ }
+}
diff --git a/BritishHorseRacingApi/Models/FixtureSession.cs b/BritishHorseRacingApi/Models/FixtureSession.cs
new file mode 100644
index 0000000..4b223b1
--- /dev/null
+++ b/BritishHorseRacingApi/Models/FixtureSession.cs
@@ -0,0 +1,4 @@
+namespace BritishHorseRacingApi.Models
+{
+ public enum FixtureSession { Afternoon, Evening, Floodlit };
+}
diff --git a/BritishHorseRacingApi/Models/FixtureType.cs b/BritishHorseRacingApi/Models/FixtureType.cs
new file mode 100644
index 0000000..6ba7bf4
--- /dev/null
+++ b/BritishHorseRacingApi/Models/FixtureType.cs
@@ -0,0 +1,4 @@
+namespace BritishHorseRacingApi.Models
+{
+ public enum FixtureType { Flat, Jump, Mixed };
+}
diff --git a/BritishHorseRacingApi/Models/Jockey.cs b/BritishHorseRacingApi/Models/Jockey.cs
new file mode 100644
index 0000000..cb6248d
--- /dev/null
+++ b/BritishHorseRacingApi/Models/Jockey.cs
@@ -0,0 +1,19 @@
+using System;
+
+namespace BritishHorseRacingApi.Models
+{
+ public class Jockey
+ {
+ public int Id { get; set; }
+ public string Name { get; set; }
+ public string County { get; set; }
+ public DateTimeOffset DateOfBirth { get; set; }
+ public int Age { get; set; }
+ public int LowestRidingWeight { get; set; }
+ public int? DaysSinceLastWin { get; set; }
+ public string LicenseType { get; set; }
+ public string JockeyImage { get; set; }
+ public Career Career { get; set; }
+ public RecentPerformance[] RecentPerformances { get; set; }
+ }
+}
diff --git a/BritishHorseRacingApi/Models/JockeySummary.cs b/BritishHorseRacingApi/Models/JockeySummary.cs
new file mode 100644
index 0000000..3555760
--- /dev/null
+++ b/BritishHorseRacingApi/Models/JockeySummary.cs
@@ -0,0 +1,13 @@
+namespace BritishHorseRacingApi.Models
+{
+ public class JockeySummary
+ {
+ public int Id { get; set; }
+ public string Name { get; set; }
+ public string County { get; set; }
+ public int Age { get; set; }
+ public int? DaysSinceLastWin { get; set; }
+ public string LicenseType { get; set; }
+ public string JockeyImage { get; set; }
+ }
+}
diff --git a/BritishHorseRacingApi/Models/LastRun.cs b/BritishHorseRacingApi/Models/LastRun.cs
new file mode 100644
index 0000000..8bbbf64
--- /dev/null
+++ b/BritishHorseRacingApi/Models/LastRun.cs
@@ -0,0 +1,13 @@
+using Newtonsoft.Json;
+
+namespace BritishHorseRacingApi.Models
+{
+ public partial class LastRun
+ {
+ [JsonProperty("raceType")]
+ public string RaceType { get; set; }
+
+ [JsonProperty("days")]
+ public int Days { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/BritishHorseRacingApi/Models/Lineage.cs b/BritishHorseRacingApi/Models/Lineage.cs
new file mode 100644
index 0000000..032857c
--- /dev/null
+++ b/BritishHorseRacingApi/Models/Lineage.cs
@@ -0,0 +1,12 @@
+using Newtonsoft.Json;
+
+namespace BritishHorseRacingApi.Models
+{
+ public class Lineage
+ {
+ [JsonConverter(typeof(FalseOrObjectConverter))]
+ public LineageAnimal Dam { get; set; }
+ [JsonConverter(typeof(FalseOrObjectConverter))]
+ public LineageAnimal Sire { get; set; }
+ }
+}
diff --git a/BritishHorseRacingApi/Models/LineageAnimal.cs b/BritishHorseRacingApi/Models/LineageAnimal.cs
new file mode 100644
index 0000000..edc4b27
--- /dev/null
+++ b/BritishHorseRacingApi/Models/LineageAnimal.cs
@@ -0,0 +1,9 @@
+namespace BritishHorseRacingApi.Models
+{
+ public class LineageAnimal
+ {
+ public int AnimalId { get; set; }
+ public string AnimalName { get; set; }
+ }
+
+}
diff --git a/BritishHorseRacingApi/Models/NonRunner.cs b/BritishHorseRacingApi/Models/NonRunner.cs
new file mode 100644
index 0000000..44ebb7a
--- /dev/null
+++ b/BritishHorseRacingApi/Models/NonRunner.cs
@@ -0,0 +1,41 @@
+using Newtonsoft.Json;
+using System;
+
+namespace BritishHorseRacingApi.Models
+{
+ public class NonRunner
+ {
+ [JsonProperty("fixtureId")]
+ public long FixtureId { get; set; }
+
+ [JsonProperty("fixtureYear")]
+ public int FixtureYear { get; set; }
+
+ [JsonProperty("raceId")]
+ public long RaceId { get; set; }
+
+ [JsonProperty("divisionSequence")]
+ public long DivisionSequence { get; set; }
+
+ [JsonProperty("raceName")]
+ public string RaceName { get; set; }
+
+ [JsonProperty("horseName")]
+ public string HorseName { get; set; }
+
+ [JsonProperty("fixtureDate")]
+ public DateTime FixtureDate { get; set; }
+
+ [JsonProperty("raceTime")]
+ public TimeSpan RaceTime { get; set; }
+
+ [JsonProperty("declaredDate")]
+ public DateTime? DeclaredDate { get; set; }
+
+ [JsonProperty("declaredTime")]
+ public TimeSpan DeclaredTime { get; set; }
+
+ [JsonProperty("declaredReason")]
+ public string DeclaredReason { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/BritishHorseRacingApi/Models/Official.cs b/BritishHorseRacingApi/Models/Official.cs
new file mode 100644
index 0000000..0151649
--- /dev/null
+++ b/BritishHorseRacingApi/Models/Official.cs
@@ -0,0 +1,8 @@
+namespace BritishHorseRacingApi.Models
+{
+ public partial class Official
+ {
+ public string Category { get; set; }
+ public string[] Officials { get; set; }
+ }
+}
diff --git a/BritishHorseRacingApi/Models/PagedResult.cs b/BritishHorseRacingApi/Models/PagedResult.cs
new file mode 100644
index 0000000..37189ef
--- /dev/null
+++ b/BritishHorseRacingApi/Models/PagedResult.cs
@@ -0,0 +1,18 @@
+using Newtonsoft.Json;
+
+namespace BritishHorseRacingApi.Models
+{
+ public class PagedResult
+ {
+ public int Total { get; set; }
+ [JsonProperty("per_page")]
+ public int PerPage { get; set; }
+ [JsonProperty("current_page")]
+ public int CurrentPage { get; set; }
+ [JsonProperty("last_page")]
+ public int LastPage { get; set; }
+ public int From { get; set; }
+ public int To { get; set; }
+ public T[] Data { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/BritishHorseRacingApi/Models/PerformanceDetail.cs b/BritishHorseRacingApi/Models/PerformanceDetail.cs
new file mode 100644
index 0000000..1eb45d2
--- /dev/null
+++ b/BritishHorseRacingApi/Models/PerformanceDetail.cs
@@ -0,0 +1,11 @@
+namespace BritishHorseRacingApi.Models
+{
+ public class PerformanceDetail
+ {
+ public int TotalWins { get; set; }
+ public int TotalRuns { get; set; }
+ public int TotalPlaces { get; set; }
+ public decimal? TotalPrizeMoney { get; set; }
+ }
+
+}
diff --git a/BritishHorseRacingApi/Models/Race.cs b/BritishHorseRacingApi/Models/Race.cs
new file mode 100644
index 0000000..6da7c30
--- /dev/null
+++ b/BritishHorseRacingApi/Models/Race.cs
@@ -0,0 +1,36 @@
+using BritishHorseRacingApi.Helpers;
+using System;
+using System.Text.RegularExpressions;
+
+namespace BritishHorseRacingApi.Models
+{
+ public class Race
+ {
+ public int RaceId { get; set; }
+ public int YearOfRace { get; set; }
+ public int DivisionSequence { get; set; }
+ public DateTime RaceDate { get; set; }
+ public TimeSpan RaceTime { get; set; }
+ public string RaceName { get; set; }
+ public string AgeLimit { get; set; }
+ public int PrizeAmount { get; set; }
+ public string PrizeCurrency { get; set; }
+ public int RaceClass { get; set; }
+ public string RatingBand { get; set; }
+ public string RawDistanceText { get; set; }
+ public string DistanceUnits { get; set; }
+ public int DistanceValue { get; set; }
+ public string DistanceText { get; set; }
+ public string DistanceChange { get; set; }
+ public string RaceCriteriaRaceType { get; set; }
+ public int AbandonedReasonCode { get; set; }
+ public int BlackTypeRace { get; set; }
+ public string DistanceChangeText { get; set; }
+ public bool Plus10 { get; set; }
+ public WinnersDetail[] WinnersDetails { get; set; }
+
+ public Regex _distanceExtractorRegex = new Regex(@"(?'miles'[0-9\.]*(?=m))?m? ?(?'furlongs'[0-9\.]*(?=f))?f? ?.*?(?'yards'[0-9\.]*(?=y))", RegexOptions.Compiled);
+
+ public int? DistanceInYards() => DataConverterHelper.DistanceStringToYards(DistanceText);
+ }
+}
diff --git a/BritishHorseRacingApi/Models/RacePlanningCode.cs b/BritishHorseRacingApi/Models/RacePlanningCode.cs
new file mode 100644
index 0000000..1f81e64
--- /dev/null
+++ b/BritishHorseRacingApi/Models/RacePlanningCode.cs
@@ -0,0 +1,4 @@
+namespace BritishHorseRacingApi.Models
+{
+ public enum RacePlanningCode { Normal };
+}
diff --git a/BritishHorseRacingApi/Models/Racecourse.cs b/BritishHorseRacingApi/Models/Racecourse.cs
new file mode 100644
index 0000000..b74ae5a
--- /dev/null
+++ b/BritishHorseRacingApi/Models/Racecourse.cs
@@ -0,0 +1,29 @@
+using Newtonsoft.Json;
+using System;
+
+namespace BritishHorseRacingApi.Models
+{
+ public class Racecourse
+ {
+ public int CourseId { get; set; }
+ public string Name { get; set; }
+ [JsonProperty("Type")]
+ public TrackStyle TrackStyle { get; set; }
+ public Region Region { get; set; }
+ public string Phone { get; set; }
+ public string Email { get; set; }
+ public string Web { get; set; }
+ public string Logo { get; set; }
+ public string Address1 { get; set; }
+ public string Address2 { get; set; }
+ public string Address3 { get; set; }
+ public string Address4 { get; set; }
+ public string Address5 { get; set; }
+ public string Postcode { get; set; }
+ public Country? Country { get; set; }
+ public string Latitude { get; set; }
+ public string Longitude { get; set; }
+ public DateTime? FirstRace { get; set; }
+ public DateTime NextFixtureDate { get; set; }
+ }
+}
diff --git a/BritishHorseRacingApi/Models/RacingTrackType.cs b/BritishHorseRacingApi/Models/RacingTrackType.cs
new file mode 100644
index 0000000..8597240
--- /dev/null
+++ b/BritishHorseRacingApi/Models/RacingTrackType.cs
@@ -0,0 +1,4 @@
+namespace BritishHorseRacingApi.Models
+{
+ public enum RacingTrackType { Awt, Turf, Mixed };
+}
diff --git a/BritishHorseRacingApi/Models/RecentPerformance.cs b/BritishHorseRacingApi/Models/RecentPerformance.cs
new file mode 100644
index 0000000..8edb451
--- /dev/null
+++ b/BritishHorseRacingApi/Models/RecentPerformance.cs
@@ -0,0 +1,37 @@
+using System;
+
+namespace BritishHorseRacingApi.Models
+{
+ public class RecentPerformance
+ {
+ public int? RaceId { get; set; }
+ public int? YearOfRace { get; set; }
+ public int? DivisionSequence { get; set; }
+ public int? CourseId { get; set; }
+ public string CourseName { get; set; }
+ public int? RaceRunners { get; set; }
+ public int? ResultPosition { get; set; }
+ public string BettingRatio { get; set; }
+ public int? AnimalId { get; set; }
+ public string AnimalName { get; set; }
+ public int? JockeyId { get; set; }
+ public string JockeyName { get; set; }
+ public int? WeightCarried { get; set; }
+ public int? TrainerId { get; set; }
+ public string TrainerName { get; set; }
+ public DateTimeOffset? RaceDateTime { get; set; }
+ public DateTimeOffset? RaceDate { get; set; }
+ public DateTimeOffset? RaceTime { get; set; }
+ public int? RaceClass { get; set; }
+ public TrackStyle RaceType { get; set; }
+ public string RaceDistanceText { get; set; }
+ public string RaceName { get; set; }
+ public string PrizeMoney { get; set; }
+ public string Going { get; set; }
+ public int? FlatRating { get; set; }
+ public object AwtRating { get; set; }
+ public object ChaseRating { get; set; }
+ public object HurdleRating { get; set; }
+ public Status Status { get; set; }
+ }
+}
diff --git a/BritishHorseRacingApi/Models/Region.cs b/BritishHorseRacingApi/Models/Region.cs
new file mode 100644
index 0000000..bd3d358
--- /dev/null
+++ b/BritishHorseRacingApi/Models/Region.cs
@@ -0,0 +1,4 @@
+namespace BritishHorseRacingApi.Models
+{
+ public enum Region { Midlands, North, South };
+}
diff --git a/BritishHorseRacingApi/Models/Runner.cs b/BritishHorseRacingApi/Models/Runner.cs
new file mode 100644
index 0000000..104c9c9
--- /dev/null
+++ b/BritishHorseRacingApi/Models/Runner.cs
@@ -0,0 +1,139 @@
+using Newtonsoft.Json;
+
+namespace BritishHorseRacingApi.Models
+{
+ public partial class Runner
+ {
+ [JsonProperty("raceId")]
+ public int RaceId { get; set; }
+
+ [JsonProperty("yearOfRace")]
+ public int YearOfRace { get; set; }
+
+ [JsonProperty("divisionSequence")]
+ public int DivisionSequence { get; set; }
+
+ [JsonProperty("animalId")]
+ public int AnimalId { get; set; }
+
+ [JsonProperty("racehorseName")]
+ public string RacehorseName { get; set; }
+
+ [JsonProperty("ageYears")]
+ public int AgeYears { get; set; }
+
+ [JsonProperty("bred")]
+ public string Bred { get; set; }
+
+ [JsonProperty("clothNumber")]
+ public int ClothNumber { get; set; }
+
+ [JsonProperty("drawnStall")]
+ public int? DrawnStall { get; set; }
+
+ [JsonProperty("reserveNumber")]
+ public string ReserveNumber { get; set; }
+
+ [JsonProperty("bhaRating")]
+ public string BhaRating { get; set; }
+
+ [JsonProperty("formFigures")]
+ public string FormFigures { get; set; }
+
+ [JsonProperty("weightValue")]
+ public string WeightValue { get; set; }
+
+ [JsonProperty("penaltyValue")]
+ public int PenaltyValue { get; set; }
+
+ [JsonProperty("sexType")]
+ public SexType SexType { get; set; }
+
+ [JsonProperty("nonRunnerDeclaredReason")]
+ public string NonRunnerDeclaredReason { get; set; }
+
+ [JsonProperty("nonRunnerDeclaredDate")]
+ public string NonRunnerDeclaredDate { get; set; }
+
+ [JsonProperty("nonRunnerDeclaredTime")]
+ public string NonRunnerDeclaredTime { get; set; }
+
+ [JsonProperty("runner")]
+ public int RunnerNumber { get; set; }
+
+ [JsonProperty("status")]
+ public Status Status { get; set; }
+
+ [JsonProperty("jockeyId")]
+ public int JockeyId { get; set; }
+
+ [JsonProperty("jockeyName")]
+ public string JockeyName { get; set; }
+
+ [JsonProperty("weightsJockeyClaiming")]
+ public int WeightsJockeyClaiming { get; set; }
+
+ [JsonProperty("headGearWearingCode")]
+ public string HeadGearWearingCode { get; set; }
+
+ [JsonProperty("headGearWearingSheepSkin")]
+ public int HeadGearWearingSheepSkin { get; set; }
+
+ [JsonProperty("headGearDescription")]
+ public string HeadGearDescription { get; set; }
+
+ [JsonProperty("headGearAbbreviation")]
+ public string HeadGearAbbreviation { get; set; }
+
+ [JsonProperty("jockeyProfile")]
+ public int JockeyProfile { get; set; }
+
+ [JsonProperty("courseWinner")]
+ public string CourseWinner { get; set; }
+
+ [JsonProperty("distanceWinner")]
+ public string DistanceWinner { get; set; }
+
+ [JsonProperty("windSurgeryFirstRun")]
+ public string WindSurgeryFirstRun { get; set; }
+
+ [JsonProperty("trainerId")]
+ public int TrainerId { get; set; }
+
+ [JsonProperty("trainerName")]
+ public string TrainerName { get; set; }
+
+ [JsonProperty("ownerId")]
+ public int OwnerId { get; set; }
+
+ [JsonProperty("ownerName")]
+ public string OwnerName { get; set; }
+
+ [JsonProperty("wbSilkCode")]
+ public string WbSilkCode { get; set; }
+
+ [JsonProperty("wbSilkDescription")]
+ public string WbSilkDescription { get; set; }
+
+ [JsonProperty("silkImage")]
+ public string SilkImage { get; set; }
+
+ [JsonProperty("plus10")]
+ public int Plus10 { get; set; }
+
+ [JsonProperty("forecastPriceNumerator")]
+ public string ForecastPriceNumerator { get; set; }
+
+ [JsonProperty("forecastPriceDenominator")]
+ public string ForecastPriceDenominator { get; set; }
+
+ [JsonProperty("weightText")]
+ public string WeightText { get; set; }
+
+ [JsonProperty("odds")]
+ public string Odds { get; set; }
+
+ [JsonProperty("lastRuns")]
+ public LastRun[] LastRuns { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/BritishHorseRacingApi/Models/RunnerResult.cs b/BritishHorseRacingApi/Models/RunnerResult.cs
new file mode 100644
index 0000000..499fc9b
--- /dev/null
+++ b/BritishHorseRacingApi/Models/RunnerResult.cs
@@ -0,0 +1,55 @@
+using BritishHorseRacingApi.Helpers;
+using System;
+using System.Text.RegularExpressions;
+
+namespace BritishHorseRacingApi.Models
+{
+ public class RunnerResult
+ {
+ public int RaceId { get; set; }
+ public int YearOfRace { get; set; }
+ public int DivisionSequence { get; set; }
+ public int HorseId { get; set; }
+ public int AnimalId { get; set; }
+ public int? ResultFinishPos { get; set; }
+ public string Status { get; set; }
+ public int Ran { get; set; }
+ public string NonRunnerDeclaredReason { get; set; }
+ public DateTime? NonRunnerDeclaredDate { get; set; }
+ public TimeSpan? NonRunnerDeclaredTime { get; set; }
+ public string RacehorseName { get; set; }
+ public int AgeYear { get; set; }
+ public string Bred { get; set; }
+ public int? ClothNumber { get; set; }
+ public int? DrawnStall { get; set; }
+ public int? WeightValue { get; set; }
+ public string SexType { get; set; }
+ public int? JockeyId { get; set; }
+ public string JockeyName { get; set; }
+ public int JockeyProfile { get; set; }
+ public int? TrainerId { get; set; }
+ public string TrainerName { get; set; }
+ public int? OwnerId { get; set; }
+ public string OwnerName { get; set; }
+ public string WbSilkCode { get; set; }
+ public string WbSilkDescription { get; set; }
+ public string SilkImage { get; set; }
+ public int Plus10 { get; set; }
+ public string ResultBtnDistance { get; set; }
+ public string PositionFinishTime { get; set; }
+ public string BettingRatio { get; set; }
+ public string PerformanceFigure { get; set; }
+ public int? RatingFlat { get; set; }
+ public string RatingAwt { get; set; }
+ public string RatingChase { get; set; }
+ public string RatingHurdle { get; set; }
+ public string RaceCriteriaRaceType { get; set; }
+ public int Hro { get; set; }
+ public string FinishTime { get; set; }
+ public int? FinishTimeInMilliseconds() => DataConverterHelper.StopWatchStringToMilliseconds(FinishTime);
+
+ public int LossMilliseconds() => DataConverterHelper.StopwatchStringDifferenceToMilliseconds(FinishTime);
+
+ public string RacehorseNameWithoutConuntry => RacehorseName.Split('(')[0].Trim();
+ }
+}
diff --git a/BritishHorseRacingApi/Models/SexType.cs b/BritishHorseRacingApi/Models/SexType.cs
new file mode 100644
index 0000000..3b1173e
--- /dev/null
+++ b/BritishHorseRacingApi/Models/SexType.cs
@@ -0,0 +1,4 @@
+namespace BritishHorseRacingApi.Models
+{
+ public enum SexType { Filly, Gelding };
+}
diff --git a/BritishHorseRacingApi/Models/SingleResult.cs b/BritishHorseRacingApi/Models/SingleResult.cs
new file mode 100644
index 0000000..a2207fe
--- /dev/null
+++ b/BritishHorseRacingApi/Models/SingleResult.cs
@@ -0,0 +1,10 @@
+using Newtonsoft.Json;
+
+namespace BritishHorseRacingApi.Models
+{
+
+ public class SingleResult
+ {
+ public T Data { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/BritishHorseRacingApi/Models/Status.cs b/BritishHorseRacingApi/Models/Status.cs
new file mode 100644
index 0000000..5ef7d8f
--- /dev/null
+++ b/BritishHorseRacingApi/Models/Status.cs
@@ -0,0 +1,4 @@
+namespace BritishHorseRacingApi.Models
+{
+ public enum Status { Runner, Dnf, Withdrawn };
+}
diff --git a/BritishHorseRacingApi/Models/TokenResponse.cs b/BritishHorseRacingApi/Models/TokenResponse.cs
new file mode 100644
index 0000000..f2f2341
--- /dev/null
+++ b/BritishHorseRacingApi/Models/TokenResponse.cs
@@ -0,0 +1,11 @@
+using Newtonsoft.Json;
+
+namespace BritishHorseRacingApi.Models
+{
+ public class TokenResponse
+ {
+ [JsonProperty("token")]
+ public string Token { get; set; }
+ public string Session { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/BritishHorseRacingApi/Models/TrackStyle.cs b/BritishHorseRacingApi/Models/TrackStyle.cs
new file mode 100644
index 0000000..1a86dc7
--- /dev/null
+++ b/BritishHorseRacingApi/Models/TrackStyle.cs
@@ -0,0 +1,5 @@
+namespace BritishHorseRacingApi.Models
+{
+ public enum TrackStyle { Flat, Jump, Mixed };
+
+}
diff --git a/BritishHorseRacingApi/Models/WinnersDetail.cs b/BritishHorseRacingApi/Models/WinnersDetail.cs
new file mode 100644
index 0000000..7cae2fd
--- /dev/null
+++ b/BritishHorseRacingApi/Models/WinnersDetail.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace BritishHorseRacingApi.Models
+{
+ public class WinnersDetail
+ {
+ public int Position { get; set; }
+ public string JockeyName { get; set; }
+ public string Trainername { get; set; }
+ public Uri SilkImage { get; set; }
+ public string RacehorseName { get; set; }
+ }
+
+}
diff --git a/Jenkinsfile b/Jenkinsfile
new file mode 100644
index 0000000..bcffd4a
--- /dev/null
+++ b/Jenkinsfile
@@ -0,0 +1,37 @@
+pipeline
+{
+ agent { dockerfile true }
+ stages
+ {
+ stage('Build')
+ {
+ steps
+ {
+ sh 'dotnet restore'
+ sh 'dotnet build -c Release --no-restore'
+ sh "dotnet pack -c Release --no-restore --no-build -p:PackageVersion=1.0.${env.BUILD_NUMBER}"
+ }
+ }
+
+ stage('Archive artifacts')
+ {
+ steps
+ {
+ archiveArtifacts artifacts:'*/bin/Release/**/*.nupkg'
+ }
+ }
+
+ stage('Publish nuget artifacts')
+ {
+ steps
+ {
+ sh "./JenkinsfileScripts/PublishNugetArtifacts.sh ${env.BUILD_NUMBER}"
+ }
+ }
+ }
+ post {
+ always {
+ deleteDir() /* clean up our workspace */
+ }
+ }
+}
\ No newline at end of file