Weather application tutorial in C# and Blazor using OpenWeather API

This blog post will be a step-by-step tutorial on how to create a weather forecast application in ASP.NET Core using Blazor and C#. We will be using the OpenWeather API as our weather API.

By the end of the tutorial, we will have built a complete example application in Blazor using C# where the user can search for a location and see the weather forecast for the next 5 days.

OpenWeather is a popular weather API that can provide data on current weather forecasts, forecasts by minute, hour or day, weather alerts, historic weather data, weather maps and more.

With their free price plan, which is what we’ll be using in this tutorial, it’s super easy to get started. The free tier includes a Geocoding API that we can use to obtain the latitude and longitude of a location. We can also obtain the current weather and a 3-hour forecast for 5 days using the free tier.

Obtain geographical coordinates using OpenWeather’s Geocoding API

We will first start by building a service that requests a list of locations from OpenWeather’s Geocoding API. Once you have signed up and obtained a free API key, we will use the instructions found here to perform a request to the API and see the response in its raw JSON format. Enter the following Url into the browser, ensuring to replace API_KEY_HERE with your API key:

http://api.openweathermap.org/geo/1.0/direct?q=london&limit=5&appid=API_KEY_HERE

You should see a JSON response that looks similar to the following:

It’s great that we have the data, but we need this data in a more usable format in C# code. So next we’ll use this JSON to C# tool that will convert this JSON code into a C# class that we can then use in code.

Now that we have our C# classes that we can parse our JSON data into, create a class in the DTOs folder, name it LocationDTO and paste in the classes we have just generated. I’ve renamed the Root class to LocationDTO so that it’s a bit more descriptive for its purpose:

public class LocationDTO
    {
        public string name { get; set; }
        public LocalNames local_names { get; set; }
        public double lat { get; set; }
        public double lon { get; set; }
        public string country { get; set; }
        public string state { get; set; }
    }

public class LocalNames
    {
        public string sr { get; set; }
        public string lt { get; set; }
        public string ar { get; set; }
        public string uk { get; set; }
        public string ru { get; set; }
        public string ur { get; set; }
        public string fa { get; set; }
        public string mk { get; set; }
        public string hu { get; set; }
        public string ko { get; set; }
        public string en { get; set; }
        public string zh { get; set; }
    }

Next, in the DTOs folder, create a C# class called LocationSearchDTO. This will act as the model for our search form which we will build later on:

public class LocationSearchDTO
    {
        [Required]
        public string? Location { get; set; }
    }

Now we can create our service that will query the OpenWeather API. In the project root, create a folder titled Services and inside there create another folder titled Interfaces. Create the following C# interface:

public interface IOpenWeatherService
    {
        Task<IEnumerable<LocationDTO>?> GetLocations(LocationSearchDTO locationSearchDTO);
    }

In the Services folder, create the following C# class which implements the interface:

public class OpenWeatherService : IOpenWeatherService
    {
        private readonly HttpClient client;
        private readonly string apiKey;

        public OpenWeatherService(IConfiguration configuration)
        {
            client = new();
            apiKey = configuration["OpenWeatherAPIKey"];
        }

        public async Task<IEnumerable<LocationDTO>?> GetLocations(LocationSearchDTO locationSearchDTO)
        {
            IEnumerable<LocationDTO>? locations = null;
            string requestUri = GetGeoRequestUri(locationSearchDTO);

            HttpResponseMessage response = await client.GetAsync(requestUri);

            if (response.IsSuccessStatusCode)
                locations = await response.Content.ReadAsAsync<IEnumerable<LocationDTO>>();

            return locations;
        }

        private string GetGeoRequestUri(LocationSearchDTO locationSearchDTO) =>
            $"https://api.openweathermap.org/geo/1.0/direct?q={locationSearchDTO.Location}&limit=5&appid={apiKey}";
    }

Note that we’re ensuring that a successful status code is returned from our request. Also note how the API key isn’t hard coded. We can instead store it in appsettings.json and use IConfiguration to retrieve it:

I have limited the number of location results that are retrieved to 5, but this can be changed to preference. Also, this tutorial will only be implementing one field when searching a location. OpenWeather’s Geocoding API can actually accept 3 values for its query parameter. These are city name, state code and country code. We will only be searching by city name, but this is a potential improvement you may want to make via a state and/or country dropdown menu.

Building the search form

First, create a folder titled Components. Inside here, create the following LocationSelect component. This is a card that will show for every location retrieved from the Geocoding API. As the API fetches multiple results, it will be up to the user to select one of the locations they wish to see weather information for.

<div class="card-group">
    @foreach (var location in Locations)
    {
        <div class="card">
            <div class="card-body">
                <p class="card-title d-inline">
                    @location.name
                </p>
                <p class="card-text">@location.state - @location.country</p>
            </div>
            <div class="card-footer text-center">
                <button class="btn btn-primary" @onclick="() => OnSelectLocation.InvokeAsync(location)">Select</button>
            </div>
        </div>
    }
</div>

@code {
    [Parameter] public IEnumerable<LocationDTO> Locations { get; set; }
    [Parameter] public EventCallback<LocationDTO> OnSelectLocation { get; set; }
}

Now we’ll create a component titled LocationSearch that implements the component we created above:

<EditForm Model="@locationSearch" OnValidSubmit="@HandleValidSubmit">
    <DataAnnotationsValidator />
    <ValidationSummary />

    <p>
        <label>
            Enter a place
            <InputText @bind-Value="locationSearch.Location" class="form-control" />
        </label>
    </p>

    <p>
        <button type="submit" class="btn btn-primary">Submit</button>
    </p>

</EditForm>

@if (locations != null)
{
    <LocationSelect Locations="locations"
                    OnSelectLocation="HandleOnSelectLocation"/>
}

@code {
    [Inject] IOpenWeatherService OpenWeatherService { get; set; }
    [Parameter] public EventCallback<LocationDTO> OnSelectLocation { get; set; }

    LocationSearchDTO locationSearch = new();
    IEnumerable<LocationDTO>? locations;

    private async Task HandleValidSubmit() =>
        locations = await OpenWeatherService.GetLocations(locationSearch);

    private async Task HandleOnSelectLocation(LocationDTO location) =>
        await OnSelectLocation.InvokeAsync(location);
}

The locations object starts off as null, meaning the LocationSelect component is not yet visible. On successful submission of the form, we perform a call to fetch a list of locations based on the query entered which is assigned to the locations object, meaning the cards now show.

The OnSelectLocation callback gets fired when a user selects the form, meaning we can handle when a user selects a location outside of this component.

We will pop our LocationSearch component on the home page and conduct a search to ensure it works:

@page "/"

<PageTitle>Weather App</PageTitle>

<LocationSearch OnSelectLocation="HandleSelectLocation" />

@code {
    private async Task HandleSelectLocation(LocationDTO location)
    {

    }
}

By putting a breakpoint in the HandleSelectLocation method and conducting a search for a location, we can see that we can obtain the latitude and longitude for our chosen location:

Retrieving the current weather from OpenWeather API

Now that we have a means of obtaining geographical coordinates for a location, we can now build on our service to retrieve weather data.

First we will use the documentation specified here to obtain information for the current weather forecast. As we did with the Geocoding API, we’ll take the request Url and open it in the browser. I’ve replaced the latitude and longitude values with the values retrieved from our London search. Don’t forgot to replace API_KEY_HERE with your API key:

https://api.openweathermap.org/data/2.5/weather?lat=51.5073219&lon=-0.1276474&appid=API_KEY_HERE

In the DTOs folder I’ve created another directory for OpenWeather and inside there another directory titled Current. We can then take this JSON response and run it through the JSON to C# tool again in order to generate the following classes. Note that I’ve changed the name of the Root class to CurrentWeatherDTO so that it’s more descriptive:

public class CurrentWeatherDTO
    {
        public Coord coord { get; set; }
        public List<Weather> weather { get; set; }
        public string @base { get; set; }
        public Main main { get; set; }
        public int visibility { get; set; }
        public Wind wind { get; set; }
        public Clouds clouds { get; set; }
        public int dt { get; set; }
        public Sys sys { get; set; }
        public int timezone { get; set; }
        public int id { get; set; }
        public string name { get; set; }
        public int cod { get; set; }
    }

    public class Clouds
    {
        public int all { get; set; }
    }

    public class Coord
    {
        public double lon { get; set; }
        public double lat { get; set; }
    }

    public class Main
    {
        public double temp { get; set; }
        public double feels_like { get; set; }
        public double temp_min { get; set; }
        public double temp_max { get; set; }
        public int pressure { get; set; }
        public int humidity { get; set; }
    }

    public class Sys
    {
        public int type { get; set; }
        public int id { get; set; }
        public string country { get; set; }
        public int sunrise { get; set; }
        public int sunset { get; set; }
    }

    public class Weather
    {
        public int id { get; set; }
        public string main { get; set; }
        public string description { get; set; }
        public string icon { get; set; }
    }

    public class Wind
    {
        public double speed { get; set; }
        public int deg { get; set; }
        public double gust { get; set; }
    }

Next, add the following C# methods to our OpenWeatherService:

private async Task<CurrentWeatherDTO> GetCurrentWeather(LocationDTO location)
        {
            CurrentWeatherDTO weatherForecast = new();
            string requestUri = GetCurrentWeatherRequestUri(location);

            HttpResponseMessage response = await client.GetAsync(requestUri);

            if (response.IsSuccessStatusCode)
                weatherForecast = await response.Content.ReadAsAsync<CurrentWeatherDTO>();

            return weatherForecast;
        }

private string GetCurrentWeatherRequestUri(LocationDTO location) =>
            $"https://api.openweathermap.org/data/2.5/weather?lat={location.lat}&lon={location.lon}&units=metric&appid={apiKey}";

Note that the method is private; we will be grouping the calls for current weather and 5-day weather forecast in the same method.

Retrieving the weather forecast from OpenWeather API

Next we just have to repeat the above steps but for the weather forecast, documentation for which can be found here.

Enter the following Url in the browser, ensuring to replace API_KEY_HERE with your API key:

https://api.openweathermap.org/data/2.5/forecast?lat=51.5073219&lon=-0.1276474&appid=API_KEY_HERE

From the following response, we get the following C# classes. I’ve renamed Root to WeatherForecastDTO and placed this in a directory called Forecast.

public class WeatherForecastDTO
    {
        public string cod { get; set; }
        public int message { get; set; }
        public int cnt { get; set; }
        public List<List> list { get; set; }
        public City city { get; set; }
    }

    public class City
    {
        public int id { get; set; }
        public string name { get; set; }
        public Coord coord { get; set; }
        public string country { get; set; }
        public int population { get; set; }
        public int timezone { get; set; }
        public int sunrise { get; set; }
        public int sunset { get; set; }
    }

    public class Clouds
    {
        public int all { get; set; }
    }

    public class Coord
    {
        public double lat { get; set; }
        public double lon { get; set; }
    }

    public class List
    {
        public int dt { get; set; }
        public Main main { get; set; }
        public List<Weather> weather { get; set; }
        public Clouds clouds { get; set; }
        public Wind wind { get; set; }
        public int visibility { get; set; }
        public double pop { get; set; }
        public Sys sys { get; set; }
        public string dt_txt { get; set; }
        public Rain rain { get; set; }
        public DateTime dtDateTime
        {
            get
            {
                return DateTime.Parse(dt_txt);
            }
        }
    }

    public class Main
    {
        public double temp { get; set; }
        public double feels_like { get; set; }
        public double temp_min { get; set; }
        public double temp_max { get; set; }
        public int pressure { get; set; }
        public int sea_level { get; set; }
        public int grnd_level { get; set; }
        public int humidity { get; set; }
        public double temp_kf { get; set; }
    }

    public class Rain
    {
        public double _3h { get; set; }
    }

    public class Sys
    {
        public string pod { get; set; }
    }

    public class Weather
    {
        public int id { get; set; }
        public string main { get; set; }
        public string description { get; set; }
        public string icon { get; set; }
    }

    public class Wind
    {
        public double speed { get; set; }
        public int deg { get; set; }
        public double gust { get; set; }
    }

Note that in the List class, a property has been added that takes the string value for date retrieved from the API and parses it as a DateTime. This is so we can query it as a DateTime and be able to order and group our weather information into specific days.

Now we have to update our OpenWeatherService again with a call to fetch the weather forecast:

private async Task<WeatherForecastDTO> GetWeatherForecast(LocationDTO location)
        {
            WeatherForecastDTO weatherForecast = new();
            string requestUri = GetWeatherForecastRequestUri(location);

            HttpResponseMessage response = await client.GetAsync(requestUri);

            if (response.IsSuccessStatusCode)
                weatherForecast = await response.Content.ReadAsAsync<WeatherForecastDTO>();

            return weatherForecast;
        }

private string GetWeatherForecastRequestUri(LocationDTO location) =>
            $"https://api.openweathermap.org/data/2.5/forecast?lat={location.lat}&lon={location.lon}&units=metric&appid={apiKey}";

Displaying the current weather and weather forecast

We have built a component that allows us to search the geographical coordinates of a location. We have also built a service that fetches the current and future weather forecast based on a location. Now it’s time to bring these components together and display our results on the page.

First we’ll create a DTO that will be used to bring back the current weather and weather forecast together. Create the following WeatherDTO:

public class WeatherDTO
    {
        public LocationDTO Location { get; set; }

        public CurrentWeatherDTO CurrentWeather { get; set; }

        public WeatherForecastDTO WeatherForecast { get; set; }
    }

Next, add the following line to IOpenWeatherService:

Task<WeatherDTO> GetWeather(LocationDTO location);

Then implement the above interface in OpenWeatherService:

public async Task<WeatherDTO> GetWeather(LocationDTO location)
        {
            return new WeatherDTO
            {
                Location = location,
                CurrentWeather = await GetCurrentWeather(location),
                WeatherForecast = await GetWeatherForecast(location)
            };
        }

Create a new component titled WeatherCard and add the following code:

<div class="card text-center h-100">
    @if (!string.IsNullOrWhiteSpace(DateTime))
    {
        <div class="card-header">
            @DateTime
        </div>
    }
    <div class="card-body">
        <p class="mb-0">
            @WeatherDescription
        </p>
        <p class="display-6">
            @Temperature °c
        </p>
        <p class="small">
            Feels like @TemperatureFeelsLike °c
        </p>
        <p class="small">
            Max @TemperatureMax °c Min @TemperatureMin °c
        </p>
        <p class="small">
            @Humidity % humidity
        </p>
        <p class="small">
            Wind @WindSpeed meter/sec
        </p>
    </div>
</div>

@code {
    [Parameter] public string? DateTime { get; set; }
    [Parameter] public string WeatherDescription { get; set; }
    [Parameter] public double Temperature { get; set; }
    [Parameter] public double TemperatureFeelsLike { get; set; }
    [Parameter] public double TemperatureMax { get; set; }
    [Parameter] public double TemperatureMin { get; set; }
    [Parameter] public int Humidity { get; set; }
    [Parameter] public double WindSpeed { get; set; }
}

Now create another component titled WeatherDisplay and add the following code:

<div class="row mb-4">
    <div class="col-md-12">
        <h2 class="d-inline me-2">Current conditions for @Weather.Location.name</h2>
 
        <div class="row row-cols-1 row-cols-md-5 g-4">
            <div class="col">
                <WeatherCard WeatherDescription="@Weather.CurrentWeather.weather.First().main"
                             Temperature="@Weather.CurrentWeather.main.temp"
                             TemperatureFeelsLike="@Weather.CurrentWeather.main.feels_like"
                             TemperatureMax="@Weather.CurrentWeather.main.temp_max"
                             TemperatureMin="@Weather.CurrentWeather.main.temp_min"
                             Humidity="@Weather.CurrentWeather.main.humidity"
                             WindSpeed="@Weather.CurrentWeather.wind.speed" />
            </div>
        </div>
    </div>
</div>

@foreach (var date in Weather.WeatherForecast.list.Select(l => l.dtDateTime.Date).Distinct())
{
    <div class="row mb-4">
        <div class="col-md-12">
            <h2>Weather forecast for @date.ToShortDateString()</h2>
            <div class="row row-cols-1 row-cols-md-4 g-4">
                @foreach (var item in Weather.WeatherForecast.list
               .Where(l => l.dtDateTime.Date == date)
               .OrderBy(l => l.dtDateTime))
                {
                    <div class="col">
                        <WeatherCard DateTime="@item.dtDateTime.ToString("HH:mm dd/MM/yy")"
                             WeatherDescription="@item.weather.First().main"
                             Temperature="@item.main.temp"
                             TemperatureFeelsLike="@item.main.feels_like"
                             TemperatureMax="@item.main.temp_max"
                             TemperatureMin="@item.main.temp_min"
                             Humidity="@item.main.humidity"
                             WindSpeed="@item.wind.speed" />
                    </div>
                }
            </div>
        </div>
    </div>
}

@code {
    [Parameter] public WeatherDTO Weather { get; set; }
}

The top row simply shows one instance of WeatherCard for the current conditions. Beneath that however, we are obtaining a distinct list of dates from the list of weather forecast items and for each date displaying a header and the appropriate weather forecast items for that date.

Now we bring everything together on the home page so it looks like the following:

@page "/"

<PageTitle>Weather App</PageTitle>

@if (weather != null)
{
    <button @onclick="() => weather = null" class="btn btn-primary mb-4 me-2">Back to search</button>

    <WeatherDisplay Weather="weather" />
}
else
{
    <LocationSearch OnSelectLocation="HandleSelectLocation" />
}

@code {
    [Inject] IOpenWeatherService OpenWeatherService { get; set; }

    WeatherDTO? weather;

    private async Task HandleSelectLocation(LocationDTO location) =>
        weather = await OpenWeatherService.GetWeather(location);
}

When the page first loads, the weather object will be null. This means that the search will be in view. Once a search has been conducted and a location selected, HandleSelectLocation will retrieve the weather information. The Weather object will no longer be null, meaning the weather display component shows. I’ve also added a “back” button which sets the weather object back to null which in turn will re-show the search component.

Adding icons

You may have noticed in certain screenshots that icons have been used to picture the country and the weather conditions.

Country Flags API

For the country flags, there is a neat little API that fetches a flag based on a country code, which the OpenWeather Geocoding API just so happens to return.

So using the Country Flags API we can show a flag beside each location. We can implement the endpoint found in the documentation and add a property to our LocationDTO:

public string country { get; set; }
public string countryFlagImgSrc
{
    get
    {
        return $"https://countryflagsapi.com/png/{country}";
    }
}

We can then update our LocationSelect component to include an image tag for the flag:

<div class="card-group">
    @foreach (var location in Locations)
    {
        <div class="card">
            <div class="card-body">
                <p class="card-title d-inline">
                    @location.name
                </p>
                <p class="d-inline">
                    <img src="@location.countryFlagImgSrc" class="mb-1" height="15" />
                </p>
                <p class="card-text">@location.state - @location.country</p>
            </div>
            <div class="card-footer text-center">
                <button class="btn btn-primary" @onclick="() => OnSelectLocation.InvokeAsync(location)">Select</button>
            </div>
        </div>
    }
</div>

@code {
    [Parameter] public IEnumerable<LocationDTO> Locations { get; set; }
    [Parameter] public EventCallback<LocationDTO> OnSelectLocation { get; set; }
}

OpenWeather icons

OpenWeather provide a set of weather icons, the documentation for which can be found here. Using the same premise as the Country Flags API, we can add a property to our Weather DTOs that exposes the endpoint:

public string icon { get; set; }
public string iconImgSrc
{
    get
    {
        return $"http://openweathermap.org/img/wn/{icon}@2x.png";
    }
}

Now we just need to update our WeatherCard component to include a parameter for the image path and the image tag:

<div class="card text-center h-100">
    @if (!string.IsNullOrWhiteSpace(DateTime))
    {
        <div class="card-header">
            @DateTime
        </div>
    }
    <div class="card-body">
        <p class="mb-0">
            <img src="@WeatherIconImgSrc" height="60" />
            @WeatherDescription
        </p>
        <p class="display-6">
            @Temperature °c
        </p>
        <p class="small">
            Feels like @TemperatureFeelsLike °c
        </p>
        <p class="small">
            Max @TemperatureMax °c Min @TemperatureMin °c
        </p>
        <p class="small">
            @Humidity % humidity
        </p>
        <p class="small">
            Wind @WindSpeed meter/sec
        </p>
    </div>
</div>

@code {
    [Parameter] public string? DateTime { get; set; }
    [Parameter] public string WeatherIconImgSrc { get; set; }
    [Parameter] public string WeatherDescription { get; set; }
    [Parameter] public double Temperature { get; set; }
    [Parameter] public double TemperatureFeelsLike { get; set; }
    [Parameter] public double TemperatureMax { get; set; }
    [Parameter] public double TemperatureMin { get; set; }
    [Parameter] public int Humidity { get; set; }
    [Parameter] public double WindSpeed { get; set; }
}

Don’t forget to pass the parameter through where WeatherCard is referenced in the WeatherDisplay component:

<WeatherCard DateTime="@item.dtDateTime.ToString("HH:mm dd/MM/yy")"
                             WeatherIconImgSrc="@item.weather.First().iconImgSrc"
                             WeatherDescription="@item.weather.First().main"
                             Temperature="@item.main.temp"
                             TemperatureFeelsLike="@item.main.feels_like"
                             TemperatureMax="@item.main.temp_max"
                             TemperatureMin="@item.main.temp_min"
                             Humidity="@item.main.humidity"
                             WindSpeed="@item.wind.speed" />

Conclusion

That concludes this C# Blazor example tutorial. In this blog post we have built an example of a weather forecast application in ASP.NET Core from start to finish using the OpenWeather API.

We have covered retrieving data from an API, converting raw JSON data into C# classes, parsing data from an API into a class and use of self-contained Blazor Razor components.

I hope that this tutorial will be able to help someone and as always feel free to leave any feedback or suggestions for improvement in the comments below.

3 thoughts on “Weather application tutorial in C# and Blazor using OpenWeather API”

Comments are closed.

Scroll to Top