ASP.NET Core:stub del servizio self-hosted con un'interfaccia a riga di comando

ASP.NET Core:stub del servizio self-hosted con un'interfaccia a riga di comando

Quando ti stai integrando con un'API di terze parti, potresti voler inviare richieste a uno stub del servizio invece di inviarle all'API reale. Lo scopo di uno stub del servizio è simulare l'API di terze parti restituendo risposte codificate. Questo è simile al modo in cui vengono utilizzati i mock negli unit test:aiuta a fornire un'API affidabile e prevedibile per codificare e testare.

Esistono due modi principali per implementare uno stub di servizio:

  • Restituisci le risposte in base ai valori nella richiesta (con regole nel codice o un file di configurazione).
  • Restituisci buone risposte predefinite e lascia che l'utente modifichi ciò che dovrebbe restituire (tramite un'interfaccia a riga di comando).

La seconda opzione è più semplice, più esplicita e più facile da mantenere (poiché non è necessario analizzare la richiesta e disporre di una serie di condizioni per determinare la risposta). In questo articolo, mostrerò come implementare questo approccio usando un'API Web ASP.NET Core self-hosted. Poiché è self-hosted, è relativamente semplice da implementare e utilizzare.

1 – Configura il self-hosting e avvia il ciclo dei comandi

ASP.NET Core usa Kestrel come server Web predefinito, il che significa che è ospitato automaticamente per impostazione predefinita. Puoi chiamare webBuilder.UseKestrel() se vuoi renderlo più esplicito. Il ciclo dei comandi è un metodo in esecuzione in un'altra attività (quindi non blocca l'app Web), in attesa dell'input dell'utente in un ciclo while.

Il codice seguente configura l'app Web in modo che sia self-hosted, abilita il supporto dell'app console e avvia un ciclo di comandi:

public static async Task Main(string[] args)
{
	string url = "https://localhost:12345";

	var commandLoopTask = Task.Run(() => CommandLoop(url));

	var builder = Host.CreateDefaultBuilder(args)
		.ConfigureWebHostDefaults(webBuilder =>
		{
			webBuilder.UseKestrel()
			.UseStartup<Startup>()
			.UseUrls(url)
			.ConfigureLogging(loggingBuilder => loggingBuilder.ClearProviders());
		});


	await Task.WhenAny(builder.RunConsoleAsync(), commandLoopTask);
}
private static void CommandLoop(string url)
{
	Console.WriteLine("CommandLoop - Implement this in step 4.");
	while (true)
	{
		var input = Console.ReadLine();
	}
}
Code language: C# (cs)

Quando viene avviato, lo visualizzerà sulla riga di comando:

CommandLoop - Implement this in step 4.Code language: plaintext (plaintext)

Note:

  • builder.RunConsoleAsync() viene utilizzato per abilitare il supporto dell'app console.
  • Il motivo per utilizzare Task.WhenAny(builder.RunConsoleAsync(), commandLoopTask) è poter interrompere il processo se il ciclo dei comandi genera un'eccezione.
  • loggingBuilder.ClearProvider() disattiva i messaggi di registrazione all'avvio.

2 – Facoltativo – Rimuovere il profilo IISExpress da launchSettings.json

Se vuoi essere in grado di eseguire lo stub del servizio eseguibile da Visual Studio, è meglio aggiornare launchSettings.json in modo da non incorrere in problemi:

  • Rimuovere il profilo IISExpress, le impostazioni IIS e la proprietà applicationUrl.
  • Imposta launchBrowser su false.

Se lo avvii accidentalmente con il profilo IISExpress, vedrai l'errore:Errore HTTP 500.30 – Errore di avvio in-process ANCM . Se non imposti launchBrowser=false, quando chiudi l'app della console vedrai l'errore:Nessun processo è associato a questo oggetto .

Se stai utilizzando launchSettings.json predefinito, rimuovi tutte le righe evidenziate:

{
  "iisSettings": {
    "windowsAuthentication": false,
    "anonymousAuthentication": true,
    "iisExpress": {
      "applicationUrl": "http://localhost:30652/",
      "sslPort": 44367
    }
  },
  "profiles": {
    "IIS Express": {
      "commandName": "IISExpress",
      "launchBrowser": true,
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "ServiceStub": {
      "commandName": "Project",
      "launchBrowser": true,
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      },
      "applicationUrl": "https://localhost:5001;http://localhost:5000"
    }
  }
}
Code language: JSON / JSON with Comments (json)

E quindi imposta launchBrowser su false. Alla fine, launchSettings.json dovrebbe assomigliare a questo:

{
  "profiles": {
    "ServiceStub": {
      "commandName": "Project",
      "launchBrowser": false,
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    }
  }
}
Code language: JSON / JSON with Comments (json)

3 – Crea un endpoint stubbed

Crea controller/endpoint che corrispondono agli endpoint dell'API di terze parti. Fai in modo che gli endpoint restituiscano buoni valori predefiniti. Per poter modificare il valore restituito dall'interfaccia della riga di comando, aggiungi una proprietà statica pubblica (per ogni endpoint), quindi restituisci questa proprietà dall'endpoint.

Ad esempio, supponiamo che tu stia integrando un'API di terze parti con un endpoint dello stato di integrità. Può restituire Sano, Degradato o Malsano. Ecco come eliminare questo endpoint:

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using System;

[ApiController]
[Route("[controller]")]
public class HealthStatusController : ControllerBase
{
	public static HealthStatus Status { get; set; } = HealthStatus.Healthy;
	[HttpGet()]
	public string Get()
	{
		Console.WriteLine("Request received: GET /HealthStatus");
		return Status.ToString();
	}
}
Code language: C# (cs)

4 – Implementa il ciclo di comandi

Lo scopo del ciclo dei comandi è quello di consentire di modificare ciò che restituisce lo stub del servizio. Continuando dall'esempio precedente, ciò consente di modificare il valore restituito dall'endpoint dello stato di integrità:

using ServiceStub.Controllers;
using Microsoft.Extensions.Diagnostics.HealthChecks;

private static void CommandLoop(string url)
{
	Console.WriteLine($"Stubbed endpoint: GET {url}/status");
	Console.WriteLine("Commands:");
	Console.WriteLine("\tset-status <Healthy, Unhealthy, or Degraded> Example: set-status Healthy");

	while (true)
	{
		Console.WriteLine($"Current status: {HealthStatusController.Status}");
		var args = Console.ReadLine().Split();

		if (args.Length < 2 || args[0] != "set-status")
		{
			Console.WriteLine("Invalid command");
			continue;
		}

		if (!Enum.TryParse<HealthStatus>(args[1], ignoreCase: true, out HealthStatus status))
		{
			Console.WriteLine("Invalid value for HealthStatus");
			continue;
		}

		HealthStatusController.Status = status;
	}
}
Code language: C# (cs)

Questo è uno scenario molto semplificato che accetta solo un comando e un singolo valore semplice. Molto probabilmente avrai a che fare con dati più complessi. In tal caso, è possibile codificare i dati complessi e assegnargli un nome di scenario. Quindi l'utente può specificare quale scenario desidera utilizzare.

Il punto principale è mantenere questa app stub del servizio il più semplice possibile, in modo che sia facile da mantenere.

5 – Esegui l'app

Puoi eseguire questa app da Visual Studio (assicurati di puntare al profilo del progetto) o semplicemente fare doppio clic sull'eseguibile. Poiché si tratta di un'app Web self-hosted, non è necessario fare nulla di speciale per distribuirla.

Una volta avviato, vedrai quanto segue in una finestra della console:

Stubbed endpoint: GET https://localhost:12345/status
Commands:
        set-status <Healthy, Unhealthy, or Degraded> Example: set-status Healthy
Current status: HealthyCode language: plaintext (plaintext)

Invia una richiesta al suo endpoint (sto usando Postman):

GET https://localhost:12345/HealthStatusCode language: plaintext (plaintext)

Ciò restituisce la seguente risposta:

Status: 200
Body: HealthyCode language: plaintext (plaintext)

Nella riga di comando, cambialo in non integro:

set-status Unhealthy
Current status: UnhealthyCode language: plaintext (plaintext)

Invia nuovamente la richiesta:

GET https://localhost:12345/HealthStatusCode language: plaintext (plaintext)

Questa volta tornerà:

Status: 200
Body: UnhealthyCode language: plaintext (plaintext)

Questo mostra come puoi modificare la risposta stub dall'interfaccia della riga di comando.

Aggiungi supporto per i test automatici

Se disponi di test automatizzati e desideri essere in grado di modificare ciò che lo stub del servizio restituisce a livello di codice, puoi aggiungere un endpoint per questo.

[HttpPost("SetResponse/{status}")]
public ActionResult SetResponse(HealthStatus status)
{
	Console.WriteLine("Request received: POST /HealthStatus");
	Status = status;
	return Ok($"Changed status to {status}");
}
Code language: C# (cs)

Invia una richiesta a questo endpoint (sto usando Postman):

POST https://localhost:12345/HealthStatus/SetResponse/UnhealthyCode language: plaintext (plaintext)

Ecco cosa restituisce:

Status: OK
Body: Changed status to UnhealthyCode language: plaintext (plaintext)

Ora invia una richiesta GET per verificare che lo stato sia stato modificato:

GET https://localhost:12345/HealthStatusCode language: plaintext (plaintext)

Questo restituisce quanto segue:

Status: OK
Body: UnhealthyCode language: plaintext (plaintext)

Questo mostra che la risposta può essere modificata a livello di codice.

Codice in GitHub

Il codice sorgente completo del progetto utilizzato in questo articolo è disponibile qui: https://github.com/makolyte/aspdotnet-servicestub-withcli