I think this is expected; see e.g.

[link:blogs.msdn.com]

for more on known types and the DataContractSerializer. (Note that your LessonPaymentStatus is a discriminated union, which effectively is a class hierarchy. You might make it an enum instead by making it e.g. "|Paid = 0" and "|Unpaid =1".)

Additionally, you typically should avoid both lists and tuples in 'interop code', since these are F#-specific data types. Right now your data is a list of tuples; choosing e.g. an array of Foo (where Foo is a new DataContract class type with the LessonPaymentStatus & string members) will provide easier interop both with C# as well as in terms of the wsdl/xml interop.

By on 4/25/2009 4:41 AM ()

Thanks for your thoughts, however I'd expect clear boundaries where F#-specific data types are supported and where don't. For instance if I have both server and client in F#, ideally I'd prefer using most of the wsdl/xml support (in terms of type system) not to loose F# semantics.

I also started playing with Enums and discovered

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[DataContract] [Flags]

type LessonPaymentStatus =
 | [EnumMember] Paid = 1
 | [EnumMember] Unpaid = 2

[DataContract]                      
type public LessonInfo() = 
    let mutable _paymentStatus : LessonPaymentStatus = LessonPaymentStatus.Unpaid

    let mutable _name : String = String.Empty

    [DataMember]
    member public x.PaymentStatus
        with get() = _paymentStatus
        and set(value) = _paymentStatus <- value

Throws an exception System.Runtime.Serialization.SerializationException: Enum value 'Unpaid' is invalid for type 'Contracts+LessonPaymentStatus' and cannot be serialized. Ensure that the necessary enum values are present and are marked with EnumMemberAttribute attribute if the type has DataContractAttribute attribute.

IL code shows that F# compiler generates

1
2
3
4
5
6
.. 
.field assembly valuetype Contracts/LessonPaymentStatus _paymentStatus@18

...
  IL_000f:  stfld      valuetype Contracts/LessonPaymentStatus Contracts/LessonInfo::_paymentStatus@18
...

which is seems correct.

By on 4/25/2009 1:06 PM ()

If you know the client will also be F#, then you may want to check out NetDataContractSerializer, a la

[link:www.pluralsight.com]

Anyway, I do not repro your exception with a simple example; here is my code, feel free to share more of yours:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
 

#light

open System
open System.Runtime.Serialization 

[<DataContract>]
type LessonPaymentStatus = 
    | [<EnumMember>] Paid = 1 
    | [<EnumMember>] Unpaid = 2 

[<DataContract>] 
type public LessonInfo() = 
    let mutable _paymentStatus : LessonPaymentStatus = LessonPaymentStatus.Unpaid 
    let mutable _name : String = String.Empty 
    [<DataMember>] 
    member x.PaymentStatus 
        with get() = _paymentStatus 
        and set(value) = _paymentStatus <- value

// quickie test
let dcs = new DataContractSerializer(typeof<LessonInfo>)
let li = new LessonInfo()
li.PaymentStatus <- LessonPaymentStatus.Paid 
dcs.WriteObject(new System.IO.MemoryStream(), li)  // ensure does not throw

// WCF scenario

open System.ServiceModel 

[<ServiceContract>]
type IContract =
    [<OperationContract>]
    abstract GetLessonInfo : unit -> LessonInfo

type MyService() =
    interface IContract with
        member this.GetLessonInfo() =
            let li = new LessonInfo()
            li.PaymentStatus <- LessonPaymentStatus.Unpaid 
            li
            
[<EntryPoint>]
let Main(args) =
    let binding = new WSHttpBinding()
    let addr = "http://localhost/Blah"
    let service = new ServiceHost(typeof<MyService>, [|new Uri(addr)|])
    service.AddServiceEndpoint(typeof<IContract>, binding, "") |> ignore
    service.Open()
    
    let cf = new ChannelFactory<IContract>(binding, new EndpointAddress(addr))
    let client = cf.CreateChannel()
    let channel = client :?> IClientChannel 
    channel.Open()
    let li = client.GetLessonInfo()
    printfn "%A" li.PaymentStatus 
    channel.Close()
    
    service.Close()
    0

By on 4/25/2009 2:06 PM ()

Here is my test in C#

1
2
3
4
5
6
7
var dcs = new DataContractSerializer(typeof(Contracts.LessonInfo));
            var li = new Contracts.LessonInfo
                         {
                             PaymentStatus = Contracts.LessonPaymentStatus.Paid
                         };

            dcs.WriteObject(new System.IO.MemoryStream(), li); 

And I get an exception

Enum value 'Paid' is invalid for type 'Contracts+LessonPaymentStatus' and cannot be serialized. Ensure that the necessary enum values are present and are marked with EnumMemberAttribute attribute if the type has DataContractAttribute attribute.

Contracts in F# project are defined as

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#light

module Contracts

open System
open System.Collections
open System.Collections.Generic
open System.Runtime.Serialization
open System.ServiceModel

[<DataContract>] [<Flags>]
type public LessonPaymentStatus =
 | Paid = 1
 | Unpaid = 2

[<DataContract>]                      
type public LessonInfo() = 
    let mutable _paymentStatus : LessonPaymentStatus = LessonPaymentStatus.Unpaid
    let mutable _name : String = String.Empty

    [<DataMember>]
    member public x.PaymentStatus
        with get() = _paymentStatus
        and set(value) = _paymentStatus <- value
By on 4/26/2009 2:31 AM ()

Check here for instructions on posting code that doesn't get mangled:

[link:cs.hubfs.net]

I think the only difference between my code and yours was yours had a "Flags" attribute on the enum (which you don't want) - have you tried removing it?

By on 4/26/2009 3:14 AM ()
IntelliFactory Offices Copyright (c) 2011-2012 IntelliFactory. All rights reserved.
Home | Products | Consulting | Trainings | Blogs | Jobs | Contact Us | Terms of Use | Privacy Policy | Cookie Policy
Built with WebSharper