There is a simple way to access a hidden "God Mode" in Windows 7 and Vista. With a name like that, your expectations might be a little high -- and no, Windows is not secretly invincible -- but the trick is awesome nevertheless
"God Mode" simply provides users with a centralized Control Panel for all of Windows' settings, from changing your desktop background to setting up a VPN or partitioning your hard drive. In all, there are nearly 50 categories and 279 items and most have several entries
It's almost comical how simple it is to access it
1. Create a new folder. Anywhere is fine, I created one on my Desktop.
2. Rename the folder to: God Mode.{ED7BA470-8E54-465E-825C-99712043E01C} *Note: The "God Mode" chunk can be called anything you want.
3. The default folder icon will change to a Control Panel icon, and you can open it to view all of the settings.
User reports suggest that it may crash Windows Vista 64-bit, so proceed with caution. For what it's worth, I've successfully used the "feature" on Windows 7 Home Premium and Ultimate 64-bit.
Thursday, May 19, 2011
Email Validation in c# Application
private void TextBox1_Validating(object sender, CancelEventArgs e)
{
System.Text.RegularExpressions.Regex rEmail = new System.Text.RegularExpressions.Regex(@"^[a-zA-Z][\w\.-]*[a-zA-Z0-9]@[a-zA-Z0-9][\w\.-]*[a-zA-Z0-9]\.[a-zA-Z][a-zA-Z\.]*[a-zA-Z]$");
if (txtEmail.Text.Length > 0)
{
if(!rEmail.IsMatch(txtEmail.Text))
{
MessageBox.Show("Please provide valid email address", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
txtEmail.SelectAll();
}
}
}
{
System.Text.RegularExpressions.Regex rEmail = new System.Text.RegularExpressions.Regex(@"^[a-zA-Z][\w\.-]*[a-zA-Z0-9]@[a-zA-Z0-9][\w\.-]*[a-zA-Z0-9]\.[a-zA-Z][a-zA-Z\.]*[a-zA-Z]$");
if (txtEmail.Text.Length > 0)
{
if(!rEmail.IsMatch(txtEmail.Text))
{
MessageBox.Show("Please provide valid email address", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
txtEmail.SelectAll();
}
}
}
Get IP Address in C#
Using .NET 1.1 and 2.0 to retrieve the IP address
Well, in .NET 1.1 and 2.0 there are methods in the System.Net namespace that do that. Speaking of System.Net, don't forget to include the using statement, to avoid the long lines I have below:
using System.Net;
// Get the hostname
string myHost = System.Net.Dns.GetHostName();
// Get the IP from the host name
string myIP = System.Net.Dns.GetHostByName(myHost).AddressList[0].ToString();
Well, in .NET 1.1 and 2.0 there are methods in the System.Net namespace that do that. Speaking of System.Net, don't forget to include the using statement, to avoid the long lines I have below:
using System.Net;
// Get the hostname
string myHost = System.Net.Dns.GetHostName();
// Get the IP from the host name
string myIP = System.Net.Dns.GetHostByName(myHost).AddressList[0].ToString();
3 ways to speed up MySQL
There are ONLY 3 ways to speed up MySQL, and everything else is simply a finer point of one of these 3 ways. Here they are, in order of importance:
1. Optimize queries
2. Tune the MySQL configuration
3. Add more hardware
============================================================
#1. Query Optimization
============================================================
The most common problem with MySQL performance is unoptimized queries. Here are some examples of unoptimized queries:
- Queries that don't use indexes.
- Queries that use SELECT *.
- Queries that search full-text fields.
- Queries that are not properly limited.
- Queries that use ORDER BY unnecessarily.
Indexes
By far, the biggest problem queries are ones that don't use indexes or don't use the BEST indexes. Indexes are the key to getting the best performance out of your queries. Indexes are basically shortcuts for MySQL - they work the same way as an index in a classroom textbook. Let's say you wanted to look up all pages containing "gr8gonzo." You COULD go through every word in the book and find all the pages, but it's far faster to just flip to the index and see that "gr8gonzo" happens to be on pages 2, 6, and 32.
Most people know how to use basic indexes, but most people don't know how to use the BEST indexes. A lot of queries have more than one thing in the WHERE clause, like this:
SELECT fields FROM mytable
WHERE field1 > 123 AND field2 = 'gr8gonzo';
Most people will have an index for field1 and an index for field2. This is good, and the query will try to make use of one of those indexes (and will be faster). But if this is a query that is run frequently, it would be even better to have ANOTHER index that has BOTH field1 and field2 in it. That (usually) gives you the best query performance.
That said, you don't want to just create tons of these indexes, since each index does take a little bit of extra work for MySQL to update whenever the table changes, and those little bits can add up over time. You should really only create these multi-field indexes when there are frequent, slow queries that COULD take advantage of them. In section 2 of this article, we'll cover some ways of having MySQL tell you what queries need a tune-up, but there is one way to tell immediately if your query isn't using indexes...
EXPLAIN-ing Queries
If I wanted to see if the above query was working well, I could use EXPLAIN to do it. When you EXPLAIN a query, you're simply asking MySQL to tell you what it WOULD do if it ran the query for you. It responds with a computerish version of "Well, in order to run your query, I would use this index. That would leave me with X rows, which I would then look at in order to figure out which ones you wanted."
To EXPLAIN a query, all you have to do is run the same query but put "EXPLAIN" in front of it:
EXPLAIN SELECT fields FROM mytable
WHERE field1 > 123 AND field2 = 'gr8gonzo';
The result looks something like this:
+----+-------------+---------+------+---------------+------+---------+------+--------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+---------+------+---------------+------+---------+------+--------+-------------+
| 1 | SIMPLE | mytable | ALL | PRIMARY | NULL | NULL | NULL | 898256 | Using where |
+----+-------------+---------+------+---------------+------+---------+------+--------+-------------+
.
WHOA! At first glance, this is probably really confusing, but you can often just ignore a lot of the information, like id, select_type, table, type, and ref. And MySQL sometimes calls indexes "keys", so now let's look at the same result but without the extra columns:
+---------------+------+---------+------+--------+-------------+
| possible_keys | key | key_len | ref | rows | Extra |
+---------------+------+---------+------+--------+-------------+
| PRIMARY | NULL | NULL | NULL | 898256 | Using where |
+---------------+------+---------+------+--------+-------------+
Basically what this says is that MySQL had to go one-by-one through 898,256 rows and check each one to see if field1 > 123 and if field2 = 'gr8gonzo'. That's a lot of processing to do, especially if the final result is just a couple of rows (meaning that there are nearly 900,000 rows that get searched uselessly). Let's try adding in an index for one of those fields:
ALTER TABLE `mytable`
ADD INDEX `IDX_FIELD1` (`field1`) ;
If we re-run the EXPLAIN, we'll see:
+---------------+---------------+---------+-------+------+-------------+
| possible_keys | key | key_len | ref | rows | Extra |
+---------------+---------------+---------+-------+------+-------------+
| IDX_FIELD1 | IDX_FIELD1 | 5 | const | 1246 | Using where |
+---------------+---------------+---------+-------+------+-------------+
Well, now we're down to only looking at 1,246 rows. That's much better than 898 thousand, but we can do even better. Our query uses two fields in the WHERE clause, so we can probably gain better performance by adding in an index containing BOTH those fields:
ALTER TABLE `mytable`
ADD INDEX `IDX_FIELDS1_2` (`field1`, `field2`) ;
...and now re-run the EXPLAIN and we get.
+---------------+---------------+---------+-------------+------+-------------+
| possible_keys | key | key_len | ref | rows | Extra |
+---------------+---------------+---------+-------------+------+-------------+
| IDX_FIELDS1_2 | IDX_FIELDS1_2 | 5 | const,const | 16 | Using where |
+---------------+---------------+---------+-------------+------+-------------+
Voila! Now when we run the exact same query for real, we know that MySQL only has to search through 16 rows instead of nearly 1 million. A guaranteed speed increase, and it was free!
NOTE: In the above output, "possible_keys" will sometimes show more than one index, indicating that there's more than one choice that could help the query run faster. However the "chosen" index will be in the "key" field. The "ref" can give you an idea of how many fields are involved in the index. For example, if you have an index on one field, your "ref" column will probably just say "const" but if you have an index on two fields and both of those fields are in the WHERE clause, then you'll probably see "const,const" in the "ref" column.
ANOTHER NOTE: Whenever MySQL has to look at every row in the table, it's called a "table scan." Table scans are the slowest way MySQL can look for data. When you EXPLAIN a query, look at the "type" column - if it says "ALL" then MySQL is doing a table scan to find your data. If it says something else, like "range", then it is making use of an index. Occasionally, on small tables, MySQL will do a table scan even if you have an index. This is just MySQL knowing what is best in that situation, but you normally want to avoid these. Here's a link to the MySQL documentation on avoiding table scans:
http://dev.mysql.com/doc/refman/5.0/en/how-to-avoid-table-scan.html
There's a lot of in-depth optimization stuff on how to use EXPLAIN. If you feel like reading documentation, check here:
http://dev.mysql.com/doc/refman/5.0/en/using-explain.html
I also recently found and highly recommend a free MySQL manager application called HeidiSQL that (among other things) makes it easy to create and update your indexes. Plus, when you add indexes, it will show you the SQL code it ran to create those indexes, making it a useful learning tool.
http://www.heidisql.com/
There's also phpMyAdmin, which is installed on a lot of web hosts:
http://www.phpmyadmin.net
Using SELECT *
I'm guilty of it. It's far easier to write queries that use SELECT * and not have to worry about typing out 10 fields names, but it COULD be the culprit that slows down your web application. Here's a common mistake:
Let's say you run a web site that collects stories written by your members. All the stories are put into one big table called stories. So far so good. But now let's say you have a query out there that is used to create a menu to link to all the stories:
SELECT * FROM stories;
Well, if the CONTENTS of each story is in the stories table, then whenever you run the above query, MySQL is also sending every letter of every story in the system back to your script. If you have 1,000 stories that are about 10k each, then everytime someone views the menu, your script is downloading 10 megabytes of extra data that it just throws away without using. What a waste!
Instead, try changing your query to something like:
SELECT storyID,storyName,storyDate FROM stories;
Now we're only selecting a few fields that we need for the menu. Get into the habit of specifying ONLY the fields your script needs, and you'll find that it's easier than you think, and your scripts WILL run faster.
TIP: There's a quick way to see a summary of all the fields in the table and what type of field they are:
DESCRIBE mytable;
The Full Text
Let's stick with the "stories" example above. People will probably want to search through stories for specific words. If your story content is in a full-text field (e.g. TEXT datatype), then chances are that you're searching like this:
SELECT storyID FROM stories
WHERE storyContent LIKE '%fondled the hubcaps%';
This probably runs quickly when you don't have many stories, but it'll get slower and slower and slower over time. In this case, consider an open-source product called Sphinx Search:
http://sphinxsearch.com/
It specializes in taking your full-text content and making it searchable. A query that takes 10 seconds to run in MySQL could take 0.1 seconds in Sphinx, and that is not an exaggeration. The downside is that it is a separate program / daemon, and requires a bit of know-how to set up and get running, but it's worth the time. They have community forums to help, and some people here at Experts Exchange (like me), can also help.
Add LIMITs
This one's simple - if you only need a couple of rows out of thousands that are being returned (e.g. getting the top 10 of something), then add a LIMIT clause to the end of your query:
SELECT storyID FROM stories
ORDER BY storyRating DESC
LIMIT 10;
It can sometimes be useful to run a query that counts the number of rows in your result before pulling all of them. This can give you an idea of how to limit your rows or how to run your next query (although this largely depends on your particular situation). Here's a way to quickly get the number of stories from our example:
SELECT COUNT(storyID) AS storyCount FROM stories;
The results will be a row containing a field called "storyCount." This type of technique becomes more useful as your database grows larger and larger.
The ORDER BY Dilemma
Using ORDER BY is great for sorting, but sometimes it can create real slowdowns on MySQL. When you ORDER BY a field, MySQL first finds all the rows that will be in your results, and THEN goes back and re-orders them according to that ORDER BY field. If you have a lot of rows, then MySQL has to do a lot of re-ordering, which can be very slow.
In the above example on LIMITs, the query would have to sort every single story by its rating before returning the top 10. However, if I know that all of the top 10 stories have a rating of 4 or higher, then I could reduce the number of stories to be sorted like this:
SELECT storyID FROM stories
WHERE storyRating >= 4
ORDER BY storyRating DESC
LIMIT 10;
Now MySQL may only have to sort through 100 stories instead of 10,000.
Sometimes it's worth asking yourself whether you REALLY need to use ORDER BY at all. Sometimes it's faster to skip the ordering info altogether on the database and use PHP or something else to handle the sorting (although MySQL is usually faster at it).
One other trick is to create an index of the fields you're SELECTing and ORDERing BY. So if you had a query:
SELECT storyID,storyRating FROM stories
ORDER BY storyRating DESC;
Then, the query could benefit a lot from an multi-field index of storyID and storyRating.
============================================================
#2. The MySQL Configuration
============================================================
There are a LOT of ways to configure MySQL, but all of them start with the my.cnf configuration file (that's usually what it's called). Generally speaking, you can tune up MySQL by telling it to cache things in memory. When it stores any data in memory, MySQL can access it almost instantly instead of having to go back to the full database on the hard drive and look up the requested data (which is slow).
Here's an example section of a my.cnf file (I've taken out some extra parameters that weren't performance-related and some others that I won't discuss in this article):
[mysqld]
skip-name-resolve
query_cache_size = 16M
log-slow-queries=/var/log/slowqueries.log
long_query_time = 4
log-queries-not-using-indexes
table_cache = 512
tmp_table_size = 128M
max_heap_table_size = 128M
myisam_sort_buffer_size = 8M
sort_buffer_size = 8M
join_buffer_size = 256K
key_buffer = 128M
Toggle HighlightingOpen in New WindowSelect All
The first thing I always do is disable name resolution (skip-name-resolve). Basically, name resolution just tries to look up a "caller ID" on whoever is connecting to the database. I still don't know why it's enabled by default. It's not only a potential security problem, but it's usually unnecessary for most web server setups (since the web server is the one that does the connecting, not the visitors), and it has the potential to crash the system (if your DNS goes down for a while and MySQL gets filled up with connections that are waiting to be "resolved").
Next, enable the query cache (query_cache_size). In the above example, I've got a 16-megabyte query cache. Basically, if I run a query that takes 5 seconds to run, and then I refresh a page or something (causing the query to run again), then the query will run instantly because MySQL will remember the results of the query from the first time. If the tables involved in the query get changed, though, then it will clear any cached results that use those tables (so you're always getting accurate data). Start with a 16-megabyte cache and work your way up as necessary (I'll explain in a bit how to tell when to increase the cache).
Third, enable the slow query log (log-slow-queries and long_query_time and log-queries-not-using-indexes). This tells MySQL to keep track of all the queries that take longer than a certain number of seconds (long_query_time) to complete. The log-queries-not-using-indexes option also includes queries that don't use indexes (simple enough). Just let the log sit for a day or two while you use your application, and then look at it to find all the queries that need to be optimized.
The last section of lines have several different purposes (caching joins, ORDER BY results, temporary tables, etc), which all affect speed, but it's difficult to know exactly what values to use sometimes. That's why I recommend using MySQLTuner:
http://wiki.mysqltuner.com/MySQLTuner
It's a Perl script that you just download and run on your database server after letting your server run for a few days (without restarting). The script will look at all the statistics that MySQL collects and will make recommendations on what to change in your my.cnf file to make things run better (like increasing query cache size or table_cache and that type of thing). It's pretty straightforward and doesn't take long to run.
============================================================
#3. Instant Speed! Just Add Hardware!
============================================================
This is usually the most obvious answer. Upgrade to a faster CPU, add more RAM, etc... and you'll run faster. This is true, but there are a few things to know first.
First, the NUMBER of hard drives is more important than the SPACE. Some people make the mistake of getting two 1-terabyte drives and just using those to run their database server. By adding multiple hard disks in a RAID array (which is what most servers use anyway), you're effectively distributing the load.
If two queries are running at the same time and you only have two hard drives, then there's a good chance that the data for both queries is located on the same hard drive. Since a hard drive can only do one thing at a time, one of the queries will have to wait a little bit longer for the other one to finish before it can run. But if you have, say, 6 hard drives or more (the more the merrier), then one query might need data from Hard Drive #2 while the second query needs data from Hard Drive #5. Both hard drives can work at the same time and send data back nearly simultaneously. At least that's the gist of it, so spend money on multiple, fast hard disks for that extra bump in speed. Hard disks are usually the biggest speed bottleneck anyway (hardware-wise).
Last point on hard disks - if someone else is setting up your server and wants to know what RAID level you want to use, try to use RAID 10 (safe + good performance). Otherwise, use RAID 1 (safe). Other RAID levels have their advantages and disadvantages, but those are my standard recommendations.
Second, there's usually not a direct correlation between RAM and speed. Just because you add more RAM doesn't mean the system automatically uses it (or uses it right). If you've got several gigabytes of RAM already, then any additional RAM should probably go towards bigger caches. There are other uses, as well (like increasing the number of maximum connections), but if you're reading this article, then you may not be at that point yet anyway.
Third, CPU is sort of like luck - it affects everything a little bit, and affects some things a LOT. If you're writing a math-hungry application, crunching statistics, etc... then investing in the latest and greatest CPU and motherboard may be a good choice. If it's just your standard web/database server, then there are probably better ways of spending your money (-cough- more hard drives -cough-).
Finally, if one server just can't handle all the traffic, consider setting up another server with dual-master replication for a quick-n-dirty way of load-balancing. (Note - replication doesn't actually do load-balancing, it just keeps two servers in complete, realtime sync so you can send 50% of visitors to one database server and the other 50% to the other server. It also makes for a handy backup / failover system.)
http://www.neocodesoftware.com/replication/
FINAL TIP: A lot of places will have a test database server and a real database server, but the test database server only has very few rows in it (just enough for a basic test). This makes it easy to misjudge your application's REAL performance. Try to make sure that your test database has similar data to your real database so you get a better picture of how your queries will perform in the real world. Many MySQL manager programs like phpMyAdmin and HeidiSQL make it easy to download all the data from your real database so you can upload it into your test database. (There's also a command line tool called mysqldump.)
That's it! If you've read this far, then you know all the major (and some minor) steps in improving your MySQL performance, and you're now further ahead than most others who are still trying to read through those boring, thick manuals. If you run into any trouble with any of the points made here, please feel free to comment and/or post questions in the Experts Exchange MySQL area.
1. Optimize queries
2. Tune the MySQL configuration
3. Add more hardware
============================================================
#1. Query Optimization
============================================================
The most common problem with MySQL performance is unoptimized queries. Here are some examples of unoptimized queries:
- Queries that don't use indexes.
- Queries that use SELECT *.
- Queries that search full-text fields.
- Queries that are not properly limited.
- Queries that use ORDER BY unnecessarily.
Indexes
By far, the biggest problem queries are ones that don't use indexes or don't use the BEST indexes. Indexes are the key to getting the best performance out of your queries. Indexes are basically shortcuts for MySQL - they work the same way as an index in a classroom textbook. Let's say you wanted to look up all pages containing "gr8gonzo." You COULD go through every word in the book and find all the pages, but it's far faster to just flip to the index and see that "gr8gonzo" happens to be on pages 2, 6, and 32.
Most people know how to use basic indexes, but most people don't know how to use the BEST indexes. A lot of queries have more than one thing in the WHERE clause, like this:
SELECT fields FROM mytable
WHERE field1 > 123 AND field2 = 'gr8gonzo';
Most people will have an index for field1 and an index for field2. This is good, and the query will try to make use of one of those indexes (and will be faster). But if this is a query that is run frequently, it would be even better to have ANOTHER index that has BOTH field1 and field2 in it. That (usually) gives you the best query performance.
That said, you don't want to just create tons of these indexes, since each index does take a little bit of extra work for MySQL to update whenever the table changes, and those little bits can add up over time. You should really only create these multi-field indexes when there are frequent, slow queries that COULD take advantage of them. In section 2 of this article, we'll cover some ways of having MySQL tell you what queries need a tune-up, but there is one way to tell immediately if your query isn't using indexes...
EXPLAIN-ing Queries
If I wanted to see if the above query was working well, I could use EXPLAIN to do it. When you EXPLAIN a query, you're simply asking MySQL to tell you what it WOULD do if it ran the query for you. It responds with a computerish version of "Well, in order to run your query, I would use this index. That would leave me with X rows, which I would then look at in order to figure out which ones you wanted."
To EXPLAIN a query, all you have to do is run the same query but put "EXPLAIN" in front of it:
EXPLAIN SELECT fields FROM mytable
WHERE field1 > 123 AND field2 = 'gr8gonzo';
The result looks something like this:
+----+-------------+---------+------+---------------+------+---------+------+--------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+---------+------+---------------+------+---------+------+--------+-------------+
| 1 | SIMPLE | mytable | ALL | PRIMARY | NULL | NULL | NULL | 898256 | Using where |
+----+-------------+---------+------+---------------+------+---------+------+--------+-------------+
.
WHOA! At first glance, this is probably really confusing, but you can often just ignore a lot of the information, like id, select_type, table, type, and ref. And MySQL sometimes calls indexes "keys", so now let's look at the same result but without the extra columns:
+---------------+------+---------+------+--------+-------------+
| possible_keys | key | key_len | ref | rows | Extra |
+---------------+------+---------+------+--------+-------------+
| PRIMARY | NULL | NULL | NULL | 898256 | Using where |
+---------------+------+---------+------+--------+-------------+
Basically what this says is that MySQL had to go one-by-one through 898,256 rows and check each one to see if field1 > 123 and if field2 = 'gr8gonzo'. That's a lot of processing to do, especially if the final result is just a couple of rows (meaning that there are nearly 900,000 rows that get searched uselessly). Let's try adding in an index for one of those fields:
ALTER TABLE `mytable`
ADD INDEX `IDX_FIELD1` (`field1`) ;
If we re-run the EXPLAIN, we'll see:
+---------------+---------------+---------+-------+------+-------------+
| possible_keys | key | key_len | ref | rows | Extra |
+---------------+---------------+---------+-------+------+-------------+
| IDX_FIELD1 | IDX_FIELD1 | 5 | const | 1246 | Using where |
+---------------+---------------+---------+-------+------+-------------+
Well, now we're down to only looking at 1,246 rows. That's much better than 898 thousand, but we can do even better. Our query uses two fields in the WHERE clause, so we can probably gain better performance by adding in an index containing BOTH those fields:
ALTER TABLE `mytable`
ADD INDEX `IDX_FIELDS1_2` (`field1`, `field2`) ;
...and now re-run the EXPLAIN and we get.
+---------------+---------------+---------+-------------+------+-------------+
| possible_keys | key | key_len | ref | rows | Extra |
+---------------+---------------+---------+-------------+------+-------------+
| IDX_FIELDS1_2 | IDX_FIELDS1_2 | 5 | const,const | 16 | Using where |
+---------------+---------------+---------+-------------+------+-------------+
Voila! Now when we run the exact same query for real, we know that MySQL only has to search through 16 rows instead of nearly 1 million. A guaranteed speed increase, and it was free!
NOTE: In the above output, "possible_keys" will sometimes show more than one index, indicating that there's more than one choice that could help the query run faster. However the "chosen" index will be in the "key" field. The "ref" can give you an idea of how many fields are involved in the index. For example, if you have an index on one field, your "ref" column will probably just say "const" but if you have an index on two fields and both of those fields are in the WHERE clause, then you'll probably see "const,const" in the "ref" column.
ANOTHER NOTE: Whenever MySQL has to look at every row in the table, it's called a "table scan." Table scans are the slowest way MySQL can look for data. When you EXPLAIN a query, look at the "type" column - if it says "ALL" then MySQL is doing a table scan to find your data. If it says something else, like "range", then it is making use of an index. Occasionally, on small tables, MySQL will do a table scan even if you have an index. This is just MySQL knowing what is best in that situation, but you normally want to avoid these. Here's a link to the MySQL documentation on avoiding table scans:
http://dev.mysql.com/doc/refman/5.0/en/how-to-avoid-table-scan.html
There's a lot of in-depth optimization stuff on how to use EXPLAIN. If you feel like reading documentation, check here:
http://dev.mysql.com/doc/refman/5.0/en/using-explain.html
I also recently found and highly recommend a free MySQL manager application called HeidiSQL that (among other things) makes it easy to create and update your indexes. Plus, when you add indexes, it will show you the SQL code it ran to create those indexes, making it a useful learning tool.
http://www.heidisql.com/
There's also phpMyAdmin, which is installed on a lot of web hosts:
http://www.phpmyadmin.net
Using SELECT *
I'm guilty of it. It's far easier to write queries that use SELECT * and not have to worry about typing out 10 fields names, but it COULD be the culprit that slows down your web application. Here's a common mistake:
Let's say you run a web site that collects stories written by your members. All the stories are put into one big table called stories. So far so good. But now let's say you have a query out there that is used to create a menu to link to all the stories:
SELECT * FROM stories;
Well, if the CONTENTS of each story is in the stories table, then whenever you run the above query, MySQL is also sending every letter of every story in the system back to your script. If you have 1,000 stories that are about 10k each, then everytime someone views the menu, your script is downloading 10 megabytes of extra data that it just throws away without using. What a waste!
Instead, try changing your query to something like:
SELECT storyID,storyName,storyDate FROM stories;
Now we're only selecting a few fields that we need for the menu. Get into the habit of specifying ONLY the fields your script needs, and you'll find that it's easier than you think, and your scripts WILL run faster.
TIP: There's a quick way to see a summary of all the fields in the table and what type of field they are:
DESCRIBE mytable;
The Full Text
Let's stick with the "stories" example above. People will probably want to search through stories for specific words. If your story content is in a full-text field (e.g. TEXT datatype), then chances are that you're searching like this:
SELECT storyID FROM stories
WHERE storyContent LIKE '%fondled the hubcaps%';
This probably runs quickly when you don't have many stories, but it'll get slower and slower and slower over time. In this case, consider an open-source product called Sphinx Search:
http://sphinxsearch.com/
It specializes in taking your full-text content and making it searchable. A query that takes 10 seconds to run in MySQL could take 0.1 seconds in Sphinx, and that is not an exaggeration. The downside is that it is a separate program / daemon, and requires a bit of know-how to set up and get running, but it's worth the time. They have community forums to help, and some people here at Experts Exchange (like me), can also help.
Add LIMITs
This one's simple - if you only need a couple of rows out of thousands that are being returned (e.g. getting the top 10 of something), then add a LIMIT clause to the end of your query:
SELECT storyID FROM stories
ORDER BY storyRating DESC
LIMIT 10;
It can sometimes be useful to run a query that counts the number of rows in your result before pulling all of them. This can give you an idea of how to limit your rows or how to run your next query (although this largely depends on your particular situation). Here's a way to quickly get the number of stories from our example:
SELECT COUNT(storyID) AS storyCount FROM stories;
The results will be a row containing a field called "storyCount." This type of technique becomes more useful as your database grows larger and larger.
The ORDER BY Dilemma
Using ORDER BY is great for sorting, but sometimes it can create real slowdowns on MySQL. When you ORDER BY a field, MySQL first finds all the rows that will be in your results, and THEN goes back and re-orders them according to that ORDER BY field. If you have a lot of rows, then MySQL has to do a lot of re-ordering, which can be very slow.
In the above example on LIMITs, the query would have to sort every single story by its rating before returning the top 10. However, if I know that all of the top 10 stories have a rating of 4 or higher, then I could reduce the number of stories to be sorted like this:
SELECT storyID FROM stories
WHERE storyRating >= 4
ORDER BY storyRating DESC
LIMIT 10;
Now MySQL may only have to sort through 100 stories instead of 10,000.
Sometimes it's worth asking yourself whether you REALLY need to use ORDER BY at all. Sometimes it's faster to skip the ordering info altogether on the database and use PHP or something else to handle the sorting (although MySQL is usually faster at it).
One other trick is to create an index of the fields you're SELECTing and ORDERing BY. So if you had a query:
SELECT storyID,storyRating FROM stories
ORDER BY storyRating DESC;
Then, the query could benefit a lot from an multi-field index of storyID and storyRating.
============================================================
#2. The MySQL Configuration
============================================================
There are a LOT of ways to configure MySQL, but all of them start with the my.cnf configuration file (that's usually what it's called). Generally speaking, you can tune up MySQL by telling it to cache things in memory. When it stores any data in memory, MySQL can access it almost instantly instead of having to go back to the full database on the hard drive and look up the requested data (which is slow).
Here's an example section of a my.cnf file (I've taken out some extra parameters that weren't performance-related and some others that I won't discuss in this article):
[mysqld]
skip-name-resolve
query_cache_size = 16M
log-slow-queries=/var/log/slowqueries.log
long_query_time = 4
log-queries-not-using-indexes
table_cache = 512
tmp_table_size = 128M
max_heap_table_size = 128M
myisam_sort_buffer_size = 8M
sort_buffer_size = 8M
join_buffer_size = 256K
key_buffer = 128M
Toggle HighlightingOpen in New WindowSelect All
The first thing I always do is disable name resolution (skip-name-resolve). Basically, name resolution just tries to look up a "caller ID" on whoever is connecting to the database. I still don't know why it's enabled by default. It's not only a potential security problem, but it's usually unnecessary for most web server setups (since the web server is the one that does the connecting, not the visitors), and it has the potential to crash the system (if your DNS goes down for a while and MySQL gets filled up with connections that are waiting to be "resolved").
Next, enable the query cache (query_cache_size). In the above example, I've got a 16-megabyte query cache. Basically, if I run a query that takes 5 seconds to run, and then I refresh a page or something (causing the query to run again), then the query will run instantly because MySQL will remember the results of the query from the first time. If the tables involved in the query get changed, though, then it will clear any cached results that use those tables (so you're always getting accurate data). Start with a 16-megabyte cache and work your way up as necessary (I'll explain in a bit how to tell when to increase the cache).
Third, enable the slow query log (log-slow-queries and long_query_time and log-queries-not-using-indexes). This tells MySQL to keep track of all the queries that take longer than a certain number of seconds (long_query_time) to complete. The log-queries-not-using-indexes option also includes queries that don't use indexes (simple enough). Just let the log sit for a day or two while you use your application, and then look at it to find all the queries that need to be optimized.
The last section of lines have several different purposes (caching joins, ORDER BY results, temporary tables, etc), which all affect speed, but it's difficult to know exactly what values to use sometimes. That's why I recommend using MySQLTuner:
http://wiki.mysqltuner.com/MySQLTuner
It's a Perl script that you just download and run on your database server after letting your server run for a few days (without restarting). The script will look at all the statistics that MySQL collects and will make recommendations on what to change in your my.cnf file to make things run better (like increasing query cache size or table_cache and that type of thing). It's pretty straightforward and doesn't take long to run.
============================================================
#3. Instant Speed! Just Add Hardware!
============================================================
This is usually the most obvious answer. Upgrade to a faster CPU, add more RAM, etc... and you'll run faster. This is true, but there are a few things to know first.
First, the NUMBER of hard drives is more important than the SPACE. Some people make the mistake of getting two 1-terabyte drives and just using those to run their database server. By adding multiple hard disks in a RAID array (which is what most servers use anyway), you're effectively distributing the load.
If two queries are running at the same time and you only have two hard drives, then there's a good chance that the data for both queries is located on the same hard drive. Since a hard drive can only do one thing at a time, one of the queries will have to wait a little bit longer for the other one to finish before it can run. But if you have, say, 6 hard drives or more (the more the merrier), then one query might need data from Hard Drive #2 while the second query needs data from Hard Drive #5. Both hard drives can work at the same time and send data back nearly simultaneously. At least that's the gist of it, so spend money on multiple, fast hard disks for that extra bump in speed. Hard disks are usually the biggest speed bottleneck anyway (hardware-wise).
Last point on hard disks - if someone else is setting up your server and wants to know what RAID level you want to use, try to use RAID 10 (safe + good performance). Otherwise, use RAID 1 (safe). Other RAID levels have their advantages and disadvantages, but those are my standard recommendations.
Second, there's usually not a direct correlation between RAM and speed. Just because you add more RAM doesn't mean the system automatically uses it (or uses it right). If you've got several gigabytes of RAM already, then any additional RAM should probably go towards bigger caches. There are other uses, as well (like increasing the number of maximum connections), but if you're reading this article, then you may not be at that point yet anyway.
Third, CPU is sort of like luck - it affects everything a little bit, and affects some things a LOT. If you're writing a math-hungry application, crunching statistics, etc... then investing in the latest and greatest CPU and motherboard may be a good choice. If it's just your standard web/database server, then there are probably better ways of spending your money (-cough- more hard drives -cough-).
Finally, if one server just can't handle all the traffic, consider setting up another server with dual-master replication for a quick-n-dirty way of load-balancing. (Note - replication doesn't actually do load-balancing, it just keeps two servers in complete, realtime sync so you can send 50% of visitors to one database server and the other 50% to the other server. It also makes for a handy backup / failover system.)
http://www.neocodesoftware.com/replication/
FINAL TIP: A lot of places will have a test database server and a real database server, but the test database server only has very few rows in it (just enough for a basic test). This makes it easy to misjudge your application's REAL performance. Try to make sure that your test database has similar data to your real database so you get a better picture of how your queries will perform in the real world. Many MySQL manager programs like phpMyAdmin and HeidiSQL make it easy to download all the data from your real database so you can upload it into your test database. (There's also a command line tool called mysqldump.)
That's it! If you've read this far, then you know all the major (and some minor) steps in improving your MySQL performance, and you're now further ahead than most others who are still trying to read through those boring, thick manuals. If you run into any trouble with any of the points made here, please feel free to comment and/or post questions in the Experts Exchange MySQL area.
Text to Speech Using C#
To convert text to speech just add a reference in your project to System.Speech(.Net) and to Microsoft speech object library(com).
Then use the following code
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Speech;
using System.Speech.Synthesis;
namespace Speech
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
SpeechSynthesizer synth = new SpeechSynthesizer();
synth.Rate = -10;
synth.Speak(textBox1.Text);
synth.Dispose();
}
}
}
Then use the following code
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Speech;
using System.Speech.Synthesis;
namespace Speech
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
SpeechSynthesizer synth = new SpeechSynthesizer();
synth.Rate = -10;
synth.Speak(textBox1.Text);
synth.Dispose();
}
}
}
Caching in ASP.NET
Caching is one of the best practices to improve the performance of any application we develop. For example, any business application will be very dynamic, complex and resource intensive in nature. In these scenarios, one can consider caching the output from a resource intensive processing so that the output can be served directly for the subsequent requests without processing it again. Moving forward, we will see how to do caching in ASP.Net and different techniques the framework offers to support caching.
ASP.Net supports the below caching techniques,
1. Page Level Caching(Output Caching)
2. Fragment caching or Control (Output Caching)
3. Application or Data Caching(also programmatic caching)
Page Level Caching
Using this technique we can cache the whole aspx page output. Since we are caching page output, it is called as page output caching. In this technique, the page will be executed for the first time and will be cached for the duration specified in the expiry setting. The subsequent requests will be served with the cached page output instead of processing the page again. This is achieved by using @OutputCache directive in the aspx page. The below setting will cache the Default.aspx page for 120 seconds.
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<%@ OutputCache Duration="120" VaryByParam="None"%>
It is compulsory to have Duration and VaryByParam parameter with some settings in @OutputCache directive for the caching to work.
There will be situations where we need cache the page for different input parameters on the page. The input will be querystring in GET and form fields in POST requests. It is obvious that the page output will be different for different input parameters. In this case, to cache the output of different versions of page we need to set the parameter name(s) in VaryByParam parameter. For example, if you have querystring called catid and if you like to cache the page for different category values then the below will work,
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<%@ OutputCache Duration="120" VaryByParam="catid"%>
The same setting will apply to a POST request with catid as form field. The above setting will cache the page out with different catid values.
The None value in VaryByParam will make ASP.Net to cache the page without any input parameter. You can give multiple parameters by separating the parameter with semicolon(;). If you want to cache the page for all the parameter values, then specify VaryByParam="*".
The other parameters which helps in caching different version of page is,
Ø VaryByControl
Ø VaryByHeader
Ø VaryByCustom
Ø VaryByContentEncodings
VaryByControl
This parameter accepts semicolon separated list of control ids to cache the different versions of page. See below,
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<%@ OutputCache Duration="120" VaryByControl="txtEmpID"%>
The above setting will cache the page for different values in txtEmpID.
Note
If you specify the VaryByControl then VaryByParam is optional i.e. Duration and either VaryByControl or VaryByParam is mandatory.
VaryByHeader
This setting will cache different versions of the page depending upon the http header set in the request. Refer below,
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<%@ OutputCache Duration="120" VaryByParam="None" VaryByHeader="User-Agent" %>
The above setting will cache different version of page when requested from different user agents i.e. IE, firefox, etc. You can specify multiple header values separated by semicolon.
VaryByCustom
This can be used to cache with a custom parameter. Refer this post which speaks about the parameter usage.
VaryByContentEncodings
This parameter can be used to cache different version of the page depending on different encoding.
Where the Cached elements are stored ?
The other useful parameter of @OutputCache directive is Location which dictates where the cached items should be stored. It is an enumeration of type OutputCacheLocation. The possible values are,
Any
The output cache can be located on the browser client, on a proxy server, or on the server where the request was processed.
Client
The output cache is located on the browser client where the request is originated.
Downstream
This make the cache to be stored in proxy servers and the client that made the request other than the processing server.
Server
The output cache is located on the Web server where the request was processed
None
This dictates that the cache is stored nowhere. In other words, the output cache is disabled for the requested page.
ServerAndClient
The output cache can be stored only at the origin server or at the requesting client.
Fragment caching or Control Caching
Using this technique we can cache a part or fragment of a page in ASP.Net. The fragment that needs to be cached should be a usercontrol so that it can be cached. Again, this is done by using @OutputCache directive. Refer below,
<%@ Control Language="C#" AutoEventWireup="true" CodeFile="WebUserControl.ascx.cs" Inherits="WebUserControl" %>
<%@ OutputCache Duration="120" VaryByParam="None" %>
The fragment caching supports VaryByParam, VaryByControl and VaryByCustom parameters.
Application or Data Caching (also called programmatic caching)
Using this technique, we can cache data programmatically from a complex and resource intensive calculations/processing. Something like this,
if (Cache["categories"] == null)
{
Cache["categories"] = GetCats();
}
else
{
DataSet ds = Cache["categories"] as DataSet;
}
ASP.Net supports the below caching techniques,
1. Page Level Caching(Output Caching)
2. Fragment caching or Control (Output Caching)
3. Application or Data Caching(also programmatic caching)
Page Level Caching
Using this technique we can cache the whole aspx page output. Since we are caching page output, it is called as page output caching. In this technique, the page will be executed for the first time and will be cached for the duration specified in the expiry setting. The subsequent requests will be served with the cached page output instead of processing the page again. This is achieved by using @OutputCache directive in the aspx page. The below setting will cache the Default.aspx page for 120 seconds.
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<%@ OutputCache Duration="120" VaryByParam="None"%>
It is compulsory to have Duration and VaryByParam parameter with some settings in @OutputCache directive for the caching to work.
There will be situations where we need cache the page for different input parameters on the page. The input will be querystring in GET and form fields in POST requests. It is obvious that the page output will be different for different input parameters. In this case, to cache the output of different versions of page we need to set the parameter name(s) in VaryByParam parameter. For example, if you have querystring called catid and if you like to cache the page for different category values then the below will work,
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<%@ OutputCache Duration="120" VaryByParam="catid"%>
The same setting will apply to a POST request with catid as form field. The above setting will cache the page out with different catid values.
The None value in VaryByParam will make ASP.Net to cache the page without any input parameter. You can give multiple parameters by separating the parameter with semicolon(;). If you want to cache the page for all the parameter values, then specify VaryByParam="*".
The other parameters which helps in caching different version of page is,
Ø VaryByControl
Ø VaryByHeader
Ø VaryByCustom
Ø VaryByContentEncodings
VaryByControl
This parameter accepts semicolon separated list of control ids to cache the different versions of page. See below,
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<%@ OutputCache Duration="120" VaryByControl="txtEmpID"%>
The above setting will cache the page for different values in txtEmpID.
Note
If you specify the VaryByControl then VaryByParam is optional i.e. Duration and either VaryByControl or VaryByParam is mandatory.
VaryByHeader
This setting will cache different versions of the page depending upon the http header set in the request. Refer below,
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<%@ OutputCache Duration="120" VaryByParam="None" VaryByHeader="User-Agent" %>
The above setting will cache different version of page when requested from different user agents i.e. IE, firefox, etc. You can specify multiple header values separated by semicolon.
VaryByCustom
This can be used to cache with a custom parameter. Refer this post which speaks about the parameter usage.
VaryByContentEncodings
This parameter can be used to cache different version of the page depending on different encoding.
Where the Cached elements are stored ?
The other useful parameter of @OutputCache directive is Location which dictates where the cached items should be stored. It is an enumeration of type OutputCacheLocation. The possible values are,
Any
The output cache can be located on the browser client, on a proxy server, or on the server where the request was processed.
Client
The output cache is located on the browser client where the request is originated.
Downstream
This make the cache to be stored in proxy servers and the client that made the request other than the processing server.
Server
The output cache is located on the Web server where the request was processed
None
This dictates that the cache is stored nowhere. In other words, the output cache is disabled for the requested page.
ServerAndClient
The output cache can be stored only at the origin server or at the requesting client.
Fragment caching or Control Caching
Using this technique we can cache a part or fragment of a page in ASP.Net. The fragment that needs to be cached should be a usercontrol so that it can be cached. Again, this is done by using @OutputCache directive. Refer below,
<%@ Control Language="C#" AutoEventWireup="true" CodeFile="WebUserControl.ascx.cs" Inherits="WebUserControl" %>
<%@ OutputCache Duration="120" VaryByParam="None" %>
The fragment caching supports VaryByParam, VaryByControl and VaryByCustom parameters.
Application or Data Caching (also called programmatic caching)
Using this technique, we can cache data programmatically from a complex and resource intensive calculations/processing. Something like this,
if (Cache["categories"] == null)
{
Cache["categories"] = GetCats();
}
else
{
DataSet ds = Cache["categories"] as DataSet;
}
SQL Injection is ASP.Net
SQL injection is an attack where an executable query is inserted or injected with the input data. The injected code will then gets executed with the application identity and hence causing the damage. For example, an attacker can take advantage of this vulnerability to gain access to restricted areas by injecting script to pass the user verification against DB or delete your database or tables very easily.
Moving forward, we will try to understand the SQL injection attack with an example code.
Consider the below code,
private void GetEmp()
{
SqlConnection con = new SqlConnection(ConfigurationManager.
ConnectionStrings["ConnectionString"].ConnectionString);
SqlDataReader dr = null;
try
{
con.Open();
SqlCommand com = new SqlCommand("Select * from Employee where EmpID =" + txtID.Text, con);
dr = com.ExecuteReader();
while (dr.Read())
{
txtAge.Text = dr["Age"].ToString();
txtName.Text = dr["EmpName"].ToString();
}
}
catch
{
}
finally
{
dr.Close();
con.Close();
}
}
In the above code, the SQL query is written inline and the parameter value is concatenated directly reading from TextBox.
See below,
"Select * from Employee where EmpID =" + txtID.Text
Now, consider you are giving the below string as input in the TextBox(txtID),
0;delete from employee; --
When executed, the above code will successfully delete the Employee table data as it is prone to SQL injection vulnerability.
Now, let’s see the above scenario in detail.
If you take a close look at the above input, the semicolon after the number 0 will complete the previous SQL statement. Then comes the disastrous delete statement and -- will make any statement after its occurrence void since it is a commenting operator.
On execution, final query that will be formed and executed will be like below.
Select * from Employee where EmpID =0;delete from employee; --
Now, let see an another example where a user can gain access to restricted sections by bye-passing the authentication logic that has SQL injection vulnerability. Consider the below sql statement which is used for login screen.
"Select count(login) from tblLogin where login = '+"txtLogin.Text"+' and password ='"+txtPass.Text+"' "
Now, a malicious user can give the below input to txtLogin TextBox to bye-pass the where condition.
' OR 1=1 ;--
The above input will produce a SQL query something like below and can produce count greater than 1 and thus achieving his aim.
Select count(login) from tblLogin where login = '' OR 1=1 ;-- and password ='test'
We will now move forward and will understand how to prevent SQL injection in ASP.Net.
1. Clean input Validation
2. Use Parameterized SQL query
3. Using Stored Procedure
4. Using ORM tools
Clean Input Validation
Always have a validation on user input in place. Choose white listing as opposed to blacklisting because we certainly know what is expected as input. But, doing this alone may not be sufficient at times. You need employ other techniques as well.
Use Parameterized Query
This is one way of preventing SQL injection attack. Never believe the user’s input and directly concatenate it with query like above. Instead, it is always advisable to have an input validation and use parameterized query. So the above code can be re-written as,
private void GetEmp(int empid)
{
SqlConnection con = new SqlConnection(ConfigurationManager.
ConnectionStrings["ConnectionString"].ConnectionString);
SqlDataReader dr = null;
try
{
con.Open();
SqlCommand com = new SqlCommand("Select * from Employee where EmpID =@EmpID", con);
com.Parameters.Add(new SqlParameter("@EmpID", empid));
dr = com.ExecuteReader();
while (dr.Read())
{
txtAge.Text = dr["Age"].ToString();
txtName.Text = dr["EmpName"].ToString();
}
}
catch
{
}
finally
{
dr.Close();
con.Close();
}
}
Using Stored Procedure
Using stored procedure is another way of preventing SQL injection attack. When using stored procedures, you are forced to use parameters through the parameter collection object of SqlCommand object. So, the above code can be re-written as,
private void GetEmp(int empid)
{
SqlConnection con = new SqlConnection(ConfigurationManager.
ConnectionStrings["ConnectionString"].ConnectionString);
SqlDataReader dr = null;
try
{
con.Open();
SqlCommand com = new SqlCommand("PRC_GetEmployee", con);
com.CommandType = CommandType.StoredProcedure;
com.Parameters.Add(new SqlParameter("@EmpID", empid));
dr = com.ExecuteReader();
while (dr.Read())
{
txtAge.Text = dr["Age"].ToString();
txtName.Text = dr["EmpName"].ToString();
}
}
catch
{
}
finally
{
dr.Close();
con.Close();
}
}
The above code will prevent SQL injection attack.
Sometimes, when using dynamically constructed query in stored procedures it will still carry the vulnerability. Hence, make sure you are doing proper filtrations on the input to remove unwarranted characters (like ‘, ;, etc) from the user input.
Using ORM tools
When you use ORM tools like LINQ to SQL, LINQ to Entities or NHibernate for data access then the tool themselves will handle the SQL injection attacks for you. Since, these ORM tools will generate the query to execute against the DB we need not worry about the SQL injection attacks.
Things to Consider
Using Low Privileged account to execute SQL statement
In SQL injection attack, the injected code will run under the identity of the application. Hence, you can provide very least permission set for the application service account on the database. For example, provide only execute access on database. Again, using this method will not prevent SQL injection attack fully as the malicious user will be still able to execute some script and get some crucial data or bye pass some filtering conditions(as discussed above for login screens) to gain more access than designated.
Conclusion
It is really important to protect our web assets from malicious users or programs. SQL injection is one of the most common vulnerability (also, an easy way to take advantage) which can be easily addressed. Hence, your application should prevent this vulnerability at any cause. To recap,
1. Filter user data or sanitize the data
2. Use Parameterized query or Stored procedures or use ORM for data access.
3. As an extra measure, always provide the least required permission for application service account
Moving forward, we will try to understand the SQL injection attack with an example code.
Consider the below code,
private void GetEmp()
{
SqlConnection con = new SqlConnection(ConfigurationManager.
ConnectionStrings["ConnectionString"].ConnectionString);
SqlDataReader dr = null;
try
{
con.Open();
SqlCommand com = new SqlCommand("Select * from Employee where EmpID =" + txtID.Text, con);
dr = com.ExecuteReader();
while (dr.Read())
{
txtAge.Text = dr["Age"].ToString();
txtName.Text = dr["EmpName"].ToString();
}
}
catch
{
}
finally
{
dr.Close();
con.Close();
}
}
In the above code, the SQL query is written inline and the parameter value is concatenated directly reading from TextBox.
See below,
"Select * from Employee where EmpID =" + txtID.Text
Now, consider you are giving the below string as input in the TextBox(txtID),
0;delete from employee; --
When executed, the above code will successfully delete the Employee table data as it is prone to SQL injection vulnerability.
Now, let’s see the above scenario in detail.
If you take a close look at the above input, the semicolon after the number 0 will complete the previous SQL statement. Then comes the disastrous delete statement and -- will make any statement after its occurrence void since it is a commenting operator.
On execution, final query that will be formed and executed will be like below.
Select * from Employee where EmpID =0;delete from employee; --
Now, let see an another example where a user can gain access to restricted sections by bye-passing the authentication logic that has SQL injection vulnerability. Consider the below sql statement which is used for login screen.
"Select count(login) from tblLogin where login = '+"txtLogin.Text"+' and password ='"+txtPass.Text+"' "
Now, a malicious user can give the below input to txtLogin TextBox to bye-pass the where condition.
' OR 1=1 ;--
The above input will produce a SQL query something like below and can produce count greater than 1 and thus achieving his aim.
Select count(login) from tblLogin where login = '' OR 1=1 ;-- and password ='test'
We will now move forward and will understand how to prevent SQL injection in ASP.Net.
1. Clean input Validation
2. Use Parameterized SQL query
3. Using Stored Procedure
4. Using ORM tools
Clean Input Validation
Always have a validation on user input in place. Choose white listing as opposed to blacklisting because we certainly know what is expected as input. But, doing this alone may not be sufficient at times. You need employ other techniques as well.
Use Parameterized Query
This is one way of preventing SQL injection attack. Never believe the user’s input and directly concatenate it with query like above. Instead, it is always advisable to have an input validation and use parameterized query. So the above code can be re-written as,
private void GetEmp(int empid)
{
SqlConnection con = new SqlConnection(ConfigurationManager.
ConnectionStrings["ConnectionString"].ConnectionString);
SqlDataReader dr = null;
try
{
con.Open();
SqlCommand com = new SqlCommand("Select * from Employee where EmpID =@EmpID", con);
com.Parameters.Add(new SqlParameter("@EmpID", empid));
dr = com.ExecuteReader();
while (dr.Read())
{
txtAge.Text = dr["Age"].ToString();
txtName.Text = dr["EmpName"].ToString();
}
}
catch
{
}
finally
{
dr.Close();
con.Close();
}
}
Using Stored Procedure
Using stored procedure is another way of preventing SQL injection attack. When using stored procedures, you are forced to use parameters through the parameter collection object of SqlCommand object. So, the above code can be re-written as,
private void GetEmp(int empid)
{
SqlConnection con = new SqlConnection(ConfigurationManager.
ConnectionStrings["ConnectionString"].ConnectionString);
SqlDataReader dr = null;
try
{
con.Open();
SqlCommand com = new SqlCommand("PRC_GetEmployee", con);
com.CommandType = CommandType.StoredProcedure;
com.Parameters.Add(new SqlParameter("@EmpID", empid));
dr = com.ExecuteReader();
while (dr.Read())
{
txtAge.Text = dr["Age"].ToString();
txtName.Text = dr["EmpName"].ToString();
}
}
catch
{
}
finally
{
dr.Close();
con.Close();
}
}
The above code will prevent SQL injection attack.
Sometimes, when using dynamically constructed query in stored procedures it will still carry the vulnerability. Hence, make sure you are doing proper filtrations on the input to remove unwarranted characters (like ‘, ;, etc) from the user input.
Using ORM tools
When you use ORM tools like LINQ to SQL, LINQ to Entities or NHibernate for data access then the tool themselves will handle the SQL injection attacks for you. Since, these ORM tools will generate the query to execute against the DB we need not worry about the SQL injection attacks.
Things to Consider
Using Low Privileged account to execute SQL statement
In SQL injection attack, the injected code will run under the identity of the application. Hence, you can provide very least permission set for the application service account on the database. For example, provide only execute access on database. Again, using this method will not prevent SQL injection attack fully as the malicious user will be still able to execute some script and get some crucial data or bye pass some filtering conditions(as discussed above for login screens) to gain more access than designated.
Conclusion
It is really important to protect our web assets from malicious users or programs. SQL injection is one of the most common vulnerability (also, an easy way to take advantage) which can be easily addressed. Hence, your application should prevent this vulnerability at any cause. To recap,
1. Filter user data or sanitize the data
2. Use Parameterized query or Stored procedures or use ORM for data access.
3. As an extra measure, always provide the least required permission for application service account
HttpUtility Class Methods in Asp.Net
HttpUtility is the class library provided by the Asp.Net consists of set of methods which are useful in developing web applications. The utility mainly provides methods for encoding and decoding urls useful for processing Web Requests. In this article I will be explaining in detail about each method by quoting some real time scenarios where actually these methods are useful.
Here is the list of the HttpUtility methods, I will be explaining in this article.
1. UrlEncode/UrlDecode
2. HtmlEncode/HtmlDecode
3. HtmlAttributeEncode
4. ParseQueryString
5. UrlPathEncode
1.UrlEncode/UrlDecode Method
Purpose
The main purpose of these two methods is to encode and decode the urls respectively. We need to encode the urls because some of the characters are not safe sending those across browser. Some characters are being misunderstood by the browser and that leads to data mismatch on the receiving end. Characters such as a question mark (?), ampersand (&), slash mark (/), and spaces might be truncated or corrupted by some browsers.
For instance user requires sending the data in the url querystrings in the following format
Eg:
string Url = "http://localhost:9618/WebForm1.aspx?" + "names=suresh#satheesh#vijay#";
On the receiving end if the developer tries to read the url querystring values in the following way
string QueryStringValues = Request.QueryString["names"];
the developers reads it as “suresh” which is not the expected result
The workaround for this we need to encode the url and read the querystring values to get the expected result.
UrlEncode
Eg:
string Url = "http://localhost:9618/WebForm1.aspx?" + HttpUtility.UrlEncode("names=suresh#satheesh#vijay");
Response.Redirect(Url);
When we use the UrlEncode for the above url, the new url looks in the following way:
http://localhost:9618/WebForm1.aspx?names=suresh%23satheesh%23vijay
When the user tries to fetch the querystring values on the receiving end, the expected result is “suresh#satheesh#vijay”
Eg:
string QueryStringValues = Request.QueryString["names"];
Response.Write(QueryStringValues);
UrlDecode
This method is used to convert the encoded url back into a decoded string.
If you want to decode the entire url on the receiving end, just decode in the following way.
Eg:
string Url = HttpUtility.UrlDecode(Request.RawUrl);
The expected result would be "/WebForm1.aspx?names=suresh#satheesh#vijay” ;
We can also decode only the querystring values on the receiving end in the following way,
Eg:
string QueryStringValues = Request.QueryString["names"];
QueryStringValues = HttpUtility.UrlDecode(QueryStringValues);
2. HtmlEnCode/HtmlDecode Method
HtmlEncode -Purpose
The main purpose of HtmlEncode is to convert a HTML-encoded string for reliable HTTP transmission from the Web server to a client. It mainly converts the characters that are not allowed in Html like “<”,”>”, spaces and punctuation marks. For example, < is replaced with < and " is replaced with ".
HtmlDecode converts back to the original string which is html encoded.
For instance user has a requirement where he wants to display the data in the following way
Eg: “”
If the developer uses the following code, it is misinterpreted on the browser receiving end
Label1.Text = "" and nothing is visible on the browser.
To achive this we need use the HtmlEncode for displaying the correct data.
Eg:
Label1.Text = HttpUtility.HtmlEncode("");
Also Htmlencoding is used to avoid Cross-site scripting.
3. HtmlAttributeEncode Method
Purpose
This method is used to encode only certain characters like Quotation marks (“), ampersands (&), and left angle brackets. Performance wise, this method is faster than the HtmlEncode.
Eg:
Response.Write(HttpUtility.HtmlAttributeEncode(TextBox3.Text));
4.ParseQueryString Method
Purpose
This method is used to retrieve the querystring values of an url in the NameValueCollection format. This helps developers for traversing easily to read the querystring values when there are multiple querystring values in the url. The NameValueCollection in .Net framework comes with namespace System.Collections.Specialized.
Let’s say user has an url with querystring values on the receiving end then ParseQueryString is used for retrieving the values.
Eg:
string Url = "http://localhost:3873/WebForm1.aspx?" + "param1=val1¶m2=val2¶m3=val3¶m4=val4¶m5=val5";
Response.Redirect(Url);
On the receiving end or code behind we can read the querystring values using the ParseQueryString in the following way.
Uri Uri = new Uri(Request.Url.AbsoluteUri);
NameValueCollection nvColl = HttpUtility.ParseQueryString(Uri.Query);
string param1value = nvColl["param1"];
On the receiving end the Request.QueryString also retrieves the NameValueCollection format of querystring values. The difference between HttpUtility.ParseQueryString and Request.QueryString is the former requires the complete url as input where as the later doesn’t require.
So HttpUtility.ParseQueryString can be used not only on the receiving end but on any code behind file when developer requires to split and play with the url to read the querystring values.
5.UrlPathEncode Method
Purpose
UrlPathEncode is similar to UrlEncode method which is used to encode the url. The only difference between two is UrlEncode replaces space with “+” and UrlPathEncode replaces space with “%20”.
The other important point is using UrlPathEncode we can only encode portion of the url i.e strings. If we try to encode entire url it won’t get encoded.
For instance if we try to encode in the following way, the url is not encoded
Eg:
string strUrl = "http://localhost:9618/WebForm1.aspx?names=suresh satheesh vidjay";
strUrl = HttpUtility.UrlPathEncode(strUrl);
We can use UrlPathEncode in the following way to encode only portion of querystring
Eg:
string strUrl = "http://localhost:9618/WebForm1.aspx?names" + HttpUtility.UrlPathEncode("suresh satheesh vijay");
Here is the list of the HttpUtility methods, I will be explaining in this article.
1. UrlEncode/UrlDecode
2. HtmlEncode/HtmlDecode
3. HtmlAttributeEncode
4. ParseQueryString
5. UrlPathEncode
1.UrlEncode/UrlDecode Method
Purpose
The main purpose of these two methods is to encode and decode the urls respectively. We need to encode the urls because some of the characters are not safe sending those across browser. Some characters are being misunderstood by the browser and that leads to data mismatch on the receiving end. Characters such as a question mark (?), ampersand (&), slash mark (/), and spaces might be truncated or corrupted by some browsers.
For instance user requires sending the data in the url querystrings in the following format
Eg:
string Url = "http://localhost:9618/WebForm1.aspx?" + "names=suresh#satheesh#vijay#";
On the receiving end if the developer tries to read the url querystring values in the following way
string QueryStringValues = Request.QueryString["names"];
the developers reads it as “suresh” which is not the expected result
The workaround for this we need to encode the url and read the querystring values to get the expected result.
UrlEncode
Eg:
string Url = "http://localhost:9618/WebForm1.aspx?" + HttpUtility.UrlEncode("names=suresh#satheesh#vijay");
Response.Redirect(Url);
When we use the UrlEncode for the above url, the new url looks in the following way:
http://localhost:9618/WebForm1.aspx?names=suresh%23satheesh%23vijay
When the user tries to fetch the querystring values on the receiving end, the expected result is “suresh#satheesh#vijay”
Eg:
string QueryStringValues = Request.QueryString["names"];
Response.Write(QueryStringValues);
UrlDecode
This method is used to convert the encoded url back into a decoded string.
If you want to decode the entire url on the receiving end, just decode in the following way.
Eg:
string Url = HttpUtility.UrlDecode(Request.RawUrl);
The expected result would be "/WebForm1.aspx?names=suresh#satheesh#vijay” ;
We can also decode only the querystring values on the receiving end in the following way,
Eg:
string QueryStringValues = Request.QueryString["names"];
QueryStringValues = HttpUtility.UrlDecode(QueryStringValues);
2. HtmlEnCode/HtmlDecode Method
HtmlEncode -Purpose
The main purpose of HtmlEncode is to convert a HTML-encoded string for reliable HTTP transmission from the Web server to a client. It mainly converts the characters that are not allowed in Html like “<”,”>”, spaces and punctuation marks. For example, < is replaced with < and " is replaced with ".
HtmlDecode converts back to the original string which is html encoded.
For instance user has a requirement where he wants to display the data in the following way
Eg: “
If the developer uses the following code, it is misinterpreted on the browser receiving end
Label1.Text = "
To achive this we need use the HtmlEncode for displaying the correct data.
Eg:
Label1.Text = HttpUtility.HtmlEncode("
Also Htmlencoding is used to avoid Cross-site scripting.
3. HtmlAttributeEncode Method
Purpose
This method is used to encode only certain characters like Quotation marks (“), ampersands (&), and left angle brackets. Performance wise, this method is faster than the HtmlEncode.
Eg:
Response.Write(HttpUtility.HtmlAttributeEncode(TextBox3.Text));
4.ParseQueryString Method
Purpose
This method is used to retrieve the querystring values of an url in the NameValueCollection format. This helps developers for traversing easily to read the querystring values when there are multiple querystring values in the url. The NameValueCollection in .Net framework comes with namespace System.Collections.Specialized.
Let’s say user has an url with querystring values on the receiving end then ParseQueryString is used for retrieving the values.
Eg:
string Url = "http://localhost:3873/WebForm1.aspx?" + "param1=val1¶m2=val2¶m3=val3¶m4=val4¶m5=val5";
Response.Redirect(Url);
On the receiving end or code behind we can read the querystring values using the ParseQueryString in the following way.
Uri Uri = new Uri(Request.Url.AbsoluteUri);
NameValueCollection nvColl = HttpUtility.ParseQueryString(Uri.Query);
string param1value = nvColl["param1"];
On the receiving end the Request.QueryString also retrieves the NameValueCollection format of querystring values. The difference between HttpUtility.ParseQueryString and Request.QueryString is the former requires the complete url as input where as the later doesn’t require.
So HttpUtility.ParseQueryString can be used not only on the receiving end but on any code behind file when developer requires to split and play with the url to read the querystring values.
5.UrlPathEncode Method
Purpose
UrlPathEncode is similar to UrlEncode method which is used to encode the url. The only difference between two is UrlEncode replaces space with “+” and UrlPathEncode replaces space with “%20”.
The other important point is using UrlPathEncode we can only encode portion of the url i.e strings. If we try to encode entire url it won’t get encoded.
For instance if we try to encode in the following way, the url is not encoded
Eg:
string strUrl = "http://localhost:9618/WebForm1.aspx?names=suresh satheesh vidjay";
strUrl = HttpUtility.UrlPathEncode(strUrl);
We can use UrlPathEncode in the following way to encode only portion of querystring
Eg:
string strUrl = "http://localhost:9618/WebForm1.aspx?names" + HttpUtility.UrlPathEncode("suresh satheesh vijay");
Understanding Web Security Vulnerabilities and Preventing it in Web Applications
Internet has become one of the most important and widely used communication medium in this modern world. Be it knowledge gathering or information sharing or social networking, internet plays a vital role. Since everything is automated and people wants everything to be done sitting in front of the computer, the internet age has also brought more threats and security nightmares on our web assets in the form of hackers, spammers and automated bots and viruses.
Moving forward, let’s understand the most common security threats and vulnerabilities that can be found in web applications and the ways of preventing it. Even though, this article uses ASP.Net terms all the vulnerabilities are applicable to any web applications that is developed on any platforms.
How to Build a Secured ASP.Net Website?
In coming section, we will see more about the below security threats and different ways to prevent the same.
Code Injection Attacks
Session Hijacking
Identity spoofing
Network eavesdropping
Information disclosure
Web Security Testing
Free Web Security Testing Tools
Code Injection Attacks
This is one of the very common attacks where a malicious user can inject some arbitrary scripts into our application. The injected code will then get executed in the application identity to do the intended damage.
Sql Injection and Cross Site Scripting (XSS) attacks are the 2 common attacks under this category. Read the below codedigest articles to know more about these attacks and preventing it.
What is SQL Injection Attack? How to prevent SQL Injection in ASP.Net?
What is Cross-Site Scripting (XSS) attack? How to prevent XSS attack in ASP.Net?
Session Hijacking
Session is a unique identifier generated by a web application to identify a connected/authenticated user. These identifiers are often stored in cookies and urls that travel forth and back the web server. In this attack, an attacker can steal a user’s session id and connect to the website with that session. The web application will receive the attacker’s request and will allow the attacker to access anything the user has access to.
How it is done?
1. An attacker can use XSS attack and inject some scripts which will then send the cookie information to the attacker. This specific attack where an attacker hijacking a user’s cookie is called Cookie Replay attack.
2. An attacker can use some network sniffer tools and get the sensitive data that gets transported in the network from the website. This is commonly called as Network eavesdropping.
How to prevent Session Hijacking?
1. Prevent Cross-Site Scripting vulnerability in your websites. Read the above article.
2. Prevent storing some secured information in Hidden Fields, Query Strings, View State, Form Fields, etc. This is called Web Parameter Tampering or Parameter manipulation attack.
3. Always use SSL certificates (https) on authentication pages and in the modules which does some secured transactions.
4. Don’t allow multiple users to connect to the same session from different machines. You can do this by restricting user to connect only from one IP.
5. Re-Authenticate users when doing secured transactions.
6. Verify the user by asking some security verification questions when doing secured transactions.
Identity spoofing
Identity spoofing is a mechanism where an attacker steals the identity of an existing user of your application and gaining access to restricted sections. This is normally done by guessing the username and password, stealing username and password in non-SSL communication link.
How it is done?
1. Guessing the username and passwords. The attacker can use Brute force attack or Dictionary attack to do this. This will happen when there is no password policy implemented in your websites.
2. Using network sniffer tools to read the protected data sent over non-SSL communication links i.e. Network eavesdropping attack.
3. Using Sql Injection attack. An attacker can get the username and password in application when the application stores password as clear text in database.
How to prevent Identity Spoofing?
1. Have a strong password policy. For example, accept the passwords that have at least one number and a symbol with a minimum length of 8 characters when registering the user.
2. Always use SSL certificates (https) on authentication pages and in the modules that does some secured transactions.
3. Prevent code injection attacks.
4. Display last visited time and the IP from where the user visited to ensure when re-authenticating to the site. This will enable users to reset the password when there is a suspicion.
5. If required, make the users to change the password every 3 months as part of password policy.
Web Parameter Tampering or Parameter manipulation
Form fields, View State, Query String are vulnerable to this attack. A malicious user can get hold of the sensitive information that is sent through these parameters and can exploit your application.
How it is done?
1. Using network sniffer tools to read the protected data sent over non-SSL communication links i.e. Network eavesdropping attack.
2. An attacker can steal the session id from query string and gain access to restricted area.
3. An attacker can steal the cookies and gain access to restricted area.
How to prevent Web Parameter Tampering or Parameter manipulation?
1. Always use SSL certificates (https) on authentication pages and in the modules that does some secured transactions.
2. Don’t use persistent cookies for storing authentication tokens (session ids).
3. As a user, don’t select “Remember password” option in Logon screen in a public computer.
Network eavesdropping
An attacker can use some network monitoring tools to obtain some sensitive data from the data packets sent between web server and the user.
How it is done?
Using network sniffer tools to read the protected data sent over non-SSL communication links i.e. Network eavesdropping attack.
How to prevent Web Parameter Tampering or Parameter manipulation?
1. Always use SSL certificates (https) on authentication pages and in the modules that does some secured transactions.
2. Prevent storing some secured information in Hidden Fields, Query Strings, View State, Form Fields, etc. This is called Web Parameter Tampering or Parameter manipulation attack.
Information disclosure
This can occur when our application use weak exception handling techniques. For example, our application may throw technical details with the exception details when we have not properly handled exceptions. The details like database name, schema details, network path details, etc may get exposed outside.
How it is done?
An attacker can exploit your application by giving appropriate inputs that can generate exceptions with sensitive information.
Consider the below exception message,
System.Data.SqlClient.SqlException: Invalid object name 'Employees1'.
The above message clearly says, there is no object called Employees1 in your database. An application that has Sql injection vulnerability can be exploited easily using the above details. The attacker can guess different table names specific to the functionality the page offers and finally get the correct table name and can do the damage.
How to prevent Information disclosure vulnerability?
1. Display generic error message in case of exception. You can log the detailed exception, stack trace for debugging purposes.
2. Redirect to generic error page when any error occurs.
3. Never set the mode attribute to “Off” in element in Web.Config.
4. Do not frame the error message with specific information that could hint an attacker to achieve his target. For example, in a login page never show an error messages like “Your userid is invalid” or “Your password does not match the typed userid”. This tells the attacker that the typed in userid is invalid and the former hints that the userid is valid and the password is invalid. You can instead show a generic message like “Login failed. Try again”.
Web Security Testing
Understanding the importance of building secured web applications, nowadays most of the companies have security testing as part of their SDLC process. There is lots of free and commercial automated web security testing tools available which can be employed for this. Some of them are,
1. Websecurify
2. x5s
3. N-Stalker
4. WebCruiser
Just download anyone of the above application and try using it.
Things to consider
1. As a practice, always provide very minimal permission to the application service account.
2. Separate each application by creating separate application pool when deploying.
3. Never use sa account or any account with admin privilege to connect to your database. Provide very minimal permission to the service account.
4. If you use SQL server authentication to connect to your database or if you use shared hosting, then consider encrypting the connection string in Web.Config.
5. Always have generic error page. Show only generic error messages. Set either “On” or “RemoteOnly” option to mode attribute of element in Web.Config. Never set “Off”.
6. Never set ValidateRequest="True" at Web.Config level. If you want to allow HTML input from users, the set ValidateRequest="True" at page level(In @Page directive)
7. Never turn off the EnableViewStateMac. This attribute will dictate ASP.Net whether to verify or not to verify the ViewState value when the page is posted back to server. If the ViewState is changed when it is posted back then ASP.Net will throw an exception.
8. Always use CAPTCHA to verify the users in input forms.
9. Never send the passwords in email.
10. Always do input validations on server side even though you have JavaScript validation in place. Remember, JavaScript can be turned off in client side and the validations can be easily bye-passed.
11. Consider re-authenticating users when doing critical transaction. Or you can have a separate transaction password if required.
12. Use the error messages that are more generic. Never frame an error message that is very subjective which could hint an attacker.
Moving forward, let’s understand the most common security threats and vulnerabilities that can be found in web applications and the ways of preventing it. Even though, this article uses ASP.Net terms all the vulnerabilities are applicable to any web applications that is developed on any platforms.
How to Build a Secured ASP.Net Website?
In coming section, we will see more about the below security threats and different ways to prevent the same.
Code Injection Attacks
Session Hijacking
Identity spoofing
Network eavesdropping
Information disclosure
Web Security Testing
Free Web Security Testing Tools
Code Injection Attacks
This is one of the very common attacks where a malicious user can inject some arbitrary scripts into our application. The injected code will then get executed in the application identity to do the intended damage.
Sql Injection and Cross Site Scripting (XSS) attacks are the 2 common attacks under this category. Read the below codedigest articles to know more about these attacks and preventing it.
What is SQL Injection Attack? How to prevent SQL Injection in ASP.Net?
What is Cross-Site Scripting (XSS) attack? How to prevent XSS attack in ASP.Net?
Session Hijacking
Session is a unique identifier generated by a web application to identify a connected/authenticated user. These identifiers are often stored in cookies and urls that travel forth and back the web server. In this attack, an attacker can steal a user’s session id and connect to the website with that session. The web application will receive the attacker’s request and will allow the attacker to access anything the user has access to.
How it is done?
1. An attacker can use XSS attack and inject some scripts which will then send the cookie information to the attacker. This specific attack where an attacker hijacking a user’s cookie is called Cookie Replay attack.
2. An attacker can use some network sniffer tools and get the sensitive data that gets transported in the network from the website. This is commonly called as Network eavesdropping.
How to prevent Session Hijacking?
1. Prevent Cross-Site Scripting vulnerability in your websites. Read the above article.
2. Prevent storing some secured information in Hidden Fields, Query Strings, View State, Form Fields, etc. This is called Web Parameter Tampering or Parameter manipulation attack.
3. Always use SSL certificates (https) on authentication pages and in the modules which does some secured transactions.
4. Don’t allow multiple users to connect to the same session from different machines. You can do this by restricting user to connect only from one IP.
5. Re-Authenticate users when doing secured transactions.
6. Verify the user by asking some security verification questions when doing secured transactions.
Identity spoofing
Identity spoofing is a mechanism where an attacker steals the identity of an existing user of your application and gaining access to restricted sections. This is normally done by guessing the username and password, stealing username and password in non-SSL communication link.
How it is done?
1. Guessing the username and passwords. The attacker can use Brute force attack or Dictionary attack to do this. This will happen when there is no password policy implemented in your websites.
2. Using network sniffer tools to read the protected data sent over non-SSL communication links i.e. Network eavesdropping attack.
3. Using Sql Injection attack. An attacker can get the username and password in application when the application stores password as clear text in database.
How to prevent Identity Spoofing?
1. Have a strong password policy. For example, accept the passwords that have at least one number and a symbol with a minimum length of 8 characters when registering the user.
2. Always use SSL certificates (https) on authentication pages and in the modules that does some secured transactions.
3. Prevent code injection attacks.
4. Display last visited time and the IP from where the user visited to ensure when re-authenticating to the site. This will enable users to reset the password when there is a suspicion.
5. If required, make the users to change the password every 3 months as part of password policy.
Web Parameter Tampering or Parameter manipulation
Form fields, View State, Query String are vulnerable to this attack. A malicious user can get hold of the sensitive information that is sent through these parameters and can exploit your application.
How it is done?
1. Using network sniffer tools to read the protected data sent over non-SSL communication links i.e. Network eavesdropping attack.
2. An attacker can steal the session id from query string and gain access to restricted area.
3. An attacker can steal the cookies and gain access to restricted area.
How to prevent Web Parameter Tampering or Parameter manipulation?
1. Always use SSL certificates (https) on authentication pages and in the modules that does some secured transactions.
2. Don’t use persistent cookies for storing authentication tokens (session ids).
3. As a user, don’t select “Remember password” option in Logon screen in a public computer.
Network eavesdropping
An attacker can use some network monitoring tools to obtain some sensitive data from the data packets sent between web server and the user.
How it is done?
Using network sniffer tools to read the protected data sent over non-SSL communication links i.e. Network eavesdropping attack.
How to prevent Web Parameter Tampering or Parameter manipulation?
1. Always use SSL certificates (https) on authentication pages and in the modules that does some secured transactions.
2. Prevent storing some secured information in Hidden Fields, Query Strings, View State, Form Fields, etc. This is called Web Parameter Tampering or Parameter manipulation attack.
Information disclosure
This can occur when our application use weak exception handling techniques. For example, our application may throw technical details with the exception details when we have not properly handled exceptions. The details like database name, schema details, network path details, etc may get exposed outside.
How it is done?
An attacker can exploit your application by giving appropriate inputs that can generate exceptions with sensitive information.
Consider the below exception message,
System.Data.SqlClient.SqlException: Invalid object name 'Employees1'.
The above message clearly says, there is no object called Employees1 in your database. An application that has Sql injection vulnerability can be exploited easily using the above details. The attacker can guess different table names specific to the functionality the page offers and finally get the correct table name and can do the damage.
How to prevent Information disclosure vulnerability?
1. Display generic error message in case of exception. You can log the detailed exception, stack trace for debugging purposes.
2. Redirect to generic error page when any error occurs.
3. Never set the mode attribute to “Off” in
4. Do not frame the error message with specific information that could hint an attacker to achieve his target. For example, in a login page never show an error messages like “Your userid is invalid” or “Your password does not match the typed userid”. This tells the attacker that the typed in userid is invalid and the former hints that the userid is valid and the password is invalid. You can instead show a generic message like “Login failed. Try again”.
Web Security Testing
Understanding the importance of building secured web applications, nowadays most of the companies have security testing as part of their SDLC process. There is lots of free and commercial automated web security testing tools available which can be employed for this. Some of them are,
1. Websecurify
2. x5s
3. N-Stalker
4. WebCruiser
Just download anyone of the above application and try using it.
Things to consider
1. As a practice, always provide very minimal permission to the application service account.
2. Separate each application by creating separate application pool when deploying.
3. Never use sa account or any account with admin privilege to connect to your database. Provide very minimal permission to the service account.
4. If you use SQL server authentication to connect to your database or if you use shared hosting, then consider encrypting the connection string in Web.Config.
5. Always have generic error page. Show only generic error messages. Set either “On” or “RemoteOnly” option to mode attribute of
6. Never set ValidateRequest="True" at Web.Config level. If you want to allow HTML input from users, the set ValidateRequest="True" at page level(In @Page directive)
7. Never turn off the EnableViewStateMac. This attribute will dictate ASP.Net whether to verify or not to verify the ViewState value when the page is posted back to server. If the ViewState is changed when it is posted back then ASP.Net will throw an exception.
8. Always use CAPTCHA to verify the users in input forms.
9. Never send the passwords in email.
10. Always do input validations on server side even though you have JavaScript validation in place. Remember, JavaScript can be turned off in client side and the validations can be easily bye-passed.
11. Consider re-authenticating users when doing critical transaction. Or you can have a separate transaction password if required.
12. Use the error messages that are more generic. Never frame an error message that is very subjective which could hint an attacker.
Taskbar Notification through ASP.Net
Introduction
I coded a MSN Messenger-like skinnable popup, with a close button which looks almost like Microsoft's one (with the associated skin).
The TaskbarNotifier class inherits from System.Windows.Forms.Form and adds a few methods to it.
Features
The MSN messenger like popup supports:
* A custom transparent bitmap background
* A skinnable 3-State close button
* A clickable title text
* A clickable content text
* A selection rectangle
* Custom fonts and colors for the different states of the text (normal/hover)
* Animation speed parameters
Compatibility
This class is stand alone and doesn't need any particular libraries except .NET default ones. It runs in managed code and hence should be portable.
How to use the class
* First of all, copy TaskbarNotifier.cs in your project directory.
* Add on the top of your source code the directive:
using CustomUIControls;
* Add a member variable in your class:
TaskbarNotifier taskbarNotifier;
* In your constructor add the following lines:
taskbarNotifier=new TaskbarNotifier();
taskbarNotifier.SetBackgroundBitmap("skin.bmp",
Color.FromArgb(255,0,255));
taskbarNotifier.SetCloseBitmap("close.bmp",
Color.FromArgb(255,0,255),new Point(127,8));
taskbarNotifier.TitleRectangle=new Rectangle(40,9,70,25);
taskbarNotifier.ContentRectangle=new Rectangle(8,41,133,68);
taskbarNotifier.TitleClick+=new EventHandler(TitleClick);
taskbarNotifier.ContentClick+=new EventHandler(ContentClick);
taskbarNotifier.CloseClick+=new EventHandler(CloseClick);
Details:
taskbarNotifier.SetBackgroundBitmap("skin.bmp",
Color.FromArgb(255,0,255));
taskbarNotifier.SetCloseBitmap("close.bmp",
Color.FromArgb(255,0,255),new Point(127,8));
The first line sets the background bitmap skin and transparency color (must be present), and the second line sets the 3-State close button with its transparency color and its location on the window (this line is optional if you don't want a close button).
taskbarNotifier.TitleRectangle=new Rectangle(40,9,70,25);
taskbarNotifier.ContentRectangle=new Rectangle(8,41,133,68);
These two lines allow us to define the rectangles in which will be displayed, the title and content texts.
taskbarNotifier.TitleClick+=new EventHandler(OnTitleClick);
taskbarNotifier.ContentClick+=new EventHandler(OnContentClick);
taskbarNotifier.CloseClick+=new EventHandler(OnCloseClick);
These 3 lines allow us to intercept events on the popup such as title/content or close button have been clicked
* Then we are done, we just need to call:
taskbarNotifier.Show("TitleText","ContentText",500,3000,500);
This will show the popup animation with the showing/visible/hiding animations time set as 500ms/3000ms/500ms.
You can play with a few properties:
* Title, content fonts and colors
* Ability to click or not on the title/content/close button
* You can disable the focus rect
* ... (see below for more details)
Class documentation
Methods
void Show(string strTitle, string strContent, int nTimeToShow,
int nTimeToStay, int nTimeToHide)
Displays the popup for a certain amount of time.
Parameters
* strTitle: The string which will be shown as the title of the popup
* strContent: The string which will be shown as the content of the popup
* nTimeToShow: Duration of the showing animation (in milliseconds)
* nTimeToStay: Duration of the visible state before collapsing (in milliseconds)
* nTimeToHide: Duration of the hiding animation (in milliseconds)
void Hide()
Forces the popup to hide.
void SetBackgroundBitmap(string strFilename, Color transparencyColor)
Sets the background bitmap and its transparency color.
Parameters
* strFilename: Path of the background bitmap on the disk
* transparencyColor: Color of the bitmap which won't be visible
void SetBackgroundBitmap(Image image, Color transparencyColor)
Sets the background bitmap and its transparency color.
Parameters
# image: Background bitmap
# transparencyColor: Color of the bitmap which won't be visible
void SetCloseBitmap(string strFilename,
Color transparencyColor, Point position)
Sets the 3-State close button bitmap, its transparency color and its coordinates.
Parameters
* strFilename: Path of the 3-state close button bitmap on the disk (width must be a multiple of 3)
* transparencyColor: Color of the bitmap which won't be visible
* position: Location of the close button on the popup
void SetCloseBitmap(Image image, Color transparencyColor, Point position)
Sets the 3-State close button bitmap, its transparency color and its coordinates.
Parameters
* image: Image/Bitmap object which represents the 3-state close button bitmap (width must be a multiple of 3)
* transparencyColor: Color of the bitmap which won't be visible
* position: Location of the close button on the popup
Properties
string TitleText (get/set)
string ContentText (get/set)
TaskbarStates TaskbarState (get)
Color NormalTitleColor (get/set)
Color HoverTitleColor (get/set)
Color NormalContentColor (get/set)
Color HoverContentColor (get/set)
Font NormalTitleFont (get/set)
Font HoverTitleFont (get/set)
Font NormalContentFont (get/set)
Font HoverContentFont (get/set)
Rectangle TitleRectangle (get/set) //must be defined before calling show())
Rectangle ContentRectangle (get/set) //must be defined before calling show())
bool TitleClickable (get/set) (default = false);
bool ContentClickable (get/set) (default = true);
bool CloseClickable (get/set) (default = true);
bool EnableSelectionRectangle (get/set) (default = true);
Events
event EventHandler CloseClick
event EventHandler TitleClick
event EventHandler ContentClick
Technical issues
The popup is skinned using a region generated dynamically from a bitmap and a transparency color:
protected Region BitmapToRegion(Bitmap bitmap, Color transparencyColor)
{
if (bitmap == null)
throw new ArgumentNullException("Bitmap", "Bitmap cannot be null!");
int height = bitmap.Height;
int width = bitmap.Width;
GraphicsPath path = new GraphicsPath();
for (int j=0; j for (int i=0; i {
if (bitmap.GetPixel(i, j) == transparencyColor)
continue;
int x0 = i;
while ((i < width) &&
(bitmap.GetPixel(i, j) != transparencyColor))
i++;
path.AddRectangle(new Rectangle(x0, j, i-x0, 1));
}
Region region = new Region(path);
path.Dispose();
return region;
}
The refresh() of the popup is done using the double buffering technique to avoid flickering:
protected override void OnPaintBackground(PaintEventArgs pea)
{
Graphics grfx = pea.Graphics;
grfx.PageUnit = GraphicsUnit.Pixel;
Graphics offScreenGraphics;
Bitmap offscreenBitmap;
offscreenBitmap = new Bitmap(BackgroundBitmap.Width,
BackgroundBitmap.Height);
offScreenGraphics = Graphics.FromImage(offscreenBitmap);
if (BackgroundBitmap != null)
{
offScreenGraphics.DrawImage(BackgroundBitmap,
0, 0, BackgroundBitmap.Width, BackgroundBitmap.Height);
}
DrawCloseButton(offScreenGraphics);
DrawText(offScreenGraphics);
grfx.DrawImage(offscreenBitmap, 0, 0);
}
Bugs/Limitations
Since I wanted to keep only managed code, I used the Screen.GetWorkingArea(WorkAreaRectangle) function instead of using unmanaged code to get the taskbar position. As a result, I made the popup always appear at the bottom of WorkAreaRectangle whichever position the taskbar has.
I didn't find any C# managed equivalent to the Win32 function ShowWindow(SW_SHOWNOACTIVATE) to make the popup, not steal the focus of the active window.
Code for TaskbarNotification.cs
// C# TaskbarNotifier Class v1.0
// by John O'Byrne - 02 december 2002
// 01 april 2003 : Small fix in the OnMouseUp handler
// 11 january 2003 : Patrick Vanden Driessche added a few enhancements
// Small Enhancements/Bugfix
// Small bugfix: When Content text measures larger than the corresponding ContentRectangle
// the focus rectangle was not correctly drawn. This has been solved.
// Added KeepVisibleOnMouseOver
// Added ReShowOnMouseOver
// Added If the Title or Content are too long to fit in the corresponding Rectangles,
// the text is truncateed and the ellipses are appended (StringTrimming).
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace CustomUIControls
{
///
/// TaskbarNotifier allows to display MSN style/Skinned instant messaging popups
///
public class TaskbarNotifier : System.Windows.Forms.Form
{
#region TaskbarNotifier Protected Members
protected Bitmap BackgroundBitmap = null;
protected Bitmap CloseBitmap = null;
protected Point CloseBitmapLocation;
protected Size CloseBitmapSize;
protected Rectangle RealTitleRectangle;
protected Rectangle RealContentRectangle;
protected Rectangle WorkAreaRectangle;
protected Timer timer = new Timer();
protected TaskbarStates taskbarState = TaskbarStates.hidden;
protected string titleText;
protected string contentText;
protected Color normalTitleColor = Color.FromArgb(255,0,0);
protected Color hoverTitleColor = Color.FromArgb(255,0,0);
protected Color normalContentColor = Color.FromArgb(0,0,0);
protected Color hoverContentColor = Color.FromArgb(0,0,0x66);
protected Font normalTitleFont = new Font("Arial",12,FontStyle.Regular,GraphicsUnit.Pixel);
protected Font hoverTitleFont = new Font("Arial",12,FontStyle.Bold,GraphicsUnit.Pixel);
protected Font normalContentFont = new Font("Arial",11,FontStyle.Regular,GraphicsUnit.Pixel);
protected Font hoverContentFont = new Font("Arial",11,FontStyle.Regular,GraphicsUnit.Pixel);
protected int nShowEvents;
protected int nHideEvents;
protected int nVisibleEvents;
protected int nIncrementShow;
protected int nIncrementHide;
protected bool bIsMouseOverPopup = false;
protected bool bIsMouseOverClose = false;
protected bool bIsMouseOverContent = false;
protected bool bIsMouseOverTitle = false;
protected bool bIsMouseDown = false;
protected bool bKeepVisibleOnMouseOver = true; // Added Rev 002
protected bool bReShowOnMouseOver = false; // Added Rev 002
#endregion
#region TaskbarNotifier Public Members
public Rectangle TitleRectangle;
public Rectangle ContentRectangle;
public bool TitleClickable = false;
public bool ContentClickable = true;
public bool CloseClickable = true;
public bool EnableSelectionRectangle = true;
public event EventHandler CloseClick = null;
public event EventHandler TitleClick = null;
public event EventHandler ContentClick = null;
#endregion
#region TaskbarNotifier Enums
///
/// List of the different popup animation status
///
public enum TaskbarStates
{
hidden = 0,
appearing = 1,
visible = 2,
disappearing = 3
}
#endregion
#region TaskbarNotifier Constructor
///
/// The Constructor for TaskbarNotifier
///
public TaskbarNotifier()
{
// Window Style
FormBorderStyle = FormBorderStyle.None;
WindowState = FormWindowState.Minimized;
base.Show();
base.Hide();
WindowState = FormWindowState.Normal;
ShowInTaskbar = false;
TopMost = true;
MaximizeBox = false;
MinimizeBox = false;
ControlBox = false;
timer.Enabled = true;
timer.Tick += new EventHandler(OnTimer);
}
#endregion
#region TaskbarNotifier Properties
///
/// Get the current TaskbarState (hidden, showing, visible, hiding)
///
public TaskbarStates TaskbarState
{
get
{
return taskbarState;
}
}
///
/// Get/Set the popup Title Text
///
public string TitleText
{
get
{
return titleText;
}
set
{
titleText=value;
Refresh();
}
}
///
/// Get/Set the popup Content Text
///
public string ContentText
{
get
{
return contentText;
}
set
{
contentText=value;
Refresh();
}
}
///
/// Get/Set the Normal Title Color
///
public Color NormalTitleColor
{
get
{
return normalTitleColor;
}
set
{
normalTitleColor = value;
Refresh();
}
}
///
/// Get/Set the Hover Title Color
///
public Color HoverTitleColor
{
get
{
return hoverTitleColor;
}
set
{
hoverTitleColor = value;
Refresh();
}
}
///
/// Get/Set the Normal Content Color
///
public Color NormalContentColor
{
get
{
return normalContentColor;
}
set
{
normalContentColor = value;
Refresh();
}
}
///
/// Get/Set the Hover Content Color
///
public Color HoverContentColor
{
get
{
return hoverContentColor;
}
set
{
hoverContentColor = value;
Refresh();
}
}
///
/// Get/Set the Normal Title Font
///
public Font NormalTitleFont
{
get
{
return normalTitleFont;
}
set
{
normalTitleFont = value;
Refresh();
}
}
///
/// Get/Set the Hover Title Font
///
public Font HoverTitleFont
{
get
{
return hoverTitleFont;
}
set
{
hoverTitleFont = value;
Refresh();
}
}
///
/// Get/Set the Normal Content Font
///
public Font NormalContentFont
{
get
{
return normalContentFont;
}
set
{
normalContentFont = value;
Refresh();
}
}
///
/// Get/Set the Hover Content Font
///
public Font HoverContentFont
{
get
{
return hoverContentFont;
}
set
{
hoverContentFont = value;
Refresh();
}
}
///
/// Indicates if the popup should remain visible when the mouse pointer is over it.
/// Added Rev 002
///
public bool KeepVisibleOnMousOver
{
get
{
return bKeepVisibleOnMouseOver;
}
set
{
bKeepVisibleOnMouseOver=value;
}
}
///
/// Indicates if the popup should appear again when mouse moves over it while it's disappearing.
/// Added Rev 002
///
public bool ReShowOnMouseOver
{
get
{
return bReShowOnMouseOver;
}
set
{
bReShowOnMouseOver=value;
}
}
#endregion
#region TaskbarNotifier Public Methods
[DllImport("user32.dll")]
private static extern Boolean ShowWindow(IntPtr hWnd,Int32 nCmdShow);
///
/// Displays the popup for a certain amount of time
///
public void Show(string strTitle, string strContent, int nTimeToShow, int nTimeToStay, int nTimeToHide)
{
WorkAreaRectangle = Screen.GetWorkingArea(WorkAreaRectangle);
titleText = strTitle;
contentText = strContent;
nVisibleEvents = nTimeToStay;
CalculateMouseRectangles();
// We calculate the pixel increment and the timer value for the showing animation
int nEvents;
if (nTimeToShow > 10)
{
nEvents = Math.Min((nTimeToShow / 10), BackgroundBitmap.Height);
nShowEvents = nTimeToShow / nEvents;
nIncrementShow = BackgroundBitmap.Height / nEvents;
}
else
{
nShowEvents = 10;
nIncrementShow = BackgroundBitmap.Height;
}
// We calculate the pixel increment and the timer value for the hiding animation
if( nTimeToHide > 10)
{
nEvents = Math.Min((nTimeToHide / 10), BackgroundBitmap.Height);
nHideEvents = nTimeToHide / nEvents;
nIncrementHide = BackgroundBitmap.Height / nEvents;
}
else
{
nHideEvents = 10;
nIncrementHide = BackgroundBitmap.Height;
}
switch (taskbarState)
{
case TaskbarStates.hidden:
taskbarState = TaskbarStates.appearing;
SetBounds(WorkAreaRectangle.Right-BackgroundBitmap.Width-17, WorkAreaRectangle.Bottom-1, BackgroundBitmap.Width, 0);
timer.Interval = nShowEvents;
timer.Start();
// We Show the popup without stealing focus
ShowWindow(this.Handle, 4);
break;
case TaskbarStates.appearing:
Refresh();
break;
case TaskbarStates.visible:
timer.Stop();
timer.Interval = nVisibleEvents;
timer.Start();
Refresh();
break;
case TaskbarStates.disappearing:
timer.Stop();
taskbarState = TaskbarStates.visible;
SetBounds(WorkAreaRectangle.Right-BackgroundBitmap.Width-17, WorkAreaRectangle.Bottom-BackgroundBitmap.Height-1, BackgroundBitmap.Width, BackgroundBitmap.Height);
timer.Interval = nVisibleEvents;
timer.Start();
Refresh();
break;
}
}
///
/// Hides the popup
///
///Nothing
public new void Hide()
{
if (taskbarState != TaskbarStates.hidden)
{
timer.Stop();
taskbarState = TaskbarStates.hidden;
base.Hide();
}
}
///
/// Sets the background bitmap and its transparency color
///
public void SetBackgroundBitmap(string strFilename, Color transparencyColor)
{
BackgroundBitmap = new Bitmap(strFilename);
Width = BackgroundBitmap.Width;
Height = BackgroundBitmap.Height;
Region = BitmapToRegion(BackgroundBitmap, transparencyColor);
}
///
/// Sets the background bitmap and its transparency color
///
public void SetBackgroundBitmap(Image image, Color transparencyColor)
{
BackgroundBitmap = new Bitmap(image);
Width = BackgroundBitmap.Width;
Height = BackgroundBitmap.Height;
Region = BitmapToRegion(BackgroundBitmap, transparencyColor);
}
///
/// Sets the 3-State Close Button bitmap, its transparency color and its coordinates
///
public void SetCloseBitmap(string strFilename, Color transparencyColor, Point position)
{
CloseBitmap = new Bitmap(strFilename);
CloseBitmap.MakeTransparent(transparencyColor);
CloseBitmapSize = new Size(CloseBitmap.Width/3, CloseBitmap.Height);
CloseBitmapLocation = position;
}
///
/// Sets the 3-State Close Button bitmap, its transparency color and its coordinates
///
public void SetCloseBitmap(Image image, Color transparencyColor, Point position)
{
CloseBitmap = new Bitmap(image);
CloseBitmap.MakeTransparent(transparencyColor);
CloseBitmapSize = new Size(CloseBitmap.Width/3, CloseBitmap.Height);
CloseBitmapLocation = position;
}
#endregion
#region TaskbarNotifier Protected Methods
protected void DrawCloseButton(Graphics grfx)
{
if (CloseBitmap != null)
{
Rectangle rectDest = new Rectangle(CloseBitmapLocation, CloseBitmapSize);
Rectangle rectSrc;
if (bIsMouseOverClose)
{
if (bIsMouseDown)
rectSrc = new Rectangle(new Point(CloseBitmapSize.Width*2, 0), CloseBitmapSize);
else
rectSrc = new Rectangle(new Point(CloseBitmapSize.Width, 0), CloseBitmapSize);
}
else
rectSrc = new Rectangle(new Point(0, 0), CloseBitmapSize);
grfx.DrawImage(CloseBitmap, rectDest, rectSrc, GraphicsUnit.Pixel);
}
}
protected void DrawText(Graphics grfx)
{
if (titleText != null && titleText.Length != 0)
{
StringFormat sf = new StringFormat();
sf.Alignment = StringAlignment.Near;
sf.LineAlignment = StringAlignment.Center;
sf.FormatFlags = StringFormatFlags.NoWrap;
sf.Trimming = StringTrimming.EllipsisCharacter; // Added Rev 002
if (bIsMouseOverTitle)
grfx.DrawString(titleText, hoverTitleFont, new SolidBrush(hoverTitleColor), TitleRectangle, sf);
else
grfx.DrawString(titleText, normalTitleFont, new SolidBrush(normalTitleColor), TitleRectangle, sf);
}
if (contentText != null && contentText.Length != 0)
{
StringFormat sf = new StringFormat();
sf.Alignment = StringAlignment.Center;
sf.LineAlignment = StringAlignment.Center;
sf.FormatFlags = StringFormatFlags.MeasureTrailingSpaces;
sf.Trimming = StringTrimming.Word; // Added Rev 002
if (bIsMouseOverContent)
{
grfx.DrawString(contentText, hoverContentFont, new SolidBrush(hoverContentColor), ContentRectangle, sf);
if (EnableSelectionRectangle)
ControlPaint.DrawBorder3D(grfx, RealContentRectangle, Border3DStyle.Etched, Border3DSide.Top | Border3DSide.Bottom | Border3DSide.Left | Border3DSide.Right);
}
else
grfx.DrawString(contentText, normalContentFont, new SolidBrush(normalContentColor), ContentRectangle, sf);
}
}
protected void CalculateMouseRectangles()
{
Graphics grfx = CreateGraphics();
StringFormat sf = new StringFormat();
sf.Alignment = StringAlignment.Center;
sf.LineAlignment = StringAlignment.Center;
sf.FormatFlags = StringFormatFlags.MeasureTrailingSpaces;
SizeF sizefTitle = grfx.MeasureString(titleText, hoverTitleFont, TitleRectangle.Width, sf);
SizeF sizefContent = grfx.MeasureString(contentText, hoverContentFont, ContentRectangle.Width, sf);
grfx.Dispose();
// Added Rev 002
//We should check if the title size really fits inside the pre-defined title rectangle
if (sizefTitle.Height > TitleRectangle.Height)
{
RealTitleRectangle = new Rectangle(TitleRectangle.Left, TitleRectangle.Top, TitleRectangle.Width , TitleRectangle.Height );
}
else
{
RealTitleRectangle = new Rectangle(TitleRectangle.Left, TitleRectangle.Top, (int)sizefTitle.Width, (int)sizefTitle.Height);
}
RealTitleRectangle.Inflate(0,2);
// Added Rev 002
//We should check if the Content size really fits inside the pre-defined Content rectangle
if (sizefContent.Height > ContentRectangle.Height)
{
RealContentRectangle = new Rectangle((ContentRectangle.Width-(int)sizefContent.Width)/2+ContentRectangle.Left, ContentRectangle.Top, (int)sizefContent.Width, ContentRectangle.Height );
}
else
{
RealContentRectangle = new Rectangle((ContentRectangle.Width-(int)sizefContent.Width)/2+ContentRectangle.Left, (ContentRectangle.Height-(int)sizefContent.Height)/2+ContentRectangle.Top, (int)sizefContent.Width, (int)sizefContent.Height);
}
RealContentRectangle.Inflate(0,2);
}
protected Region BitmapToRegion(Bitmap bitmap, Color transparencyColor)
{
if (bitmap == null)
throw new ArgumentNullException("Bitmap", "Bitmap cannot be null!");
int height = bitmap.Height;
int width = bitmap.Width;
GraphicsPath path = new GraphicsPath();
for (int j=0; j for (int i=0; i {
if (bitmap.GetPixel(i, j) == transparencyColor)
continue;
int x0 = i;
while ((i < width) && (bitmap.GetPixel(i, j) != transparencyColor))
i++;
path.AddRectangle(new Rectangle(x0, j, i-x0, 1));
}
Region region = new Region(path);
path.Dispose();
return region;
}
#endregion
#region TaskbarNotifier Events Overrides
protected void OnTimer(Object obj, EventArgs ea)
{
switch (taskbarState)
{
case TaskbarStates.appearing:
if (Height < BackgroundBitmap.Height)
SetBounds(Left, Top-nIncrementShow ,Width, Height + nIncrementShow);
else
{
timer.Stop();
Height = BackgroundBitmap.Height;
timer.Interval = nVisibleEvents;
taskbarState = TaskbarStates.visible;
timer.Start();
}
break;
case TaskbarStates.visible:
timer.Stop();
timer.Interval = nHideEvents;
// Added Rev 002
if ((bKeepVisibleOnMouseOver && !bIsMouseOverPopup ) || (!bKeepVisibleOnMouseOver))
{
taskbarState = TaskbarStates.disappearing;
}
//taskbarState = TaskbarStates.disappearing; // Rev 002
timer.Start();
break;
case TaskbarStates.disappearing:
// Added Rev 002
if (bReShowOnMouseOver && bIsMouseOverPopup)
{
taskbarState = TaskbarStates.appearing;
}
else
{
if (Top < WorkAreaRectangle.Bottom)
SetBounds(Left, Top + nIncrementHide, Width, Height - nIncrementHide);
else
Hide();
}
break;
}
}
protected override void OnMouseEnter(EventArgs ea)
{
base.OnMouseEnter(ea);
bIsMouseOverPopup = true;
Refresh();
}
protected override void OnMouseLeave(EventArgs ea)
{
base.OnMouseLeave(ea);
bIsMouseOverPopup = false;
bIsMouseOverClose = false;
bIsMouseOverTitle = false;
bIsMouseOverContent = false;
Refresh();
}
protected override void OnMouseMove(MouseEventArgs mea)
{
base.OnMouseMove(mea);
bool bContentModified = false;
if ( (mea.X > CloseBitmapLocation.X) && (mea.X < CloseBitmapLocation.X + CloseBitmapSize.Width) && (mea.Y > CloseBitmapLocation.Y) && (mea.Y < CloseBitmapLocation.Y + CloseBitmapSize.Height) && CloseClickable )
{
if (!bIsMouseOverClose)
{
bIsMouseOverClose = true;
bIsMouseOverTitle = false;
bIsMouseOverContent = false;
Cursor = Cursors.Hand;
bContentModified = true;
}
}
else if (RealContentRectangle.Contains(new Point(mea.X, mea.Y)) && ContentClickable)
{
if (!bIsMouseOverContent)
{
bIsMouseOverClose = false;
bIsMouseOverTitle = false;
bIsMouseOverContent = true;
Cursor = Cursors.Hand;
bContentModified = true;
}
}
else if (RealTitleRectangle.Contains(new Point(mea.X, mea.Y)) && TitleClickable)
{
if (!bIsMouseOverTitle)
{
bIsMouseOverClose = false;
bIsMouseOverTitle = true;
bIsMouseOverContent = false;
Cursor = Cursors.Hand;
bContentModified = true;
}
}
else
{
if (bIsMouseOverClose || bIsMouseOverTitle || bIsMouseOverContent)
bContentModified = true;
bIsMouseOverClose = false;
bIsMouseOverTitle = false;
bIsMouseOverContent = false;
Cursor = Cursors.Default;
}
if (bContentModified)
Refresh();
}
protected override void OnMouseDown(MouseEventArgs mea)
{
base.OnMouseDown(mea);
bIsMouseDown = true;
if (bIsMouseOverClose)
Refresh();
}
protected override void OnMouseUp(MouseEventArgs mea)
{
base.OnMouseUp(mea);
bIsMouseDown = false;
if (bIsMouseOverClose)
{
Hide();
if (CloseClick != null)
CloseClick(this, new EventArgs());
}
else if (bIsMouseOverTitle)
{
if (TitleClick != null)
TitleClick(this, new EventArgs());
}
else if (bIsMouseOverContent)
{
if (ContentClick != null)
ContentClick(this, new EventArgs());
}
}
protected override void OnPaintBackground(PaintEventArgs pea)
{
Graphics grfx = pea.Graphics;
grfx.PageUnit = GraphicsUnit.Pixel;
Graphics offScreenGraphics;
Bitmap offscreenBitmap;
offscreenBitmap = new Bitmap(BackgroundBitmap.Width, BackgroundBitmap.Height);
offScreenGraphics = Graphics.FromImage(offscreenBitmap);
if (BackgroundBitmap != null)
{
offScreenGraphics.DrawImage(BackgroundBitmap, 0, 0, BackgroundBitmap.Width, BackgroundBitmap.Height);
}
DrawCloseButton(offScreenGraphics);
DrawText(offScreenGraphics);
grfx.DrawImage(offscreenBitmap, 0, 0);
}
#endregion
}
}
I coded a MSN Messenger-like skinnable popup, with a close button which looks almost like Microsoft's one (with the associated skin).
The TaskbarNotifier class inherits from System.Windows.Forms.Form and adds a few methods to it.
Features
The MSN messenger like popup supports:
* A custom transparent bitmap background
* A skinnable 3-State close button
* A clickable title text
* A clickable content text
* A selection rectangle
* Custom fonts and colors for the different states of the text (normal/hover)
* Animation speed parameters
Compatibility
This class is stand alone and doesn't need any particular libraries except .NET default ones. It runs in managed code and hence should be portable.
How to use the class
* First of all, copy TaskbarNotifier.cs in your project directory.
* Add on the top of your source code the directive:
using CustomUIControls;
* Add a member variable in your class:
TaskbarNotifier taskbarNotifier;
* In your constructor add the following lines:
taskbarNotifier=new TaskbarNotifier();
taskbarNotifier.SetBackgroundBitmap("skin.bmp",
Color.FromArgb(255,0,255));
taskbarNotifier.SetCloseBitmap("close.bmp",
Color.FromArgb(255,0,255),new Point(127,8));
taskbarNotifier.TitleRectangle=new Rectangle(40,9,70,25);
taskbarNotifier.ContentRectangle=new Rectangle(8,41,133,68);
taskbarNotifier.TitleClick+=new EventHandler(TitleClick);
taskbarNotifier.ContentClick+=new EventHandler(ContentClick);
taskbarNotifier.CloseClick+=new EventHandler(CloseClick);
Details:
taskbarNotifier.SetBackgroundBitmap("skin.bmp",
Color.FromArgb(255,0,255));
taskbarNotifier.SetCloseBitmap("close.bmp",
Color.FromArgb(255,0,255),new Point(127,8));
The first line sets the background bitmap skin and transparency color (must be present), and the second line sets the 3-State close button with its transparency color and its location on the window (this line is optional if you don't want a close button).
taskbarNotifier.TitleRectangle=new Rectangle(40,9,70,25);
taskbarNotifier.ContentRectangle=new Rectangle(8,41,133,68);
These two lines allow us to define the rectangles in which will be displayed, the title and content texts.
taskbarNotifier.TitleClick+=new EventHandler(OnTitleClick);
taskbarNotifier.ContentClick+=new EventHandler(OnContentClick);
taskbarNotifier.CloseClick+=new EventHandler(OnCloseClick);
These 3 lines allow us to intercept events on the popup such as title/content or close button have been clicked
* Then we are done, we just need to call:
taskbarNotifier.Show("TitleText","ContentText",500,3000,500);
This will show the popup animation with the showing/visible/hiding animations time set as 500ms/3000ms/500ms.
You can play with a few properties:
* Title, content fonts and colors
* Ability to click or not on the title/content/close button
* You can disable the focus rect
* ... (see below for more details)
Class documentation
Methods
void Show(string strTitle, string strContent, int nTimeToShow,
int nTimeToStay, int nTimeToHide)
Displays the popup for a certain amount of time.
Parameters
* strTitle: The string which will be shown as the title of the popup
* strContent: The string which will be shown as the content of the popup
* nTimeToShow: Duration of the showing animation (in milliseconds)
* nTimeToStay: Duration of the visible state before collapsing (in milliseconds)
* nTimeToHide: Duration of the hiding animation (in milliseconds)
void Hide()
Forces the popup to hide.
void SetBackgroundBitmap(string strFilename, Color transparencyColor)
Sets the background bitmap and its transparency color.
Parameters
* strFilename: Path of the background bitmap on the disk
* transparencyColor: Color of the bitmap which won't be visible
void SetBackgroundBitmap(Image image, Color transparencyColor)
Sets the background bitmap and its transparency color.
Parameters
# image: Background bitmap
# transparencyColor: Color of the bitmap which won't be visible
void SetCloseBitmap(string strFilename,
Color transparencyColor, Point position)
Sets the 3-State close button bitmap, its transparency color and its coordinates.
Parameters
* strFilename: Path of the 3-state close button bitmap on the disk (width must be a multiple of 3)
* transparencyColor: Color of the bitmap which won't be visible
* position: Location of the close button on the popup
void SetCloseBitmap(Image image, Color transparencyColor, Point position)
Sets the 3-State close button bitmap, its transparency color and its coordinates.
Parameters
* image: Image/Bitmap object which represents the 3-state close button bitmap (width must be a multiple of 3)
* transparencyColor: Color of the bitmap which won't be visible
* position: Location of the close button on the popup
Properties
string TitleText (get/set)
string ContentText (get/set)
TaskbarStates TaskbarState (get)
Color NormalTitleColor (get/set)
Color HoverTitleColor (get/set)
Color NormalContentColor (get/set)
Color HoverContentColor (get/set)
Font NormalTitleFont (get/set)
Font HoverTitleFont (get/set)
Font NormalContentFont (get/set)
Font HoverContentFont (get/set)
Rectangle TitleRectangle (get/set) //must be defined before calling show())
Rectangle ContentRectangle (get/set) //must be defined before calling show())
bool TitleClickable (get/set) (default = false);
bool ContentClickable (get/set) (default = true);
bool CloseClickable (get/set) (default = true);
bool EnableSelectionRectangle (get/set) (default = true);
Events
event EventHandler CloseClick
event EventHandler TitleClick
event EventHandler ContentClick
Technical issues
The popup is skinned using a region generated dynamically from a bitmap and a transparency color:
protected Region BitmapToRegion(Bitmap bitmap, Color transparencyColor)
{
if (bitmap == null)
throw new ArgumentNullException("Bitmap", "Bitmap cannot be null!");
int height = bitmap.Height;
int width = bitmap.Width;
GraphicsPath path = new GraphicsPath();
for (int j=0; j
if (bitmap.GetPixel(i, j) == transparencyColor)
continue;
int x0 = i;
while ((i < width) &&
(bitmap.GetPixel(i, j) != transparencyColor))
i++;
path.AddRectangle(new Rectangle(x0, j, i-x0, 1));
}
Region region = new Region(path);
path.Dispose();
return region;
}
The refresh() of the popup is done using the double buffering technique to avoid flickering:
protected override void OnPaintBackground(PaintEventArgs pea)
{
Graphics grfx = pea.Graphics;
grfx.PageUnit = GraphicsUnit.Pixel;
Graphics offScreenGraphics;
Bitmap offscreenBitmap;
offscreenBitmap = new Bitmap(BackgroundBitmap.Width,
BackgroundBitmap.Height);
offScreenGraphics = Graphics.FromImage(offscreenBitmap);
if (BackgroundBitmap != null)
{
offScreenGraphics.DrawImage(BackgroundBitmap,
0, 0, BackgroundBitmap.Width, BackgroundBitmap.Height);
}
DrawCloseButton(offScreenGraphics);
DrawText(offScreenGraphics);
grfx.DrawImage(offscreenBitmap, 0, 0);
}
Bugs/Limitations
Since I wanted to keep only managed code, I used the Screen.GetWorkingArea(WorkAreaRectangle) function instead of using unmanaged code to get the taskbar position. As a result, I made the popup always appear at the bottom of WorkAreaRectangle whichever position the taskbar has.
I didn't find any C# managed equivalent to the Win32 function ShowWindow(SW_SHOWNOACTIVATE) to make the popup, not steal the focus of the active window.
Code for TaskbarNotification.cs
// C# TaskbarNotifier Class v1.0
// by John O'Byrne - 02 december 2002
// 01 april 2003 : Small fix in the OnMouseUp handler
// 11 january 2003 : Patrick Vanden Driessche
// Small Enhancements/Bugfix
// Small bugfix: When Content text measures larger than the corresponding ContentRectangle
// the focus rectangle was not correctly drawn. This has been solved.
// Added KeepVisibleOnMouseOver
// Added ReShowOnMouseOver
// Added If the Title or Content are too long to fit in the corresponding Rectangles,
// the text is truncateed and the ellipses are appended (StringTrimming).
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace CustomUIControls
{
///
/// TaskbarNotifier allows to display MSN style/Skinned instant messaging popups
///
public class TaskbarNotifier : System.Windows.Forms.Form
{
#region TaskbarNotifier Protected Members
protected Bitmap BackgroundBitmap = null;
protected Bitmap CloseBitmap = null;
protected Point CloseBitmapLocation;
protected Size CloseBitmapSize;
protected Rectangle RealTitleRectangle;
protected Rectangle RealContentRectangle;
protected Rectangle WorkAreaRectangle;
protected Timer timer = new Timer();
protected TaskbarStates taskbarState = TaskbarStates.hidden;
protected string titleText;
protected string contentText;
protected Color normalTitleColor = Color.FromArgb(255,0,0);
protected Color hoverTitleColor = Color.FromArgb(255,0,0);
protected Color normalContentColor = Color.FromArgb(0,0,0);
protected Color hoverContentColor = Color.FromArgb(0,0,0x66);
protected Font normalTitleFont = new Font("Arial",12,FontStyle.Regular,GraphicsUnit.Pixel);
protected Font hoverTitleFont = new Font("Arial",12,FontStyle.Bold,GraphicsUnit.Pixel);
protected Font normalContentFont = new Font("Arial",11,FontStyle.Regular,GraphicsUnit.Pixel);
protected Font hoverContentFont = new Font("Arial",11,FontStyle.Regular,GraphicsUnit.Pixel);
protected int nShowEvents;
protected int nHideEvents;
protected int nVisibleEvents;
protected int nIncrementShow;
protected int nIncrementHide;
protected bool bIsMouseOverPopup = false;
protected bool bIsMouseOverClose = false;
protected bool bIsMouseOverContent = false;
protected bool bIsMouseOverTitle = false;
protected bool bIsMouseDown = false;
protected bool bKeepVisibleOnMouseOver = true; // Added Rev 002
protected bool bReShowOnMouseOver = false; // Added Rev 002
#endregion
#region TaskbarNotifier Public Members
public Rectangle TitleRectangle;
public Rectangle ContentRectangle;
public bool TitleClickable = false;
public bool ContentClickable = true;
public bool CloseClickable = true;
public bool EnableSelectionRectangle = true;
public event EventHandler CloseClick = null;
public event EventHandler TitleClick = null;
public event EventHandler ContentClick = null;
#endregion
#region TaskbarNotifier Enums
///
/// List of the different popup animation status
///
public enum TaskbarStates
{
hidden = 0,
appearing = 1,
visible = 2,
disappearing = 3
}
#endregion
#region TaskbarNotifier Constructor
///
/// The Constructor for TaskbarNotifier
///
public TaskbarNotifier()
{
// Window Style
FormBorderStyle = FormBorderStyle.None;
WindowState = FormWindowState.Minimized;
base.Show();
base.Hide();
WindowState = FormWindowState.Normal;
ShowInTaskbar = false;
TopMost = true;
MaximizeBox = false;
MinimizeBox = false;
ControlBox = false;
timer.Enabled = true;
timer.Tick += new EventHandler(OnTimer);
}
#endregion
#region TaskbarNotifier Properties
///
/// Get the current TaskbarState (hidden, showing, visible, hiding)
///
public TaskbarStates TaskbarState
{
get
{
return taskbarState;
}
}
///
/// Get/Set the popup Title Text
///
public string TitleText
{
get
{
return titleText;
}
set
{
titleText=value;
Refresh();
}
}
///
/// Get/Set the popup Content Text
///
public string ContentText
{
get
{
return contentText;
}
set
{
contentText=value;
Refresh();
}
}
///
/// Get/Set the Normal Title Color
///
public Color NormalTitleColor
{
get
{
return normalTitleColor;
}
set
{
normalTitleColor = value;
Refresh();
}
}
///
/// Get/Set the Hover Title Color
///
public Color HoverTitleColor
{
get
{
return hoverTitleColor;
}
set
{
hoverTitleColor = value;
Refresh();
}
}
///
/// Get/Set the Normal Content Color
///
public Color NormalContentColor
{
get
{
return normalContentColor;
}
set
{
normalContentColor = value;
Refresh();
}
}
///
/// Get/Set the Hover Content Color
///
public Color HoverContentColor
{
get
{
return hoverContentColor;
}
set
{
hoverContentColor = value;
Refresh();
}
}
///
/// Get/Set the Normal Title Font
///
public Font NormalTitleFont
{
get
{
return normalTitleFont;
}
set
{
normalTitleFont = value;
Refresh();
}
}
///
/// Get/Set the Hover Title Font
///
public Font HoverTitleFont
{
get
{
return hoverTitleFont;
}
set
{
hoverTitleFont = value;
Refresh();
}
}
///
/// Get/Set the Normal Content Font
///
public Font NormalContentFont
{
get
{
return normalContentFont;
}
set
{
normalContentFont = value;
Refresh();
}
}
///
/// Get/Set the Hover Content Font
///
public Font HoverContentFont
{
get
{
return hoverContentFont;
}
set
{
hoverContentFont = value;
Refresh();
}
}
///
/// Indicates if the popup should remain visible when the mouse pointer is over it.
/// Added Rev 002
///
public bool KeepVisibleOnMousOver
{
get
{
return bKeepVisibleOnMouseOver;
}
set
{
bKeepVisibleOnMouseOver=value;
}
}
///
/// Indicates if the popup should appear again when mouse moves over it while it's disappearing.
/// Added Rev 002
///
public bool ReShowOnMouseOver
{
get
{
return bReShowOnMouseOver;
}
set
{
bReShowOnMouseOver=value;
}
}
#endregion
#region TaskbarNotifier Public Methods
[DllImport("user32.dll")]
private static extern Boolean ShowWindow(IntPtr hWnd,Int32 nCmdShow);
///
/// Displays the popup for a certain amount of time
///
public void Show(string strTitle, string strContent, int nTimeToShow, int nTimeToStay, int nTimeToHide)
{
WorkAreaRectangle = Screen.GetWorkingArea(WorkAreaRectangle);
titleText = strTitle;
contentText = strContent;
nVisibleEvents = nTimeToStay;
CalculateMouseRectangles();
// We calculate the pixel increment and the timer value for the showing animation
int nEvents;
if (nTimeToShow > 10)
{
nEvents = Math.Min((nTimeToShow / 10), BackgroundBitmap.Height);
nShowEvents = nTimeToShow / nEvents;
nIncrementShow = BackgroundBitmap.Height / nEvents;
}
else
{
nShowEvents = 10;
nIncrementShow = BackgroundBitmap.Height;
}
// We calculate the pixel increment and the timer value for the hiding animation
if( nTimeToHide > 10)
{
nEvents = Math.Min((nTimeToHide / 10), BackgroundBitmap.Height);
nHideEvents = nTimeToHide / nEvents;
nIncrementHide = BackgroundBitmap.Height / nEvents;
}
else
{
nHideEvents = 10;
nIncrementHide = BackgroundBitmap.Height;
}
switch (taskbarState)
{
case TaskbarStates.hidden:
taskbarState = TaskbarStates.appearing;
SetBounds(WorkAreaRectangle.Right-BackgroundBitmap.Width-17, WorkAreaRectangle.Bottom-1, BackgroundBitmap.Width, 0);
timer.Interval = nShowEvents;
timer.Start();
// We Show the popup without stealing focus
ShowWindow(this.Handle, 4);
break;
case TaskbarStates.appearing:
Refresh();
break;
case TaskbarStates.visible:
timer.Stop();
timer.Interval = nVisibleEvents;
timer.Start();
Refresh();
break;
case TaskbarStates.disappearing:
timer.Stop();
taskbarState = TaskbarStates.visible;
SetBounds(WorkAreaRectangle.Right-BackgroundBitmap.Width-17, WorkAreaRectangle.Bottom-BackgroundBitmap.Height-1, BackgroundBitmap.Width, BackgroundBitmap.Height);
timer.Interval = nVisibleEvents;
timer.Start();
Refresh();
break;
}
}
///
/// Hides the popup
///
///
public new void Hide()
{
if (taskbarState != TaskbarStates.hidden)
{
timer.Stop();
taskbarState = TaskbarStates.hidden;
base.Hide();
}
}
///
/// Sets the background bitmap and its transparency color
///
public void SetBackgroundBitmap(string strFilename, Color transparencyColor)
{
BackgroundBitmap = new Bitmap(strFilename);
Width = BackgroundBitmap.Width;
Height = BackgroundBitmap.Height;
Region = BitmapToRegion(BackgroundBitmap, transparencyColor);
}
///
/// Sets the background bitmap and its transparency color
///
public void SetBackgroundBitmap(Image image, Color transparencyColor)
{
BackgroundBitmap = new Bitmap(image);
Width = BackgroundBitmap.Width;
Height = BackgroundBitmap.Height;
Region = BitmapToRegion(BackgroundBitmap, transparencyColor);
}
///
/// Sets the 3-State Close Button bitmap, its transparency color and its coordinates
///
public void SetCloseBitmap(string strFilename, Color transparencyColor, Point position)
{
CloseBitmap = new Bitmap(strFilename);
CloseBitmap.MakeTransparent(transparencyColor);
CloseBitmapSize = new Size(CloseBitmap.Width/3, CloseBitmap.Height);
CloseBitmapLocation = position;
}
///
/// Sets the 3-State Close Button bitmap, its transparency color and its coordinates
///
public void SetCloseBitmap(Image image, Color transparencyColor, Point position)
{
CloseBitmap = new Bitmap(image);
CloseBitmap.MakeTransparent(transparencyColor);
CloseBitmapSize = new Size(CloseBitmap.Width/3, CloseBitmap.Height);
CloseBitmapLocation = position;
}
#endregion
#region TaskbarNotifier Protected Methods
protected void DrawCloseButton(Graphics grfx)
{
if (CloseBitmap != null)
{
Rectangle rectDest = new Rectangle(CloseBitmapLocation, CloseBitmapSize);
Rectangle rectSrc;
if (bIsMouseOverClose)
{
if (bIsMouseDown)
rectSrc = new Rectangle(new Point(CloseBitmapSize.Width*2, 0), CloseBitmapSize);
else
rectSrc = new Rectangle(new Point(CloseBitmapSize.Width, 0), CloseBitmapSize);
}
else
rectSrc = new Rectangle(new Point(0, 0), CloseBitmapSize);
grfx.DrawImage(CloseBitmap, rectDest, rectSrc, GraphicsUnit.Pixel);
}
}
protected void DrawText(Graphics grfx)
{
if (titleText != null && titleText.Length != 0)
{
StringFormat sf = new StringFormat();
sf.Alignment = StringAlignment.Near;
sf.LineAlignment = StringAlignment.Center;
sf.FormatFlags = StringFormatFlags.NoWrap;
sf.Trimming = StringTrimming.EllipsisCharacter; // Added Rev 002
if (bIsMouseOverTitle)
grfx.DrawString(titleText, hoverTitleFont, new SolidBrush(hoverTitleColor), TitleRectangle, sf);
else
grfx.DrawString(titleText, normalTitleFont, new SolidBrush(normalTitleColor), TitleRectangle, sf);
}
if (contentText != null && contentText.Length != 0)
{
StringFormat sf = new StringFormat();
sf.Alignment = StringAlignment.Center;
sf.LineAlignment = StringAlignment.Center;
sf.FormatFlags = StringFormatFlags.MeasureTrailingSpaces;
sf.Trimming = StringTrimming.Word; // Added Rev 002
if (bIsMouseOverContent)
{
grfx.DrawString(contentText, hoverContentFont, new SolidBrush(hoverContentColor), ContentRectangle, sf);
if (EnableSelectionRectangle)
ControlPaint.DrawBorder3D(grfx, RealContentRectangle, Border3DStyle.Etched, Border3DSide.Top | Border3DSide.Bottom | Border3DSide.Left | Border3DSide.Right);
}
else
grfx.DrawString(contentText, normalContentFont, new SolidBrush(normalContentColor), ContentRectangle, sf);
}
}
protected void CalculateMouseRectangles()
{
Graphics grfx = CreateGraphics();
StringFormat sf = new StringFormat();
sf.Alignment = StringAlignment.Center;
sf.LineAlignment = StringAlignment.Center;
sf.FormatFlags = StringFormatFlags.MeasureTrailingSpaces;
SizeF sizefTitle = grfx.MeasureString(titleText, hoverTitleFont, TitleRectangle.Width, sf);
SizeF sizefContent = grfx.MeasureString(contentText, hoverContentFont, ContentRectangle.Width, sf);
grfx.Dispose();
// Added Rev 002
//We should check if the title size really fits inside the pre-defined title rectangle
if (sizefTitle.Height > TitleRectangle.Height)
{
RealTitleRectangle = new Rectangle(TitleRectangle.Left, TitleRectangle.Top, TitleRectangle.Width , TitleRectangle.Height );
}
else
{
RealTitleRectangle = new Rectangle(TitleRectangle.Left, TitleRectangle.Top, (int)sizefTitle.Width, (int)sizefTitle.Height);
}
RealTitleRectangle.Inflate(0,2);
// Added Rev 002
//We should check if the Content size really fits inside the pre-defined Content rectangle
if (sizefContent.Height > ContentRectangle.Height)
{
RealContentRectangle = new Rectangle((ContentRectangle.Width-(int)sizefContent.Width)/2+ContentRectangle.Left, ContentRectangle.Top, (int)sizefContent.Width, ContentRectangle.Height );
}
else
{
RealContentRectangle = new Rectangle((ContentRectangle.Width-(int)sizefContent.Width)/2+ContentRectangle.Left, (ContentRectangle.Height-(int)sizefContent.Height)/2+ContentRectangle.Top, (int)sizefContent.Width, (int)sizefContent.Height);
}
RealContentRectangle.Inflate(0,2);
}
protected Region BitmapToRegion(Bitmap bitmap, Color transparencyColor)
{
if (bitmap == null)
throw new ArgumentNullException("Bitmap", "Bitmap cannot be null!");
int height = bitmap.Height;
int width = bitmap.Width;
GraphicsPath path = new GraphicsPath();
for (int j=0; j
if (bitmap.GetPixel(i, j) == transparencyColor)
continue;
int x0 = i;
while ((i < width) && (bitmap.GetPixel(i, j) != transparencyColor))
i++;
path.AddRectangle(new Rectangle(x0, j, i-x0, 1));
}
Region region = new Region(path);
path.Dispose();
return region;
}
#endregion
#region TaskbarNotifier Events Overrides
protected void OnTimer(Object obj, EventArgs ea)
{
switch (taskbarState)
{
case TaskbarStates.appearing:
if (Height < BackgroundBitmap.Height)
SetBounds(Left, Top-nIncrementShow ,Width, Height + nIncrementShow);
else
{
timer.Stop();
Height = BackgroundBitmap.Height;
timer.Interval = nVisibleEvents;
taskbarState = TaskbarStates.visible;
timer.Start();
}
break;
case TaskbarStates.visible:
timer.Stop();
timer.Interval = nHideEvents;
// Added Rev 002
if ((bKeepVisibleOnMouseOver && !bIsMouseOverPopup ) || (!bKeepVisibleOnMouseOver))
{
taskbarState = TaskbarStates.disappearing;
}
//taskbarState = TaskbarStates.disappearing; // Rev 002
timer.Start();
break;
case TaskbarStates.disappearing:
// Added Rev 002
if (bReShowOnMouseOver && bIsMouseOverPopup)
{
taskbarState = TaskbarStates.appearing;
}
else
{
if (Top < WorkAreaRectangle.Bottom)
SetBounds(Left, Top + nIncrementHide, Width, Height - nIncrementHide);
else
Hide();
}
break;
}
}
protected override void OnMouseEnter(EventArgs ea)
{
base.OnMouseEnter(ea);
bIsMouseOverPopup = true;
Refresh();
}
protected override void OnMouseLeave(EventArgs ea)
{
base.OnMouseLeave(ea);
bIsMouseOverPopup = false;
bIsMouseOverClose = false;
bIsMouseOverTitle = false;
bIsMouseOverContent = false;
Refresh();
}
protected override void OnMouseMove(MouseEventArgs mea)
{
base.OnMouseMove(mea);
bool bContentModified = false;
if ( (mea.X > CloseBitmapLocation.X) && (mea.X < CloseBitmapLocation.X + CloseBitmapSize.Width) && (mea.Y > CloseBitmapLocation.Y) && (mea.Y < CloseBitmapLocation.Y + CloseBitmapSize.Height) && CloseClickable )
{
if (!bIsMouseOverClose)
{
bIsMouseOverClose = true;
bIsMouseOverTitle = false;
bIsMouseOverContent = false;
Cursor = Cursors.Hand;
bContentModified = true;
}
}
else if (RealContentRectangle.Contains(new Point(mea.X, mea.Y)) && ContentClickable)
{
if (!bIsMouseOverContent)
{
bIsMouseOverClose = false;
bIsMouseOverTitle = false;
bIsMouseOverContent = true;
Cursor = Cursors.Hand;
bContentModified = true;
}
}
else if (RealTitleRectangle.Contains(new Point(mea.X, mea.Y)) && TitleClickable)
{
if (!bIsMouseOverTitle)
{
bIsMouseOverClose = false;
bIsMouseOverTitle = true;
bIsMouseOverContent = false;
Cursor = Cursors.Hand;
bContentModified = true;
}
}
else
{
if (bIsMouseOverClose || bIsMouseOverTitle || bIsMouseOverContent)
bContentModified = true;
bIsMouseOverClose = false;
bIsMouseOverTitle = false;
bIsMouseOverContent = false;
Cursor = Cursors.Default;
}
if (bContentModified)
Refresh();
}
protected override void OnMouseDown(MouseEventArgs mea)
{
base.OnMouseDown(mea);
bIsMouseDown = true;
if (bIsMouseOverClose)
Refresh();
}
protected override void OnMouseUp(MouseEventArgs mea)
{
base.OnMouseUp(mea);
bIsMouseDown = false;
if (bIsMouseOverClose)
{
Hide();
if (CloseClick != null)
CloseClick(this, new EventArgs());
}
else if (bIsMouseOverTitle)
{
if (TitleClick != null)
TitleClick(this, new EventArgs());
}
else if (bIsMouseOverContent)
{
if (ContentClick != null)
ContentClick(this, new EventArgs());
}
}
protected override void OnPaintBackground(PaintEventArgs pea)
{
Graphics grfx = pea.Graphics;
grfx.PageUnit = GraphicsUnit.Pixel;
Graphics offScreenGraphics;
Bitmap offscreenBitmap;
offscreenBitmap = new Bitmap(BackgroundBitmap.Width, BackgroundBitmap.Height);
offScreenGraphics = Graphics.FromImage(offscreenBitmap);
if (BackgroundBitmap != null)
{
offScreenGraphics.DrawImage(BackgroundBitmap, 0, 0, BackgroundBitmap.Width, BackgroundBitmap.Height);
}
DrawCloseButton(offScreenGraphics);
DrawText(offScreenGraphics);
grfx.DrawImage(offscreenBitmap, 0, 0);
}
#endregion
}
}
Subscribe to:
Posts (Atom)