← Back to Articles         
Calling Microservices in ASP.NET Core

Calling Microservices in ASP.NET Core

Published on 28 Sep 2026     7 min read Microservices
Microservice Design

How Do You Call One ASP.NET Core Microservice from Another?

In ASP.NET Core, one microservice commonly calls another using HttpClient for synchronous HTTP/REST communication.

For production applications, IHttpClientFactory—usually via a typed client or named client—is preferred over repeatedly creating new HttpClient().


1. What Does It Mean?

Suppose we have two microservices:

Order Service
Product Service

When the Order Service needs product information, it can call the Product Service's REST API:

Order Service
     │
     │ HTTP GET /api/products/101
     ▼
Product Service
     │
     │ Product JSON
     ▼
Order Service

This is synchronous service-to-service communication because Order Service waits for Product Service's response.


2. Why Do We Need It?

Each microservice should focus on its own business capability and generally own its own data.

For example:

Product Service
    └── Product data

Order Service
    └── Order data

Order Service should generally not directly query Product Service's database.

Instead:

Order Service
      │
      │ REST API
      ▼
Product Service
      │
      ▼
Product Database

This preserves service boundaries and allows the Product Service to control its own data and business rules.


3. Purpose

Service-to-service API calls allow one microservice to:

  • Retrieve information from another service
  • Request another service to perform an operation
  • Maintain clear service boundaries
  • Avoid direct database sharing
  • Keep services independently maintainable and deployable

4. Simple ASP.NET Core Example

Assume we have:

ProductService → https://localhost:7001
OrderService   → https://localhost:7002

Order Service needs product information from Product Service.


Step 1 — Product Microservice

Create a simple endpoint in ProductService:

[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    [HttpGet("{id}")]
    public IActionResult GetProduct(int id)
    {
        var product = new
        {
            Id = id,
            Name = "Laptop",
            Price = 75000
        };

        return Ok(product);
    }
}

Calling:

GET https://localhost:7001/api/products/101

returns:

{
  "id": 101,
  "name": "Laptop",
  "price": 75000
}

5. Create DTO in Order Service

The Order Service needs a model representing the response:

public class ProductDto
{
    public int Id { get; set; }

    public string Name { get; set; } = string.Empty;

    public decimal Price { get; set; }
}

6. Create Product API Client

In OrderService:

public class ProductApiClient
{
    private readonly HttpClient _httpClient;

    public ProductApiClient(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    public async Task<ProductDto?> GetProductAsync(int id)
    {
        return await _httpClient
            .GetFromJsonAsync<ProductDto>($"api/products/{id}");
    }
}

Here, HttpClient is injected through Dependency Injection.


7. Register HttpClient

In Order Service's Program.cs:

builder.Services.AddHttpClient<ProductApiClient>(client =>
{
    client.BaseAddress =
        new Uri("https://localhost:7001/");
});

This is called a typed HttpClient.

ASP.NET Core creates and injects the configured HttpClient into:

ProductApiClient

So you don't manually create:

new HttpClient();

for every request.


8. Call Product Service from Order Service

Now inject ProductApiClient into the Order controller:

[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
    private readonly ProductApiClient _productApiClient;

    public OrdersController(ProductApiClient productApiClient)
    {
        _productApiClient = productApiClient;
    }

    [HttpPost("{productId}")]
    public async Task<IActionResult> CreateOrder(int productId)
    {
        var product =
            await _productApiClient.GetProductAsync(productId);

        if (product == null)
        {
            return BadRequest("Product not found");
        }

        return Ok(new
        {
            Message = "Order created successfully",
            Product = product.Name,
            Price = product.Price
        });
    }
}

9. Complete Flow

When the client calls:

POST https://localhost:7002/api/orders/101

the flow becomes:

Client
  │
  │ POST /api/orders/101
  ▼
Order Service
  │
  │ ProductApiClient
  │
  │ GET /api/products/101
  ▼
Product Service
  │
  │ Product Details
  ▼
Order Service
  │
  │ Create Order
  ▼
Client

The important point is:

Client
   ↓
Order Service
   ↓ HttpClient
Product Service
   ↓
Product DB

Order Service doesn't directly access Product Service's database.


10. Why Use IHttpClientFactory?

You might see code like:

var client = new HttpClient();

Although this can work for simple cases, repeatedly creating and disposing HttpClient is not the recommended application pattern.

ASP.NET Core provides IHttpClientFactory.

When using:

builder.Services.AddHttpClient<ProductApiClient>();

ASP.NET Core uses the HttpClientFactory infrastructure.

It provides:

  • Centralized configuration
  • Proper underlying handler management
  • Logging integration
  • DNS change handling
  • Easier resilience configuration
  • Named and typed clients

11. Don't Hard-Code the URL

For demonstration, we used:

new Uri("https://localhost:7001/")

In a real application, avoid hard-coding service URLs.

appsettings.json

{
  "Services": {
    "ProductService": "https://localhost:7001/"
  }
}

Then:

builder.Services.AddHttpClient<ProductApiClient>((serviceProvider, client) =>
{
    var configuration =
        serviceProvider.GetRequiredService<IConfiguration>();

    client.BaseAddress = new Uri(
        configuration["Services:ProductService"]!);
});

Now different environments can provide different addresses:

Development
    ↓
https://localhost:7001

SIT
    ↓
https://sit-product-service/

UAT
    ↓
https://uat-product-service/

Production
    ↓
https://product-service/

12. What About Authentication?

If Product Service is protected, Order Service may also need to send authentication credentials.

For example:

Authorization: Bearer <access-token>

or another service-to-service authentication mechanism.

Conceptually:

Order Service
     │
     │ Authorization
     │ Bearer Token
     ▼
Product Service

The exact mechanism depends on the environment—for example OAuth 2.0 client credentials, managed identities, API gateway policies, or platform-specific workload identity.


13. What If Product Service Is Down?

This is an important microservices problem.

Suppose:

Order Service
     │
     X
Product Service DOWN

The Order Service should not wait indefinitely.

Production systems commonly consider:

  • Timeout
  • Retry for suitable transient failures
  • Circuit Breaker
  • Fallback, where appropriate
  • Logging
  • Distributed tracing

Be careful with retries for operations such as:

POST /api/payments

Blind retries could potentially execute an operation more than once unless the API is designed with idempotency.


14. Advantages

Clear Service Boundaries

Each microservice exposes its functionality through a contract.

Independent Databases

Order Service   → Order DB

Product Service → Product DB

One service doesn't need direct access to another service's tables.

Independent Deployment

Product Service can be deployed separately from Order Service, provided its API contract remains compatible.

Standard Communication

HTTP/REST works across different technologies.

For example:

.NET Service
     ↓ REST
Java Service
     ↓ REST
Node.js Service

15. Disadvantages

Network Latency

A local method call:

GetProduct();

is much cheaper than a network call:

Order Service → Network → Product Service

Service Availability Dependency

If Product Service is unavailable, Order Service may not be able to complete operations that require it.

Cascading Failures

Avoid excessive chains such as:

A → B → C → D → E

A slow downstream service can affect the entire chain.

More Operational Complexity

You need to consider:

  • Service discovery
  • Authentication
  • Timeouts
  • Retries
  • Circuit breakers
  • Logging
  • Distributed tracing

16. REST vs Messaging

Calling another service with HttpClient is appropriate when an immediate response is required.

Order Service
      │
      │ Need answer now
      ▼
Product Service

        REST / HTTP

If the other service doesn't need to respond immediately, messaging may be more appropriate:

Order Service
      │
      │ OrderCreated
      ▼
Message Broker
      │
      ├── Notification Service
      └── Analytics Service

        Messaging

So:

Need immediate response → REST/gRPC
Can process asynchronously → Messaging


17. Key Points

  • Microservices can call each other using REST APIs over HTTP/HTTPS.
  • In ASP.NET Core, use HttpClient.
  • Prefer configuring it through IHttpClientFactory.
  • Typed clients are a clean approach for encapsulating calls to a particular service.
  • Don't repeatedly create new HttpClient() for application service calls.
  • Don't hard-code service URLs in production code.
  • Avoid accessing another microservice's database directly.
  • Configure timeouts and resilience policies for remote calls.
  • Secure service-to-service communication appropriately.
  • Avoid long chains of synchronous calls.
  • Use messaging when an immediate response isn't required.

18. Interview Questions & Answers

Q1. How do you call one ASP.NET Core Microservice from another?

Answer:
For synchronous REST communication, we commonly use HttpClient configured through IHttpClientFactory. The calling microservice sends an HTTP request to the target microservice and processes its response.

Q2. Why use IHttpClientFactory?

Answer:
It provides centralized HttpClient configuration, manages underlying HTTP handlers correctly, supports logging and DNS changes, and integrates well with resilience mechanisms.

Q3. What is a typed HttpClient?

Answer:
A typed client is a class that encapsulates communication with a particular external service.

For example:

ProductApiClient

can contain all calls from Order Service to Product Service.

Q4. Should Order Service directly access Product Service's database?

Answer:
Generally, no. Product Service should own its data. Order Service should communicate through the Product Service's API or an appropriate messaging contract.

Q5. What if the called microservice is unavailable?

Answer:
The calling service should use appropriate resilience techniques such as timeouts, retries for transient failures, circuit breakers, and fallbacks where applicable, together with logging and monitoring.

Q6. Should service URLs be hard-coded?

Answer:
No. Service addresses should normally come from configuration, environment variables, service discovery, DNS, or the deployment platform.

Q7. When should messaging be used instead of HttpClient?

Answer:
Use messaging when the operation can be processed asynchronously and the caller doesn't require an immediate result.

Interview One-Line Answer

In ASP.NET Core, one microservice commonly calls another synchronously using HttpClient configured through IHttpClientFactory; for asynchronous communication, use a message broker such as RabbitMQ, Kafka, or Azure Service Bus.

← Back to Articles