Search code examples
asp.net-coreurl-rewritingweb-config

How to replicate rewrite rule in .net core startup from web.config, API calls returning index.html


I have following Web.Config, which works fine for IIS. However its not working for self hosted like in Windows Service.

 <system.webServer>
  <rewrite>
    <rules>
      <rule name="Angular Routes" stopProcessing="true">
        <match url="./*" />
        <conditions logicalGrouping="MatchAll">
          <add input="{REQUEST_URI}" pattern="^/(api)" negate="true" />
        </conditions>
      </rule>
    </rules>
  </rewrite>
</system.webServer>

So that the UI won't treat /api as part of html url.

However I hosted this application in windows service and this web.config file is no longer works. The index.html file is being returned.

Is there a way I could add this rule to .net core, so that same rule is applied when I host it in Windows Service rather than IIS.

Startup class:

public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();
            
            services.AddSpaStaticFiles(configuration =>
            {
                configuration.RootPath = "publish/ClientApp/dist";
            });
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
                app.UseHsts();
            }

            app.UseHttpsRedirection();

            app.UseStaticFiles();

            if (!env.IsDevelopment())
            {
                app.UseSpaStaticFiles();
            }

            app.UseRouting();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller}/{action=Index}/{id?}");
            });

            app.UseSpa(spa =>
            {
                spa.Options.SourcePath = "ClientApp";

                if (env.IsDevelopment())
                {
                    spa.UseAngularCliServer(npmScript: "start");
                }
            });
        }

Program Class:

Added following logic to run as Windows Service

public static void Main(string[] args)
        {
            //Check for the Debugger is attached or not if attached then run the application in IIS or IISExpress
            var isService = false;

            //when the service start we need to pass the --service parameter while running the .exe
            if (Debugger.IsAttached == false && args.Contains("--service"))
            {
                isService = true;
            }

            if (isService)
            {
                //Get the Content Root Directory
                var pathToContentRoot = Directory.GetCurrentDirectory();

                string ConfigurationFile = "appsettings.json"; //Configuration file.
                string portNo = "5003"; //Port

                var pathToExe = Process.GetCurrentProcess().MainModule.FileName;
                pathToContentRoot = Path.GetDirectoryName(pathToExe);

                //Get the json file and read the service port no if available in the json file.
                string AppJsonFilePath = Path.Combine(pathToContentRoot, ConfigurationFile);

                if (File.Exists(AppJsonFilePath))
                {
                    using (StreamReader sr = new StreamReader(AppJsonFilePath))
                    {
                        string jsonData = sr.ReadToEnd();
                        JObject jObject = JObject.Parse(jsonData);
                        if (jObject["ServicePort"] != null)
                            portNo = jObject["ServicePort"].ToString();

                    }
                }

                var host = WebHost.CreateDefaultBuilder(args)
                .UseContentRoot(pathToContentRoot)
                .UseStartup<Startup>()
                .UseUrls("http://localhost:" + portNo)
                .Build();

                host.RunAsService();
            }
            else
            {
                CreateHostBuilder(args).Build().Run();
            }
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });

Solution

  • ASP.NET Core URL Rewriting Middleware was created specifically for this purpose. Per the documentation: Use URL Rewriting Middleware when you're unable to use the following approaches:

    • URL Rewrite module with IIS on Windows Server
    • Apache mod_rewrite module on Apache Server
    • URL rewriting on Nginx

    The first step is to establish URL rewrite and redirect rules by creating an instance of the RewriteOptions class with extension methods for any of your rewrite rules. An example of this is defined below:

    public void Configure(IApplicationBuilder app)
    {
        using (StreamReader apacheModRewriteStreamReader = 
            File.OpenText("ApacheModRewrite.txt"))
        using (StreamReader iisUrlRewriteStreamReader = 
            File.OpenText("IISUrlRewrite.xml")) 
        {
            var options = new RewriteOptions()
                .AddRedirect("redirect-rule/(.*)", "redirected/$1")
                .AddRewrite(@"^rewrite-rule/(\d+)/(\d+)", "rewritten?var1=$1&var2=$2", 
                    skipRemainingRules: true)
                .AddApacheModRewrite(apacheModRewriteStreamReader)
                .AddIISUrlRewrite(iisUrlRewriteStreamReader)
                .Add(MethodRules.RedirectXmlFileRequests)
                .Add(MethodRules.RewriteTextFileRequests)
                .Add(new RedirectImageRequests(".png", "/png-images"))
                .Add(new RedirectImageRequests(".jpg", "/jpg-images"));
    
            app.UseRewriter(options);
        }
    
        app.UseStaticFiles();
    
        app.Run(context => context.Response.WriteAsync(
            $"Rewritten or Redirected Url: " +
            $"{context.Request.Path + context.Request.QueryString}"));
    }
    

    Regex Matching

    If you specifically want to use rewrites utilizing regex matching then you can use AddRedirect to redirect requests. The first parameter contains your regex for matching on the path of the incoming URL. The second parameter is the replacement string. The third parameter, if present, specifies the status code. If you don't specify the status code, the status code defaults to 302 - Found, which indicates that the resource is temporarily moved or replaced.

    Example:

    AddRewrite(@"^rewrite-rule/(\d+)/(\d+)", "rewritten?var1=$1&var2=$2", 
                    skipRemainingRules: true)
    

    For your specific request

    For your specific request you would use something akin to the following in your Configure method:

    var options = new RewriteOptions().AddRewrite(@"\/(api)\/(.*)", "\/$2",true);
    

    Keep in mind you may have to adjust the regex values to suit a specific purpose depending on your application configuration. app.UseRewriter(rewrite);