How to debug UrlRewriter.NET? - asp.net

I have looked at their help page it seems like I can register a debug logger that outputs information to the 'standard ASP.NET debug window'. My problem is I don't know what that means, if it means the debug output window in Visual Studio (where you see build output, debug output and more) I am not seeing any UrlRewriter debug output.
The rules are working (mostly) I just want to get more debug output to fix issues.
I added the register call to the rewriter section like this:
<rewriter>
<register logger="Intelligencia.UrlRewriter.Logging.DebugLogger, Intelligencia.UrlRewriter" />
....
</rewriter>
I am hosting this website locally in IIS on Vista, to debug it I attach the debugger to the w3wp process.
Other selected parts from the web.config"
<compilation debug="true">
<assemblies>
...
</assemblies>
</compilation>
<trace enabled="true"/>
Where should I see the debug output from UrlRewriter.NET? If it is in the Visual Studio debug output window, any ideas why I am not seeing it there?

Try to run the DebugView for read the messages from Intelligencia.UrlRewriter
Get it from here
http://technet.microsoft.com/en-us/sysinternals/bb896647.aspx
I see the source code, and I belive thats works, but if not works, then why not get the source code, and compile it with your project and just debug it on site ?

I wanted to debug because I had the impression my rewrite rules weren't functioning.
After a while I figured out I had to alter my web.config and add the following line to the 'system.web', 'httpModules' section:
<add name="UrlRewriter" type="Intelligencia.UrlRewriter.RewriterHttpModule, Intelligencia.UrlRewriter"/>

For anyone who wants to log the event messages to a file as well as see them in the debug output window, here's a piece of code I created.
Please only use this in a development environment, this code is not optimized.
usage:
In your asp.net application, add a reference to this library (MyPresentationLayer.Web).
Add the following element to 'rewriter' node:
<register logger="IntelligenciaExt.Web.Logging.UrlRewriterIntelligencia.FileLogger, IntelligenciaExt.Web"/>
By default the log file can be found outside your 'www' folder, in the subfolder 'intelligenciaLog'.
using System;
using SysDiag = System.Diagnostics;
using System.IO;
using Intelligencia.UrlRewriter.Logging;
namespace MyPresentationLayer.Web.Logging.UrlRewriterIntelligencia
{
/// <summary>
/// Custom logger for Intelligencia UrlRewriter.net that logs messages
/// to a plain text file (../intelligenciaLog/log.txt).
/// </summary>
public class FileLogger : IRewriteLogger
{
private const string _logFolderName = "../intelligenciaLog";
private const string _logFileName = "log.txt";
private const string _appName = "UrlRewriterIntelligencia.FileLogger";
public FileLogger()
{
LogToFile(Level.Info, "Created new instance of class 'FileLogger'");
}
public void Debug(object message)
{
LogToFile(Level.Debug, (string)message);
}
public void Error(object message, Exception exception)
{
string errorMessage = String.Format("{0} ({1})", message, exception);
LogToFile(Level.Error, errorMessage);
}
public void Error(object message)
{
LogToFile(Level.Error, (string)message);
}
public void Fatal(object message, Exception exception)
{
string fatalMessage = String.Format("{0} ({1})", message, exception);
LogToFile(Level.Fatal, fatalMessage);
}
public void Info(object message)
{
LogToFile(Level.Info, (string)message);
}
public void Warn(object message)
{
LogToFile(Level.Warn, (string)message);
}
private static void LogToFile(Level level, string message)
{
string outputMessage = String.Format("[{0} {1} {2}] {3}", DateTime.Now.ToString("yyyyMMdd HH:mm:ss"),
_appName.PadRight(50, ' '), level.ToString().PadRight(5, ' '),
message);
SysDiag.Debug.WriteLine(outputMessage);
try
{
string pathToLogFolder =Path.Combine(AppDomain.CurrentDomain.BaseDirectory, _logFolderName);
if (!Directory.Exists(pathToLogFolder))
{
Directory.CreateDirectory(pathToLogFolder);
}
string fullPathToLogFile = Path.Combine(pathToLogFolder, _logFileName);
using (StreamWriter w = File.AppendText(fullPathToLogFile))
{
w.WriteLine(outputMessage);
// Update the underlying file.
w.Flush(); // Close the writer and underlying file.
w.Close();
}
}
catch (Exception) { }
}
internal enum Level
{
Warn,
Fatal,
Info,
Error,
Debug
}
}
}

Related

How to force browser to cache file in IIS?

Currently, I append application version to all JavaScript & StyleSheet files to prevent caching in old browsers. It works fine. However, I would like to cache all JavaScript & StyleSheet without any request to web server.
With current setting, web server responses like the following image. I don't want browser to spend time to check ETag for all JavaScript & StyleSheet files.
Here is current setting in web.config
<clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="365.00:00:00" />
Here is a simple solution with an HttpModule which works across ASP implementations. We used it in a SPA app. It will ask browser to cache certain resources for a year. The home/landing page is an exception and will always be checked using ETag.
Step 1:
The first step you've have already done which is adding the version number in the url of each resource. We do this as an automated step in the build process.
Step 2: Next add a CacheModule class into your app:
public class CacheModule : IHttpModule
{
// extensions to cache, e.g. ".css",".html",".js",".png",".jpg",".gif",".ico",".woff",".eot",".svg",".ttf"
private readonly string[] _extensions = ConfigurationManager.AppSettings["CacheModule_Extensions"].Split(",");
private readonly string[] _exceptions = ConfigurationManager.AppSettings["CacheModule_Exceptions"].Split(",");
public void Dispose() {}
public void Init(HttpApplication context)
{
context.EndRequest += (sender, args) =>
{
var ctx = HttpContext.Current;
var path = ctx.Request.Url.GetComponents(UriComponents.Path, UriFormat.SafeUnescaped);
var isExcept = _exceptions.Any(path.Contains);
ctx.Response.AddHeader("Cache-Control", "private");
if (_extensions.Any(path.Contains) && ! isExcept )
{
ctx.Response.AddHeader("Expires", (DateTime.Now.ToUniversalTime().AddYears(1)).ToString("r"));
}
else if (isExcept)
{
ctx.Response.AddHeader("Expires", (DateTime.Now.ToUniversalTime().AddHours(-1)).ToString("r"));
ctx.Response.AddHeader("Max-Age", "0");
}
};
}
}
Step 3: Finally you put in your config:
<?xml version="1.0"?>
<configuration>
<appSettings>
<!-- resource extensions to cache -->
<add key="CacheModule_Extensions" value=".css,.html,.js,.png,.jpg,.gif,.ico,.woff,.eot,.svg,.ttf" />
<!-- exceptions to caching such as home/landing page e.g. "index.html" or any page/resource with known url that users may enter directly or may be redirected to -->
<add key="CacheModule_Exceptions" value="index.html,buildinfo.html,unsupportedmobilebrowser.html, unsupportedbrowser.html" />
</appSettings>
<system.webServer>
<modules>
<add name="CacheModule" type="MyApp.Caching.CacheModule, MyApp"/>
</modules>
</system.webServer>
</configuration>
As IIS serves files and watches for changes, IIS will always send re-validation cache header, forcing browser to check for changes. To get rid of this problem, we designed CachedRoute as shown below, however this works well in ASP.NET MVC but with little changes you can implement same in ASP.NET WebForms as well.
This code also gives you benefit of moving your static resources to CDN.
Cached Version URL Prefix
We had to come up with versioning of static content like /cached/version/, this is nothing but just a url prefix for static asset. version can be any random alphanumeric string totally useless, but identifies different version.
Well one of the easiest approach is to use a version key in the URL.
First, create build version in AssemblyInfo.cs
[assembly: AssemblyVersion("1.5.*.*")]
Leave, * as build number replacement, .net compiler will automatically increment with each build.
Or define version in app settings as follow
<appSettings>
<add key="Static-Content-Version" value="1.5.445.55565"/>
<add key="CDNHost" value="cdn1111.cloudfront.net"/>
</appSettings>
// Route configuration
// set CDN if you have
string cdnHost = WebConfigrationManager.AppSettings["CDNHost"];
if(!string.IsEmpty(cdnHost)){
CachedRoute.CDNHost = cdnHost;
}
// get assembly build information
string version = typeof(RouteConfig).Assembly.GetName().Version.ToString();
CachedRoute.CORSOrigins = "*";
CachedRoute.Register(routes, TimeSpam.FromDays(30), version);
Now on each page, reference your static content as,
<script src="#CachedRoute.CachedUrl("/scripts/jquery-1.11.1.js")"></script>
While rendering, your page will be rendered as (without CDN)
<script src="/cached/1.5.445.55565/scripts/jquery-1.11.1.js"></script>
With CDN as
<script
src="//cdn111.cloudfront.net/cached/1.5.445.55565/scripts/jquery-1.11.1.js">
</script>
Putting version in URL path instead of query string makes CDN perform better as query strings can be ignored in CDN configuration (which is usually the default case).
Advantages
If you set version same as Assembly version, it becomes easy to put new build. Otherwise you have to manually change web.config with every time when version changes.
CachedRoute Class from
https://github.com/neurospeech/atoms-mvc.net/blob/master/src/Mvc/CachedRoute.cs
public class CachedRoute : HttpTaskAsyncHandler, IRouteHandler
{
private CachedRoute()
{
// only one per app..
}
private string Prefix { get; set; }
public static string Version { get; private set; }
private TimeSpan MaxAge { get; set; }
public static string CORSOrigins { get; set; }
//private static CachedRoute Instance;
public static void Register(
RouteCollection routes,
TimeSpan? maxAge = null,
string version = null)
{
CachedRoute sc = new CachedRoute();
sc.MaxAge = maxAge == null ? TimeSpan.FromDays(30) : maxAge.Value;
if (string.IsNullOrWhiteSpace(version))
{
version = WebConfigurationManager.AppSettings["Static-Content-Version"];
if (string.IsNullOrWhiteSpace(version))
{
version = Assembly.GetCallingAssembly().GetName().Version.ToString();
}
}
Version = version;
var route = new Route("cached/{version}/{*name}", sc);
route.Defaults = new RouteValueDictionary();
route.Defaults["version"] = "1";
routes.Add(route);
}
public override bool IsReusable
{
get
{
return true;
}
}
public static string CDNHost { get; set; }
public static HtmlString CachedUrl(string p)
{
if (!p.StartsWith("/"))
throw new InvalidOperationException("Please provide full path starting with /");
string cdnPrefix = string.IsNullOrWhiteSpace(CDNHost) ? "" : ("//" + CDNHost);
return new HtmlString(cdnPrefix + "/cached/" + Version + p);
}
public override async Task ProcessRequestAsync(HttpContext context)
{
var Response = context.Response;
Response.Cache.SetCacheability(HttpCacheability.Public);
Response.Cache.SetMaxAge(MaxAge);
Response.Cache.SetExpires(DateTime.Now.Add(MaxAge));
if (CORSOrigins != null)
{
Response.Headers.Add("Access-Control-Allow-Origin", CORSOrigins);
}
string FilePath = context.Items["FilePath"] as string;
var file = new FileInfo(context.Server.MapPath("/" + FilePath));
if (!file.Exists)
{
throw new FileNotFoundException(file.FullName);
}
Response.ContentType = MimeMapping.GetMimeMapping(file.FullName);
using (var fs = file.OpenRead())
{
await fs.CopyToAsync(Response.OutputStream);
}
}
IHttpHandler IRouteHandler.GetHttpHandler(RequestContext requestContext)
{
//FilePath = requestContext.RouteData.GetRequiredString("name");
requestContext.HttpContext.Items["FilePath"] = requestContext.RouteData.GetRequiredString("name");
return (IHttpHandler)this;
}
}
Sample Response Headers for first request
Access-Control-Allow-Origin:*
Cache-Control:public
Content-Length:453
Content-Type:image/png
Date:Sat, 04 Jul 2015 08:04:55 GMT
Expires:Mon, 03 Aug 2015 00:46:43 GMT
Server:Microsoft-IIS/8.5
Via:1.1 ********************************
X-Amz-Cf-Id: ******************************
X-AspNet-Version:4.0.30319
X-AspNetMvc-Version:5.2
X-Cache:Miss from cloudfront
X-Powered-By:ASP.NET
See, there is no ETag, Vary by, Last Modified or validation header and also see explicit Expires header, when you send explicit Expires header, Browser will never try to validate cache.

HttpModule isn't processing request authentication

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.

How to get exception details on shared error.cshtml

I am using ASPNet MVC4 with HandleErrorAttribute filter configured on Global.asax
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
On my web.config I have configured customError mode to RemoteOnly:
<customErrors mode="RemoteOnly" />
So, the user are redirected to ~/Shared/Error.cshtml when a exception are raised on any View, that`s ok.
Now, I can catch this exception for log:
~/Shared/Error.cshtml code:
#{
log4net.LogManager
.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType)
.Fatal("Not captured exception", ex);
}
Actually this code are working fine (without EX parameter), but I need to improve my log including exception details.
How I can get exception details on this page?
Just make your Error.cshtml view strongly typed to HandleErrorInfo which is the model being passed to this view by the HandleErrorAttribute:
#model System.Web.Mvc.HandleErrorInfo
#{
log4net
.LogManager
.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType)
.Fatal("Not captured exception", Model.Exception);
}
And by the way, logging inside a view doesn't seem like the cleanest thing you could do. I'd rather write my custom error handler attribute and log the exception there.
I'd recommend writing a custom handler and log within, e.g.
[AttributeUsage(AttributeTargets.All, Inherited = true)]
public class HandleErrorAttributeCustom : HandleErrorAttribute
{
public override void OnException(ExceptionContext context)
{
base.OnException(context);
log4net.LogManager
.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType)
.Fatal("Not captured exception", context.Exception);
}
}

Ajax Url to Routed WCF in Asp.Net 4 Web Forms

I implemented Routing in Asp.Net 4 Web App.
With aspx file it works fine, but it doesn't work with WCF.
I have WCF which is called from javascipt file by ajax request.
My WCF code is:
[ServiceContract(Namespace = "")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class Signature
{
/// <summary>
/// Get signature info by email address.
/// </summary>
[OperationContract]
[WebInvoke(ResponseFormat = WebMessageFormat.Json)]
public string GetSignatureInfo(string email)
{
...
}
}
}
Web.config:
<services>
<service name="WEB.Services.Signature">
<endpoint address="" behaviorConfiguration="WEB.Services.SignatureAspNetAjaxBehavior"
binding="webHttpBinding" contract="WEB.Services.Signature" />
</service>
</services>
javascript:
var _data = {
"email": self.email.val()
};
$.ajax({
url: '../Signature'
, type: 'POST'
, dataType: 'json'
, data: JSON.stringify(_data)
, success: function (data) {
console.log('success');
}
, error: function () {
console.log('error');
}
});
Global.asax:
void RegisterRoutes(RouteCollection routes)
{
var sr = new ServiceRoute("Signature", new WebServiceHostFactory(), typeof(Signature));
sr.Url = "Services/Signature.svc/GetSignatureInfo";
routes.Add(sr);
}
void Application_Start(object sender, EventArgs e)
{
// Code that runs on application startup
RegisterRoutes(RouteTable.Routes);
}
I get "NetworkError: 404 Not Found - _http://www.my-project.com/Signature". Where am I wrong or what ajax url should be ?!!! Please, help.
Thanks in advance.
My work colleague has found a solution:
The problem was in IIS configuration -
If my app is under Default Site , I should add ajax url with prefix of project. For example, if my Service is located in Web project, I should enter url like : '/Web/Services/MyService/MyFunction' in js file. The same url should be on Global.asax file, in ServiceRoute url, but without first '/'.
In this case, it will work fine locally,but there will be a problem to put it on production server.
The optimal solution:
1. IIS : add web site, configure physical path to him, change its port(!!!).
2. .js + Global.asax : just remove project name from url in both places. So, the url will be like : in js file '/Services/MyService/MyFunction' and in Global.asax 'Services/MyService/MyFunction' (without first '/')
That's all. Big thanks to Miri(colleague).

Execute some code for each request for ASP.NET (aspx and cshtml )

Is there a way to write some code that would be executed for each request to a .aspx or a .cshtml page in asp.net 4.5 apart from using a base page class. it is a very huge project and making changes to all pages to use a base page is a nightmare. Also i am not sure how would this be done for a cshtml page since they don't have a class.
Can we use the Application_BeginRequest and target only the aspx and cshtml files since the website is running in integrated mode.?
basically, i have to check if a user who is accessing the website has a specific ip address against a database and if yes then allow access otherwise redirect.
we are using IIS8 and ASP.Net 4.5 and ASP.Net Razor Web Pages
Also i am not sure how would this be done for a cshtml page since they don't have a class.
You could place a _ViewStart.cshtml file whose contents will get executed on each request.
Alternatively you could write a custom Http Module:
public class MyModule: IHttpModule
{
public void Init(HttpApplication app)
{
app.BeginRequest += new EventHandler(OnBeginRequest);
}
public void Dispose()
{
}
public void OnBeginRequest(object s, EventArgs e)
{
// this code here's gonna get executed on each request
}
}
and then simply register this module in your web.config:
<system.webServer>
<modules>
<add name="MyModule" type="SomeNamespace.MyModule, SomeAssembly" />
</modules>
...
</system.webServer>
or if you are running in Classic Mode:
<system.web>
<httpModules>
<add name="MyModule" type="SomeNamespace.MyModule, SomeAssembly" />
</httpModules>
</system.web>
basically, i have to check if a user who is accessing the website has
a specific ip address against a database and if yes then allow access
otherwise redirect.
Inside the OnBeginRequest method you could get the current user IP:
public void OnBeginRequest(object sender, EventArgs e)
{
var app = sender as HttpApplication;
var request = app.Context.Request;
string ip = request.UserHostAddress;
// do your checks against the database
}
Asp.net MVC filters are especially designed for that purpose.
You would implement ActionFilterAttribute like this (maybe put this new class in a Filters folder in your webapp solution):
public class IpFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
string ip = filterContext.HttpContext.Request.UserHostAddress;
if(!testIp(ip))
{
if (true /* You want to use a route name*/)
filterContext.Result = new RedirectToRouteResult("badIpRouteName");
else /* you want an url */
filterContext.Result = new RedirectResult("~/badIpController/badIpAction");
}
base.OnActionExecuting(filterContext);
}
private bool testIp(string inputIp)
{
return true /* do you ip test here */;
}
}
Then you have to decorate any action that would perform the ipcheck with IpFilter like so :
[IpFilter]
public ActionResult AnyActionWhichNeedsGoodIp()
{
/* do stuff */
}

Resources