RESTful Web API Development with [HttpGet] in C# – The Definitive Guide

RESTful Web API Development with [HttpGet] in C# – The Definitive Guide

Building RESTful Web APIs in C# with [HttpGet] – The Ultimate Guide

Building RESTful Web APIs in C# with [HttpGet] – The Ultimate Guide

1. Introduction to RESTful APIs

1.1 What Are RESTful APIs?

In the world of web development, RESTful APIs are the backbone of modern systems. They power everything from mobile apps and front-end interfaces to backend microservices. REST stands for Representational State Transfer, and it’s a design pattern used to structure communication between systems over HTTP.

RESTful APIs follow a simple principle—each HTTP method (GET, POST, PUT, DELETE) represents a specific type of action. GET requests retrieve data, POST creates, PUT updates, and DELETE removes resources. This clear mapping between HTTP methods and CRUD operations makes REST easy to implement and even easier to consume.

This guide dives deep into building RESTful APIs using [HttpGet] in C# and ASP.NET Core. From basic setup to advanced techniques like pagination, filtering, authentication, and performance optimizations—you’ll find everything you need here.

1.2 Benefits of REST and HTTP Verbs

  • Scalability: RESTful services can be cached, load-balanced, and scaled horizontally.
  • Simplicity: It uses standard HTTP methods, making it familiar and easy to test.
  • Statelessness: Each call from the client contains all the information needed.
  • Cacheability: GET methods, in particular, benefit from native browser or CDN caching.

1.3 Role of [HttpGet] in REST

The [HttpGet] attribute in ASP.NET Core Web APIs is used to mark a controller method that should respond to HTTP GET requests. These methods are designed to fetch data without modifying it. They are idempotent, meaning that multiple identical requests have the same effect as a single one.


[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase {
    
    [HttpGet]
    public IActionResult GetAllUsers() {
        return Ok(UserRepository.GetAll());
    }
}

This method maps directly to GET /api/users and returns all users. It’s clean, efficient, and highly readable for developers consuming your API.

2. Setting Up ASP.NET Core Web API Project

2.1 Installing .NET SDK

To start building Web APIs in C#, you need the .NET SDK installed. Head to the official .NET download page and install the latest LTS version. Once installed, open a terminal or command prompt to verify:


dotnet --version

This command outputs the installed SDK version. You’re ready to scaffold your API project.

2.2 Creating Your First API Project

Use the .NET CLI to create a new Web API project:


dotnet new webapi -n MyRestApi
cd MyRestApi

This command sets up a complete ASP.NET Core Web API project with WeatherForecast sample controller. Run it using:


dotnet run

Navigate to https://localhost:5001/swagger in your browser to see Swagger UI in action. Congratulations! Your API project is live.

2.3 Project Structure Overview

Your Web API project includes several files and folders:

  • Controllers: Contains controller classes like WeatherForecastController.cs
  • Program.cs: Main entry point and DI registration
  • appsettings.json: App configuration (DB, logging, etc.)
  • Properties/launchSettings.json: Environment settings for dev mode

Keeping this structure clean is essential for scaling your API. You’ll likely add Models, Services, Repositories, and DTOs as the project grows.

3. Routing & Controllers

3.1 What Is Routing?

Routing is how ASP.NET Core maps incoming HTTP requests to specific controller actions. It uses templates like [Route("api/[controller]")] to define these patterns. By default, the route uses the controller name minus the "Controller" suffix.


[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase {
    [HttpGet]
    public IActionResult GetProducts() {
        return Ok(productService.GetAll());
    }
}

3.2 Attribute Routing with [HttpGet]

Attribute routing allows you to fine-tune the endpoint URL. Instead of relying on conventions, you define the full route for a method.


[HttpGet("latest")]
public IActionResult GetLatestProducts() {
    return Ok(productService.GetLatest());
}

This will respond to requests like GET /api/products/latest. It’s simple, clean, and flexible.

3.3 Default Routing Conventions

If no custom route is defined, ASP.NET Core uses the method name and HTTP verb. So [HttpGet] without parameters will map to GET /api/controllername. Adding a route like [HttpGet("{id}")] maps to GET /api/controllername/5.


[HttpGet("{id}")]
public IActionResult GetById(int id) {
    var item = productService.GetById(id);
    if (item == null) return NotFound();
    return Ok(item);
}

4. Building GET Endpoints

4.1 Simple GET Returning Strings

Here’s your "Hello World" of GET endpoints. It returns plain text when you hit the URL.


[HttpGet("ping")]
public string Ping() {
    return "API is alive!";
}

Open your browser and visit /api/yourcontroller/ping. This verifies your API is working and responding to GET requests.

4.2 GET Returning JSON Models

Let’s return structured data. Create a model:


public class Product {
    public int Id { get; set; }
    public string Name { get; set; }
}

Now update your controller to return a list of products:


[HttpGet("list")]
public IActionResult GetAll() {
    var products = new List<Product> {
        new Product { Id = 1, Name = "Book" },
        new Product { Id = 2, Name = "Pen" }
    };
    return Ok(products);
}

Accessing /api/products/list now returns JSON formatted data—perfect for frontend clients.

4.3 GET with URL Parameters (id)


[HttpGet("{id}")]
public IActionResult GetProduct(int id) {
    var product = productService.GetById(id);
    if (product == null) return NotFound();
    return Ok(product);
}

This matches routes like /api/products/5 and returns the corresponding product or a 404 if not found.

4.4 Query String Parameters

Query strings allow for filtering, pagination, and more:


[HttpGet("search")]
public IActionResult Search([FromQuery] string keyword) {
    var results = productService.Search(keyword);
    return Ok(results);
}

Now you can call /api/products/search?keyword=book and get results based on the user’s input.

5. CRUD Operations Overview

5.1 HTTP Verbs and REST Design

In RESTful APIs, each HTTP verb has a distinct purpose. Understanding their role helps you follow best practices and deliver a predictable, scalable API experience:

  • GET – Retrieve a resource or collection of resources (read-only)
  • POST – Create a new resource
  • PUT – Update an existing resource
  • DELETE – Remove a resource

Using these correctly ensures idempotency, improves client compatibility, and simplifies API documentation. [HttpGet] specifically handles safe, read-only operations that don’t modify server state.

5.2 Setting Up POST, PUT, DELETE

Here’s how you might define the other CRUD methods alongside your GET endpoint:


[HttpPost]
public IActionResult CreateProduct([FromBody] Product product) {
    var created = productService.Create(product);
    return CreatedAtAction(nameof(GetProduct), new { id = created.Id }, created);
}

[HttpPut("{id}")]
public IActionResult UpdateProduct(int id, [FromBody] Product product) {
    var updated = productService.Update(id, product);
    if (updated == null) return NotFound();
    return Ok(updated);
}

[HttpDelete("{id}")]
public IActionResult DeleteProduct(int id) {
    bool success = productService.Delete(id);
    return success ? NoContent() : NotFound();
}

Note how each method returns a proper HTTP status code—this is important for client clarity and API consistency.

5.3 Combining GET with Other Operations

It’s common to combine multiple GET endpoints alongside POST/PUT/DELETE. For instance, you may have:

  • GET /api/products – List all products
  • GET /api/products/5 – Get a single product by ID
  • GET /api/products/search?query=pen – Filter products

Each endpoint should be clear in purpose and return predictable results. Using descriptive routes and query parameters helps frontend and third-party developers consume your API with confidence.

---

6. Data Persistence & EF Core

6.1 Setting Up DbContext

Entity Framework Core (EF Core) is Microsoft’s ORM that helps you interact with databases using C#. First, install the EF Core packages in your API project:


dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Tools

Create your model and DbContext:


public class Product {
    public int Id { get; set; }
    public string Name { get; set; }
}

public class AppDbContext : DbContext {
    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }

    public DbSet<Product> Products { get; set; }
}

6.2 Migrations and Database Setup

Now that your context is ready, register it in Program.cs:


builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("Default")));

Then create your migration and apply it:


dotnet ef migrations add InitialCreate
dotnet ef database update

This creates the SQL schema based on your models—zero manual scripting needed.

6.3 Using DbContext in GET Methods

Inject your DbContext in a controller to fetch records with [HttpGet]:


[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase {
    private readonly AppDbContext _context;

    public ProductsController(AppDbContext context) {
        _context = context;
    }

    [HttpGet]
    public async Task<IActionResult> GetProducts() {
        var products = await _context.Products.ToListAsync();
        return Ok(products);
    }
}

This is a fully connected Web API backed by a real SQL database. You can now retrieve live data directly from the database using GET requests.

---

7. Querying and Filtering Data

7.1 Pagination

Pagination allows you to split results into pages to reduce payload size and improve client-side performance. Here’s a basic paginated GET endpoint:


[HttpGet("paged")]
public async Task<IActionResult> GetPaged([FromQuery] int page = 1, [FromQuery] int size = 10) {
    var products = await _context.Products
        .Skip((page - 1) * size)
        .Take(size)
        .ToListAsync();

    return Ok(products);
}

Call it using: /api/products/paged?page=2&size=5

7.2 Filtering and Search Parameters

GET endpoints can support multiple filters using query strings:


[HttpGet("filter")]
public async Task<IActionResult> FilterProducts([FromQuery] string name) {
    var filtered = await _context.Products
        .Where(p => p.Name.Contains(name))
        .ToListAsync();

    return Ok(filtered);
}

This approach supports frontend autocomplete, dynamic search, and lightweight filtering.

7.3 Sorting Results

Support sorting by name or ID using a query parameter:


[HttpGet("sort")]
public async Task<IActionResult> SortProducts([FromQuery] string sortBy = "name") {
    var products = sortBy == "id"
        ? await _context.Products.OrderBy(p => p.Id).ToListAsync()
        : await _context.Products.OrderBy(p => p.Name).ToListAsync();

    return Ok(products);
}

These small enhancements greatly improve the developer experience for consumers of your API.

8. Handling HTTP Responses

8.1 Returning IActionResult

In ASP.NET Core Web APIs, returning IActionResult gives you flexibility to return different HTTP status codes depending on your business logic. This approach is preferred over returning raw data types like string or List<T> for public-facing APIs.


[HttpGet("{id}")]
public IActionResult GetProductById(int id) {
    var product = _context.Products.Find(id);
    if (product == null) return NotFound();
    return Ok(product);
}

This code returns a 200 OK if the product is found and a 404 Not Found otherwise. Clients and search engines understand and respect these standardized responses, which improves UX and SEO crawlability.

8.2 Returning Ok, NotFound, BadRequest

Use these built-in methods for clear communication with clients:

  • Ok(): Return data successfully
  • NotFound(): Indicate missing data
  • BadRequest(): Client error, usually due to invalid input
  • CreatedAtAction(): Return new resource URI after creation

[HttpGet("{name}")]
public IActionResult FindProductByName(string name) {
    if (string.IsNullOrWhiteSpace(name))
        return BadRequest("Name cannot be empty.");

    var product = _context.Products.FirstOrDefault(p => p.Name == name);
    return product == null ? NotFound() : Ok(product);
}

8.3 Exception Filters and Error Handling

To handle errors globally, create a custom exception filter:


public class GlobalExceptionFilter : IExceptionFilter {
    private readonly ILogger<GlobalExceptionFilter> _logger;

    public GlobalExceptionFilter(ILogger<GlobalExceptionFilter> logger) {
        _logger = logger;
    }

    public void OnException(ExceptionContext context) {
        _logger.LogError(context.Exception, "Unhandled exception occurred");
        context.Result = new ObjectResult("Something went wrong")
        {
            StatusCode = 500
        };
    }
}

Register it in Program.cs:


builder.Services.AddControllers(options =>
    options.Filters.Add<GlobalExceptionFilter>());

This ensures consistent error handling across all GET and other endpoints—great for AdSense-safe sites where errors must be gracefully handled.

---

9. Versioning Web APIs

9.1 Why Version Your API?

Over time, your API evolves. To maintain backward compatibility, it's best to version your APIs. This helps consumers migrate slowly without breaking existing apps. GET endpoints often change as new query parameters or models are introduced.

9.2 URL vs Header Versioning

There are two popular approaches:

  • URL Versioning: /api/v1/products, /api/v2/products
  • Header Versioning: Pass version info via custom HTTP headers

URL versioning is more SEO-friendly and easier to document in Swagger.

9.3 Decorating Endpoints with Version Metadata

Install the versioning NuGet package:


dotnet add package Microsoft.AspNetCore.Mvc.Versioning

In Program.cs:


builder.Services.AddApiVersioning(options => {
    options.AssumeDefaultVersionWhenUnspecified = true;
    options.DefaultApiVersion = new ApiVersion(1, 0);
    options.ReportApiVersions = true;
});

Then decorate your controller:


[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/products")]
[ApiController]
public class ProductsV1Controller : ControllerBase { ... }
---

10. Security & CORS

10.1 Enabling CORS for GET Requests

Cross-Origin Resource Sharing (CORS) is vital for allowing web apps from other domains to access your API. This is particularly relevant for frontend SPAs hosted on different domains.


builder.Services.AddCors(options => {
    options.AddPolicy("AllowAll", builder =>
        builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader());
});

app.UseCors("AllowAll");

You can also restrict CORS to only GET:


.AllowAnyOrigin().WithMethods("GET").AllowAnyHeader();

10.2 API Key Authentication

Secure your public-facing GET endpoints using simple API key headers:


public class ApiKeyMiddleware {
    private readonly RequestDelegate _next;
    private const string APIKEY = "X-API-KEY";

    public ApiKeyMiddleware(RequestDelegate next) {
        _next = next;
    }

    public async Task Invoke(HttpContext context) {
        if (!context.Request.Headers.TryGetValue(APIKEY, out var key) ||
            key != "secret-api-key") {
            context.Response.StatusCode = 401;
            await context.Response.WriteAsync("API Key is missing or invalid");
            return;
        }

        await _next(context);
    }
}

10.3 JWT Tokens and Claim-Based Auth

For full user-based security, implement JWT authentication. Install the necessary packages and configure JWT in Program.cs. Then decorate your controller with:


[Authorize]
[HttpGet]
public IActionResult GetSecureData() {
    return Ok("This data requires valid token.");
}

Google AdSense prefers well-secured APIs with proper access control, so adding auth is a plus.

---

11. Documentation with Swagger

11.1 Adding Swashbuckle

Swagger auto-generates API documentation, making your endpoints discoverable by other devs and SEO crawlers. Install the package:


dotnet add package Swashbuckle.AspNetCore

11.2 Customizing GET Endpoint Docs

Enable Swagger in Program.cs:


builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

In app.UseSwagger() and app.UseSwaggerUI(), you expose your API via the browser at /swagger.

Add XML comments above each method:


/// <summary>Returns a list of all products</summary>
[HttpGet]
public IActionResult GetAllProducts() {
    return Ok(productService.GetAll());
}

11.3 Testing APIs via Swagger UI

Swagger UI lets you test GET, POST, PUT, DELETE endpoints directly from your browser. This improves the discoverability of your API and builds trust with AdSense reviewers and users alike.

12. Testing GET Endpoints

12.1 Unit Testing Controllers

Unit testing ensures your GET endpoints behave correctly under different conditions. Use xUnit or NUnit to test controller logic in isolation:


public class ProductsControllerTests {
    [Fact]
    public void GetAll_ReturnsOk_WithListOfProducts() {
        var fakeProducts = new List<Product> {
            new() { Id = 1, Name = "Keyboard" },
            new() { Id = 2, Name = "Mouse" }
        };

        var mockContext = new Mock<AppDbContext>();
        // Setup mocking logic here...

        var controller = new ProductsController(mockContext.Object);
        var result = controller.GetAll();

        Assert.IsType<OkObjectResult>(result);
    }
}

Unit tests run fast and give you confidence your controller logic is solid.

12.2 Integration Testing with WebApplicationFactory

For real HTTP simulation, use integration tests to validate entire request/response flows:


public class ApiIntegrationTests : IClassFixture<WebApplicationFactory<Program>> {
    private readonly HttpClient _client;

    public ApiIntegrationTests(WebApplicationFactory<Program> factory) {
        _client = factory.CreateClient();
    }

    [Fact]
    public async Task Get_Endpoint_Returns_200() {
        var response = await _client.GetAsync("/api/products");
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
    }
}

Use this to test security, headers, routing, and real database scenarios.

12.3 Mocking DbContext

To isolate tests from the database, mock your DbContext with InMemory or a mocking framework:


services.AddDbContext<AppDbContext>(options =>
    options.UseInMemoryDatabase("TestDb"));

This avoids SQL Server dependencies and allows full test coverage for GET logic.

---

13. Performance Optimizations

13.1 Response Caching GET Results

GET endpoints can be cached since they are read-only. Use ResponseCache to avoid unnecessary processing:


[HttpGet]
[ResponseCache(Duration = 60)]
public IActionResult GetCachedData() {
    return Ok(dataService.GetTopItems());
}

Cache headers help CDNs and browsers serve repeated requests instantly.

13.2 Efficient Serialization Options

Use System.Text.Json with optimized settings in Program.cs:


builder.Services.AddControllers().AddJsonOptions(opts => {
    opts.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
    opts.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
});

This reduces payload size and accelerates GET responses.

13.3 Using IHttpClientFactory for Outbound GET Calls

If your API calls external services, avoid new HttpClient(). Register and inject IHttpClientFactory:


builder.Services.AddHttpClient();

Then use it in services:


public class ExternalApiService {
    private readonly HttpClient _http;

    public ExternalApiService(HttpClient http) {
        _http = http;
    }

    public async Task<string> FetchData() {
        var response = await _http.GetAsync("https://api.example.com/data");
        return await response.Content.ReadAsStringAsync();
    }
}
---

14. Monitoring & Logging

14.1 Built‑in Logging in .NET

ASP.NET Core includes logging via ILogger<T>. Inject it into controllers and services:


private readonly ILogger<ProductsController> _logger;

public ProductsController(ILogger<ProductsController> logger) {
    _logger = logger;
}

[HttpGet]
public IActionResult GetAll() {
    _logger.LogInformation("Fetching all products");
    return Ok(_context.Products.ToList());
}

14.2 Logging GET Requests and Responses

Use middleware to log each request and response body:


app.Use(async (context, next) => {
    var logger = context.RequestServices.GetRequiredService<ILogger<Program>>();
    logger.LogInformation("Request: {Method} {Path}", context.Request.Method, context.Request.Path);
    await next();
});

14.3 Health Checks and Telemetry

Add HealthChecks to monitor API uptime for bots and load balancers:


builder.Services.AddHealthChecks();
app.MapHealthChecks("/health");
---

15. Best Practices & Anti-Patterns

15.1 Keeping GET Endpoints Idempotent

Never perform writes, updates, or deletions inside [HttpGet] endpoints. GETs should never change data. This helps with SEO, AdSense trust, and browser cache reliability.

15.2 Avoiding “Fat Controllers”

Move business logic into services. Controllers should only handle routing and minimal orchestration.

15.3 Proper Use of DTOs and View Models

Don’t expose your database models directly. Create DTOs to shape responses and hide sensitive fields:


public class ProductDto {
    public int Id { get; set; }
    public string Name { get; set; }
}
---

16. Frequently Asked Questions (FAQs)

1. What is the difference between GET and POST in C# APIs?

GET is used to retrieve data without changing server state, while POST is used to create or modify data. GET can be cached and bookmarked—POST cannot.

2. Can I cache GET responses?

Yes. Use ResponseCache or external caching layers like Redis or CDN edge caching for performance and SEO boosts.

3. How do I handle 404 errors in GET endpoints?

Return NotFound() when data isn’t available. Avoid throwing exceptions or returning null directly.

4. How to secure GET endpoints?

Use API keys or JWT token authorization. CORS policies also help limit access from unauthorized domains.

5. What tools help test GET APIs?

Use Swagger UI, Postman, curl, or integration test frameworks like xUnit + TestServer. Always test real-world scenarios.

---

17. Conclusion

RESTful APIs are the foundation of modern app development, and [HttpGet] endpoints are the most commonly used. Whether you're building a public API, an internal service, or a backend for a frontend app, the principles and techniques in this guide will help you design fast, scalable, and secure GET operations in ASP.NET Core.

By following best practices like versioning, filtering, caching, and DTO separation, your API will not only be developer-friendly—it will also meet the standards Google AdSense and search engines look for when evaluating quality content and crawlable web structure.

---

Please don’t forget to leave a review.

Post a Comment

Post a Comment (0)

Previous Post Next Post

ads

ads

Update cookies preferences