« Asp.net core web api » : différence entre les versions
De Banane Atomic
Aller à la navigationAller à la recherche
(→DELETE) |
(→GET) |
||
Ligne 30 : | Ligne 30 : | ||
[HttpGet] | [HttpGet] | ||
[ProducesResponseType(200, Type = typeof(IEnumerable<Item>))] | [ProducesResponseType(200, Type = typeof(IEnumerable<Item>))] | ||
public IActionResult Get() | public IActionResult Get() => Ok(_repository.GetAllItems()); | ||
[HttpGet("{id:int}")] | [HttpGet("{id:int}")] | ||
Ligne 43 : | Ligne 40 : | ||
try | try | ||
{ | { | ||
var item = _repository. | var item = _repository.Get(id); | ||
if (item | if (item == null) | ||
return NotFound(); // status 404 Not Found | return NotFound(); // status 404 Not Found | ||
return Ok(item); // status 200 OK | |||
} | } | ||
catch (Exception ex) | catch (Exception ex) |
Version du 15 janvier 2020 à 13:10
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() => Ok(_repository.GetAllItems()); [HttpGet("{id:int}")] [ProducesResponseType(200, Type = typeof(Item))] [ProducesResponseType(404)] [ProducesResponseType(500)] public IActionResult Get(int id) { try { var item = _repository.Get(id); if (item == null) return NotFound(); // status 404 Not Found return Ok(item); // status 200 OK } 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(400)] [ProducesResponseType(404)] public IActionResult Put([FromBody] Item item) { if (item == null) return BadRequest(); if (!ModelState.IsValid) return BadRequest(ModelState); var itemToUpdate = _itemRepository.Get(item.Id); if (itemToUpdate == null) return NotFound(); // status 404 Not Found _itemRepository.Update(itemToUpdate); return NoContent(); } |
DELETE
[HttpDelete("{id:int}")] [ProducesResponseType(204)] [ProducesResponseType(400)] [ProducesResponseType(404)] public IActionResult Delete(int id) { if (id <= 0) return BadRequest(); var itemToDelete = _itemRepository.Get(id); if (itemToDelete == null) return NotFound(); _itemRepository.Remove(id); return NoContent(); } |
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é.