Get List Example

Overview

This document demonstrates the complete Get List flow using the GetShippingDetails feature from the EBusiness application. The flow starts with a GET request to the controller and uses Query projects instead of Handlers in the DomainHandler section for data retrieval. This is similar to Get Paged List but returns a simple IEnumerable<T> without pagination.

Complete Flow Architecture

GET Request → Controller → Service → Query Handler → RESTClient → External System

Response ← Controller ← Service ← Query Handler ← RESTClient ← External System

Detailed Flow Breakdown

1. GET Request

2. Controller (API Entry Point)

3. Service Layer (Business Orchestration)

4. Query Handler (RESTClient Integration)
   ├── Internal DTO → Request DTO Mapping
   ├── RESTClient Call to External System
   └── Response DTO → Internal DTO Mapping

5. Response (Simple List)

Step-by-Step Implementation

1. API Controller - The Entry Point

File: ShippingController_GetShippingDetails.cs

[HttpGet()]
[Route("GetShippingDetails")]
[ProducesResponseType(typeof(IEnumerable<GetShippingDetailsDto>), 200)]
public async Task<IActionResult> GetShippingDetails([FromQuery]GetShippingDetailsParams parameters)
{
    return RunQueryListService<GetShippingDetailsParams, GetShippingDetailsDto>(
                parameters, _processShippingService.GetShippingDetails);
}

What Happens:

  • HTTP Method: GET /api/Shipping/GetShippingDetails

  • Input: GetShippingDetailsParams from query parameters

  • Action: Calls the service layer to process the query

  • Response: HTTP 200 OK with IEnumerable<GetShippingDetailsDto>

2. Service Layer - Business Orchestration

File: ProcessShippingService_GetShippingDetails.cs

public IEnumerable<GetShippingDetailsDto> GetShippingDetails(GetShippingDetailsParams @params)
{
    return _flexHost.GetFlexiQuery<GetShippingDetails>().AssignParameters(@params).Fetch();
}

What Happens:

  • Query Resolution: Gets the FlexiQuery instance for GetShippingDetails

  • Parameter Assignment: Assigns query parameters to the query handler

  • Query Execution: Calls the Fetch() method to execute the query

  • Result: Returns simple list of shipping details for lookup

3. Query Handler - RESTClient Integration

File: GetShippingDetails.cs

public class GetShippingDetails : FlexiQueryEnumerableBridge<Shipping, GetShippingDetailsDto>
{
    protected readonly ILogger<GetShippingDetails> _logger;
    protected GetShippingDetailsParams _params;
    protected readonly ShippingRESTClient _restClient;

    public GetShippingDetails(ILogger<GetShippingDetails> logger, ShippingRESTClient restClient)
    {
        _logger = logger;
        _restClient = restClient;
    }

    public virtual GetShippingDetails AssignParameters(GetShippingDetailsParams @params)
    {
        _params = @params;
        return this;
    }

    public override IEnumerable<GetShippingDetailsDto> Fetch()
    {
        try
        {
            // Convert internal parameters to Request DTO
            GetShippingDetailsRequestDto requestParams = new GetShippingDetailsRequestDto
            {
                SearchTerm = _params.SearchTerm,
                OrderId = _params.OrderId,
                Status = _params.Status?.ToString(),
                FromDate = _params.FromDate,
                ToDate = _params.ToDate,
                Limit = _params.Limit
            };

            // Call external REST service
            var response = _restClient.GetShippingDetails(requestParams).Result;

            if (response.IsSuccessStatusCode)
            {
                var responseContent = response.Content.ReadAsStringAsync().Result;
                var responseDto = JsonConvert.DeserializeObject<GetShippingDetailsResponseDto>(responseContent);
                
                // Convert Response DTO to Internal DTO
                var result = responseDto.ShippingDetails?.Select(x => new GetShippingDetailsDto
                {
                    Id = x.Id,
                    OrderId = x.OrderId,
                    ShippingAddress = x.ShippingAddress,
                    TrackingNumber = x.TrackingNumber,
                    Status = ParseShippingStatus(x.Status),
                    StatusDescription = x.StatusDescription,
                    TotalWeight = x.TotalWeight,
                    CreatedDate = x.CreatedDate,
                    IsActive = x.IsActive
                }).ToList() ?? new List<GetShippingDetailsDto>();

                _logger.LogDebug("Shipping details retrieved successfully. Count: {Count}", result.Count);
                return result;
            }
            else
            {
                _logger.LogWarning("Failed to retrieve shipping details. Status: {StatusCode}", response.StatusCode);
                return new List<GetShippingDetailsDto>();
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error retrieving shipping details");
            return new List<GetShippingDetailsDto>();
        }
    }

    private ShippingStatus ParseShippingStatus(string status)
    {
        return status?.ToLower() switch
        {
            "created" => ShippingStatus.Created,
            "in_transit" => ShippingStatus.InTransit,
            "delivered" => ShippingStatus.Delivered,
            "cancelled" => ShippingStatus.Cancelled,
            _ => ShippingStatus.Unknown
        };
    }
}

What Happens:

  • Parameter Assignment: Stores query parameters for use in RESTClient calls

  • Request Mapping: Converts internal parameters to Request DTO

  • RESTClient Call: Calls external shipping service via RESTClient

  • Response Mapping: Converts Response DTO to Internal DTO list

  • Error Handling: Handles success/failure responses from external system

  • Data Transformation: Applies business logic during mapping

4. Query Parameters - Input DTO

File: GetShippingDetailsParams.cs

public class GetShippingDetailsParams : DtoBridge
{
    // Add custom filtering parameters here
    public string SearchTerm { get; set; }
    public string OrderId { get; set; }
    public ShippingStatus? Status { get; set; }
    public DateTime? FromDate { get; set; }
    public DateTime? ToDate { get; set; }
    public int? Limit { get; set; }
}

What Happens:

  • Custom Filters: Allows filtering by search term, order ID, status, date range

  • Limit Support: Optional limit for lookup results

  • Query Parameters: Automatically bound from URL query string

5. Output DTO - Data Transfer Object

File: GetShippingDetailsDto.cs

public partial class GetShippingDetailsDto : DtoBridge 
{
    public string Id { get; set; }
    public string OrderId { get; set; }
    public string ShippingAddress { get; set; }
    public string TrackingNumber { get; set; }
    public ShippingStatus Status { get; set; }
    public string StatusDescription { get; set; }
    public decimal TotalWeight { get; set; }
    public DateTime CreatedDate { get; set; }
    public bool IsActive { get; set; }
}

What Happens:

  • Lookup Data: Contains essential fields for dropdown/lookup scenarios

  • Minimal Structure: Only includes necessary fields for selection

  • Performance: Lightweight DTO for fast loading

6. AutoMapper Configuration - Data Transformation

File: GetShippingDetailsMapperConfiguration.cs

public partial class GetShippingDetailsMapperConfiguration : FlexMapperProfile
{
    public GetShippingDetailsMapperConfiguration() : base()
    {
        CreateMap<Shipping, GetShippingDetailsDto>()
            .ForMember(d => d.StatusDescription, opt => opt.MapFrom(s => s.Status.ToString()))
            .ForMember(d => d.IsActive, opt => opt.MapFrom(s => !s.IsSoftDeleted));
    }
}

What Happens:

  • Entity to DTO Mapping: Maps domain entities to DTOs

  • Custom Mappings: Handles complex property transformations

  • Lookup Optimization: Maps only necessary fields for lookup scenarios

Key Differences from Get Paged List

Get List vs Get Paged List Characteristics

Aspect
Get List
Get Paged List

Return Type

IEnumerable<T>

FlexiPagedList<T>

Pagination

❌ No pagination

✅ Built-in pagination

Use Case

Lookup/Dropdown

Data grids/tables

Performance

Fast for small datasets

Optimized for large datasets

Memory Usage

Lower (no pagination metadata)

Higher (includes pagination info)

Controller Method

RunQueryListService

RunQueryPagedService

Query Base

FlexiQueryEnumerableBridge

FlexiQueryPagedListBridge

Get List-Specific Features

  1. Simple List: Returns basic IEnumerable<T> without pagination

  2. Lookup Optimized: Designed for dropdown/lookup scenarios

  3. Lightweight: Minimal data transfer for performance

  4. No Pagination: All results returned in single response

  5. Fast Loading: Optimized for quick data retrieval

Common Use Cases

  • Dropdown Lists: Populate dropdown controls

  • Lookup Tables: Search and select scenarios

  • Reference Data: Load reference data for forms

  • Auto-complete: Provide suggestions for input fields

  • Quick Filters: Fast filtering options

Flow Summary

Synchronous Flow (Data Retrieval)

  1. GET Request → Controller receives request with query parameters

  2. Service Processing → Business orchestration and query resolution

  3. Query Handler → Database query building and execution

  4. AutoMapper → Entity-to-DTO transformation

  5. Response → HTTP 200 OK with simple list

No Asynchronous Flow

  • No Events: Query operations don't publish events

  • No Subscribers: No side effects or event processing

  • Immediate Response: Data is returned immediately

Query Building Patterns

Basic Query Building

protected override IQueryable<T> Build<T>()
{
    _repoFactory.Init(_params);
    IQueryable<T> query = _repoFactory.GetRepo().FindAll<T>();
    
    // Add filtering logic here
    if (!string.IsNullOrEmpty(_params.SearchTerm))
    {
        query = query.Where(s => s.ShippingAddress.Contains(_params.SearchTerm) || 
                                s.TrackingNumber.Contains(_params.SearchTerm));
    }
    
    if (!string.IsNullOrEmpty(_params.OrderId))
    {
        query = query.Where(s => s.OrderId == _params.OrderId);
    }
    
    if (_params.Status.HasValue)
    {
        query = query.Where(s => s.Status == _params.Status.Value);
    }
    
    // Apply limit if specified
    if (_params.Limit.HasValue)
    {
        query = query.Take(_params.Limit.Value);
    }
    
    return query;
}

Advanced Query Building

protected override IQueryable<T> Build<T>()
{
    _repoFactory.Init(_params);
    IQueryable<T> query = _repoFactory.GetRepo().FindAll<T>();
    
    // Exclude soft deleted items
    query = query.Where(s => !s.IsSoftDeleted);
    
    // Search functionality
    if (!string.IsNullOrEmpty(_params.SearchTerm))
    {
        var searchTerm = _params.SearchTerm.ToLower();
        query = query.Where(s => s.ShippingAddress.ToLower().Contains(searchTerm) || 
                                s.TrackingNumber.ToLower().Contains(searchTerm) ||
                                s.OrderId.ToLower().Contains(searchTerm));
    }
    
    // Order ID filtering
    if (!string.IsNullOrEmpty(_params.OrderId))
    {
        query = query.Where(s => s.OrderId == _params.OrderId);
    }
    
    // Status filtering
    if (_params.Status.HasValue)
    {
        query = query.Where(s => s.Status == _params.Status.Value);
    }
    
    // Date range filtering
    if (_params.FromDate.HasValue)
    {
        query = query.Where(s => s.CreatedDate >= _params.FromDate.Value);
    }
    
    if (_params.ToDate.HasValue)
    {
        query = query.Where(s => s.CreatedDate <= _params.ToDate.Value);
    }
    
    // Sorting for consistent results
    query = query.OrderByDescending(s => s.CreatedDate);
    
    // Apply limit for performance
    if (_params.Limit.HasValue)
    {
        query = query.Take(_params.Limit.Value);
    }
    else
    {
        // Default limit for lookup scenarios
        query = query.Take(100);
    }
    
    return query;
}

Performance Considerations

Optimization Strategies

  1. Limit Results: Always apply reasonable limits for lookup scenarios

  2. Selective Fields: Only return necessary fields in DTO

  3. Indexing: Ensure proper database indexes on filter fields

  4. Caching: Consider caching for frequently accessed lookup data

  5. Lazy Loading: Use lazy loading for related entities when appropriate

When to Use Get List vs Get Paged List

Scenario
Use Get List
Use Get Paged List

Dropdown Population

✅ Yes

❌ No

Lookup Tables

✅ Yes

❌ No

Auto-complete

✅ Yes

❌ No

Data Grids

❌ No

✅ Yes

Large Datasets

❌ No

✅ Yes

Admin Interfaces

❌ No

✅ Yes

Reporting

❌ No

✅ Yes

Key Benefits

  • Performance: Fast loading for small datasets

  • Simplicity: Simple list without pagination complexity

  • Lookup Optimized: Perfect for dropdown/lookup scenarios

  • Lightweight: Minimal data transfer

  • Type Safety: Strongly typed DTOs and parameters

  • AutoMapper: Automatic entity-to-DTO mapping

  • No Side Effects: Read-only operations

  • Testable: Each component can be tested independently

  • Maintainable: Clear separation of concerns


This GetShippingDetails example demonstrates how FlexBase enables clean, maintainable, and scalable list operations optimized for lookup and dropdown scenarios! 🚀

Last updated