Merge branch 'cecil-light'
[mono.git] / mcs / class / System.ServiceModel.Web / System.ServiceModel.Description / WebHttpBehavior.cs
1 //
2 // WebHttpBehavior.cs
3 //
4 // Author:
5 //      Atsushi Enomoto  <atsushi@ximian.com>
6 //
7 // Copyright (C) 2008 Novell, Inc (http://www.novell.com)
8 //
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:
16 // 
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 // 
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.
27 //
28 using System;
29 using System.Net;
30 using System.ServiceModel;
31 using System.ServiceModel.Channels;
32 using System.ServiceModel.Dispatcher;
33 using System.ServiceModel.Web;
34
35 namespace System.ServiceModel.Description
36 {
37
38         internal static class WebHttpBehaviorExtensions
39         {
40                 public static WebAttributeInfo GetWebAttributeInfo (this OperationDescription od)
41                 {
42 #if NET_2_1
43                         var mi = od.BeginMethod ?? od.SyncMethod;
44                         var atts = mi.GetCustomAttributes (typeof (WebGetAttribute), true);
45                         if (atts.Length == 1)
46                                 return ((WebGetAttribute) atts [0]).Info;
47                         atts = mi.GetCustomAttributes (typeof (WebInvokeAttribute), true);
48                         if (atts.Length == 1)
49                                 return ((WebInvokeAttribute) atts [0]).Info;
50                         return null;
51 #else
52                         foreach (IOperationBehavior ob in od.Behaviors) {
53                                 WebAttributeInfo info = null;
54                                 var wg = ob as WebGetAttribute;
55                                 if (wg != null)
56                                         return wg.Info;
57                                 var wi = ob as WebInvokeAttribute;
58                                 if (wi != null)
59                                         return wi.Info;
60                         }
61                         return new WebGetAttribute ().Info; // blank one
62 #endif
63                 }
64         }
65
66         public class WebHttpBehavior
67 #if !NET_2_1
68          : IEndpointBehavior
69 #endif
70         {
71                 public WebHttpBehavior ()
72                 {
73                         DefaultBodyStyle = WebMessageBodyStyle.Bare;
74                         DefaultOutgoingRequestFormat = WebMessageFormat.Xml;
75                         DefaultOutgoingResponseFormat = WebMessageFormat.Xml;
76                 }
77
78 #if NET_4_0
79                 public virtual bool AutomaticFormatSelectionEnabled { get; set; }
80
81                 public virtual bool FaultExceptionEnabled { get; set; }
82
83                 public virtual bool HelpEnabled { get; set; }
84 #endif
85
86                 public virtual WebMessageBodyStyle DefaultBodyStyle { get; set; }
87
88                 public virtual WebMessageFormat DefaultOutgoingRequestFormat { get; set; }
89
90                 public virtual WebMessageFormat DefaultOutgoingResponseFormat { get; set; }
91
92                 public virtual void AddBindingParameters (ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
93                 {
94                         // nothing
95                 }
96
97                 [MonoTODO]
98                 protected virtual void AddClientErrorInspector (ServiceEndpoint endpoint, ClientRuntime clientRuntime)
99                 {
100                         // clientRuntime.MessageInspectors.Add (something);
101                 }
102
103 #if !NET_2_1
104                 protected virtual void AddServerErrorHandlers (ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
105                 {
106                         endpointDispatcher.ChannelDispatcher.ErrorHandlers.Add (new WebHttpErrorHandler ());
107                 }
108 #endif
109
110                 public virtual void ApplyClientBehavior (ServiceEndpoint endpoint, ClientRuntime clientRuntime)
111                 {
112                         AddClientErrorInspector (endpoint, clientRuntime);
113 #if MOONLIGHT
114                         throw new NotSupportedException ("Due to the lack of ClientRuntime.Operations, Silverlight cannot support this binding.");
115 #else
116                         foreach (ClientOperation oper in clientRuntime.Operations) {
117                                 var req = GetRequestClientFormatter (endpoint.Contract.Operations.Find (oper.Name), endpoint);
118                                 var res = GetReplyClientFormatter (endpoint.Contract.Operations.Find (oper.Name), endpoint);
119                                 oper.Formatter = new ClientPairFormatter (req, res);
120                         }
121 #endif
122                 }
123
124 #if !NET_2_1
125                 public virtual void ApplyDispatchBehavior (ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
126                 {
127                         endpointDispatcher.DispatchRuntime.OperationSelector = GetOperationSelector (endpoint);
128                         // FIXME: get HostNameComparisonMode from WebHttpBinding by some means.
129                         endpointDispatcher.FilterPriority = 1; // It is to take higher priority than that of ServiceMetadataExtension (whose URL likely conflicts with this one).
130                         endpointDispatcher.AddressFilter = new PrefixEndpointAddressMessageFilter (endpoint.Address);
131                         endpointDispatcher.ContractFilter = new MatchAllMessageFilter ();
132                         AddServerErrorHandlers (endpoint, endpointDispatcher);
133
134                         foreach (DispatchOperation oper in endpointDispatcher.DispatchRuntime.Operations) {
135                                 var req = GetRequestDispatchFormatter (endpoint.Contract.Operations.Find (oper.Name), endpoint);
136                                 var res = GetReplyDispatchFormatter (endpoint.Contract.Operations.Find (oper.Name), endpoint);
137                                 oper.Formatter = new DispatchPairFormatter (req, res);
138                         }
139                         endpointDispatcher.DispatchRuntime.UnhandledDispatchOperation = new DispatchOperation (endpointDispatcher.DispatchRuntime, "*", "*", "*") {
140                                 Invoker = new EndpointNotFoundOperationInvoker (),
141                                 DeserializeRequest = false,
142                                 SerializeReply = false};
143                 }
144 #endif
145
146                 internal class ClientPairFormatter : IClientMessageFormatter
147                 {
148                         public ClientPairFormatter (IClientMessageFormatter request, IClientMessageFormatter reply)
149                         {
150                                 this.request = request;
151                                 this.reply = reply;
152                         }
153
154                         IClientMessageFormatter request, reply;
155
156                         public Message SerializeRequest (MessageVersion messageVersion, object [] parameters)
157                         {
158                                 return request.SerializeRequest (messageVersion, parameters);
159                         }
160
161                         public object DeserializeReply (Message message, object [] parameters)
162                         {
163                                 return reply.DeserializeReply (message, parameters);
164                         }
165                 }
166
167 #if !NET_2_1
168                 internal class DispatchPairFormatter : IDispatchMessageFormatter
169                 {
170                         public DispatchPairFormatter (IDispatchMessageFormatter request, IDispatchMessageFormatter reply)
171                         {
172                                 this.request = request;
173                                 this.reply = reply;
174                         }
175
176                         IDispatchMessageFormatter request;
177                         IDispatchMessageFormatter reply;
178
179                         public void DeserializeRequest (Message message, object [] parameters)
180                         {
181                                 request.DeserializeRequest (message, parameters);
182                         }
183
184                         public Message SerializeReply (MessageVersion messageVersion, object [] parameters, object result)
185                         {
186                                 return reply.SerializeReply (messageVersion, parameters, result);
187                         }
188                 }
189
190                 protected virtual WebHttpDispatchOperationSelector GetOperationSelector (ServiceEndpoint endpoint)
191                 {
192                         return new WebHttpDispatchOperationSelector (endpoint);
193                 }
194 #endif
195
196                 protected virtual QueryStringConverter GetQueryStringConverter (OperationDescription operationDescription)
197                 {
198                         return new QueryStringConverter ();
199                 }
200
201                 protected virtual IClientMessageFormatter GetReplyClientFormatter (OperationDescription operationDescription, ServiceEndpoint endpoint)
202                 {
203                         return new WebMessageFormatter.ReplyClientFormatter (operationDescription, endpoint, GetQueryStringConverter (operationDescription), this);
204                 }
205
206 #if !NET_2_1
207                 protected virtual IDispatchMessageFormatter GetReplyDispatchFormatter (OperationDescription operationDescription, ServiceEndpoint endpoint)
208                 {
209                         return new WebMessageFormatter.ReplyDispatchFormatter (operationDescription, endpoint, GetQueryStringConverter (operationDescription), this);
210                 }
211 #endif
212
213                 protected virtual IClientMessageFormatter GetRequestClientFormatter (OperationDescription operationDescription, ServiceEndpoint endpoint)
214                 {
215                         return new WebMessageFormatter.RequestClientFormatter (operationDescription, endpoint, GetQueryStringConverter (operationDescription), this);
216                 }
217
218 #if !NET_2_1
219                 protected virtual IDispatchMessageFormatter GetRequestDispatchFormatter (OperationDescription operationDescription, ServiceEndpoint endpoint)
220                 {
221                         return new WebMessageFormatter.RequestDispatchFormatter (operationDescription, endpoint, GetQueryStringConverter (operationDescription), this);
222                 }
223 #endif
224
225                 WebMessageBodyStyle GetBodyStyle (WebAttributeInfo wai)
226                 {
227                         return wai != null && wai.IsBodyStyleSetExplicitly ? wai.BodyStyle : DefaultBodyStyle;
228                 }
229
230                 protected void ValidateOperation (OperationDescription operation)
231                 {
232                         var wai = operation.GetWebAttributeInfo ();
233                         if (wai.Method == "GET")
234                                 return;
235                         var style = GetBodyStyle (wai);
236
237                         // if the style is wrapped there won't be problems
238                         if (style == WebMessageBodyStyle.Wrapped)
239                                 return;
240
241                         string [] parameters;
242                         if (wai.UriTemplate != null) {
243                                 // find all variables in the URI
244                                 var uri = new UriTemplate (wai.UriTemplate);
245                                 parameters = new string [uri.PathSegmentVariableNames.Count + uri.QueryValueVariableNames.Count];
246                                 uri.PathSegmentVariableNames.CopyTo (parameters, 0);
247                                 uri.QueryValueVariableNames.CopyTo (parameters, uri.PathSegmentVariableNames.Count);
248
249                                 // sort because Array.BinarySearch is the easiest way for case-insensitive search
250                                 Array.Sort (parameters, StringComparer.InvariantCultureIgnoreCase);
251                         } else
252                                 parameters = new string [0];
253
254                         bool hasBody = false;
255
256                         foreach (var msg in operation.Messages) {
257                                 if (msg.Direction == MessageDirection.Input) {
258                                         // the message is for a request
259                                         // if requests are wrapped there is nothing to check
260                                         if (style == WebMessageBodyStyle.WrappedRequest)
261                                                 continue;
262
263                                         foreach (var part in msg.Body.Parts) {
264                                                 if (Array.BinarySearch (parameters, part.Name, StringComparer.InvariantCultureIgnoreCase) < 0) {
265                                                         // this part of the message is not covered by a variable in the URI
266                                                         // so it must be passed in the body
267                                                         if (hasBody)
268                                                                 throw new InvalidOperationException (String.Format ("Operation '{0}' has multiple message body parts. Add parameters to the UriTemplate or change the BodyStyle to 'Wrapped' or 'WrappedRequest' on the WebInvoke/WebGet attribute.", operation.Name));
269                                                         hasBody = true;
270                                                 }
271                                         }
272                                 } else {
273                                         // the message is for a response
274                                         if (style != WebMessageBodyStyle.WrappedResponse && msg.Body.Parts.Count > 0)
275                                                 throw new InvalidOperationException (String.Format ("Operation '{0}' has output parameters. BodyStyle must be 'Wrapped' or 'WrappedResponse' on the operation WebInvoke/WebGet attribute.", operation.Name));
276                                 }
277                         }
278                 }
279                 
280                 [MonoTODO ("check UriTemplate validity")]
281                 public virtual void Validate (ServiceEndpoint endpoint)
282                 {
283                         if (endpoint == null)
284                                 throw new ArgumentNullException ("endpoint");
285
286                         foreach (var oper in endpoint.Contract.Operations) {
287                                 ValidateOperation (oper);
288                         }
289
290                         ValidateBinding (endpoint);
291                 }
292
293                 protected virtual void ValidateBinding (ServiceEndpoint endpoint)
294                 {
295                         switch (endpoint.Binding.Scheme) {
296                         case "http":
297                         case "https":
298                                 break;
299                         default:
300                                 throw new InvalidOperationException ("Only http and https are allowed for WebHttpBehavior");
301                         }
302                         if (!endpoint.Binding.MessageVersion.Equals (MessageVersion.None))
303                                 throw new InvalidOperationException ("Only MessageVersion.None is allowed for WebHttpBehavior");
304                         if (!endpoint.Binding.CreateBindingElements ().Find<TransportBindingElement> ().ManualAddressing)
305                                 throw new InvalidOperationException ("ManualAddressing in the transport binding element in the binding must be true for WebHttpBehavior");
306                 }
307
308 #if !NET_2_1
309                 internal class WebHttpErrorHandler : IErrorHandler
310                 {
311                         public void ProvideFault (Exception error, MessageVersion version, ref Message fault)
312                         {
313                                 if (!(error is EndpointNotFoundException))
314                                         return;
315                                 fault = Message.CreateMessage (version, null);
316                                 var prop = new HttpResponseMessageProperty ();
317                                 prop.StatusCode = HttpStatusCode.NotFound;
318                                 fault.Properties.Add (HttpResponseMessageProperty.Name, prop);
319                         }
320                         
321                         public bool HandleError (Exception error)
322                         {
323                                 return false;
324                         }
325                 }
326
327                 class EndpointNotFoundOperationInvoker : IOperationInvoker
328                 {
329                         public bool IsSynchronous {
330                                 get { return true; }
331                         }
332
333                         public object [] AllocateInputs ()
334                         {
335                                 return new object [1];
336                         }
337                         
338                         public object Invoke (object instance, object [] inputs, out object [] outputs)
339                         {
340                                 throw new EndpointNotFoundException ();
341                         }
342                         
343                         public IAsyncResult InvokeBegin (object instance, object [] inputs, AsyncCallback callback, object state)
344                         {
345                                 throw new EndpointNotFoundException ();
346                         }
347
348                         public object InvokeEnd (object instance, out object [] outputs, IAsyncResult result)
349                         {
350                                 throw new InvalidOperationException ();
351                         }
352                 }
353 #endif
354         }
355 }