CQRS - artemovsergey/ASP GitHub Wiki

Request

public record AddUserRequest(User user) : IRequest<AddUserRequest.Response>
{
    public const string RouteTemplate = "api/users";
    public record Response(int userId);
}

public class AddUserRequestValidator : AbstractValidator<AddUserRequest>
{
    public AddUserRequestValidator()
    {
        RuleFor(x => x.user).SetValidator(new UserValidator());
    }
}

Handler

public class AddUserHandler : IRequestHandler<AddUserRequest,AddUserRequest.Response>
{
    private readonly HttpClient _httpClient;
    private readonly string BaseUrl = "https://localhost:7214";
    public AddUserHandler(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }
    public async Task<AddUserRequest.Response> Handle(AddUserRequest request, CancellationToken cancellationToken)
    {
        Console.WriteLine("Работает метод Handle из Handler");
        var response = await _httpClient.PostAsJsonAsync<AddUserRequest>($"{BaseUrl}/{AddUserRequest.RouteTemplate}", request, cancellationToken);
        if (response.IsSuccessStatusCode)
        {
            var userId = await response.Content.ReadFromJsonAsync<int>(cancellationToken:cancellationToken);
            return new AddUserRequest.Response(userId);
        }
        else
        {
            return new AddUserRequest.Response(-1);
        }
    }
}

Behaviors

public class LoggingBehavior<TRequest> : IRequestPreProcessor<TRequest>
{
    private readonly ILogger<TRequest> _logger;
    
    public async Task Process(TRequest request,  CancellationToken cancellationToken)
    {
        var requestName = typeof(TRequest).Name;
        _logger.LogInformation("Request: {request}", request);
    }
}

internal class PerformanceBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
    private readonly ILogger<PerformanceBehavior<TRequest, TResponse>> _logger;

    public PerformanceBehavior(ILogger<PerformanceBehavior<TRequest, TResponse>> logger)
    {
        _logger = logger;
    }
    
    public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
    {
        _logger.LogWarning("PerfomanceBehavior");
        
        var result = await next();
        return result;
    }
}

public class UnhandledExceptionBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
    private readonly ILogger<TRequest> _logger;
    
    public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
    {
        try { return await next(); }
        catch (Exception ex)
        {
            var requestName = typeof(TRequest).Name;
            _logger.LogError(ex, "Request: UnhandledException for Request {Name} {@Request}", requestName, request);
            throw;
        }
    }
}

public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : IRequest<TResponse>
{
    private readonly IEnumerable<IValidator<TRequest>> _validators;
    
    public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators) { _validators = validators; }
    
    public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
    {
        if (!_validators.Any()) return await next();
        
        var context = new  ValidationContext<TRequest>(request);
        
        var validationResults = await Task.WhenAll(_validators.Select(v => v.ValidateAsync(context, cancellationToken)));
        
        var failures = validationResults.SelectMany(r => r.Errors).Where(f => f != null).ToList();
        
        return await next();
    }
}

Handlers

public class AddRepositoryHandler : IRequestHandler<AddRepositoryRequest, AddRepositoryRequest.Response>
{
    private readonly ProjectStoreContext _db;
    private readonly ILogger<AddRepositoryHandler> _log;
    public AddRepositoryHandler(ProjectStoreContext db, ILogger<AddRepositoryHandler> log)
    {
        _db = db;
        _log = log;
    }
    
    public async Task<AddRepositoryRequest.Response> Handle(AddRepositoryRequest request, CancellationToken cancellationToken)
    {
        _log.LogWarning("Создание объекта репозитория");

        try
        {
            _db.Repositories.Add(request.Repository);
            await _db.SaveChangesAsync();
            _log.LogInformation($"Репозиторий создан!"); 
        }
        catch (NpgsqlException e)
        {
            _log.LogError($"NpgsqlException: {e.InnerException}"); 
        }
        catch (Exception e)
        {
            _log.LogError($"Exception: {e.InnerException}");
        }
        
        //Unit.Value
        return new AddRepositoryRequest.Response(new Repository());
        
    }
}
public class RepositoryHandler : IRequestHandler<RepositoryRequest, RepositoryRequest.Response>
{
    private readonly ProjectStoreContext _db;
    private readonly ILogger<RepositoryHandler> _log;
    public RepositoryHandler(ProjectStoreContext db, ILogger<RepositoryHandler> log)
    {
        _db = db;
        _log = log;
    }

    public async Task<RepositoryRequest.Response> Handle(RepositoryRequest request, CancellationToken cancellationToken)
    {
        
        if (!string.IsNullOrEmpty(request.sortColumn) && IsValidProperty(request.sortColumn))
        {
            request = request with
            {
                sortOrder = !string.IsNullOrEmpty(request.sortOrder) && request.sortOrder.ToUpper() == "ASC"
                    ? "ASC"
                    : "DESC"
            };
        }
        
        IQueryable<Repository> newsList = _db.Repositories;

        if (!string.IsNullOrEmpty(request.filterColumn)
            && !string.IsNullOrEmpty(request.filterQuery)
            && IsValidProperty(request.filterColumn))
        {
            //users = users.Where(u => $"{filterColumn}".Contains($"{filterQuery})"));
            newsList = newsList.Where($"{request.filterColumn}.Contains(@0)", request.filterQuery);

            Console.WriteLine($"Фильтрация: {newsList.Count()}");

            // Если в базе данных нет записей, то поиск по API Github и сохранение в базу
            if (newsList.Count() == 0)
            {
                var githubpublicapi = $"https://api.github.com/search/repositories?q={request.filterQuery}";
                
                HttpClient client = new();
                client.DefaultRequestHeaders.Add("User-Agent", "YourAppName/1.0");
                HttpResponseMessage response = await client.GetAsync(githubpublicapi);
                
                string responseBody = await response.Content.ReadAsStringAsync();
                
                var jsonObject = JObject.Parse(responseBody);
                JToken items = jsonObject["items"];
                
                // Todo Настроить hash для уникальности
                foreach (JToken item in items)
                {
                    Console.WriteLine(item["name"]);
                    Console.WriteLine("--------------------------");
                    
                    var repo = new Repository
                    {
                        Name = item["name"].ToString(),
                        Owner = item["owner"]["login"].ToString(),
                        Stargazers = item["stargazers_count"].Value<int>(),
                        Watchers = item["watchers_count"].Value<int>(),
                        Url = item["html_url"].ToString()
                    };
                    _db.Repositories.Add(repo);
                    await _db.SaveChangesAsync();
                }
                
            }
            
            
        }
        
        
        var respositories = await newsList.OrderBy($"{request.sortColumn} {request.sortOrder}")
            .Skip(request.pageIndex * request.pageSize)
            .Take(request.pageSize)
            .ToListAsync();
        
        var apiresult = new ApiResult<Repository>((List<Repository>)respositories,
            _db.Repositories.Count(),
            request.pageIndex,
            request.pageSize,
            request.sortColumn,
            request.sortOrder,
            request.filterColumn,
            request.filterQuery);
        
        return new RepositoryRequest.Response(apiresult);
    }
    
    public static bool IsValidProperty(string propertyName,
        bool throwExceptionIfNotFound = true)
    {
        var prop = typeof(Repository).GetProperty(
            propertyName,

            BindingFlags.IgnoreCase |
            BindingFlags.Public |
            BindingFlags.Instance);
        if (prop == null && throwExceptionIfNotFound)
            throw new NotSupportedException(
                string.Format($"ERROR: Property '{propertyName}' does not exist.")
            );
        return prop != null;
    }
}
public class DeleteRepositoryHandler : IRequestHandler<DeleteRepositoryRequest, DeleteRepositoryRequest.Response>
{
    private readonly ProjectStoreContext _db;
    private readonly ILogger<DeleteRepositoryHandler> _log;
    public DeleteRepositoryHandler(ProjectStoreContext db, ILogger<DeleteRepositoryHandler> log)
    {
        _db = db;
        _log = log;
    }
    
    public async Task<DeleteRepositoryRequest.Response> Handle(DeleteRepositoryRequest request, CancellationToken cancellationToken)
    {
        try
        {
            _db.Remove(request.Repository);
            await _db.SaveChangesAsync();
            _log.LogInformation($"Репозиторий с Id = {request.Repository.Id} удален!" );
        }
        catch (NpgsqlException e)
        {
            _log.LogError($"NpgsqlException: {e.InnerException}");
        }
        catch (Exception e)
        {
            _log.LogError($"Exception: {e.InnerException}");
        }

        return new DeleteRepositoryRequest.Response(true);
    }
}
public class EditRepositoryHandler : IRequestHandler<EditRepositoryRequest, EditRepositoryRequest.Response>
{
    private readonly ProjectStoreContext _db;
    private readonly ILogger<EditRepositoryHandler> _log;

    public EditRepositoryHandler(ProjectStoreContext db, ILogger<EditRepositoryHandler> log)
    {
        _db = db;
        _log = log;
    }


    public async Task<EditRepositoryRequest.Response> Handle(EditRepositoryRequest request,
        CancellationToken cancellationToken)
    {
        try
        {
            _db.Update(request.Repository);
            await _db.SaveChangesAsync();
            _log.LogInformation($"Репозиторий отредактирован");
        }
        catch (NpgsqlException e)
        {
            _log.LogError($"NpgsqlException: {e.InnerException}");
        }
        catch (Exception e)
        {
            _log.LogError($"Exception: {e.InnerException}");
        }

        return new EditRepositoryRequest.Response(request.Repository);
    }
}

Requests

public record AddRepositoryRequest(Repository repo) : IRequest<AddRepositoryRequest.Response>
{
    public Repository Repository = repo;
    public const string RouteTemplate = "/api/Repository";
    public record Response(Repository repo);
}

public class AddRepositoryRequestValidator :  AbstractValidator<AddRepositoryRequest>
{
    private readonly ProjectStoreContext _context;
    public AddRepositoryRequestValidator(ProjectStoreContext context)
    {
        _context = context;
        RuleFor(v => v.Repository.Name)
            .NotEmpty().WithMessage("Name is required");
    }
}

public class DeleteRepositoryRequest(Repository repo) : IRequest<DeleteRepositoryRequest.Response>
{
    public Repository Repository = repo;
    public record Response(bool Result);
}

public class EditRepositoryRequest(Repository repo) : IRequest<EditRepositoryRequest.Response>
{
    public string RouteTemplate = "/api/Repository";

    public Repository Repository = repo;
    public record Response(Repository repo);
}

public record RepositoryRequest(string? sortColumn,
                                string? sortOrder,
                                int pageIndex,
                                int pageSize,
                                string? filterColumn,
                                string? filterQuery) : IRequest<RepositoryRequest.Response>
{
    public record Response(ApiResult<Repository> result);
}
⚠️ **GitHub.com Fallback** ⚠️