我们有一个Web API,它对所有属性(包括枚举)使用了蛇形序列化,为了启用它,我们在启动时就使用了它:
services
.AddMvcCore()
.AddJsonOptions(opt =>
{
opt.SerializerSettings.DateTimeZoneHandling = Newtonsoft.Json.DateTimeZoneHandling.Local;
opt.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.None;
opt.SerializerSettings.ContractResolver = new DefaultContractResolver
{
NamingStrategy = new SnakeCaseNamingStrategy { ProcessDictionaryKeys = true }
};
opt.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;
opt.SerializerSettings.Converters.Add(new StringEnumConverter());
})
.AddApiExplorer()
.AddJsonFormatters(j => j.ContractResolver = new DefaultContractResolver { NamingStrategy = new SnakeCaseNamingStrategy() { ProcessDictionaryKeys = true } })
这在属性上效果很好,但是我们在路由和枚举方面遇到了问题,例如,我们有这个枚举(我们也尝试过使用JsonProperty,但失败的方式相同):
[DataContract(Name = "document_type")]
public enum DocumentType
{
[EnumMember(Value = "passport")]
Passport,
[EnumMember(Value = "proof_of_address")]
ProofOfAddress,
}
,并且我们尝试按类型搜索文档,因此我们采用以下路线:
/clients/{clientId:guid/documents/{documentType}
,并在控制器中显示:
[HttpGet]
[Route("/clients/{clientId:guid}/documents/{documentType}")]
public async Task<IActionResult> FindClientDocuments([FromRoute] Guid clientId, [FromRoute] DocumentType documentType)
通过此路线,一切正常:
/ clients / 60a00cd4-59e2-4f52-871a-4029370f6dd8 / documents / ProofOfAddress
但不适用于此
clients / 60a00cd4-59e2-4f52-871a-4029370f6dd8 / documents / proof_of_address
在后一种情况下,枚举始终是默认值,或者如果我们添加动作过滤器,则错误为“值'proof_of_address'无效。”
除了尝试使用过滤器自己转换值之外,是否有其他方法可以使这种设想有效?
谢谢
答案 0 :(得分:1)
我认为这个问题可以分为两部分,
EnumMemberAttribute
我将回答第二部分,希望第一部分应该相对容易实施/研究。我直接在第一部分中加入了link to a question。
告诉MVC使用自定义枚举转换来绑定模型输入。
总是通过模型绑定从客户端获取值,而json序列化主要处理将响应数据格式化为json以便输出。因此,您的解决方案应考虑将要使用的枚举反序列化告知模型绑定程序。 (enum <=>字符串转换的默认实现不考虑属性)。
我为我的枚举尝试了以下自定义模型活页夹,并且效果很好。 FooType
是我的枚举:
public enum FooType
{
[Description("test")]
TestFoo,
[Description("another")]
AnotherFooType
}
我跳过[EnumMember]
属性并选择了[Description]
属性仅仅是因为我想使用Humanizer's Enum utility,但是您可以实现自己的从{{1 }}属性,只需替换我对EnumMember
的调用,即可在this question上找到一个示例。
DeHumanizeTo<FooType>()
然后,在我的控制器中,我告诉MVC使用此绑定器来绑定我的参数,如下所示:
public class FooTypeBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext) => Task.Run(() => this.BindModel(bindingContext));
private void BindModel(ModelBindingContext bindingContext)
{
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (valueProviderResult.Length == 0)
{
bindingContext.ModelState.AddModelError(bindingContext.ModelName, "No value was provided for enum");
return;
}
var stringValue = valueProviderResult.FirstValue;
try
{
bindingContext.Model = this.FromString(stringValue);
}
catch (NoMatchFoundException ex)
{
bindingContext.ModelState.AddModelError(bindingContext.ModelName, ex.Message);
}
}
private FooType FromString(string input)
{
//Here you should implement your custom way of checking the [EnumMember] attribute, and convert input into your enum.
if (Enum.TryParse(typeof(FooType), input, true, out object value))
{
return (FooType)value;
}
else return input.DehumanizeTo<FooType>();
}
}
我希望这会有所帮助。