Web requests becoming very slow when DNS server unreachable - c#

QA team discovered that when the webserver (serving embedded device UI) runs on a system where the primary DNS server is unreachable, the UI becomes unbearably unresponsive - taking some 16s for each action.
First I figured that serving static pages did not suffer from this problem.
Excluding my first suspect - logging - I have finally pinpointed the cause to Intelligencia.UrlRewriter (nuget package ver. 2.0.0.9). It appears that on each request, the rewriter makes a copy of all server variables, which includes "REMOTE_HOST". This is a dynamic variable and is only run when needed. This in turn involves a call to HttpRequest.UserHostName.
Question is simple: how to avoid reverse address lookups in web applications that use the UrlRewriter?
I offer a big hammer answer myself, but would like to hear other options.

I have added another class library project with a simple http module:
public sealed class NoRemoteHostLookup : IHttpModule
{
public void Dispose()
{
}
public void Init(HttpApplication context)
{
context.BeginRequest += ContextOnBeginRequest;
}
private void ContextOnBeginRequest(object sender, EventArgs eventArgs)
{
var request = HttpContext.Current?.Request;
if (request != null)
request.ServerVariables["REMOTE_HOST"] = request.ServerVariables["REMOTE_ADDR"];
}
}
This is then referenced by the web app project and configured in web.config before the UrlRewriter:
<system.web>
<httpModules>
<add type="NoRemoteHostLookupModule.NoRemoteHostLookup, NoRemoteHostLookupModule" name="NoRemoteHostLookup" />
<add type="Intelligencia.UrlRewriter.RewriterHttpModule, Intelligencia.UrlRewriter" name="UrlRewriter" />
</httpModules>
</system.web>
Once the "REMOTE_HOST" variable is set, the framework refrains from resolving it later on when accessed by UrlRewriter.
Well this is a theory. It works in my Visual Studio debugging session, but not on our mono target framework where I get a System.NotSupportedException.
I made a bug report https://github.com/sethyates/urlrewriter/issues/9
Meanwhile I transferred the rewrite rules to the nginx configuration and stripped away UrlRewriter in production.

Related

While working on IIS System.Diagnostic.Trace.CorrelationManager.ActivityId is generated empty

we are using System.Diagnostic.Trace.CorrelationManager class to accomplish end to end tracing.The class has a guid property "ActivityId" which is generated by default on every request.
Our tracing is working well on iis express but after deploy project on iis System.Diagnostic.Trace.CorrelationManager.ActivityId is not generated.
IIS Express will probably have a module configured which will be setting the ActivityId on the CorrelationManager. I believe a number of modules do this, one example is FailedRequestsTracingModule (you don't actually need be monitoring failed requests so may have less of a performance overhead) and many APM tools like Stackify will do the same.
If Trace.CorrelationManager.ActivityId is empty you could ensure IIS is configured to use a module that sets it or alternatively you can create a simple one like:
public class SetActivityIdModule : IHttpModule {
public void Init(HttpApplication context) {
context.BeginRequest += (sender, args) =>
{
if (Trace.CorrelationManager.ActivityId == Guid.Empty) Trace.CorrelationManager.ActivityId = Guid.NewGuid();
};
}
public void Dispose() {}
}
The solutions i came up with enabling request tracing module on iis but that leads too much overload on server so i decided to generate it by my self instead of trusting on iis

ASP.Net MVC/Web API Custom Dynamic Bundle Working in Production but not Dev

In my development environment, I'm receiving 404 responses when I try to load a dynamically generated bundle. Strangely, this isn't occurring in our production environment, so it's not really catastrophic, but it makes development a bit frustrating.
What should happen is:
At application pre start (WebActivator PreStart), set up Dependency Resolver, Web API and MVC config stuff.
At application post start (WebActivator PostStart), load a service via dependency resolver (let's say IMyService).
Instantiate custom IBundleTransform (JsonBundleTransform - see below for code).
Call IEnumerable<string> IMyService.ListSupportedGroups().
Loop through supported groups and build custom bundle (CustomBundle - see below...).
Add custom bundles to BundleTable.Bundles.
Add static JS/CSS files to BundleTable.Bundles.
Reference various bundles in pages and see content in browser.
It fails at step 8 for the custom bundles with 404 errors. When debugging, the custom bundle transformer is never called. As I mentioned above, however, everything works fine in production - though I've compared the config files and can't see anything missing from my dev configs which should have any effect. Also, in production, the bundled content is rendered correctly regardless of the value for compilation->debug in the web.config.
Other notes:
I'm using IIS 7.5 in production, IIS 8 in development.
When I set debug="false" in dev, i get 404's on all of my bundles.
This was working but stopped at some point and I can't identify when or why that happened.
The code I'm using is as follows (redundant code removed, names changed to protected the innocent, etc...):
The Transform
public class JsonBundleTransform: IBundleTransform
{
public void Process( BundleContext context, BundleResponse response )
{
var bundle = context.BundleCollection.FirstOrDefault(b => b.Path == context.BundleVirtualPath) as CustomBundle;
response.Content = string.Format( ";var obj = {0};", JsonConvert.SerializeObject( bundle.KeyValues ) );
response.ContentType = "application/javascript";
response.Cacheability = HttpCacheability.Server;
}
}
The Custom Bundle
public class CustomBundle: Bundle
{
public CustomBundle( string virtualPath, IBundleTransform transform, IMyService myService, string groupId ) : base( virtualPath, transform )
{
keyValues = myService.GetKeyValuesByGroupId( groupId );
}
public IDictionary<string, string> KeyValues { get; private set; }
}
The Config
public class BundleConfig
{
public static void RegisterBundles( BundleCollection bundles, IMyService myService )
{
var transform = new JsonBundleTransform();
var jsonBundles = myService
.ListSupportedGroups()
.Select( groupId =>
new CustomBundle(
string.Format( "~/resource/script/keyValues-{0}.js", groupId ),
transform,
myService,
groupId
)
);
foreach ( var jsonBundle in jsonBundles ) {
bundles.Add( jsonBundle );
}
// Static bundles added here...
}
}
Rendering the Script
#Scripts.Render( Url.Content( string.Format( "~/resource/script/keyValues-{0}.js", Model.GroupId ) ) ) )
Any idea what I'm missing here to get this working?
Thanks in advance for any help or advice offered.
Edit
Given that it's working in production, I'm leaning strongly towards thinking that the problem is an environmental problem and not a code issue - though, for the life of me, I can't figure out what it is yet. I think the most likely candidate is something do with configuration or references (IIS versions seem an unlikely cause, to be honest).
If you append ".js" to the virtual path of your bundles, IIS will interpret any request for those bundles as a request for a static file, unless:
You have instructed IIS to run all managed modules for all requests
You have explicitly configured IIS to process your bundle urls using the routing handler
If you compare the production and dev configurations, I suspect you'll find that the production environment is has "runAllManagedModulesForAllRequests" set to "true".
The easiest fix for this issue is simply to remove the ".js" extension from the virtual paths of your bundles. If you prefer to keep the extension, you can configure IIS to pass the requests to the routing handler:
<system.webServer>
<handlers>
<add name="UrlRoutingHandler"
type="System.Web.Routing.UrlRoutingHandler,
System.Web, Version=4.0.0.0,
Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a"
path="/resource/script/*"
verb="GET"/>
</handlers>
</system.webServer>
You could also leave "runAllManagedModulesForAllRequests" enabled, but that has performance concerns, as there is no reason to run static files through the entire managed pipeline.

Can you change the ConnectionString configuration value at runtime? Or... do I even need to?

First post, I'm a complete .Net/C# novice thrown in at the deep end!
I've inherited a C# wed application due to someone leaving at work and me being the only one with bandwidth! But not the .Net, C# knowledge!
The app is used by people on different sites all over the world. They log in using the corporate login details and as such they log into different servers depending on where they are located, (Europe, America or India).
The guy who wrote the app couldn't work out how to switch the ConnectionString in web.config depending on location, so duplicated the whole app for each domain! With the only variation being a single IP address in web.config for each duplicated version of the app! Then did a simple web front page which took the user to "their" version of the app depending on where they said they were in the world!
The first thing I want to do is to move to a single version to maintain, so I need to be able to switch the connection string or how to login?
I've spent several days trying to work out how I get to ConnectionString (defined in web.config) from my Login class, only to discover the values set in web.config seem to be read only, so I can't alter them.
So I guess the first question is, am I barking up the wrong tree? Can I just set all the information that AspNetActiveDirectoryMembershipProvider (see code later) requires and call it from my login class? Or is the ConnectionString route the Ipso facto way to set up connections in .Net/C#? So therefor I do need to find out how to change/specify/add the value at runtime.
Three possibilities I can think of:- (The first is the one I've ground to a hult with)
Change the ConnectionString for ADService in my web.config from my Login class?
Change what AspNetActiveDirectoryMembershipProvider uses, so from my Login class magicly get it to use EMEA_ADService or PACIFIC_ADService as defined in web.config?
Is it possible to define a new connectionString and call AspNetActiveDirectoryMembershipProvider all from my Login class, not using web.config for this connection at all?
Here's a bit of my/his web.config file and my Login class
Cuttings from Web.config
<connectionStrings>
<add name="ADService" connectionString="LDAP://12.345.67.8" /> *---- Original ConnectionString (IP address changed)----*
<add name="EMEA_ADService" connectionString="LDAP://12.345.67.8" /> *---- Added by me playing around unsuccessfully! ----*
<add name="PACIFIC_ADService" connectionString="LDAP://12.345.67.9" /> *---- Added by me playing around unsuccessfully! ----*
~
</connectionStrings>
<authentication mode="Forms">
<forms loginUrl="~/Login.aspx" timeout="2880" /> *---- The background class for this popup (Login.aspx.cs) is where I'm currently trying to affect ConnectionString----*
</authentication>
*---- Pretty sure this is the bit that actually does the login verification----*
<membership defaultProvider="AspNetActiveDirectoryMembershipProvider">
<providers>
<clear />
<add name="AspNetActiveDirectoryMembershipProvider" type="System.Web.Security.ActiveDirectoryMembershipProvider, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=12345678" connectionStringName="ADService" applicationName="/." description="ADService" />
</providers>
</membership>
This is as far as I've got in my class before finding out that I don't appear to be able to alter ConnectionString!
Cuttings from Login.aspx.cs
public partial class Login : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
ConnectionStringSettingsCollection connections = ConfigurationManager.ConnectionStrings; //this is now working :)
string userDomain = Environment.UserDomainName; //Probably don't need this, it seems to give the login domain on the machine. Don't know yet if that will be the users machine or the server the app runs on?
if (connections.Count != 0)
{
foreach (ConnectionStringSettings connection in connections)
{
string testname = connections["ADService"].Name;
string testConnectionString = connections["ADService"].ConnectionString;
connections["ADService"].ConnectionString = "LDAP://12.345.67.9";
testConnectionString = connections["ADService"].ConnectionString;
Any hint would be very welcome!
P.S. I have requested a .Net/C# course at work! ;)
You wouldn't want to alter the existing connection string. Rather, you'd want to alter which connection string your Data Access Layer was using to call different service stacks. You could then choose a connection string at runtime based on whatever input parameters you wanted to use. which in your case might be an IP range.
asp.net mvc multiple connection strings
Handling multiple connection strings in ONE DataAccess Layer
http://msdn.microsoft.com/en-us/library/aa479086.aspx
The microsoft article is particularly interesting since it actually takes an architectural look at proper patterns for resolving dilemmas like yours. I think you got stuck with the short end of the stick! Best of luck!
The Web.config cannot be modified at Runtime. I would suggest setting some kind of flag via a login link or combobox on the website for people to use to choose where they want to login. It is not the job of the server to figure out what a user wants to do.

Querystring without argument name

I am trying to get this to work. I have a DNN module in which I read from a querystring and perform a few steps. All of that is working fine. Now I am trying to clean up the URL while reading the querystring
Right now, the URL looks something like this:
http://mysite.website.com/?pid=1234
I would like it to look like:
http://mysite.website.com/1234
Is something like this even possible?
You are much better to use a proper rewriting solution for DotNetNuke (e.g. iFinity UrlMaster and there are others...).
You can then write a custom url provider for your module.
That's what I've done on my site to rewrite parts of my articles module (e.g. www.ventrian.com/blog/
You can find more information about urlmaster here:
http://www.ifinity.com.au/Products/Url_Master_DNN_SEO_Urls
look at using a URL Rewriter module. There are several third party ones for IIS6, but Microsoft provides one for IIS7 and IIS7.5. You basically configure it with a regular expression and change the output.
Microsoft's rewrite module for IIS7 is available at: http://www.iis.net/downloads/microsoft/url-rewrite
You've got a couple of choices:
Explore the rewrite capabilities available in DNN and how to use them. They can be found in Host Settings > Advanced Settings > Friendly URL Settings. Or use the 2nd option based on which version of IIS you're working on.
2a. URL Rewrite Module for IIS 7 & above
2b. "ISAPI_Rewrite 3" by HeliconTech (has free version too, that does the job pretty well)
You can accomplish what you are looking for without interacting with DNN at all by using an HttpModule. Kind of like this:
public class PidRewriteModule : System.Web.IHttpModule
{
public void Dispose()
{
}
public void Init(System.Web.HttpApplication context)
{
context.BeginRequest += new EventHandler(context_BeginRequest);
}
void context_BeginRequest(object sender, EventArgs e)
{
HttpApplication app = sender as HttpApplication;
if (app != null)
{
Match mPidCheck = new Regex(#"^/(?<pid>[0-9]+)/?$").Match(app.Context.Request.Url.AbsolutePath);
if (mPidCheck.Success)
{
app.Context.RewritePath("~/default.aspx", String.Empty, String.Concat("pid=", mPidCheck.Groups["pid"].Value));
}
}
else
return;
}
}
Then you can add this to your Web.config:
<modules runAllManagedModulesForAllRequests="true">
<add name="PidRewriteModule" type="Assembly.Namespace.PidRewriteModule, Assembly"/>
</modules>
Put that in the system.webServer node. Substitute Assembly and Namespace respectively.
All of this info is for IIS7. It's not entirely different for IIS 6, but previous implementations you have to go the route of ISAPI filters.

How to implement URL rewriting with Windows Azure?

I have an ASP.NET / C# website that's hosted on Windows Azure. The site is a predictions-based social site with a feed of prediction summaries on the main page. If you click on a summary, you're redirected to the details page for that prediction using a simple QueryString.
For example:
http://www.ipredikt.com/details.aspx?id=14
This particular prediction is entitled "Paris Hilton will win the Nobel Peace Prize" so what I'd like to do is implement URL rewriting for my site on Azure as follows:
http://www.ipredikt.com/predictions/14/paris-hilton-will-win-the-nobel-peace-prize
What are some strategies and best practices for doing this? And can someone point me to a good Azure-specific article or two.
The hyphenated title ("paris-hilton-bla-bla") is really just to make the URL more human readable; I don't envision relying on it at all in terms of loading pages. In fact, I'd probably allow duplicate titles since I'll be relying on the prediction ID in the URL.
EDIT:
Forgot to mention that we are NOT based on MVC. We came up w/ our own architecture that uses PageMethods and WebMethods to return JSON to the client. We rely on ASP.NET AJAX to do all of the JSON serialization, and almost all of our UI is built dynamically on the client using jQuery.
EDIT: SOLUTION
Thought I'd share my solution now that I have things up and running.
I made a new class as follows (copied verbatim from somewhere):
public class WebFormRouteHandler<T> : IRouteHandler where T : IHttpHandler, new()
{
public string VirtualPath { get; set; }
public WebFormRouteHandler(string virtualPath)
{
this.VirtualPath = virtualPath;
}
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
return (VirtualPath != null)
? (IHttpHandler)BuildManager.CreateInstanceFromVirtualPath(VirtualPath, typeof(T))
: new T();
}
}
I added the following method to Global.asax. The actual method is MUCH, much longer (it covers every page in the site). You'll see that I support calling the predictions page in lots of different ways: with an id, with an id + title, etc. (The "...fb" versions of pages are for the Facebook app version of my site which use a different MasterPage.)
public static void RegisterRoutes(RouteCollection routes)
{
// Details : 'predictions' Page
var routeHandlerDetails = new WebFormRouteHandler<Page>("~/details.aspx");
var routeHandlerDetailsFb = new WebFormRouteHandler<Page>("~/detailsfb.aspx");
routes.Add(new Route("predictions/{id}", routeHandlerDetails));
routes.Add(new Route("predictions/{id}/{title}", routeHandlerDetails));
routes.Add(new Route("fb/predictions/{id}", routeHandlerDetailsFb));
routes.Add(new Route("fb/predictions/{id}/{title}", routeHandlerDetailsFb));
}
...and this method is called from Application_Start()
void Application_Start(object sender, EventArgs e)
{
RegisterRoutes(RouteTable.Routes);
}
Then I added the following to web.config in the system.webServer block:
<!-- Added for URL Routing -->
<modules runAllManagedModulesForAllRequests="true">
<add name="UrlRoutingModule"
type="System.Web.Routing.UrlRoutingModule, System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
</modules>
<!-- Added for URL Routing -->
<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>
I also had to exclude the virtual "predictions" directory from authentication (because almost all parts of our site are accessible my non-auth users):
<!-- Url routing -->
<location path="predictions">
<system.web>
<authorization>
<allow users="*" />
</authorization>
</system.web>
</location>
Finally, I no longer rely on QueryString string parameters when loading pages, so I had to write some new helper methods. Here's one that extracts a numerical value from the new routing URL (I'll be cleaning this up to only have a single 'return'.):
public static int GetRouteDataValueAsNumber(HttpRequest request, string propertyName)
{
if ((request == null) ||
(request.RequestContext == null) ||
(request.RequestContext.RouteData == null) ||
(request.RequestContext.RouteData.Values[propertyName] == null))
{
return -1;
}
try
{
return System.Convert.ToInt32(request.RequestContext.RouteData.Values[propertyName]);
}
catch
{
}
return -1;
}
Now when I need to read a routing value (like a prediction ID), I do the following:
long _predictionId = System.Convert.ToInt64(WebAppUtils.GetRouteDataValueAsNumber(Request, "id"));
Works great! Now my site feels like an MVC app with friendly and self-documenting URLs.
Oh, last thing, you also need to enable HTTP Redirection as follows:
Start => Control Panel => Program => Turns Windows Features On => Internet Information Services => World Wide Web Services => Common HTTP Features => (select checkbox for) HTTP Redirection.
The easiest way to implement this would be a programmatic approach using the System.Web.Routing assembly.
This basically works by including the UrlRoutingModule in your web.config, and defining patterns that resolve the target page based on matching routes. If you are familiar with ASP.NET MVC, then you have used this routing strategy before, but MVC is not necessary to use Routing.
Here are some resources to help you get started:
MSDN Documentation for the System.Web.Routing namespace - official documentation
Scott Gu on URL Routing for MVC - * Note that this article explains routing in the context of an ASP.NET MVC application, however, the same methodology will work regardless of whether or not you are using MVC
ASP.NET Routing... Goodbye URL rewriting, by Chris Cavanagh - An explanatory article
Exploring System.Web.Routing, by Justin Etheredge - A case study explaining how to use routing independently of the MVC architecture
About Windows Azure ...
If you take this approach, it doesn't really matter that you are using Windows Azure. However, I found an article by Michael Kennedy called ASP.NET Routing in Windows Azure Using WebForms, explaining how to easily deploy such a solution on Windows Azure. The article even has a sample project for download.
Azure web roles have the IIS7 Url Rewriting module installed - http://msdn.microsoft.com/en-us/library/dd573358.aspx
The "how to" for this module is at http://learn.iis.net/page.aspx/460/using-the-url-rewrite-module/
For your Paris example, you basically need to setup a rule that maps the url
http://www.ipredikt.com/predictions/14/paris-hilton-will-win-the-nobel-peace-prize
to
http://www.ipredikt.com/details.aspx?id=14
This is something like:
Pattern -
^predictions/([0-9]+)/([_0-9a-z-]+)
Action -
details.aspx?id={R:1}
For more on defining these rules see http://learn.iis.net/page.aspx/461/creating-rewrite-rules-for-the-url-rewrite-module/

Categories

Resources