« Asp.net core web api » : différence entre les versions
De Banane Atomic
Aller à la navigationAller à la recherche
m (Nicolas a déplacé la page Asp.net core api vers Asp.net core web api) |
|||
Ligne 135 : | Ligne 135 : | ||
else | else | ||
return NotFound(); | return NotFound(); | ||
</kode> | |||
= Client = | |||
<filebox fn='Services/ItemDataService.cs'> | |||
public class ItemDataService : IItemDataService | |||
{ | |||
private readonly HttpClient _httpClient; | |||
// injection de HttpClient | |||
public ItemDataService(HttpClient httpClient) | |||
{ | |||
_httpClient = httpClient; | |||
} | |||
</filebox> | |||
== GET == | |||
<kode lang='cs'> | |||
public async Task<IEnumerable<Employee>> GetAllItemsAsync() | |||
{ | |||
return await JsonSerializer.DeserializeAsync<IEnumerable<Item>>( | |||
await _httpClient.GetStreamAsync($"api/item"), | |||
new JsonSerializerOptions() | |||
{ | |||
PropertyNameCaseInsensitive = true | |||
}); | |||
} | |||
public async Task<Item> GetItemAsync(int itemId) | |||
{ | |||
return await JsonSerializer.DeserializeAsync<Item>( | |||
await _httpClient.GetStreamAsync($"api/item/{itemId}"), | |||
new JsonSerializerOptions() | |||
{ | |||
PropertyNameCaseInsensitive = true | |||
}); | |||
} | |||
</kode> | |||
== POST == | |||
<kode lang='cs'> | |||
public async Task<Item> AddItemAsync(Item item) | |||
{ | |||
var itemJson = | |||
new StringContent(JsonSerializer.Serialize(item), Encoding.UTF8, "application/json"); | |||
var response = await _httpClient.PostAsync("api/item", itemJson); | |||
if (response.IsSuccessStatusCode) | |||
{ | |||
return await JsonSerializer.DeserializeAsync<Item>(await response.Content.ReadAsStreamAsync()); | |||
} | |||
return null; | |||
} | |||
</kode> | |||
== PUT == | |||
<kode lang='cs'> | |||
public async Task UpdateItemAsync(Item item) | |||
{ | |||
var itemJson = | |||
new StringContent(JsonSerializer.Serialize(item), Encoding.UTF8, "application/json"); | |||
await _httpClient.PutAsync("api/item", itemJson); | |||
} | |||
</kode> | |||
== DELETE == | |||
<kode lang='cs'> | |||
public async Task DeleteItemAsync(int itemId) | |||
{ | |||
await _httpClient.DeleteAsync($"api/item/{itemId}"); | |||
} | |||
</kode> | </kode> | ||
Version du 11 janvier 2020 à 18:03
Liens
- Path
- Build web APIs with ASP.NET CoreBuild web APIs with ASP.NET Core
- Controller action return types in ASP.NET Core Web API
- StatusCodes
Projet
Clique-droit sur le projet → Properties → Debug → décocher Launch browser
Controller
Controllers/ItemsController.cs |
[Produces("application/json")] // force le format JSON [Route("api/[controller]")] // /api/items [ApiController] public class ItemsController : ControllerBase { private readonly IMyAppRepository _repository; public ListApiController(IMyAppRepository repository) { _repository = repository; } |
GET
// [HttpGet("[action]")] [HttpGet] [ProducesResponseType(200, Type = typeof(IEnumerable<Item>))] public IActionResult Get() { return Ok(_repository.GetAllItems()); } [HttpGet("{id:int}")] [ProducesResponseType(200, Type = typeof(Item))] [ProducesResponseType(404)] [ProducesResponseType(500)] public IActionResult Get(int id) { try { var item = _repository.GetItem(id); if (item != null) { return Ok(item); // status 200 OK } else { return NotFound(); // status 404 Not Found } } catch (Exception ex) { // using Microsoft.AspNetCore.Http; return StatusCode(StatusCodes.Status500InternalServerError, $"Failed to get Item {id} ({ex})"); } } |
POST
[HttpPost] [ProducesResponseType(201, Type = typeof(Item))] [ProducesResponseType(500)] public IActionResult Post([FromBody]Item item) { try { _repository.Add(item); if (_repository.SaveAll()) { // status 201 Created return Created($"https://localhost:5001/api/items/{item.Id}", item); return CreatedAtAction(nameof(Get), new { id = item.Id }, item); return CreatedAtRoute("api", new { controller = "items", action = nameof(Get), id = $"{item.Id}" }, item); } else { return StatusCode(StatusCodes.Status500InternalServerError, "Failed to save."); } } catch (Exception ex) { return StatusCode(StatusCodes.Status500InternalServerError, $"Failed to add Item ({ex})"); } } |
Startup.cs |
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseMvc(routes => { routes.MapRoute( name: "api", template: "api/{controller=Items}/{id?}"); }); |
PUT / Update
[ProducesResponseType(204)] [ProducesResponseType(404)] public IActionResult Put(int id, Item updatedItem) { var item = _items.SingleOrDefault(i => i.Id == id); if (item != null) { item.Name = updatedItem.Name; return NoContent(); } else return NotFound(); |
DELETE
[HttpDelete("{id:int}")] [ProducesResponseType(204)] [ProducesResponseType(404)] public IActionResult Delete(int id) { var item = _items.SingleOrDefault(i => i.Id == id); if (item != null) { if (_items.Remove(item)) return NoContent(); else return StatusCode(StatusCodes.Status500InternalServerError); } else return NotFound(); |
Client
Services/ItemDataService.cs |
public class ItemDataService : IItemDataService { private readonly HttpClient _httpClient; // injection de HttpClient public ItemDataService(HttpClient httpClient) { _httpClient = httpClient; } |
GET
public async Task<IEnumerable<Employee>> GetAllItemsAsync() { return await JsonSerializer.DeserializeAsync<IEnumerable<Item>>( await _httpClient.GetStreamAsync($"api/item"), new JsonSerializerOptions() { PropertyNameCaseInsensitive = true }); } public async Task<Item> GetItemAsync(int itemId) { return await JsonSerializer.DeserializeAsync<Item>( await _httpClient.GetStreamAsync($"api/item/{itemId}"), new JsonSerializerOptions() { PropertyNameCaseInsensitive = true }); } |
POST
public async Task<Item> AddItemAsync(Item item) { var itemJson = new StringContent(JsonSerializer.Serialize(item), Encoding.UTF8, "application/json"); var response = await _httpClient.PostAsync("api/item", itemJson); if (response.IsSuccessStatusCode) { return await JsonSerializer.DeserializeAsync<Item>(await response.Content.ReadAsStreamAsync()); } return null; } |
PUT
public async Task UpdateItemAsync(Item item) { var itemJson = new StringContent(JsonSerializer.Serialize(item), Encoding.UTF8, "application/json"); await _httpClient.PutAsync("api/item", itemJson); } |
DELETE
public async Task DeleteItemAsync(int itemId) { await _httpClient.DeleteAsync($"api/item/{itemId}"); } |
Validation
ItemViewModel.cs |
public class ItemViewModel { public int Id { get; set; } [Required] [MinLength(6)] public string Name { get; set; } } |
[HttpPost] public IActionResult Post([FromBody]ItemViewModel itemVm) { if (ModelState.IsValid) { var item = itemVm.ToEfItem(); _repository.Add(item); if (_repository.SaveAll()) { var newItemVm = new ItemViewModel(item); return Created($"/api/ListApi/{newItemVm.Id}", newItemVm); } else { return BadRequest("Failed to save."); } } else { return BadRequest(ModelState); } |
Query Strings
Url: http://localhost:80/api/items?option1=true
ItemsController.cs |
[HttpGet] public IActionResult Get(bool option1 = false) // par défaut à false { } |
Images
Download
MyController.cs |
[HttpGet] public IActionResult Get() { var filePath = Path.Combine("~", "folder", "image.jpeg"); // VirtualFileResult return File(filePath, "image/jpeg"); } |
Base64
MyController.cs |
[HttpGet] public IActionResult Get() { var filePath = Path.Combine("~", "folder", "image.jpeg"); byte[] bytesArray = System.IO.File.ReadAllBytes(filePath); return Ok("data:image/jpeg;base64," + Convert.ToBase64String(bytesArray)); } |
Upload
MyController.cs |
[HttpPost] public async Task<IActionResult> Post([FromForm]IFormFile myFile) { var filePath = Path.Combine(folderPath, photo.FileName); using (var stream = new FileStream(filePath, FileMode.Create)) { await myFile.CopyToAsync(stream); } return Ok(filePath); } |
Postman → Body → form-data
- key: myFile
- File
- Value: Choose Files
Si myFile est toujours null, enlever [FromForm] |
Renvoyer le message d'erreur des exceptions
ExceptionFilterAttribute
ApiExceptionFilterAttribute.cs |
public class ApiExceptionFilterAttribute : ExceptionFilterAttribute { public override void OnException(ExceptionContext context) { context.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError; context.HttpContext.Response.ContentType = "text/plain"; context.HttpContext.Response.WriteAsync(context.Exception.Message).ConfigureAwait(false); } } |
MyController.cs |
[ApiExceptionFilter] [Route("api/[controller]")] public class MyController : Controller |
ExceptionMiddleware
ExceptionMiddleware.cs |
public class ExceptionMiddleware { public async Task Invoke(HttpContext context) { context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; var ex = context.Features.Get<IExceptionHandlerFeature>()?.Error; if (ex == null) return; context.Response.ContentType = "text/plain"; await context.Response.WriteAsync(ex.Message); } } |
Startup.cs |
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseExceptionHandler(new ExceptionHandlerOptions { ExceptionHandler = new ExceptionMiddleware().Invoke }); app.UseMvc(); |
Différencier la gestion d'erreur pour mvc et webapi
Utiliser un ApiExceptionFilterAttribute et un MvcExceptionFilter
UseWhen et UseExceptionHandler
Startup.cs |
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseWhen(x => x.Request.Path.Value.StartsWith("/api"), builder => { builder.UseExceptionHandler(new ExceptionHandlerOptions { ExceptionHandler = new ExceptionMiddleware().Invoke }); }); app.UseWhen(x => !x.Request.Path.Value.StartsWith("/api"), builder => { builder.UseExceptionHandler("/Home/Error"); }); |
UseExceptionHandler
ExceptionMiddleware.cs |
public class ExceptionMiddleware { public async Task Invoke(HttpContext context) { if (context.Request.Path.Value.StartsWith("/api")) { context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; var ex = context.Features.Get<IExceptionHandlerFeature>()?.Error; if (ex == null) return; context.Response.ContentType = "text/plain"; await context.Response.WriteAsync(ex.Message).ConfigureAwait(false); } else { // redirect to /Home/Error } } } |
UseStatusCodePagesWithReExecute
Startup.cs |
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseStatusCodePagesWithReExecute("/error/{0}"); app.UseExceptionHandler("/error/500"); app.UseMvc(); |
[HttpGet("/error/{code:int}")] public string Error(int code) { return $"Error: {code}"; } |
Erreurs
JsonSerializationException: Self referencing loop detected for property 'xxx' ...
Startup.cs |
services.AddMvc() // ignorer les références circulaires entre objets dans EF pour JSON .AddJsonOptions(opt => opt.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore); |
SqlException: Cannot insert explicit value for identity column in table 'xxx' when IDENTITY_INSERT is set to OFF
Impossible de sauvegarder les changements si une entité a été insérée avec une valeur ≠ default dans une propriété bindée avec une colonne Identity.
Solution: forcer la valeur de cette propriété a default. La bdd mettra la valeur de la propriété a jour après avoir inséré l'entité.