2 // ContractDescriptionGenerator.cs
5 // Atsushi Enomoto <atsushi@ximian.com>
7 // Copyright (C) 2005-2007 Novell, Inc. http://www.novell.com
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 using System.Collections;
30 using System.Collections.Generic;
31 using System.Collections.ObjectModel;
32 using System.Net.Security;
33 using System.Reflection;
34 using System.Runtime.Serialization;
35 using System.ServiceModel;
36 using System.ServiceModel.Channels;
38 namespace System.ServiceModel.Description
40 internal static class ContractDescriptionGenerator
42 public static OperationContractAttribute
43 GetOperationContractAttribute (MethodBase method)
45 object [] matts = method.GetCustomAttributes (
46 typeof (OperationContractAttribute), false);
47 if (matts.Length == 0)
49 return (OperationContractAttribute) matts [0];
52 static void GetServiceContractAttribute (Type type, Dictionary<Type,ServiceContractAttribute> table)
54 for (; type != null; type = type.BaseType) {
55 foreach (ServiceContractAttribute i in
56 type.GetCustomAttributes (
57 typeof (ServiceContractAttribute), true))
59 foreach (Type t in type.GetInterfaces ())
60 GetServiceContractAttribute (t, table);
63 public static Dictionary<Type, ServiceContractAttribute> GetServiceContractAttributes (Type type)
65 Dictionary<Type, ServiceContractAttribute> table = new Dictionary<Type, ServiceContractAttribute> ();
66 GetServiceContractAttribute (type, table);
71 public static ContractDescription GetContract (
73 return GetContract (contractType, (Type) null);
77 public static ContractDescription GetContract (
78 Type contractType, object serviceImplementation) {
79 if (serviceImplementation == null)
80 throw new ArgumentNullException ("serviceImplementation");
81 return GetContract (contractType,
82 serviceImplementation.GetType ());
85 public static MessageContractAttribute GetMessageContractAttribute (Type type)
87 for (Type t = type; t != null; t = t.BaseType) {
88 object [] matts = t.GetCustomAttributes (
89 typeof (MessageContractAttribute), true);
91 return (MessageContractAttribute) matts [0];
97 public static ContractDescription GetContract (
98 Type givenContractType, Type givenServiceType)
100 // FIXME: serviceType should be used for specifying attributes like OperationBehavior.
102 Type exactContractType = null;
103 ServiceContractAttribute sca = null;
104 Dictionary<Type, ServiceContractAttribute> contracts =
105 GetServiceContractAttributes (givenServiceType ?? givenContractType);
106 if (contracts.ContainsKey(givenContractType)) {
107 exactContractType = givenContractType;
108 sca = contracts[givenContractType];
110 foreach (Type t in contracts.Keys)
111 if (t.IsAssignableFrom(givenContractType)) {
113 throw new InvalidOperationException("The contract type of " + givenContractType + " is ambiguous: can be either " + exactContractType + " or " + t);
114 exactContractType = t;
119 throw new InvalidOperationException (String.Format ("Attempted to get contract type from '{0}' which neither is a service contract nor does it inherit service contract.", givenContractType));
121 string name = sca.Name ?? exactContractType.Name;
122 string ns = sca.Namespace ?? "http://tempuri.org/";
124 ContractDescription cd =
125 new ContractDescription (name, ns);
126 cd.ContractType = exactContractType;
127 cd.CallbackContractType = sca.CallbackContract;
128 cd.SessionMode = sca.SessionMode;
129 if (sca.ConfigurationName != null)
130 cd.ConfigurationName = sca.ConfigurationName;
132 cd.ConfigurationName = exactContractType.FullName;
133 if (sca.HasProtectionLevel)
134 cd.ProtectionLevel = sca.ProtectionLevel;
136 // FIXME: load Behaviors
137 MethodInfo [] contractMethods = exactContractType.GetMethods ();
138 MethodInfo [] serviceMethods = contractMethods;
139 if (givenServiceType != null && exactContractType.IsInterface) {
140 serviceMethods = givenServiceType.GetInterfaceMap (exactContractType).TargetMethods;
143 for (int i = 0; i < contractMethods.Length; ++i)
146 MethodInfo mi = contractMethods [i];
147 OperationContractAttribute oca = GetOperationContractAttribute (mi);
150 MethodInfo end = null;
151 if (oca.AsyncPattern) {
152 if (String.Compare ("Begin", 0, mi.Name,0, 5) != 0)
153 throw new InvalidOperationException ("For async operation contract patterns, the initiator method name must start with 'Begin'.");
154 end = givenContractType.GetMethod ("End" + mi.Name.Substring (5));
156 throw new InvalidOperationException ("For async operation contract patterns, corresponding End method is required for each Begin method.");
157 if (GetOperationContractAttribute (end) != null)
158 throw new InvalidOperationException ("Async 'End' method must not have OperationContractAttribute. It is automatically treated as the EndMethod of the corresponding 'Begin' method.");
160 OperationDescription od = GetOrCreateOperation (cd,
164 end != null ? end.ReturnType : null);
169 // FIXME: enable this when I found where this check is needed.
171 if (cd.Operations.Count == 0)
172 throw new InvalidOperationException (String.Format ("The service contract type {0} has no operation. At least one operation must exist.", contractType));
177 static OperationDescription GetOrCreateOperation (
178 ContractDescription cd, MethodInfo mi, MethodInfo serviceMethod,
179 OperationContractAttribute oca,
180 Type asyncReturnType)
182 string name = oca.Name ?? (oca.AsyncPattern ? mi.Name.Substring (5) : mi.Name);
184 OperationDescription od = null;
185 foreach (OperationDescription iter in cd.Operations) {
186 if (iter.Name == name) {
192 od = new OperationDescription (name, cd);
193 od.IsOneWay = oca.IsOneWay;
194 if (oca.HasProtectionLevel)
195 od.ProtectionLevel = oca.ProtectionLevel;
196 od.Messages.Add (GetMessage (od, mi, oca, true, null));
198 od.Messages.Add (GetMessage (od, mi, oca, false, asyncReturnType));
199 foreach (ServiceKnownTypeAttribute a in cd.ContractType.GetCustomAttributes (typeof (ServiceKnownTypeAttribute), false))
200 foreach (Type t in a.GetTypes ())
201 od.KnownTypes.Add (t);
202 foreach (ServiceKnownTypeAttribute a in serviceMethod.GetCustomAttributes (typeof (ServiceKnownTypeAttribute), false))
203 foreach (Type t in a.GetTypes ())
204 od.KnownTypes.Add (t);
205 cd.Operations.Add (od);
207 else if (oca.AsyncPattern && od.BeginMethod != null ||
208 !oca.AsyncPattern && od.SyncMethod != null)
209 throw new InvalidOperationException ("A contract cannot have two operations that have the identical names and different set of parameters.");
211 if (oca.AsyncPattern)
215 od.IsInitiating = oca.IsInitiating;
216 od.IsTerminating = oca.IsTerminating;
218 if (mi != serviceMethod)
219 foreach (object obj in mi.GetCustomAttributes (typeof (IOperationBehavior), true))
220 od.Behaviors.Add ((IOperationBehavior) obj);
222 if (serviceMethod != null) {
223 foreach (object obj in serviceMethod.GetCustomAttributes (typeof(IOperationBehavior),true))
224 od.Behaviors.Add ((IOperationBehavior) obj);
227 if (od.Behaviors.Find<OperationBehaviorAttribute>() == null)
228 od.Behaviors.Add (new OperationBehaviorAttribute ());
230 // FIXME: fill KnownTypes, Behaviors and Faults.
235 static MessageDescription GetMessage (
236 OperationDescription od, MethodInfo mi,
237 OperationContractAttribute oca, bool isRequest,
238 Type asyncReturnType)
240 ContractDescription cd = od.DeclaringContract;
241 ParameterInfo [] plist = mi.GetParameters ();
242 Type messageType = null;
243 string action = isRequest ? oca.Action : oca.ReplyAction;
244 MessageContractAttribute mca;
246 Type retType = asyncReturnType;
247 if (!isRequest && retType == null)
248 retType = mi.ReturnType;
250 // If the argument is only one and has [MessageContract]
251 // then infer it as a typed messsage
253 int len = mi.Name.StartsWith ("Begin", StringComparison.Ordinal) ? 3 : 1;
254 mca = plist.Length != len ? null :
255 GetMessageContractAttribute (plist [0].ParameterType);
257 messageType = plist [0].ParameterType;
260 mca = GetMessageContractAttribute (retType);
262 messageType = retType;
266 action = String.Concat (cd.Namespace,
267 cd.Namespace.EndsWith ("/") ? "" : "/", cd.Name, "/",
268 od.Name, isRequest ? String.Empty : "Response");
271 return CreateMessageDescription (messageType, cd.Namespace, action, isRequest, mca);
272 return CreateMessageDescription (oca, plist, od.Name, cd.Namespace, action, isRequest, retType, mi.ReturnTypeCustomAttributes);
275 public static MessageDescription CreateMessageDescription (
276 Type messageType, string defaultNamespace, string action, bool isRequest, MessageContractAttribute mca)
278 MessageDescription md = new MessageDescription (
279 action, isRequest ? MessageDirection.Input :
280 MessageDirection.Output);
281 md.MessageType = MessageFilterOutByRef (messageType);
282 if (mca.HasProtectionLevel)
283 md.ProtectionLevel = mca.ProtectionLevel;
285 MessageBodyDescription mb = md.Body;
287 mb.WrapperName = mca.WrapperName ?? messageType.Name;
288 mb.WrapperNamespace = mca.WrapperNamespace ?? defaultNamespace;
292 foreach (MemberInfo bmi in messageType.GetMembers (BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) {
295 if (bmi is FieldInfo) {
296 FieldInfo fi = (FieldInfo) bmi;
297 mtype = fi.FieldType;
300 else if (bmi is PropertyInfo) {
301 PropertyInfo pi = (PropertyInfo) bmi;
302 mtype = pi.PropertyType;
308 MessageBodyMemberAttribute mba = GetMessageBodyMemberAttribute (bmi);
312 MessagePartDescription pd = CreatePartCore (mba, mname, defaultNamespace);
314 pd.Type = MessageFilterOutByRef (mtype);
319 // FIXME: fill headers and properties.
323 public static MessageDescription CreateMessageDescription (
324 OperationContractAttribute oca, ParameterInfo[] plist, string name, string defaultNamespace, string action, bool isRequest, Type retType, ICustomAttributeProvider retTypeAttributes)
326 MessageDescription md = new MessageDescription (
327 action, isRequest ? MessageDirection.Input :
328 MessageDirection.Output);
330 MessageBodyDescription mb = md.Body;
331 mb.WrapperName = name + (isRequest ? String.Empty : "Response");
332 mb.WrapperNamespace = defaultNamespace;
334 if (oca.HasProtectionLevel)
335 md.ProtectionLevel = oca.ProtectionLevel;
339 foreach (ParameterInfo pi in plist) {
340 // AsyncCallback and state are extraneous.
341 if (oca.AsyncPattern && pi.Position == plist.Length - 2)
345 // - out parameter in request
346 // - neither out nor ref parameter in reply
347 if (isRequest && pi.IsOut)
349 if (!isRequest && !pi.IsOut && !pi.ParameterType.IsByRef)
352 MessagePartDescription pd = CreatePartCore (GetMessageParameterAttribute (pi), pi.Name, defaultNamespace);
354 pd.Type = MessageFilterOutByRef (pi.ParameterType);
360 MessagePartDescription mp = CreatePartCore (GetMessageParameterAttribute (retTypeAttributes), name + "Result", mb.WrapperNamespace);
366 // FIXME: fill properties.
371 public static void FillMessageBodyDescriptionByContract (
372 Type messageType, MessageBodyDescription mb)
376 static MessagePartDescription CreatePartCore (
377 MessageParameterAttribute mpa, string defaultName,
378 string defaultNamespace)
381 if (mpa != null && mpa.Name != null)
385 return new MessagePartDescription (pname, defaultNamespace);
388 static MessagePartDescription CreatePartCore (
389 MessageBodyMemberAttribute mba, string defaultName,
390 string defaultNamespace)
392 string pname = null, pns = null;
394 if (mba.Name != null)
396 if (mba.Namespace != null)
402 pns = defaultNamespace;
404 return new MessagePartDescription (pname, pns);
407 static Type MessageFilterOutByRef (Type type)
409 return type == null ? null :
410 type.IsByRef ? type.GetElementType () : type;
413 static MessageParameterAttribute GetMessageParameterAttribute (ICustomAttributeProvider provider)
415 object [] attrs = provider.GetCustomAttributes (
416 typeof (MessageParameterAttribute), true);
417 return attrs.Length > 0 ? (MessageParameterAttribute) attrs [0] : null;
420 static MessageBodyMemberAttribute GetMessageBodyMemberAttribute (MemberInfo mi)
422 object [] matts = mi.GetCustomAttributes (
423 typeof (MessageBodyMemberAttribute), true);
424 return matts.Length > 0 ? (MessageBodyMemberAttribute) matts [0] : null;