اللي بيعمل Consume للـ API هو الـ Front-end أو الموبايل فعشان كدا وارد لما يعمل call للـ API يحصل Errors كتير سواء من عنده أو من عندي

Error Types

عندنا 5 أنواع ممكن يحصلوا، وكل نوع بيبقا له شكل معين في الـ Response زي الـ Not Found ودا بيبقا مختلف الـ Response بتاعه عن باقي الـ Errors فأنا محتاج أعمل Standard لشكل الـ Response بتاع كل أنواع الـ Errors بس قبل كدا محتاجين نشوف الـ Default response بتاع كل Error هنعمل Controller عبارة عن Dummy controller عشان نشوف بس كل أنواع الـ Errors


في فولدر الـ Controller نعمل BuggyController هنخليه يتكلم مع الـ DbContext على طول لأن كل هدفي تعليمي

public class BuggyController : BaseApiController
{
	private readonly StoreContext _dbContext;
	public BuggyController(StoreContext dbContext)
	{
		_dbContext = dbContext;
	}
 
	[HttpGet("notfound")] // GET : api/buggy/notfound
	public ActionResult GetNotFoundRequest()
	{
		var product = _dbContext.Products.Find(100);
		if (product is null)
		{
			return NotFound();
		}
		return Ok(product);
	}
 
	[HttpGet("servererror")] // GET : api/buggy/servererror
	public ActionResult GetServerError()
	{
		var product = _dbContext.Products.Find(100);
		var productToReturn = product.ToString();
		// Will Throw Exception [NullReferenceException]
 
		return Ok(productToReturn);
	}
 
	[HttpGet("badrequest")] // GET : api/buggy/badrequest
	public ActionResult GetBadRequest()
	{
		return BadRequest();
	}
 
	[HttpGet("badrequest/{id}")] // GET : api/buggy/badrequest/notInt
	public ActionResult GetBadRequest(int id) // Validation Error
	{
		return Ok();
	}
 
	// NotFound Endpoint
}

الشكل الـ Default بتاع كل Error

// NOT Found (404)
{
    "type": "https://tools.ietf.org/html/rfc9110#section-15.5.5",
    "title": "Not Found",
    "status": 404,
    "traceId": "00-76bba64d3e55df4dd64f7c8db088a6df-0f2b2b5e7087ebd7-00"
}
 
// Exception | Server Error (500)
// We need to make standard for errors response
// شكلها يخض
System.NullReferenceException: Object reference not set to an instance of an object.
 
// Bad Request (400)
{
    "type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
    "title": "Bad Request",
    "status": 400,
    "traceId": "00-0a727c192bc97e7340ff5140fdce3cc2-4455f52914570684-00"
}
 
// Validation Error (400)
{
    "type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
    "title": "One or more validation errors occurred.",
    "status": 400,
    "errors": {
        "id": [
            "The value 'five' is not valid."
        ]
    },
    "traceId": "00-6b47962809501efc9d3bd3bfb3dfe8f7-a321133256e6d6f6-00"
}
 
// Not Found Endpoint (404)
// With no body

أنا محتاج أعمل شكل Standard للـ Response بتاع الـ Errors دي

Handling

Not Found, Bad Request and Unauthorized

هنعمل Handle للـ Error عشان نخليه يرجع اللي انا عايزه

[HttpGet("notfound")] // GET : api/buggy/notfound
public ActionResult GetNotFoundRequest()
{
    var product = _dbContext.Products.Find(100);
    if (product is null)
    {
        return NotFound(new {Message = "Not Found", StatusCode = 404});
    }
    return Ok(product);
}
 
// Return
{
    "message": "Not Found",
    "statusCode": 404
}

بس مينفعش أستخدم Object من Anonymous Type لأني هستخدمه كتير جدًا مش مرة واحدة بس انما لو هستخدمه مرة واحدة هعمله Anonymous عادي


نروح في الـ API ونعمل Folder ونسميه Errors وبعدين نعمل Class نسميه ApiResponse

public class ApiResponse
{
	public int StatusCode {get; set;}
	public string Message {get; set;}
 
	public ApiResponse(int statuseCode, string? message = null)
	{
		StatusCode = statusCode;
		Message = message ?? GetDefaultMessageForStatusCode(statusCode);
	}
 
	private string? GetDefaultMessageForStatusCode(int statusCode)
	{
		// Switch Expression
		return statusCode switch
		{
			400 => "Bad Request", // A bad request, you have made
			401 => "Unauthorized", // Authorized, you are not
			404 => "Resource was not found",
			500 => "Errors are the path to the dark side. Errors lead to anger. Anger leads to hate. Hate leads to career shift",
			_ => null
		}
	}
}
 
[HttpGet("notfound")] // GET : api/buggy/notfound
public ActionResult GetNotFoundRequest()
{
    var product = _dbContext.Products.Find(100);
    if (product is null)
    {
        return NotFound(new ApiResponse(404)); 
        // We can not send message like that
    }
    return Ok(product);
}
 

ممكن نغير أي Not Found أو Bad Request أو Unauthorized دلوقتي ونحطلها الـ Object دي

Validation Error

  • مش بيدخل في الـ Endpoint أصلًا فكدا انا محتاج أوصل للـ Default Configuration اللي بيرجعلي الـ Response بالشكل دا

  • بيحصل بسبب ان مثلًا الـ Endpoint بياخد Int وانا باعته String

  • كل Parameters الـ Endpoints بنسميه Model ولو هو غلط بيبقا Not Valid

  • عايزه يرجع زي الـ Not Found بس هزود حاجة كمان وهي Array of string بتاع الـ Errors

  • نروح في Folder الـ Errors ونعمل Class نسميه ApiValidationErrorResponse

public class ApiValidationErrorResponse : ApiResponse
{
	public IEnumerable<string> Errors {get; set;}
 
	// Because it's a bad request error
	public ApiValidationErrorResponse() : base(400)
	{
		Errors = new List<string>();
	}
}

نروح في الـ Main في الـ Services ونحط دي

builder.Services.Configure<ApiBehaviorOptions>(options =>
{
	options.InvalidModelStateResponseFactory = (actionContext) =>
	{
		var errros = actionContext.ModelState.Where(P => P.Value.Errors.Count() > 0)
					.SelectMany(P => P.Value.Errors)
					.Sekect(E => E.ErrorMessage);
					.ToArray();
		var validationErrorResponse = new ApiValidationErrorResponse()
		{
			Errors = errors
		};
 
		return new BadRequestObjectResult(validationErrorResponse);
	};
});

Server Error

Wrong Routing

عايزين نعمل Endpoint في حالة ان الـ Routing غلط يوديني على الـ Endpoint دي

  • فهروح أعمل ليها Controller واضيف الـ Endpoint دي
  • بعد كدا اضيف Middle ware اسمها app.UseStatusCodePagesWithRedirects("/Errors/{0}) ودي من خلالها بقوله في حالة ان لو الـ Endpoint مش موجودة روح دايركت على الـ Endpoint دي وبديله الكود 0 بشكل Dynamic لان انا هناك عندي براميتر واحد وهو اسمه code فهنا ترتيبه صفر عشان كدا قولتله 0
  • بس الأفضل منها في الحالة دي هي UseStatusCodePagesWithReExecute("/Errors/0") والاختلاف بينهم في الـ Behavior فقط وهو هنا بيفضل على نفس الـ Endpoint اللي فيها المشكلة وبيقولك في رسالة ان في Error 404 بس كدا
[Route("api/[controller]")]
[ApiController]
[ApiExplorerSettings(IgnoreApi = true)]
// ضفت الاتربيوت دا عشان خاطر الاسوجر تول تشتغل عشان هي لازم يكون في فيرب عشان تشتغل
public class ErrorsContoller : ControllerBase
{
	public ActionResult Error(int code)
	{
		return NotFound(new ApiResponse(code));
	}
}
 
// Program
app.UseStatusecodePagesWithReExecute("/Errors/{0}");
// before this
app.UseHttpRedirection();

Improving Swagger Documentation