Compare commits

23 Commits

Author SHA1 Message Date
a44b4f9421 Initial work on getting minimal API to work 2024-01-13 16:44:03 -08:00
810993cbf3 Update launch settings 2024-01-13 16:43:44 -08:00
8d6999c95c Move it to a base class so we can override the deserializer in a different project 2024-01-13 14:20:38 -08:00
704d6b24dc Don't reference to EF Core as AOT does not yet support it 2024-01-13 13:44:20 -08:00
81d01f28a3 Add initial work to get minimal API's going 2024-01-13 13:30:28 -08:00
bdd247e6e0 Inital work on breaking out the core logic out of the MVC API Web App in preparation to migrating to minimal API's in a seperate project 2024-01-13 12:42:46 -08:00
52708bd2c2 Add self contained runtime deployment 2024-01-13 12:15:55 -08:00
9c5fbc314f Merge branch 'master' of ssh://git.startmywifi.com:2251/AS1024/GeoFeed 2024-01-08 23:21:17 -08:00
1f3778095c Make the code cleaner to read 2024-01-08 23:18:26 -08:00
2d5c318689 Update license text 2024-01-08 14:59:18 -08:00
dd643ab8ab Update the readme file 2024-01-08 10:48:48 -08:00
2d68115f25 Complete the implementation of returning cached data from the local disk cache 2024-01-08 10:41:25 -08:00
020db780a6 Add example connection string 2024-01-08 10:23:02 -08:00
5fec5f4f92 Forward all cancellation tokens and run the local cache first 2024-01-08 10:22:51 -08:00
c43b218974 Add necessary tools 2024-01-08 10:08:46 -08:00
c18e486750 Add initial DB Migrations 2024-01-08 10:08:36 -08:00
9974bb5f0c Create various scopes - forgot that EF Core DBContexts don't work in a singleton service 2024-01-08 10:08:28 -08:00
37841db663 Inject the necessary services into the DI container 2024-01-08 09:57:51 -08:00
e974635cfb Break it out to individual files and initial cache service 2024-01-08 09:57:37 -08:00
2fa8f32f44 Initial work on caching without calling dependencies 2024-01-08 09:18:35 -08:00
6e9d58c6e3 Initial skeleton work of periodic on disk caching 2024-01-08 00:13:08 -08:00
ade2861395 Merge branch 'master' of ssh://git.startmywifi.com:2251/AS1024/GeoFeed 2024-01-07 14:58:49 -08:00
4ea9f2ca7f Update readme and license text 2024-01-07 11:11:20 -08:00
40 changed files with 1058 additions and 66 deletions

View File

@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\AS1024.GeoFeed.Core\AS1024.GeoFeed.Core.csproj" />
<ProjectReference Include="..\AS1024.GeoFeed.Models\AS1024.GeoFeed.Models.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,15 @@
using Microsoft.EntityFrameworkCore;
namespace AS1024.GeoFeed.Core.GeoFeedSqliteLocalCache
{
public class GeoFeedCacheDbContext : DbContext
{
public GeoFeedCacheDbContext(DbContextOptions options)
: base(options)
{
}
public virtual DbSet<GeoFeedCacheEntry> GeoFeedCacheEntries { get; set; }
}
}

View File

@@ -0,0 +1,12 @@
using AS1024.GeoFeed.Models;
using System.ComponentModel.DataAnnotations;
namespace AS1024.GeoFeed.Core.GeoFeedSqliteLocalCache
{
public class GeoFeedCacheEntry : IPGeoFeed
{
[Key]
public int Id { get; set; }
}
}

View File

@@ -0,0 +1,58 @@
using AS1024.GeoFeed.Core.Interfaces;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System.Runtime.CompilerServices;
namespace AS1024.GeoFeed.Core.GeoFeedSqliteLocalCache
{
public class GeoFeedCacheService : IHostedService
{
private readonly ILogger<GeoFeedCacheService> logger;
private readonly IGeoFeedProvider feedProvider;
private readonly IHost host;
private readonly IGeoFeedPersistentCacheProvider persistentCacheProvider;
public GeoFeedCacheService(ILogger<GeoFeedCacheService> logger,
IGeoFeedProvider feedProvider,
IHost host,
IGeoFeedPersistentCacheProvider persistentCacheProvider)
{
this.logger = logger;
this.feedProvider = feedProvider;
this.host = host;
this.persistentCacheProvider = persistentCacheProvider;
}
public Task StartAsync(CancellationToken cancellationToken)
{
_ = StartPerioidicSync(cancellationToken);
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
public async Task<bool> StartPerioidicSync(CancellationToken Token)
{
while (!Token.IsCancellationRequested)
{
try
{
var results = await feedProvider.GetGeoFeedData();
await persistentCacheProvider.CacheGeoFeed(results);
}
catch (Exception)
{
logger.LogWarning("On disk cache failed to run. Waiting on 30 minutes before retry...");
}
await Task.Delay(TimeSpan.FromMinutes(30));
}
return false;
}
}
}

View File

@@ -0,0 +1,17 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
namespace AS1024.GeoFeed.Core.GeoFeedSqliteLocalCache
{
public class GeoFeedDesignTimeMigration : IDesignTimeDbContextFactory<GeoFeedCacheDbContext>
{
public GeoFeedCacheDbContext CreateDbContext(string[] args)
{
var builder = new DbContextOptionsBuilder<GeoFeedCacheDbContext>();
builder.UseSqlite("Data Source=migratedb.db");
return new GeoFeedCacheDbContext(builder.Options);
}
}
}

View File

@@ -0,0 +1,72 @@
using AS1024.GeoFeed.Core.Interfaces;
using AS1024.GeoFeed.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Hosting;
using AS1024.GeoFeed.Core.Tools;
namespace AS1024.GeoFeed.Core.GeoFeedSqliteLocalCache
{
public class GeoFeedSqliteCache : IGeoFeedPersistentCacheProvider
{
protected readonly GeoFeedCacheDbContext dbContext;
private readonly IGeoFeedProvider feedProvider;
public GeoFeedSqliteCache(GeoFeedCacheDbContext geoFeedCacheDb,
IHost host,
IGeoFeedProvider provider)
{
dbContext = geoFeedCacheDb;
feedProvider = provider;
}
public string ProviderName => "sqlite";
public async Task<bool> CacheGeoFeed(IList<IPGeoFeed> pGeoFeeds)
{
await DBContextMigrate();
List<GeoFeedCacheEntry> geoFeedCacheEntry = [];
var results = pGeoFeeds.ToList();
results.ForEach(x =>
{
geoFeedCacheEntry.Add(new()
{
Prefix = x.Prefix,
GeolocCity = x.GeolocCity,
GeolocCountry = x.GeolocCountry,
GeolocHasLocation = x.GeolocHasLocation,
GeolocPostalCode = x.GeolocPostalCode,
GeolocRegion = x.GeolocRegion
});
});
if (dbContext.GeoFeedCacheEntries.Any())
{
dbContext.GeoFeedCacheEntries.RemoveRange(dbContext.GeoFeedCacheEntries.ToArray());
}
await dbContext.AddRangeAsync(geoFeedCacheEntry);
await dbContext.SaveChangesAsync();
return true;
}
public string GetGeoFeed()
{
var results =
dbContext.GeoFeedCacheEntries.ToList();
List<IPGeoFeed> cachedData = [];
results.ForEach(cachedData.Add);
return cachedData.ToGeoFeedCsv();
}
private async Task DBContextMigrate()
{
if (dbContext.Database.GetPendingMigrations().Any())
{
await dbContext.Database.MigrateAsync();
}
}
}
}

View File

@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\AS1024.GeoFeed.Core\AS1024.GeoFeed.Core.csproj" />
<ProjectReference Include="..\AS1024.GeoFeed.Models\AS1024.GeoFeed.Models.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,15 @@
using Microsoft.EntityFrameworkCore;
namespace AS1024.GeoFeed.Core.GeoFeedSqliteLocalCache
{
public class GeoFeedCacheDbContext : DbContext
{
public GeoFeedCacheDbContext(DbContextOptions options)
: base(options)
{
}
public virtual DbSet<GeoFeedCacheEntry> GeoFeedCacheEntries { get; set; }
}
}

View File

@@ -0,0 +1,12 @@
using AS1024.GeoFeed.Models;
using System.ComponentModel.DataAnnotations;
namespace AS1024.GeoFeed.Core.GeoFeedSqliteLocalCache
{
public class GeoFeedCacheEntry : IPGeoFeed
{
[Key]
public int Id { get; set; }
}
}

View File

@@ -0,0 +1,17 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
namespace AS1024.GeoFeed.Core.GeoFeedSqliteLocalCache
{
public class GeoFeedDesignTimeMigration : IDesignTimeDbContextFactory<GeoFeedCacheDbContext>
{
public GeoFeedCacheDbContext CreateDbContext(string[] args)
{
var builder = new DbContextOptionsBuilder<GeoFeedCacheDbContext>();
builder.UseSqlite("Data Source=migratedb.db");
return new GeoFeedCacheDbContext(builder.Options);
}
}
}

View File

@@ -0,0 +1,72 @@
using AS1024.GeoFeed.Core.Interfaces;
using AS1024.GeoFeed.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Hosting;
using AS1024.GeoFeed.Core.Tools;
namespace AS1024.GeoFeed.Core.GeoFeedSqliteLocalCache
{
public class GeoFeedSqliteCache : IGeoFeedPersistentCacheProvider
{
protected readonly GeoFeedCacheDbContext dbContext;
private readonly IGeoFeedProvider feedProvider;
public GeoFeedSqliteCache(GeoFeedCacheDbContext geoFeedCacheDb,
IHost host,
IGeoFeedProvider provider)
{
dbContext = geoFeedCacheDb;
feedProvider = provider;
}
public string ProviderName => "sqlite";
public async Task<bool> CacheGeoFeed(IList<IPGeoFeed> pGeoFeeds)
{
await DBContextMigrate();
List<GeoFeedCacheEntry> geoFeedCacheEntry = [];
var results = pGeoFeeds.ToList();
results.ForEach(x =>
{
geoFeedCacheEntry.Add(new()
{
Prefix = x.Prefix,
GeolocCity = x.GeolocCity,
GeolocCountry = x.GeolocCountry,
GeolocHasLocation = x.GeolocHasLocation,
GeolocPostalCode = x.GeolocPostalCode,
GeolocRegion = x.GeolocRegion
});
});
if (dbContext.GeoFeedCacheEntries.Any())
{
dbContext.GeoFeedCacheEntries.RemoveRange(dbContext.GeoFeedCacheEntries.ToArray());
}
await dbContext.AddRangeAsync(geoFeedCacheEntry);
await dbContext.SaveChangesAsync();
return true;
}
public string GetGeoFeed()
{
var results =
dbContext.GeoFeedCacheEntries.ToList();
List<IPGeoFeed> cachedData = [];
results.ForEach(cachedData.Add);
return cachedData.ToGeoFeedCsv();
}
private async Task DBContextMigrate()
{
if (dbContext.Database.GetPendingMigrations().Any())
{
await dbContext.Database.MigrateAsync();
}
}
}
}

View File

@@ -0,0 +1,54 @@
// <auto-generated />
using System;
using AS1024.GeoFeed.Core.GeoFeedSqliteLocalCache;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace AS1024.GeoFeed.Core.SqliteGeoFeedCache.Migrations
{
[DbContext(typeof(GeoFeedCacheDbContext))]
[Migration("20240114002908_InitialMigration")]
partial class InitialMigration
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "8.0.0");
modelBuilder.Entity("AS1024.GeoFeed.Core.GeoFeedSqliteLocalCache.GeoFeedCacheEntry", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("GeolocCity")
.HasColumnType("TEXT");
b.Property<string>("GeolocCountry")
.HasColumnType("TEXT");
b.Property<bool?>("GeolocHasLocation")
.HasColumnType("INTEGER");
b.Property<string>("GeolocPostalCode")
.HasColumnType("TEXT");
b.Property<string>("GeolocRegion")
.HasColumnType("TEXT");
b.Property<string>("Prefix")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("GeoFeedCacheEntries");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,39 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace AS1024.GeoFeed.Core.SqliteGeoFeedCache.Migrations
{
/// <inheritdoc />
public partial class InitialMigration : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "GeoFeedCacheEntries",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
GeolocCity = table.Column<string>(type: "TEXT", nullable: true),
GeolocCountry = table.Column<string>(type: "TEXT", nullable: true),
GeolocHasLocation = table.Column<bool>(type: "INTEGER", nullable: true),
GeolocRegion = table.Column<string>(type: "TEXT", nullable: true),
GeolocPostalCode = table.Column<string>(type: "TEXT", nullable: true),
Prefix = table.Column<string>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_GeoFeedCacheEntries", x => x.Id);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "GeoFeedCacheEntries");
}
}
}

View File

@@ -0,0 +1,51 @@
// <auto-generated />
using System;
using AS1024.GeoFeed.Core.GeoFeedSqliteLocalCache;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace AS1024.GeoFeed.Core.SqliteGeoFeedCache.Migrations
{
[DbContext(typeof(GeoFeedCacheDbContext))]
partial class GeoFeedCacheDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "8.0.0");
modelBuilder.Entity("AS1024.GeoFeed.Core.GeoFeedSqliteLocalCache.GeoFeedCacheEntry", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("GeolocCity")
.HasColumnType("TEXT");
b.Property<string>("GeolocCountry")
.HasColumnType("TEXT");
b.Property<bool?>("GeolocHasLocation")
.HasColumnType("INTEGER");
b.Property<string>("GeolocPostalCode")
.HasColumnType("TEXT");
b.Property<string>("GeolocRegion")
.HasColumnType("TEXT");
b.Property<string>("Prefix")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("GeoFeedCacheEntries");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\AS1024.GeoFeed.Models\AS1024.GeoFeed.Models.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,58 @@
using AS1024.GeoFeed.Core.Interfaces;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace AS1024.GeoFeed.Core.CacheService
{
public class GeoFeedCacheService : IHostedService
{
private readonly ILogger<GeoFeedCacheService> logger;
private readonly IGeoFeedProvider feedProvider;
private readonly IHost host;
public GeoFeedCacheService(ILogger<GeoFeedCacheService> logger,
IGeoFeedProvider feedProvider,
IHost host)
{
this.logger = logger;
this.feedProvider = feedProvider;
this.host = host;
}
public Task StartAsync(CancellationToken cancellationToken)
{
_ = StartPerioidicSync(cancellationToken);
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
public async Task<bool> StartPerioidicSync(CancellationToken Token)
{
while (!Token.IsCancellationRequested)
{
try
{
var scope = host.Services.CreateScope();
var persistentCacheProvider =
scope.ServiceProvider.GetRequiredService<IGeoFeedPersistentCacheProvider>();
var results = await feedProvider.GetGeoFeedData();
await persistentCacheProvider.CacheGeoFeed(results);
}
catch (Exception)
{
logger.LogWarning("On disk cache failed to run. Waiting on 30 minutes before retry...");
}
await Task.Delay(TimeSpan.FromMinutes(30));
}
return false;
}
}
}

View File

@@ -0,0 +1,55 @@
using AS1024.GeoFeed.Core.Interfaces;
using AS1024.GeoFeed.Models;
using AS1024.GeoFeed.Core.Tools;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
namespace AS1024.GeoFeed.Core.CacheService
{
public class GeoFeedLocalFileCache : IGeoFeedPersistentCacheProvider
{
private readonly IConfiguration configuration;
private readonly ILogger<GeoFeedLocalFileCache> logger;
public GeoFeedLocalFileCache(IConfiguration _configuration,
ILogger<GeoFeedLocalFileCache> logger)
{
configuration = _configuration;
this.logger = logger;
}
string IGeoFeedPersistentCacheProvider.ProviderName => "file";
Task<bool> IGeoFeedPersistentCacheProvider.CacheGeoFeed(IList<IPGeoFeed> pGeoFeeds)
{
string? tempPath = GetTempPath();
logger.LogInformation($"Writing geofeed data to: {tempPath}");
File.WriteAllText(tempPath, pGeoFeeds.ToList().
ToGeoFeedCsv());
return Task.FromResult(true);
}
private string GetTempPath()
{
string? tempPath = Path.Combine(Path.GetTempPath(), "geoFeedCache.csv");
logger.LogInformation($"Getting GeoFeed data from: {tempPath}");
if (!string.IsNullOrEmpty(configuration["TempCache"]))
tempPath = configuration["TempCache"];
return tempPath;
}
string IGeoFeedPersistentCacheProvider.GetGeoFeed()
{
return File.ReadAllText(GetTempPath());
}
}
}

View File

@@ -1,21 +1,23 @@

using AS1024.GeoFeed.Interfaces;
using AS1024.GeoFeed.Core.Interfaces;
using AS1024.GeoFeed.Models;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace AS1024.GeoFeed.GeoFeedBuilder
namespace AS1024.GeoFeed.Core.GeoFeedPreloader
{
public class PreLoadGeoFeed : IHostedService
{
private readonly ILogger<PreLoadGeoFeed> logger;
private readonly IGeoFeedProvider provider;
private readonly IMemoryCache memoryCache;
private readonly IWebHostEnvironment environment;
private readonly IHostEnvironment environment;
private const string GeoFeedCacheKey = "GeoFeedData";
public PreLoadGeoFeed(ILogger<PreLoadGeoFeed> logger,
IGeoFeedProvider provider,
IMemoryCache memoryCache,
IWebHostEnvironment environment)
IHostEnvironment environment)
{
this.logger = logger;
this.provider = provider;
@@ -29,7 +31,8 @@ namespace AS1024.GeoFeed.GeoFeedBuilder
{
if (environment.IsProduction())
await StartPreLoad();
} catch (Exception ex)
}
catch (Exception ex)
{
logger.LogWarning($"Failed to preload, exception settings below:\n{ex}");
}
@@ -38,7 +41,7 @@ namespace AS1024.GeoFeed.GeoFeedBuilder
private async Task StartPreLoad()
{
logger.LogInformation("Preloading GeoFeed data in memory...");
List<Models.IPGeoFeed> feed = await provider.GetGeoFeedData();
List<IPGeoFeed> feed = await provider.GetGeoFeedData();
MemoryCacheEntryOptions cacheEntryOptions = new MemoryCacheEntryOptions().SetAbsoluteExpiration(TimeSpan.FromMinutes(45));
memoryCache.Set(GeoFeedCacheKey, feed, cacheEntryOptions);
}

View File

@@ -0,0 +1,14 @@
using AS1024.GeoFeed.Core.Interfaces;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace AS1024.GeoFeed.Core.GeoFeedProviders
{
public class NetBoxGeoFeedProvider : NetBoxGeoFeedProviderBase, IGeoFeedProvider
{
public NetBoxGeoFeedProvider(IConfiguration configuration, ILogger<NetBoxGeoFeedProvider> logger, IHttpClientFactory httpClientFactory)
: base(configuration, logger, httpClientFactory)
{
}
}
}

View File

@@ -1,26 +1,28 @@
using AS1024.GeoFeed.Interfaces;
using AS1024.GeoFeed.Core.Interfaces;
using AS1024.GeoFeed.Models;
using System.Text.Json;
using System.Net.Http.Headers;
using System.Net.Sockets;
using System.Web;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace AS1024.GeoFeed.GeoFeedBuilder
namespace AS1024.GeoFeed.Core.GeoFeedProviders
{
public class NetBoxGeoFeedProvider : IGeoFeedProvider
public class NetBoxGeoFeedProviderBase : IGeoFeedProvider
{
private readonly IConfiguration configuration;
private readonly ILogger<NetBoxGeoFeedProvider> logger;
private readonly IList<AddressFamily> addressFamilies = new List<AddressFamily>()
protected readonly IConfiguration configuration;
protected readonly ILogger<NetBoxGeoFeedProvider> logger;
protected readonly IList<AddressFamily> addressFamilies = new List<AddressFamily>()
{
AddressFamily.InterNetwork,
AddressFamily.InterNetworkV6
};
private readonly IHttpClientFactory httpClientFactory;
protected readonly IHttpClientFactory httpClientFactory;
string IGeoFeedProvider.GeoFeedProviderName => "netbox";
public NetBoxGeoFeedProvider(IConfiguration configuration,
public NetBoxGeoFeedProviderBase(IConfiguration configuration,
ILogger<NetBoxGeoFeedProvider> logger,
IHttpClientFactory httpClientFactory)
{
@@ -31,7 +33,7 @@ namespace AS1024.GeoFeed.GeoFeedBuilder
public async Task<List<IPGeoFeed>> GetGeoFeedData()
{
List<IPGeoFeed> geoFeed = new List<IPGeoFeed>();
List<IPGeoFeed> geoFeed = [];
using HttpClient client = httpClientFactory.CreateClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Token", configuration["APIKey"]);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
@@ -46,45 +48,44 @@ namespace AS1024.GeoFeed.GeoFeedBuilder
logger.LogDebug($"Making request to {uri}...");
using HttpResponseMessage result = await client.GetAsync(uri);
if (result.IsSuccessStatusCode)
if (!result.IsSuccessStatusCode)
{
string stringResult = await result.Content.ReadAsStringAsync();
jsonData = JsonSerializer.Deserialize<NetboxData>(stringResult, new JsonSerializerOptions {
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
});
break;
}
string stringResult = await result.Content.ReadAsStringAsync();
jsonData = DeserializeJsonData(stringResult);
if (jsonData?.Results == null || jsonData.Results.Count == 0)
{
break;
}
if (jsonData?.Results == null || jsonData.Results.Count == 0)
{
break;
}
foreach (Result data in jsonData.Results)
foreach (Result data in jsonData.Results)
{
try
{
try
geoFeed.Add(new IPGeoFeed
{
geoFeed.Add(new IPGeoFeed
{
Prefix = data.Prefix,
GeolocCity = data.CustomFields.GeolocCity,
GeolocRegion = data.CustomFields.GeolocRegion,
GeolocCountry = data.CustomFields.GeolocCountry,
GeolocHasLocation = data.CustomFields.GeolocHasLocation,
GeolocPostalCode = data.CustomFields.GeolocPostalCode
});
}
catch (Exception ex)
{
logger.LogError(ex.ToString());
}
Prefix = data.Prefix,
GeolocCity = data.CustomFields.GeolocCity,
GeolocRegion = data.CustomFields.GeolocRegion,
GeolocCountry = data.CustomFields.GeolocCountry,
GeolocHasLocation = data.CustomFields.GeolocHasLocation,
GeolocPostalCode = data.CustomFields.GeolocPostalCode
});
}
if (!string.IsNullOrEmpty(jsonData.Next))
catch (Exception ex)
{
uri = new Uri(jsonData.Next);
continue;
logger.LogError(ex.ToString());
}
}
if (!string.IsNullOrEmpty(jsonData.Next))
{
uri = new Uri(jsonData.Next);
continue;
}
break;
}
}
@@ -92,6 +93,14 @@ namespace AS1024.GeoFeed.GeoFeedBuilder
return geoFeed;
}
protected virtual NetboxData? DeserializeJsonData(string stringResult)
{
return JsonSerializer.Deserialize<NetboxData>(stringResult, new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
});
}
protected Uri BuildNetBoxURI(AddressFamily family)
{
System.Collections.Specialized.NameValueCollection queryParameters = HttpUtility.ParseQueryString(string.Empty);

View File

@@ -0,0 +1,31 @@
using AS1024.GeoFeed.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AS1024.GeoFeed.Core.Interfaces
{
/// <summary>
/// Represents a persistent cache GeoFeed provider
/// </summary>
public interface IGeoFeedPersistentCacheProvider
{
/// <summary>
/// Name of the provider
/// </summary>
public string ProviderName { get; }
/// <summary>
/// Returns the GeoFeed
/// </summary>
/// <returns>String of the CSV geofeed</returns>
public string GetGeoFeed();
/// <summary>
/// Stores the GeoFeed in the cache backend
/// </summary>
/// <param name="pGeoFeeds">GeoFeed retrieved from the backend</param>
/// <returns></returns>
public Task<bool> CacheGeoFeed(IList<IPGeoFeed> pGeoFeeds);
}
}

View File

@@ -1,6 +1,6 @@
using AS1024.GeoFeed.Models;
namespace AS1024.GeoFeed.Interfaces
namespace AS1024.GeoFeed.Core.Interfaces
{
public interface IGeoFeedProvider
{

View File

@@ -1,7 +1,7 @@
using AS1024.GeoFeed.Models;
using System.Text;
namespace AS1024.GeoFeed.GeoFeedBuilder
namespace AS1024.GeoFeed.Core.Tools
{
public static class GeoFeedTools
{

View File

@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<InvariantGlobalization>true</InvariantGlobalization>
<PublishAot>true</PublishAot>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.5" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\AS1024.GeoFeed.Core\AS1024.GeoFeed.Core.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,29 @@
#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
USER app
WORKDIR /app
EXPOSE 8080
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
# Install clang/zlib1g-dev dependencies for publishing to native
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
clang zlib1g-dev
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["AS1024.GeoFeed.MinimalAPI/AS1024.GeoFeed.MinimalAPI.csproj", "AS1024.GeoFeed.MinimalAPI/"]
RUN dotnet restore "./AS1024.GeoFeed.MinimalAPI/./AS1024.GeoFeed.MinimalAPI.csproj"
COPY . .
WORKDIR "/src/AS1024.GeoFeed.MinimalAPI"
RUN dotnet build "./AS1024.GeoFeed.MinimalAPI.csproj" -c $BUILD_CONFIGURATION -o /app/build
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./AS1024.GeoFeed.MinimalAPI.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=true
FROM mcr.microsoft.com/dotnet/runtime-deps:8.0 AS final
WORKDIR /app
EXPOSE 8080
COPY --from=publish /app/publish .
ENTRYPOINT ["./AS1024.GeoFeed.MinimalAPI"]

View File

@@ -0,0 +1,31 @@
using AS1024.GeoFeed.Core.GeoFeedProviders;
using AS1024.GeoFeed.Core.Interfaces;
using AS1024.GeoFeed.Models;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
using System.Threading.Tasks;
namespace AS1024.GeoFeed.MinimalAPI
{
internal class NetboxAoTGeoFeedProvider : NetBoxGeoFeedProvider, IGeoFeedProvider
{
private readonly JsonSerializerOptions appJsonSerializer;
public NetboxAoTGeoFeedProvider(IConfiguration configuration,
ILogger<NetBoxGeoFeedProvider> logger,
IHttpClientFactory httpClientFactory)
: base(configuration, logger, httpClientFactory)
{
}
protected override NetboxData? DeserializeJsonData(string stringResult)
{
return JsonSerializer.Deserialize(stringResult, AppJsonSerializerContext.Default.NetboxData);
}
}
}

View File

@@ -0,0 +1,51 @@
using AS1024.GeoFeed.Core.GeoFeedProviders;
using AS1024.GeoFeed.Core.Interfaces;
using AS1024.GeoFeed.Core.Tools;
using AS1024.GeoFeed.Models;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
using System.Text.Json.Serialization;
namespace AS1024.GeoFeed.MinimalAPI
{
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateSlimBuilder(args);
builder.Services.AddTransient<IGeoFeedProvider, NetboxAoTGeoFeedProvider>();
builder.Services.AddMemoryCache();
builder.Services.AddLogging();
builder.Services.AddHttpClient();
builder.Services.ConfigureHttpJsonOptions(options => {
options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default);
});
var app = builder.Build();
var geoFeed = app.Map("/geofeed.csv", async (IGeoFeedProvider provider, ILogger<Program> logger) => {
try
{
var results =
await provider.GetGeoFeedData();
return results.ToGeoFeedCsv();
}
catch (Exception ex)
{
logger.LogError($"Error: {ex}");
}
return "";
});
app.Run();
}
}
[JsonSerializable(typeof(NetboxData))]
[JsonSerializable(typeof(Result))]
[JsonSerializable(typeof(CustomFields))]
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.SnakeCaseLower)]
internal partial class AppJsonSerializerContext : JsonSerializerContext
{
}
}

View File

@@ -0,0 +1,24 @@
{
"profiles": {
"http": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "geofeed.csv",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true,
"applicationUrl": "http://localhost:5127"
},
"Docker": {
"commandName": "Docker",
"launchBrowser": true,
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/todos",
"environmentVariables": {
"ASPNETCORE_HTTP_PORTS": "8080"
},
"publishAllPorts": true
}
},
"$schema": "http://json.schemastore.org/launchsettings.json"
}

View File

@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

View File

@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@@ -3,7 +3,18 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.8.34330.188
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AS1024.GeoFeed", "AS1024.GeoFeed\AS1024.GeoFeed.csproj", "{6292097C-7F35-45BB-B2B0-1918DF49FE7D}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AS1024.GeoFeed", "AS1024.GeoFeed\AS1024.GeoFeed.csproj", "{6292097C-7F35-45BB-B2B0-1918DF49FE7D}"
ProjectSection(ProjectDependencies) = postProject
{8BA82A53-29E7-44A2-91BA-57C15BB4B0F5} = {8BA82A53-29E7-44A2-91BA-57C15BB4B0F5}
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AS1024.GeoFeed.Models", "AS1024.GeoFeed.Models\AS1024.GeoFeed.Models.csproj", "{8BA82A53-29E7-44A2-91BA-57C15BB4B0F5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AS1024.GeoFeed.Core", "AS1024.GeoFeed.Core\AS1024.GeoFeed.Core.csproj", "{EE6D6C87-29C4-42A1-8251-7D2BF20F1797}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AS1024.GeoFeed.MinimalAPI", "AS1024.GeoFeed.MinimalAPI\AS1024.GeoFeed.MinimalAPI.csproj", "{36F2958C-8D0E-463B-9BF3-D6E55E6FC0B8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AS1024.GeoFeed.Core.SqliteGeoFeedCache", "AS1024.GeoFeed.Core.SqliteGeoFeedCache\AS1024.GeoFeed.Core.SqliteGeoFeedCache.csproj", "{3459BB31-FA7A-44D1-872D-C5338ACFBF80}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -15,6 +26,22 @@ Global
{6292097C-7F35-45BB-B2B0-1918DF49FE7D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6292097C-7F35-45BB-B2B0-1918DF49FE7D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6292097C-7F35-45BB-B2B0-1918DF49FE7D}.Release|Any CPU.Build.0 = Release|Any CPU
{8BA82A53-29E7-44A2-91BA-57C15BB4B0F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8BA82A53-29E7-44A2-91BA-57C15BB4B0F5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8BA82A53-29E7-44A2-91BA-57C15BB4B0F5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8BA82A53-29E7-44A2-91BA-57C15BB4B0F5}.Release|Any CPU.Build.0 = Release|Any CPU
{EE6D6C87-29C4-42A1-8251-7D2BF20F1797}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EE6D6C87-29C4-42A1-8251-7D2BF20F1797}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EE6D6C87-29C4-42A1-8251-7D2BF20F1797}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EE6D6C87-29C4-42A1-8251-7D2BF20F1797}.Release|Any CPU.Build.0 = Release|Any CPU
{36F2958C-8D0E-463B-9BF3-D6E55E6FC0B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{36F2958C-8D0E-463B-9BF3-D6E55E6FC0B8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{36F2958C-8D0E-463B-9BF3-D6E55E6FC0B8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{36F2958C-8D0E-463B-9BF3-D6E55E6FC0B8}.Release|Any CPU.Build.0 = Release|Any CPU
{3459BB31-FA7A-44D1-872D-C5338ACFBF80}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3459BB31-FA7A-44D1-872D-C5338ACFBF80}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3459BB31-FA7A-44D1-872D-C5338ACFBF80}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3459BB31-FA7A-44D1-872D-C5338ACFBF80}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@@ -9,7 +9,22 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.5" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\AS1024.GeoFeed.Core.SqliteGeoFeedCache\AS1024.GeoFeed.Core.SqliteGeoFeedCache.csproj" />
<ProjectReference Include="..\AS1024.GeoFeed.Core\AS1024.GeoFeed.Core.csproj" />
<ProjectReference Include="..\AS1024.GeoFeed.Models\AS1024.GeoFeed.Models.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,9 +1,9 @@
using Microsoft.AspNetCore.Mvc;
using AS1024.GeoFeed.Interfaces;
using AS1024.GeoFeed.GeoFeedBuilder;
using AS1024.GeoFeed.Core.Interfaces;
using Microsoft.Extensions.Caching.Memory;
using AS1024.GeoFeed.Models;
using System.Text;
using AS1024.GeoFeed.Core.Tools;
namespace AS1024.GeoFeed.Controllers
{
@@ -17,13 +17,16 @@ namespace AS1024.GeoFeed.Controllers
private readonly IMemoryCache memoryCache;
private readonly IWebHostEnvironment environment;
private readonly ILogger<GeofeedController> logger;
private readonly IGeoFeedPersistentCacheProvider geoFeedPersistentCache;
private const string GeoFeedCacheKey = "GeoFeedData";
public GeofeedController(IGeoFeedProvider builder,
IMemoryCache memoryCache,
IWebHostEnvironment environment,
ILogger<GeofeedController> logger) {
ILogger<GeofeedController> logger,
IGeoFeedPersistentCacheProvider geoFeedPersistentCache) {
this.logger = logger;
this.geoFeedPersistentCache = geoFeedPersistentCache;
this.builder = builder;
this.memoryCache = memoryCache;
this.environment = environment;
@@ -46,19 +49,39 @@ namespace AS1024.GeoFeed.Controllers
}
}
string csvContent = feed.ToGeoFeedCsv(); // Assuming ToGeoFeedCsv() returns a string in CSV format.
byte[] contentBytes = Encoding.UTF8.GetBytes(csvContent);
string contentType = "text/csv";
return ReturnFile(feed);
} catch (HttpRequestException ex)
{
logger.LogWarning($"Temporary failure of retrieving GeoData from upstream. {ex}");
var cachedData = geoFeedPersistentCache.GetGeoFeed();
return ReturnFile(cachedData);
}
return new FileContentResult(contentBytes, contentType)
{
FileDownloadName = "geofeed.csv"
};
} catch (Exception ex)
catch (Exception ex)
{
logger.LogError($"Geofeed generation failed. Exception: {ex}");
return StatusCode(500);
}
}
[NonAction]
private IActionResult ReturnFile(List<IPGeoFeed>? feed)
{
string csvContent = feed.ToGeoFeedCsv(); // Assuming ToGeoFeedCsv() returns a string in CSV format.
return ReturnFile(csvContent);
}
[NonAction]
private IActionResult ReturnFile(string csvContent)
{
byte[] contentBytes = Encoding.UTF8.GetBytes(csvContent);
string contentType = "text/csv";
return new FileContentResult(contentBytes, contentType)
{
FileDownloadName = "geofeed.csv"
};
}
}
}

View File

@@ -0,0 +1,54 @@
#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
FROM alpine:latest AS base
ENV \
# UID of the non-root user 'app'
APP_UID=1654 \
# Configure web servers to bind to port 8080 when present
ASPNETCORE_HTTP_PORTS=8080 \
# Enable detection of running in a container
DOTNET_RUNNING_IN_CONTAINER=true \
# Set the invariant mode since ICU package isn't included (see https://github.com/dotnet/announcements/issues/20)
DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=true
RUN apk add --upgrade --no-cache \
ca-certificates-bundle \
\
# .NET dependencies
libgcc \
libssl3 \
libstdc++ \
zlib
# Create a non-root user and group
RUN addgroup \
--gid=$APP_UID \
app \
&& adduser \
--uid=$APP_UID \
--ingroup=app \
--disabled-password \
app
WORKDIR /app
USER app
EXPOSE 8080
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS build
ARG BUILD_CONFIGURATION=Release
ARG TARGETARCH
WORKDIR /src
COPY ["AS1024.GeoFeed/AS1024.GeoFeed.csproj", "AS1024.GeoFeed/"]
RUN dotnet restore "./AS1024.GeoFeed/./AS1024.GeoFeed.csproj" -a $TARGETARCH
COPY . .
WORKDIR "/src/AS1024.GeoFeed"
RUN dotnet build "./AS1024.GeoFeed.csproj" -c $BUILD_CONFIGURATION -o /app/build -a $TARGETARCH /p:StaticLink=true
FROM --platform=$BUILDPLATFORM build AS publish
ARG BUILD_CONFIGURATION=Release
ARG TARGETARCH
RUN dotnet publish "./AS1024.GeoFeed.csproj" -c $BUILD_CONFIGURATION -o /app/publish -a $TARGETARCH --self-contained
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["/app/AS1024.GeoFeed"]

View File

@@ -1,5 +1,9 @@
using AS1024.GeoFeed.GeoFeedBuilder;
using AS1024.GeoFeed.Interfaces;
using AS1024.GeoFeed.Core.Interfaces;
using AS1024.GeoFeed.Core.GeoFeedPreloader;
using AS1024.GeoFeed.Core.GeoFeedProviders;
using Microsoft.EntityFrameworkCore;
using AS1024.GeoFeed.Core.GeoFeedSqliteLocalCache;
using AS1024.GeoFeed.Core.CacheService;
namespace AS1024.GeoFeed
{
@@ -11,6 +15,13 @@ namespace AS1024.GeoFeed
builder.Services.AddHostedService<PreLoadGeoFeed>();
builder.Services.AddTransient<IGeoFeedProvider, NetBoxGeoFeedProvider>();
builder.Services.AddDbContext<GeoFeedCacheDbContext>(
options =>
{
options.UseSqlite(builder.Configuration.GetConnectionString("LocalFeedCache"));
});
builder.Services.AddScoped<IGeoFeedPersistentCacheProvider, GeoFeedSqliteCache>();
builder.Services.AddHostedService<GeoFeedCacheService>();
builder.Services.AddHttpClient();
builder.Services.AddMemoryCache();
// Add services to the container.

View File

@@ -5,6 +5,9 @@
"Microsoft.AspNetCore": "Warning"
}
},
"ConnectionStrings": {
"LocalFeedCache": "Data Source=localcache.db"
},
"AllowedHosts": "*",
"APIKey": "",
"NetBoxHost": ""

View File

@@ -1,6 +1,6 @@
BSD 3-Clause License
Copyright (c) [year], [fullname]
Copyright (c) 2024, 12393239 Canada Inc.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

View File

@@ -10,6 +10,7 @@ The application is implemented in C# using .NET 8.0, ensuring a robust and moder
- **GeoFeed Generation**: Dynamically generates a geolocation feed in CSV format as specified in RFC 8805.
- **Caching Mechanism**: Implements an efficient caching strategy to reduce redundant API calls and enhance performance.
- **Local Disk Fallback Caching Mechanism**: In the event of a failure to communicate with the NetBox instance specified, the web app will return data that is locally cached inside a SQLite database.
- **Secure Communication**: Ensures secure data retrieval from NetBox over HTTPS.
## Configuration
@@ -18,9 +19,22 @@ The application requires the following configuration variables to be set:
1. **APIKey**: This is the API key used for authenticating with the NetBox API. Ensure this key has the necessary permissions to access the required resources.
2. **NetBoxHost**: The hostname of the NetBox instance from which the application retrieves data. For example, `netbox.example.com`.
3. **LocalFeedCache**: This connection string is for a local SQLite Database that caches the geofeed data from Netbox.
These variables can be set in your application's configuration file or through environment variables, depending on your deployment strategy.
## NetBox Custom Fields
Ensure that your NetBox instance is configured with the following custom fields:
- `geoloc_city`: (Text) Represents the city and is not required to be filled in.
- `geoloc_country`: (Selection) Represents the country and is not required to be filled in.
- `geoloc_has_location`: (Boolean) Indicates if there is geolocation data available and is required.
- `geoloc_postal_code`: (Text) Represents the postal code and is not required to be filled in.
- `geoloc_region`: (Selection) Represents the region and is not required to be filled in.
These fields are critical for the application to accurately retrieve and format geolocation data.
## Getting Started
To build and run the application, follow these steps:
@@ -32,10 +46,6 @@ To build and run the application, follow these steps:
5. After a successful build, you can start the application by running `dotnet run`.
6. The application will start, and you can access the endpoints as specified.
## Docker Build
A Dockerfile is provided for your convienence. This has not been tested as of yet.
## Endpoints
The application provides the following key endpoints: