WCF: Exposing readonly DataMember properties without set?
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):
- Boundaries are explicit
- Services are autonomous
- Services share schema and contract, not class
- 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:
-
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. -
Reference the contract assembly from both your service application project (I'll call mine
SomeService.Web
) and from your client projects (mine is calledSomeService.Client
). This might result in the following project dependencies inside your solution: -
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:
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");
}
}
}
stiank81
System Developer mainly living in the .Net-world. Coding in C#, Javascript, html, css, ..
Updated on March 22, 2020Comments
-
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 over 14 yearsAh, 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 over 14 yearsWhat 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 over 14 yearsno. READONLY is a C# term, not a SOA. You can't make part of XML readonly
-
stiank81 over 14 yearsThat explains it very well. Thanks for a clarifying!
-
Krzysztof Kozmic over 14 yearsit's a datacontract, not a service contract
-
Tom Lianza almost 13 yearsThe 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 almost 13 yearsThis information is nice, but I don't think it answers the question directly.
-
ChrisG almost 12 yearsThanks. 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 over 11 yearsAll 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 almost 10 yearsIn 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 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 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 almost 9 yearsIf
FirstName
andSurname
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 about 8 yearsAgreed with @atoumey. Marc, could you elaborate on a possible solution to OP's problem?