I have a problem with logging an message to EventViewer\WindowsLogs using a custom HTTPModule class. I've already try to run Visual Studio with admin rights, I also tried from IIS 6.0. It doesn't crash, it just doesn't add any code. Below it's the module class and the config file.
HttpModule
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Diagnostics;
namespace Chapter_V.HttpModules
{
public class MyHttpModule : IHttpModule
{
public void Init(HttpApplication application)
{
application.AuthenticateRequest += new EventHandler(OnAuthentication);
}
private void OnAuthentication(object sender, EventArgs e)
{
string name = HttpContext.Current.User.Identity.Name;
EventLog log = new EventLog();
log.Source = "My First HttpModule";
log.WriteEntry(name + " was authenticated !");
}
public void Dispose()
{
}
}
}
web.config
<?xml version="1.0"?>
<configuration>
<system.web>
<httpModules>
<add name="MyHttpModule" type="Chapter_V.HttpModules.MyHttpModule,ChapterV"/>
</httpModules>
</system.web>
<system.webServer>
<modules>
<add name="MyHttpModule" type="Chapter_V.HttpModules.MyHttpModule,ChapterV"/>
</modules>
</system.webServer>
</configuration>
Do you have any ideea about this issue? (this is only for study purposes)
Provided the user identity on your application pool has the rights to create event log entries.
Try running the following script:
eventcreate /ID 1 /L APPLICATION /T INFORMATION /SO "My First HttpModule" /D "My first log"
This will create a new event source named "My First HttpModule" under APPLICATION event log as INFORMATION event type.
Not sure of the exact reasons why, but a source must already exist (be created) in the event log before it can be used in code.
Source of information is here
Related
I have an HttpHandler that I'm trying to use to put a little security layer over a certain directory in my site, but it's behaving strangely.
I've got it registered like this in my Web.Config: no longer valid since I'm in IIS 7.5
<httpHandlers>
<add verb="*" path="/courses/*" type="CoursesAuthenticationHandler" />
I can't tell if it's actually being called or not, because regardless of the code, it always seems to do nothing. On the flip side, if there are any errors in the code, it does show me an error page until I've corrected the error.
Here's the handler itself:
using System;
using System.Web;
public class CoursesAuthenticationHandler : IHttpHandler
{
public bool IsReusable
{
get { return true; }
}
public void ProcessRequest(HttpContext context)
{
if (!context.Request.IsAuthenticated)
context.Response.Redirect("/");
}
}
So... that's pretty much it. The handler is being registered and analyzed at compile time, but doesn't actually do what it's expected to.
Edit: I realized that I'm using IIS 7.5 and that does indeed have an impact on this implementation.
For IIS 7, here's the Web.Config registration I used:
<handlers accessPolicy="Read, Execute, Script">
<add name="CoursesAuthenticationHandler"
verb="*"
path="/courses/*"
type="CoursesAuthenticationHandler"
resourceType="Unspecified" />
Edit 2: Progress! When not logged in, requests made to the /courses/ directory are redirected to the login page. However, authenticated requests to the /courses/ directory return empty pages...
Edit 3: Per #PatrickHofman's suggestion, I've switched to using an HttpModule.
The Web.Config registration:
<modules>
<add name="CourseAuthenticationModule" type="CourseAuthenticationModule" />
The code:
using System;
using System.Web;
public class CourseAuthenticationModule : IHttpModule
{
public void Dispose() { }
public void Init(HttpApplication context)
{
context.BeginRequest += new EventHandler(BeginRequest);
}
public void BeginRequest(Object source, EventArgs e)
{
HttpApplication app = (HttpApplication)source;
HttpContext context = app.Context;
HttpRequest request = context.Request;
HttpResponse response = context.Response;
if (request.Path.ToLower().StartsWith("/courses/") && !request.IsAuthenticated)
{
response.Redirect("/");
}
}
}
Now the problem is that !request.IsAuthenticated is always false. If I'm logged in, and navigate to the /courses/ directory, I'm redirected to the homepage.
What's the deal?
I think the last problem lies in the fact that a HttpHander handles stuff. It is the end point of a request.
Since you didn't add anything to the request, the response will end up empty.
Are you looking for HttpModules? They can be stacked.
As a possible solution when only files are necessary: read the files yourself in the request by either reading and writing to response or use TransmitFile. For ASP.NET pages you need modules.
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've wrote a simple handler:
public class ImageHandler : IHttpHandler, IRequiresSessionState
{
public bool IsReusable
{
get { return true; }
}
public void ProcessRequest(HttpContext context)
{
byte[] imgData = context.Session["Data"] as byte[];
if (imgData != null)
{
context.Response.CacheControl = "no-cache";
context.Response.Cache.SetCacheability(HttpCacheability.NoCache);
context.Response.ContentType = "image/png";
context.Response.BinaryWrite(imgData);
context.Response.Flush();
}
}
}
And setup the web.config:
<system.web>
<httpHandlers>
<add verb="GET" path="image.png" type="TestWeb.Handlers.ImageHandler, TestWeb" />
</httpHandlers>
</system.web>
<system.webServer>
<handlers>
<add name="Image" verb="GET" path="image.png" type="TestWeb.Handlers.ImageHandler, TestWeb" />
</handlers>
</system.webServer>
If I run the code allowing VS start a new IIS service and open a new tab it reaches the breakpoint on the handler.
If I set don't open a page. Wait for request from an external application it never reaches the handler.
It is not just the breakpoint, no code from the handler executes when I run the website configured on IIS. It only works if I start from VS.
What did I miss when configuring IIS7 ?
I had to switch the Application Pool to Integrated mode, it was using classic.
And I had to remove the handler configuration from <system.web> because it was giving me error 500.23.
HTTP Error 500.23 - Internal Server
Error An ASP.NET setting has been
detected that does not apply in
Integrated managed pipeline mode.
you need to attach to the asp.net worker process. go to tools/attach to process and choose the w3p process.
Here's the big picture. We're running a server in IIS 6 that hosts several web sites and applications, and we're in the process of moving the whole thing to a different data center with a slightly different setup. We've notified our users and updated our DNS info so that theoretically everyone will be happily hitting the new server from day 1, but we know that someone will inevitably fall through the cracks.
The powers that be want a "Listener" page/handler that will receive all requests to the server and log the entire request to a text file, including (especially) POST data.
That's where I'm stuck. I don't know how to implement a single handler that will receive all requests to the server. I vaguely understand IIS 6 redirection options, but they all seem to lose the POST data on the redirect. I also know a little about IIS 6's built-in logging, but it ignores POST data as well.
Is there a simple(ish) way to route all requests to the server so that they all hit a single handler, while maintaining post data?
EDIT: This is in WebForms, if that matters, but other solutions (if small) are definitely worth considering.
If all the requests are POST's to ASP.NET forms then you could plugin a HttpModule to capture and log this data.
You wouldn't have to rebuild all your applications to deploy this either. All it would take is to drop the HttpModule into each application's /bin folder and add it to the <httpModules> section of your web.config files. For example:
using System;
using System.Diagnostics;
using System.Web;
public class SimpleLogger : IHttpModule
{
private HttpApplication _application;
public void Dispose() { }
public void Init(HttpApplication context)
{
_application = context;
context.BeginRequest += new EventHandler(Context_BeginRequest);
}
void Context_BeginRequest(object sender, EventArgs e)
{
foreach (string key in _application.Request.Form.AllKeys)
{
// You can get everything on the Request object at this point
// Output to debug but you'd write to a file or a database here.
Debug.WriteLine(key + "=" + _application.Request.Form[key]);
}
}
}
In your web.config file add the logger:
<httpModules>
<add name="MyLogger" type="SimpleLogger, SimpleLogger"/>
</httpModules>
Be careful though. If your site captures credit card details or other sensitive data. You may need to ensure this is filtered out or have it encrypted and away from personel who should have no need to see this information.
Also if you're logging to files, make sure the log files are outside any public facing web folders.
Here is code of custom HTTP module we use to log HTTP POST request data.
using System;
using System.Web;
namespace MySolution.HttpModules
{
public class HttpPOSTLogger : IHttpModule
{
public void Dispose()
{
}
public void Init(HttpApplication context)
{
context.BeginRequest += new EventHandler(context_BeginRequest);
}
private void context_BeginRequest(object sender, EventArgs e)
{
if (sender != null && sender is HttpApplication)
{
var request = (sender as HttpApplication).Request;
var response = (sender as HttpApplication).Response;
if (request != null && response != null && request.HttpMethod.ToUpper() == "POST")
{
var body = HttpUtility.UrlDecode(request.Form.ToString());
if (!string.IsNullOrWhiteSpace(body))
response.AppendToLog(body);
}
}
}
}
}
Do not forget to register it in web.config of you application.
Use system.WebServer section for IIS Integrated Model
<system.webServer>
<modules>
<add name="HttpPOSTLogger" type="MySolution.HttpModules.HttpPOSTLogger, MySolution.HttpModules" />
</modules>
</system.webServer>
Use system.web section for IIS Classic Model
<system.web>
<httpModules>
<add name="HttpPOSTLogger" type="MySolution.HttpModules.HttpPOSTLogger, MySolution.HttpModules"/>
</httpModules>
</system.web>
IIS log Before applying module:
::1, -, 10/31/2017, 10:53:20, W3SVC1, machine-name, ::1, 5, 681, 662, 200, 0, POST, /MySolution/MyService.svc/MyMethod, -,
IIS log After applying module:
::1, -, 10/31/2017, 10:53:20, W3SVC1, machine-name, ::1, 5, 681, 662, 200, 0, POST, /MySolution/MyService.svc/MyMethod, {"model":{"Platform":"Mobile","EntityID":"420003"}},
Full article:
https://www.codeproject.com/Tips/1213108/HttpModule-for-logging-HTTP-POST-data-in-IIS-Log
So my scenarios a little funny but theres a reason for it.
I have a parent web application, called Parent, and a second web application called Child. Child is a virtual directory in IIS7 under Parent which is an application in IIS. Child is not a child directory of parent in the file system, only in IIS as a virtual directory. On application loading (Application_Start in global.asax) in the parent web application i tell it to load the child web dlls from childs bin folder using Assembly.LoadFrom() loading it into the app domain of Parent. Then when i try to visit /Child/Default.aspx I get an error saying:
Parser Error
Parser Error Message: Could not load type 'Child._Default'.
Now the Child.dll (the web dll containing childs code behind etc) is in the app domain of the parent application and i can successfully reflect it and its members from code behind in the Parent page Default.aspx.
Furthermore on the Child/Default.aspx if i change the Inherits="Child._Default" to Inherits="System.Web.UI.Page" and then in <% %> tags on the page enumerate the dlls in the app domain i can see Child.dll and reflect its members and invoke functions.
One thing that works is changing CodeBehind to CodeFile in the page directive. Then the page parses correctly. However this only works when the websites are in uncompiled, non published form.
What's happening is the appdomain isn't looking within it's assembly list when it's trying to resolve the "Child" assembly for the page in the Child project.
What you need to do is use the AssemblyResolve event handler in the AppDomain. You can do so like this:
First we create and AssemblyLoader class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Reflection;
using System.IO;
namespace Parent
{
internal class AssemblyLoader
{
private static List<AssemblyInformation> virtualDirectoryAssemblies = new List<AssemblyInformation>();
private static readonly string virtualDirectoryBinFolderFormatString = "~/{0}/bin/";
private static readonly string[] pathSplitParams = new string[1] { "\\" };
private static readonly string[] assemblyNameSplitParams = new string[1] { "," };
internal static Assembly AssemblyResolve(object sender, ResolveEventArgs e)
{
var name = e.Name.Split(assemblyNameSplitParams, StringSplitOptions.RemoveEmptyEntries).First();
if (!virtualDirectoryAssemblies.Exists(a => a.Name.Equals(name)))
return null;
return Assembly.LoadFrom(virtualDirectoryAssemblies.Single(a => a.Name.Equals(name)).Path);
}
internal static void LoadVirtualDirectories(List<string> virtualDirectories)
{
foreach (var v in virtualDirectories)
{
var path = HttpContext.Current.Server.MapPath(string.Format(virtualDirectoryBinFolderFormatString, v));
AppDomain.CurrentDomain.AppendPrivatePath(path);
AppDomain.CurrentDomain.SetShadowCopyPath(path);
var assemblies = Directory.GetFiles(path, "*.dll", SearchOption.AllDirectories).ToList();
foreach (var a in assemblies)
{
var name = a.Split(pathSplitParams, StringSplitOptions.RemoveEmptyEntries).Last().Replace(".dll", string.Empty);
if(!virtualDirectoryAssemblies.Exists(i => i.Name.Equals(name)))
{
virtualDirectoryAssemblies.Add(new AssemblyInformation
{
Name = name,
Path = a
});
}
}
}
}
class AssemblyInformation
{
public string Name { get;set; }
public string Path { get; set; }
}
}
}
In the web.config file for the Parent project I added this (if you have more virtual directories, the idea is to have a comma deliminated list):
<appSettings>
<add key="VirtualDirectories" value="Child"/>
</appSettings>
In the web.config of the child project, you add this reference to the assembly Child assembly:
<system.web>
<compilation>
<assemblies>
<add assembly="Child, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
</assemblies>
</compilation>
</system.web>
It can also be like this:
<system.web>
<compilation>
<assemblies>
<add assembly="Child"/>
</assemblies>
</compilation>
</system.web>
Now, last but not least, we put this into the Global.asax:
protected void Application_Start(object sender, EventArgs e)
{
AppDomain.CurrentDomain.AssemblyResolve += AssemblyLoader.AssemblyResolve;
var virtualDirectories =
ConfigurationManager.AppSettings.Get("VirtualDirectories").Split(new string[1] { "," }, StringSplitOptions.RemoveEmptyEntries).ToList();
AssemblyLoader.LoadVirtualDirectories(virtualDirectories);
}
And we're done... :P