WebApi + ClientApp, GraphQL, Reflection

This commit is contained in:
Boris Milašinović
2026-05-06 20:55:05 +02:00
parent 8f7c704a90
commit 4fb3de19f6
196 changed files with 10395 additions and 0 deletions

View File

@@ -0,0 +1,37 @@
using Events.WebAPI.Contract.DTOs;
using Events.WebAPI.Contract.LookupQueries;
using Events.WebAPI.Handlers.EF.Data.Postgres;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace Events.WebAPI.Handlers.EF.QueryHandlers;
public class CountriesLookupQueryHandler : IRequestHandler<LookupCountryQuery, List<IdName<string>>>
{
private readonly EventsContext ctx;
public CountriesLookupQueryHandler(EventsContext ctx)
{
this.ctx = ctx;
}
public async Task<List<IdName<string>>> Handle(LookupCountryQuery request, CancellationToken cancellationToken)
{
var query = ctx.Countries.AsNoTracking();
if (!string.IsNullOrWhiteSpace(request.Text))
{
string text = request.Text.Trim();
query = query.Where(c => Microsoft.EntityFrameworkCore.EF.Functions.ILike(c.Name, $"%{text}%"));
}
return await query
.OrderBy(c => c.Name)
.Select(c => new IdName<string>
{
Id = c.Code,
Name = c.Name
})
.ToListAsync(cancellationToken);
}
}

View File

@@ -0,0 +1,17 @@
using AutoMapper;
using Events.WebAPI.Contract.DTOs;
using Events.WebAPI.Handlers.EF.Data.Postgres;
using Events.WebAPI.Handlers.EF.Models;
using Events.WebAPI.Handlers.EF.QueryHandlers.Generic;
using Microsoft.Extensions.Logging;
using Sieve.Services;
namespace Events.WebAPI.Handlers.EF.QueryHandlers;
public class EventsQueryHandler : GenericQueryHandler<EventDTO, Event, int>
{
public EventsQueryHandler(EventsContext ctx, ILogger<EventsQueryHandler> logger, IMapper mapper, ISieveProcessor sieveProcessor)
: base(ctx, logger, mapper, sieveProcessor)
{
}
}

View File

@@ -0,0 +1,97 @@
using AutoMapper;
using Events.WebAPI.Contract.DTOs;
using Events.WebAPI.Contract.Queries.Generic;
using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Sieve.Models;
using Sieve.Services;
namespace Events.WebAPI.Handlers.EF.QueryHandlers.Generic
{
public abstract class GenericQueryHandler<TDto, TDal, TPK> :
IRequestHandler<GetSingleItemQuery<TDto, TPK>, TDto?>,
IRequestHandler<GetCountQuery<TDto>, int>,
IRequestHandler<DoesItemExistsQuery<TDto, TPK>, bool>,
IRequestHandler<GetItemsQuery<TDto>, List<TDto>>
where TDal : class, IHasIdAsPK<TPK>
where TPK : IEquatable<TPK>
{
private readonly DbContext ctx;
protected readonly ILogger logger;
private readonly IMapper mapper;
private readonly ISieveProcessor sieveProcessor;
public GenericQueryHandler(DbContext ctx, ILogger logger, IMapper mapper, ISieveProcessor sieveProcessor)
{
this.ctx = ctx;
this.logger = logger;
this.mapper = mapper;
this.sieveProcessor = sieveProcessor;
}
public virtual async Task<int> Handle(GetCountQuery<TDto> request, CancellationToken cancellationToken)
{
var query = ctx.Set<TDal>().AsNoTracking();
IQueryable<TDto> projectedQuery = mapper.ProjectTo<TDto>(query);
SieveModel sieveModel = new SieveModel()
{
Filters = request.Filters,
};
var filteredQuery = sieveProcessor.Apply(sieveModel, projectedQuery, applyFiltering: true, applySorting: false, applyPagination: false);
int count = await filteredQuery.CountAsync(cancellationToken);
return count;
}
public virtual async Task<TDto?> Handle(GetSingleItemQuery<TDto, TPK> request, CancellationToken cancellationToken)
{
var query = ctx.Set<TDal>()
.AsNoTracking()
.Where(t => t.Id.Equals(request.Id));
IQueryable<TDto> projectedQuery = mapper.ProjectTo<TDto>(query);
var item = await projectedQuery.FirstOrDefaultAsync(cancellationToken);
return item;
}
public virtual async Task<bool> Handle(DoesItemExistsQuery<TDto, TPK> request, CancellationToken cancellationToken)
{
var query = ctx.Set<TDal>()
.AsNoTracking()
.Where(t => t.Id.Equals(request.Id));
bool exists = await query.AnyAsync(cancellationToken);
return exists;
}
public virtual async Task<List<TDto>> Handle(GetItemsQuery<TDto> request, CancellationToken cancellationToken)
{
var query = ctx.Set<TDal>().AsNoTracking();
IQueryable<TDto> projectedQuery = mapper.ProjectTo<TDto>(query);
SieveModel sieveModel = new SieveModel()
{
Filters = request.Filters,
Sorts = BuildSortExpression(request),
PageSize = request.PageSize,
Page = request.Page
};
var filteredQuery = sieveProcessor.Apply(sieveModel, projectedQuery, applyFiltering: true, applySorting: true, applyPagination: true);
var data = await filteredQuery.ToListAsync(cancellationToken);
return data;
}
private static string? BuildSortExpression(GetItemsQuery<TDto> request)
{
if (!string.IsNullOrWhiteSpace(request.Sort))
{
return request.Ascending ? request.Sort : "-" + request.Sort;
}
bool paginationRequested = request.Page.HasValue || request.PageSize.HasValue;
return paginationRequested ? nameof(IHasIdAsPK<int>.Id) : null;
}
}
}

View File

@@ -0,0 +1,48 @@
using Events.WebAPI.Contract.DTOs;
using Events.WebAPI.Contract.LookupQueries;
using Events.WebAPI.Handlers.EF.Data.Postgres;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace Events.WebAPI.Handlers.EF.QueryHandlers;
public class PeopleLookupQueryHandler : IRequestHandler<LookupPeopleQuery, List<IdName<int>>>
{
private readonly EventsContext ctx;
public PeopleLookupQueryHandler(EventsContext ctx)
{
this.ctx = ctx;
}
public async Task<List<IdName<int>>> Handle(LookupPeopleQuery request, CancellationToken cancellationToken)
{
var query = ctx.People.AsNoTracking();
if (!string.IsNullOrWhiteSpace(request.Text))
{
string text = request.Text.Trim();
query = query.Where(p =>
global::Microsoft.EntityFrameworkCore.EF.Functions.ILike(
p.FirstNameTranscription + " " + p.LastNameTranscription,
$"%{text}%"));
}
if (!string.IsNullOrWhiteSpace(request.CountryCode))
{
string countryCode = request.CountryCode.Trim();
query = query.Where(p => p.CountryCode == countryCode);
}
return await query
.OrderBy(p => p.FirstNameTranscription)
.ThenBy(p => p.LastNameTranscription)
.Select(p => new IdName<int>
{
Id = p.Id,
Name = p.FirstName + " " + p.LastName,
Description = p.FirstNameTranscription + " " + p.LastNameTranscription
})
.ToListAsync(cancellationToken);
}
}

View File

@@ -0,0 +1,17 @@
using AutoMapper;
using Events.WebAPI.Contract.DTOs;
using Events.WebAPI.Handlers.EF.Data.Postgres;
using Events.WebAPI.Handlers.EF.Models;
using Events.WebAPI.Handlers.EF.QueryHandlers.Generic;
using Microsoft.Extensions.Logging;
using Sieve.Services;
namespace Events.WebAPI.Handlers.EF.QueryHandlers;
public class PeopleQueryHandler : GenericQueryHandler<PersonDTO, Person, int>
{
public PeopleQueryHandler(EventsContext ctx, ILogger<PeopleQueryHandler> logger, IMapper mapper, ISieveProcessor sieveProcessor)
: base(ctx, logger, mapper, sieveProcessor)
{
}
}

View File

@@ -0,0 +1,17 @@
using AutoMapper;
using Events.WebAPI.Contract.DTOs;
using Events.WebAPI.Handlers.EF.Data.Postgres;
using Events.WebAPI.Handlers.EF.Models;
using Events.WebAPI.Handlers.EF.QueryHandlers.Generic;
using Microsoft.Extensions.Logging;
using Sieve.Services;
namespace Events.WebAPI.Handlers.EF.QueryHandlers;
public class RegistrationsQueryHandler : GenericQueryHandler<RegistrationDTO, Registration, int>
{
public RegistrationsQueryHandler(EventsContext ctx, ILogger<RegistrationsQueryHandler> logger, IMapper mapper, ISieveProcessor sieveProcessor)
: base(ctx, logger, mapper, sieveProcessor)
{
}
}

View File

@@ -0,0 +1,18 @@
using AutoMapper;
using Events.WebAPI.Contract.DTOs;
using Events.WebAPI.Handlers.EF.Data.Postgres;
using Events.WebAPI.Handlers.EF.Models;
using Events.WebAPI.Handlers.EF.QueryHandlers.Generic;
using Microsoft.Extensions.Logging;
using Sieve.Services;
namespace Events.WebAPI.Handlers.EF.QueryHandlers;
public class SportsQueryHandler : GenericQueryHandler<SportDTO, Sport, int>
{
public SportsQueryHandler(EventsContext ctx, ILogger<SportsQueryHandler> logger, IMapper mapper, ISieveProcessor sieveProcessor)
: base(ctx, logger, mapper, sieveProcessor)
{
}
}