aspnet / BasicMiddleware

[Archived] Basic middleware components for ASP.NET Core. Project moved to https://github.com/aspnet/AspNetCore
Apache License 2.0
169 stars 84 forks source link

Create URL Rewrite Middleware #43

Closed jkotalik closed 8 years ago

jkotalik commented 8 years ago

Objectives:

jkotalik commented 8 years ago

Implementation Proposal:

Create a new builder, UrlRewriteBuilder. This builder would, for example, have a method: RewritePath(string regexCompare, Func<UrlRewriteContext, Task> onMatch) which would add a rule for the HttpRequest's path string. The UrlRewriteContext will be used to store any information that was captured in the regex matching. When a HttpRequest comes in, if there is a regex match on the input's path string, the onMatch function would be called.

Users can provided their own onMatch function and have access to the UrlRewriteContext. We will also provide overloads/ methods to do certain tasks (Ex overloading RewritePath to take in two strings, with a regex string to convert the matched string appropriately).

Cowlephant commented 8 years ago

So I'm guessing with several months away for 1.1.0 release, we should be looking into one of the expressed alternative fixes?

Tratcher commented 8 years ago

Yes, it will be a few weeks before there's a working nightly build for this.

jkotalik commented 8 years ago

Use Cases and Extensibility :

DaveSlinn commented 8 years ago

This is not directly related, but I am currently using web.config url rewrite for my .net core web app and it works, but i want to use web.config transformations to add url rewrite entry for release build and publish and that doesn't seem to be working with .net core. Do .config transforms not work anymore with dotnet.exe publish?

Tratcher commented 8 years ago

No, config transforms are not supported.

moozzyk commented 8 years ago

You could actually try this: https://github.com/aspnet/IISIntegration/issues/146#issuecomment-222388384

DaveSlinn commented 8 years ago

Thanks for the suggestion - managed to use nil4's dotnet xdt package integrated into our build process (angular 2 app building on-premises and publishing to azure) - and is working fine. I'll keep an eye on this WIP in the coming weeks and see if it makes sense to transition over to UrlRewriteBuilder when it becomes available.

wpostma commented 8 years ago

What is the plan for dotnet 1.1 with regards to URL rewrite and redirect support while deploying to IIS?

  1. Will IIS redirection rules configured from within the IIS manager with URL Rewrite extension installed, be supported? Currently it looks like in rtm, when I try to configure a redirect rule like this, from within IIS, it results in an HTTP 500.19 error, either Cannot add duplicate collection entry of type 'add' with unique key attribute 'name' set to 'aspNetCore', or that it can't find web.config? Sample IIS rewrite rule, which I want to have rewrite http://localhost/api/v3/worklist/xyz?a=3 as http://localhost/api/Worklist/xyz?a=3
<system.webServer>
  <rewrite>
    <globalRules>
      <rule name="api/v3/worklist">
        <match url="^api/v3/worklist/(.*)" />
        <conditions>
        </conditions>
        <serverVariables>
        </serverVariables>
        <action type="Rewrite" url="api/Worklist/{R:1}" />
      </rule>
    </globalRules>
  </rewrite>
</system.webServer>
  1. Is web.config also going to allow redirect and rewrite?
jkotalik commented 8 years ago

1: With regards to the error, it seems that the error in not related at all to URL rewrite. Try compiling the project again without the rewrite section at all, and see if you get the same error. I think you most likely have your web.config defined twice. Ex:

<?xml version="1.0"?>
<configuration>
  <system.webServer>
    <handlers>
      <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified"/>
    </handlers>
    <aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="false" />
  </system.webServer>
</configuration>

The handlers section should only be in your project once.

2: web.config will still support IIS UrlRewrite which will be independent from and will run before the AspNetCore UrlRewrite. Therefore, both redirects and rewrites are still supported.

wpostma commented 8 years ago

Okay. It Works! Yay! In case this helps anybody else, here's what ought to have been obvious to me:

  1. I should have created the Rewrite URL rules INSIDE the application.
  2. Start with an EMPTY IIS configuration (delete your old junk) on your first developer machine attempt.
  3. Create a new IIS site, which will have its own newly created app pool. Set that app pool to use "No Managed Code".
  4. Inside the app pool, create applications.
  5. Create URL Rewrite rules in the APPLICATION node level in IIS Manager, not the MACHINE node level.
jkotalik commented 8 years ago

A few talking points we have to go over for the mod_rewrite implementation: @DamianEdwards @davidfowl @Tratcher

stodolos commented 8 years ago

Need help! I couldn't put together exactly what to put in the base web.config vs. the wwwroot\web.config. This is what my wwwroot\web.config looked like in RC1 Update 1:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.webServer>
    <handlers>
      <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
      <remove name="OPTIONSVerbHandler" />
      <remove name="TRACEVerbHandler" />
      <add name="httpPlatformHandler" path="*" verb="*" modules="httpPlatformHandler" resourceType="Unspecified"/>
    </handlers>
    <!-- These caused issues in dnx46/x64 -->
    <!--<modules runAllManagedModulesForAllRequests="true" />-->
    <rewrite>
      <rules>
        <!--Redirect selected traffic to index -->
        <rule name="Index Rule" stopProcessing="true">
          <match url=".*" />
          <conditions logicalGrouping="MatchAll">
            <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
            <add input="{REQUEST_URI}" matchType="Pattern" pattern="^/api/" negate="true" />
          </conditions>
          <action type="Rewrite" url="/index.html" />
        </rule>
      </rules>
    </rewrite>
    <httpPlatform
      processPath="%DNX_PATH%"
      arguments="%DNX_ARGS%"
      stdoutLogEnabled="false"
      startupTimeLimit="3600"/>
  </system.webServer>
</configuration>

How do I keep this same behavior in ASP.NET Core 1.0?

jkotalik commented 8 years ago

@stodolos The goal of this middleware is to allow people to transfer their rewrite rules from IIS and other sources to the ASP.NET Core middleware model. So the section:

 <rewrite>
      <rules>
           ...

will now be moved to a separate file that will be imported and parsed. The reason we can't just use the wwwroot or web.config is file checks simply will not work with Core.

The middleware for IIS Rewrite is functional, however still in a volatile state (APIs may/will change). This feature should be in a complete state in 2-3 weeks.

Regarding the other parts of web.config, @Tratcher ? Fairly sure the rest will go in wwwroot, but not sure.

Tratcher commented 8 years ago

Web.config needs to go into your project root since RC2. We're also no longer using httpPlatformHandler, it's aspNetCore now. https://github.com/aspnet/IISIntegration/blob/dev/samples/IISSample/web.config#L9-L13

stodolos commented 8 years ago

Thanks @ZestyBread and @Tratcher. What's the best and proper way to make this code part of my solution against the 1.0? I noticed that this solution references the 1.1 versions of things like Microsoft.AspNetCore.Http.Extensions.

Thanks again in advance

stodolos commented 8 years ago

I've tried A LOT of different ways to get this to work, and even when it works fine locally, it fails in Azure.

Every time I hit a deep link in Azure, I receive the following:

The resource you are looking for has been removed, had its name changed, or is temporarily unavailable.

However, even when browse to the site's root, and login (using 3rd party auth service), POSTs to my API controller return as 404s, even though it works fine in my local debug, and when I launch my app's EXE file from the command line on a local publish.

My latest attempt was to use a RouteBuilder to try and somewhat mimic what my old rewrites were doing. I just had all gets go to index.html, which works fine locally, but not in Azure.:

var routeBuilder = new RouteBuilder(app);

routeBuilder.MapGet("{*anything}", context =>
{
    context.Response.Redirect("/index.html");
    return Task.FromResult(0);
});

var routes = routeBuilder.Build();
app.UseRouter(routes);
stodolos commented 8 years ago

Update... I got it to work in Azure!

So I left in the route builder logic above, and I think the last culprit was the structure of the .xproj of my web application.

I noticed that when I started publishing to Azure, it would put ALL of the files inside of the wwwroot folder. So I would up with all the assemblies, etc. in the wwwroot folder, along with a child wwwroot folder, which had all of the files you'd expect in the parent wwwroot folder.

Seeing this, I compared a newly created ASP.NET Core 1.0 web application project file to my upgraded-several-times project file. Here are the key differences that I came across (and when I made them match the newer project format, magic took place):

  1. VSToolsPath -- Old: <Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" /> -- New: <Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
  2. BaseIntermediateOutputPath -- Old: <BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath> -- New: <BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
  3. OutputPath -- Old: <OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath> -- New: <OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
  4. TargetFrameworkVersion -- Old: Didn't have it -- New: <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
  5. DnxInvisibleContent -- Old: Didn't have it -- New:
<ItemGroup>
      <DnxInvisibleContent Include="bower.json" />
      <DnxInvisibleContent Include=".bowerrc" />
  </ItemGroup>
  1. targets -- Old: <Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" /> -- New: <Import Project="$(VSToolsPath)\DotNet.Web\Microsoft.DotNet.Web.targets" Condition="'$(VSToolsPath)' != ''" />

P.S. Sorry for clogging up this thread but hopefully this saves someone else out there.

stodolos commented 8 years ago

Wound up changing my routing to a middleware by following an example here.

Flood commented 8 years ago

Will this middleware solve deep linking (i.e example.com/deep/link) for SPAs? I mean not to just redirect to startpage.

wpostma commented 8 years ago

Is this routing/middleware system only capable of redirecting within a single dotnet process, or can it redirect among multiple dotnet processes?

villanus commented 8 years ago

I saw you on the community standup. Can we support NGinx rewite formats too???

Tratcher commented 8 years ago

@villanus open a new bug for that please.

sankritayayana commented 8 years ago

any workaround or solution for issue. it's becoming a blocker for us.

natemcmaster commented 8 years ago

@sankritayayana We haven't released the feature yet. Prerelease versions are available on our nightly feeds. (See https://github.com/aspnet/Home/wiki/NuGet-feeds)