« Asp.net core web api » : différence entre les versions

De Banane Atomic
Aller à la navigationAller à la recherche
Ligne 130 : Ligne 130 :
<kode lang='cs'>
<kode lang='cs'>
[HttpDelete("{id:int}")]
[HttpDelete("{id:int}")]
[ProducesResponseType(204)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(400)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(404)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public IActionResult Delete(int id)
public IActionResult Delete(int id)
{
{

Version du 25 janvier 2020 à 22:37

Liens

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

Csharp.svg
// [HttpGet("[action]")]
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable<Item>))]
public IActionResult Get() => Ok(_repository.GetAllItems());

[HttpGet("{id:int}")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Item))]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
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

Csharp.svg
[HttpPost]
[ProducesResponseType(StatusCodes.Status201Created, Type = typeof(Item))]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
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

Csharp.svg
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
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(item);
    return NoContent();
}

DELETE

Cs.svg
[HttpDelete("{id:int}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
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

Cs.svg
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

Cs.svg
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

Cs.svg
public async Task UpdateItemAsync(Item item)
{
    var itemJson =
        new StringContent(JsonSerializer.Serialize(item), Encoding.UTF8, "application/json");

    await _httpClient.PutAsync("api/item", itemJson);
}

DELETE

Cs.svg
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; }
}
Csharp.svg
[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();
Cs.svg
[HttpGet("/error/{code:int}")]
public string Error(int code)
{
    return $"Error: {code}";
}

Erreurs

Unsupported Media Type with Postman

  • Body → raw = json à envoyer
  • Headers → Content-Type = application/json

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é.