I have an Asp.Net Web Api project, and i am trying to create a simple IHttpModule for logging errors.
The module gets loaded correctly, because i could register to BeginRequest / EndRequest events. However, the Error event is never triggered.
I have also added and removed the runAllManagedModulesForAllRequests="true" attribute from web.config, but still with no effect.
public class ErrorLogModule : IHttpModule
{
public void Init(HttpApplication context)
{
context.Error += Context_Error;
}
// method never triggered
private void Context_Error(object sender, EventArgs e)
{
HttpContext ctx = HttpContext.Current;
Exception exception = ctx.Server.GetLastError();
// todo
// log Exception
}
public void Dispose()
{
}
}
<system.webServer>
<modules runAllManagedModulesForAllRequests="true">
<add name="ErrorLogger" type="HttpModules.HttpModules.ErrorLogModule" />
</modules>
</system.webServer>
[HttpGet]
[Route("triggerError")]
public string TriggerError()
{
int test = 0;
var a = 1 / test;
return "Hello Workd";
}
You can use better logging approach, that 100% working.
See this Microsoft article.
Shortly speaking you can implement
YourExceptionLogger: ExceptionLogger
with just one override method and register it by
config.Services.Add(typeof(IExceptionLogger), new YourExceptionLogger());
I write a custom handler to handle requests with OPTIONS verb.
public class OptionsRequestHandler : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
string origin = context.Request.Headers.Get("Origin");
context.Response.AddHeader("Access-Control-Allow-Origin", origin);
context.Response.AddHeader("Access-Control-Allow-Methods", "*");
context.Response.AddHeader("Access-Control-Allow-Headers", "accept, authorization, content-type");
}
public bool IsReusable
{
get { return false; }
}
}
And have registered this handler in web.config.
<system.webServer>
<modules>
......
</modules>
<handlers>
......
<add name="OptionsHandler" path="*" verb="OPTIONS" type="REAMS.Infrastructure.RequestHandlers.OptionsRequestHandler"/>
<add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="GET,POST,DELETE,PUT,HEAD" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
</handlers>
But the handler is never selected for options requests. Is there anything wrong? Thanks!
Finally figured out this problem. Because, by default, MVC framework map request to a handler by path, it is not possible to map a handler to a request by the request verb.
To do this, I will need to preempt the handler selection of MVC and implement my own module. Here is a working copy for anyone interested.
public class OptionsVerbModule : IHttpModule
{
public void Init(HttpApplication context)
{
context.PostRequestHandlerExecute += onPostRequestHandlerExecute;
context.PostResolveRequestCache += onPostResolveRequestCache;
}
private void onPostResolveRequestCache(object sender, EventArgs eventArgs)
{
if (string.Equals(HttpContext.Current.Request.HttpMethod, "OPTIONS", StringComparison.OrdinalIgnoreCase))
{
HttpContext.Current.RemapHandler(new OptionsRequestHandler());
}
}
private void onPostRequestHandlerExecute(object sender, EventArgs e)
{
string origin = HttpContext.Current.Request.Headers.Get("Origin");
if (origin != null)
{
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", origin);
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Credentials", "true");
}
}
public void Dispose()
{
}
}
What the module does is to check the verb of the request and if it is of 'OPTIONS' request, selected my customized handler in the question rather than map to MVC handler.
I do this because I find existing EnableCors for Web API is not suitable for my application needs and by using this customized process I have more control as well.
I've created a custom module:
namespace KittenFarm.ServerModules
{
public class CustomServerHeaderModule : IHttpModule
{
public void Init(HttpApplication context)
{
context.PreSendRequestHeaders += OnPreSendRequestHeaders;
}
public void Dispose()
{ }
void OnPreSendRequestHeaders(object sender, EventArgs e)
{
HttpContext.Current.Response.Headers.Remove("Server");
}
}
}
And I've registered it in my web config:
<system.webServer>
<validation validateIntegratedModeConfiguration="false"/>
<modules runAllManagedModulesForAllRequests="true">
<add name="CustomServerHeader" type="CustomServerHeaderModule" />
</modules>
....
However, it never seems to run.
I suspected it was a namespace issue, but I have tried every combination of namespaces in the type= section I can think of and it never hits the breakpoint I put in it.
Any ideas?
You are correct, it is your namespace that is missing from the type.
The following worked for me:
<modules runAllManagedModulesForAllRequests="true">
<add name="CustomServerHeader" type="KittenFarm.ServerModules.CustomServerHeaderModule" />
</modules>
The problem was that the solution was set up to use the Visual Studio Development Server, which doesn't pick up the http modules. After I changed it to use my local IIS, it worked.
You can set your function to run before Application_Start method is called. Add below code in global.asax
[assembly:PreApplicationStartMethod(typeof(CS.MVCHttpModule.MvcApplication),"Register")]
See http://dotnetlionet.blogspot.in/2015_06_01_archive.html
I create an IIS Module that appends text to the page before it loads. When I go to the URL, this works perfect the first time the page loads. On subsequent loads, however, the text is never appended.
Any thoughts on how to remedy this?
== CODE ==
Here's my web.config:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.web>
<compilation debug="true" />
</system.web>
<system.webServer>
<modules>
<add name="MIModule" type="MI.MyModule, MI" />
</modules>
<caching enabled="false" enableKernelCache="false" />
</system.webServer>
</configuration>
Some module code:
public void context_PreRequestHandlerExecute(Object source, EventArgs e)
{
HttpApplication app = (HttpApplication)source;
HttpRequest request = app.Context.Request;
string pageContent = app.Response.Output.ToString();
string useragent = "HI!<br />" + pageContent + "<hr />" ;
try
{
_current.Response.Output.Write(useragent);
}
catch
{
}
}
and the rest of the code:
private HttpContext _current = null;
#region IHttpModule Members
public void Dispose()
{
throw new Exception("Not implemented");
}
public void Init(HttpApplication context)
{
_current = context.Context;
context.PreRequestHandlerExecute += new EventHandler(context_PreRequestHandlerExecute);
}
#endregion
Is the _current variable actually HttpContext.Current? Is it a static field in your module? When/how is it initialized? My guess is that the empty catch clause gobbles up all errors, and following that thought, you most probably get a null reference on _current. Try removing the try/catch to learn more on what's wrong with your code
Python frameworks always provide ways to handle URLs that convey the data of the request in an elegant way, like for example http://somewhere.overtherainbow.com/userid/123424/
I want you to notice the ending path /userid/123424/
How do you do this in ASP.NET?
This example uses ASP.NET Routing to implement friendly URLs.
Examples of the mappings that the application handles are:
http://samplesite/userid/1234 - http://samplesite/users.aspx?userid=1234
http://samplesite/userid/1235 - http://samplesite/users.aspx?userid=1235
This example uses querystrings and avoids any requirement to modify the code on the aspx page.
Step 1 - add the necessary entries to web.config
<system.web>
<compilation debug="true">
<assemblies>
…
<add assembly="System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
</assemblies>
</compilation>
…
<httpModules>
…
<add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
</httpModules>
</system.web>
<system.webServer>
…
<modules>
…
<add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
</modules>
<handlers
…
<add name="UrlRoutingHandler" preCondition="integratedMode" verb="*" path="UrlRouting.axd" type="System.Web.HttpForbiddenHandler, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"/>
</handlers>
</system.webServer>
Step 2 - add a routing table in global.asax
Define the mapping from the friendly URL to the aspx page, saving the requested userid for later use.
void Application_Start(object sender, EventArgs e)
{
RegisterRoutes(RouteTable.Routes);
}
public static void RegisterRoutes(RouteCollection routes)
{
routes.Add("UseridRoute", new Route
(
"userid/{userid}",
new CustomRouteHandler("~/users.aspx")
));
}
Step 3 - implement the route handler
Add the querystring to the current context before the routing takes place.
using System.Web.Compilation;
using System.Web.UI;
using System.Web;
using System.Web.Routing;
public class CustomRouteHandler : IRouteHandler
{
public CustomRouteHandler(string virtualPath)
{
this.VirtualPath = virtualPath;
}
public string VirtualPath { get; private set; }
public IHttpHandler GetHttpHandler(RequestContext
requestContext)
{
// Add the querystring to the URL in the current context
string queryString = "?userid=" + requestContext.RouteData.Values["userid"];
HttpContext.Current.RewritePath(
string.Concat(
VirtualPath,
queryString));
var page = BuildManager.CreateInstanceFromVirtualPath
(VirtualPath, typeof(Page)) as IHttpHandler;
return page;
}
}
Code from users.aspx
The code on the aspx page for reference.
protected void Page_Load(object sender, EventArgs e)
{
string id = Page.Request.QueryString["userid"];
switch (id)
{
case "1234":
lblUserId.Text = id;
lblUserName.Text = "Bill";
break;
case "1235":
lblUserId.Text = id;
lblUserName.Text = "Claire";
break;
case "1236":
lblUserId.Text = id;
lblUserName.Text = "David";
break;
default:
lblUserId.Text = "0000";
lblUserName.Text = "Unknown";
break;
}
This is an alternative example that also uses ASP.NET Routing to implement friendly URLs.
Examples of the mappings that the application handles are:
http://samplesite/userid/1234 - http://samplesite/users.aspx?userid=1234
http://samplesite/userid/1235 - http://samplesite/users.aspx?userid=1235
This example does not use querystrings but requires additional code on the aspx page.
Step 1 - add the necessary entries to web.config
<system.web>
<compilation debug="true">
<assemblies>
…
<add assembly="System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
</assemblies>
</compilation>
…
<httpModules>
…
<add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
</httpModules>
</system.web>
<system.webServer>
…
<modules>
…
<add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
</modules>
<handlers
…
<add name="UrlRoutingHandler" preCondition="integratedMode" verb="*" path="UrlRouting.axd" type="System.Web.HttpForbiddenHandler, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"/>
</handlers>
</system.webServer>
Step 2 - add a routing table in global.asax
Define the mapping from the friendly URL to the aspx page, saving the requested userid for later use.
void Application_Start(object sender, EventArgs e)
{
RegisterRoutes(RouteTable.Routes);
}
public static void RegisterRoutes(RouteCollection routes)
{
routes.Add("UseridRoute", new Route
(
"userid/{userid}",
new CustomRouteHandler("~/users.aspx")
));
}
Step 3 - implement the route handler
Pass the routing context, containing the parameter, to the page. (Note the definition of IRoutablePage)
using System.Web.Compilation;
using System.Web.UI;
using System.Web;
using System.Web.Routing;
public interface IRoutablePage
{
RequestContext RequestContext { set; }
}
public class CustomRouteHandler : IRouteHandler
{
public CustomRouteHandler(string virtualPath)
{
this.VirtualPath = virtualPath;
}
public string VirtualPath { get; private set; }
public IHttpHandler GetHttpHandler(RequestContext
requestContext)
{
var page = BuildManager.CreateInstanceFromVirtualPath
(VirtualPath, typeof(Page)) as IHttpHandler;
if (page != null)
{
var routablePage = page as IRoutablePage;
if (routablePage != null) routablePage.RequestContext = requestContext;
}
return page;
}
}
Step 4 - Retrieve the parameter on the target page
Note the implemetation of IRoutablePage.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.Routing;
public partial class users : System.Web.UI.Page, IRoutablePage
{
protected RequestContext requestContext;
protected object RouteValue(string key)
{
return requestContext.RouteData.Values[key];
}
protected void Page_Load(object sender, EventArgs e)
{
string id = RouteValue("userid").ToString();
switch (id)
{
case "1234":
lblUserId.Text = id;
lblUserName.Text = "Bill";
break;
case "1235":
lblUserId.Text = id;
lblUserName.Text = "Claire";
break;
case "1236":
lblUserId.Text = id;
lblUserName.Text = "David";
break;
default:
lblUserId.Text = "0000";
lblUserName.Text = "Unknown";
break;
}
}
#region IRoutablePage Members
public RequestContext RequestContext
{
set { requestContext = value; }
}
#endregion
}
Here's another way of doing it using ASP.NET MVC
First off, here's the controller code with two actions. Index gets a list of users from the model, userid gets an individual user:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Ajax;
namespace MvcApplication1.Controllers
{
public class UsersController : Controller
{
public ActionResult Index()
{
return View(Models.UserDB.GetUsers());
}
public ActionResult userid(int id)
{
return View(Models.UserDB.GetUser(id));
}
}
}
Here's the Index.asp view, it uses an ActionLink to create links in the correct format:
<%# Page Language="C#" AutoEventWireup="true" CodeBehind="Index.aspx.cs" Inherits="MvcApplication1.Views.Index" %>
<%# Import Namespace="MvcApplication1.Controllers" %>
<%# Import Namespace="MvcApplication1.Models" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title></title>
</head>
<body>
<div>
<h2>Index of Users</h2>
<ul>
<% foreach (User user in (IEnumerable)ViewData.Model) { %>
<li>
<%= Html.ActionLink(user.name, "userid", new {id = user.id })%>
</li>
<% } %>
</ul>
</div>
</body>
</html>
And here's the userid.aspx view which displays an individual's details:
<%# Page Language="C#" AutoEventWireup="true" CodeBehind="userid.aspx.cs" Inherits="MvcApplication1.Views.Users.userid" %>
<%# Import Namespace="MvcApplication1.Controllers" %>
<%# Import Namespace="MvcApplication1.Models" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<div>
<table border ="1">
<tr>
<td>
ID
</td>
<td>
<%=((User)ViewData.Model).id %>
</td>
</tr>
<tr>
<td>
Name
</td>
<td>
<%=((User)ViewData.Model).name %>
</td>
</tr>
</table>
</div>
</body>
</html>
And finally for completeness, here's the model code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace MvcApplication1.Models
{
public class UserDB
{
private static List<User> users = new List<User>{
new User(){id=12345, name="Bill"},
new User(){id=12346, name="Claire"},
new User(){id=12347, name="David"}
};
public static List<User> GetUsers()
{
return users;
}
public static User GetUser(int id)
{
return users.First(user => user.id == id);
}
}
public class User
{
public int id { get; set; }
public string name { get; set; }
}
}
I've been using a URL rewriter by Intelligencia:
http://urlrewriter.net/
It was so easy to configure - maybe an hour to get it all up and running. Very few problems with it...
I'd recommend it, but I should mentioned I've not tried any other ones.
Good luck!
Also, check out ASP.NET MVC or if you're set on webforms, the new System.Web.Routing namespace in ASP.NET 3.5 SP1
I've developed an open source NuGet library for this problem which implicitly converts EveryMvc/Url to every-mvc/url.
Dashed urls are much more SEO friendly and easier to read. Lowercase URLs tend to create less problems. (More on my blog post)
NuGet Package: https://www.nuget.org/packages/LowercaseDashedRoute/
To install it, simply open the NuGet window in the Visual Studio by right clicking the Project and selecting NuGet Package Manager, and on the "Online" tab type "Lowercase Dashed Route", and it should pop up.
Alternatively, you can run this code in the Package Manager Console:
Install-Package LowercaseDashedRoute
After that you should open App_Start/RouteConfig.cs and comment out existing route.MapRoute(...) call and add this instead:
routes.Add(new LowercaseDashedRoute("{controller}/{action}/{id}",
new RouteValueDictionary(
new { controller = "Home", action = "Index", id = UrlParameter.Optional }),
new DashedRouteHandler()
)
);
That's it. All the urls are lowercase, dashed, and converted implicitly without you doing anything more.
Open Source Project Url: https://github.com/AtaS/lowercase-dashed-route