Fixing an issue with output parameters that are of type IntPtr
[castle.git] / MonoRail / Castle.MonoRail.Framework / Services / DefaultUrlBuilder.cs
blob78f4a958ad894577f5a81f5dd89f908f1ceedeb9
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.Services
17 using System;
18 using System.Collections;
19 using System.Collections.Specialized;
20 using Castle.Core;
21 using Castle.MonoRail.Framework.Internal;
22 using Configuration;
23 using Framework;
25 /// <summary>
26 /// Default implementation of <see cref="IUrlBuilder"/>
27 /// </summary>
28 /// <remarks>
29 /// The property <see cref="UseExtensions"/> defines whether the builder should output
30 /// file extension. This might be handy to use in combination with a url rewrite strategy
31 /// </remarks>
32 /// <seealso cref="BuildUrl(UrlInfo,IDictionary)"/>
33 public class DefaultUrlBuilder : IUrlBuilder, IServiceEnabledComponent
35 private bool useExtensions = true;
36 private IServerUtility serverUtil;
37 private IRoutingEngine routingEng;
39 /// <summary>
40 /// Initializes a new instance of the <see cref="DefaultUrlBuilder"/> class.
41 /// </summary>
42 public DefaultUrlBuilder()
46 /// <summary>
47 /// Initializes a new instance of the <see cref="DefaultUrlBuilder"/> class.
48 /// </summary>
49 /// <param name="serverUtil">The server util.</param>
50 /// <param name="routingEng">The routing eng.</param>
51 public DefaultUrlBuilder(IServerUtility serverUtil, IRoutingEngine routingEng)
53 this.serverUtil = serverUtil;
54 this.routingEng = routingEng;
57 #region Properties
59 /// <summary>
60 /// Gets or sets a value indicating whether the builder should output an extension.
61 /// </summary>
62 /// <value><c>true</c> if should use extensions; otherwise, <c>false</c>.</value>
63 public bool UseExtensions
65 get { return useExtensions; }
66 set { useExtensions = value; }
69 /// <summary>
70 /// Gets or sets the server utility instance.
71 /// </summary>
72 /// <value>The server util.</value>
73 public IServerUtility ServerUtil
75 get { return serverUtil; }
76 set { serverUtil = value; }
79 /// <summary>
80 /// Gets or sets the routing engine.
81 /// </summary>
82 /// <value>The routing engine.</value>
83 public IRoutingEngine RoutingEngine
85 get { return routingEng; }
86 set { routingEng = value; }
89 #endregion
91 #region IServiceEnabledComponent
93 /// <summary>
94 /// Services the specified provider.
95 /// </summary>
96 /// <param name="provider">The provider.</param>
97 public void Service(IServiceProvider provider)
99 IMonoRailConfiguration config = (IMonoRailConfiguration) provider.GetService(typeof(IMonoRailConfiguration));
100 useExtensions = config.UrlConfig.UseExtensions;
102 serverUtil = (IServerUtility) provider.GetService(typeof(IServerUtility));
103 routingEng = (IRoutingEngine) provider.GetService(typeof(IRoutingEngine));
106 #endregion
108 #region IUrlBuilder
110 /// <summary>
111 /// Builds the URL using the current url as contextual information and the specified parameters.
112 /// <para>
113 /// Common parameters includes area, controller and action. See <see cref="UrlBuilderParameters"/>
114 /// for more information regarding the parameters.
115 /// </para>
116 /// </summary>
117 /// <param name="current">The current Url information.</param>
118 /// <param name="parameters">The parameters.</param>
119 /// <returns></returns>
120 public virtual string BuildUrl(UrlInfo current, IDictionary parameters)
122 AssertArguments(current, parameters);
124 UrlBuilderParameters typedParams = UrlBuilderParameters.From(parameters);
126 return BuildUrl(current, typedParams, ConvertRouteParams(typedParams.RouteParameters));
129 /// <summary>
130 /// Builds the URL using the current url as contextual information and the specified parameters.
131 /// <para>
132 /// Common parameters includes area, controller and action. See <see cref="UrlBuilderParameters"/>
133 /// for more information regarding the parameters.
134 /// </para>
135 /// </summary>
136 /// <param name="current">The current Url information.</param>
137 /// <param name="parameters">The parameters.</param>
138 /// <param name="routeParameters">The route parameters.</param>
139 /// <returns></returns>
140 public virtual string BuildUrl(UrlInfo current, IDictionary parameters, IDictionary routeParameters)
142 AssertArguments(current, parameters);
144 UrlBuilderParameters typedParams = UrlBuilderParameters.From(parameters, routeParameters);
146 return BuildUrl(current, typedParams, ConvertRouteParams(typedParams.RouteParameters));
149 /// <summary>
150 /// Builds the URL using the current url as contextual information and the specified parameters.
151 /// <para>
152 /// Common parameters includes area, controller and action. See <see cref="UrlBuilderParameters"/>
153 /// for more information regarding the parameters.
154 /// </para>
155 /// </summary>
156 /// <param name="current">The current Url information.</param>
157 /// <param name="parameters">The parameters.</param>
158 /// <returns></returns>
159 public virtual string BuildUrl(UrlInfo current, UrlBuilderParameters parameters)
161 AssertArguments(current, parameters);
163 return BuildUrl(current, parameters, ConvertRouteParams(parameters.RouteParameters));
166 /// <summary>
167 /// Builds the URL using the current url as contextual information and the specified parameters.
168 /// <para>
169 /// Common parameters includes area, controller and action. See <see cref="UrlBuilderParameters"/>
170 /// for more information regarding the parameters.
171 /// </para>
172 /// </summary>
173 /// <param name="current">The current Url information.</param>
174 /// <param name="parameters">The parameters.</param>
175 /// <param name="routeParameters">The route parameters.</param>
176 /// <returns></returns>
177 public virtual string BuildUrl(UrlInfo current, UrlBuilderParameters parameters, IDictionary routeParameters)
179 AssertArguments(current, parameters);
181 bool encodeForLink = parameters.EncodeForLink;
183 UrlParts url = CreateUrlPartsBuilder(current, parameters, routeParameters);
185 if (encodeForLink)
187 return url.BuildPathForLink(serverUtil);
190 return url.BuildPath();
193 /// <summary>
194 /// Builds the URL using the current url as contextual information and the specified parameters.
195 /// <para>
196 /// Common parameters includes area, controller and action. See <see cref="UrlBuilderParameters"/>
197 /// for more information regarding the parameters.
198 /// </para>
199 /// </summary>
200 /// <param name="current">The current Url information.</param>
201 /// <param name="parameters">The parameters.</param>
202 /// <returns></returns>
203 public virtual UrlParts CreateUrlPartsBuilder(UrlInfo current, IDictionary parameters)
205 AssertArguments(current, parameters);
207 UrlBuilderParameters typedParams = UrlBuilderParameters.From(parameters);
209 return CreateUrlPartsBuilder(current, parameters, ConvertRouteParams(typedParams.RouteParameters));
212 /// <summary>
213 /// Builds the URL using the current url as contextual information and the specified parameters.
214 /// <para>
215 /// Common parameters includes area, controller and action. See <see cref="UrlBuilderParameters"/>
216 /// for more information regarding the parameters.
217 /// </para>
218 /// </summary>
219 /// <param name="current">The current Url information.</param>
220 /// <param name="parameters">The parameters.</param>
221 /// <param name="routeParameters">The route parameters.</param>
222 /// <returns></returns>
223 public virtual UrlParts CreateUrlPartsBuilder(UrlInfo current, IDictionary parameters, IDictionary routeParameters)
225 AssertArguments(current, parameters);
227 UrlBuilderParameters typedParams = UrlBuilderParameters.From(parameters, routeParameters);
229 return CreateUrlPartsBuilder(current, parameters, ConvertRouteParams(typedParams.RouteParameters));
232 /// <summary>
233 /// Builds the URL using the current url as contextual information and the specified parameters.
234 /// <para>
235 /// Common parameters includes area, controller and action. See <see cref="UrlBuilderParameters"/>
236 /// for more information regarding the parameters.
237 /// </para>
238 /// </summary>
239 /// <param name="current">The current Url information.</param>
240 /// <param name="parameters">The parameters.</param>
241 /// <returns></returns>
242 public virtual UrlParts CreateUrlPartsBuilder(UrlInfo current, UrlBuilderParameters parameters)
244 AssertArguments(current, parameters);
246 return CreateUrlPartsBuilder(current, parameters, ConvertRouteParams(parameters.RouteParameters));
249 /// <summary>
250 /// Builds the URL using the current url as contextual information and the specified parameters.
251 /// <para>
252 /// Common parameters includes area, controller and action. See <see cref="UrlBuilderParameters"/>
253 /// for more information regarding the parameters.
254 /// </para>
255 /// </summary>
256 /// <param name="current">The current Url information.</param>
257 /// <param name="parameters">The parameters.</param>
258 /// <param name="routeParameters">The route parameters.</param>
259 /// <returns></returns>
260 public virtual UrlParts CreateUrlPartsBuilder(UrlInfo current, UrlBuilderParameters parameters, IDictionary routeParameters)
262 AssertArguments(current, parameters);
264 string appVirtualDir = current.AppVirtualDir;
266 string area = parameters.Area ?? current.Area;
267 string controller = parameters.Controller ?? current.Controller;
268 string action = parameters.Action ?? current.Action;
270 if (appVirtualDir.Length > 1 && !(appVirtualDir[0] == '/'))
272 appVirtualDir = "/" + appVirtualDir;
275 string path = ComputeStandardBasePath(appVirtualDir, area);
276 path = ApplyBasePathOrAbsolutePathIfNecessary(appVirtualDir, area, current, parameters, path);
278 UrlParts parts = TryCreateUrlUsingRegisteredRoutes(current.Domain, parameters, appVirtualDir, routeParameters);
280 if (parts == null)
282 parts = new UrlParts(path, controller, action + (useExtensions ? SafeExt(current.Extension) : ""));
283 AppendPathInfo(parts, parameters);
286 AppendQueryString(parts, parameters);
288 return parts;
291 #endregion
293 /// <summary>
294 /// Tries the create URL using registered routes.
295 /// </summary>
296 /// <param name="domain">The domain.</param>
297 /// <param name="parameters">The parameters.</param>
298 /// <param name="appVirtualDir">The app virtual dir.</param>
299 /// <param name="routeParameters">The route parameters.</param>
300 /// <returns></returns>
301 protected UrlParts TryCreateUrlUsingRegisteredRoutes(string domain, UrlBuilderParameters parameters,
302 string appVirtualDir,
303 IDictionary routeParameters)
305 if (routingEng != null && !routingEng.IsEmpty)
307 // We take was was explicitly set (we do not inherit those from the context)
308 routeParameters["area"] = parameters.Area;
309 routeParameters["controller"] = parameters.Controller;
310 routeParameters["action"] = parameters.Action;
312 if (parameters.UseCurrentRouteParams)
314 if (parameters.RouteMatch == null)
316 throw new InvalidOperationException("Error creating URL. 'UseCurrentRouteParams' was set, but routematch is null.");
319 CommonUtils.MergeOptions(routeParameters, parameters.RouteMatch.Parameters);
322 string url;
324 if (parameters.RouteName != null)
326 url = routingEng.CreateUrl(parameters.RouteName, domain, appVirtualDir, routeParameters);
328 else
330 url = routingEng.CreateUrl(domain, appVirtualDir, routeParameters);
333 if (url != null)
335 return new UrlParts(url);
339 return null;
342 /// <summary>
343 /// Appends the path info.
344 /// </summary>
345 /// <param name="parts">The parts.</param>
346 /// <param name="parameters">The parameters.</param>
347 protected virtual void AppendPathInfo(UrlParts parts, UrlBuilderParameters parameters)
349 if (!string.IsNullOrEmpty(parameters.PathInfo))
351 parts.PathInfoDict.Parse(parameters.PathInfo);
355 /// <summary>
356 /// Converts the route params.
357 /// </summary>
358 /// <param name="routeParams">The route params.</param>
359 /// <returns></returns>
360 protected virtual IDictionary ConvertRouteParams(object routeParams)
362 IDictionary parameters;
364 if (routeParams != null)
366 if (typeof(IDictionary).IsAssignableFrom(routeParams.GetType()))
368 parameters = (IDictionary) routeParams;
370 else
372 parameters = new ReflectionBasedDictionaryAdapter(routeParams);
375 // Forces copying entries to a non readonly dictionary, preserving the original one
376 parameters = new Hashtable(parameters, StringComparer.InvariantCultureIgnoreCase);
378 else
380 parameters = new HybridDictionary(true);
383 return parameters;
386 /// <summary>
387 /// Computes the standard base path.
388 /// </summary>
389 /// <param name="appVirtualDir">The app virtual dir.</param>
390 /// <param name="area">The area.</param>
391 /// <returns></returns>
392 protected virtual string ComputeStandardBasePath(string appVirtualDir, string area)
394 string path;
396 if (area != String.Empty)
398 path = appVirtualDir + "/" + area + "/";
400 else
402 path = appVirtualDir + "/";
405 return path;
408 /// <summary>
409 /// Applies the base path or absolute path if necessary.
410 /// </summary>
411 /// <param name="appVirtualDir">The app virtual dir.</param>
412 /// <param name="area">The area.</param>
413 /// <param name="current">The current.</param>
414 /// <param name="parameters">The parameters.</param>
415 /// <param name="path">The path.</param>
416 /// <returns></returns>
417 protected virtual string ApplyBasePathOrAbsolutePathIfNecessary(string appVirtualDir, string area, UrlInfo current,
418 UrlBuilderParameters parameters, string path)
420 bool createAbsolutePath = parameters.CreateAbsolutePath;
421 string basePath = parameters.BasePath;
423 if (!string.IsNullOrEmpty(basePath))
425 basePath = basePath[basePath.Length - 1] == '/' ? basePath.Substring(0, basePath.Length - 1) : basePath;
427 if (basePath.EndsWith(area, StringComparison.InvariantCultureIgnoreCase))
429 path = basePath;
431 else
433 path = basePath + "/" + area;
436 else if (createAbsolutePath)
438 string domain = parameters.Domain ?? current.Domain;
439 string subdomain = parameters.Subdomain ?? current.Subdomain;
440 string protocol = parameters.Protocol ?? current.Protocol;
441 int port = parameters.Port == 0 ? current.Port : 0;
443 bool includePort =
444 (protocol == "http" && port != 80) ||
445 (protocol == "https" && port != 443);
447 path = protocol + "://";
449 if (!string.IsNullOrEmpty(subdomain))
451 path += subdomain + "." + domain;
453 else
455 path += domain;
458 if (includePort)
460 path += ":" + port;
463 path += ComputeStandardBasePath(appVirtualDir, area);
466 return path;
469 /// <summary>
470 /// Appends the query string.
471 /// </summary>
472 /// <param name="parts">The parts.</param>
473 /// <param name="parameters">The parameters.</param>
474 protected virtual void AppendQueryString(UrlParts parts, UrlBuilderParameters parameters)
476 object queryString = parameters.QueryString;
478 string suffix = string.Empty;
480 if (queryString != null)
482 if (queryString is IDictionary)
484 IDictionary qsDictionary = (IDictionary) queryString;
486 suffix = CommonUtils.BuildQueryString(serverUtil, qsDictionary, false);
488 else if (queryString is NameValueCollection)
490 suffix = CommonUtils.BuildQueryString(serverUtil, (NameValueCollection) queryString, false);
492 else if (queryString is string && ((string)queryString).Length > 0)
494 string[] pairs = queryString.ToString().Split('&');
496 suffix = string.Empty;
498 foreach(string pair in pairs)
500 string[] keyvalues = pair.Split(new char[] { '=' }, 2);
502 if (keyvalues.Length < 2) continue;
504 if (suffix.Length != 0)
506 suffix += "&";
509 suffix += serverUtil.UrlEncode(keyvalues[0]) + "=" + serverUtil.UrlEncode(keyvalues[1]);
514 if (suffix != string.Empty)
516 parts.SetQueryString(suffix);
520 private string SafeExt(string extension)
522 if (extension.StartsWith("."))
524 return extension;
527 return "." + extension;
530 private static void AssertArguments<T>(UrlInfo current, T parameters) where T : class
532 if (current == null)
534 throw new ArgumentNullException("current");
536 if (parameters == null)
538 throw new ArgumentNullException("parameters");