setting all protected methods on WizardStepPage as virtual
[castle.git] / MonoRail / Castle.MonoRail.Framework / SmartDispatcherController.cs
blob61479ba9c92b5c14785c76defdb19223358981c4
1 // Copyright 2004-2008 Castle Project - http://www.castleproject.org/
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
15 namespace Castle.MonoRail.Framework
17 using System;
18 using System.Collections.Generic;
19 using System.Reflection;
20 using System.Collections;
21 using System.Collections.Specialized;
22 using Castle.Components.Binder;
23 using Providers;
25 /// <summary>
26 /// Specialization of <see cref="Controller"/> that tries
27 /// to match the request params to method arguments.
28 /// </summary>
29 /// <remarks>
30 /// You don't even need to always use databinding within
31 /// arguments. <see cref="BindObject(ParamStore,Type,string)"/>
32 /// and <see cref="BindObjectInstance(object,string)"/>
33 /// provides the same functionality to be used in place.
34 /// </remarks>
35 public abstract class SmartDispatcherController : Controller
37 private IDataBinder binder;
39 /// <summary>
40 /// Represents the errors associated with an instance bound.
41 /// </summary>
42 protected IDictionary<object, ErrorList> boundInstances = new Dictionary<object, ErrorList>();
44 /// <summary>
45 /// Initializes a new instance of the <see cref="SmartDispatcherController"/> class.
46 /// </summary>
47 protected SmartDispatcherController() : this(new DataBinder())
51 /// <summary>
52 /// Initializes a new instance of the <see cref="SmartDispatcherController"/> class.
53 /// </summary>
54 /// <param name="binder">The binder.</param>
55 protected SmartDispatcherController(IDataBinder binder)
57 this.binder = binder;
60 /// <summary>
61 /// Gets the binder.
62 /// </summary>
63 /// <value>The binder.</value>
64 public IDataBinder Binder
66 get { return binder; }
69 /// <summary>
70 /// Constructs the parameters for the action and invokes it.
71 /// </summary>
72 /// <param name="method">The method.</param>
73 /// <param name="request">The request.</param>
74 /// <param name="extraArgs">The extra args.</param>
75 /// <returns></returns>
76 protected override object InvokeMethod(MethodInfo method, IRequest request, IDictionary<string, object> extraArgs)
78 ParameterInfo[] parameters = method.GetParameters();
80 object[] methodArgs = BuildMethodArguments(parameters, request, extraArgs);
82 return method.Invoke(this, methodArgs);
85 /// <summary>
86 /// Uses a simple heuristic to select the best method -- especially in the
87 /// case of method overloads.
88 /// </summary>
89 /// <param name="action">The action name</param>
90 /// <param name="actions">The avaliable actions</param>
91 /// <param name="request">The request instance</param>
92 /// <param name="actionArgs">The custom arguments for the action</param>
93 /// <param name="actionType">Type of the action.</param>
94 /// <returns></returns>
95 protected override MethodInfo SelectMethod(string action, IDictionary actions, IRequest request, IDictionary<string, object> actionArgs, ActionType actionType)
97 object methods = actions[action];
99 // should check for single-option as soon as possible (performance improvement)
100 if (methods is MethodInfo) return (MethodInfo) methods;
102 if (methods is AsyncActionPair)
104 AsyncActionPair pair = (AsyncActionPair) methods;
105 if (actionType == ActionType.AsyncBegin)
106 return pair.BeginActionInfo;
107 else
108 return pair.EndActionInfo;
111 ArrayList candidates = (ArrayList) methods;
113 if (candidates == null) return null;
115 return SelectBestCandidate((MethodInfo[]) candidates.ToArray(typeof(MethodInfo)),
116 request.Params, actionArgs);
119 /// <summary>
120 /// Selects the best method given the set of entries
121 /// avaliable on <paramref name="webParams"/> and <paramref name="actionArgs"/>
122 /// </summary>
123 /// <param name="candidates">The candidates.</param>
124 /// <param name="webParams">The web params.</param>
125 /// <param name="actionArgs">The custom action args.</param>
126 /// <returns></returns>
127 protected virtual MethodInfo SelectBestCandidate(MethodInfo[] candidates,
128 NameValueCollection webParams,
129 IDictionary<string, object> actionArgs)
131 if (candidates.Length == 1)
133 // There's nothing much to do in this situation
134 return candidates[0];
137 int lastMaxPoints = int.MinValue;
138 MethodInfo bestCandidate = null;
140 foreach(MethodInfo candidate in candidates)
142 int points = CalculatePoints(candidate, webParams, actionArgs);
144 if (lastMaxPoints < points)
146 lastMaxPoints = points;
147 bestCandidate = candidate;
151 return bestCandidate;
154 /// <summary>
155 /// Gets the name of the request parameter.
156 /// </summary>
157 /// <param name="param">The param.</param>
158 /// <returns></returns>
159 protected virtual String GetRequestParameterName(ParameterInfo param)
161 return param.Name;
164 /// <summary>
165 /// Uses a simplest algorithm to compute points for a method
166 /// based on parameters available, which in turn reflects
167 /// the best method is the one which the parameters will be
168 /// able to satistfy more arguments
169 /// </summary>
170 /// <param name="candidate">The method candidate</param>
171 /// <param name="webParams">Parameter source</param>
172 /// <param name="actionArgs">Extra parameters</param>
173 /// <returns></returns>
174 protected int CalculatePoints(MethodInfo candidate, NameValueCollection webParams, IDictionary<string, object> actionArgs)
176 int points = 0;
177 int matchCount = 0;
179 ParameterInfo[] parameters = candidate.GetParameters();
181 foreach(ParameterInfo param in parameters)
184 // If the param is decorated with an attribute that implements IParameterBinder
185 // then it calculates the points itself
188 object[] attributes = param.GetCustomAttributes(false);
190 String requestParameterName;
192 bool calculated = false;
194 foreach(object attr in attributes)
196 IParameterBinder actionParam = attr as IParameterBinder;
198 if (actionParam == null) continue;
200 points += actionParam.CalculateParamPoints(Context, this, ControllerContext, param);
201 calculated = true;
204 if (calculated) continue;
207 // Otherwise
210 requestParameterName = GetRequestParameterName(param);
212 Type parameterType = param.ParameterType;
213 bool usedActionArgs = false;
215 if ((actionArgs != null) && actionArgs.ContainsKey(requestParameterName))
217 object value = actionArgs[requestParameterName];
218 Type actionArgType = value != null ? value.GetType() : param.ParameterType;
220 bool exactMatch;
222 if (binder.Converter.CanConvert(parameterType, actionArgType, value, out exactMatch))
224 points += 10;
225 matchCount++;
226 usedActionArgs = true;
230 if (!usedActionArgs && binder.CanBindParameter(parameterType, requestParameterName, Request.ParamsNode))
232 points += 10;
233 matchCount++;
237 // the bonus should be nice only for disambiguation.
238 // otherwise, unmatched-parameterless-actions will always have
239 // the same weight as matched-single-parameter-actions.
240 if (matchCount == parameters.Length)
242 points += 5;
245 return points;
248 /// <summary>
249 /// Returns an array that hopefully fills the arguments of the selected action.
250 /// </summary>
251 /// <remarks>
252 /// Each parameter is inspected and we try to obtain an implementation of
253 /// <see cref="IParameterBinder"/> from the attributes the parameter have (if any).
254 /// If an implementation is found, it's used to fill the value for that parameter.
255 /// Otherwise we use simple conversion to obtain the value.
256 /// </remarks>
257 /// <param name="parameters">Parameters to obtain the values to</param>
258 /// <param name="request">The current request, which is the source to obtain the data</param>
259 /// <param name="actionArgs">Extra arguments to pass to the action.</param>
260 /// <returns>An array with the arguments values</returns>
261 protected virtual object[] BuildMethodArguments(ParameterInfo[] parameters, IRequest request, IDictionary<string, object> actionArgs)
263 object[] args = new object[parameters.Length];
264 String paramName = String.Empty;
265 String value = String.Empty;
269 for(int argIndex = 0; argIndex < args.Length; argIndex++)
272 // If the parameter is decorated with an attribute
273 // that implements IParameterBinder, it's up to it
274 // to convert itself
277 ParameterInfo param = parameters[argIndex];
278 paramName = GetRequestParameterName(param);
280 bool handled = false;
282 object[] attributes = param.GetCustomAttributes(false);
284 foreach(object attr in attributes)
286 IParameterBinder paramBinder = attr as IParameterBinder;
288 if (paramBinder != null)
290 args[argIndex] = paramBinder.Bind(Context, this, ControllerContext, param);
291 handled = true;
292 break;
297 // Otherwise we handle it
300 if (!handled)
302 object convertedVal= null;
303 bool conversionSucceeded = false;
305 if (actionArgs != null && actionArgs.ContainsKey(paramName))
307 object actionArg = actionArgs[paramName];
309 Type actionArgType = actionArg != null ? actionArg.GetType() : param.ParameterType;
311 convertedVal = binder.Converter.Convert(param.ParameterType, actionArgType, actionArg, out conversionSucceeded);
314 if (!conversionSucceeded)
316 convertedVal = binder.BindParameter(param.ParameterType, paramName, Request.ParamsNode);
319 args[argIndex] = convertedVal;
323 catch(FormatException ex)
325 throw new MonoRailException(
326 String.Format("Could not convert {0} to request type. " +
327 "Argument value is '{1}'", paramName, Params.Get(paramName)), ex);
329 catch(Exception ex)
331 throw new MonoRailException(
332 String.Format("Error building method arguments. " +
333 "Last param analyzed was {0} with value '{1}'", paramName, value), ex);
336 return args;
339 /// <summary>
340 /// Binds the object of the specified type using the given prefix.
341 /// </summary>
342 /// <param name="targetType">Type of the target.</param>
343 /// <param name="prefix">The prefix.</param>
344 /// <returns></returns>
345 protected object BindObject(Type targetType, String prefix)
347 return BindObject(ParamStore.Params, targetType, prefix);
350 /// <summary>
351 /// Binds the object of the specified type using the given prefix.
352 /// but only using the entries from the collection specified on the <paramref name="from"/>
353 /// </summary>
354 /// <param name="from">Restricts the data source of entries.</param>
355 /// <param name="targetType">Type of the target.</param>
356 /// <param name="prefix">The prefix.</param>
357 /// <returns></returns>
358 protected object BindObject(ParamStore from, Type targetType, String prefix)
360 return BindObject(from, targetType, prefix, null, null);
363 /// <summary>
364 /// Binds the object of the specified type using the given prefix.
365 /// but only using the entries from the collection specified on the <paramref name="from"/>
366 /// </summary>
367 /// <param name="from">From.</param>
368 /// <param name="targetType">Type of the target.</param>
369 /// <param name="prefix">The prefix.</param>
370 /// <param name="excludedProperties">The excluded properties, comma separated list.</param>
371 /// <param name="allowedProperties">The allowed properties, comma separated list.</param>
372 /// <returns></returns>
373 protected object BindObject(ParamStore from, Type targetType, String prefix, String excludedProperties,
374 String allowedProperties)
376 CompositeNode node = Request.ObtainParamsNode(from);
378 object instance = binder.BindObject(targetType, prefix, excludedProperties, allowedProperties, node);
380 boundInstances[instance] = binder.ErrorList;
381 PopulateValidatorErrorSummary(instance, binder.GetValidationSummary(instance));
383 return instance;
386 /// <summary>
387 /// Binds the object instance using the specified prefix.
388 /// </summary>
389 /// <param name="instance">The instance.</param>
390 /// <param name="prefix">The prefix.</param>
391 protected void BindObjectInstance(object instance, String prefix)
393 BindObjectInstance(instance, ParamStore.Params, prefix);
396 /// <summary>
397 /// Binds the object instance using the given prefix.
398 /// but only using the entries from the collection specified on the <paramref name="from"/>
399 /// </summary>
400 /// <param name="instance">The instance.</param>
401 /// <param name="from">From.</param>
402 /// <param name="prefix">The prefix.</param>
403 protected void BindObjectInstance(object instance, ParamStore from, String prefix)
405 CompositeNode node = Request.ObtainParamsNode(from);
407 binder.BindObjectInstance(instance, prefix, node);
409 boundInstances[instance] = binder.ErrorList;
410 PopulateValidatorErrorSummary(instance, binder.GetValidationSummary(instance));
413 /// <summary>
414 /// Binds the object of the specified type using the given prefix.
415 /// </summary>
416 /// <typeparam name="T">Target type</typeparam>
417 /// <param name="prefix">The prefix.</param>
418 /// <returns></returns>
419 protected T BindObject<T>(String prefix)
421 return (T) BindObject(typeof(T), prefix);
424 /// <summary>
425 /// Binds the object of the specified type using the given prefix.
426 /// but only using the entries from the collection specified on the <paramref name="from"/>
427 /// </summary>
428 /// <typeparam name="T">Target type</typeparam>
429 /// <param name="from">From.</param>
430 /// <param name="prefix">The prefix.</param>
431 /// <returns></returns>
432 protected T BindObject<T>(ParamStore from, String prefix)
434 return (T) BindObject(from, typeof(T), prefix);
437 /// <summary>
438 /// Binds the object of the specified type using the given prefix.
439 /// but only using the entries from the collection specified on the <paramref name="from"/>
440 /// </summary>
441 /// <typeparam name="T"></typeparam>
442 /// <param name="from">From.</param>
443 /// <param name="prefix">The prefix.</param>
444 /// <param name="excludedProperties">The excluded properties.</param>
445 /// <param name="allowedProperties">The allowed properties.</param>
446 /// <returns></returns>
447 protected T BindObject<T>(ParamStore from, String prefix, String excludedProperties, String allowedProperties)
449 return (T) BindObject(from, typeof(T), prefix, excludedProperties, allowedProperties);