2016-01-22 35 views
1

WCF支持使用在UriTemplate內使用FlagsAttribute作爲參數標記的枚舉類型。就像這樣:RESTful WCF服務中標誌枚舉的默認值

[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

所有這些作品真的很好,只要網址實際上包含options參數的值。但是,如果我要求的資源,而不在URL中指定的值,如:

GET /resource

我得到的消息值異常不能爲空\ r \ n參數名:價值 和以下堆棧跟蹤:

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在這種情況下通過nullEnum.Parse(...)。因此MyServiceContract的執行將不會執行。

當然,我可以切換到string作爲options參數的類型,並在服務實現中自己完成所有解析工作,但這不是我想要的,真的。

有誰知道一個乾淨的解決方案已經OptionsEnum.None傳遞到服務實現,如果網址不包含值(就像0傳遞類型int的省略參數)?

我已經嘗試使用自定義TypeConverter實現,但即使這似乎並不奏效。看看QueryStringConverter的實現,它似乎總是試圖自行轉換enum類型。

回答

3

好吧,我發現了一個可重複使用的解決方案,並且不涉及爲flags參數的類型切換到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. 
     } 
    } 
}