Skip to content

Commit 1d6629c

Browse files
committed
Add new solution
1 parent aab8bbf commit 1d6629c

16 files changed

Lines changed: 344 additions & 0 deletions
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
namespace WeatherApp.Domain;
2+
3+
public interface IGeoLocationService
4+
{
5+
Task<(double Lat, double Lon)> GetCoordinatesAsync(string city);
6+
}
7+
8+
public interface IWeatherProvider
9+
{
10+
Task<WeatherData> GetWeatherAsync(double lat, double lon);
11+
}
12+
13+
public interface IDataSink
14+
{
15+
void OnWeatherDataReceived(WeatherData data);
16+
}
17+
18+
public interface IWeatherProcessor
19+
{
20+
void Process(WeatherData data);
21+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace WeatherApp.Domain;
2+
3+
public class WeatherData
4+
{
5+
public DateTime Timestamp { get; set; } = DateTime.UtcNow;
6+
public string Description { get; set; } = string.Empty;
7+
public double Temperature { get; set; }
8+
public double WindSpeed { get; set; }
9+
public string City { get; set; } = string.Empty;
10+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
global using System;
2+
global using System.IO;
3+
global using System.Net.Http;
4+
global using System.Threading.Tasks;
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
using Microsoft.Extensions.DependencyInjection;
2+
using Microsoft.Extensions.Hosting;
3+
using Microsoft.Extensions.Logging;
4+
using Microsoft.Extensions.Configuration;
5+
using WeatherApp.Domain;
6+
using WeatherApp.Services;
7+
using WeatherApp.Sinks;
8+
9+
public class Program
10+
{
11+
public static async Task Main(string[] args)
12+
{
13+
var host = Host.CreateDefaultBuilder(args)
14+
.ConfigureServices((context, services) =>
15+
{
16+
IConfiguration config = context.Configuration;
17+
18+
string openWeatherApiKey = config["OpenWeatherMap:ApiKey"] ?? throw new InvalidOperationException("OpenWeatherMap:ApiKey is not configured");
19+
string thingSpeakApiKey = config["ThingSpeak:ApiKey"] ?? throw new InvalidOperationException("ThingSpeak:ApiKey is not configured");
20+
21+
services.AddLogging(cfg => cfg.AddConsole());
22+
23+
services.AddSingleton<IGeoLocationService>(sp =>
24+
new OpenWeatherMapGeoService(openWeatherApiKey, sp.GetRequiredService<ILogger<OpenWeatherMapGeoService>>()));
25+
26+
services.AddSingleton<IWeatherProvider>(sp =>
27+
new OpenWeatherMapWeatherProvider(openWeatherApiKey, sp.GetRequiredService<ILogger<OpenWeatherMapWeatherProvider>>()));
28+
29+
services.AddSingleton<IWeatherProcessor, WindWarningProcessor>();
30+
31+
services.AddSingleton<IDataSink>(sp =>
32+
new CsvSink("wetterdaten.csv", sp.GetRequiredService<ILogger<CsvSink>>()));
33+
34+
services.AddSingleton<IDataSink>(sp =>
35+
new ThingSpeakSink(thingSpeakApiKey, sp.GetRequiredService<ILogger<ThingSpeakSink>>()));
36+
37+
services.AddSingleton<IDataSink, ConsoleSink>();
38+
39+
services.AddSingleton<WeatherAggregator>();
40+
})
41+
.Build();
42+
43+
var aggregator = host.Services.GetRequiredService<WeatherAggregator>();
44+
await aggregator.FetchAndDistributeAsync("Freiberg");
45+
}
46+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using System.Net.Http;
2+
using System.Text.Json;
3+
using WeatherApp.Domain;
4+
using Microsoft.Extensions.Logging;
5+
6+
namespace WeatherApp.Services;
7+
8+
public class OpenWeatherMapGeoService : IGeoLocationService
9+
{
10+
private readonly string apiKey;
11+
private readonly ILogger<OpenWeatherMapGeoService> logger;
12+
private readonly HttpClient http = new();
13+
14+
public OpenWeatherMapGeoService(string apiKey, ILogger<OpenWeatherMapGeoService> logger)
15+
{
16+
this.apiKey = apiKey;
17+
this.logger = logger;
18+
}
19+
20+
public async Task<(double Lat, double Lon)> GetCoordinatesAsync(string city)
21+
{
22+
var url = $"http://api.openweathermap.org/geo/1.0/direct?q={city}&limit=1&appid={apiKey}";
23+
var json = await http.GetStringAsync(url);
24+
var doc = JsonDocument.Parse(json);
25+
var root = doc.RootElement[0];
26+
double lat = root.GetProperty("lat").GetDouble();
27+
double lon = root.GetProperty("lon").GetDouble();
28+
logger.LogInformation("Geo Koordinaten für {city}: {lat}, {lon}", city, lat, lon);
29+
return (lat, lon);
30+
}
31+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
using System.Net.Http;
2+
using System.Text.Json;
3+
using WeatherApp.Domain;
4+
using Microsoft.Extensions.Logging;
5+
6+
namespace WeatherApp.Services;
7+
8+
public class OpenWeatherMapWeatherProvider : IWeatherProvider
9+
{
10+
private readonly string apiKey;
11+
private readonly ILogger<OpenWeatherMapWeatherProvider> logger;
12+
private readonly HttpClient http = new();
13+
14+
public OpenWeatherMapWeatherProvider(string apiKey, ILogger<OpenWeatherMapWeatherProvider> logger)
15+
{
16+
this.apiKey = apiKey;
17+
this.logger = logger;
18+
}
19+
20+
public async Task<WeatherData> GetWeatherAsync(double lat, double lon)
21+
{
22+
var url = $"http://api.openweathermap.org/data/2.5/weather?lat={lat}&lon={lon}&appid={apiKey}&units=metric";
23+
var json = await http.GetStringAsync(url);
24+
var doc = JsonDocument.Parse(json);
25+
var root = doc.RootElement;
26+
27+
var data = new WeatherData
28+
{
29+
Description = root.GetProperty("weather")[0].GetProperty("description").GetString() ?? "",
30+
Temperature = root.GetProperty("main").GetProperty("temp").GetDouble(),
31+
WindSpeed = root.GetProperty("wind").GetProperty("speed").GetDouble()
32+
};
33+
34+
logger.LogInformation("Wetterdaten abgerufen: {@data}", data);
35+
return data;
36+
}
37+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
using WeatherApp.Domain;
2+
using Microsoft.Extensions.Logging;
3+
4+
namespace WeatherApp.Services;
5+
6+
public class WeatherAggregator
7+
{
8+
private readonly IGeoLocationService geo;
9+
private readonly IWeatherProvider provider;
10+
private readonly IEnumerable<IWeatherProcessor> processors;
11+
private readonly IEnumerable<IDataSink> sinks;
12+
private readonly ILogger<WeatherAggregator> logger;
13+
14+
public WeatherAggregator(
15+
IGeoLocationService geo,
16+
IWeatherProvider provider,
17+
IEnumerable<IWeatherProcessor> processors,
18+
IEnumerable<IDataSink> sinks,
19+
ILogger<WeatherAggregator> logger)
20+
{
21+
this.geo = geo;
22+
this.provider = provider;
23+
this.processors = processors;
24+
this.sinks = sinks;
25+
this.logger = logger;
26+
}
27+
28+
public async Task FetchAndDistributeAsync(string city)
29+
{
30+
logger.LogInformation("Abruf für Stadt: {city}", city);
31+
var (lat, lon) = await geo.GetCoordinatesAsync(city);
32+
var data = await provider.GetWeatherAsync(lat, lon);
33+
data.City = city;
34+
35+
foreach (var processor in processors)
36+
processor.Process(data);
37+
38+
foreach (var sink in sinks)
39+
sink.OnWeatherDataReceived(data);
40+
}
41+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using WeatherApp.Domain;
2+
using Microsoft.Extensions.Logging;
3+
4+
namespace WeatherApp.Services;
5+
6+
public class WindWarningProcessor : IWeatherProcessor
7+
{
8+
private readonly ILogger<WindWarningProcessor> logger;
9+
10+
public WindWarningProcessor(ILogger<WindWarningProcessor> logger)
11+
{
12+
this.logger = logger;
13+
}
14+
15+
public void Process(WeatherData data)
16+
{
17+
if (data.WindSpeed > 10)
18+
logger.LogWarning("Starker Wind in {city}: {speed} m/s", data.City, data.WindSpeed);
19+
}
20+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using WeatherApp.Domain;
2+
using Microsoft.Extensions.Logging;
3+
4+
namespace WeatherApp.Sinks;
5+
6+
public class ConsoleSink : IDataSink
7+
{
8+
public void OnWeatherDataReceived(WeatherData data)
9+
{
10+
Console.WriteLine($"[{data.Timestamp:u}] Wetter in {data.City}: {data.Description}, {data.Temperature}°C, {data.WindSpeed} m/s");
11+
}
12+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using WeatherApp.Domain;
2+
using Microsoft.Extensions.Logging;
3+
4+
namespace WeatherApp.Sinks;
5+
6+
public class CsvSink : IDataSink
7+
{
8+
private readonly string path;
9+
private readonly ILogger<CsvSink> logger;
10+
11+
public CsvSink(string path, ILogger<CsvSink> logger)
12+
{
13+
this.path = path;
14+
this.logger = logger;
15+
}
16+
17+
public void OnWeatherDataReceived(WeatherData data)
18+
{
19+
bool fileExists = File.Exists(path);
20+
using var writer = new StreamWriter(path, append: true);
21+
if (!fileExists)
22+
writer.WriteLine("Timestamp,City,Description,Temperature,WindSpeed");
23+
writer.WriteLine($"{data.Timestamp:u},{data.City},{data.Description},{data.Temperature},{data.WindSpeed}");
24+
logger.LogInformation("CSV gespeichert: {path}", path);
25+
}
26+
}

0 commit comments

Comments
 (0)