Search code examples
c#asp.netasp.net-coreurlurl-parameters

Is there a way to work with parameters in URL path segments in ASP Net/ASP Net Core


Recently I have read an article about URL and its parts and what is defined in the standard. There is an interesting part about parameters. Usually, you pass parameters in one of the following ways:

  • In the URL path as a segment;
  • In the URL query string;
  • In the request body;
  • In the request header;

However, according to the article there is one more way - to pass parameters in the URL path segments separating them from the segment with a semicolon:

parameters – talking about parameters, these can also appear after the path but before the query string, also separated from the rest of the URL and from each other by ; characters e.g.:

http://www.blah.com/some/crazy/path.html;param1=foo;param2=bar

I have never encountered this approach before and the article mentions that it is rarely used. Is is supported in .NET, particularly in ASP.NET or ASP.NET Core?


Solution

  • I don't really care if the kind of weird format for query parameters is standard or not. If you try it yourself, you'll see that ASP.NET Core does not support that format. It's still considered a segment and once parsed, if it's not matched by any route patterns, the response is simply 404.

    To support that kind of weird format (I talk about the original format in your question, not another even weirder format in your comment), theoretically you can use a custom QueryStringValueProviderFactory. The default one creates the QueryStringValueProvider from Request.Query. In your custom one, you can create a QueryStringValueProvider from your own set of parameters which can be parsed from the raw request URL. However this way is not that easy because it's too late for your request to come at the model binding phase (where the value providers are used to build up the request model as well as the action parameters). Because it's too late so your request path does not even match with any route pattern so the pipeline will be short-circuited with a response of 404.

    Technically to follow that approach, you need to somehow make it reach the model binding phase first (meaning make the request work as if the semicolon-separated query parameters didn't exist). I think it's possible to remove the last segment (if it contains any semicolon) inside the routing process. However it's of course not easy. That way is simply too complicated.

    Here I would like to introduce another simpler (and working) approach. We can use a middleware to parse the URL and build-up the query string (maybe appended to the existing standard query string if any) ourselves before the request coming into the MVC pipeline.

    It's just simple like this:

    //an extension method for conveniently registering your middleware later 
    public static class ComplexQueryStringMiddlewareExtensions
    {
        public static IApplicationBuilder UseComplexQueryStringMiddleware(this IApplicationBuilder appBuilder)
        {
            return appBuilder.Use((context, next) => {
                var path = context.Request.Path;
                var semicolonSepParameters = path.Value.Split(';');
                //the first part is always the correct path
                context.Request.Path = semicolonSepParameters[0];
                semicolonSepParameters = semicolonSepParameters.Skip(1).Where(e => !string.IsNullOrWhiteSpace(e)).ToArray();
                if (semicolonSepParameters.Length > 0)
                {
                    var appendedQueryString = string.Join("&", semicolonSepParameters);
                    //in case there is some standard query string as well
                    if (context.Request.Query != null && context.Request.Query.Count > 0)
                    {
                        appendedQueryString = context.Request.QueryString + "&" + appendedQueryString;
                    } else
                    {
                        appendedQueryString = "?" + appendedQueryString;
                    }
                    context.Request.QueryString = new Microsoft.AspNetCore.Http.QueryString(appendedQueryString);
                }
                return next();
            });
        }
    }
    

    Now inside the Startup.Configure method, ensure that your middleware registration is placed before UseRouting (if any):

    app.UseComplexQueryStringMiddleware();
    //if this exists (which is usually by the default generated code)
    //this must be after the above
    app.UseRouting();
    //of course the UseEndpoints is always at the end
    

    Now your http://www.blah.com/some/crazy/path.html;param1=foo;param2=bar should work just like http://www.blah.com/some/crazy/path.html?param1=foo&param2=bar. You can even mix the 2 formats together like http://www.blah.com/some/crazy/path.html;param1=foo;param2=bar?param3=ok&param4=yes.