This tutorial demonstrates how to block IPs in ASP.NET using C#.

This is a five step process (some screen shots at the end).

We will be using a database to store the blacklist so that we don't destroy our web.config file.  This technique pulls your IP blacklist from a database and caches it, so that it's available to all sessions.  This is the best approach for websites that don't have a massive blacklist because it reduces the number of times you have to access your database.  At the beginning of each user's session we will use LINQ to check the user's IP against our cached blacklist.

STEP 1.  Create a table of IP addresses.  It can have as many columns as you like but the 'key' column should be "IP."
STEP 2.  Create a stored procedure that calls this SQL statement:  SELECT * FROM IPblackList
              IPblackList is the name of my table....change it if you call your table something else.  If you need help with
              this database task, respond to this thread and I will help you.
STEP 3.  Add the following class to your application.  I recommend placing the code in a separate library (.dll file) so
             that you can reuse it.  Another option is to simply add the code to your APP_Code folder
             (or your root directory if you built a Visual Studio "Project" instead of "Website"). 

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Web;
using System.Web.Configuration;

namespace AnyName
     //  This is a "Data Access" class so it's named  XxxxAccess.  This is C# so start class names with upper case
     public class LogAccess 
            //  This will hold our connection string after we fetch it from the web.config file
            private string connectionString;

            // This is the default constructor.  It only contains one line of code that fetches our connection string.
            public LogAccess()
// This references a connection string that is stored in my web.config file. The
                  // connection string looks like this in my web.config:
                  // <configuration>
                  //   <connectionStrings>
                  //      <add name="Ax" connectionString="Data Source=localhost\sqlexpress;Initial Catalog=MyDatabase;
                  //  User Id=YouID;Password=YourPW" />  Connection string must be on one line. Don't wrap like this. 
                  //    </connectionStrings>
                  //  </configuration>

                 connectionString = WebConfigurationManager.ConnectionStrings["Ax"].ConnectionString;

// Provide this Data Access class with a non-default constructor that accepts an alternate connection string. 
            // Optional, but a good practice.

            public LogAccess(string connectionString)
                    this.connectionString = connectionString;

            // This method retrieves your IP blacklist as a dataset by calling the stored procedure created in step# 2.
            public DataSet GetIPblackList()
                   SqlConnection con = new SqlConnection(connectionString);
                   SqlCommand cmd = new SqlCommand("GetIPblackList", con);
                   cmd.CommandType = CommandType.StoredProcedure;
                   SqlDataAdapter da = new SqlDataAdapter(cmd);

                   DataSet ds = new DataSet();
                       da.Fill(ds, "Blacklist");
                   return ds;

// This next method accepts an IP (string) and uses LINQ to check it against our cached blacklist (dataset). 
            // It returns the number of IPs that match.  That number should always be 0 or 1 because you can't have
            // duplicates of a key field (column).

            public bool IsIpValid(string IP)
// Note that when we use LINQ to query a dataset, it does NOT ignore blank spaces.  For example, I store
                   // my IPs in fields (as strings) that are long enough to hold either IP v6 or IP v4 addresses.  This means that
                   // every time I pull an IP v4 address, I also get a lot of blank spaces.  This is why I used Trim() below.
                   // Even if you only use IP v4, the length of the IP address varies a bit so be careful. 
                   // Again, note that "IP" represents the key field (column) in my table.

                   string ip = IP.Trim();
                   DataTable ips = MyCache.CacheDataSet.Tables["Blacklist"]; 

                   var query = from r in ips.AsEnumerable().AsParallel()      // See SoonerSoftware's comment
                                      where r.Field<string>("IP").Trim() == ip
                                      select r;

                   int count = query.Count();

                   if (count == 1)
                       return false;
                       return true
      }  // End LogAccess Class
} // End namespace

STEP 4.  Add a Global.asax file to your website/project if you do not already have one.  If you put the code above into
              your App_Code folder or root directory like I stated then you don't need to reference it.  If you placed the
              code into a separate Data Access library (best practice), then you need add a reference to the library in
              your reference folder and a using statement at the top of the Global.asax file that brings
              the namespace into scope.  In the Global.asax file you are going to add the following code to the
              Application_Start event handler like this:

        void Application_Start(object sender, EventArgs e)
// This initializes our static variables (pulls the blacklist from the database and stores it in memory).
            // To fully understand what I have done here you will have to see my custom cache and session objects below.

*TIP:  Always wrap your cache and session variables.  It ensures type safety, reduces errors, and saves a lot of typing.  When using cache, we always have to check for a null value first but with this technique there is no need to code that check over and over again.  If you catch yourself repeatedly typing the same code then I promise you there is a better way. Here is how your cache and session objects would look for this tutorial.  Again, we would place this class in our App_Code folder.

    public class MyCache
            // private constructor
            private MyCache()
                    LogAccess la = new LogAccess();
                    CacheDataSet = la.GetIPblackList();

            // Gets the cache object
            public static MyCache Current
                            MyCache cache =
                                HttpContext.Current.Cache["Cache"] as MyCache;
                            if (cache == null)
                                cache = new MyCache();
                                HttpContext.Current.Cache.Insert("Cache", cache, null, DateTime.MaxValue, TimeSpan.FromMinutes(45));
                    return cache;

            public static DataSet CacheDataSet { get; set; }

            public static void LoadStaticCache()
                     MyCache cache =
                         HttpContext.Current.Cache["Cache"] as MyCache;
                     if (cache == null)
                         cache = new MyCache();
                         HttpContext.Current.Cache.Insert("Cache", cache, null, DateTime.MaxValue, TimeSpan.FromMinutes(45));

STEP 5.  Error Handling.  I left out the error handling statements to make this easier to read and because you probably don't handle your errors like we do.  Test your code to ensure that it behaves like you want it to.  Simulate the DB server being down by messing up the connection string and see what happens. Add the appropriate error handling to make it behave the way you want it to under every condition.   

***********************  DONE! ****************************

*NOTE: Always use custom error pages but don't include anything on your custom 403 that tells the pinhead their IP is blacklisted.  Otherwise, they will grab another IP and keep bothering you.

*NOTE:  Most residential users get served a new IP address every couple days (DHCP).  Monitor your block list and only keep repeat offender IPs blacklisted for a long time.  Otherwise, eventually your list will get so long that innocent users may randomly get assigned one of your blacklisted IPs (slim chance but it can happen).