szkut
szkut

Reputation: 381

URL Rewriting Middleware ASP.Net Core 2.0

I can't write correctly a URL Rewriting Middleware. I think the regex is correct. I use this functionality for the first time and maybe I do not understand something.

Example urls:

http://localhost:55830/shop/alefajniealsdnoqwwdnanxc!@#lxncqihen41j2ln4nkzcbjnzxncas?valueId=116
http://localhost:55830/shop/whatever?valueId=116
http://localhost:55830/shop/toquestionmark?valueId=116

Regex:

\/shop\/([^\/?]*)(?=[^\/]*$)

Startup, Configure:

var rewrite = new RewriteOptions().AddRewrite(
        @"\/shop\/([^\/?]*)(?=[^\/]*$)",
        "/shop/$1",
        true
    );
app.UseRewriter(rewrite);

Maybe the order related to another methods have matters?

Controller:

[Route(RouteUrl.Listing + RouteUrl.Slash + "{" + ActionFilter.Value + "?}", Name = RouteUrl.Name.ShopBase)]
public ActionResult Index(string value, int valueId)
{
    return View();
}

For example, When I redirect to:

http://localhost:55830/shop/shoes?valueId=116

I want to show url like this:

http://localhost:55830/shop/shoes

Upvotes: 2

Views: 10154

Answers (2)

Siavash
Siavash

Reputation: 3009

based on this article: https://www.softfluent.com/blog/dev/Page-redirection-and-URL-Rewriting-with-ASP-NET-Core

When you develop a web application, you often need to add some redirection rules. The most common redirection rules are: redirect from "http" to "https", add "www", or move a website to another domain. URL rewriting is often use to provide user friendly URL.

I want to explain the difference between redirection and rewrite. Redirecting sends a HTTP 301 or 302 to the client, telling the client that it should access the page using another URL. The browser will update the URL visible in the address bar, and make a new request using the new URL. On the other hand, rewriting happens on the server, and is a translation of one URL to another. The server will use the new URL to process the request. The client doesn't know that the server has rewritten the URL.

With IIS, you can use the web.config file to define the redirection and rewrite rules or use RewritePath:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.webServer>
    <rewrite>
      <rules>
        <rule name="Redirect to https">
          <match url="(.*)" />
          <conditions>
            <add input="{HTTPS}" pattern="Off" />
            <add input="{REQUEST_METHOD}" pattern="^get$|^head$" />
            <add input="{HTTP_HOST}" negate="true" pattern="localhost" />
          </conditions>
          <action type="Redirect" url="https://{HTTP_HOST}/{R:1}" />
        </rule>
      </rules>
    </rewrite>
  </system.webServer>
</configuration>

However, this doesn't work anymore with ASP.NET Core. Instead, you can use the new NuGet package: Microsoft.AspNetCore.Rewrite (GitHub). This package is very easy to use. Open the startup.cs file and edit the Configure method:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, ISiteProvider siteProvider)
{           
    app.UseRewriter(new RewriteOptions()
        .AddRedirectToHttps()
        .AddRedirect(@"^section1/(.*)", "new/$1", (int)HttpStatusCode.Redirect)
        .AddRedirect(@"^section2/(\\d+)/(.*)", "new/$1/$2", (int)HttpStatusCode.MovedPermanently)
        .AddRewrite("^feed$", "/?format=rss", skipRemainingRules: false));

    app.UseStaticFiles();

    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller=Home}/{action=Index}/{id?}");
    });
}

The rules are based on regex and substitutions. The regex is evaluated on the HttpContext.Request.Path, which does not include the domain, nor the protocol. This means you cannot redirect to another domain or add "www" using this method, but don't worry, I will show you how to do this after!

Microsoft has decided to help you using this new package. Indeed, if you already have a web.config file or even an .htaccess file (.net core is cross platform) you can import them directly:

app.UseRewriter(new RewriteOptions()
    .AddIISUrlRewrite(env.ContentRootFileProvider, "web.config")
    .AddApacheModRewrite(env.ContentRootFileProvider, ".htaccess"));

If you have complex rules that can't be expressed using a regex, you can write your own rule. A rule is a class that implements Microsoft.AspNetCore.Rewrite.IRule:

// app.UseRewriter(new RewriteOptions().Add(new RedirectWwwRule()));

public class RedirectWwwRule : Microsoft.AspNetCore.Rewrite.IRule
{
    public int StatusCode { get; } = (int)HttpStatusCode.MovedPermanently;
    public bool ExcludeLocalhost { get; set; } = true;

    public void ApplyRule(RewriteContext context)
    {
        var request = context.HttpContext.Request;
        var host = request.Host;
        if (host.Host.StartsWith("www", StringComparison.OrdinalIgnoreCase))
        {
            context.Result = RuleResult.ContinueRules;
            return;
        }

        if (ExcludeLocalhost && string.Equals(host.Host, "localhost", StringComparison.OrdinalIgnoreCase))
        {
            context.Result = RuleResult.ContinueRules;
            return;
        }

        string newPath = request.Scheme + "://www." + host.Value + request.PathBase + request.Path + request.QueryString;

        var response = context.HttpContext.Response;
        response.StatusCode = StatusCode;
        response.Headers[HeaderNames.Location] = newPath;
        context.Result = RuleResult.EndResponse; // Do not continue processing the request        
    }
}

Upvotes: 16

szkut
szkut

Reputation: 381

The solution of my problem was simpler than it seemed. I admit that I did not know before that I can combine POST with GET:

View: - strongly type partial view @model MainNavigationArchon

<form asp-controller="@Model.Controller" asp-action="@RouteUrl.Index" asp-route-value="@AureliaCMS.Infrastructure.Helpers.StringHelper.RemoveDiacritics(Model.Value.ToLower())" method="post">
    <input type="hidden" asp-for="@Model.ValueId" />
    <button class="btn btn-link">@Model.Value</button>
</form>

Controller:

[Route("shop" + RouteUrl.Slash + "{value}", Name = RouteUrl.Name.ShopBase)]
public ActionResult Index(int valueId)
{
    return View();
}

Url contains:

shop from route attribute

shoes from asp-route-value

and ?valueId=116 is sending by hidden field asp-for="@Model.ValueId"

Result:

I'm sending something like http://localhost:55830/shop/shoes?valueId=116 and showing the http://localhost:55830/shop/shoes

Upvotes: 0

Related Questions