ASP.NET Core API returns HTML instead of JSON – Complete Fix | FreeLearning365.com

ASP.NET Core API returns HTML instead of JSON – Complete Fix | FreeLearning365.com
FreeLearning365.com 📡 API
🚨 Content-Type Mismatch

ASP.NET Core API returns HTML instead of JSON
Complete Fix Guide

“Your API returns HTML when you expected JSON” — This is a common but frustrating issue when building REST APIs with ASP.NET Core. This guide explains why it happens and provides concrete, production-ready solutions for every scenario.

🔍 1. Introduction

You’re building a clean REST API with ASP.NET Core. You test it in Postman or your Angular app, and instead of receiving JSON, you get a full HTML page. The response Content-Type is text/html instead of application/json. This can be confusing and break your client-side application.

This issue usually stems from how ASP.NET Core handles content negotiation, exception handling, or misconfigured formatters. The good news: it’s almost always a straightforward fix. This guide walks you through every possible cause and solution.

💡 Key insight: ASP.NET Core uses content negotiation to choose the response format based on the Accept header. If the client doesn't send application/json or the server can't serialize the response, it may fall back to HTML.

2. Common Causes

  • Missing or incorrect Accept header – The client doesn't request JSON.
  • Unhandled exceptions – The Developer Exception Page returns HTML (in development).
  • Invalid model state[ApiController] returns a validation error page.
  • Wrong return type – Returning an object that can't be serialized to JSON.
  • XML formatter – The client requests XML, and the server returns it.
  • Misconfigured JsonSerializerSettings – Circular references or recursion.
  • Custom middleware – Middleware intercepts and returns HTML.

📨 3. Accept Header

The Accept header tells the server what content types the client can handle. If your client sends Accept: text/html or omits the header, ASP.NET Core may return HTML (if the XML/JSON formatters can't handle the response).

✅ Correct Client Request (Angular / HttpClient)

// Angular – automatically sets Accept: application/json this.http.get('/api/products').subscribe(...); // But you can also set it explicitly const headers = new HttpHeaders().set('Accept', 'application/json'); this.http.get('/api/products', { headers }).subscribe(...);

✅ Postman / cURL

curl -H "Accept: application/json" https://localhost:5001/api/products

🔧 Force JSON Response on the Server

If you want the API to always return JSON regardless of the Accept header, you can configure the server to ignore Accept and use JSON as the default.

// Program.cs builder.Services.AddControllers(options => { options.Filters.Add(new ProducesAttribute("application/json")); options.RespectBrowserAcceptHeader = false; // Ignore Accept header }) .AddJsonOptions(options => { options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; });

🐞 4. Exception Handling

When an unhandled exception occurs in development, ASP.NET Core returns the Developer Exception Page (HTML) by default. In production, it returns a static error page or the configured exception handler. This is a common source of HTML responses in APIs.

✅ Global Exception Middleware to Return JSON

// Program.cs app.UseExceptionHandler(errorApp => { errorApp.Run(async context => { context.Response.StatusCode = 500; context.Response.ContentType = "application/json"; var exception = context.Features.Get<IExceptionHandlerFeature>()?.Error; var response = new { status = 500, title = "Internal Server Error", detail = exception?.Message ?? "An error occurred." }; await context.Response.WriteAsync(JsonSerializer.Serialize(response)); }); });

✅ Using ProblemDetails (RFC 7807)

// Program.cs app.UseExceptionHandler(new ExceptionHandlerOptions { ExceptionHandler = async context => { var exception = context.Features.Get<IExceptionHandlerFeature>()?.Error; var details = new ProblemDetails { Status = 500, Title = "Internal Server Error", Detail = exception?.Message, Instance = context.Request.Path }; context.Response.StatusCode = 500; context.Response.ContentType = "application/problem+json"; await context.Response.WriteAsync(JsonSerializer.Serialize(details)); } });
✅ Best practice: Always return structured JSON errors from APIs, even when exceptions occur. Use ProblemDetails for standards compliance.

🧩 5. JSON Formatters

ASP.NET Core supports multiple output formatters (JSON, XML, etc.). If the client's Accept header matches XML first, the API may return XML. Here's how to control formatters.

✅ Remove XML Formatter

If you want to only support JSON, remove the XML formatter:

builder.Services.AddControllers(options => { options.OutputFormatters.RemoveType<XmlDataContractSerializerOutputFormatter>(); options.OutputFormatters.RemoveType<XmlSerializerOutputFormatter>(); options.FormatterMappings.SetMediaTypeMappingForFormat("json", MediaTypeHeaderValue.Parse("application/json")); }) .AddJsonOptions(options => { options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; });

✅ Use AddNewtonsoftJson (if needed)

If you have legacy code that depends on Newtonsoft.Json, you can use it instead of System.Text.Json:

builder.Services.AddControllers().AddNewtonsoftJson(options => { options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; });

📤 6. Return Types

If your action method returns an object that cannot be serialized to JSON (e.g., Stream, FileStream, or a type with circular references), ASP.NET Core may fall back to HTML.

✅ Use IActionResult with Ok()

[HttpGet("{id}")] public IActionResult GetProduct(int id) { var product = _context.Products.Find(id); if (product == null) { return NotFound(new { message = "Product not found" }); } return Ok(product); // Returns JSON }

✅ Use ActionResult<T>

[HttpGet] public ActionResult<List<Product>> GetProducts() { return Ok(_context.Products.ToList()); }

✅ Handle Circular References

// In Program.cs with System.Text.Json builder.Services.AddControllers().AddJsonOptions(options => { options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles; }); // Or with Newtonsoft options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;

📋 7. Invalid Model State

When using [ApiController], ASP.NET Core automatically validates model state and returns a 400 Bad Request with validation errors. If the client doesn't accept JSON, it may return HTML.

// Ensure the client Accept header is set to application/json // Or force the response to be JSON builder.Services.AddControllers() .ConfigureApiBehaviorOptions(options => { options.InvalidModelStateResponseFactory = context => { var errors = context.ModelState.Values .SelectMany(v => v.Errors) .Select(e => e.ErrorMessage); return new BadRequestObjectResult(new { status = 400, title = "Validation Error", errors = errors }); }; });

This custom factory ensures that validation errors always return JSON.

🐞 8. Debugging

  • Check the request Accept header: In DevTools or Postman, inspect the request headers.
  • Inspect the response Content-Type: It should be application/json.
  • Enable logging: Add builder.Logging.AddConsole() to see what's happening.
  • Use the Produces attribute: Add [Produces("application/json")] to your controller or action.
  • Check for middleware order: Middleware order matters. UseExceptionHandler and UseRouting should be correctly placed.
  • Test with cURL: curl -H "Accept: application/json" https://localhost:5001/api/test to isolate client issues.
  • Use return Json(...): For older or custom code, you can use return Json(new { property = "value" }).
❓ Frequently Asked Questions

🏆 10. Best Practices

  • Always set the Accept header – Clients should explicitly request application/json.
  • Use [Produces("application/json")] to force JSON responses on your controllers.
  • Implement global exception handling that returns structured JSON errors.
  • Use ProblemDetails for standardized error responses (RFC 7807).
  • Remove XML formatters if your API only serves JSON.
  • Configure JSON options globally for consistent serialization (camelCase, reference handling).
  • Use ActionResult<T> for strongly typed responses.
  • Log all unhandled exceptions for monitoring and debugging.
  • Test your API with different Accept headers to ensure it behaves correctly.
✅ Final thought: An API that returns HTML instead of JSON is usually a sign of misconfigured content negotiation or exception handling. With the fixes in this guide, you'll ensure your API always returns clean, consistent JSON responses.

Post a Comment

0 Comments