gpt4 book ai didi

c# - RESTful WCF 服务中标志枚举的默认值

转载 作者:太空宇宙 更新时间:2023-11-03 23:22:25 24 4
gpt4 key购买 nike

WCF 支持使用标记有 FlagsAttribute 的枚举类型作为 UriTemplate 中的参数。像这样:

[DataContract]
[Flags]
public enum OptionsEnum
{
[EnumMember]
None = 0,
[EnumMember]
MyOption1 = 1,
[EnumMember]
MyOption2 = 2,
[EnumMember]
MyOption3 = 4,
[EnumMember]
MyOption4 = 8
}

[ServiceContract]
public interface MyServiceContract
{
[OperationContract]
[WebInvoke(Method = "GET", UriTemplate = "resource?options={options}")]
void MyOperation(OptionsEnum options);
}

然后可以通过这样的 URL 请求资源:

GET/resource?options=None

GET/resource?options=MyOption1

GET/resource?options=MyOption1,MyOption3

只要 URL 实际上包含 options 参数的值,所有这些都非常有效。但是,如果我在没有在 URL 中指定值的情况下请求资源,如下所示:

获取/资源

我收到消息 Value cannot be null.\r\nParameter name: value 的异常以及以下堆栈跟踪:

at System.Enum.TryParseEnum(Type enumType, String value, Boolean ignoreCase, EnumResult& parseResult)
at System.Enum.Parse(Type enumType, String value, Boolean ignoreCase)
at System.ServiceModel.Dispatcher.QueryStringConverter.ConvertStringToValue(String parameter, Type parameterType)
at System.ServiceModel.Dispatcher.UriTemplateDispatchFormatter.DeserializeRequest(Message message, Object[] parameters)
at System.ServiceModel.Dispatcher.DispatchOperationRuntime.DeserializeInputs(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage31(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean isOperationContextSet)

显然,这是因为 QueryStringConverter 在这种情况下将 null 传递给 Enum.Parse(...)。因此 MyServiceContract 的实现将不会被执行。

当然,我可以针对 options 参数的类型切换到 string 并在服务实现中自己完成所有解析工作,但这不是我想要的, 真的。

如果 URL 不包含值(就像为省略的参数传递 0 一样),有没有人知道将 OptionsEnum.None 传递到服务实现中的干净解决方案int 类型)?

我已经尝试过使用自定义 TypeConverter 实现,但即使那样似乎也行不通。查看 QueryStringConverter 的实现,它似乎总是会尝试自行转换 enum 类型。

最佳答案

好的,我已经找到了一个可重用的解决方案,并且不涉及为标志参数的类型切换到 string。不过,我希望有更简单的东西。无论如何,如果它对其他人有帮助,就在这里。

方法比较简单:

  • 将枚举包装在引用类型中。
  • 使用 TypeConverter 自定义将值从 string 转换为由 WCF 的 QueryStringConverter 实现的标志枚举的过程。
/// <summary>
/// Wraps a flags enum value.
/// </summary>
/// <remarks>
/// This class is meant to be used in conjunction with
/// <see cref="FlagsConverter{TFlags,TEnum}"/> and simply boxes an enum.
/// This is necessary in order to customize WCF's default behavior for enum
/// types (as implemented by <see cref="QueryStringConverter"/>) by using a
/// <see cref="TypeConverter"/>.
/// </remarks>
/// <devdoc>
/// We prefer this over using an 1-Tuple (<see cref="Tuple{T1} "/>) as it
/// allows us to add constraints on the type parameter. Also, the value
/// wrapped by a <see cref="Tuple{T1} "/> is read-only, which we don't want
/// here, as there is no reason to prevent [OperationContract] methods from
/// writing the wrapped <see cref="Value"/>.
/// </devdoc>
/// <typeparam name="TEnum">
/// The enum type. Must be attributed with <see cref="FlagsAttribute"/>.
/// </typeparam>
public abstract class Flags<TEnum>
where TEnum : struct, IConvertible
{
// Use a static c'tor to add run-time checks on the type parameter that
// cannot be checked at compile-time via constraints.
static Flags()
{
if (!typeof(TEnum).IsEnum)
{
throw new InvalidOperationException("T is not an enum");
}

if (!typeof(TEnum).IsDefined(typeof(FlagsAttribute), false))
{
throw new InvalidOperationException("T is not a flags enum");
}
}

/// <summary>
/// The enum value.
/// </summary>
public TEnum Value
{
get;
set;
}
}

/// <summary>
/// A <see cref="TypeConverter"/> implementation that converts from
/// <c>string</c> to <see cref="Flags{TEnum}"/>.
/// </summary>
/// <remarks>
/// <para>
/// Meant to be used in conjunction with <see cref="Flags{TEnum}"/>.
/// The purpose of this <see cref="TypeConverter"/> implementation is to
/// convert both <c>null</c> and the empty string to <c>default(TEnum)</c>
/// instead of throwing an exception. This way, a flags enum (wrapped in an
/// instance of <see cref="Flags{TEnum}"/>) can be used as the type of an
/// optional parameter in a RESTful WCF <c>OperationContract</c> method.
/// </para>
/// <para>
/// If the string value (as provided by <see cref="QueryStringConverter"/>)
/// is <c>null</c> or empty or can be successfully parsed into an enum
/// value of type <typeparamref name="TEnum"/>, this implementation will
/// provide an instance of <typeparamref name="TFlags"/>. Thus, there is no
/// need to check a <typeparamref name="TFlags"/>-typed value for
/// <c>null</c> within the implementation of an <c>OperationContract</c>
/// method.
/// </para>
/// </remarks>
/// <typeparam name="TFlags">
/// A sub-class of <see cref="Flags{TEnum}"/>. Must have a default c'tor.
/// </typeparam>
/// <typeparam name="TEnum">
/// The enum type. Must be attributed with <see cref="FlagsAttribute"/>.
/// </typeparam>
public class FlagsConverter<TFlags, TEnum> : TypeConverter
where TFlags : Flags<TEnum>, new()
where TEnum : struct, IConvertible
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string);
}

public override object ConvertFrom(ITypeDescriptorContext context,
CultureInfo culture, object value)
{
if (string.IsNullOrEmpty((string)value))
{
// The following line is what Flags<> and FlagsConverter<,> is
// all about: Provide the enum's default value (meaning no flag
// is set) for null and the empty string instead of passing it
// into Enum.Parse() (which results in an ArgumentException).
return new TFlags { Value = default(TEnum) };
}

// Otherwise, just pass the value on the Enum.Parse(), i.e. don't
// add any further changes to WCF's default behavior as implemented
// by QueryStringConverter.
return new TFlags { Value = (TEnum)Enum.Parse(typeof(TEnum), (string)value, true) };
}
}

这两个类现在可以用来解决这样的问题(重复使用原始问题中的示例):

[DataContract]
[Flags]
public enum OptionsEnum
{
[EnumMember]
None = 0,
[EnumMember]
MyOption1 = 1,
[EnumMember]
MyOption2 = 2,
[EnumMember]
MyOption3 = 4,
[EnumMember]
MyOption4 = 8
}

[DataContract]
[TypeConverter(typeof(FlagsConverter<MyOptionalFlags, OptionsEnum>))]
public class MyOptionalFlags
: Flags<OptionsEnum>
{
// We don't add anything here, as the whole purpose of this class is to
// wrap the OptionsEnum in a class that will be instantiated by the
// attributed FlagsConverter.
}

[ServiceContract]
public interface MyServiceContract
{
[OperationContract]
[WebInvoke(Method = "GET", UriTemplate = "resource?options={options}")]
void MyOperation(MyOptionalFlags options);
}

public class MyServiceContractImpl
: MyServiceContract
{
public void MyOperation(MyOptionalFlags options)
{
if (options.Value == OptionsEnum.None)
{
// We will now get here for requests that do not specify a
// value for the options parameter in the URL. Note that just
// like for an enum value, we don't have to check if options is
// null here.
}
}
}

关于c# - RESTful WCF 服务中标志枚举的默认值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34953030/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com