From 86e55596786f8d65854a75632512750b6e09faae Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 22 Sep 2017 01:57:18 -0400 Subject: switch mod update endpoint to GET with comma-delimited mod keys (#336) --- .../Controllers/ModsController.cs | 8 +-- .../Framework/CommaDelimitedModelBinder.cs | 58 ++++++++++++++++++++++ .../Framework/CommaDelimitedModelBinderProvider.cs | 27 ++++++++++ src/StardewModdingAPI.Web/Models/ModSearchModel.cs | 9 ---- src/StardewModdingAPI.Web/Startup.cs | 13 ++++- 5 files changed, 100 insertions(+), 15 deletions(-) create mode 100644 src/StardewModdingAPI.Web/Framework/CommaDelimitedModelBinder.cs create mode 100644 src/StardewModdingAPI.Web/Framework/CommaDelimitedModelBinderProvider.cs delete mode 100644 src/StardewModdingAPI.Web/Models/ModSearchModel.cs diff --git a/src/StardewModdingAPI.Web/Controllers/ModsController.cs b/src/StardewModdingAPI.Web/Controllers/ModsController.cs index c7b2aba7..d3b49445 100644 --- a/src/StardewModdingAPI.Web/Controllers/ModsController.cs +++ b/src/StardewModdingAPI.Web/Controllers/ModsController.cs @@ -29,13 +29,13 @@ namespace StardewModdingAPI.Web.Controllers ** Public methods *********/ /// Fetch version metadata for the given mods. - /// The mod update search criteria. - [HttpPost] - public async Task Post([FromBody] ModSearchModel search) + /// The namespaced mod keys to search. + [HttpGet] + public async Task Post(IEnumerable modKeys) { IList result = new List(); - foreach (string modKey in search.ModKeys) + foreach (string modKey in modKeys) { // parse mod key if (!this.TryParseModKey(modKey, out string vendorKey, out string modID)) diff --git a/src/StardewModdingAPI.Web/Framework/CommaDelimitedModelBinder.cs b/src/StardewModdingAPI.Web/Framework/CommaDelimitedModelBinder.cs new file mode 100644 index 00000000..119b18e6 --- /dev/null +++ b/src/StardewModdingAPI.Web/Framework/CommaDelimitedModelBinder.cs @@ -0,0 +1,58 @@ +using System; +using System.ComponentModel; +using System.Reflection; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace StardewModdingAPI.Web.Framework +{ + /// Maps comma-delimited values to an parameter. + /// Derived from . + public class CommaDelimitedModelBinder : IModelBinder + { + /********* + ** Public methods + *********/ + /// Attempts to bind a model. + /// The model binding context. + public Task BindModelAsync(ModelBindingContext bindingContext) + { + // validate + if (bindingContext == null) + throw new ArgumentNullException(nameof(bindingContext)); + + // extract values + string modelName = bindingContext.ModelName; + ValueProviderResult valueProviderResult = bindingContext.ValueProvider.GetValue(modelName); + string[] values = valueProviderResult + .ToString() + .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + Type elementType = bindingContext.ModelType.GetTypeInfo().GenericTypeArguments[0]; + if (values.Length == 0) + { + bindingContext.Result = ModelBindingResult.Success(Array.CreateInstance(elementType, 0)); + return Task.CompletedTask; + } + + // map values + TypeConverter converter = TypeDescriptor.GetConverter(elementType); + Array typedArray = Array.CreateInstance(elementType, values.Length); + try + { + for (int i = 0; i < values.Length; ++i) + { + string value = values[i]; + object convertedValue = converter.ConvertFromString(value); + typedArray.SetValue(convertedValue, i); + } + } + catch (Exception exception) + { + bindingContext.ModelState.TryAddModelError(modelName, exception, bindingContext.ModelMetadata); + } + + bindingContext.Result = ModelBindingResult.Success(typedArray); + return Task.CompletedTask; + } + } +} diff --git a/src/StardewModdingAPI.Web/Framework/CommaDelimitedModelBinderProvider.cs b/src/StardewModdingAPI.Web/Framework/CommaDelimitedModelBinderProvider.cs new file mode 100644 index 00000000..1b3f0073 --- /dev/null +++ b/src/StardewModdingAPI.Web/Framework/CommaDelimitedModelBinderProvider.cs @@ -0,0 +1,27 @@ +using System; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace StardewModdingAPI.Web.Framework +{ + /// Provides comma-delimited model binds for mapping parameters. + /// Derived from . + public class CommaDelimitedModelBinderProvider : IModelBinderProvider + { + /********* + ** Public methods + *********/ + /// Creates a model binder based on the given context. + /// The model binding context. + public IModelBinder GetBinder(ModelBinderProviderContext context) + { + // validate + if (context == null) + throw new ArgumentNullException(nameof(context)); + + // get model binder + return context.Metadata.IsEnumerableType && !context.Metadata.ElementMetadata.IsComplexType + ? new CommaDelimitedModelBinder() + : null; + } + } +} diff --git a/src/StardewModdingAPI.Web/Models/ModSearchModel.cs b/src/StardewModdingAPI.Web/Models/ModSearchModel.cs deleted file mode 100644 index 852ea439..00000000 --- a/src/StardewModdingAPI.Web/Models/ModSearchModel.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace StardewModdingAPI.Web.Models -{ - /// The mod update search criteria. - public class ModSearchModel - { - /// The namespaced mod keys to search. - public string[] ModKeys { get; set; } - } -} diff --git a/src/StardewModdingAPI.Web/Startup.cs b/src/StardewModdingAPI.Web/Startup.cs index c7a5e8fe..c1f03b34 100644 --- a/src/StardewModdingAPI.Web/Startup.cs +++ b/src/StardewModdingAPI.Web/Startup.cs @@ -1,8 +1,11 @@ -using Microsoft.AspNetCore.Builder; +using System.Linq; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.ModelBinding.Binders; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using StardewModdingAPI.Web.Framework; namespace StardewModdingAPI.Web { @@ -35,7 +38,13 @@ namespace StardewModdingAPI.Web /// The service injection container. public void ConfigureServices(IServiceCollection services) { - services.AddMvc(); + services + .AddMvc(options => + { + // add support for comma-delimited parameters + ArrayModelBinderProvider arrayModelBinderProvider = options.ModelBinderProviders.OfType().First(); + options.ModelBinderProviders.Insert(options.ModelBinderProviders.IndexOf(arrayModelBinderProvider), new CommaDelimitedModelBinderProvider()); + }); } /// The method called by the runtime to configure the HTTP request pipeline. -- cgit