I just converted a bunch of web services to Web API2. Now my C# code blows up when the browser sends an empty string and it enters my code converted to null. I have researched global solutions and none that I have found work for me.
I can of course set it manually for every string in all my Web API models, but I have scores of models so would prefer a global solution.
Been here: string.empty converted to null when passing JSON object to MVC Controller and other pages and attempted to implement each solution, but to no avail.
How can I globally set the default for ConvertEmptyStringToNull to false?
You need to swap out the ModelMetadataProvider
with one that sets the ConvertEmptyStringToNull
to false
Such as:
public class EmptyStringAllowedModelMetadataProvider : DataAnnotationsModelMetadataProvider
{
protected override CachedDataAnnotationsModelMetadata CreateMetadataFromPrototype(CachedDataAnnotationsModelMetadata prototype, Func<object> modelAccessor)
{
var metadata = base.CreateMetadataFromPrototype(prototype, modelAccessor);
metadata.ConvertEmptyStringToNull = false;
return metadata;
}
protected override CachedDataAnnotationsModelMetadata CreateMetadataPrototype(IEnumerable<Attribute> attributes, Type containerType, Type modelType, string propertyName)
{
var metadata = base.CreateMetadataPrototype(attributes, containerType, modelType, propertyName);
metadata.ConvertEmptyStringToNull = false;
return metadata;
}
}
You would register in your WebApiConfig like:
config.Services.Replace(typeof(ModelMetadataProvider), new EmptyStringAllowedModelMetadataProvider());
This was inspired by https://gist.github.com/nakamura-to/4029706
you can try Aspect pattern by using Postsharp and declare below Aspect.
It will apply for entire solution. Worked for me.
using System;
using System.Linq;
using System.Reflection;
using Helpers.Aspects;
using PostSharp.Aspects;
using PostSharp.Extensibility;
[assembly: EmptyStringModelBindingAspect(
AttributeTargetTypes = @"regex:[^\.]*\.Controllers\..*Controller",
AttributeTargetTypeAttributes = MulticastAttributes.Public,
AttributeTargetElements = MulticastTargets.Method,
AttributeTargetMemberAttributes = MulticastAttributes.Public)]
namespace Helpers.Aspects
{
[Serializable]
public class EmptyStringModelBindingAspect : MethodInterceptionAspect
{
public override void OnInvoke(MethodInterceptionArgs args)
{
for (int i = 0; i < args.Arguments.Count; i++)
{
FixString(args, i);
FixStringsInObjects(args.Arguments[i]);
}
args.Proceed();
}
private static void FixString(MethodInterceptionArgs args, int i)
{
if (args.Arguments[i] is string && args.Arguments[i] == null)
{
args.Arguments.SetArgument(i, string.Empty);
}
}
private static void FixStringsInObjects(object obj)
{
if (obj == null)
{
return;
}
var type = obj.GetType();
var properties = (from p in type.GetProperties()
let paramerers = p.GetIndexParameters()
where !paramerers.Any()
where p.PropertyType == typeof (string) &&
p.CanRead &&
p.CanWrite &&
p.GetValue(obj, null) == null
select p).ToArray();
foreach (var item in properties)
{
item.SetValue(obj, string.Empty, null);
}
}
public override bool CompileTimeValidate(MethodBase method)
{
return !(method.Name.StartsWith("get_") || method.Name.StartsWith("set_"));
}
}
}