Eventi
Partecipa alla sfida fest per le competenze di intelligenza artificiale
8 apr, 15 - 28 mag, 07
Affinare le tue competenze di intelligenza artificiale e partecipare agli sweep per vincere un esame di certificazione gratuito
Register now!Questo browser non è più supportato.
Esegui l'aggiornamento a Microsoft Edge per sfruttare i vantaggi di funzionalità più recenti, aggiornamenti della sicurezza e supporto tecnico.
Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Nota
Questa non è la versione più recente di questo articolo. Per la versione corrente, vedere la versione .NET 9 di questo articolo.
Avviso
Questa versione di ASP.NET Core non è più supportata. Per altre informazioni, vedere i criteri di supporto di .NET e .NET Core. Per la versione corrente, vedere la versione .NET 9 di questo articolo.
Importante
Queste informazioni si riferiscono a un prodotto non definitive che può essere modificato in modo sostanziale prima che venga rilasciato commercialmente. Microsoft non riconosce alcuna garanzia, espressa o implicita, in merito alle informazioni qui fornite.
Per la versione corrente, vedere la versione .NET 9 di questo articolo.
Gli endpoint minimi supportano i tipi di valori restituiti seguenti:
string
- Questo include Task<string>
e ValueTask<string>
.T
(Qualsiasi altro tipo): include Task<T>
e ValueTask<T>
.IResult
based: Questo include Task<IResult>
e ValueTask<IResult>
.Comportamento | Tipo di Contenuto (Content-Type) |
---|---|
Il framework scrive la stringa direttamente nella risposta. | text/plain |
Considera il seguente gestore di percorso, che restituisce un Hello world
testo.
app.MapGet("/hello", () => "Hello World");
Il 200
codice di stato viene restituito con l'intestazione text/plain
Content-Type e il contenuto seguente.
Hello World
Comportamento | Tipo di Contenuto (Content-Type) |
---|---|
Il framework JSON serializza la risposta. | application/json |
Considerare il seguente gestore di route, che restituisce un tipo anonimo contenente una proprietà stringa Message
.
app.MapGet("/hello", () => new { Message = "Hello World" });
Il 200
codice di stato viene restituito con l'intestazione application/json
Content-Type e il contenuto seguente.
{"message":"Hello World"}
Comportamento | Tipo di Contenuto (Content-Type) |
---|---|
Il framework chiama IResult.ExecuteAsync. | Stabilito dall'implementazione IResult . |
L'interfaccia IResult
definisce un contratto che rappresenta il risultato di un endpoint HTTP. La classe static Results e i TypedResult statici vengono usati per creare vari IResult
oggetti che rappresentano diversi tipi di risposte.
Le Results classi statiche e TypedResults forniscono set simili di helper di risultati. La TypedResults
classe è l'equivalente tipizzato della Results
classe . Tuttavia, il tipo restituito degli Results
helper è IResult, mentre il tipo restituito di ogni TypedResults
helper è uno tra i IResult
tipi di implementazione. La differenza significa che per Results
helper è necessaria una conversione quando è necessario il tipo concreto, ad esempio per i test unitari. I tipi di implementazione sono definiti nello spazio dei nomi Microsoft.AspNetCore.Http.HttpResults.
La restituzione TypedResults
anziché Results
presenta i vantaggi seguenti:
TypedResults
gli helper restituiscono oggetti fortemente tipizzati, che possono migliorare la leggibilità del codice, la capacità di eseguire test unitari e ridurre la probabilità di errori di runtime.Si consideri l'endpoint seguente, per il quale viene generato un 200 OK
codice di stato con la risposta JSON prevista.
app.MapGet("/hello", () => Results.Ok(new Message() { Text = "Hello World!" }))
.Produces<Message>();
Per documentare correttamente questo endpoint, viene chiamato il metodo extensions Produces
. Tuttavia, non è necessario chiamare Produces
se TypedResults
viene usato invece di Results
, come illustrato nel codice seguente.
TypedResults
fornisce automaticamente i metadati per l'endpoint.
app.MapGet("/hello2", () => TypedResults.Ok(new Message() { Text = "Hello World!" }));
Per altre informazioni sulla descrizione di un tipo di risposta, vedere Supporto OpenAPI nelle API minime.
Come accennato in precedenza, quando si usa TypedResults
, non è necessaria una conversione. Si consideri l'API minima seguente che restituisce una TypedResults
classe
public static async Task<Ok<Todo[]>> GetAllTodos(TodoGroupDbContext database)
{
var todos = await database.Todos.ToArrayAsync();
return TypedResults.Ok(todos);
}
Il test seguente verifica la presenza del tipo concreto completo:
[Fact]
public async Task GetAllReturnsTodosFromDatabase()
{
// Arrange
await using var context = new MockDb().CreateDbContext();
context.Todos.Add(new Todo
{
Id = 1,
Title = "Test title 1",
Description = "Test description 1",
IsDone = false
});
context.Todos.Add(new Todo
{
Id = 2,
Title = "Test title 2",
Description = "Test description 2",
IsDone = true
});
await context.SaveChangesAsync();
// Act
var result = await TodoEndpointsV1.GetAllTodos(context);
//Assert
Assert.IsType<Ok<Todo[]>>(result);
Assert.NotNull(result.Value);
Assert.NotEmpty(result.Value);
Assert.Collection(result.Value, todo1 =>
{
Assert.Equal(1, todo1.Id);
Assert.Equal("Test title 1", todo1.Title);
Assert.False(todo1.IsDone);
}, todo2 =>
{
Assert.Equal(2, todo2.Id);
Assert.Equal("Test title 2", todo2.Title);
Assert.True(todo2.IsDone);
});
}
Poiché tutti i metodi su Results
restituiscono IResult
nella loro firma, il compilatore inferisce automaticamente quello come tipo restituito del delegato della richiesta quando vengono restituiti risultati diversi da un singolo endpoint.
TypedResults
richiede l'uso di Results<T1, TN>
da tali delegati.
Il metodo seguente viene compilato perché sia Results.Ok
che Results.NotFound
vengono dichiarati come restituiti IResult
, anche se i tipi concreti effettivi degli oggetti restituiti sono diversi:
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());
Il metodo seguente non viene compilato perché TypedResults.Ok
e TypedResults.NotFound
vengono dichiarati come tipi diversi e il compilatore non tenterà di dedurre il tipo di corrispondenza migliore:
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(todo)
: TypedResults.NotFound());
Per usare TypedResults
, il tipo restituito deve essere completamente dichiarato; quando è asincrono, è necessario il wrapper Task<>
. L'uso di TypedResults
è più dettagliato, ma questo è il compromesso per avere le informazioni sul tipo staticamente disponibili e quindi in grado di autodescriversi con OpenAPI:
app.MapGet("/todoitems/{id}", async Task<Results<Ok<Todo>, NotFound>> (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(todo)
: TypedResults.NotFound());
Usare Results<TResult1, TResultN>
come tipo restituito del gestore endpoint invece di IResult
quando:
IResult
tipi di implementazione.TypedResult
per creare gli IResult
oggetti .Questa alternativa è migliore rispetto alla restituzione IResult
perché i tipi di unione generica mantengono automaticamente i metadati dell'endpoint. Poiché i Results<TResult1, TResultN>
tipi di unione implementano operatori cast impliciti, il compilatore può convertire automaticamente i tipi specificati negli argomenti generici in un'istanza del tipo di unione.
Questo ha il vantaggio aggiunto di fornire un controllo in fase di compilazione che un gestore di route restituisce effettivamente solo i risultati che dichiara. Se si tenta di restituire un tipo non dichiarato come uno degli argomenti generici a Results<>
, risulta in un errore di compilazione.
Si consideri l'endpoint seguente, per il quale viene restituito un 400 BadRequest
codice di stato quando è orderId
maggiore di 999
. In caso contrario, produce un 200 OK
con il contenuto previsto.
app.MapGet("/orders/{orderId}", IResult (int orderId)
=> orderId > 999 ? TypedResults.BadRequest() : TypedResults.Ok(new Order(orderId)))
.Produces(400)
.Produces<Order>();
Per documentare correttamente questo endpoint, viene chiamato il metodo Produces
di estensione. Tuttavia, poiché l'helper TypedResults
include automaticamente i metadati per l'endpoint, è possibile restituire invece il Results<T1, Tn>
tipo di unione, come illustrato nel codice seguente.
app.MapGet("/orders/{orderId}", Results<BadRequest, Ok<Order>> (int orderId)
=> orderId > 999 ? TypedResults.BadRequest() : TypedResults.Ok(new Order(orderId)));
Gli helper di risultati comuni esistono nelle classi statiche Results e TypedResults. Restituire TypedResults
è preferibile a restituire Results
. Per altre informazioni, vedere TypedResults vs Results.
Le sezioni seguenti illustrano l'utilizzo delle funzioni di risultati comuni.
app.MapGet("/hello", () => Results.Json(new { Message = "Hello World" }));
WriteAsJsonAsync è un modo alternativo per restituire JSON:
app.MapGet("/", (HttpContext context) => context.Response.WriteAsJsonAsync
(new { Message = "Hello World" }));
app.MapGet("/405", () => Results.StatusCode(405));
app.MapGet("/500", () => Results.InternalServerError("Something went wrong!"));
L'esempio precedente restituisce un codice di stato 500.
app.MapGet("/problem", () =>
{
var extensions = new List<KeyValuePair<string, object?>> { new("test", "value") };
return TypedResults.Problem("This is an error with extensions",
extensions: extensions);
});
app.MapGet("/text", () => Results.Text("This is some text"));
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
var proxyClient = new HttpClient();
app.MapGet("/pokemon", async () =>
{
var stream = await proxyClient.GetStreamAsync("http://contoso/pokedex.json");
// Proxy the response as JSON
return Results.Stream(stream, "application/json");
});
app.Run();
Results.Stream
gli overload consentono l'accesso al flusso di risposta HTTP sottostante senza buffering. L'esempio seguente usa ImageSharp per restituire una dimensione ridotta dell'immagine specificata:
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Processing;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/process-image/{strImage}", (string strImage, HttpContext http, CancellationToken token) =>
{
http.Response.Headers.CacheControl = $"public,max-age={TimeSpan.FromHours(24).TotalSeconds}";
return Results.Stream(stream => ResizeImageAsync(strImage, stream, token), "image/jpeg");
});
async Task ResizeImageAsync(string strImage, Stream stream, CancellationToken token)
{
var strPath = $"wwwroot/img/{strImage}";
using var image = await Image.LoadAsync(strPath, token);
int width = image.Width / 2;
int height = image.Height / 2;
image.Mutate(x =>x.Resize(width, height));
await image.SaveAsync(stream, JpegFormat.Instance, cancellationToken: token);
}
L'esempio seguente trasmette un'immagine dall'archivio BLOB di Azure:
app.MapGet("/stream-image/{containerName}/{blobName}",
async (string blobName, string containerName, CancellationToken token) =>
{
var conStr = builder.Configuration["blogConStr"];
BlobContainerClient blobContainerClient = new BlobContainerClient(conStr, containerName);
BlobClient blobClient = blobContainerClient.GetBlobClient(blobName);
return Results.Stream(await blobClient.OpenReadAsync(cancellationToken: token), "image/jpeg");
});
L'esempio seguente trasmette un video da un BLOB di Azure:
// GET /stream-video/videos/earth.mp4
app.MapGet("/stream-video/{containerName}/{blobName}",
async (HttpContext http, CancellationToken token, string blobName, string containerName) =>
{
var conStr = builder.Configuration["blogConStr"];
BlobContainerClient blobContainerClient = new BlobContainerClient(conStr, containerName);
BlobClient blobClient = blobContainerClient.GetBlobClient(blobName);
var properties = await blobClient.GetPropertiesAsync(cancellationToken: token);
DateTimeOffset lastModified = properties.Value.LastModified;
long length = properties.Value.ContentLength;
long etagHash = lastModified.ToFileTime() ^ length;
var entityTag = new EntityTagHeaderValue('\"' + Convert.ToString(etagHash, 16) + '\"');
http.Response.Headers.CacheControl = $"public,max-age={TimeSpan.FromHours(24).TotalSeconds}";
return Results.Stream(await blobClient.OpenReadAsync(cancellationToken: token),
contentType: "video/mp4",
lastModified: lastModified,
entityTag: entityTag,
enableRangeProcessing: true);
});
L'API TypedResults.ServerSentEvents supporta la restituzione di un risultato serverSentEvents .
Server-Sent Eventi è una tecnologia push server che consente a un server di inviare un flusso di messaggi di evento a un client tramite una singola connessione HTTP. In .NET i messaggi di evento vengono rappresentati come SseItem<T>
oggetti, che possono contenere un tipo di evento, un ID e un payload di dati di tipo T
.
La classe TypedResults ha un metodo statico denominato ServerSentEvents che può essere usato per restituire un ServerSentEvents
risultato. Il primo parametro di questo metodo è un oggetto IAsyncEnumerable<SseItem<T>>
che rappresenta il flusso di messaggi di evento da inviare al client.
L'esempio seguente illustra come usare l'API TypedResults.ServerSentEvents
per restituire un flusso di eventi di frequenza cardiaca come oggetti JSON al client:
app.MapGet("sse-item", (CancellationToken cancellationToken) =>
{
async IAsyncEnumerable<SseItem<int>> GetHeartRate(
[EnumeratorCancellation] CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
var heartRate = Random.Shared.Next(60, 100);
yield return new SseItem<int>(heartRate, eventType: "heartRate")
{
ReconnectionInterval = TimeSpan.FromMinutes(1)
};
await Task.Delay(2000, cancellationToken);
}
}
return TypedResults.ServerSentEvents(GetHeartRate(cancellationToken));
});
Per altre informazioni, vedere l'app di esempio api minima che usa l'API TypedResults.ServerSentEvents
per restituire un flusso di eventi di frequenza cardiaca come stringa, ServerSentEvents
e oggetti JSON al client.
app.MapGet("/old-path", () => Results.Redirect("/new-path"));
app.MapGet("/download", () => Results.File("myfile.text"));
Le seguenti interfacce nel namespace Microsoft.AspNetCore.Http consentono di rilevare il tipo IResult
in fase di esecuzione, un pattern comune nelle implementazioni di filtri.
Ecco un esempio di filtro che usa una di queste interfacce:
app.MapGet("/weatherforecast", (int days) =>
{
if (days <= 0)
{
return Results.BadRequest();
}
var forecast = Enumerable.Range(1, days).Select(index =>
new WeatherForecast(DateTime.Now.AddDays(index), Random.Shared.Next(-20, 55), "Cool"))
.ToArray();
return Results.Ok(forecast);
}).
AddEndpointFilter(async (context, next) =>
{
var result = await next(context);
return result switch
{
IValueHttpResult<WeatherForecast[]> weatherForecastResult => new WeatherHttpResult(weatherForecastResult.Value),
_ => result
};
});
Per altre informazioni, vedere Filtri in app delle API minime e tipi di implementazione IResult.
Usare l'oggetto HttpResponse
per modificare le intestazioni di risposta:
app.MapGet("/", (HttpContext context) => {
// Set a custom header
context.Response.Headers["X-Custom-Header"] = "CustomValue";
// Set a known header
context.Response.Headers.CacheControl = $"public,max-age=3600";
return "Hello World";
});
Le applicazioni possono controllare le risposte implementando un tipo personalizzato IResult . Il codice seguente è un esempio di tipo di risultato HTML:
using System.Net.Mime;
using System.Text;
static class ResultsExtensions
{
public static IResult Html(this IResultExtensions resultExtensions, string html)
{
ArgumentNullException.ThrowIfNull(resultExtensions);
return new HtmlResult(html);
}
}
class HtmlResult : IResult
{
private readonly string _html;
public HtmlResult(string html)
{
_html = html;
}
public Task ExecuteAsync(HttpContext httpContext)
{
httpContext.Response.ContentType = MediaTypeNames.Text.Html;
httpContext.Response.ContentLength = Encoding.UTF8.GetByteCount(_html);
return httpContext.Response.WriteAsync(_html);
}
}
È consigliabile aggiungere un metodo di estensione per Microsoft.AspNetCore.Http.IResultExtensions rendere questi risultati personalizzati più individuabili.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/html", () => Results.Extensions.Html(@$"<!doctype html>
<html>
<head><title>miniHTML</title></head>
<body>
<h1>Hello World</h1>
<p>The time on the server is {DateTime.Now:O}</p>
</body>
</html>"));
app.Run();
Inoltre, un tipo personalizzato IResult
può fornire una propria annotazione implementando l'interfaccia IEndpointMetadataProvider . Ad esempio, il codice seguente aggiunge un'annotazione al tipo precedente HtmlResult
che descrive la risposta prodotta dall'endpoint.
class HtmlResult : IResult, IEndpointMetadataProvider
{
private readonly string _html;
public HtmlResult(string html)
{
_html = html;
}
public Task ExecuteAsync(HttpContext httpContext)
{
httpContext.Response.ContentType = MediaTypeNames.Text.Html;
httpContext.Response.ContentLength = Encoding.UTF8.GetByteCount(_html);
return httpContext.Response.WriteAsync(_html);
}
public static void PopulateMetadata(MethodInfo method, EndpointBuilder builder)
{
builder.Metadata.Add(new ProducesHtmlMetadata());
}
}
ProducesHtmlMetadata
è un'implementazione di IProducesResponseTypeMetadata che definisce il tipo di contenuto prodotto della risposta text/html
e il codice di stato 200 OK
.
internal sealed class ProducesHtmlMetadata : IProducesResponseTypeMetadata
{
public Type? Type => null;
public int StatusCode => 200;
public IEnumerable<string> ContentTypes { get; } = new[] { MediaTypeNames.Text.Html };
}
Un approccio alternativo consiste nell'usare il Microsoft.AspNetCore.Mvc.ProducesAttribute per descrivere la risposta prodotta. Il codice seguente modifica il PopulateMetadata
metodo per usare ProducesAttribute
.
public static void PopulateMetadata(MethodInfo method, EndpointBuilder builder)
{
builder.Metadata.Add(new ProducesAttribute(MediaTypeNames.Text.Html));
}
Per impostazione predefinita, le app per le API minime usano le opzioni di Web defaults
durante la serializzazione e la deserializzazione JSON.
Le opzioni possono essere configurate a livello globale per un'app richiamando ConfigureHttpJsonOptions. L'esempio seguente include campi pubblici e formatta l'output JSON.
var builder = WebApplication.CreateBuilder(args);
builder.Services.ConfigureHttpJsonOptions(options => {
options.SerializerOptions.WriteIndented = true;
options.SerializerOptions.IncludeFields = true;
});
var app = builder.Build();
app.MapPost("/", (Todo todo) => {
if (todo is not null) {
todo.Name = todo.NameField;
}
return todo;
});
app.Run();
class Todo {
public string? Name { get; set; }
public string? NameField;
public bool IsComplete { get; set; }
}
// If the request body contains the following JSON:
//
// {"nameField":"Walk dog", "isComplete":false}
//
// The endpoint returns the following JSON:
//
// {
// "name":"Walk dog",
// "nameField":"Walk dog",
// "isComplete":false
// }
Poiché i campi sono inclusi, il codice precedente legge NameField
e lo include nel codice JSON di output.
Per configurare le opzioni di serializzazione per un endpoint, richiamare Results.Json e passarvi un JsonSerializerOptions oggetto, come illustrato nell'esempio seguente:
using System.Text.Json;
var app = WebApplication.Create();
var options = new JsonSerializerOptions(JsonSerializerDefaults.Web)
{ WriteIndented = true };
app.MapGet("/", () =>
Results.Json(new Todo { Name = "Walk dog", IsComplete = false }, options));
app.Run();
class Todo
{
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
// The endpoint returns the following JSON:
//
// {
// "name":"Walk dog",
// "isComplete":false
// }
In alternativa, usare un overload di WriteAsJsonAsync che accetta un oggetto JsonSerializerOptions. L'esempio seguente usa questo overload per formattare l'output JSON:
using System.Text.Json;
var app = WebApplication.Create();
var options = new JsonSerializerOptions(JsonSerializerDefaults.Web) {
WriteIndented = true };
app.MapGet("/", (HttpContext context) =>
context.Response.WriteAsJsonAsync<Todo>(
new Todo { Name = "Walk dog", IsComplete = false }, options));
app.Run();
class Todo
{
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
// The endpoint returns the following JSON:
//
// {
// "name":"Walk dog",
// "isComplete":false
// }
Gli endpoint minimi supportano i tipi di valori restituiti seguenti:
string
- Questo include Task<string>
e ValueTask<string>
.T
(Qualsiasi altro tipo): include Task<T>
e ValueTask<T>
.IResult
based: Questo include Task<IResult>
e ValueTask<IResult>
.Comportamento | Tipo di Contenuto (Content-Type) |
---|---|
Il framework scrive la stringa direttamente nella risposta. | text/plain |
Si consideri il gestore di percorso seguente, che restituisce un Hello world
testo.
app.MapGet("/hello", () => "Hello World");
Il 200
codice di stato viene restituito con l'intestazione text/plain
Content-Type e il contenuto seguente.
Hello World
Comportamento | Tipo di Contenuto (Content-Type) |
---|---|
Il framework JSON serializza la risposta. | application/json |
Si consideri il seguente gestore di route, che restituisce un tipo anonimo contenente una proprietà stringa Message
.
app.MapGet("/hello", () => new { Message = "Hello World" });
Il 200
codice di stato viene restituito con l'intestazione application/json
Content-Type e il contenuto seguente.
{"message":"Hello World"}
Comportamento | Tipo di Contenuto (Content-Type) |
---|---|
Il framework chiama IResult.ExecuteAsync. | Deciso dall'implementazione IResult . |
L'interfaccia IResult
definisce un contratto che rappresenta il risultato di un endpoint HTTP. La classe static Results e i TypedResult statici vengono usati per creare vari IResult
oggetti che rappresentano diversi tipi di risposte.
Le Results classi statiche e TypedResults forniscono set simili di helper di risultati. La TypedResults
classe è l'equivalente tipizzato della Results
classe . Tuttavia, il tipo restituito degli Results
helper è IResult, mentre il tipo restituito di ogni TypedResults
helper è uno tra i IResult
tipi di implementazione. La differenza significa che per gli helper Results
è necessaria una conversione quando è necessario il tipo concreto, ad esempio per i test unitari. I tipi di implementazione sono definiti nello spazio dei nomi Microsoft.AspNetCore.Http.HttpResults.
La restituzione TypedResults
anziché Results
presenta i vantaggi seguenti:
TypedResults
gli helper restituiscono oggetti fortemente tipizzati, che possono migliorare la leggibilità del codice, la capacità di eseguire test unitari e ridurre la probabilità di errori di runtime.Si consideri l'endpoint seguente, per il quale viene generato un 200 OK
codice di stato con la risposta JSON prevista.
app.MapGet("/hello", () => Results.Ok(new Message() { Text = "Hello World!" }))
.Produces<Message>();
Per documentare correttamente questo endpoint, viene chiamato il metodo extensions Produces
. Tuttavia, non è necessario chiamare Produces
se TypedResults
viene usato invece di Results
, come illustrato nel codice seguente.
TypedResults
fornisce automaticamente i metadati per l'endpoint.
app.MapGet("/hello2", () => TypedResults.Ok(new Message() { Text = "Hello World!" }));
Per altre informazioni sulla descrizione di un tipo di risposta, vedere Supporto OpenAPI nelle API minime.
Come accennato in precedenza, quando si usa TypedResults
, non è necessaria una conversione. Si consideri l'API minima seguente che restituisce una TypedResults
classe
public static async Task<Ok<Todo[]>> GetAllTodos(TodoGroupDbContext database)
{
var todos = await database.Todos.ToArrayAsync();
return TypedResults.Ok(todos);
}
Il test seguente verifica la presenza del tipo concreto completo:
[Fact]
public async Task GetAllReturnsTodosFromDatabase()
{
// Arrange
await using var context = new MockDb().CreateDbContext();
context.Todos.Add(new Todo
{
Id = 1,
Title = "Test title 1",
Description = "Test description 1",
IsDone = false
});
context.Todos.Add(new Todo
{
Id = 2,
Title = "Test title 2",
Description = "Test description 2",
IsDone = true
});
await context.SaveChangesAsync();
// Act
var result = await TodoEndpointsV1.GetAllTodos(context);
//Assert
Assert.IsType<Ok<Todo[]>>(result);
Assert.NotNull(result.Value);
Assert.NotEmpty(result.Value);
Assert.Collection(result.Value, todo1 =>
{
Assert.Equal(1, todo1.Id);
Assert.Equal("Test title 1", todo1.Title);
Assert.False(todo1.IsDone);
}, todo2 =>
{
Assert.Equal(2, todo2.Id);
Assert.Equal("Test title 2", todo2.Title);
Assert.True(todo2.IsDone);
});
}
Poiché tutti i metodi su Results
restituiscono IResult
nella loro firma, il compilatore inferisce automaticamente quello come tipo restituito del delegato della richiesta quando vengono restituiti risultati diversi da un singolo endpoint.
TypedResults
richiede l'uso di Results<T1, TN>
da tali delegati.
Il metodo seguente viene compilato perché sia Results.Ok
che Results.NotFound
vengono dichiarati come restituiti IResult
, anche se i tipi concreti effettivi degli oggetti restituiti sono diversi:
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());
Il metodo seguente non viene compilato perché TypedResults.Ok
e TypedResults.NotFound
vengono dichiarati come tipi diversi e il compilatore non tenterà di dedurre il tipo di corrispondenza migliore:
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(todo)
: TypedResults.NotFound());
Per usare TypedResults
, il tipo restituito deve essere completamente dichiarato; quando è asincrono, è necessario il wrapper Task<>
. L'uso di TypedResults
è più dettagliato, ma questo è il compromesso per avere le informazioni sul tipo staticamente disponibili e quindi in grado di autodescriversi con OpenAPI:
app.MapGet("/todoitems/{id}", async Task<Results<Ok<Todo>, NotFound>> (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(todo)
: TypedResults.NotFound());
Usare Results<TResult1, TResultN>
come tipo restituito del gestore endpoint invece di IResult
quando:
IResult
tipi di implementazione.TypedResult
per creare gli IResult
oggetti .Questa alternativa è migliore rispetto alla restituzione IResult
perché i tipi di unione generica mantengono automaticamente i metadati dell'endpoint. Poiché i Results<TResult1, TResultN>
tipi di unione implementano operatori cast impliciti, il compilatore può convertire automaticamente i tipi specificati negli argomenti generici in un'istanza del tipo di unione.
Questo ha il vantaggio aggiunto di fornire un controllo in fase di compilazione che un gestore di route restituisce effettivamente solo i risultati che dichiara. Se si tenta di restituire un tipo non dichiarato come uno degli argomenti generici a Results<>
, risulta in un errore di compilazione.
Si consideri l'endpoint seguente, per il quale viene restituito un 400 BadRequest
codice di stato quando è orderId
maggiore di 999
. In caso contrario, produce un 200 OK
con il contenuto previsto.
app.MapGet("/orders/{orderId}", IResult (int orderId)
=> orderId > 999 ? TypedResults.BadRequest() : TypedResults.Ok(new Order(orderId)))
.Produces(400)
.Produces<Order>();
Per documentare correttamente questo endpoint, viene chiamato il metodo Produces
di estensione. Tuttavia, poiché l'helper TypedResults
include automaticamente i metadati per l'endpoint, è possibile restituire invece il Results<T1, Tn>
tipo di unione, come illustrato nel codice seguente.
app.MapGet("/orders/{orderId}", Results<BadRequest, Ok<Order>> (int orderId)
=> orderId > 999 ? TypedResults.BadRequest() : TypedResults.Ok(new Order(orderId)));
Gli helper di risultati comuni esistono nelle classi statiche Results e TypedResults. Restituire TypedResults
è preferibile a restituire Results
. Per altre informazioni, vedere TypedResults vs Results.
Le sezioni seguenti illustrano l'utilizzo delle funzioni di risultati comuni.
app.MapGet("/hello", () => Results.Json(new { Message = "Hello World" }));
WriteAsJsonAsync è un modo alternativo per restituire JSON:
app.MapGet("/", (HttpContext context) => context.Response.WriteAsJsonAsync
(new { Message = "Hello World" }));
app.MapGet("/405", () => Results.StatusCode(405));
app.MapGet("/500", () => Results.InternalServerError("Something went wrong!"));
L'esempio precedente restituisce un codice di stato 500.
app.MapGet("/problem", () =>
{
var extensions = new List<KeyValuePair<string, object?>> { new("test", "value") };
return TypedResults.Problem("This is an error with extensions",
extensions: extensions);
});
app.MapGet("/text", () => Results.Text("This is some text"));
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
var proxyClient = new HttpClient();
app.MapGet("/pokemon", async () =>
{
var stream = await proxyClient.GetStreamAsync("http://contoso/pokedex.json");
// Proxy the response as JSON
return Results.Stream(stream, "application/json");
});
app.Run();
Results.Stream
gli overload consentono l'accesso al flusso di risposta HTTP sottostante senza buffering. L'esempio seguente usa ImageSharp per restituire una dimensione ridotta dell'immagine specificata:
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Processing;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/process-image/{strImage}", (string strImage, HttpContext http, CancellationToken token) =>
{
http.Response.Headers.CacheControl = $"public,max-age={TimeSpan.FromHours(24).TotalSeconds}";
return Results.Stream(stream => ResizeImageAsync(strImage, stream, token), "image/jpeg");
});
async Task ResizeImageAsync(string strImage, Stream stream, CancellationToken token)
{
var strPath = $"wwwroot/img/{strImage}";
using var image = await Image.LoadAsync(strPath, token);
int width = image.Width / 2;
int height = image.Height / 2;
image.Mutate(x =>x.Resize(width, height));
await image.SaveAsync(stream, JpegFormat.Instance, cancellationToken: token);
}
L'esempio seguente trasmette un'immagine dall'archivio BLOB di Azure:
app.MapGet("/stream-image/{containerName}/{blobName}",
async (string blobName, string containerName, CancellationToken token) =>
{
var conStr = builder.Configuration["blogConStr"];
BlobContainerClient blobContainerClient = new BlobContainerClient(conStr, containerName);
BlobClient blobClient = blobContainerClient.GetBlobClient(blobName);
return Results.Stream(await blobClient.OpenReadAsync(cancellationToken: token), "image/jpeg");
});
L'esempio seguente trasmette un video da un BLOB di Azure:
// GET /stream-video/videos/earth.mp4
app.MapGet("/stream-video/{containerName}/{blobName}",
async (HttpContext http, CancellationToken token, string blobName, string containerName) =>
{
var conStr = builder.Configuration["blogConStr"];
BlobContainerClient blobContainerClient = new BlobContainerClient(conStr, containerName);
BlobClient blobClient = blobContainerClient.GetBlobClient(blobName);
var properties = await blobClient.GetPropertiesAsync(cancellationToken: token);
DateTimeOffset lastModified = properties.Value.LastModified;
long length = properties.Value.ContentLength;
long etagHash = lastModified.ToFileTime() ^ length;
var entityTag = new EntityTagHeaderValue('\"' + Convert.ToString(etagHash, 16) + '\"');
http.Response.Headers.CacheControl = $"public,max-age={TimeSpan.FromHours(24).TotalSeconds}";
return Results.Stream(await blobClient.OpenReadAsync(cancellationToken: token),
contentType: "video/mp4",
lastModified: lastModified,
entityTag: entityTag,
enableRangeProcessing: true);
});
app.MapGet("/old-path", () => Results.Redirect("/new-path"));
app.MapGet("/download", () => Results.File("myfile.text"));
Le seguenti interfacce nel namespace Microsoft.AspNetCore.Http consentono di rilevare il tipo IResult
in fase di esecuzione, un pattern comune nelle implementazioni di filtri.
Ecco un esempio di filtro che usa una di queste interfacce:
app.MapGet("/weatherforecast", (int days) =>
{
if (days <= 0)
{
return Results.BadRequest();
}
var forecast = Enumerable.Range(1, days).Select(index =>
new WeatherForecast(DateTime.Now.AddDays(index), Random.Shared.Next(-20, 55), "Cool"))
.ToArray();
return Results.Ok(forecast);
}).
AddEndpointFilter(async (context, next) =>
{
var result = await next(context);
return result switch
{
IValueHttpResult<WeatherForecast[]> weatherForecastResult => new WeatherHttpResult(weatherForecastResult.Value),
_ => result
};
});
Per altre informazioni, vedere Filtri in app delle API minime e tipi di implementazione IResult.
Usare l'oggetto HttpResponse
per modificare le intestazioni di risposta:
app.MapGet("/", (HttpContext context) => {
// Set a custom header
context.Response.Headers["X-Custom-Header"] = "CustomValue";
// Set a known header
context.Response.Headers.CacheControl = $"public,max-age=3600";
return "Hello World";
});
Le applicazioni possono controllare le risposte implementando un tipo personalizzato IResult . Il codice seguente è un esempio di tipo di risultato HTML:
using System.Net.Mime;
using System.Text;
static class ResultsExtensions
{
public static IResult Html(this IResultExtensions resultExtensions, string html)
{
ArgumentNullException.ThrowIfNull(resultExtensions);
return new HtmlResult(html);
}
}
class HtmlResult : IResult
{
private readonly string _html;
public HtmlResult(string html)
{
_html = html;
}
public Task ExecuteAsync(HttpContext httpContext)
{
httpContext.Response.ContentType = MediaTypeNames.Text.Html;
httpContext.Response.ContentLength = Encoding.UTF8.GetByteCount(_html);
return httpContext.Response.WriteAsync(_html);
}
}
È consigliabile aggiungere un metodo di estensione per Microsoft.AspNetCore.Http.IResultExtensions rendere questi risultati personalizzati più individuabili.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/html", () => Results.Extensions.Html(@$"<!doctype html>
<html>
<head><title>miniHTML</title></head>
<body>
<h1>Hello World</h1>
<p>The time on the server is {DateTime.Now:O}</p>
</body>
</html>"));
app.Run();
Inoltre, un tipo personalizzato IResult
può fornire una propria annotazione implementando l'interfaccia IEndpointMetadataProvider . Ad esempio, il codice seguente aggiunge un'annotazione al tipo precedente HtmlResult
che descrive la risposta prodotta dall'endpoint.
class HtmlResult : IResult, IEndpointMetadataProvider
{
private readonly string _html;
public HtmlResult(string html)
{
_html = html;
}
public Task ExecuteAsync(HttpContext httpContext)
{
httpContext.Response.ContentType = MediaTypeNames.Text.Html;
httpContext.Response.ContentLength = Encoding.UTF8.GetByteCount(_html);
return httpContext.Response.WriteAsync(_html);
}
public static void PopulateMetadata(MethodInfo method, EndpointBuilder builder)
{
builder.Metadata.Add(new ProducesHtmlMetadata());
}
}
ProducesHtmlMetadata
è un'implementazione di IProducesResponseTypeMetadata che definisce il tipo di contenuto prodotto della risposta text/html
e il codice di stato 200 OK
.
internal sealed class ProducesHtmlMetadata : IProducesResponseTypeMetadata
{
public Type? Type => null;
public int StatusCode => 200;
public IEnumerable<string> ContentTypes { get; } = new[] { MediaTypeNames.Text.Html };
}
Un approccio alternativo consiste nell'usare il Microsoft.AspNetCore.Mvc.ProducesAttribute per descrivere la risposta prodotta. Il codice seguente modifica il PopulateMetadata
metodo per usare ProducesAttribute
.
public static void PopulateMetadata(MethodInfo method, EndpointBuilder builder)
{
builder.Metadata.Add(new ProducesAttribute(MediaTypeNames.Text.Html));
}
Per impostazione predefinita, le app per le API minime usano le opzioni di Web defaults
durante la serializzazione e la deserializzazione JSON.
Le opzioni possono essere configurate a livello globale per un'app richiamando ConfigureHttpJsonOptions. L'esempio seguente include campi pubblici e formatta l'output JSON.
var builder = WebApplication.CreateBuilder(args);
builder.Services.ConfigureHttpJsonOptions(options => {
options.SerializerOptions.WriteIndented = true;
options.SerializerOptions.IncludeFields = true;
});
var app = builder.Build();
app.MapPost("/", (Todo todo) => {
if (todo is not null) {
todo.Name = todo.NameField;
}
return todo;
});
app.Run();
class Todo {
public string? Name { get; set; }
public string? NameField;
public bool IsComplete { get; set; }
}
// If the request body contains the following JSON:
//
// {"nameField":"Walk dog", "isComplete":false}
//
// The endpoint returns the following JSON:
//
// {
// "name":"Walk dog",
// "nameField":"Walk dog",
// "isComplete":false
// }
Poiché i campi sono inclusi, il codice precedente legge NameField
e lo include nel codice JSON di output.
Per configurare le opzioni di serializzazione per un endpoint, richiamare Results.Json e passarvi un JsonSerializerOptions oggetto, come illustrato nell'esempio seguente:
using System.Text.Json;
var app = WebApplication.Create();
var options = new JsonSerializerOptions(JsonSerializerDefaults.Web)
{ WriteIndented = true };
app.MapGet("/", () =>
Results.Json(new Todo { Name = "Walk dog", IsComplete = false }, options));
app.Run();
class Todo
{
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
// The endpoint returns the following JSON:
//
// {
// "name":"Walk dog",
// "isComplete":false
// }
In alternativa, usare un overload di WriteAsJsonAsync che accetta un oggetto JsonSerializerOptions. L'esempio seguente usa questo overload per formattare l'output JSON:
using System.Text.Json;
var app = WebApplication.Create();
var options = new JsonSerializerOptions(JsonSerializerDefaults.Web) {
WriteIndented = true };
app.MapGet("/", (HttpContext context) =>
context.Response.WriteAsJsonAsync<Todo>(
new Todo { Name = "Walk dog", IsComplete = false }, options));
app.Run();
class Todo
{
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
// The endpoint returns the following JSON:
//
// {
// "name":"Walk dog",
// "isComplete":false
// }
Gli endpoint minimi supportano i tipi di valori restituiti seguenti:
string
- Questo include Task<string>
e ValueTask<string>
.T
(Qualsiasi altro tipo): include Task<T>
e ValueTask<T>
.IResult
based: Questo include Task<IResult>
e ValueTask<IResult>
.Comportamento | Tipo di Contenuto (Content-Type) |
---|---|
Il framework scrive la stringa direttamente nella risposta. | text/plain |
Si consideri il gestore del percorso seguente, che restituisce un Hello world
testo.
app.MapGet("/hello", () => "Hello World");
Il 200
codice di stato viene restituito con l'intestazione text/plain
Content-Type e il contenuto seguente.
Hello World
Comportamento | Tipo di Contenuto (Content-Type) |
---|---|
Il framework JSON serializza la risposta. | application/json |
Si consideri il gestore di route seguente, che restituisce un tipo anonimo contenente una Message
proprietà stringa.
app.MapGet("/hello", () => new { Message = "Hello World" });
Il 200
codice di stato viene restituito con l'intestazione application/json
Content-Type e il contenuto seguente.
{"message":"Hello World"}
Comportamento | Tipo di Contenuto (Content-Type) |
---|---|
Il framework chiama IResult.ExecuteAsync. | Deciso dall'implementazione IResult . |
L'interfaccia IResult
definisce un contratto che rappresenta il risultato di un endpoint HTTP. La classe static Results e i TypedResult statici vengono usati per creare vari IResult
oggetti che rappresentano diversi tipi di risposte.
Le Results classi statiche e TypedResults forniscono set simili di helper di risultati. La TypedResults
classe è l'equivalente tipizzato della Results
classe . Tuttavia, il tipo restituito degli Results
helper è IResult, mentre il tipo restituito di ogni TypedResults
helper è uno tra i IResult
tipi di implementazione. La differenza significa che per gli helper Results
è necessaria una conversione quando il tipo concreto è richiesto, ad esempio per i test unitari. I tipi di implementazione sono definiti nello spazio dei nomi Microsoft.AspNetCore.Http.HttpResults.
La restituzione TypedResults
anziché Results
presenta i vantaggi seguenti:
TypedResults
gli helper restituiscono oggetti fortemente tipizzati, che possono migliorare la leggibilità del codice, la capacità di eseguire test unitari e ridurre la probabilità di errori di runtime.Si consideri l'endpoint seguente, per il quale viene generato un 200 OK
codice di stato con la risposta JSON prevista.
app.MapGet("/hello", () => Results.Ok(new Message() { Text = "Hello World!" }))
.Produces<Message>();
Per documentare correttamente questo endpoint, viene chiamato il metodo extensions Produces
. Tuttavia, non è necessario chiamare Produces
se TypedResults
viene usato invece di Results
, come illustrato nel codice seguente.
TypedResults
fornisce automaticamente i metadati per l'endpoint.
app.MapGet("/hello2", () => TypedResults.Ok(new Message() { Text = "Hello World!" }));
Per altre informazioni sulla descrizione di un tipo di risposta, vedere Supporto OpenAPI nelle API minime.
Come accennato in precedenza, quando si usa TypedResults
, non è necessaria una conversione. Si consideri l'API minima seguente che restituisce una TypedResults
classe
public static async Task<Ok<Todo[]>> GetAllTodos(TodoGroupDbContext database)
{
var todos = await database.Todos.ToArrayAsync();
return TypedResults.Ok(todos);
}
Il test seguente verifica la presenza del tipo concreto completo:
[Fact]
public async Task GetAllReturnsTodosFromDatabase()
{
// Arrange
await using var context = new MockDb().CreateDbContext();
context.Todos.Add(new Todo
{
Id = 1,
Title = "Test title 1",
Description = "Test description 1",
IsDone = false
});
context.Todos.Add(new Todo
{
Id = 2,
Title = "Test title 2",
Description = "Test description 2",
IsDone = true
});
await context.SaveChangesAsync();
// Act
var result = await TodoEndpointsV1.GetAllTodos(context);
//Assert
Assert.IsType<Ok<Todo[]>>(result);
Assert.NotNull(result.Value);
Assert.NotEmpty(result.Value);
Assert.Collection(result.Value, todo1 =>
{
Assert.Equal(1, todo1.Id);
Assert.Equal("Test title 1", todo1.Title);
Assert.False(todo1.IsDone);
}, todo2 =>
{
Assert.Equal(2, todo2.Id);
Assert.Equal("Test title 2", todo2.Title);
Assert.True(todo2.IsDone);
});
}
Poiché tutti i metodi su Results
restituiscono IResult
nella loro firma, il compilatore inferisce automaticamente quello come tipo restituito del delegato della richiesta quando vengono restituiti risultati diversi da un singolo endpoint.
TypedResults
richiede l'uso di Results<T1, TN>
da tali delegati.
Il metodo seguente viene compilato perché sia Results.Ok
che Results.NotFound
vengono dichiarati come restituiti IResult
, anche se i tipi concreti effettivi degli oggetti restituiti sono diversi:
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());
Il metodo seguente non viene compilato perché TypedResults.Ok
e TypedResults.NotFound
vengono dichiarati come tipi diversi e il compilatore non tenterà di dedurre il tipo di corrispondenza migliore:
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(todo)
: TypedResults.NotFound());
Per usare TypedResults
, il tipo restituito deve essere completamente dichiarato; quando è asincrono, è necessario il wrapper Task<>
. L'uso di TypedResults
è più dettagliato, ma questo è il compromesso per avere le informazioni sul tipo staticamente disponibili e quindi in grado di autodescriversi con OpenAPI:
app.MapGet("/todoitems/{id}", async Task<Results<Ok<Todo>, NotFound>> (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(todo)
: TypedResults.NotFound());
Usare Results<TResult1, TResultN>
come tipo restituito del gestore endpoint invece di IResult
quando:
IResult
tipi di implementazione.TypedResult
per creare gli IResult
oggetti .Questa alternativa è migliore rispetto alla restituzione IResult
perché i tipi di unione generica mantengono automaticamente i metadati dell'endpoint. Poiché i Results<TResult1, TResultN>
tipi di unione implementano operatori cast impliciti, il compilatore può convertire automaticamente i tipi specificati negli argomenti generici in un'istanza del tipo di unione.
Questo ha il vantaggio aggiunto di fornire un controllo in fase di compilazione che un gestore di route restituisce effettivamente solo i risultati che dichiara. Se si tenta di restituire un tipo non dichiarato come uno degli argomenti generici a Results<>
, risulta in un errore di compilazione.
Si consideri l'endpoint seguente, per il quale viene restituito un 400 BadRequest
codice di stato quando è orderId
maggiore di 999
. In caso contrario, produce un 200 OK
con il contenuto previsto.
app.MapGet("/orders/{orderId}", IResult (int orderId)
=> orderId > 999 ? TypedResults.BadRequest() : TypedResults.Ok(new Order(orderId)))
.Produces(400)
.Produces<Order>();
Per documentare correttamente questo endpoint, viene chiamato il metodo Produces
di estensione. Tuttavia, poiché l'helper TypedResults
include automaticamente i metadati per l'endpoint, è possibile restituire invece il Results<T1, Tn>
tipo di unione, come illustrato nel codice seguente.
app.MapGet("/orders/{orderId}", Results<BadRequest, Ok<Order>> (int orderId)
=> orderId > 999 ? TypedResults.BadRequest() : TypedResults.Ok(new Order(orderId)));
Gli helper di risultati comuni esistono nelle classi statiche Results e TypedResults. Restituire TypedResults
è preferibile a restituire Results
. Per altre informazioni, vedere TypedResults vs Results.
Le sezioni seguenti illustrano l'utilizzo delle funzioni di risultati comuni.
app.MapGet("/hello", () => Results.Json(new { Message = "Hello World" }));
WriteAsJsonAsync è un modo alternativo per restituire JSON:
app.MapGet("/", (HttpContext context) => context.Response.WriteAsJsonAsync
(new { Message = "Hello World" }));
app.MapGet("/405", () => Results.StatusCode(405));
app.MapGet("/text", () => Results.Text("This is some text"));
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
var proxyClient = new HttpClient();
app.MapGet("/pokemon", async () =>
{
var stream = await proxyClient.GetStreamAsync("http://contoso/pokedex.json");
// Proxy the response as JSON
return Results.Stream(stream, "application/json");
});
app.Run();
Results.Stream
gli overload consentono l'accesso al flusso di risposta HTTP sottostante senza buffering. L'esempio seguente usa ImageSharp per restituire una dimensione ridotta dell'immagine specificata:
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Processing;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/process-image/{strImage}", (string strImage, HttpContext http, CancellationToken token) =>
{
http.Response.Headers.CacheControl = $"public,max-age={TimeSpan.FromHours(24).TotalSeconds}";
return Results.Stream(stream => ResizeImageAsync(strImage, stream, token), "image/jpeg");
});
async Task ResizeImageAsync(string strImage, Stream stream, CancellationToken token)
{
var strPath = $"wwwroot/img/{strImage}";
using var image = await Image.LoadAsync(strPath, token);
int width = image.Width / 2;
int height = image.Height / 2;
image.Mutate(x =>x.Resize(width, height));
await image.SaveAsync(stream, JpegFormat.Instance, cancellationToken: token);
}
L'esempio seguente trasmette un'immagine dall'archivio BLOB di Azure:
app.MapGet("/stream-image/{containerName}/{blobName}",
async (string blobName, string containerName, CancellationToken token) =>
{
var conStr = builder.Configuration["blogConStr"];
BlobContainerClient blobContainerClient = new BlobContainerClient(conStr, containerName);
BlobClient blobClient = blobContainerClient.GetBlobClient(blobName);
return Results.Stream(await blobClient.OpenReadAsync(cancellationToken: token), "image/jpeg");
});
L'esempio seguente trasmette un video da un BLOB di Azure:
// GET /stream-video/videos/earth.mp4
app.MapGet("/stream-video/{containerName}/{blobName}",
async (HttpContext http, CancellationToken token, string blobName, string containerName) =>
{
var conStr = builder.Configuration["blogConStr"];
BlobContainerClient blobContainerClient = new BlobContainerClient(conStr, containerName);
BlobClient blobClient = blobContainerClient.GetBlobClient(blobName);
var properties = await blobClient.GetPropertiesAsync(cancellationToken: token);
DateTimeOffset lastModified = properties.Value.LastModified;
long length = properties.Value.ContentLength;
long etagHash = lastModified.ToFileTime() ^ length;
var entityTag = new EntityTagHeaderValue('\"' + Convert.ToString(etagHash, 16) + '\"');
http.Response.Headers.CacheControl = $"public,max-age={TimeSpan.FromHours(24).TotalSeconds}";
return Results.Stream(await blobClient.OpenReadAsync(cancellationToken: token),
contentType: "video/mp4",
lastModified: lastModified,
entityTag: entityTag,
enableRangeProcessing: true);
});
app.MapGet("/old-path", () => Results.Redirect("/new-path"));
app.MapGet("/download", () => Results.File("myfile.text"));
Le seguenti interfacce nel namespace Microsoft.AspNetCore.Http consentono di rilevare il tipo IResult
in fase di esecuzione, un pattern comune nelle implementazioni di filtri.
Ecco un esempio di filtro che usa una di queste interfacce:
app.MapGet("/weatherforecast", (int days) =>
{
if (days <= 0)
{
return Results.BadRequest();
}
var forecast = Enumerable.Range(1, days).Select(index =>
new WeatherForecast(DateTime.Now.AddDays(index), Random.Shared.Next(-20, 55), "Cool"))
.ToArray();
return Results.Ok(forecast);
}).
AddEndpointFilter(async (context, next) =>
{
var result = await next(context);
return result switch
{
IValueHttpResult<WeatherForecast[]> weatherForecastResult => new WeatherHttpResult(weatherForecastResult.Value),
_ => result
};
});
Per altre informazioni, vedere Filtri in app delle API minime e tipi di implementazione IResult.
Le applicazioni possono controllare le risposte implementando un tipo personalizzato IResult . Il codice seguente è un esempio di tipo di risultato HTML:
using System.Net.Mime;
using System.Text;
static class ResultsExtensions
{
public static IResult Html(this IResultExtensions resultExtensions, string html)
{
ArgumentNullException.ThrowIfNull(resultExtensions);
return new HtmlResult(html);
}
}
class HtmlResult : IResult
{
private readonly string _html;
public HtmlResult(string html)
{
_html = html;
}
public Task ExecuteAsync(HttpContext httpContext)
{
httpContext.Response.ContentType = MediaTypeNames.Text.Html;
httpContext.Response.ContentLength = Encoding.UTF8.GetByteCount(_html);
return httpContext.Response.WriteAsync(_html);
}
}
È consigliabile aggiungere un metodo di estensione per Microsoft.AspNetCore.Http.IResultExtensions rendere questi risultati personalizzati più individuabili.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/html", () => Results.Extensions.Html(@$"<!doctype html>
<html>
<head><title>miniHTML</title></head>
<body>
<h1>Hello World</h1>
<p>The time on the server is {DateTime.Now:O}</p>
</body>
</html>"));
app.Run();
Inoltre, un tipo personalizzato IResult
può fornire una propria annotazione implementando l'interfaccia IEndpointMetadataProvider . Ad esempio, il codice seguente aggiunge un'annotazione al tipo precedente HtmlResult
che descrive la risposta prodotta dall'endpoint.
class HtmlResult : IResult, IEndpointMetadataProvider
{
private readonly string _html;
public HtmlResult(string html)
{
_html = html;
}
public Task ExecuteAsync(HttpContext httpContext)
{
httpContext.Response.ContentType = MediaTypeNames.Text.Html;
httpContext.Response.ContentLength = Encoding.UTF8.GetByteCount(_html);
return httpContext.Response.WriteAsync(_html);
}
public static void PopulateMetadata(MethodInfo method, EndpointBuilder builder)
{
builder.Metadata.Add(new ProducesHtmlMetadata());
}
}
ProducesHtmlMetadata
è un'implementazione di IProducesResponseTypeMetadata che definisce il tipo di contenuto prodotto della risposta text/html
e il codice di stato 200 OK
.
internal sealed class ProducesHtmlMetadata : IProducesResponseTypeMetadata
{
public Type? Type => null;
public int StatusCode => 200;
public IEnumerable<string> ContentTypes { get; } = new[] { MediaTypeNames.Text.Html };
}
Un approccio alternativo consiste nell'usare il Microsoft.AspNetCore.Mvc.ProducesAttribute per descrivere la risposta prodotta. Il codice seguente modifica il PopulateMetadata
metodo per usare ProducesAttribute
.
public static void PopulateMetadata(MethodInfo method, EndpointBuilder builder)
{
builder.Metadata.Add(new ProducesAttribute(MediaTypeNames.Text.Html));
}
Per impostazione predefinita, le app per le API minime usano le opzioni Web defaults
durante la serializzazione e la deserializzazione JSON.
Le opzioni possono essere configurate a livello globale per un'app richiamando ConfigureHttpJsonOptions. L'esempio seguente include campi pubblici e formatta l'output JSON.
var builder = WebApplication.CreateBuilder(args);
builder.Services.ConfigureHttpJsonOptions(options => {
options.SerializerOptions.WriteIndented = true;
options.SerializerOptions.IncludeFields = true;
});
var app = builder.Build();
app.MapPost("/", (Todo todo) => {
if (todo is not null) {
todo.Name = todo.NameField;
}
return todo;
});
app.Run();
class Todo {
public string? Name { get; set; }
public string? NameField;
public bool IsComplete { get; set; }
}
// If the request body contains the following JSON:
//
// {"nameField":"Walk dog", "isComplete":false}
//
// The endpoint returns the following JSON:
//
// {
// "name":"Walk dog",
// "nameField":"Walk dog",
// "isComplete":false
// }
Poiché i campi sono inclusi, il codice precedente legge NameField
e lo include nel codice JSON di output.
Per configurare le opzioni di serializzazione per un endpoint, richiamare Results.Json e passarvi un JsonSerializerOptions oggetto, come illustrato nell'esempio seguente:
using System.Text.Json;
var app = WebApplication.Create();
var options = new JsonSerializerOptions(JsonSerializerDefaults.Web)
{ WriteIndented = true };
app.MapGet("/", () =>
Results.Json(new Todo { Name = "Walk dog", IsComplete = false }, options));
app.Run();
class Todo
{
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
// The endpoint returns the following JSON:
//
// {
// "name":"Walk dog",
// "isComplete":false
// }
In alternativa, usare un overload di WriteAsJsonAsync che accetta un oggetto JsonSerializerOptions. L'esempio seguente usa questo overload per formattare l'output JSON:
using System.Text.Json;
var app = WebApplication.Create();
var options = new JsonSerializerOptions(JsonSerializerDefaults.Web) {
WriteIndented = true };
app.MapGet("/", (HttpContext context) =>
context.Response.WriteAsJsonAsync<Todo>(
new Todo { Name = "Walk dog", IsComplete = false }, options));
app.Run();
class Todo
{
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
// The endpoint returns the following JSON:
//
// {
// "name":"Walk dog",
// "isComplete":false
// }
Feedback su ASP.NET Core
ASP.NET Core è un progetto di open source. Selezionare un collegamento per fornire feedback:
Eventi
Partecipa alla sfida fest per le competenze di intelligenza artificiale
8 apr, 15 - 28 mag, 07
Affinare le tue competenze di intelligenza artificiale e partecipare agli sweep per vincere un esame di certificazione gratuito
Register now!