The service layer encapsulates all business logic and data access operations, providing a clean separation between controllers and the database.
Service Layer Pattern
Services follow an interface-based design for dependency injection and testability:
Service Registration
All services are registered in Startup.cs:76 using the transient lifetime:
public void ConfigureServices ( IServiceCollection services )
{
// AutoMapper
services . AddAutoMapper ( typeof ( Startup ));
// Business Services
services . AddTransient < ICategoryService , CategoryService >();
services . AddTransient < INacionLicitanteService , NacionLicitanteService >();
services . AddTransient < IBiddingParticipantService , BiddingParticipantService >();
services . AddTransient < IDocumentService , DocumentService >();
services . AddTransient < IBlogService , BlogService >();
services . AddTransient < IPQRSDService , PQRSDService >();
services . AddTransient < IBrigadeService , BrigadeService >();
services . AddTransient < IProductService , ProductService >();
services . AddTransient < IEmployeeService , EmployeeService >();
// Common Services
services . AddTransient < IUploadedFileIIS , UploadedFileIIS >();
services . AddTransient < IFormatStringUrl , FormatStringUrl >();
services . AddTransient < IEmailSendGrid , EmailSendGrid >();
}
Transient Lifetime : A new instance is created each time the service is requested. This is appropriate for lightweight, stateless services.
Core Service Pattern
Interface
Implementation
Controller Usage
Define a service interface with all operations: // services/ProductService.cs:15
public interface IProductService
{
Task < IEnumerable < Product >> GetAll ();
Task < ProductDto > Details ( int ? id );
Task < ProductDto > Details ( string urlProduct );
Task < ProductDto > Create ( ProductCreateDto model );
Task < Product > GetById ( int ? id );
Task DeleteConfirmed ( int id );
bool ProductExists ( int id );
bool DuplicaName ( string _stringName );
}
Benefits :
Enables dependency injection
Facilitates unit testing with mocks
Documents available operations
Supports multiple implementations
Implement the interface with actual business logic: // services/ProductService.cs:26
public class ProductService : IProductService
{
private readonly ApplicationDbContext _context ;
private readonly IMapper _mapper ;
private readonly IFormatStringUrl _formatStringUrl ;
public ProductService (
ApplicationDbContext context ,
IMapper mapper ,
IFormatStringUrl formatStringUrl )
{
_context = context ;
_mapper = mapper ;
_formatStringUrl = formatStringUrl ;
}
public async Task < IEnumerable < Product >> GetAll ()
{
return await _context
. Products
. OrderByDescending ( x => x . DateCreate )
. ToListAsync ();
}
public async Task < ProductDto > Details ( int ? id )
{
var product = await _context . Products
. FirstOrDefaultAsync ( m => m . ProductId == id );
return _mapper . Map < ProductDto >( product );
}
// ... more methods
}
Controllers inject and use services: // prjESPSantaFeAnt/Controllers/ProductsController.cs:11
public class ProductsController : Controller
{
private readonly IProductService _productService ;
public ProductsController ( IProductService productService )
{
_productService = productService ;
}
public async Task < IActionResult > Index ()
{
var products = from a in await _productService . GetAll ()
select new ModelViewProduct
{
ProductId = a . ProductId ,
Name = a . Name ,
UrlProduct = a . UrlProduct
};
return View ( products );
}
}
Service Examples
ProductService
Manages product/service catalog operations:
Full ProductService Implementation
// services/ProductService.cs:26
public class ProductService : IProductService
{
private readonly ApplicationDbContext _context ;
private readonly IMapper _mapper ;
private readonly IFormatStringUrl _formatStringUrl ;
public ProductService (
ApplicationDbContext context ,
IMapper mapper ,
IFormatStringUrl formatStringUrl )
{
_context = context ;
_mapper = mapper ;
_formatStringUrl = formatStringUrl ;
}
public async Task < ProductDto > Create ( ProductCreateDto model )
{
var dateCreate = DateTime . Now ;
var urlProduct = _formatStringUrl . FormatString ( model . Name );
var product = new Product
{
ProductId = model . ProductId ,
Name = model . Name ,
UrlProduct = urlProduct . Trim (),
Icono = model . Icono + ".svg" ,
Description = model . Description ,
DateCreate = dateCreate
};
await _context . AddAsync ( product );
await _context . SaveChangesAsync ();
return _mapper . Map < ProductDto >( product );
}
public async Task DeleteConfirmed ( int id )
{
_context . Remove ( new Product { ProductId = id });
await _context . SaveChangesAsync ();
}
public bool DuplicaName ( string stringName )
{
return _context . Products . Any ( e => e . Name == stringName );
}
}
Key Features :
URL slug generation using IFormatStringUrl
Duplicate name checking
AutoMapper for entity-to-DTO conversion
Async operations throughout
CategoryService
Manages service categories with file upload:
// services/CategoryService.cs:31
public class CategoryService : ICategoryService
{
private readonly ApplicationDbContext _context ;
private readonly IMapper _mapper ;
private readonly IFormatStringUrl _formatStringUrl ;
private readonly IUploadedFileIIS _uploadedFileIIS ;
private readonly string _account = "categories" ;
public async Task < CategoryDto > Create ( CategoryCreateDto model )
{
var dateCreate = DateTime . Now ;
var urlCategory = _formatStringUrl . FormatString ( model . NameCategory );
var coverPage = _uploadedFileIIS . UploadedFileImage (
model . CoverPage , _account );
var category = new Category
{
Id = model . Id ,
NameCategory = model . NameCategory . Trim (),
UrlCategory = urlCategory ,
Description = model . Description . Trim (),
CoverPage = coverPage ,
Statud = model . Statud ,
DateCreate = dateCreate
};
await _context . AddAsync ( category );
await _context . SaveChangesAsync ();
return _mapper . Map < CategoryDto >( category );
}
public async Task Edit ( int id , CategoryEditDto model )
{
var dateUpdate = DateTime . Now ;
var category = await _context . Categories . SingleAsync ( x => x . Id == id );
if ( model . CoverPage != null )
{
var searchCoverPage = await _context . Categories
. SingleAsync ( x => x . Id == id );
category . CoverPage = _uploadedFileIIS . UploadedFileImage (
searchCoverPage . CoverPage ,
model . CoverPage ,
_account ,
false );
}
category . Description = model . Description ;
category . Statud = model . Statud ;
category . DateUpdate = dateUpdate ;
await _context . SaveChangesAsync ();
}
}
Key Features :
File upload handling with IUploadedFileIIS
Image replacement during edits
URL slug generation for SEO
Automatic timestamp management
Common Services
Utility services provide cross-cutting functionality:
File Upload
URL Formatting
Email Service
// services/Commons/UploadedFileIIS.cs
public interface IUploadedFileIIS
{
string UploadedFileImage ( IFormFile file , string account );
string UploadedFileImage (
string oldFile ,
IFormFile newFile ,
string account ,
bool delete );
string UploadedFilePDF ( IFormFile file , string account );
void DeleteConfirmed ( string fileName , string account );
}
Responsibilities :
Upload files to IIS directory
Replace existing files
Delete files
Organize by account/folder
// services/Commons/FormatStringUrl.cs
public interface IFormatStringUrl
{
string FormatString ( string input );
}
Purpose : Convert titles to URL-friendly slugsExample:
Input: "Acueducto y Alcantarillado"
Output: "acueducto-y-alcantarillado"
// services/Commons/EmailSendGrid.cs
public interface IEmailSendGrid
{
Task < Response > SendEmail (
string email ,
string subject ,
string message );
}
Integration : Uses SendGrid API for email deliveryUse Cases :
PQRSD notifications
Account confirmations
Password resets
AutoMapper Integration
AutoMapper handles entity-to-DTO conversions:
// prjESPSantaFeAnt/Config/AutoMapperConfig.cs:11
public class AutoMapperConfig : Profile
{
public AutoMapperConfig ()
{
CreateMap < Master , NacionLicitanteDto >();
CreateMap < Master , BlogDto >();
CreateMap < Master , BrigadeDto >();
CreateMap < Category , CategoryDto >();
CreateMap < BiddingParticipant , BiddingParticipantDTO >();
CreateMap < PQRSD , PQRSDDto >();
CreateMap < Document , DocumentDTO >();
CreateMap < Product , ProductDto >();
CreateMap < Employee , EmployeeDto >();
}
}
Usage in Services :
// Convert entity to DTO
var productDto = _mapper . Map < ProductDto >( product );
// Convert collection
var productDtos = _mapper . Map < IEnumerable < ProductDto >>( products );
AutoMapper is registered in Startup.cs:74 with services.AddAutoMapper(typeof(Startup)), which scans for all Profile classes.
Service Best Practices
Async/Await Pattern
All database operations use async methods:
public async Task < IEnumerable < Product >> GetAll ()
{
return await _context
. Products
. OrderByDescending ( x => x . DateCreate )
. ToListAsync ();
}
Benefits :
Non-blocking I/O operations
Better scalability
Improved responsiveness
Dependency Injection
Services receive dependencies via constructor:
public CategoryService (
ApplicationDbContext context ,
IMapper mapper ,
IFormatStringUrl formatStringUrl ,
IUploadedFileIIS uploadedFileIIS )
{
_context = context ;
_mapper = mapper ;
_formatStringUrl = formatStringUrl ;
_uploadedFileIIS = uploadedFileIIS ;
}
Benefits :
Loose coupling
Testability with mocks
Single Responsibility Principle
DTO Pattern
Separate DTOs for different operations:
// Create DTO - limited fields
public class ProductCreateDto
{
public string Name { get ; set ; }
public string Icono { get ; set ; }
public string Description { get ; set ; }
}
// View DTO - all display fields
public class ProductDto
{
public int ProductId { get ; set ; }
public string Name { get ; set ; }
public string UrlProduct { get ; set ; }
public string Icono { get ; set ; }
public string Description { get ; set ; }
public DateTime DateCreate { get ; set ; }
}
Benefits :
Prevent over-posting attacks
Hide internal entity structure
Optimize data transfer
Error Handling
Services handle errors at appropriate levels:
public async Task < Product > GetById ( int ? id )
{
// Let EF Core throw if not found
return await _context . Products
. FirstOrDefaultAsync ( x => x . ProductId == id );
}
Controllers check for null and return appropriate HTTP responses:
public async Task < IActionResult > Details ( int ? id )
{
if ( id == null )
{
return NotFound ();
}
var product = await _productService . Details ( id );
if ( product == null )
{
return NotFound ();
}
return View ( product );
}
Service Composition
Services can depend on other services:
public class CategoryService : ICategoryService
{
private readonly ApplicationDbContext _context ;
private readonly IMapper _mapper ;
private readonly IFormatStringUrl _formatStringUrl ; // Common service
private readonly IUploadedFileIIS _uploadedFileIIS ; // Common service
// Both common services injected via DI
public CategoryService (
ApplicationDbContext context ,
IMapper mapper ,
IFormatStringUrl formatStringUrl ,
IUploadedFileIIS uploadedFileIIS )
{
_context = context ;
_mapper = mapper ;
_formatStringUrl = formatStringUrl ;
_uploadedFileIIS = uploadedFileIIS ;
}
}
Testing Services
Interface-based design enables easy unit testing:
// Mock the service in tests
var mockProductService = new Mock < IProductService >();
mockProductService
. Setup ( s => s . GetAll ())
. ReturnsAsync ( new List < Product > { /* test data */ });
// Inject mock into controller
var controller = new ProductsController ( mockProductService . Object );
Service Layer Benefits
Testability Interface-based design allows easy mocking and unit testing
Reusability Business logic can be reused across multiple controllers
Maintainability Centralized logic is easier to modify and debug
Separation of Concerns Clear boundaries between presentation and business logic
Next Steps
Authentication Learn about security and user management
API Reference Explore service implementations
Controllers See how controllers use services
Database Schema Understand the data layer