I have a custom IHttpModule that I would like to only work on a specific route.
For example : http://example.com/HandleAzureTask
I want this module to only be invoked/handled on the /HandleAzureTask route.
Since this is not a controller, I can't really set the [Authorize] attribute on it; how can I force it only to be invoked/handled if user is authenticated?
I am on ASP.NET MVC 4 and currently have my module added to web.config as such:
<modules>
<remove name="AzureWebDAVModule" />
<add name="AzureWebDAVModule" type="VMC.WebDAV.Azure.Module.AzureWebDAVModule, VMC.WebDAV.Azure.Module" />
</modules>
HttpModules are called on every request (HttpHandlers, instead, can be filtered). If you just want to perform your task only on the selected route, you can do the following:
Set up a route like this:
routes.MapRoute(
name: "AzureWebDAVRoute",
url: "HandleAzureTask",
// notice the enableHandler parameter
defaults: new { controller = "YourController", action = "YourAction", enableHandler = true }
);
On your module:
public class AzureWebDAVModule : IHttpModule
{
public void Init(HttpApplication context)
{
// you can't directly access Request object here, it will throw an exception
context.PostAcquireRequestState += new EventHandler(context_PostAcquireRequestState);
}
void context_PostAcquireRequestState(object sender, EventArgs e)
{
HttpApplication context = (HttpApplication)sender;
RouteData routeData = context.Request.RequestContext.RouteData;
if (routeData != null && routeData.Values["enableHandler"] != null)
{
// do your stuff
}
}
public void Dispose()
{
//
}
}
Now your task will be performed on the selected route only. Please note that you need the parameter since you can't find the current route by name.
Why not just create a ordinary folder namned /HandleAzureTask and put a seperate web.config inside that folder with the module registration.
Then the module will run for all request in that folder.
To get the authorization to work you can also set the authorization element in the web.config to disallow *
Related
I want to always redirect to my home page if someone puts something like www.example/bla/qwerty.hm (it doesnt have controller and it doesnt exist) to www.example.com.
I want to redirect all urls, which have not controller, to my home page and not show error. For example: www.example.com/auto (auto is not controller) and it will be redirect to my home page. How do I do that?
I tried Route configuration
routes.MapRoute(
name: "MyRedirect"
, url: "{contextRedirect}/{*tasks}"
, defaults: new { controller = "Redirect", action = "TotalRedirect", contextRedirect = "" }
);
...
public ActionResult TotalRedirect(string contextRedirect)
{
return RedirectToAction("Index", "Home");
}
but this is called every time and it makes an infinity loop (it is called every time when redirectToAction is called)
The problem disappears if I write all RouteMaps for all existing controllers before this MapRoute, but I have lot of controllers, and I want avoid writing RouteMaps for all controllers.
Instead of MapRoute I tried Web.config and change errors
<customErrors mode="On">
<error redirect="/Error" statusCode="404" />
<error redirect="/Error" statusCode="500" />
</customErrors>
Error is controller which return RedirectToAction and I got same result as point 1 (infinity loop). But when I change /Error to /Home it is working (because Home return View), but on Url path is saved error text Home?aspxerrorpath=/auto. After redirect I do not want show text, that means if page will be redirected in to www.example.com, it not show www.example/Home?aspxerrorpath=/auto.
I am new to Asp.net MVC, and I don't know proper way how to do it.
Update
After some research I think there are two ways how to do it.
(Thanks to KevinLamb) Redirect from error with Application_Error and Web.Confing httpError. This is working for me:
This settings is put in Web.Confing on Project level, that means first Web.Config you see in Project Explorer (there is second Web.Config on View folder). Then you must create Controller named Error with ActionResult ErrorHandle
<system.webServer>
<httpErrors errorMode="Custom" existingResponse="Replace">
<remove statusCode="404"/>
<error statusCode="404" responseMode="ExecuteURL" path="/Error/ErrorHandle"/>
<remove statusCode="400"/>
<error statusCode="400" responseMode="ExecuteURL" path="/Error/ErrorHandle"/>
<remove statusCode="500"/>
<error statusCode="500" responseMode="ExecuteURL" path="/Error/ErrorHandle"/>
</httpErrors>
...
// Error Controller .cs
namespace MyWebApp.Controllers
{
public class ErrorController : Controller
{
// GET: Error
public ActionResult Index()
{
return RedirectToAction("Home", "Index");
}
public ActionResult ErrorHandle()
{
return RedirectToAction("Index", "Home");
}
}
}
...
// code inside Global.asax.cs MvcApplication class
protected void Application_Error(object sender, EventArgs e)
{
Exception ex = Server.GetLastError();
//Add some logging here
if(ex.GetType().IsAssignableFrom(typeof(HttpException)))
{
//Possibly log that you're redirecting the user
Response.Clear();
Response.Redirect("~/");
}
}
This is easy part.
Another way I discover is create HttpHandler or HttpModule. I am new in MVC and Asp.Net world and HttpHandler is not working allways for me, because it works only once, then only when application change page, but it not detect Url created by user (only first time). HttpModule work allways for me, but I don't know if it is good or bad. It is little harder then 1. point but you don't need Application_Error and httpErrors in Web.Config.
If you have httpErrors and Application_Error, delete it and create Module (Right click on Project > Add new Item > In search put "module" > and select Asp.Net Module. It create module class with Init and Dispose methods. Then create your own method and register it to BeginRequest.
And here is code for my HttpModule
using System;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using System.Reflection;
using System.Linq;
namespace MyWebApp
{
public class ErrorHttpModule : IHttpModule
{
#region IHttpModule Members
public void Dispose()
{
//clean-up code here.
}
public void Init(HttpApplication context)
{
// Below is an example of how you can handle LogRequest event and provide
// custom logging implementation for it
// context.LogRequest += new EventHandler(OnLogRequest);
context.BeginRequest += new EventHandler(BR); // register your own method in to Event where you check Url
}
#endregion
private HttpContext context = null;
public void BR(Object source, EventArgs e)
{
context = System.Web.HttpContext.Current;
// collect all controllers in web application
Assembly asm = Assembly.GetAssembly(typeof(MyWebApp.MvcApplication)); // need using System.Reflection;
var controlleractionlist = asm.GetTypes()
.Where(type => typeof(System.Web.Mvc.Controller).IsAssignableFrom(type)) // need using System.Linq;
.SelectMany(type => type.GetMethods(BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public))
.Where(m => !m.GetCustomAttributes(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute), true).Any())
.Select(x => new { Controller = x.DeclaringType.Name, Action = x.Name, ReturnType = x.ReturnType.Name, Attributes = String.Join(",", x.GetCustomAttributes().Select(a => a.GetType().Name.Replace("Attribute", ""))) })
.OrderBy(x => x.Controller).ThenBy(x => x.Action).ToList();
// Get Url
string page = "";
if (context != null)
{
page = context.Request.Url.PathAndQuery;
}
string newUrl;
if (!String.IsNullOrEmpty(page))
{
bool continute = true;
// does url contain controller or action?
foreach (var controller in controlleractionlist)
{
string cpath = "/" + controller.Controller.Replace("Controller", "") + (controller.Action == "Index" ? "" : "/" + controller.Action);
if (cpath == page)
{
// Yes, don't continue to redirect
continute = false;
break;
}
else if (page == ("/" + controller.Action))
{
// Yes, don't continue to redirect
continute = false;
break;
}
}
// does page load your content, script etc.. ?
if (page.Contains("Content/") == true
|| page.Contains("Scripts/") == true
|| page.Contains(".ico") == true
|| page == "/"
)
{
// Yes, don't redirect.
continute = false;
}
if (continute)
{
// anything else will redirect to Home page
var urlHelper = new UrlHelper(context.Request.RequestContext); // nned using System.Web.Mvc;
newUrl = urlHelper.Action("About", "Home");
context.Response.Status = "301 Moved Permanently";
context.Response.AddHeader("Location", newUrl);
context.Response.End();
}
}
}
public void OnLogRequest(Object source, EventArgs e)
{
//custom logging logic can go here
}
}
}
And finnaly add module in to Web.Config (on Project level (ProjectName > Web.Config), not inside folder under Project (ProjectName > View > Web.Config))
<system.webServer>
<modules>
<add name="MyHttpErrorModule" type="MyWebApp.ErrorHttpModule, MyWebApp"/>
</modules>
</system.webServer>
Extended question
Either first or second point, I have problem with both, when in url is put characters like that /abc=45%$#r. It leads to Bad Request - Invalid URL HTTP Error 400. The request URL is invalid. and this don't detect Application_Error, httpErrors in Web.Config or my HttpModule with BeginRequest. Therefore I think this is on IIS settings but what I need to set up? It looks like those characters make IIS mess %$.
First you'll need to set errors to pass through in IIS so that your application can handle the file/parameter links instead of IIS. Add the below to your Web.config:
<system.webServer>
<httpErrors existingResponse="PassThrough" />
</system.webServer>
If you want to redirect to your home page (without any extra parameters in the URL) when you get an issue with a user going to the wrong controller/action, add the following code to your Global.asax.cs.
protected void Application_Error(object sender, EventArgs e)
{
Exception ex = Server.GetLastError();
//Add some logging here
if(ex.GetType().IsAssignableFrom(typeof(HttpException)))
{
//Possibly log that you're redirecting the user
Response.Clear();
Response.Redirect("~/");
}
}
Basically, what this is is doing is making all errors in your ASP.NET MVC website go through this method. Then it checks to see if the exception is a HttpException. If it is, it will redirect to your home controller and action. If it is not a HttpException it will continue doing the error handling process. This way you can keep handling the important errors properly.
Instead of getting a HTTP 404 response, I'd like to have a generic 404 Not Found page (HTTP 200). I know you can set that up in MVC 5 with
<customErrors mode="On">
<error statusCode="404" redirect="~/Error/NotFound" />
</customErrors>
But I can't seem to figure out how to do this in MVC 6. I'm guessing it should be in either the UseMvc routing table, or a custom middleware.
Startup.cs:
public class Startup
{
public void Configure(IApplicationBuilder app)
{
// PICK YOUR FLAVOR.
// app.UseErrorPage(ErrorPageOptions.ShowAll);
app.UseStatusCodePages(); // There is a default response but any of the following can be used to change the behavior.
// app.UseStatusCodePages(context => context.HttpContext.Response.SendAsync("Handler, status code: " + context.HttpContext.Response.StatusCode, "text/plain"));
// app.UseStatusCodePages("text/plain", "Response, status code: {0}");
// app.UseStatusCodePagesWithRedirects("~/errors/{0}"); // PathBase relative
// app.UseStatusCodePagesWithRedirects("/base/errors/{0}"); // Absolute
// app.UseStatusCodePages(builder => builder.UseWelcomePage());
// app.UseStatusCodePagesWithReExecute("/errors/{0}");
}
}
project.json:
"dependencies": {
"Microsoft.AspNet.Diagnostics": "1.0.0-*",
// ....
}
note that, if you are hosting on IIS (or azure), the web.config does still works and it's big maybe needed in some hosting scenarios. (it should be placed inside wwwroot folder
You can handle 404 errors with the UseStatusCodePagesWithReExecute
but you need to set the status code at the end of your configure pipeline first.
public void Configure(IApplicationBuilder app)
{
app.UseIISPlatformHandler();
if (string.Equals(_env.EnvironmentName, "Development", StringComparison.OrdinalIgnoreCase))
{
app.UseDeveloperExceptionPage();
app.UseRuntimeInfoPage();
} else
{
app.UseExceptionHandler("/Error");
}
app.UseStatusCodePagesWithReExecute("/Error/Status/{0}");
app.UseStaticFiles();
app.UseMvc();
app.Use((context, next) =>
{
context.Response.StatusCode = 404;
return next();
});
}
This will set the status code to 404 if nothing in the pipeline can handle it (which is what you want).
Then you can easily capture and handle the response any way you want in your controller.
[Route("error")]
public class ErrorController : Controller
{
[Route("Status/{statusCode}")]
public IActionResult StatusCode(int statusCode)
{
//logic to generate status code response
return View("Status", model);
}
}
If anyone is looking to create custom 404 pages with HTML, you can do this:
Startup.cs
public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.UseStatusCodePagesWithReExecute("/errors/{0}");
}
}
project.json
"dependencies": {
"Microsoft.AspNet.Diagnostics": "1.0.0-*",
// ....
}
ErrorsController.cs
public class ErrorsController : Controller
{
// ....
[Route("/Error/{statusCode}")]
public IActionResult ErrorRoute(string statusCode)
{
if (statusCode == "500" | statusCode == "404")
{
return View(statusCode);
}
return View("~/Views/Errors/Default.cshtml");
}
}
All that's left after this is to create a 404.cshtml, 500.cshtml, and Default.cshtml in your Errors view folder and customize the HTML to your liking.
In the Configure function of the Startup class, call:
app.UseErrorPage(ErrorPageOptions.ShowAll);
more production ready:
if (string.Equals(env.EnvironmentName, "Development", StringComparison.OrdinalIgnoreCase))
{
app.UseBrowserLink();
app.UseErrorPage(ErrorPageOptions.ShowAll);
app.UseDatabaseErrorPage(DatabaseErrorPageOptions.ShowAll);
}
else
{
// Add Error handling middleware which catches all application specific errors and
// send the request to the following path or controller action.
app.UseErrorHandler("/Home/Error");
}
There is a middleware called StatusCodePagesMiddleware which I believe was added post-Beta3.
Sample: https://github.com/aspnet/Diagnostics/blob/dev/samples/StatusCodePagesSample/Startup.cs
If you have a standard ASP.NET 5 Web application template project you can get a really nice solution that will handle both keeping the error status code at the same time as you serve the cshtml file of your choice.
Startup.cs, in the Configure method
Replace
app.UseExceptionHandler("/Error");
with
app.UseStatusCodePagesWithReExecute("/Home/Errors/{0}");
Then remove the following catchall:
app.Run(async (context) =>
{
var logger = loggerFactory.CreateLogger("Catchall Endpoint");
logger.LogInformation("No endpoint found for request {path}", context.Request.Path);
await context.Response.WriteAsync("No endpoint found - try /api/todo.");
});
In HomeController.cs
Replace the current Error method with the following one:
public IActionResult Errors(string id)
{
if (id == "500" | id == "404")
{
return View($"~/Views/Home/Error/{id}.cshtml");
}
return View("~/Views/Shared/Error.cshtml");
}
That way you can add error files you feel the users can benefit from (while search engines and such still get the right status codes)
Now add a folder named Error in ~Views/Home folder
Create two .cshtml files. Name the first one 404.cshtml and the second one 500.cshtml.
Give them some useful content and the start the application and write something like http://localhost:44306/idonthaveanendpointhere and confirm the setup works.
Also press F12 developer tools and confirm the status code is 404.
I have a website that is in a pre-integration phase. In other words, I have ensured that the site runs fine on my local Development Server (VS2012) utilizing the dynamically generated ASP.NET Development Server that runs at the time of debug executions; and I have now created a sub-domain of my domain on the Web Host Server and deployed my site there.
My decision to do this was because I obviously don't want users accessing the site until it has undergone thorough testing on the actual Host. My problem is though most of the site functions without issue (including URL's), there are a few links that produce the HTTP 404 error.
"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."
It then references the View/Controller path in the context of the Error that I know exists.
Why it only happens for certain Uri requests and not others is somewhat puzzling to me.
So I am strongly suspecting that it has something to do with the Default Routing Configuration for MVC and I believe if I were to move the site to the main domain, the issue will likely resolve but then again, it would defeat the purpose of setting it up in the SubDomain for testing before public access.
I need some viable options here but don't no where to start.
Should I address the issue from the perspective of the Routing Configuration and create 2 separate Global.asax.cs files? One for the domain and the other for the subdomain testing? And if so, How should I modify the file to accomodate for the Subdomain.
Or is there a more elegant solution for approaching the Integration process?
----------------- UPDATE ---------------------
So I've been troubleshooting the problem and it appears as though the 404 Error is only being generated for a method in my Controller that is returning a string.
I have a function that is being called in my View that looks like this:
<script>
function Subscribe(slvl) {
$.ajax({
type: 'POST',
datatype: "text",
data: { level: slvl },
url: '#Url.Action("Upgrade", "Profile")',
success: function (result) {
if (result) {
window.location = result;
}
},
error: function onError(result) {
alert(result.responseText);
}
});
}
</script>
I cannot post the full details of the Controller but it simply returns a string and whether I were to post the entire method or a simple one, the results would be the same. So for illustration only it looks something like this.
[HttpPost]
public string Upgrade(string level)
{
var uri = "http://www.someUri.com?Upgrade=" + level;
return uri;
}
This code is producing a HTTP 404 Error complaining that the path Profile/Upgrade cannot be found.
But I've found that if I use reference to a different method being called in the same Controller with the only exception being that it returns an ActionResult to a different View, the Error goes away and I'm redirected to the alternate view.
Any Ideas? So maybe it has nothing to do with the Subdomain???
First of all you need to create your own Custom Route
public class SubDomainRoute : Route
{
private readonly string[] namespaces;
public SubDomainRoute(string url, object defaults, string[] namespaces)
: base(url, new RouteValueDictionary(defaults), new MvcRouteHandler())
{
this.namespaces = namespaces;
}
public override RouteData GetRouteData(HttpContextBase httpContext)
{
if (the subdomain is the expected one, then update your route data)
{
var routeData = base.GetRouteData(httpContext);
if (this.namespaces != null && this.namespaces.Length > 0)
{
routeData.DataTokens["Namespaces"] = this.namespaces;
}
routeData.DataTokens["Area"] = "Your Subdomain Area";
routeData.DataTokens["UseNamespaceFallback"] = bool.FalseString;
return routeData;
}
return null;
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
if (is your expected subdomain)
{
return base.GetVirtualPath(requestContext, values);
}
return null;
}
}
Then you have to use the custom route in the area registration like this:
public override void RegisterArea(AreaRegistrationContext context)
{
context.Routes.Add("YourAreaName_default", new CustomSubDomainRoute(
"{controller}/{action}/{id}",
new { area = "Your Area", controller = "Home", action = "Index", id = UrlParameter.Optional },
new[] { typeof(Controllers.HomeController).Namespace } // Namespaces defaults
));
}
And finally, my advise is to use redirect to route, because the routeData Area is lost between the different requests.
Use your global property route name when redirecting.
[Authorize]
public class YourController : Controller
{
protected virtual string RouteName
{
get
{
return "YourRouteName";
}
}
public ActionResult Test(params here)
{
return RedirectToRoute(this.RouteName, new { action = "your action" });
}
}
Ref: http://forums.asp.net/t/1967197.aspx?Subdomain+in+mvc4+can+not+redirect+to+controller+in+area+
I have a MVC4 application with Windows Authentication. User can type url of any of the 10 views to load the application. There is no specific home page
I need to redirect to a session timeout view if the user was idle for more than a minute. I have kept the session timeout value in config file as one minute. And I have created an action filter to check one particular session value. This particular session value is set in the Session_Start of the Global.asax.
But, when the timeout period is over, the request again hits the Session_Start and it is assigning the value. Hence my action filter is not redirecting to the error view.
What are the possible solutions to overcome this?
Web.Config
<system.web>
<!--Impersonate-->
<identity impersonate="true"/>
<!--Session Mode and Timeout-->
<sessionState mode="InProc" timeout="1" />
<authentication mode="Windows">
</authentication>
<authorization>
<allow users="?" />
</authorization>
</system.web>
Action Filter
[AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class SessionCheckAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(System.Web.Mvc.ActionExecutingContext filterContext)
{
string controllerName = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName.ToLower();
HttpSessionStateBase session = filterContext.HttpContext.Session;
var activeSession = session["IsActiveSession"];
if (activeSession == null)
{
//Redirect
var url = new UrlHelper(filterContext.RequestContext);
var loginUrl = url.Content("~/Error/SessionTimeout");
filterContext.HttpContext.Response.Redirect(loginUrl, true);
}
}
}
Global.ASAX
protected void Session_Start(object sender, EventArgs e)
{
Session["IsActiveSession"] = DateTime.Now;
}
Instead of setting a session value and checking it in your action filter, simply check HttpContext.Current.Session.IsNewSession to see if a new session was created for the current request. Modifying your action filter, you would end up with something like this:
[AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class SessionCheckAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(System.Web.Mvc.ActionExecutingContext filterContext)
{
string controllerName = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName.ToLower();
HttpSessionStateBase session = filterContext.HttpContext.Session;
if (session.IsNewSession)
{
//Redirect
var url = new UrlHelper(filterContext.RequestContext);
var loginUrl = url.Content("~/Error/SessionTimeout");
filterContext.HttpContext.Response.Redirect(loginUrl, true);
}
}
}
if you want to get fancy and make sure they had a previous session before the new session that was created for this request, you can update the if statement to check if an old session cookie was sent with the request:
string cookieHeader = filterContext.HttpContext.Request.Headers["Cookie"];
if (session.IsNewSession && cookieHeader != null && cookieHeader.IndexOf("ASP.NET_SessionId") >= 0)
{
...
}
But since it looks like you are sending them to a login page, this is probably not something you have to worry about here. If you do use this check, note that this code assumes the default "ASP.NET_SessionId" cookie name; this could be changed in your web.config, in which case you would need to either update the IndexOf parameter with the new cookie name or get the cookie name programmatically.
I am new to asp.net mvc and now struggling with url routing. I'm using asp.net mvc 3 RC2.
How can I create a url routing that IGNORES the very end extension in url. the extension can be: .html, .aspx, .php, .anything.
For example, these urls:
/Home.html
/Home.en
/Home.fr
/Home
should go to Home controller?
one more example:
/Home/About.html
/Home/About.en
/Home/About.fr
/Home/About
should go to Home controller and About action.
thank you :)
I'm not sure if you're using IIS7, but if so, then I would recommend a rewrite rule which checks for urls ending in .xyz and then doing a rewrites for them without the .xyz.
Something like this:
<rewrite>
<rules>
<rule name="HtmlRewrite">
<match url="(.*)(\.\w+)$" />
<action type="Rewrite" url="{R:1}" />
</rule>
</rules>
</rewrite>
This will handle the use cases you suggested. Anything that ends with an extension and some characters will be rewritten to a url without the extension. The benefit of this is that you will only need one route because everything goes into your application without one.
You just need to tweak the default route in Global.asax.cs, try this:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}.{extension}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional });
The {extension} value from the url will be included in the route data, but you can just safely ignore it if you don't need it
Either create your own route class, or use this regex route implementation: http://blog.sb2.fr/post/2009/01/03/Regular-Expression-MapRoute-With-ASPNET-MVC.aspx
You could handle this in IIS instead of ASP.NET MVC using IIS Url rewriting. See for example: http://learn.iis.net/page.aspx/496/iis-url-rewriting-and-aspnet-routing/
I started to work on this question as a weekend assignment :D
below code will work as requested in question. please refer below references
1] MyUrlRoute Class : RouteBase
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
namespace MvcIgnoreUrl
{
#region //References
// SO question /http://stackoverflow.com/questions/4449449/asp-net-mvc-to-ignore-html-at-the-end-of-all-url
// Implementing Custom Base entry - Pro Asp.Net MVc Framework
//- http://books.google.com/books?id=tD3FfFcnJxYC&pg=PA251&lpg=PA251&dq=.net+RouteBase&source=bl&ots=IQhFwmGOVw&sig=0TgcFFgWyFRVpXgfGY1dIUc0VX4&hl=en&ei=z61UTMKwF4aWsgPHs7XbAg&sa=X&oi=book_result&ct=result&resnum=6&ved=0CC4Q6AEwBQ#v=onepage&q=.net%20RouteBase&f=false
// SO previous Question on ihttphandler - http://stackoverflow.com/questions/3359816/can-asp-net-routing-be-used-to-create-clean-urls-for-ashx-ihttphander-handle
// phil haack's Route Debugger http://haacked.com/archive/2008/03/13/url-routing-debugger.aspx
#endregion
public class MyUrlRoute : RouteBase
{
public override RouteData GetRouteData(HttpContextBase httpContext)
{
//~/Account/LogOn
//~/Home.aspx - Works fine
//~/home/index.aspx -Works Fine
//http://localhost:57282/home/index/1/2/3 - Works fine
//http://localhost:57282/Account/Register http://localhost:57282/Account/LogOn - Works Fine
string url = httpContext.Request.AppRelativeCurrentExecutionFilePath;
//check null for URL
const string defaultcontrollername = "Home";
string[] spliturl = url.Split("//".ToCharArray());
string controllername = String.Empty;
string actionname = "Index";
if (spliturl.Length == 2) //for ~/home.aspx and ~/
{
if (String.IsNullOrEmpty(spliturl[1])) //TODO: http://localhost:57282/ not working - to make it working
{
controllername = defaultcontrollername;
}
else
{
controllername = spliturl[1];
if (controllername.Contains("."))
{
controllername = controllername.Substring(0, controllername.LastIndexOf("."));
}
}
}
else if (spliturl.Length == 3) // For #/home/index.aspx and /home/about
{
controllername = spliturl[1];
actionname = spliturl[2];
if (actionname.Contains("."))
{
actionname = actionname.Substring(0, actionname.LastIndexOf("."));
}
}
else //final block in final case sned it to Home Controller
{
controllername = defaultcontrollername;
}
RouteData rd = new RouteData(this, new MvcRouteHandler());
rd.Values.Add("controller", controllername);
rd.Values.Add("action", actionname);
rd.Values.Add("url", url);
return rd;
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
return null;
}
}
}
in global.asax.cs add below code
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.Add(new MyUrlRoute()); // Add before your default Routes
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
working as expected.
possibly you can improve if/elseif code.
Using the Application_BeginRequest, will allow you to intercept all incoming requests, and allow you to trim the extension. Make sure to ignore requests for your content, such as .css, .js, .jpg, etc. Otherwise those requests will have their extensions trimmed as well.
protected void Application_BeginRequest(object sender, EventArgs e)
{
String originalPath = HttpContext.Current.Request.Url.AbsolutePath;
//Ignore content files (e.g. .css, .js, .jpg, .png, etc.)
if (!Regex.Match(originalPath, "^/[cC]ontent").Success)
{
//Search for a file extension (1 - 5 charaters long)
Match match = Regex.Match(originalPath, "\\.[a-zA-Z0-9]{1,5}$");
if (match.Success)
{
String modifiedPath = String.Format("~{0}", originalPath.Replace(match.Value, String.Empty));
HttpContext.Current.RewritePath(modifiedPath);
}
}
}
If you are using IIS 7, you should look at Dan Atkinson's answer.
I'm using IIS 6, so, in my case, I have the option to install isapi rewrite for IIS 6 or create custom route. I prefer to create my simple custom route class
AndraRoute.cs
// extend Route class,
// so that we can manipulate original RouteData
// by overriding method GetRouteDate
public class AndraRoute : Route
{
// constructor
public AndraRoute(
string url,
RouteValueDictionary defaults,
RouteValueDictionary constraints,
IRouteHandler routeHandler)
: base(url, defaults, constraints, routeHandler)
{
}
// get original RouteData
// check if any route data value has extension '.html' or '.anything'
// remove the extension
public override RouteData GetRouteData(HttpContextBase httpContext)
{
var data = base.GetRouteData(httpContext);
if (data == null) return null;
// from original route data, check
foreach (var pair in data.Values)
{
if (pair.Value.ToString().Contains('.'))
{
var splits = pair.Value.ToString().Split('.');
if (splits[1] == "html" || splits[1] == "anything")
{
data.Values[pair.Key] = splits[0];
}
break;
}
}
return data;
}
}
RouteCollectionExtensionHelper.cs
public static class RouteCollectionExtensionHelper
{
public static Route MapAndraRoute(this RouteCollection routes,
string name, string url, object defaults, object constraints,
string[] namespaces)
{
if (routes == null)
{
throw new ArgumentNullException("routes");
}
if (url == null)
{
throw new ArgumentNullException("url");
}
var route = new AndraRoute(url,
new RouteValueDictionary(defaults),
new RouteValueDictionary(constraints),
new MvcRouteHandler());
if ((namespaces != null) && (namespaces.Length > 0))
{
route.DataTokens = new RouteValueDictionary();
route.DataTokens["Namespaces"] = namespaces;
}
routes.Add(name, route);
return route;
}
}
RegisterRoutes method in Global.asax
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.IgnoreRoute("Content/{*pathInfo}");
routes.MapAndraRoute(
"Product",
"product/{id}/{slug}",
new { controller = "product", action = "detail" },
null, null
);
routes.MapAndraRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "home", action = "index", id = UrlParameter.Optional },
null, null
);
}