« Asp.net core web api » : différence entre les versions
De Banane Atomic
Aller à la navigationAller à la recherche
(→Liens) |
(→POST) |
||
Ligne 65 : | Ligne 65 : | ||
[HttpPost] | [HttpPost] | ||
[ProducesResponseType(201, Type = typeof(Item))] | [ProducesResponseType(201, Type = typeof(Item))] | ||
[ProducesResponseType(400)] | |||
[ProducesResponseType(500)] | [ProducesResponseType(500)] | ||
public IActionResult Post([FromBody]Item item) | public IActionResult Post([FromBody] Item item) | ||
{ | { | ||
if (item == null) | |||
return BadRequest(); | |||
if (!ModelState.IsValid) | |||
return BadRequest(ModelState); | |||
try | try | ||
{ | { | ||
var createdItem = _itemRepository.Add(item); | |||
return Created("item", createdItem); | |||
if (_repository.SaveAll()) | if (_repository.SaveAll()) | ||
{ | { |
Version du 14 janvier 2020 à 23:04
Liens
- Create web APIs with ASP.NET Core
- Build web APIs with ASP.NET CoreBuild web APIs with ASP.NET Core
- Controller action return types in ASP.NET Core Web API
- StatusCodes
- Path
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(400)] [ProducesResponseType(500)] public IActionResult Post([FromBody] Item item) { if (item == null) return BadRequest(); if (!ModelState.IsValid) return BadRequest(ModelState); try { var createdItem = _itemRepository.Add(item); return Created("item", createdItem); 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é.