ASP.Net C# Routing; HttpHandler; and HttpModule not working - c#

I am having quite a few problems with custom extensions and intercepting existing handlers.
What am I trying to do
Based upon persisted options, I would like all 'virtual' extensions to be handled by set handlers. All pages are dynamically built, and no actual files exist on the site. The site populates the content, forms the html output and returns it as the web result.
This is required as I am setting up a fat/thin relationship between 2 servers. The thin server will simply pass on the request to the fat server - where the request is processed and response issued back down the line.
The project is for a dynamic multi-domain content management system. The thin server may not be .net compatible (hence the external request), but will be .net optimised (hence the need for handlers).
The Problem
What I want is to re-route existing extensions - aspx; php; html.
I have achieved this in my local environment using a custom HttpModule which sets the appropriate handler. I have explored setting the tag in config, but the the extensions are re-routed using dynamic rules that are persisted.
As mentioned, this solution works on localhost.
When uploaded, the .Net extensions are handled by the module correctly but any custom extensions or non-.net extensions return a 404 error.
Seeking an alternative, I have experimented with routing within Global, but this dis not work either.
I have also attempted to use to register the custom extensions... but each are met with the same result - 404 not found.
Global Routing attempt:
public class Global : System.Web.HttpApplication
{
void Application_Start(object sender, EventArgs e)
{
RegisterRoutes(RouteTable.Routes);
}
public static void RegisterRoutes(RouteCollection routes)
{
routes.Add(new Route("{action}.sqs", new SqlRequestHandler()));
}
.Config (for handler and module attempt)
<system.web>
<compilation debug="true" targetFramework="4.0" />
<httpRuntime requestValidationMode="2.0" />
<customErrors mode="Off"/>
<httpHandlers>
<add path="*.sqs" verb="*" type="CmsMapper.VirtualHandler, CmsMapper" />
<add path="*.sql" verb="*" type="CmsMapper.VirtualHandler, CmsMapper" />
</httpHandlers>
<httpModules>
<add name="SisBerCMS" type="CmsMapper.VirtualModule, CmsMapper" />
</httpModules>
</system.web>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true" />
<modules>
<add name="SisBerCMS" type="CmsMapper.VirtualModule, CmsMapper" />
</modules>
<handlers>
<add path="*.sqs" verb="*" type="CmsMapper.VirtualHandler, CmsMapper" name="sqsHandler" />
<add path="*.sql" verb="*" type="CmsMapper.VirtualHandler, CmsMapper" name="sqlHandler" />
</handlers>
</system.webServer>
Custom Module (CmsMapper.VirtualModule)
if (extentionMap != null)
{
// note that extentionMap.ExtentionType is a predetermined enum
switch (extentionMap.ExtentionType)
{
// If the extention is banned then pass back a generic message
case ExtentionType.Banned:
this.WriteTextResponce("Invalid extention detected:" + extentionMap.Extention);
break;
// Remap .Ajax requests to the ajax handler
case ExtentionType.Ajax:
this._app.Context.RemapHandler(new AjaxHandler());
break;
// Remap session query or sql requests to the sql handler
case ExtentionType.SessionQuery:
this._app.Context.RemapHandler(new SqlRequestHandler());
break;
// if the extention is not ignored, re map to the virtual page handler
default:
bool isManagementServer = this._app.Context.Request.Url.Authority != VirtualModule.RESPONSE_SERVER;
bool isPostRequest = !String.IsNullOrEmpty(this._app.Context.Request.Form[HtmlRequest.RequestOrigin]);
bool isGetRequest = !String.IsNullOrEmpty(this._app.Context.Request.QueryString[HtmlRequest.RequestOrigin]);
bool isIgnored = extentionMap.ExtentionType == ExtentionType.Ignore;
if ((isPostRequest || isGetRequest) && !isIgnored)
{
this._app.Context.RemapHandler(new VirtualHandler());
}
else
{
this._app.Context.RemapHandler(new ExternalRequestHandler());
}
break;
}
}
All the handlers are pretty standard implementing the following:
public class SqlRequestHandler : IHttpHandler, IRequiresSessionState, IRouteHandler
Again, the preferred method - HttpModule - works on my localhost machine. This could be a server config issue (in which case I'm looking for a work around), but the fact that the .net extensions are being handled is strange - as this would imply that issues with medium trust should not apply, however issues regarding extension handling on the server may take priority over the .net application.
The server is shared hosting (therefore I am unable to alter the machine.config files), is IIS6 using 4.0.
Thank you for any suggestions on how to resolve this issue.
Mike

You need to configure web site in IIS 6.0 to route all extensions (including extensionless paths known as wildcard extension mapping) to ASP.NET ISAPI dll (and disable the check for file exists).
You can of course do this mapping selectively only for those extensions that you want to route via ASP.NET code. But wildcard mapping will be more useful in case you don't have predefined set of extensions.
In the absence of such mappings, IIS will not forward requests for unknown extensions to ASP.NET (and routing code will not even come into picture) - rather IIS will pass the extension to default (static file) handler that will issue 404 if file is not present.
See this article that describes these steps (for ASP.NET MVC but the same applies to your case): http://haacked.com/archive/2008/11/26/asp.net-mvc-on-iis-6-walkthrough.aspx
Near the end of article, author has given how to add wildcard script map

Related

.Net Core 2.2 Web API 405

I am trying to setup a .net core 2.2 web api to use a post verb. Anything other than a get verb returns a 405 no matter if it is run on my local machine (w10 iis eXPRESS 10.0) or the windows server (2016 R2 IIS 8.0). I've read the other posts about disabling WebDav in your config file, adding a route, and completely removing the WebDav feature. I have done all of those to no avail. I'm just starting to develop in core and find this baffling, on the same server is a non-core web api that runs on .net framework 4.5 that processes GET,PUT,POST,DELETE without error. And yes, I have restarted the server after making changes to any of the configurations. The following are the web.config changes that I made, the last one coming directly from MS. Basic project that reproduces the same error on my machine and server is here https://github.com/FranciscanMedia/error405_core/tree/master it is just a standard web api project you get when you fire up VS2019.
<system.webServer>
<handlers accessPolicy="Read, Script">
<remove name="WebDAV" />
<remove name="ExtensionlessUrlHandler-ISAPI-4.0_64bit" />
<add name="ExtensionlessUrlHandler-ISAPI-4.0_64bit"
path="*."
verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS"
modules="IsapiModule"
scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll"
preCondition="classicMode,runtimeVersionv4.0,bitness64"
responseBufferLimit="0" />
</handlers>
</system.webServer>
<system.webServer>
<modules>
<remove name="WebDAVModule" />
</modules>
<handlers>
<remove name="WebDAV" />
</handlers>
</system.webServer>
<system.webServer>
<validation validateIntegratedModeConfiguration="false"/>
<modules runAllManagedModulesForAllRequests="false">
<remove name="WebDAVModule"/>
</modules>
</system.webServer>
<system.webServer>
<handlers accessPolicy="Read, Script">
<remove name="WebDAV" />
<remove name="ExtensionlessUrlHandler-ISAPI-4.0_64bit" />
<add name="ExtensionlessUrlHandler-ISAPI-4.0_64bit"
path="*."
verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS"
modules="IsapiModule"
scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll"
preCondition="classicMode,runtimeVersionv4.0,bitness64"
responseBufferLimit="0" />
</handlers>
</system.webServer>
Short answer
It could be as simple as that. The reason is routing.
Just send your POST request to right URL like https://localhost:44327/api/values/123.
Detailed explanation
It's not the issue. It works as expected.
You make a GET request to https://localhost:44327/api/values/. It returns 200 OK.
But when you make a POST request to the same URL https://localhost:44327/api/values/. It says 405 Method not allowed.
However, you get 405. It is happening because you are hitting the GET endpoint with POST method.
Microsoft Docs says:
... the HTTP client sent a valid JSON request to the URL for a Web API application on a web server, but the server returned an HTTP 405 error message which indicates that the PUT method was not allowed for the URL. In contrast, if the request URI did not match a route for the Web API application, the server would return an HTTP 404 Not Found error.
https://learn.microsoft.com/en-us/aspnet/web-api/overview/testing-and-debugging/troubleshooting-http-405-errors-after-publishing-web-api-applications
If you simply remove the GET endpoint. The POST request will start returning 404 Not found. Which means that you are not hitting any registered route.
To send POST request you need to use different URL according to the routing rules.
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
// POST api/values
[HttpPost("{val}")]
public StatusCodeResult Post()
{
return Ok();
}
}
This attribute-based configuration means that route of your POST endpoint is /api/Values/{val}. Where {val} is any value. It's not processed in the endpoint.
If you want to process it, you should pass it to the method:
[HttpPost("{val}")]
public StatusCodeResult Post(string val)
{
return Ok();
}
I think that in your controller you have to import another library.
Try
using System.Web.Http;
Instead of
using Microsoft.AspNetCore.Mvc
Looking at what you have defined:
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
Then for the action:
[HttpPost("{val}")]
public StatusCodeResult Post()
{
return Ok();
}
Your routing matches the following url:
https://localhost:44327/api/values/StatusCodeResult
It is going to take your main route defined on your controller [Route("api/[controller]")]
Then you are defining the "template" to use "{val}"
This is telling it to use the ActionResult specific name and to expect var val to be passed/appened.
Checking out the official documentation here: https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/routing?view=aspnetcore-2.2
under section "Token replacement in route templates ([controller], [action], [area])"
They specifiy:
For convenience, attribute routes support token replacement by enclosing a token in square-braces ([, ]). The tokens [action], [area], and [controller] are replaced with the values of the action name, area name, and controller name from the action where the route is defined. In the following example, the actions match URL paths as described in the comments:
[Route("[controller]/[action]")]
public class ProductsController : Controller
{
[HttpGet] // Matches '/Products/List'
public IActionResult List() {
// ...
}
[HttpGet("{id}")] // Matches '/Products/Edit/{id}'
public IActionResult Edit(int id) {
// ...
}
}
If you want it to just route based on just verbs (follow a pattern where each api endpoint just handles operations for that specific object) then you would change your post method to just
[HttpPost]
public ActionResult Post(string val)
{
return Ok();
}
I totally agree with #Vladimir's answer. I dont have enough points to add comments to the answer by #vlaimir so i am adding my thoughts and suggestions.
The code you have on your github,
// POST api/values
[HttpPost("{val}")]
public StatusCodeResult Post()
{
return Ok();
}
This is a post and it would expect a value {val} per the route action configuration. Since you may try to hit the post without any value, thats not permitted. Ensure you supply some value and then do the POST. If you are using POSTMAN, you may have to supply the BODY of your request with some value. Swagger is a great util tool to embed into the web api's and that comes with excellent intuitive UI for our routes/resources. That might be even ideal to help determine and ensure you supply the right value.
Otherwise, you dont need to modify or worry about your IIS or IIS Express settings. or webdav.

Preventing "A potentially dangerous Request.Path" in web api

I have this controller.
public string Status([FromBody]StatusRequest p)
{
string ps= HttpContext.Current.Request["params"];
return ps;
}
It receives this post parameter value (The value is xml. Beneath is just part of it):
params=<Transaction hash=9
I get this infamous error:
A potentially dangerous Request.Form value was detected from the client
I tried a few solutions.
I tried to bind the post parameter. But there is no luck, it wont bind it so the value of 'p' is always null.
I tried setting web.config in the directory where my controller is:
<?xml version="1.0"?>
<configuration>
<system.web>
<compilation debug="true" targetFramework="4.0" />
<httpRuntime targetFramework="4.5" requestPathInvalidCharacters="?" />
<pages validateRequest="false" />
</system.web>
</configuration>
Those configurations have no effect on the files inside the directory.
Does anyone knows how to solve this?
This is really nasty exception because it reveals Server header even if you hide it so big bad guy can use that info against you.
I've found two solutions which help me. Let me explain both by using asterisk as example of dangerous symbol (but you can handle any single symbol or set of symbols in this way)
1st way is really ugly and I can't recommend it to anyone. But here it is:
Add to Global.asax.cs code
protected void Application_Error(object sender, EventArgs e)
{
if(Context.Request.RawUrl.Contains("*"))
{
Server.ClearError();
}
}
protected void Application_BeginRequest(object sender, EventArgs e)
{
if(!Context.Request.RawUrl.Contains("*"))
{
return;
}
var newPath = Context.Request.RawUrl.Replace("*", "");
base.Context.RewritePath(newPath);
}
That's it. For any url with asterisk you'll omit this annoying exception and just replace dangerous symbol with anything you want.
2nd way is slightly trickier, but as for me, much better. Just keep in mind, that you can't use it if you don't have possibilities to install URL Rewrite module for IIS. Check next article for the details. Article is a little bit dated, if you use IIS 10 as I do, you need to get URL Rewrite module here.
So first of all you have to install this module. After that add this section to your web config file in system.webServer section:
<rewrite>
<rules>
<rule name="Rewrite URL to remove asterisk from path.">
<match url="^(.*)\*(.*)$" />
<conditions logicalGrouping="MatchAny" />
<action type="Rewrite"
url="{R:1}{R:2}" />
</rule>
</rules>
</rewrite>
That's all. Now almost any malformed url with asterisk will work without this annoying error.
Why almost? Because you'll still get exception if dangerous symbol presents in the name of, for example, IIS virtual directory.
So both ways handle errors like http://localhost/WebApplication1/api*/Values
And both ways fails with url like this http://localhost/WebApplication1*/api/Values
Just remove asterisk from requestPathInvalidCharacters under Web.config
...
<system.web>
<httpRuntime requestPathInvalidCharacters="<,>,*,%,&,:,\,?" />
...

OData Function with DateTimeOffset? parameter

I'm trying to implement an OData collection function that receives two DateTimeOffset? parameters (MinSentOn and MaxSentOn) and will return some summary information from an Orders table, but I'm having routing problems when I pass the time part of the DateTimeOffset, receiving an HTTP Error 500.0 - Internal Server Error directly from IIS, because it seems it's trying to reach a file and not the controller itself.
This is my current OData configuration:
odataBuilder.Namespace = "D";
var fc =
odataBuilder.EntityType<Order>().Collection
.Function("ToExecutiveSummary")
.Returns<ExecutiveSummary>();
fc.Parameter<DateTimeOffset?>("MinSentOn");
fc.Parameter<DateTimeOffset?>("MaxSentOn");
This is the function in my controller:
[HttpGet]
public async Task<IHttpActionResult> ToExecutiveSummary(DateTimeOffset? minSentOn, DateTimeOffset? maxSentOn, CancellationToken ct)
{
return await _uow.ExecuteAndCommitAsync(async () =>
{
var query = _uow.Orders.Query();
if (minSentOn != null) query = query.Where(e => e.SentOn >= minSentOn.Value);
if (maxSentOn != null) query = query.Where(e => e.SentOn <= maxSentOn.Value);
// TODO needs optimization, test only
var executiveSummary =
query.Select(e =>
new ExecutiveSummary
{
TotalOrders = query.Count(),
TotalProducts = query.Sum(ex => ex.Quantity),
TotalPharmacies = query.GroupBy(ex => ex.Pharmacy.Id, ex => ex.Pharmacy.Id).Count()
}).FirstOrDefault();
return Ok(executiveSummary);
}, ct);
}
Snippet of the web.config changes to support the OData path and solve some routing problems I faced until I hit this wall, like dots or double escapes (changes have comments):
<configuration>
<!-- ... -->
<system.web>
<compilation debug="true" targetFramework="4.5.2" />
<!-- Removed : and % from the path filter -->
<httpRuntime targetFramework="4.5.2" requestPathInvalidCharacters="<,>,*,&,\,?"/>
<globalization uiCulture="pt-PT" culture="pt-PT" />
</system.web>
<!-- ... -->
<system.webServer>
<handlers>
<remove name="ExtensionlessUrlHandler-Integrated-4.0" />
<remove name="OPTIONSVerbHandler" />
<remove name="TRACEVerbHandler" />
<!-- to support the dot (.) for functions or actions -->
<add name="ExtensionlessUrlHandler-Integrated-4.0" path="/*" verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
</handlers>
<security>
<!-- to support double escapings, like 2015-08-10%2000:00:00.0000%2B01:00 -->
<requestFiltering allowDoubleEscaping="true"/>
</security>
</system.webServer>
<!-- ... -->
</configuration>
Now, during my testings, I'm facing the following:
If I don't pass the time part (example: http://localhost:58806/odata/Order/D.ToExecutiveSummary(MinSentOn=null,MaxSentOn=2015-08-10) ) the request reaches my code without any problem, making me believe that there aren't any problem with the OData configuration and route.
But when I include the time part (example: http://localhost:58806/odata/Order/D.ToExecutiveSummary(MinSentOn=null,MaxSentOn=2015-08-10%2000:00:00.0000%2B01:00) ) I receive an Internal Server Error (image attached) directly from the IIS. It seems it is trying to resolve to a file instead to the controller, whence the problem.
Ultimately, I know I could receive the parameters as strings and make the parse myself, but I would like to implement this without using the "hammer" :)
I do think you can use the parameter alias for DateTimeOffset parameter value.
For example:
http://localhost:58806/odata/Order/D.ToExecutiveSummary(MinSentOn=null,MaxSentOn=#p)?#p=2015-08-10%2000:00:00.0000%2B01:00
In the following Uri, you can find many parameter alias examples:
https://github.com/OData/WebApi/blob/master/OData/test/UnitTest/System.Web.OData.Test/OData/Formatter/ODataFunctionTests.cs#L24-L43
Moreover, in http://odata.github.io/WebApi/#04-06-function-parameter-support, you can find a simple guidance about function parameter.
However, the issue is a known issue related to IIS and be tracked on odata Web APi#github
Thanks.
I finally found the problem! It was related to the DateTimeOffset format I was using. I was forgetting the T, and writing 2015-08-10 00:00:00.0000%2B01:00 instead of 2015-08-10T00:00:00.0000%2B01:00 and it couldn't parse correctly.
What confused me was IIS throwing an Internal Server Error instead of some Bad Request or Not Found, and because the application exception handler wasn't being invoked, so I assumed the format was ok but IIS was having some problem with the path having unusual characters.
#Sam Xu suggestion lead me in the right path, because even using alias (I didn't know OData had support for parameter alias - thanks for that!) it was still throwing the exception...
In the end, it was a failure of mine, even if the server response should have been more enlightening...
You need to cast it to a DateTimeOffset like this:
cast(2015-08-10%2000:00:00.0000%2B01:00,Edm.DateTimeOffset)
Source: https://github.com/OData/WebApi/blob/master/OData/test/E2ETest/WebStack.QA.Test.OData/DateAndTimeOfDay/DateAndTimeOfDayTest.cs#L181

Simplest home page with ASP.NET and AnjularJS

I want to build a SPA with AngularJS and ASP.NET Web API.
In regards to the frontend webpage I would like to limit the implication of asp.net as much as possible and move all the logic into Angular, the only thing the Web API will supply is a REST service.
I have created an index.html page with some angular that loads a basic list from the server.
But my index.html is accessed using ex. http://localhost:1234/app/index.html , what I would like now is to see my index.html from http://localhost:1234/ and also get a custom error page if I access some random link from this host.
Do I require ASP.NET to do this ? I would like to limit the use of ASP.NET as much as possible, only the basic required stuff.
And I am complete new to this.
Web.config :
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.webServer>
<handlers>
<clear />
<add
name="StaticFile"
path="*" verb="*"
modules="StaticFileModule,DefaultDocumentModule,DirectoryListingModule"
resourceType="Either"
requireAccess="Read" />
</handlers>
<staticContent>
<mimeMap fileExtension=".*" mimeType="application/octet-stream" />
</staticContent>
<defaultDocument enabled="true">
<files>
<clear/>
<add value="index.html"/>
</files>
</defaultDocument>
<modules runAllManagedModulesForAllRequests="true"/>
</system.webServer>
</configuration>
Also added routing:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
Page error:
Server Error in '/' Application.
The resource cannot be found.
Description: HTTP 404. The resource you are looking for (or one of its dependencies) could have been removed, had its name changed, or is temporarily unavailable. Please review the following URL and make sure that it is spelled correctly.
Requested URL: /
Well angular only does routing on client side, for your url needs you will still need to do some config on server side.
this should do the trick:
add this to web.config:
<system.webServer>
<defaultDocument>
<files>
<clear/>
<add value="(location of index.html)"/>
</files>
</defaultDocument>
As for errors (404 is among them), answer can be found here. That is only for when someone tries to get an url that does not exist, before angular has been loaded.
But once angular is loaded up, you do everything withing config, where you configure $routeProvider:
$routeProvider.when('/someUrl', {templateUrl: 'url/templatefile.html', controller: 'templateController'})
.when('/my404route', {templateUrl: 'url/my404file.html', controller: 'my404Controller'})
//when returns $routeprovider so you can chain "whens"
//finnaly add "otherwise"
.otherwise({redirectTo: '/my404route'});
//or just route to root with '/' if you won't handle unknown routes
By default visual studio deploy the site in a subfolder. To change it right click on your web project file / properties. Then in the Web tab specify the project url to be http://localhost:1234.

HttpHandler receiving 404 and 500 errors

Im running a website on WindowsServer 2008 R2, and IIS 6.1 sp1
I created an empty ASP.NET 4.0 web application, and added an http handler to it. The job of the handler will be to redirect incoming calls to other sites based on a tokenid passed in the querystring (from a federated single sign on provider).
To test the connection, I am just parsing the token into a Dictionary and writing the information to the context.Response.
The handler works on my machine, but when I deploy it, I get 500 and 403 errors.
I get 500 errors when I make the application pool ASP.NET 4.0 Integrated, I get 404 errors when make the application pool ASP.NET 4.0 Classic
If I add a test.htm to the directory, I can access the test.htm and see its contents, but I would be expecting to see the output from the handler, so it makes me think it cant find the handler.
Here is the web.config followed by the code
<configuration>
<system.web>
<compilation debug="true" targetFramework="4.0" />
<httpHandlers>
<add verb="*" path="*"
type="RedirectSite.RedirectHttpHandler, RedirectSite, Version=1.0.0.0, Culture=neutral" />
</httpHandlers>
</system.web>
<configuration>
public class RedirectHttpHandler : IHttpHandler
{
public RedirectHttpHandler()
{ }
public void ProcessRequest(HttpContext context)
{
// check for tokenid in querystring
string tokenid = context.Request.Params["tokenid"];
string agentid = context.Request.Params["agentid"];
Dictionary<string,string> tokenItems = TokenParser.Parse(tokenid, agentid);
context.Response.Clear();
context.Response.Write("<b>Token Information</b><br/><br/>");
foreach (KeyValuePair<string, string> item in tokenItems)
{
context.Response.Write(String.Format("{0} : {1}<br/>",item.Key,item.Value));
}
}
public bool IsReusable { get; private set; }
}
Turns out that the ISAPI/CGI settings for the site were preventing 4.0 - once enabled, the site worked

Categories