Skip to content

Commit

Permalink
Feat: Added support for minimal APIs (#43)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jamess-Lucass authored Jan 18, 2024
1 parent c127894 commit c753264
Show file tree
Hide file tree
Showing 27 changed files with 239 additions and 13 deletions.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

builder.Services.AddDbContext<ApplicationDbContext>(options =>
{
options.UseInMemoryDatabase("example1");
options.UseInMemoryDatabase("controller");
});

builder.Services.AddAutoMapper(Assembly.GetExecutingAssembly());
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
8 changes: 8 additions & 0 deletions examples/minimal/ApplicationDbContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using Microsoft.EntityFrameworkCore;

public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { }

public DbSet<User> Users => Set<User>();
}
19 changes: 19 additions & 0 deletions examples/minimal/Dtos/UserDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System.Text.Json.Serialization;

public record UserDto
{
public Guid Id { get; set; }

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

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

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

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

[JsonPropertyName("displayName")]
public string UserName { get; set; } = string.Empty;

public string Gender { get; set; } = string.Empty;
}
26 changes: 26 additions & 0 deletions examples/minimal/Entities/User.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System.ComponentModel.DataAnnotations.Schema;
using Bogus.DataSets;

public record User
{
public Guid Id { get; set; }

[Column(TypeName = "varchar(128)")]
public string Firstname { get; set; } = string.Empty;

[Column(TypeName = "varchar(128)")]
public string Lastname { get; set; } = string.Empty;

[Column(TypeName = "varchar(320)")]
public string Email { get; set; } = string.Empty;

[Column(TypeName = "varchar(1024)")]
public string AvatarUrl { get; set; } = string.Empty;
public bool IsDeleted { get; set; }

[Column(TypeName = "varchar(64)")]
public string UserName { get; set; } = string.Empty;

[Column("PersonSex", TypeName = "varchar(32)")]
public string Gender { get; set; } = string.Empty;
}
4 changes: 4 additions & 0 deletions examples/minimal/Interfaces/IUserService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
public interface IUserService
{
IQueryable<UserDto> GetAllUsers();
}
9 changes: 9 additions & 0 deletions examples/minimal/Profiles/AutoMapperProfile.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using AutoMapper;

public class AutoMapperProfile : Profile
{
public AutoMapperProfile()
{
CreateMap<User, UserDto>();
}
}
69 changes: 69 additions & 0 deletions examples/minimal/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using System.Linq.Dynamic.Core;
using System.Reflection;
using Bogus;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddEndpointsApiExplorer();

builder.Services.AddSwaggerGen(options =>
{
options.OperationFilter<GoatQueryOpenAPIFilter>();
});

builder.Services.AddDbContext<ApplicationDbContext>(options =>
{
options.UseInMemoryDatabase("minimal");
});

builder.Services.AddAutoMapper(Assembly.GetExecutingAssembly());

builder.Services.AddScoped<IUserService, UserService>();

builder.Services.AddSingleton<ISearchBinder<UserDto>, UserDtoSearchBinder>();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();

using (var scope = app.Services.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
await context.Database.EnsureCreatedAsync();

// Seed data
if (!context.Users.Any())
{
var users = new Faker<User>()
.RuleFor(x => x.Firstname, f => f.Person.FirstName)
.RuleFor(x => x.Lastname, f => f.Person.LastName)
.RuleFor(x => x.Email, f => f.Person.Email)
.RuleFor(x => x.AvatarUrl, f => f.Internet.Avatar())
.RuleFor(x => x.UserName, f => f.Person.UserName)
.RuleFor(x => x.Gender, f => f.Person.Gender.ToString())
.RuleFor(x => x.IsDeleted, f => f.Random.Bool());

context.Users.AddRange(users.Generate(1_000));
context.SaveChanges();

Console.WriteLine("Seeded 1,000 fake users!");
}
}
}

app.UseHttpsRedirection();

app.MapGet("/users", (ApplicationDbContext db, [FromServices] IUserService userService, [AsParameters] Query query) =>
{
var (users, count) = userService.GetAllUsers().Apply(query);

return TypedResults.Ok(new PagedResponse<object>(users.ToDynamicList(), count));
});

app.Run();
41 changes: 41 additions & 0 deletions examples/minimal/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:42100",
"sslPort": 44374
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:5240",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7077;http://localhost:5240",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
11 changes: 11 additions & 0 deletions examples/minimal/SearchBinders/UserDtoSearchBinder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System.Linq.Expressions;

public class UserDtoSearchBinder : ISearchBinder<UserDto>
{
public Expression<Func<UserDto, bool>> Bind(string searchTerm)
{
Expression<Func<UserDto, bool>> exp = x => x.Firstname.Contains(searchTerm, StringComparison.OrdinalIgnoreCase) || x.Lastname.Contains(searchTerm, StringComparison.OrdinalIgnoreCase);

return exp;
}
}
22 changes: 22 additions & 0 deletions examples/minimal/Services/UserService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using AutoMapper;
using AutoMapper.QueryableExtensions;
using Microsoft.EntityFrameworkCore;

public class UserService : IUserService
{
private readonly ApplicationDbContext _context;
private readonly IMapper _mapper;

public UserService(ApplicationDbContext context, IMapper mapper)
{
_context = context;
_mapper = mapper;
}

public IQueryable<UserDto> GetAllUsers()
{
return _context.Users.AsNoTracking()
.Where(x => !x.IsDeleted)
.ProjectTo<UserDto>(_mapper.ConfigurationProvider);
}
}
17 changes: 17 additions & 0 deletions examples/minimal/minimal.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.1" />
<PackageReference Include="Bogus" Version="35.3.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
<ProjectReference Include="../../src/goatquery-dotnet.csproj" />
</ItemGroup>

</Project>
6 changes: 3 additions & 3 deletions src/Extensions/QueryableExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public static (IQueryable, int?) Apply<T>(this IQueryable<T> queryable, Query qu
int? count = null;

// Count
if (query.Count)
if (query.Count ?? false)
{
count = result.Count();
}
Expand All @@ -104,13 +104,13 @@ public static (IQueryable, int?) Apply<T>(this IQueryable<T> queryable, Query qu
// Skip
if (query.Skip > 0)
{
result = result.Skip(query.Skip);
result = result.Skip(query.Skip ?? 0);
}

// Top
if (query.Top > 0)
{
result = result.Take(query.Top);
result = result.Take(query.Top ?? 0);
}

return (result, count);
Expand Down
14 changes: 7 additions & 7 deletions src/Query.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
public record Query
{
public int Top { get; set; }
public int Skip { get; set; }
public bool Count { get; set; }
public string OrderBy { get; set; } = string.Empty;
public string Select { get; set; } = string.Empty;
public string Search { get; set; } = string.Empty;
public string Filter { get; set; } = string.Empty;
public int? Top { get; set; }
public int? Skip { get; set; }
public bool? Count { get; set; }
public string? OrderBy { get; set; } = string.Empty;
public string? Select { get; set; } = string.Empty;
public string? Search { get; set; } = string.Empty;
public string? Filter { get; set; } = string.Empty;
}
4 changes: 2 additions & 2 deletions tests/Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public void Test_QueryWithTop()
var (result, _) = _context.Users.AsQueryable().Apply(query);
var sql = result.ToQueryString();

var expectedSql = _context.Users.AsQueryable().Take(query.Top).ToQueryString();
var expectedSql = _context.Users.AsQueryable().Take(query.Top ?? 0).ToQueryString();

Assert.Equal(expectedSql, sql);
}
Expand All @@ -64,7 +64,7 @@ public void Test_QueryWithSkip()
var (result, _) = _context.Users.AsQueryable().Apply(query);
var sql = result.ToQueryString();

var expectedSql = _context.Users.AsQueryable().Skip(query.Skip).ToQueryString();
var expectedSql = _context.Users.AsQueryable().Skip(query.Skip ?? 0).ToQueryString();

Assert.Equal(expectedSql, sql);
}
Expand Down

0 comments on commit c753264

Please sign in to comment.