@@ -159,6 +159,101 @@ public static UrlParameter[] DecodeParam(string parameter)
159159 return retParams ;
160160 }
161161
162+ /// <summary>
163+ /// Extracts route parameters from a URL that matches a parameterized route.
164+ /// </summary>
165+ /// <param name="route">The route template with parameters (e.g., "/api/devices/{id}").</param>
166+ /// <param name="rawUrl">The actual URL being requested.</param>
167+ /// <param name="caseSensitive">Whether the comparison should be case sensitive.</param>
168+ /// <returns>An array of UrlParameter objects containing the parameter names and values, or null if the route doesn't match.</returns>
169+ public static UrlParameter [ ] ExtractRouteParameters ( string route , string rawUrl , bool caseSensitive = false )
170+ {
171+ if ( string . IsNullOrEmpty ( route ) || string . IsNullOrEmpty ( rawUrl ) )
172+ {
173+ return null ;
174+ }
175+
176+ // Remove query parameters from the URL for matching
177+ var urlParam = rawUrl . IndexOf ( ParamStart ) ;
178+ var urlPath = urlParam > 0 ? rawUrl . Substring ( 0 , urlParam ) : rawUrl ;
179+
180+ // Normalize the URL path and route for comparison
181+ var urlToCompare = caseSensitive ? urlPath : urlPath . ToLower ( ) ;
182+ var routeToCompare = caseSensitive ? route : route . ToLower ( ) ;
183+
184+ // Ensure both paths start with '/' for consistent segment splitting
185+ if ( ! urlToCompare . StartsWith ( "/" ) )
186+ {
187+ urlToCompare = "/" + urlToCompare ;
188+ }
189+ if ( ! routeToCompare . StartsWith ( "/" ) )
190+ {
191+ routeToCompare = "/" + routeToCompare ;
192+ }
193+
194+ // Split into segments
195+ var urlSegments = urlToCompare . Split ( '/' ) ;
196+ var routeSegments = routeToCompare . Split ( '/' ) ;
197+
198+ // Number of segments must match
199+ if ( urlSegments . Length != routeSegments . Length )
200+ {
201+ return null ;
202+ }
203+
204+ ArrayList parameters = new ArrayList ( ) ;
205+
206+ // Compare each segment and extract parameters
207+ for ( int i = 0 ; i < routeSegments . Length ; i ++ )
208+ {
209+ var routeSegment = routeSegments [ i ] ;
210+ var urlSegment = urlSegments [ i ] ;
211+
212+ // Skip empty segments (from leading slash)
213+ if ( string . IsNullOrEmpty ( routeSegment ) && string . IsNullOrEmpty ( urlSegment ) )
214+ {
215+ continue ;
216+ }
217+
218+ // Check if this is a parameter segment (starts and ends with curly braces)
219+ if ( routeSegment . Length > 2 &&
220+ routeSegment . StartsWith ( "{" ) &&
221+ routeSegment . EndsWith ( "}" ) )
222+ {
223+ // Parameter segment matches any non-empty segment that doesn't contain '/'
224+ if ( string . IsNullOrEmpty ( urlSegment ) || urlSegment . IndexOf ( '/' ) >= 0 )
225+ {
226+ return null ;
227+ }
228+
229+ // Extract parameter name (remove curly braces)
230+ var paramName = routeSegment . Substring ( 1 , routeSegment . Length - 2 ) ;
231+ parameters . Add ( new UrlParameter { Name = paramName , Value = urlSegments [ i ] } ) ; // Use original case for value
232+ continue ;
233+ }
234+
235+ // Exact match required for non-parameter segments
236+ if ( routeSegment != urlSegment )
237+ {
238+ return null ;
239+ }
240+ }
241+
242+ // Convert ArrayList to array
243+ if ( parameters . Count == 0 )
244+ {
245+ return null ;
246+ }
247+
248+ var result = new UrlParameter [ parameters . Count ] ;
249+ for ( int i = 0 ; i < parameters . Count ; i ++ )
250+ {
251+ result [ i ] = ( UrlParameter ) parameters [ i ] ;
252+ }
253+
254+ return result ;
255+ }
256+
162257 #endregion
163258
164259 #region Constructors
@@ -695,29 +790,68 @@ public static bool IsRouteMatch(CallbackRoutes route, string method, string rawU
695790 return false ;
696791 }
697792
793+ // Remove query parameters from the URL for matching
698794 var urlParam = rawUrl . IndexOf ( ParamStart ) ;
699- var incForSlash = route . Route . IndexOf ( '/' ) == 0 ? 0 : 1 ;
700- var rawUrlToCompare = route . CaseSensitive ? rawUrl : rawUrl . ToLower ( ) ;
795+ var urlPath = urlParam > 0 ? rawUrl . Substring ( 0 , urlParam ) : rawUrl ;
796+
797+ // Normalize the URL path and route for comparison
798+ var urlToCompare = route . CaseSensitive ? urlPath : urlPath . ToLower ( ) ;
701799 var routeToCompare = route . CaseSensitive ? route . Route : route . Route . ToLower ( ) ;
702- bool isFound ;
703-
704- if ( urlParam > 0 )
800+
801+ // Ensure both paths start with '/' for consistent segment splitting
802+ if ( ! urlToCompare . StartsWith ( "/" ) )
705803 {
706- isFound = urlParam == routeToCompare . Length + incForSlash ;
804+ urlToCompare = "/" + urlToCompare ;
707805 }
708- else
806+
807+ if ( ! routeToCompare . StartsWith ( "/" ) )
709808 {
710- isFound = rawUrlToCompare . Length == routeToCompare . Length + incForSlash ;
809+ routeToCompare = "/" + routeToCompare ;
711810 }
712-
713- // Matching the route name
714- // Matching the method type
715- if ( ! isFound ||
716- ( ! string . IsNullOrEmpty ( routeToCompare ) && rawUrlToCompare . IndexOf ( routeToCompare ) != incForSlash ) )
811+
812+ // Split into segments
813+ var urlSegments = urlToCompare . Split ( '/' ) ;
814+ var routeSegments = routeToCompare . Split ( '/' ) ;
815+
816+ // Number of segments must match
817+ if ( urlSegments . Length != routeSegments . Length )
717818 {
718819 return false ;
719820 }
720-
821+
822+ // Compare each segment
823+ for ( int i = 0 ; i < routeSegments . Length ; i ++ )
824+ {
825+ var routeSegment = routeSegments [ i ] ;
826+ var urlSegment = urlSegments [ i ] ;
827+
828+ // Skip empty segments (from leading slash)
829+ if ( string . IsNullOrEmpty ( routeSegment ) && string . IsNullOrEmpty ( urlSegment ) )
830+ {
831+ continue ;
832+ }
833+
834+ // Check if this is a parameter segment (starts and ends with curly braces)
835+ if ( routeSegment . Length > 2 &&
836+ routeSegment . StartsWith ( "{" ) &&
837+ routeSegment . EndsWith ( "}" ) )
838+ {
839+ // Parameter segment matches any non-empty segment that doesn't contain '/'
840+ if ( string . IsNullOrEmpty ( urlSegment ) || urlSegment . IndexOf ( '/' ) >= 0 )
841+ {
842+ return false ;
843+ }
844+ // Parameter matches, continue to next segment
845+ continue ;
846+ }
847+
848+ // Exact match required for non-parameter segments
849+ if ( routeSegment != urlSegment )
850+ {
851+ return false ;
852+ }
853+ }
854+
721855 return true ;
722856 }
723857
@@ -728,7 +862,15 @@ public static bool IsRouteMatch(CallbackRoutes route, string method, string rawU
728862 /// <param name="context">Context of current request.</param>
729863 protected virtual void InvokeRoute ( CallbackRoutes route , HttpListenerContext context )
730864 {
731- route . Callback . Invoke ( null , new object [ ] { new WebServerEventArgs ( context ) } ) ;
865+ // Extract route parameters if the route contains parameter placeholders
866+ var routeParameters = ExtractRouteParameters ( route . Route , context . Request . RawUrl , route . CaseSensitive ) ;
867+
868+ // Create WebServerEventArgs with or without route parameters
869+ var eventArgs = routeParameters != null
870+ ? new WebServerEventArgs ( context , routeParameters )
871+ : new WebServerEventArgs ( context ) ;
872+
873+ route . Callback . Invoke ( null , new object [ ] { eventArgs } ) ;
732874 }
733875
734876 private static void HandleContextResponse ( HttpListenerContext context )
0 commit comments