Skip to content

Instantly share code, notes, and snippets.

@ToineSeiter
Last active April 16, 2020 09:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ToineSeiter/5e124769b09ab425952526958f9caa06 to your computer and use it in GitHub Desktop.
Save ToineSeiter/5e124769b09ab425952526958f9caa06 to your computer and use it in GitHub Desktop.
Bind form-urlencoded values to property alias
using System;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Web.Mvc;
namespace Sample
{
[AttributeUsage(AttributeTargets.Property)]
public class AliasAttribute : Attribute
{
internal protected string Name { get; set; }
public AliasAttribute(string name)
{
Name = name;
}
}
public class AliasBinder : DefaultModelBinder
{
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
{
var alias = propertyDescriptor.Attributes
.OfType<AliasAttribute>()
.FirstOrDefault();
string text = CreateSubPropertyName(bindingContext.ModelName, alias?.Name ?? propertyDescriptor.Name);
if (!bindingContext.ValueProvider.ContainsPrefix(text))
{
return;
}
IModelBinder binder = Binders.GetBinder(propertyDescriptor.PropertyType);
object value = propertyDescriptor.GetValue(bindingContext.Model);
ModelMetadata modelMetadata = bindingContext.PropertyMetadata[propertyDescriptor.Name];
modelMetadata.Model = value;
ModelBindingContext bindingContext2 = new ModelBindingContext
{
ModelMetadata = modelMetadata,
ModelName = text,
ModelState = bindingContext.ModelState,
ValueProvider = bindingContext.ValueProvider
};
object propertyValue = GetPropertyValue(controllerContext, bindingContext2, propertyDescriptor, binder);
modelMetadata.Model = propertyValue;
ModelState modelState = bindingContext.ModelState[text];
if (modelState == null || modelState.Errors.Count == 0)
{
if (OnPropertyValidating(controllerContext, bindingContext, propertyDescriptor, propertyValue))
{
SetProperty(controllerContext, bindingContext, propertyDescriptor, propertyValue);
OnPropertyValidated(controllerContext, bindingContext, propertyDescriptor, propertyValue);
return;
}
}
else
{
SetProperty(controllerContext, bindingContext, propertyDescriptor, propertyValue);
foreach (ModelError current in (from err in modelState.Errors
where string.IsNullOrEmpty(err.ErrorMessage) && err.Exception != null
select err).ToList())
{
for (Exception ex = current.Exception; ex != null; ex = ex.InnerException)
{
if (ex is FormatException)
{
string displayName = modelMetadata.GetDisplayName();
string errorMessage = string.Format(CultureInfo.CurrentCulture, $"'{0}' is not valid for field {1}.", modelState.Value.AttemptedValue, displayName);
modelState.Errors.Remove(current);
modelState.Errors.Add(errorMessage);
break;
}
}
}
}
}
}
}
@ToineSeiter
Copy link
Author

To use it locally, just add the following attributes to your model class :

[ModelBinder(typeof(AliasBinder))]
public class MyModel
{
        [Alias("state")]
        public string Status { get; set; }
}

@0biWanKenobi
Copy link

Hey, I added a provider class and a call in Application_Start. This way the binder would be used on all models that

  1. have the custom attribute;
  2. are used in POST requests with "application/x-www-form-urlencoded";

You can find my fork here, hope you can merge it if it looks ok 😊

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment