Created
March 30, 2021 18:32
-
-
Save abdusco/919d2dd257bacc1e61cc9ff492fb2a2f to your computer and use it in GitHub Desktop.
ASP.NET Core json query string with Swagger support
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.ComponentModel.DataAnnotations; | |
using System.Linq; | |
using System.Net.Http; | |
using System.Net.Mime; | |
using System.Text.Json; | |
using System.Text.Json.Serialization; | |
using System.Threading.Tasks; | |
using Microsoft.AspNetCore.Mvc; | |
using Microsoft.AspNetCore.Mvc.Controllers; | |
using Microsoft.AspNetCore.Mvc.ModelBinding; | |
using Microsoft.Extensions.DependencyInjection; | |
using Microsoft.Extensions.Logging; | |
using Microsoft.OpenApi.Models; | |
using Swashbuckle.AspNetCore.SwaggerGen; | |
namespace MvcPlayground | |
{ | |
public class FromJsonQueryAttribute : ModelBinderAttribute | |
{ | |
public FromJsonQueryAttribute() | |
{ | |
BinderType = typeof(JsonQueryBinder); | |
BindingSource = BindingSource.Custom; | |
} | |
public FromJsonQueryAttribute(string queryParameterName) : this() | |
{ | |
Name = queryParameterName; | |
} | |
} | |
class JsonQueryBinder : IModelBinder | |
{ | |
public Task BindModelAsync(ModelBindingContext bindingContext) | |
{ | |
var value = bindingContext.ValueProvider.GetValue(bindingContext.FieldName).FirstValue; | |
if (value == null) | |
{ | |
return Task.CompletedTask; | |
} | |
var logger = bindingContext.HttpContext.RequestServices.GetRequiredService<ILogger<JsonQueryBinder>>(); | |
try | |
{ | |
var parsed = JsonSerializer.Deserialize(value, bindingContext.ModelType, new JsonSerializerOptions | |
{ | |
PropertyNameCaseInsensitive = true, | |
}); | |
bindingContext.Result = ModelBindingResult.Success(parsed); | |
} | |
catch (Exception e) | |
{ | |
logger.LogError(e, "Failed to bind parameter"); | |
bindingContext.Result = ModelBindingResult.Failed(); | |
} | |
return Task.CompletedTask; | |
} | |
} | |
internal static class SwaggerGenExtensions | |
{ | |
public static SwaggerGenOptions AddJsonQuerySupport(this SwaggerGenOptions options) | |
{ | |
options.OperationFilter<JsonQueryOperationFilter>(); | |
return options; | |
} | |
private class JsonQueryOperationFilter : IOperationFilter | |
{ | |
public void Apply(OpenApiOperation operation, OperationFilterContext context) | |
{ | |
if (context.ApiDescription.HttpMethod != HttpMethod.Get.Method || operation.Parameters == null) | |
{ | |
return; | |
} | |
var jsonQueryParams = context.ApiDescription.ActionDescriptor.Parameters | |
.Cast<ControllerParameterDescriptor>() | |
.Where(ad => ad.BindingInfo.BinderType == typeof(JsonQueryBinder)) | |
.ToList(); | |
if (!jsonQueryParams.Any()) | |
{ | |
return; | |
} | |
operation.Parameters = operation.Parameters.ToList() | |
.Select(p => | |
{ | |
var actionParam = jsonQueryParams.FirstOrDefault(qp => qp.Name == p.Name); | |
if (actionParam == null) | |
{ | |
return p; | |
} | |
var schema = context.SchemaGenerator.GenerateSchema(actionParam.ParameterType, context.SchemaRepository); | |
return new OpenApiParameter | |
{ | |
Name = p.Name, | |
In = ParameterLocation.Query, | |
Content = new Dictionary<string, OpenApiMediaType>() | |
{ | |
[MediaTypeNames.Application.Json] = new OpenApiMediaType() | |
{ | |
Schema = schema | |
} | |
} | |
}; | |
}).ToList(); | |
// throw new NotImplementedException(); | |
} | |
} | |
} | |
[ApiController] | |
[Route("api/[controller]")] | |
public class BindingController : ControllerBase | |
{ | |
[HttpGet("test/{id}")] | |
public Task<ActionResult> PerformQuery(string id, [FromJsonQuery] MyQuery query) | |
{ | |
return Task.FromResult<ActionResult>(Ok(query)); | |
} | |
} | |
public record MyQuery(MyFilter Filter, [Required] string Sort); | |
public record MyFilter(string Query); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment