aspnetcore query support.

This commit is contained in:
David Lebee 2021-02-02 01:05:48 -05:00
parent 808dcbd18f
commit 60abf83932
17 changed files with 402 additions and 2 deletions

19
Demo/Demo.csproj Normal file
View File

@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Swashbuckle.AspNetCore.Swagger" Version="5.6.3" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="5.6.3" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="5.6.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\PoweredSoft.CQRS.AspNetCore\PoweredSoft.CQRS.AspNetCore.csproj" />
<ProjectReference Include="..\PoweredSoft.CQRS\PoweredSoft.CQRS.csproj" />
</ItemGroup>
</Project>

26
Demo/Program.cs Normal file
View File

@ -0,0 +1,26 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Demo
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}

View File

@ -0,0 +1,28 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:52483",
"sslPort": 44343
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": false,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Demo": {
"commandName": "Project",
"launchBrowser": false,
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@ -0,0 +1,50 @@
using PoweredSoft.CQRS.Abstractions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Demo.Queries
{
public class Person
{
public long Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class PersonQuery
{
public string Search { get; set; }
}
public class PersonQueryHandler : IQueryHandler<PersonQuery, IQueryable<Person>>
{
private readonly IEnumerable<Person> _persons = new List<Person>()
{
new Person
{
Id = 1,
FirstName = "David",
LastName = "Lebee"
},
new Person
{
Id = 2,
FirstName = "John",
LastName = "Doe"
}
};
public Task<IQueryable<Person>> HandleAsync(PersonQuery query, CancellationToken cancellationToken = default)
{
var ret = _persons.AsQueryable();
if (query != null && !string.IsNullOrEmpty(query.Search))
ret = ret.Where(t => t.FirstName.Contains(query.Search) || t.LastName.Contains(query.Search));
return Task.FromResult(ret);
}
}
}

76
Demo/Startup.cs Normal file
View File

@ -0,0 +1,76 @@
using Demo.Queries;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using PoweredSoft.CQRS;
using PoweredSoft.CQRS.Abstractions;
using PoweredSoft.CQRS.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Demo
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
AddQueries(services);
services.AddPoweredSoftCQRS();
services
.AddControllers()
.AddPoweredSoftQueryController();
services.AddSwaggerGen();
}
private void AddQueries(IServiceCollection services)
{
services.AddQuery<PersonQuery, IQueryable<Person>, PersonQueryHandler>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseSwagger();
// Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.),
// specifying the Swagger JSON endpoint.
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
});
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}

View File

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}

10
Demo/appsettings.json Normal file
View File

@ -0,0 +1,10 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}

View File

@ -0,0 +1,9 @@
using System;
namespace PoweredSoft.CQRS.AspNetCore.Abstractions.Attributes
{
[AttributeUsage(AttributeTargets.Class)]
public class CommandControllerIgnoreAttribute : Attribute
{
}
}

View File

@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace PoweredSoft.CQRS.AspNetCore.Abstractions.Attributes
{
[AttributeUsage(AttributeTargets.Class)]
public class QueryControllerIgnoreAttribute : Attribute
{
}
}

View File

@ -0,0 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,21 @@
using Microsoft.AspNetCore.Mvc;
using PoweredSoft.CQRS.Abstractions;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace PoweredSoft.CQRS.AspNetCore.Mvc
{
[ApiController, Route("api/query/[controller]")]
public class QueryController<TQuery, TQueryResult> : Controller
where TQuery : class
{
[HttpPost]
public Task<TQueryResult> Handle([FromServices] IQueryHandler<TQuery, TQueryResult> handler,
[FromBody] TQuery query)
{
return handler.HandleAsync(query, this.Request.HttpContext.RequestAborted);
}
}
}

View File

@ -0,0 +1,28 @@
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.Extensions.DependencyInjection;
using PoweredSoft.CQRS.Abstractions.Discovery;
using System;
namespace PoweredSoft.CQRS.AspNetCore.Mvc
{
public class QueryControllerConvention : IControllerModelConvention
{
private readonly IServiceProvider serviceProvider;
public QueryControllerConvention(IServiceProvider serviceProvider)
{
this.serviceProvider = serviceProvider;
}
public void Apply(ControllerModel controller)
{
if (controller.ControllerType.IsGenericType && controller.ControllerType.Name.Contains("QueryController") && controller.ControllerType.Assembly == typeof(QueryControllerConvention).Assembly)
{
var genericType = controller.ControllerType.GenericTypeArguments[0];
var queryDiscovery = this.serviceProvider.GetRequiredService<IQueryDiscovery>();
var query = queryDiscovery.FindQuery(genericType);
controller.ControllerName = query.Name;
}
}
}
}

View File

@ -0,0 +1,32 @@
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.Extensions.DependencyInjection;
using PoweredSoft.CQRS.Abstractions.Discovery;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
namespace PoweredSoft.CQRS.AspNetCore.Mvc
{
public class QueryControllerFeatureProvider : IApplicationFeatureProvider<ControllerFeature>
{
private readonly ServiceProvider serviceProvider;
public QueryControllerFeatureProvider(ServiceProvider serviceProvider)
{
this.serviceProvider = serviceProvider;
}
public void PopulateFeature(IEnumerable<ApplicationPart> parts, ControllerFeature feature)
{
var queryDiscovery = this.serviceProvider.GetRequiredService<IQueryDiscovery>();
foreach (var f in queryDiscovery.GetQueries())
{
var controllerType = typeof(QueryController<,>).MakeGenericType(f.QueryType, f.QueryResultType);
var controllerTypeInfo = controllerType.GetTypeInfo();
feature.Controllers.Add(controllerTypeInfo);
}
}
}
}

View File

@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace PoweredSoft.CQRS.AspNetCore.Mvc
{
public class QueryControllerOptions
{
}
}

View File

@ -0,0 +1,30 @@
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Text;
namespace PoweredSoft.CQRS.AspNetCore.Mvc
{
public static class MvcBuilderExtensions
{
public static IMvcBuilder AddPoweredSoftQueryController(this IMvcBuilder builder, Action<QueryControllerOptions> configuration = null)
{
var options = new QueryControllerOptions();
configuration?.Invoke(options);
var services = builder.Services;
var serviceProvider = services.BuildServiceProvider();
builder.AddMvcOptions(o => o.Conventions.Add(new QueryControllerConvention(serviceProvider)));
builder.ConfigureApplicationPartManager(m => m.FeatureProviders.Add(new QueryControllerFeatureProvider(serviceProvider)));
return builder;
}
/*
public static IMvcBuilder AddPoweredSoftCommandController(this IMvcBuilder builder)
{
var services = builder.Services;
var serviceProvider = services.BuildServiceProvider();
builder.AddMvcOptions(o => o.Conventions.Add(new CommandControllerConvention(serviceProvider)));
builder.ConfigureApplicationPartManager(m => m.FeatureProviders.Add(new CommandControllerFeatureProvider(serviceProvider)));
return builder;
}*/
}
}

View File

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\PoweredSoft.CQRS.Abstractions\PoweredSoft.CQRS.Abstractions.csproj" />
</ItemGroup>
</Project>

View File

@ -3,9 +3,15 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16 # Visual Studio Version 16
VisualStudioVersion = 16.0.30907.101 VisualStudioVersion = 16.0.30907.101
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PoweredSoft.CQRS.Abstractions", "PoweredSoft.CQRS.Abstractions\PoweredSoft.CQRS.Abstractions.csproj", "{ED78E19D-31D4-4783-AE9E-2844A8541277}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PoweredSoft.CQRS.Abstractions", "PoweredSoft.CQRS.Abstractions\PoweredSoft.CQRS.Abstractions.csproj", "{ED78E19D-31D4-4783-AE9E-2844A8541277}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PoweredSoft.CQRS", "PoweredSoft.CQRS\PoweredSoft.CQRS.csproj", "{7069B98F-8736-4114-8AF5-1ACE094E6238}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PoweredSoft.CQRS", "PoweredSoft.CQRS\PoweredSoft.CQRS.csproj", "{7069B98F-8736-4114-8AF5-1ACE094E6238}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PoweredSoft.CQRS.AspNetCore", "PoweredSoft.CQRS.AspNetCore\PoweredSoft.CQRS.AspNetCore.csproj", "{A1D577E5-61BD-4E25-B2C8-1005C1D7665B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PoweredSoft.CQRS.AspNetCore.Abstractions", "PoweredSoft.CQRS.AspNetCore.Abstractions\PoweredSoft.CQRS.AspNetCore.Abstractions.csproj", "{4C466827-31D3-4081-A751-C2FC7C381D7E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Demo", "Demo\Demo.csproj", "{F15B1E11-8D4C-489E-AFF7-AA144105FE46}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -21,6 +27,18 @@ Global
{7069B98F-8736-4114-8AF5-1ACE094E6238}.Debug|Any CPU.Build.0 = Debug|Any CPU {7069B98F-8736-4114-8AF5-1ACE094E6238}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7069B98F-8736-4114-8AF5-1ACE094E6238}.Release|Any CPU.ActiveCfg = Release|Any CPU {7069B98F-8736-4114-8AF5-1ACE094E6238}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7069B98F-8736-4114-8AF5-1ACE094E6238}.Release|Any CPU.Build.0 = Release|Any CPU {7069B98F-8736-4114-8AF5-1ACE094E6238}.Release|Any CPU.Build.0 = Release|Any CPU
{A1D577E5-61BD-4E25-B2C8-1005C1D7665B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A1D577E5-61BD-4E25-B2C8-1005C1D7665B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A1D577E5-61BD-4E25-B2C8-1005C1D7665B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A1D577E5-61BD-4E25-B2C8-1005C1D7665B}.Release|Any CPU.Build.0 = Release|Any CPU
{4C466827-31D3-4081-A751-C2FC7C381D7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4C466827-31D3-4081-A751-C2FC7C381D7E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4C466827-31D3-4081-A751-C2FC7C381D7E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4C466827-31D3-4081-A751-C2FC7C381D7E}.Release|Any CPU.Build.0 = Release|Any CPU
{F15B1E11-8D4C-489E-AFF7-AA144105FE46}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F15B1E11-8D4C-489E-AFF7-AA144105FE46}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F15B1E11-8D4C-489E-AFF7-AA144105FE46}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F15B1E11-8D4C-489E-AFF7-AA144105FE46}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE