WCF: Exposing readonly DataMember properties without set?

38,440

Solution 1

Your "server-side" class won't be "made available" to the client, really.

What happens is this: based on the data contract, the client will create a new separate class from the XML schema of the service. It cannot use the server-side class per se!

It will re-create a new class from the XML schema definition, but that schema doesn't contain any of the .NET specific things like visibility or access modifiers - it's just a XML schema, after all. The client-side class will be created in such a way that it has the same "footprint" on the wire - e.g. it serializes into the same XML format, basically.

You cannot "transport" .NET specific know-how about the class through a standard SOAP-based service - after all, all you're passing around are serialized messages - no classes!

Check the "Four tenets of SOA" (defined by Don Box of Microsoft):

  1. Boundaries are explicit
  2. Services are autonomous
  3. Services share schema and contract, not class
  4. Compability is based upon policy

See point #3 - services share schema and contract, not class - you only ever share the interface and XML schema for the data contract - that's all - no .NET classes.

Solution 2

put DataMember attribute on a field not the property.

Remember thought, that WCF does not know encapsulation. Encapsulation is a OOP term, not a SOA term.

That said, remember that the field will be readonly for people using your class - anyone using the service will have full access to the field on their side.

Solution 3

There is a way to achieve this. But be warned that it directly violates the following principle cited in this answer:

"3. Services share schema and contract, not class."

If this violation does not concern you, this is what you do:

  1. Move the service and data contracts into a separate (portable) class library. (Let's call this assembly SomeService.Contracts.) This is how you'd define an immutable [DataContract] class:

    namespace SomeService.Contracts
    {
        [DataContract]
        public sealed class Foo
        {
            public Foo(int x)
            {
                this.x = x;
            }
    
            public int X
            {
                get
                {
                    return x;
                }
            }
    
            [DataMember]  // NB: applied to the backing field, not to the property!
            private readonly int x;
        }
    }
    

    Note that [DataMember] is applied to the backing field, and not to the corresponding read-only property.

  2. Reference the contract assembly from both your service application project (I'll call mine SomeService.Web) and from your client projects (mine is called SomeService.Client). This might result in the following project dependencies inside your solution:

    screenshot highlighting the project dependencies in Solution Explorer

  3. Next, when you add the service reference to your client project, make sure to have the option "reuse types" enabled, and ensure that your contract assembly (SomeService.Contracts) will be included in this:

    screenshot highlighting the relevant service reference setting

Voilà! Visual Studio, instead of generating a new Foo type from the service's WSDL schema, will reuse the immutable Foo type from your contract assembly.

One last warning: You've already strayed from the service principles cited in that other answer. But try not to stray any further. You might be tempted to start adding (business) logic to your data contract classes; don't. They should stay as close to dumb data transfer objects (DTOs) as you can manage.

Solution 4

I had some properties in a class in my service layer I wanted to pass over to Silverlight. I didn't want to create a whole new class.

Not really 'recommended', but this seemed the lesser of two evils to pass over the Total property to silverlight (solely for visual databinding).

public class PricingSummary
{
    public int TotalItemCount { get; set; } // doesnt ideally belong here but used by top bar when out of store area

    public decimal SubTotal { get; set; }
    public decimal? Taxes { get; set; }
    public decimal Discount { get; set; }
    public decimal? ShippingTotal { get; set; }
    public decimal Total
    {
        get
        {
            return + SubTotal
                   + (ShippingTotal ?? 0)
                   + (Taxes ?? 0)
                   - Discount;
        }
        set
        {
            throw new ApplicationException("Cannot be set");
        }
    }
}
Share:
38,440
stiank81
Author by

stiank81

System Developer mainly living in the .Net-world. Coding in C#, Javascript, html, css, ..

Updated on March 22, 2020

Comments

  • stiank81
    stiank81 about 4 years

    I have a server side class which I make available on the client side through a [DataContract]. This class has a readonly field which I'd like to make available through a property. However, I'm unable to do so because it doesn't seem that I'm allowed to add a [DataMember] property without having both get and set.

    So - is there a way to have a [DataMember] property without setter?

    [DataContract]
    class SomeClass
    {
        private readonly int _id; 
    
        public SomeClass() { .. }
    
        [DataMember]
        public int Id { get { return _id; } }        
    
        [DataMember]
        public string SomeString { get; set; }
    }
    

    Or will the solution be use the [DataMember] as the field - (like e.g. shown here)? Tried doing this too, but it doesn't seem to care the field is readonly..?

    Edit: Is the only way to make a readonly property by hacking it like this? (no - I don't want to do this...)

    [DataMember]
    public int Id
    {
        get { return _id; }
        private set { /* NOOP */ }
    }
    
  • stiank81
    stiank81 over 14 years
    Ah, yeah - like noted I tried setting the field as DataMember, but it was not exposed as readonly on the client side. But there's no way to make it readonly on the client side then?
  • stiank81
    stiank81 over 14 years
    What do you mean? The DataMember is in the DTO class available through a DataContract. I have a ServiceContract too, but that's not really related here - is it?
  • Krzysztof Kozmic
    Krzysztof Kozmic over 14 years
    no. READONLY is a C# term, not a SOA. You can't make part of XML readonly
  • stiank81
    stiank81 over 14 years
    That explains it very well. Thanks for a clarifying!
  • Krzysztof Kozmic
    Krzysztof Kozmic over 14 years
    it's a datacontract, not a service contract
  • Tom Lianza
    Tom Lianza almost 13 years
    The problem I've seen with this solution is that, because you can't use ISerializable and DataContract together, DataContract also defines your serialization. If you tried to serialize this object, it would throw an exception during deserialization. So, hack-wise the no-op seems the only option, versus throwing an exception.
  • atoumey
    atoumey almost 13 years
    This information is nice, but I don't think it answers the question directly.
  • ChrisG
    ChrisG almost 12 years
    Thanks. This seems like it's the best option other than creating a data-specific class... and for small apps, that's just too much extra code.
  • piers7
    piers7 over 11 years
    All of which illustrates how ridiculous the current situation is. Given the server-side class is only used to generate the contract, why require setters? An instance on the server could serialize just fine to the client without them, were it not for the DataContractSerializer being prissy. It's only the reverse scenario that would cause problems.
  • Paul Suart
    Paul Suart almost 10 years
    In this situation, implementing "Total" as an extension method works nicely (I understand that extension methods might not have been around when you wrote this post).
  • aruno
    aruno almost 10 years
    @PaulSuart I need it to be a property for databinding so it needs to be a property and there's no such thing as extension properties :-( but yes I do tend to forget about extension methods except when extending libraries
  • Ehsan Sajjad
    Ehsan Sajjad almost 9 years
    @Simon_Weaver i have FullName property with get like: [DataMember] public string FullName { get { return FirstName + " " + SurName; } } but it throws exception. ay solution ?
  • aruno
    aruno almost 9 years
    If FirstName and Surname are simple string properties this shouldn't throw an exception - maybe the exception is coming from somewhere else? Also I'd recommending doing (FirstName + " " + Surname).Trim() which gives you a fullname without spaces if both names weren't provided. What exactly is the exception you get?
  • julealgon
    julealgon about 8 years
    Agreed with @atoumey. Marc, could you elaborate on a possible solution to OP's problem?