@@ -159,6 +159,101 @@ public static UrlParameter[] DecodeParam(string parameter)
159
159
return retParams ;
160
160
}
161
161
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
+
162
257
#endregion
163
258
164
259
#region Constructors
@@ -695,29 +790,68 @@ public static bool IsRouteMatch(CallbackRoutes route, string method, string rawU
695
790
return false ;
696
791
}
697
792
793
+ // Remove query parameters from the URL for matching
698
794
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 ( ) ;
701
799
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 ( "/" ) )
705
803
{
706
- isFound = urlParam == routeToCompare . Length + incForSlash ;
804
+ urlToCompare = "/" + urlToCompare ;
707
805
}
708
- else
806
+
807
+ if ( ! routeToCompare . StartsWith ( "/" ) )
709
808
{
710
- isFound = rawUrlToCompare . Length == routeToCompare . Length + incForSlash ;
809
+ routeToCompare = "/" + routeToCompare ;
711
810
}
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 )
717
818
{
718
819
return false ;
719
820
}
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
+
721
855
return true ;
722
856
}
723
857
@@ -728,7 +862,15 @@ public static bool IsRouteMatch(CallbackRoutes route, string method, string rawU
728
862
/// <param name="context">Context of current request.</param>
729
863
protected virtual void InvokeRoute ( CallbackRoutes route , HttpListenerContext context )
730
864
{
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 } ) ;
732
874
}
733
875
734
876
private static void HandleContextResponse ( HttpListenerContext context )
0 commit comments