1 // Copyright 2004-2008 Castle Project - http://www.castleproject.org/
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
7 // http://www.apache.org/licenses/LICENSE-2.0
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
18 using System
.Collections
;
19 using System
.Collections
.Specialized
;
21 using Castle
.MonoRail
.Framework
.Internal
;
26 /// Default implementation of <see cref="IUrlBuilder"/>
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
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
;
40 /// Initializes a new instance of the <see cref="DefaultUrlBuilder"/> class.
42 public DefaultUrlBuilder()
47 /// Initializes a new instance of the <see cref="DefaultUrlBuilder"/> class.
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
;
60 /// Gets or sets a value indicating whether the builder should output an extension.
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; }
70 /// Gets or sets the server utility instance.
72 /// <value>The server util.</value>
73 public IServerUtility ServerUtil
75 get { return serverUtil; }
76 set { serverUtil = value; }
80 /// Gets or sets the routing engine.
82 /// <value>The routing engine.</value>
83 public IRoutingEngine RoutingEngine
85 get { return routingEng; }
86 set { routingEng = value; }
91 #region IServiceEnabledComponent
94 /// Services the specified provider.
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
));
111 /// Builds the URL using the current url as contextual information and the specified parameters.
113 /// Common parameters includes area, controller and action. See <see cref="UrlBuilderParameters"/>
114 /// for more information regarding the parameters.
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
));
130 /// Builds the URL using the current url as contextual information and the specified parameters.
132 /// Common parameters includes area, controller and action. See <see cref="UrlBuilderParameters"/>
133 /// for more information regarding the parameters.
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
));
150 /// Builds the URL using the current url as contextual information and the specified parameters.
152 /// Common parameters includes area, controller and action. See <see cref="UrlBuilderParameters"/>
153 /// for more information regarding the parameters.
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
));
167 /// Builds the URL using the current url as contextual information and the specified parameters.
169 /// Common parameters includes area, controller and action. See <see cref="UrlBuilderParameters"/>
170 /// for more information regarding the parameters.
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
);
187 return url
.BuildPathForLink(serverUtil
);
190 return url
.BuildPath();
194 /// Builds the URL using the current url as contextual information and the specified parameters.
196 /// Common parameters includes area, controller and action. See <see cref="UrlBuilderParameters"/>
197 /// for more information regarding the parameters.
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
));
213 /// Builds the URL using the current url as contextual information and the specified parameters.
215 /// Common parameters includes area, controller and action. See <see cref="UrlBuilderParameters"/>
216 /// for more information regarding the parameters.
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
));
233 /// Builds the URL using the current url as contextual information and the specified parameters.
235 /// Common parameters includes area, controller and action. See <see cref="UrlBuilderParameters"/>
236 /// for more information regarding the parameters.
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
));
250 /// Builds the URL using the current url as contextual information and the specified parameters.
252 /// Common parameters includes area, controller and action. See <see cref="UrlBuilderParameters"/>
253 /// for more information regarding the parameters.
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
);
282 parts
= new UrlParts(path
, controller
, action
+ (useExtensions
? SafeExt(current
.Extension
) : ""));
283 AppendPathInfo(parts
, parameters
);
286 AppendQueryString(parts
, parameters
);
294 /// Tries the create URL using registered routes.
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
);
324 if (parameters
.RouteName
!= null)
326 url
= routingEng
.CreateUrl(parameters
.RouteName
, domain
, appVirtualDir
, routeParameters
);
330 url
= routingEng
.CreateUrl(domain
, appVirtualDir
, routeParameters
);
335 return new UrlParts(url
);
343 /// Appends the path info.
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
);
356 /// Converts the route params.
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
;
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
);
380 parameters
= new HybridDictionary(true);
387 /// Computes the standard base path.
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
)
396 if (area
!= String
.Empty
)
398 path
= appVirtualDir
+ "/" + area
+ "/";
402 path
= appVirtualDir
+ "/";
409 /// Applies the base path or absolute path if necessary.
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
))
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;
444 (protocol
== "http" && port
!= 80) ||
445 (protocol
== "https" && port
!= 443);
447 path
= protocol
+ "://";
449 if (!string.IsNullOrEmpty(subdomain
))
451 path
+= subdomain
+ "." + domain
;
463 path
+= ComputeStandardBasePath(appVirtualDir
, area
);
470 /// Appends the query string.
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)
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("."))
527 return "." + extension
;
530 private static void AssertArguments
<T
>(UrlInfo current
, T parameters
) where T
: class
534 throw new ArgumentNullException("current");
536 if (parameters
== null)
538 throw new ArgumentNullException("parameters");