Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions aspnetcore/fundamentals/http-requests.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,44 @@ public class ValuesController : ControllerBase
}
```

## Make POST, PUT, and DELETE requests

In the preceding examples, all HTTP requests use the GET HTTP verb. `HttpClient` also supports other HTTP verbs, including:

* POST
* PUT
* DELETE
* PATCH

For a complete list of supported HTTP verbs, see <xref:System.Net.Http.HttpMethod>.

The following example shows how to make a HTTP POST request:

[!code-csharp[](http-requests/samples/3.x/HttpRequestsSample/Models/TodoClient.cs?name=snippet_POST)]

In the preceding code, the `CreateItemAsync` method:

* Serializes the `TodoItem` parameter to JSON using `System.Text.Json`. This uses an instance of <xref:System.Text.Json.JsonSerializerOptions> to configure the serialization process.
* Creates an instance of <xref:System.Net.Http.StringContent> to package the serialized JSON for sending in the HTTP request's body.
* Calls <xref:System.Net.Http.HttpClient.PostAsync%2A> to send the JSON content to the specified URL. This is a relative URL that gets added to the [HttpClient.BaseAddress](xref:System.Net.Http.HttpClient.BaseAddress).
* Calls <xref:System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode%2A> to throw an exception if the response status code does not indicate success.

`HttpClient` also supports other types of content. For example, <xref:System.Net.Http.MultipartContent> and <xref:System.Net.Http.StreamContent>. For a complete list of supported content, see <xref:System.Net.Http.HttpContent>.

The following example shows how to make a HTTP PUT request:

[!code-csharp[](http-requests/samples/3.x/HttpRequestsSample/Models/TodoClient.cs?name=snippet_PUT)]

The preceding code is very similar to the POST example. The `SaveItemAsync` method calls <xref:System.Net.Http.HttpClient.PutAsync%2A> instead of `PostAsync`.

The following example shows an HTTP DELETE request:

[!code-csharp[](http-requests/samples/3.x/HttpRequestsSample/Models/TodoClient.cs?name=snippet_DELETE)]

In the preceding code, the `DeleteItemAsync` method calls <xref:System.Net.Http.HttpClient.DeleteAsync%2A>. Because HTTP DELETE requests typically contain no body, the `DeleteAsync` method doesn't provide an overload that accepts an instance of `HttpContent`.

To learn more about using different HTTP verbs with `HttpClient`, see <xref:System.Net.Http.HttpClient>.

## Outgoing request middleware

`HttpClient` has the concept of delegating handlers that can be linked together for outgoing HTTP requests. `IHttpClientFactory`:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using HttpRequestsSample.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

namespace HttpRequestsSample.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class TodoItemsController : ControllerBase
{
private readonly TodoContext _context;

public TodoItemsController(TodoContext context)
{
_context = context;
}

[HttpGet]
public async Task<IEnumerable<TodoItem>> Get() =>
await _context.TodoItems.AsNoTracking().ToListAsync();

[HttpGet("{id}")]
public async Task<ActionResult<TodoItem>> Get(long id)
{
var todoItem = await _context.TodoItems.FindAsync(id);

if (todoItem == null)
return NotFound();

return todoItem;
}

[HttpPost]
public async Task<IActionResult> Post(TodoItem todoItem)
{
_context.TodoItems.Add(todoItem);
await _context.SaveChangesAsync();

return CreatedAtAction(nameof(Get), new { id = todoItem.Id, todoItem });
}

[HttpPut("{id}")]
public async Task<IActionResult> Put(long id, TodoItem todoItem)
{
if (todoItem.Id != id)
return BadRequest();

_context.Update(todoItem);
await _context.SaveChangesAsync();

return NoContent();
}

[HttpDelete("{id}")]
public async Task<IActionResult> Delete(long id)
{
var todoItem = await _context.TodoItems.FindAsync(id);

if (todoItem == null)
return NotFound();

_context.TodoItems.Remove(todoItem);
await _context.SaveChangesAsync();

return NoContent();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="3.1.4" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;

namespace HttpRequestsSample.Models
{
public class TodoClient
{
private static readonly JsonSerializerOptions _jsonSerializerOptions = new JsonSerializerOptions
{
IgnoreNullValues = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};

private readonly HttpClient _httpClient;

public TodoClient(HttpClient httpClient)
{
_httpClient = httpClient;
}

public async Task<IEnumerable<TodoItem>> GetItemsAsync()
{
using var httpResponse = await _httpClient.GetAsync("/api/TodoItems");

httpResponse.EnsureSuccessStatusCode();

using var httpResponseStream = await httpResponse.Content.ReadAsStreamAsync();

return await JsonSerializer.DeserializeAsync<List<TodoItem>>(httpResponseStream, _jsonSerializerOptions);
}

public async Task<TodoItem> GetItemAsync(long itemId)
{
using var httpResponse = await _httpClient.GetAsync($"/api/TodoItems/{itemId}");

if (httpResponse.StatusCode == HttpStatusCode.NotFound)
return null;

httpResponse.EnsureSuccessStatusCode();

using var httpResponseStream = await httpResponse.Content.ReadAsStreamAsync();

return await JsonSerializer.DeserializeAsync<TodoItem>(httpResponseStream, _jsonSerializerOptions);
}

#region snippet_POST
public async Task CreateItemAsync(TodoItem todoItem)
{
var todoItemJson = new StringContent(
JsonSerializer.Serialize(todoItem, _jsonSerializerOptions),
Encoding.UTF8,
"application/json");

using var httpResponse =
await _httpClient.PostAsync("/api/TodoItems", todoItemJson);

httpResponse.EnsureSuccessStatusCode();
}
#endregion

#region snippet_PUT
public async Task SaveItemAsync(TodoItem todoItem)
{
var todoItemJson = new StringContent(
JsonSerializer.Serialize(todoItem),
Encoding.UTF8,
"application/json");

using var httpResponse =
await _httpClient.PutAsync($"/api/TodoItems/{todoItem.Id}", todoItemJson);

httpResponse.EnsureSuccessStatusCode();
}
#endregion

#region snippet_DELETE
public async Task DeleteItemAsync(long itemId)
{
using var httpResponse =
await _httpClient.DeleteAsync($"/api/TodoItems/{itemId}");

httpResponse.EnsureSuccessStatusCode();
}
#endregion
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using Microsoft.EntityFrameworkCore;

namespace HttpRequestsSample.Models
{
public class TodoContext : DbContext
{
public TodoContext(DbContextOptions<TodoContext> options)
: base(options) { }

public DbSet<TodoItem> TodoItems { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace HttpRequestsSample.Models
{
public class TodoItem
{
public long Id { get; set; }

public string Name { get; set; }

public bool IsComplete { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
@page
@model ErrorModel

@{
ViewData["Title"] = "Error";
}

<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>

@if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@Model.RequestId</code>
</p>
}

<h3>Development Mode</h3>
<p>
Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace HttpRequestsSample.Pages
{
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public class ErrorModel : PageModel
{
public string RequestId { get; set; }

public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);

public void OnGet()
{
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
@page
@model IndexModel

@{
ViewData["Title"] = "Home";
}

<div class="row justify-content-center">
<div class="col-md-6">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<form asp-page-handler="Create" method="POST">
<div class="input-group input-group-sm">
<input name="name" placeholder="Something I need to do" class="form-control form-control-sm">
<div class="input-group-append">
<button class="btn btn-primary">Add to List</button>
</div>
</div>
</form>
</div>
</div>

@if (Model.IncompleteTodoItems.Any())
{
<hr>
<div class="row justify-content-center">
<div class="col-md-6">
<ul class="list-group mb-2">
@foreach (var todoItem in Model.IncompleteTodoItems)
{
<a asp-page="/Item" asp-route-id="@todoItem.Id" class="list-group-item list-group-item-action">@todoItem.Name</a>
}
</ul>
</div>
</div>
}

@if (Model.CompleteTodoItems.Any())
{
<div class="row justify-content-center">
<div class="col-md-6">
<p class="text-center">
<small>@Model.CompleteTodoItems.Count completed</small>
</p>
<ul class="list-group">
@foreach (var todoItem in Model.CompleteTodoItems)
{
<a asp-page="/Item" asp-route-id="@todoItem.Id" class="list-group-item list-group-item-action text-muted">@todoItem.Name</a>
}
</ul>
</div>
</div>
}

@section Styles {
<style>.list-group-item.text-muted{text-decoration:line-through}</style>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using HttpRequestsSample.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace HttpRequestsSample.Pages
{
public class IndexModel : PageModel
{
private readonly TodoClient _todoClient;

public IndexModel(TodoClient todoClient)
{
_todoClient = todoClient;
}

public List<TodoItem> CompleteTodoItems { get; private set; }

public List<TodoItem> IncompleteTodoItems { get; private set; }

public async Task OnGetAsync()
{
var todoItems = await _todoClient.GetItemsAsync();

CompleteTodoItems = todoItems.Where(x => x.IsComplete).ToList();
IncompleteTodoItems = todoItems.Except(CompleteTodoItems).ToList();
}

public async Task<IActionResult> OnPostCreateAsync([Required] string name)
{
if (!ModelState.IsValid)
{
return RedirectToPage();
}

var newTodoItem = new TodoItem
{
Name = name
};

await _todoClient.CreateItemAsync(newTodoItem);

return RedirectToPage();
}
}
}
Loading