|
|
Browse by Tags
All Tags » Service Model » Contracts (RSS)
-
When using a typed contract, incoming messages on the server are shredded on your behalf to be turned into method calls and parameters. Ordinarily, the particular method call selected for an application messages will have the same parameterized contract as the message. This allows the transformation between messages and parameters to be made with a high degree of fidelity. However, operations also permit fault responses in addition to the normal application response. The parameterized fault contract is going to look nothing like the standard application contract. Therefore, there's really no transformation that will take you from the fault message to the same parameterized contract in a way that makes any sense. The response that comes back from the server is unrepresentable using the standard data structure that the application is expecting to receive for an application response. This is why with a typed contract fault messages have to be expressed as an exceptional condition. Exceptions tend to transform the message with a much lower fidelity to the original content. With an untyped contract, incoming messages on the server are not shredded on your behalf but rather preserved in their entirety. You can think about this as performing the transformation between messages and parameters with perfect fidelity since the parameter is equal to the message. Similarly, any message response, whether it's a fault response or a normal application response, is also going to be representable with perfect fidelity. Both types of responses have the same format with an untyped contract so the application can handle them equally well. This is why with an untyped contract fault messages are preserved as messages. One of the major reasons for using untyped contracts is to have great fidelity with the wire. It wouldn't make sense to force the application to lose that fidelity for a certain class of messages. If you choose to in your application though, you can still run the same exception machinery. For details, read some of the past articles on creating and consuming faults . Next time: Streaming Web Content Read More...
|
-
How do I write a contract for a wrapped message in the default namespace? I've written a quick sample to demonstrate what happens when you write the contract without taking any namespaces into account. [ServiceContract] public interface IService { [OperationContract] [WebInvoke(BodyStyle = WebMessageBodyStyle.Wrapped, UriTemplate = "/" )] void ProcessRequest( string data); } public class Service : IService { public void ProcessRequest( string data) { Console.WriteLine(OperationContext.Current.RequestContext.RequestMessage); } } class Program { static void Main( string [] args) { string address = "http://localhost:8000/" ; WebServiceHost host = new WebServiceHost( typeof (Service), new Uri(address)); host.Open(); ChannelFactory<IService> factory = new ChannelFactory<IService>( new WebHttpBinding()); factory.Open(); IService proxy = factory.CreateChannel( new EndpointAddress(address)); using ( new OperationContextScope((IContextChannel)proxy)) { OperationContext.Current.OutgoingMessageHeaders.To = new Uri(address); proxy.ProcessRequest( "data" ); } factory.Close(); host.Close(); Console.ReadLine(); } } Running the sample reveals that the message wrapper gets an unpleasant namespace instead of the default namespace. < ProcessRequest xmlns ="http://tempuri.org/" > < data > data </ data > </ ProcessRequest > By looking at the metadata, you could figure out where this namespace was coming from using the custom namespace sample I published earlier. In this case the problem is with the operation wrapper, which comes from the service contract. Setting the service contract to have a namespace of "" gets us the desired default namespace. Next time: Using Faults with Untyped Messages Read More...
|
-
It's bad practice to use system types when defining an operation contract. A system type is often a complex composition of primitive types that has no direct analog in other implementations. By using a system type, you bind your service to the particular implementation used by that type, which effectively ends any chance of having an easily interoperable service. For example, a contract containing an IPAddress seems innocuous. [OperationContract] string LookupHostName(IPAddress address); However, that reference translates into a significantly sized chunk of metadata for defining the IPAddress type. < xs:schema xmlns:tns ="http://schemas.datacontract.org/2004/07/System.Net" elementFormDefault ="qualified" targetNamespace ="http://schemas.datacontract.org/2004/07/System.Net" xmlns:xs ="http://www.w3.org/2001/XMLSchema" > < xs:import namespace ="http://schemas.datacontract.org/2004/07/System.Net.Sockets" /> < xs:import namespace ="http://schemas.microsoft.com/2003/10/Serialization/Arrays" /> < xs:complexType name ="IPAddress" > < xs:sequence > < xs:element name ="m_Address" type ="xs:long" /> < xs:element xmlns:q1 ="http://schemas.datacontract.org/2004/07/System.Net.Sockets" name ="m_Family" type ="q1:AddressFamily" /> < xs:element name ="m_HashCode" type ="xs:int" /> < xs:element xmlns:q2 ="http://schemas.microsoft.com/2003/10/Serialization/Arrays" name ="m_Numbers" nillable ="true" type ="q2:ArrayOfunsignedShort" /> < xs:element name ="m_ScopeId" type ="xs:long" /> </ xs:sequence > </ xs:complexType > < xs:element name ="IPAddress" nillable ="true" type ="tns:IPAddress" /> </ xs:schema > Notice the inclusion of System.Net.Sockets.AddressFamily. This schema is only the tip of the iceberg. It continues on and becomes much, much worse. Nevertheless, there are times when you can accept putting interoperability aside and have a reason to bind your service to a system type. The metadata still leaves you with the problem of generating a working proxy. Although some system types are recognized and filtered out, other types slip through and will lead to compilation errors due to conflicting type definitions. The standard referencing mechanism of svcutil works with system types as well as your types. You just need to find and point svcutil at the appropriate dll so that it can compute the types to exclude. Since IPAddress is defined in system.dll, if you wanted to resolve a conflict, you Read More...
|
-
The address filter mode that we looked at last time solved the problem of funneling all of the messages with a given prefix address to our service instance. Changing the filter mode still left us with the problem of dispatching from that universal contract to all of the logical operations that live inside the address space. This is exactly the problem that UriTemplate solves. By combining templates and WebServiceHost, both of the problems get taken care of for us. Here is an equivalent contract and service implementation with some more semantics filled in for a particular application. All I've done is pick out part of the address space that I want to assign some implementation to. [ServiceContract] public interface IService2 { [OperationContract] [WebGet(UriTemplate = "/resource/{index}" )] string Get( string index); [OperationContract] [WebInvoke(UriTemplate = "/resource" )] void Add( string value ); } public class Service2 : IService2 { public string Get( string index) { Console.WriteLine( "Get {0} {1}" , WebOperationContext.Current.IncomingRequest.Method, OperationContext.Current.IncomingMessageHeaders.To); return null ; } public void Add( string value ) { Console.WriteLine( "Add {0} {1}" , WebOperationContext.Current.IncomingRequest.Method, OperationContext.Current.IncomingMessageHeaders.To); } } Hosting this service is basically the same. I can take out the endpoint definition because that gets inferred automatically. WebServiceHost host = new WebServiceHost( typeof (Service2), new Uri( "http://localhost:8000/" )); host.Open(); Client(); Console.ReadLine(); host.Close(); Finally, I can use the exact same client code as last time even though in the service I've changed my way of writing the service from a centralized approach to an address-based approach. ChannelFactory<IRequestChannel> factory = new ChannelFactory<IRequestChannel>( new WebHttpBinding()); factory.Open(); IRequestChannel proxy = factory.CreateChannel( new EndpointAddress( "http://localhost:8000/" )); using ( new OperationContextScope((IContextChannel)proxy)) { Message request = Message.CreateMessage(MessageVersion.None, string .Empty, "data" ); request.Headers.To = new Uri( "http://localhost:8000/resource" ); WebOperationContext.Current.OutgoingRequest.Method = "POST" ; proxy.Request(request); } using ( new OperationContextScope((IContextChannel)proxy)) { Message request = Message.CreateMessage(MessageVersion.None, string .Empty); request.Headers.To = new Uri( "http://localhost:8000/resource/1" Read More...
|
-
Some tips for building support for versioning into the naming of data contracts. First, the primary route for versioning should be through the namespace part of the contract rather than the member name part of the contract. Versioning the contract through member names tends to leak across the service boundary more forcefully. The programming experience of the service often makes a member name directly visible while a namespace is more or less invisible. Second, choose a single consistent scheme for identifying the version. Two popular schemes are the date of the contract and a sequential numbering system of major and minor versions. Both schemes provide the basic element required of a versioning identity, which is an unambiguous total order among the different versions. However, multiple schemes should not be mixed together for a single contract and preferably not for a single system as well. The date scheme, http://company.com/year/month/name, has issues around granularity but can be very evocative since you probably already associate dates in your mind with other events. The issue with granularity is that you have to plan ahead for a maximum update frequency. In the previous example, two updates in the same month would collide with the same name, suggesting that a contract that is updated frequently might include additional levels of refinement, such as the day of the month. However, unnecessarily fine granularity makes the name cumbersome. The numbering scheme, http://company.com/major/minor/name, gives less of a clue about what the version corresponds to but has fewer issues with granularity. Updates can happen as frequently as you want since you can just keep picking new numbers. However, you still have to give some thought to granularity when deciding how many numbering components to include. For example, a single version number may be sufficient if no distinction is needed between major and minor updates. Next time: Finding a Client Channel Read More...
|
-
I've previously talked about using WSDL extensions to provide custom modifications to the WSDL import and export process. Making modifications to an existing WSDL document or to the existing WSDL processing is a great way to make small changes when the default behavior almost gets you to where you want. However, you may have noticed that these mechanisms are a little cumbersome for providing a complete overhaul of metadata; that's because a WSDL extension is primarily a hook to supply user extensibility before or after certain steps in the WSDL import or export process. When making large-scale modifications, another tool in your toolbox is to entirely jump out of the built-in WSDL processing. The ExternalMetadataLocation property on the ServiceMetadataBehavior is one of the ways that you can opt out of the standard process. By supplying an external metadata location, you provide to clients a reference to an independent location for retrieving metadata. You might base the contents of that external metadata on the metadata files normally generated by the service (the disco.exe tool in the Windows SDK is a good way to generate all of those metadata files at once) or you might go completely wild and make the external metadata have no relationship at all to the normally generated metadata files. Next time: Avoid Exceptions in Faults Read More...
|
-
What's the difference between the Name and ConfigurationName on service contracts and behaviors? The Name property sets the name of the service in metadata while the ConfigurationName property sets the name of the service in configuration. Metadata is the part of the service description that is transmitted to others when they interrogate your service. Configuration is the purely local settings for controlling the service. Since Name is more commonly used, I'll just run through a quick example focusing on setting ConfigurationName instead. Here I've got a service contract and implementation setting both the Name and ConfigurationName properties. [ServiceContract(Name= "NotIService" , ConfigurationName= "IService" )] public interface IMyService { [OperationContract] void DoNothing(); } [ServiceBehavior(Name = "NotService" , ConfigurationName = "Service" )] public class MyService : IMyService { public void DoNothing() { } } In my app.config file, the way I'd refer to that service and service endpoint is by the ConfigurationName. < service name ="Service" > < endpoint address ="http://localhost:8000/" binding ="basicHttpBinding" bindingConfiguration ="myBindingConfiguration" contract ="IService" /> </ service > On the other hand, everywhere in the metadata, generated proxy, and even the generated proxy configuration file the service will appear as NotIService and NotService. Next time: Disabling the Visual Studio Service Host Read More...
|
-
Why does a data contract with private or internal members generate a proxy with public fields? The obvious answer is that the representation for data contracts doesn't contain information about member visibility but that just leads to the question of why the information isn't preserved by the representation. If we take a data contract that contains both public and private members, [DataContract] class Data { [DataMember] public int i; [DataMember] private string s; } Then, the type representation used to generate a proxy is based on an XML schema. < xs:schema xmlns:tns ="http://schemas.datacontract.org/2004/07/" elementFormDefault ="qualified" targetNamespace ="http://schemas.datacontract.org/2004/07/" xmlns:xs ="http://www.w3.org/2001/XMLSchema" > < xs:complexType name ="Data" > < xs:sequence > < xs:element minOccurs ="0" name ="i" type ="xs:int" /> < xs:element minOccurs ="0" name ="s" nillable ="true" type ="xs:string" /> </ xs:sequence > </ xs:complexType > < xs:element name ="Data" nillable ="true" type ="tns:Data" /> </ xs:schema > A schema is a type system that is independent of the type system used by the proxy and has no concept of member visibility. However, by saying that a member is part of the data contract, you've effectively said that that member is as intrinsically part of the data as any other public facing member. Member visibility is a facet of information hiding to suppress details that are not needed by other parts of the system, but a data member by definition is needed by other parts of the system. Therefore, regardless of how one of the parties has chosen to represent that data, it's more likely than not that the other side will need to manipulate that data to uphold the contract. Next time: Generating Types with Lists Read More...
|
-
I was asked to share a list of best practices I wrote for data contracts and data members so here it is: Do apply the DataMember attribute only to properties rather than the corresponding fields. Do not use type inheritance to version a data contract. Either modify the existing type or create an entirely new type. Do not add or remove enumeration members between versions. Do not change the name or namespace of a data contract or data members between versions. Do not change the order of data members between versions. Do set the IsRequired property to false on data members added in later versions. Do not remove data members in later versions even if they are marked as not required. Do support IExtensibleDataObject on data contracts to support future compatibility with extended types. Do apply the DataMember attribute only to public members. Serialization of private data members is not supported for partial trust environments. Do not rely on constructor visibility, constructor link demands, constructor security actions, or validation checks in the constructor to make a DataContract type safe for partially trusted callers. DataContractSerializer does not invoke the constructor when initializing an object. Next time: Using Call Context Initializers for Cleanup Read More...
|
-
This article is primarily an introduction on protecting message data since the topic overall seems to cause some confusion. The source of confusion is what it means for a service to define a contract for protecting data. Data protection flows from two different directions and at a variety of different scopes. The service can define a minimum standard of protection at various contract scopes. Service contracts cover all of the application data exchanged by the service Operation contracts cover all of the application data exchanged for that particular operation Message and fault contracts cover all of the application data contained in those particular messages Message body and message header contracts cover those particular parts of the message This minimum standard appears as the ProtectionLevel on a contract and comes in three flavors: no protection, protected by digital signing, and protected by encryption (the use of encryption implies a signature as well). Signing prevents people from tampering with the messages while encryption prevents people from reading the messages. These contracts define the application messages that are exchanged. Depending on the protocols you make use of, any number of infrastructure headers or messages may need to be inserted into the data stream to facilitate the exchange of your application messages. This infrastructure data can have its own independent rules for protection. The ProtectionLevel truly is a minimum standard. It is ok for your application to receive messages that are protected better than the minimum standard defines. You may even send messages that are protected better than the minimum standard due to the other direction for flowing data protection. Data can be protected at the wire level by using security integrated with the network transport. For example, if you send data using HTTPS, then there is a level of protection that HTTPS must apply to your messages to transport them even if your contract specified a lower standard. The net protection level is the maximum of that provided by the service and provided by the transport. As long as for every piece of data at every scope that maximum is greater than your minimum standard, then everything works. If that maximum fails to meet your minimum standard, then the service will refuse to send or receive those messages. Next time: Embedding Arbitrary XML in Faults Read More...
|
-
Why do the guids in my contract turn into strings when generating a client? You're probably mixing different types of serializers between the client and service. There's nothing wrong with this and the generated client will work correctly but you don't get the user-friendly types. To see why, let's look at the metadata. A guid in a contract with the DataContractSerializer generates a type in the http://schemas.microsoft.com/2003/10/Serialization/ namespace that looks like this: < xs:element name ="guid" nillable ="true" type ="tns:guid" /> < xs:simpleType name ="guid" > < xs:restriction base ="xs:string" > < xs:pattern value ="[\da-fA-F]{8}-[\da-fA-F]{4}-[\da-fA-F]{4}-[\da-fA-F]{4}-[\da-fA-F]{12}" /> </ xs:restriction > </ xs:simpleType > On the other hand, a guid in a contract with the XmlSerializer generates a type in the http://microsoft.com/wsdl/types/ namespace that looks like this: < xs:simpleType name ="guid" > < xs:restriction base ="xs:string" > < xs:pattern value ="[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}" /> </ xs:restriction > </ xs:simpleType > These generated types are needed because a guid is not a primitive type. DataContractSerializer came after XmlSerializer so it recognizes both definitions but XmlSerializer has to rely on the schema when it sees a DataContractSerializer guid. Since the schema is based on a string type, the generated client field is a string. The same thing happens with other serializers that don't know how to map a particular schema pattern to a user-friendly type. Next time: TCP Throttling Read More...
|
-
How do I add custom annotations to the contracts that are generated from WSDL? You first need to start with an IWsdlImportExtension. Your extension gets called each time a contract is discovered during import. Processing happens in multiple passes so we aren't quite ready to mess with generated contract yet. Instead, this is the opportunity to setup some deferred work that can be run once the contract is fully resolved. class MyWsdlImporter : IWsdlImportExtension { public void BeforeImport(ServiceDescriptionCollection wsdlDocuments, XmlSchemaSet xmlSchemas, ICollection<XmlElement> policy) { } public void ImportContract(WsdlImporter importer, WsdlContractConversionContext context) { context.Contract.Behaviors.Add( new MyServiceContractGenerator()); foreach (Operation operation in context.WsdlPortType.Operations) { OperationDescription description = context.Contract.Operations.Find(operation.Name); if (description != null ) { description.Behaviors.Add( new MyOperationContractGenerator()); } } } public void ImportEndpoint(WsdlImporter importer, WsdlEndpointConversionContext context) { } } You'd probably want to be a little bit more discriminating about which contracts you attach to and you'd probably want to actually pass some data to the contract generator so that it knows how to modify the contracts. Let's ignore those little problems and look at the contract generators. The contract generator gets invoked once it's time to actually mess with the generated contract. class MyServiceContractGenerator : IServiceContractGenerationExtension, IContractBehavior { public void GenerateContract(ServiceContractGenerationContext context) { // muck with generated service contract } #region IContractBehavior Members ... #endregion } class MyOperationContractGenerator : IOperationContractGenerationExtension, IOperationBehavior { public void GenerateOperation(OperationContractGenerationContext context) { // muck with generated operation contract } #region IOperationBehavior Members ... #endregion } The IWsdlImportExtension plugs in through the configuration file of the WSDL importer (typically svcutil.exe). < client > < metadata > < wsdlImporters > < extension type =" ... " /> </ policyImporters > </ metadata > </ client > You should be able to figure out how the reverse process works starting from some strategic examination of IWsdlExportExtension. Next time: A Proxy Proxy Factory Read More...
|
-
How do I figure out during dispatch whether a request is destined to be a metadata request or a normal application request? The reason you might care whether a request in flight is for metadata or not is because of security policy. You might want to permit lots of people to access your metadata but be very strict about who can call application methods. If you're a security routine that intercepts all sorts of different calls, then you need to know what type of call is currently happening to make the right decision. Otherwise, you don't know who to apply scrutiny to and who to just let in. In the context of the currently in flight operation, there are three pieces of data that will help you figure this out. If you see all three signs, then you know that the request is going to be for metadata retrieval. Check the contract name of the endpoint that the current operation is being dispatched to. It should be IMetadataExchange. Just in case someone defines a different contract with the same name, also check the contract namespace. It should be http://schemas.microsoft.com/2006/04/mex. Finally, check the action of the operation. If it's an operation that does metadata transfer, then the action should be http://schemas.xmlsoap.org/ws/2004/09/transfer/Get/. Next time: Silent Security Failures Read More...
|
-
Back when I did an overview of custom namespaces , I omitted any namespace declarations that wouldn't appear in the final metadata. One of those declarations is the default namespace for data contracts. I had two example data contracts, one for faults and one for normal messages, although they actually work exactly the same in practice. [DataContract(Namespace = "http://example.com/faultcontract/datacontract" )] public class MyFault { [DataMember] public string detail; } [DataContract(Namespace = "http://example.com/datacontract" )] public class MyData { [DataMember] public string data; } These data contracts include an explicitly declared XML namespace. However, you can modify the test program in the earlier article to see what happens when those namespace declarations are removed. What you end up with is an ugly looking namespace based on a fixed prefix http://schemas.datacontract.org/2004/07/ and a suffix that is the CLR namespace. If you want to get rid of that default namespace for every data contract you define, then you can add a ContractNamespaceAttribute to the assembly. This attribute defines the default XML namespace for data contracts that are located in a particular CLR namespace. You can retrieve a ContractNamespaceAttribute in the standard way for assembly attributes by calling GetCustomAttributes on the assembly with the ContractNamespaceAttribute type. Next time: Configuring SSL Certificates for Vista Read More...
|
-
I've got a sessionful contract that I want to use with HTTP. How do I get the HTTP transport to produce a sessionful channel shape? The basic design principle of channels is that they produce whatever channel shape is their natural message exchange pattern. For HTTP, the natural message exchange pattern is request-reply. This means that if you want any other channel shape, then you need to apply a layered channel that changes the message exchange pattern. That is the approach regardless of whether you want to change the channel shape to one-way, duplex, or a sessionful channel. There are no built-in HTTP specific additions to create sessionful channels. We have a sample channel that demonstrates creating a session based on HTTP cookies . There are several general-purpose protocol channels that provide sessions, such as security and reliable messaging. However, this entire line of conversation tends to indicate a fundamental flaw in thinking. A session has the semantic meaning of correlating messages together according to some principle of relationship. Sessionful services use the relationship to treat the session of messages as a connected unit. The meaning of that session ought to be a more significant factor than whether some contract has been previously declared to know about sessions. If you are scrounging around to come up with any possible session to get a service working, then something is probably wrong. Next time: XML Support Read More...
|
|
|
|