Tye’s promise:
- Making development of microservices easier by:
- Running many services with one command
- Using dependencies in containers
- Discovering addresses of other services using simple conventions
- Automating deployment of .NET applications to Kubernetes by:
- Automatically containerizing .NET applications
- Generating Kubernetes manifests with minimal knowledge or configuration
- Using a single configuration file
Installation
dotnet tool install --global Microsoft.Tye --version 0.10.0-alpha.21420.1
Running a single service
Let’s create a new front-end project, and start it via Tye.
mkdir microservices
cd microservices
dotnet new razor -n frontend
tye run frontend
Here we can see Tye has built the project, configured HTTP bindings, and started it running. We can navigate to 127.0.0.1:8000 to view the Tye dashboard.
We can stop Tye with Ctrl+C.
Running multiple services
Now suppose out front-end project needs to talk to a back-end API. We will create a solution file containing both projects
dotnet new webapi -n backend
dotnet new sln
dotnet sln add frontend backend
tye run
Now we have both services running.
We will wire up the two services using Tye’s service discovery.
- Stop Type with Ctrl+C.
- Add
WeatherForecast.cs
to thefrontend
project, to match the similar class inbackend
.
namespace frontend;
public class WeatherForecast
{
public DateTime Date { get; set; }
public int TemperatureC { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
public string? Summary { get; set; }
}
- Add
WeatherClient.cs
to thefrontend
project:
namespace frontend;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
public class WeatherClient
{
private readonly JsonSerializerOptions options = new JsonSerializerOptions()
{
PropertyNameCaseInsensitive = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
};
private readonly HttpClient client;
public WeatherClient(HttpClient client)
{
this.client = client;
}
public async Task<WeatherForecast[]> GetWeatherAsync()
{
var responseMessage = await this.client.GetAsync("/weatherforecast");
var stream = await responseMessage.Content.ReadAsStreamAsync();
return await JsonSerializer.DeserializeAsync<WeatherForecast[]>(stream, options);
}
}
4. Add a reference to the Microsoft.Tye.Extensions.Configuration
package to the frontend
project.
dotnet add frontend/frontend.csproj package Microsoft.Tye.Extensions.Configuration --version "0.2.0-*"
- Now we register WeatherClient for dependency injection in out start-up code:
using frontend;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddHttpClient<WeatherClient>(client =>
{
client.BaseAddress = builder.Configuration.GetServiceUri("backend");
});
builder.Services.AddRazorPages();
...
- Add a
Forecasts
property to theIndex
page model underPages\Index.cshtml.cs
in thefrontend
project.
public WeatherForecast[] Forecasts { get; set; }
- Change the
OnGet
method to take theWeatherClient
to call thebackend
service and store the result in theForecasts
property:
public async Task OnGet([FromServices]WeatherClient client)
{
Forecasts = await client.GetWeatherAsync();
}
- Render the
Forecasts
property in theIndex.cshtml
razor view:
@page
@model IndexModel
@{
ViewData["Title"] = "Home page";
}
<div class="text-center">
<h1 class="display-4">Welcome</h1>
<p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>
Weather Forecast:
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
@foreach (var forecast in @Model.Forecasts)
{
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
}
</tbody>
</table>
Now if we re-start the services with tye run
we should see the front-end service is displaying random weather data generated by the back-end.
Tye configuration
Let’s generate Tye’s optional configuration file, tye.yaml
, for our solution:
tye init
Here we can re-configure the services, e.g., we can set specific bindings.
(Changing the name of the backend
service means we must also update the reference to it in the frontend Program.cs
).
Adding external dependencies (Redis)
Let’s change the WeatherForecastController.Get()
method in the backend
to cache weather information using an IDistributedCache
.
- Add these using statements
using Microsoft.Extensions.Caching.Distributed;
using System.Text.Json;
- Update Get()
[HttpGet(Name = "GetWeatherForecast")]
public async Task<string> Get([FromServices]IDistributedCache cache)
{
var weather = await cache.GetStringAsync("weather");
if (weather == null)
{
var rng = new Random();
var forecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
})
.ToArray();
weather = JsonSerializer.Serialize(forecasts);
await cache.SetStringAsync("weather", weather, new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(5)
});
}
return weather;
}
- Add a package reference to
Microsoft.Extensions.Caching.StackExchangeRedis
cd backend/
dotnet add package Microsoft.Extensions.Caching.StackExchangeRedis
cd ..
- Register the IDistributedCache implementation in the backend startup code.
...
builder.Services.AddStackExchangeRedisCache(o =>
{
o.Configuration = builder.Configuration.GetConnectionString("redis");
});
builder.Services.AddControllers();
...
- Modify
tye.yaml
to include redis as a dependency.
...
- name: redis
image: redis
bindings:
- port: 6379
connectionString: "${host}:${port}"
- name: redis-cli
image: redis
args: "redis-cli -h redis MONITOR"