WCF: Exposing readonly DataMember properties without set?
Asked Answered
T

5

63

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 */ }
}
Thermostatics answered 9/12, 2009 at 13:2 Comment(1)
Your idea of a NOOP setter would result in that property not being properly transferred if you're using WCF on the client side: in DataContract deserialization, the class is instantiated without calling any constructor, and the setters of any DataMember properties are then passed whatever was returned by the getter of that property at serialization. So your NOOP setter would discard the property, leaving its value as default at deserialization. Instead, write a real, working private setter, or mark the backing variable as the DataMember instead of marking the property.Chau
I
50

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.

Infancy answered 9/12, 2009 at 13:21 Comment(4)
That explains it very well. Thanks for a clarifying!Thermostatics
This information is nice, but I don't think it answers the question directly.Damick
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.Fronde
Agreed with @atoumey. Marc, could you elaborate on a possible solution to OP's problem?Amathiste
Z
10

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.

Zavras answered 9/12, 2009 at 13:11 Comment(3)
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?Thermostatics
no. READONLY is a C# term, not a SOA. You can't make part of XML readonlyZavras
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.Tad
S
9

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.

Surat answered 23/4, 2015 at 17:56 Comment(0)
C
8

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");
        }
    }
}
Chemoreceptor answered 7/7, 2010 at 5:26 Comment(5)
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.Deyo
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).Peel
@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 librariesChemoreceptor
@Chemoreceptor i have FullName property with get like: [DataMember] public string FullName { get { return FirstName + " " + SurName; } } but it throws exception. ay solution ?Quipu
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?Chemoreceptor
T
-3

Define the Service contract (Interface) Before implementing the contract using the class.

Tilefish answered 9/12, 2009 at 13:8 Comment(2)
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?Thermostatics
it's a datacontract, not a service contractZavras

© 2022 - 2024 — McMap. All rights reserved.