I want to add/remove IP restriction on run time to a MVC 5 project.
I did a search and found two ways.
Change Dynamic Ip Restriction module on runtime.
using System;
using System.Text;
using Microsoft.Web.Administration;
internal static class Sample
{
private static void Main()
{
using (ServerManager serverManager = new ServerManager())
{
Configuration config = serverManager.GetApplicationHostConfiguration();
ConfigurationSection ipSecuritySection = config.GetSection("system.webServer/security/ipSecurity", "Default Web Site");
ConfigurationElementCollection ipSecurityCollection = ipSecuritySection.GetCollection();
ConfigurationElement addElement = ipSecurityCollection.CreateElement("add");
addElement["ipAddress"] = #"192.168.100.1";
addElement["allowed"] = false;
ipSecurityCollection.Add(addElement);
ConfigurationElement addElement1 = ipSecurityCollection.CreateElement("add");
addElement1["ipAddress"] = #"169.254.0.0";
addElement1["subnetMask"] = #"255.255.0.0";
addElement1["allowed"] = false;
ipSecurityCollection.Add(addElement1);
serverManager.CommitChanges();
}
}
}
In this way, does serverManager.CommitChanges restart the IIS or application ?
Best way to implement request throttling in ASP.NET MVC?
I will use throttling for this purpose.
If the application or IIS hasn't been restarted, I would prefer first way because it's on IIS level.
Do you have any suggestion which one is the best or any other approaches ?
First way restarts the application. Second way is working on action level (objects are created already).
Therefore, I'm blocking/redirecting request on Begin_Request. I'm adding ips which I want to block to cache. Then I'm reading cache value on begin request if request ip is in blacklist I'm redirecting it to 404.html.
private void Application_BeginRequest(object sender, EventArgs e)
{
using (var mylifeTimeScope = IoCBootstrap.Container.BeginLifetimeScope())
{
var ipHelper = mylifeTimeScope.Resolve<IIpHelper>();
if (ipHelper.BlackListIp())
{
HttpContext.Current.Response.StatusCode = 404;
HttpContext.Current.Response.Redirect("404.html");
}
}
}
ipHelper.BlackListIp() checks ip is in blacklist or not.
Related
what is the best way to capture page views by person without slowing down performance on the site. I see that stackoverflow show page views all over the place. Are they doing an insert into a db everytime i click on a page?
In asp.net-mvc, Is there any recommended way to track page view per user (my site has a login screen) so i can review which pages people are going to and how often
First off.. if what you really care about is how are customers using my site then you most likely want to look into Google Analytics or a similar service.
But if you want a quick and dirty page view record and you are using ASP.Net MVC 3 then as Chris Fulstow mentioned you're going to want to use a mix of global action filters and caching. Here is an example.
PageViewAttribute.cs
public class PageViewAttribute : ActionFilterAttribute
{
private static readonly TimeSpan pageViewDumpToDatabaseTimeSpan = new TimeSpan(0, 0, 10);
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var calledMethod = string.Format("{0} -> {1}",
filterContext.ActionDescriptor.ControllerDescriptor.ControllerName,
filterContext.ActionDescriptor.ActionName);
var cacheKey = string.Format("PV-{0}", calledMethod);
var cachedResult = HttpRuntime.Cache[cacheKey];
if(cachedResult == null)
{
HttpRuntime.Cache.Insert(cacheKey, new PageViewValue(), null, DateTime.Now.Add(pageViewDumpToDatabaseTimeSpan) , Cache.NoSlidingExpiration, CacheItemPriority.Default,
onRemove);
}
else
{
var currentValue = (PageViewValue) cachedResult;
currentValue.Value++;
}
}
private static void onRemove(string key, object value, CacheItemRemovedReason reason)
{
if (!key.StartsWith("PV-"))
{
return;
}
// write out the value to the database
}
// Used to get around weird cache behavior with value types
public class PageViewValue
{
public PageViewValue()
{
Value = 1;
}
public int Value { get; set; }
}
}
And in your Global.asax.cs
public class MvcApplication : HttpApplication
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new PageViewAttribute());
}
}
For pre-ASP.Net MVC 3 ONLY you are going to have to apply the same attribute manually to all of your actions.
[PageView]
public ActionResult CallOne()
{
}
[PageView]
public ActionResult CallTwo()
{
}
The best way would probably be a global action filter that intercepts requests to all actions on all controllers, then increments a counter in the database for the current user and page. To save hitting the database too hard, you could cache these values and invalidate them every few minutes, depending on how much traffic you're dealing with.
We use the open source Piwik: http://piwik.org/, which is setup on it's own server. One line of Javascript in the _Layout page makes a call to Piwik after the page has loaded (put the JS at the end) and does not affect page load performance at all.
In addition to just counts, you'll get a ton of info about where your users are coming from, browser, screen resolutions, installed plugins. Plus you can track conversions and use the same tool to track marketing campaigns, etc.
<soapbox>
I cannot think of a situation where you'd be better off implementing this in MVC or in your web app in general. This stuff simply does not belong in your web app and is a meta-concern that should be separated out. This approach has enabled us to track analytics for all of our apps (32 of them: mvc 2/3, webforms, php...) in a unified manner.
If you really don't want to use another tool for this purpose, I would recommend tapping into your IIS log and getting your stats from there. Again, to get any real decision making power out of it, you'll need to put a good analyzer on it. I recommend Splunk: http://www.splunk.com/
</soapbox>
I wanted to post an updated version of Shane's answer for those who are interested. Some things to consider:
You have to set the action attribute up as a service when decorating your
methods using syntax like the following :
[ServiceFilter(typeof(PageViewAttribute))]
As far as I can tell, HttpRuntime.Cache.Insert isn't a thing in .NET Core, so I used a simple implementation of IMemoryCache (You may need to add this line to your startup.cs in order to use the interface):
services.AddMemoryCache();
Because we are injecting IMemoryCache into a class that is not a controller, we need to register our attribute as a service in startup.cs, like so:
services.AddScoped<[PageViewAttribute]>(); - without brackets!
Whatever object you return when creating a cacheKey will be assigned to the 'value' parameter of the OnRemove method.
Below is the code.
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var controllerActionDescriptor = filterContext.ActionDescriptor as ControllerActionDescriptor;
var arguments = filterContext.ActionArguments;
ActionId = arguments["id"].ToString();
var calledMethod = string.Format("{0} -> {1}",
controllerActionDescriptor.ControllerName,
controllerActionDescriptor.ActionName);
var cacheKey = string.Format("PV-{0}", calledMethod);
var cachedResult = _memoryCache.Get(cacheKey);
if (cachedResult == null)
{
//Get cacheKey if found, if not create cache key with following settings
_memoryCache.GetOrCreate(cacheKey, cacheKey =>
{
cacheKey.AbsoluteExpirationRelativeToNow
= pageViewDumpToDatabaseTimeSpan;
cacheKey.SetValue(1);
cacheKey.RegisterPostEvictionCallback(onRemove);
return cacheKey.Value;
});
}
else
{
_memoryCache.Get(cacheKey);
}
}
//Called when Memory entry is removed
private void onRemove(object key, object value, EvictionReason reason, object state)
{
if (!key.ToString().StartsWith("PV-"))
{
return;
}
// write out the value to the database
SaveToDataBase(key.ToString(), (int)value);
}
As a point of reference, this was done for a .NET Core 5 MVC App.
Regards.
I have a ASP.NET MVC website that uses Windows Authentication to control access. I would like to have a specflow selenium test that checks the configuration is correct by attempting to visit the site as a non-authorised user.
As we're using domain accounts to control access there isn't a username/password login screen. The credentials of the current user are automatically passed to the site by the browser.
So for my Selenium test I need to be able to run Internet Explorer as a specific user.
I have found a number of articles about windows impersonation and I can switch to my test user during the running of the test (using the code from http://support.microsoft.com/kb/306158). However if I then create an InternetExplorerDriver it starts internet explorer with my credentials rather than the test user's (although this question and answer suggests that it should work https://sqa.stackexchange.com/questions/2277/using-selenium-webdriver-with-windows-authentication).
I can also explicitly start an Internet Explorer process as my test user, but I can't see a way of binding an InternetExplorerDriver to an already running Internet Explorer process, so this may be a dead end.
My code, basically taken from the MSDN page above is below. In the debugger I can see that WindowsIdentity.GetCurrent().Name is "testUser" in all the steps of the test.
namespace MyProject.Specs
{
using NUnit.Framework;
using OpenQA.Selenium;
using OpenQA.Selenium.IE;
using System;
using System.Runtime.InteropServices;
using System.Security.Principal;
using TechTalk.SpecFlow;
[Binding]
public class AuthorisationSteps
{
public const int LOGON32_LOGON_INTERACTIVE = 2;
public const int LOGON32_PROVIDER_DEFAULT = 0;
private static WindowsImpersonationContext impersonationContext;
private static IWebDriver driver;
[BeforeScenario]
public static void impersonateUser()
{
if (!impersonateValidUser("testUser", "testDomain", "password"))
{
throw new Exception();
}
driver = new InternetExplorerDriver();
}
[AfterScenario]
public static void cleanupUser()
{
undoImpersonation();
driver.Quit();
}
[Given(#"I am an unauthorised user")]
public void GivenIAmAnUnauthorisedUser()
{
var temp = WindowsIdentity.GetCurrent().Name;
}
[When(#"I go to the home page")]
public void WhenIGoToTheHomePage()
{
var temp = WindowsIdentity.GetCurrent().Name;
driver.Navigate().GoToUrl(BaseUrl);
}
[Then(#"I should see an error page")]
public void ThenIShouldSeeAnErrorPage()
{
var temp = WindowsIdentity.GetCurrent().Name;
Assert.That(driver.Title.Contains("Error"));
}
[DllImport("advapi32.dll")]
public static extern int LogonUserA(String lpszUserName,
String lpszDomain,
String lpszPassword,
int dwLogonType,
int dwLogonProvider,
ref IntPtr phToken);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int DuplicateToken(IntPtr hToken,
int impersonationLevel,
ref IntPtr hNewToken);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool RevertToSelf();
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public static extern bool CloseHandle(IntPtr handle);
private static bool impersonateValidUser(String userName, String domain, String password)
{
WindowsIdentity tempWindowsIdentity;
var token = IntPtr.Zero;
var tokenDuplicate = IntPtr.Zero;
if (RevertToSelf())
{
if (LogonUserA(userName, domain, password, LOGON32_LOGON_INTERACTIVE,
LOGON32_PROVIDER_DEFAULT, ref token) != 0)
{
if (DuplicateToken(token, 2, ref tokenDuplicate) != 0)
{
tempWindowsIdentity = new WindowsIdentity(tokenDuplicate);
impersonationContext = tempWindowsIdentity.Impersonate();
if (impersonationContext != null)
{
CloseHandle(token);
CloseHandle(tokenDuplicate);
return true;
}
}
}
}
if (token != IntPtr.Zero)
{
CloseHandle(token);
}
if (tokenDuplicate != IntPtr.Zero)
{
CloseHandle(tokenDuplicate);
}
return false;
}
private static void undoImpersonation()
{
impersonationContext.Undo();
}
}
}
We have many enterprise clients that use Windows Authentication for intranet facing applications and we are starting to run many Selenium tests for confirmation, regression, etc.
We've taken the helpful code from Steven's answer and refactored it into a re-usable class similar to other Impersonate posts that just weren't working for us because we wanted the tests to work both locally in development and deployed as part of the Visual Studio Team System release process.
The uri method was not working locally and neither were impersonating methods using Win32 native methods.
This one worked so here it is.
Example of a test using Steven's code refactored into a helper
[TestMethod]
public void ThisApp_WhenAccessedByUnathorizedUser_ShouldDisallowAccess()
{
string userName = "ThisAppNoAccess";
string password = "123456";
string domainName = Environment.MachineName;
using (new Perkins.Impersonator(userName, domainName, password))
{
// - Use Remote Web Driver to hook up the browser driver instance launched manually.
using (var driver = new RemoteWebDriver(new Uri("http://localhost:9515"), DesiredCapabilities.Chrome()))
{
var desiredUri = Helper.Combine(Helper.BaseURL, "/ThisApp/#/appGrid");
TestContext.WriteLine("desiredUri: {0}", desiredUri);
driver.Navigate().GoToUrl(desiredUri);
Helper.WaitForAngular(driver);
var noPermissionNotificationElement = driver.FindElementByXPath("//div[#ng-show='!vm.authorized']/div/div/div/p");
var showsNoPermissionNotification = noPermissionNotificationElement.Text.Contains("You do not have permissions to view ThisApp.");
Assert.AreEqual(true, showsNoPermissionNotification, "The text `You do not have permissions to view ThisApp.` is not being displayed!");
}
}
}
The helper class
// Idea from http://stackoverflow.com/a/34406336/16008
// - Launch the browser driver manually with other user's credentials in background
public class Perkins
{
public class Impersonator : IDisposable
{
Process _driverProcess = null;
string _driverPath = #"chromedriver.exe";
/// <summary>
/// Impersonates the specified user account by launching the selenium server under that account. Connect to it via RemoteWebDriver and localhost on port 9515.
/// </summary>
/// <remarks>
/// We may later want to enhance this by allowing for different ports, etc.
/// </remarks>
/// <param name="userName">Name of the user</param>
/// <param name="domainName">Name of the domain or computer if using a local account.</param>
/// <param name="password">The password</param>
public Impersonator(string userName, string domainName, string password)
{
ProcessStartInfo processStartInfo = new ProcessStartInfo(_driverPath);
processStartInfo.UserName = userName;
System.Security.SecureString securePassword = new System.Security.SecureString();
foreach (char c in password)
{
securePassword.AppendChar(c);
}
processStartInfo.Password = securePassword;
processStartInfo.Domain = domainName; // this is important, mcollins was getting a 'stub received bad data' without it, even though rglos was not
processStartInfo.UseShellExecute = false;
processStartInfo.LoadUserProfile = true; // this seemed to be key, without this, I get Internal Server Error 500
Thread startThread = new Thread(() =>
{
_driverProcess = Process.Start(processStartInfo);
_driverProcess.WaitForExit();
})
{ IsBackground = true };
startThread.Start();
}
public void Dispose()
{
// - Remember to close/exit/terminate the driver process and browser instance when you are done.
if (_driverProcess != null)
{
// Free managed resources
if (!_driverProcess.HasExited)
{
_driverProcess.CloseMainWindow();
_driverProcess.WaitForExit(5000);
// Kill the process if the process still alive after the wait
if (!_driverProcess.HasExited)
{
_driverProcess.Kill();
}
_driverProcess.Close();
}
_driverProcess.Dispose();
_driverProcess = null;
}
}
}
}
Perhaps this will help someone else with the same issue.
This is in fact possible. I ran into the exact problem you had. Basically, here are the steps you need to do.
Launch the browser driver manually with other user's credentials in background
Process driverProcess;
string driverPath; // The path to Selenium's IE driver.
ProcessStartInfo info = new ProcessStartInfo(driverPath)
{
UserName = "UserName", // The user name.
Password = new SecureString(), // The password for the user.
UseShellExecute = false,
LoadUserProfile = true,
Arguments = "about:blank"
};
// Start the driver in background thread
Thread startThread = new Thread(
() => {
try
{
driverProcess = Process.Start(info);
driverProcess.WaitForExit();
}
catch
{
// Close the process.
}
})
{
IsBackground = true
};
startThread.Start();
Use Remote Web Driver to hook up the browser driver instance launched manually.
var remoteDriver = new RemoteWebDriver(Uri("http://localhost:5555"), DesiredCapabilities.InternetExplorer());
Remember to close/exit/terminate the driver process and browser instance when you are done.
// Close the process when done.
if (driverProcess != null)
{
// Free managed resources
if (!driverProcess.HasExited)
{
driverProcess.CloseMainWindow();
driverProcess.WaitForExit(5000);
// Kill the process if the process still alive after the wait
if (!driverProcess.HasExited)
{
driverProcess.Kill();
}
driverProcess.Close();
}
driverProcess.Dispose();
driverProcess = null;
}
This similar question links to this Microsoft support article. Essentially you need
System.Security.Principal.WindowsImpersonationContext impersonationContext;
impersonationContext =
((System.Security.Principal.WindowsIdentity)User.Identity).Impersonate();
IWebDriver webDriver = new InternetExplorerDriver();
// do your stuff here.
impersonationContext.Undo();
There's additional code in the support article about impersonating a specific user.
Do you have a couple of old PCs? Or the capacity for some virtual machines?
If so, build a Selenium Grid set-up, and configure one to automatically login as the desired domain user and one as a non-domain user.
http://code.google.com/p/selenium/wiki/Grid2
I was having same problem when I was doing automation project for web based application which required window authentication. However, I have achieved this with using firefox, following are the steps to achieve it.
FIREFOX SETUP
OPEN RUN DIALOG OF YOUR SYSTEM AND TYPE 'firefox.exe -p' (CLOSE YOUR FIREFOX BROWSER BEFORE RUNNING THIS COMMAND) http://www.wikihow.com/Create-a-Firefox-Profile
CLICK ON CREATE PROFILE AND GIVE A NAME AS REQURIED
SELECT CREATED PROFILE AND START BROWSER AND OPEN ADD-ONS MANAGER (TOOLS - ADD-ONS)
SEARCH FOR 'AutoAuth' AND INSTALL IT. IT WILL ASK FOR RESTART, DO IT
ONCE THE FIREFOX IS RESTARTED, THAN OPEN URL IT WILL ASK YOU FOR AUTHENTICATION
ENTER USERNAME AND PASSWORD - SUBMIT IT, FIREFOX WILL ASK YOU TO REMEMBER THE PASSWORD
CLICK ON REMEMBER AND IT WILL SAVE THE PASSWORD IN FIREFOX PROFILE
COPY CREATED FIREFOX PROFILE AND SAVE IT TO REQUIRED FOLDER
IN YOUR SELENIUM SCRIPT CALL ABOVE CREATED PROFILE WITH FIREFOX DRIVER AND PASS THE SAME URL, IT WILL NOT ASK FOR AUTHENTICATION DIALOG
This is working very successfully in my project.
We use https://stackoverflow.com/a/31540010/3489693 approach for IE and Chrome over 2 years. It works fine
So it seems the problem that the question is trying to circumvent has to do with NTLM Auto Login. See Google Chrome and NTLM Auto Login Using Windows Authentication
The solutions above did not work for me since the auto-login would successfully authenticate with any user on my system, so it didn't matter which user I used for impersonation.
However, I noticed that you can outsmart auto-login by replacing localhost with any other domain name, such as the local IP address. No impersonation required :)
This may / may not work.
Try to launch your site in "CHROME".
Hit F-12, go to Application Tab -> Cookies -> Click on your site link. on left hand side look for something that represent your session id, may be JSESSIONID or similar that represents user's session, copy that.
Now open your Internet Explorer,
hit F-12 and manually create that JSESSIONID ( or similar key ) by running this command in console window
document.cookie = "JSESSIONID=your-session-id-from-chrome"
hit play button to execute script
Refresh your browser
I have a web site running Burrow, and I'd like to use it for Quartz jobs as well.
The thing is that I want them to not share any state. The Quartz jobs is running in each own threads while the Mvc framework closes the Workspace at the end of every request.
Ideally, mvc should have it's own session, and each job should have it's own session.
What are my possibilities here?
PS: I'm very new to Quartz, Burrow and MVC btw, so I'm probably missing some very essential knowledge :|
I tried a simple naive way that at least seem to work for now. Is there something fundamentally wrong I'm doing here? Will these variables be garbage collected when a thread exits?
public static class SessionManager
{
[ThreadStatic]
private static IDictionary<ISessionFactory, ISession> _sessions;
public static ISession GetSession(Type type)
{
var burrow = new BurrowFramework();
if (burrow.WorkSpaceIsReady)
{
return burrow.GetSession(type);
}
else
{
if (_sessions == null)
{
_sessions = new Dictionary<ISessionFactory, ISession>();
}
var factory = burrow.GetSessionFactory(type);
if (!_sessions.ContainsKey(factory))
{
_sessions[factory] = null;
}
var session = _sessions[factory];
if (session == null || !session.IsOpen)
{
session = _sessions[factory] = factory.OpenSession();
}
return session;
}
}
}
I'm looking at using ELMAH for the first time but have a requirement that needs to be met that I'm not sure how to go about achieving...
Basically, I am going to configure ELMAH to work under asp.net MVC and get it to log errors to the database when they occur. On top of this I be using customErrors to direct the user to a friendly message page when an error occurs. Fairly standard stuff...
The requirement is that on this custom error page I have a form which enables to user to provide extra information if they wish. Now the problem arises due to the fact that at this point the error is already logged and I need to associate the loged error with the users feedback.
Normally, if I was using my own custom implementation, after I log the error I would pass through the ID of the error to the custom error page so that an association can be made. But because of the way that ELMAH works, I don't think the same is quite possible.
Hence I was wondering how people thought that one might go about doing this....
Cheers
UPDATE:
My solution to the problem is as follows:
public class UserCurrentConextUsingWebContext : IUserCurrentConext
{
private const string _StoredExceptionName = "System.StoredException.";
private const string _StoredExceptionIdName = "System.StoredExceptionId.";
public virtual string UniqueAddress
{
get { return HttpContext.Current.Request.UserHostAddress; }
}
public Exception StoredException
{
get { return HttpContext.Current.Application[_StoredExceptionName + this.UniqueAddress] as Exception; }
set { HttpContext.Current.Application[_StoredExceptionName + this.UniqueAddress] = value; }
}
public string StoredExceptionId
{
get { return HttpContext.Current.Application[_StoredExceptionIdName + this.UniqueAddress] as string; }
set { HttpContext.Current.Application[_StoredExceptionIdName + this.UniqueAddress] = value; }
}
}
Then when the error occurs, I have something like this in my Global.asax:
public void ErrorLog_Logged(object sender, ErrorLoggedEventArgs args)
{
var item = new UserCurrentConextUsingWebContext();
item.StoredException = args.Entry.Error.Exception;
item.StoredExceptionId = args.Entry.Id;
}
Then where ever you are later you can pull out the details by
var item = new UserCurrentConextUsingWebContext();
var error = item.StoredException;
var errorId = item.StoredExceptionId;
item.StoredException = null;
item.StoredExceptionId = null;
Note this isn't 100% perfect as its possible for the same IP to have multiple requests to have errors at the same time. But the likely hood of that happening is remote. And this solution is independent of the session, which in our case is important, also some errors can cause sessions to be terminated, etc. Hence why this approach has worked nicely for us.
The ErrorLogModule in ELMAH (version 1.1 as of this writing) provides a Logged event that you can handle in Global.asax and which you can use to communicate details, say via HttpContext.Items collection, to your custom error page. If you registered the ErrorLogModule under the name ErrorLog in web.config then your event handler in Global.asax will look like this:
void ErrorLog_Logged(object sender, ErrorLoggedEventArgs args)
{
var id = args.Entry.Id
// ...
}
Our organization has a central solution for forms authentication. I am trying to implement an ASP.Net MVC app that uses this external URL - and it worked till RC! was released...
Here's what's happening
In an ActionAttribute Extension
I check for s session var
if not found
check for a request data chuck
if found, set the session var
if not found - redirect to external URL
if found
continue.
The trouble is that till I updated to RC1, this worked. Since then, so many requests are being sent to the external URL that it detects a DoS attack and shuts me out!
I removed the redirection code and replaced it with the web.config changes for Forms Auth - and the same thing happened...
Why not use Microsoft Geneva instead of attempting to roll your own authentication provider?
CODE:
public class MyAuthenticate : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.HttpContext.Session["user"] == null)
{
using (Authenticator dp = new Authenticator())
{
MyUser mu;
string data = string.Empty;
try
{
data = filterContext.HttpContext.Request["Data"];
}
catch { };
if (!string.IsNullOrEmpty(data))
{
mu = dp.Redeem(data);
if (mu.authenticated)
{
filterContext.HttpContext.Session.Clear();
AuthenticatedUser user = new AuthenticatedUser(mu);
filterContext.HttpContext.Session.Add("user", user);
FormsAuthentication.SetAuthCookie(user.UserId, false);
}
else
{
filterContext.HttpContext.Response.Redirect("MY EXTERNAL URL GOES HERE!!");
}
}
else
{
filterContext.HttpContext.Response.Redirect("MY EXTERNAL URL GOES HERE!!");
}
}
}
base.OnActionExecuting(filterContext);
}
}
}
I resolved this issue by creating a static dictionary of requesting IPs, and dropping duplicate requests from the same IP. Not a very nice solution - so if anyone figures out a better solution - let me know.