Skip to content

Instantly share code, notes, and snippets.

@davetransom
Last active September 10, 2020 03:55
Show Gist options
  • Save davetransom/f70f5cb178ec0881fefc63c1a4fd08dd to your computer and use it in GitHub Desktop.
Save davetransom/f70f5cb178ec0881fefc63c1a4fd08dd to your computer and use it in GitHub Desktop.
ShortGuid Model Binding (MVC or WebApi) example
// Configure binding provider for MVC during the general setup, e.g.
// ... AreaRegistration.RegisterAllAreas();
ModelBinderProviders.BinderProviders.Add(new MyProject.ModelBinders.Mvc.ShortGuidModelBinderProvider());
// ... FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
public class MvcSampleController : System.Web.Mvc.Controller
{
/// <summary>
/// GET /watch/00amyWGct0y_ze4lIsj2Mw?token=Xy0MVKupFES9NpmZ9TiHcw
///
/// ```
/// [Details]
/// VideoId: `00amyWGct0y_ze4lIsj2Mw` => `c9a646d3-9c61-4cb7-bfcd-ee2522c8f633`
/// Token : `Xy0MVKupFES9NpmZ9TiHcw` => `540c2d5f-a9ab-4414-bd36-9999f5388773
/// ```
/// </summary>
/// <param name="videoId"></param>
/// <param name="token"></param>
/// <returns></returns>
[Route("watch/{videoId}")]
public ActionResult WatchVideo(
// url path param
ShortGuid? videoId = null,
// query string
ShortGuid? token = null
)
{
string result = $@"[Details]
VideoId: `{videoId}` => `{videoId?.Guid.ToString() ?? "Invalid"}`
Token : `{token}` => `{token?.Guid.ToString() ?? "None"}
";
return Content(result, "text/plain");
}
public class SampleArgsModel
{
public ShortGuid? ChannelId { get; set; }
}
/// <summary>
/// GET /watch?channelId=00amyWGct0y_ze4lIsj2Mw
///
/// ```
/// ChannelId: `00amyWGct0y_ze4lIsj2Mw` => `c9a646d3-9c61-4cb7-bfcd-ee2522c8f633`
/// ```
/// </summary>
/// <param name="args"></param>
/// <returns></returns>
[Route("watch")]
public ActionResult WatchChannel(
SampleArgsModel args
)
{
return Content($"ChannelId: `{args.ChannelId}` => `{args.ChannelId?.Guid.ToString() ?? "Invalid"}`", "text/plain");
}
}
using CSharpVitamins;
using System;
using System.Web.Mvc;
namespace MyProject.ModelBinders.Mvc
{
/// <summary>
/// An MVC model binder to convert 22 charactater URL-safe Base64 into <see cref="ShortGuid"/> instances.
/// </summary>
public sealed class ShortGuidModelBinder : IModelBinder
{
/// <summary>
/// Binds the model to a value by using the specified controller context and binding context.
/// </summary>
/// <param name="controllerContext"></param>
/// <param name="bindingContext"></param>
/// <returns></returns>
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var result = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (null == result?.AttemptedValue)
return null;
// Two options exist for parsing the incoming value
// 1. TryParse which will attempt a) 22 char ShortGuid format, then normal Guid parsing, and outputs a
// ShortGuid in this case. This will accept both formats e.g."00amyWGct0y_ze4lIsj2Mw" as well as
// "c9a646d3-9c61-4cb7-bfcd-ee2522c8f633".
/*
if (ShortGuid.TryParse(result.AttemptedValue, out ShortGuid parsed))
return parsed;
*/
// 2. TryDecode which will only decocde the 22 char ShortGuid format, but outputs a Guid. This will
// only accept "00amyWGct0y_ze4lIsj2Mw".
if (ShortGuid.TryDecode(result.AttemptedValue, out Guid decoded))
return new ShortGuid(decoded);
return null;
}
}
/// <summary>
/// Provider for MVC Model Binding.
/// </summary>
/// <example>
/// <code>
/// // MVC general configuration.
/// ModelBinderProviders.BinderProviders.Add(new ShortGuidModelBinderProvider());
/// </code>
/// </example>
public sealed class ShortGuidModelBinderProvider : IModelBinderProvider
{
ShortGuidModelBinder binder = new ShortGuidModelBinder();
/// <summary>
/// Returns the model binder for the specified type.
/// <para>Supports <see cref="ShortGuid"/> and nullable `ShortGuid?`.</para>
/// </summary>
/// <param name="modelType"></param>
/// <returns></returns>
public IModelBinder GetBinder(Type modelType)
{
if (modelType == typeof(ShortGuid) || modelType == typeof(ShortGuid?))
return binder;
// Could possibly extend to also run normal Guids through here too, though YMMV.
return null;
}
}
}
[RoutePrefix("api")]
public class WebApiSampleController : System.Web.Http.ApiController
{
/// <summary>
/// GET /api/watch/FEx1sZbSD0ugmgMAF_RGHw
/// ```
/// {
/// previewId: "FEx1sZbSD0ugmgMAF_RGHw",
/// guid: "b1754c14-d296-4b0f-a09a-030017f4461f"
/// }
/// ```
/// /// </summary>
/// <param name="previewId"></param>
/// <returns></returns>
[HttpGet]
[Route("watch/{previewId}")]
public HttpResponseMessage Watch(
[ModelBinder(typeof(ShortGuidModelBinder))] ShortGuid previewId
)
{
return Request.CreateResponse(HttpStatusCode.OK, new
{
PreviewId = previewId.Value,
Guid = previewId.Guid,
});
}
}
using CSharpVitamins;
using System;
using System.Web.Http.Controllers;
using System.Web.Http.ModelBinding;
namespace MyProject.ModelBinders.WebApi
{
/// <summary>
/// A WebApi model binder to convert 22 charactater URL-safe Base64 into <see cref="ShortGuid"/> instances.
/// </summary>
public sealed class ShortGuidModelBinder : IModelBinder
{
/// <summary>
/// Binds the model to a value by using the specified controller context and binding context.
/// </summary>
/// <param name="actionContext"></param>
/// <param name="bindingContext"></param>
/// <returns></returns>
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
var result = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (null == result)
return false;
// You can decide if you want this try/catch/throw here.
try
{
// Two options exist for parsing the incoming value
// 1. TryParse which will attempt a) 22 char ShortGuid format, then normal Guid parsing, and outputs a
// ShortGuid in this case. This will accept both formats e.g."00amyWGct0y_ze4lIsj2Mw" as well as
// "c9a646d3-9c61-4cb7-bfcd-ee2522c8f633".
/*
if (ShortGuid.TryParse(result.AttemptedValue, out ShortGuid parsed))
{
bindingContext.Model = parsed;
return true;
}
*/
// 2. TryDecode which will only decocde the 22 char ShortGuid format, but outputs a Guid. This will
// only accept "00amyWGct0y_ze4lIsj2Mw".
if (ShortGuid.TryDecode(result.AttemptedValue, out Guid decoded))
{
bindingContext.Model = new ShortGuid(decoded);
return true;
}
}
catch (Exception ex)
{
throw new ArgumentException($"An invalid short-guid value for `{bindingContext.ModelName}` was provided.", ex);
}
return false;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment