Καλώς ορίσατε στο dotNETZone.gr - Σύνδεση | Εγγραφή | Βοήθεια

Singleton Patterns in C#

Πολύ ενδιαφέρον posting για Singleton Patterns σε C# (και με κάποιες αναφορές στην αντίστοιχη υλοποίηση σε Java).

http://www.yoda.arachsys.com/csharp/singleton.html

... το πέμπτο pattern είναι και το πιο ενδιαφέρον (κατά την άποψή μου), χρησιμοποιεί μια nested κλάσση (για τους Java developers inner κλάσση) χωρίς να χρειάζεται lock.

Δημοσιεύτηκε στις από το μέλος Nick Makris | 0 σχόλια

Finalization και Dispose

Μια από τις πιο χρήσιμες υπηρεσίες του CLR είναι το Garbage Collection. Δηλαδή ο περιοδικός καθαρισμός του Heap από objects που πλέον δεν χρησιμοποιούνται. Θαυμάσια, τώρα πια μπορούμε να δημιουργούμε όποιο αντικείμενο θέλουμε να το χρησιμοποιούμε για όσο θέλουμε και μετά.... να μή μας ενδιαφέρει ούτε να το "κλείσουμε" ούτε να το "καθαρίσουμε" ούτε τίποτα. Μετά από λίγη ώρα ο Garbage Collector θα καθαρίσει το Heap από τα αχρησιμοποίητα αντικείμενα.

 Αυτό ισχύει για τις περισσότερες των περιπτώσεων, όταν όμως ένας τύπος μας διαχειρίζεται unmanaged resources (για παράδειγμα database connections, queue ή file handlers), τότε πρέπει να "κλείνουμε" μόνοι μας αυτά τα resources. Στις ακόλουθες παραγράφους παραθέτω ένα πολύ γνωστό pattern για finalization και disposing τύπων που διαχειρίζονται unmanaged resources. Πρώτα όμως λίγη θεωρία.

Finalizers

Πρόκειται για μεθόδους που το όνομά τους ξεκινά με το "~" και έχουν το ίδιο όνομα με τον τύπο. Αν για παράδειγμα ο τύπος μας λέγεται MyClass ο finalizer του τύπου έχει το ακόλουθο signature:

 class MyClass
 {
        ~MyClass()
        {
//Your code here...
        }
 }

Ούτε void ούτε παράμετροι. Δεν είναι υποχρεωτικό να έχουμε finalizers για κάθε τύπο. Σε περίπτωση όμως που έχουμε τότε κατά τη διαδικασία του Garbage Collection του αντικειμένου μας γίνονται τα ακόλουθα δύο πράγματα:

  1. Το αντικείμενο "καθαρίζεται" από το heap (στην πραγματικότητα η μνήμη που καταλαμβάνει είναι και πάλι διαθέσιμη)
  2. Το αντικείμενο μπαίνει σε μια ουρά (queue) finalization. ΧΧΧμμμμμ τι είναι αυτή η finalization ουρά?, Πρόκειται για μιά κλασσική FIFO ουρά στην οποία κάθε αντικείμενο (πριν βγει από εκεί), υπόκειται σε finalization, δηλαδή απλά καλείται η finalize μεθοδός του.

Συνεπώς μπορείτε να φανταστείται το κώδικα του Garbage collector κάπως έτσι:

List<object> objectsToBeCGed = GetAllObjectsFromHeapToBeCGed();
List<object> objectsWithFinalizersToBeCGed = GetAllObjectsFromHeapToBeCGedWithFinalizers();

CleanHeapFromObjects(objectsToBeCGed );

FinalizationQueue.AddRange(objectsWithFinalizersToBeCGed.ToArray());

Προφανώς ο παραπάνω είναι ψευδω-κώδικας (και η λίστα objectsToBeCGed είναι υπερσύνολο του objectsWithFinalizersToBeCGed).

To CLR εποπτεύει την ουρά και κάθε φορά που βγάζει ένα αντικείμενο από αυτή καλεί τον Finalizer της.

Γιατί χρειαζόματε τους Finalizers?

Ας υποθέσουμε ότι φτιάχνουμε ένα τύπο (μιά κλάση) η οποία χειρίζεται unmanaged resources (π.χ. ένα database connection). Κάνουμε την κλάση να υλοποιεί το IDisposable Interface και στη Dispose μέθοδο έχουμε τον κώδικα που κλείνει το database connection:

    class MyClassThatHandlesDBConnection : IDisposable
    {
        ...
        public void Dispose()
        {
            ...
            this.DBConnection.Close();
        }
    }

Ολα φαίνονται εντάξει, αν κάποιος χρησιμοποιήσει την κλάσση με using ή καλέσει την Dispose πράγματι δεν θα έχουμε προβλήματα. Αν όμως ο χρήστης της κλάσης δεν τη χρησιμοποιήσει σωστά τότε θα έχουμε απρόσμενα αποτελέσματα και μάλλον δυσάρεστες εκπλήξεις οι οποίες ΔΕΝ φαίνονται στο UNIT TEST. Είναι ευθύνη του developer που υλοποιεί την MyClassThatHandlesDBConnection να τη φτιάξει όσο το δυνατό πιο bullet proof. Σε ένα τέτοιο σενάριο χρησιμοποίούμε τον Finalizer με το ακόλοθο pattern:

    class MyClassThatHandlesDBConnection : IDisposable
    {      
        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        ~MyClassThatHandlesDBConnection()
        {
            //Clean up unmanaged resources only
            this.Dispose(false);
        }

        protected void Dispose(bool disposing)
        {
            if (disposing)
            {
                //Clean up all managed resources if disposing is true              
            }
            //Always clean up unmanaged resources regardless of disposing parameter
            this.DBConnection.Close();
        }      
    }

Από το παραπάνω snippet κώδικα παρατηρούμε τα ακόλουθα:

  • Αν χρησιμοποιήσουμε σωστά την κλάσση (με using ή καλώντας την Dispose() explicitly), τότε καθαρίζονται τα managed και unmanaged resources ΚΑΙ ΕΠΙΠΛΕΟΝ με τη δήλωση GC.SuppressFinalize(this); λέμε στον Garbage Collector να ΜΗ βάλει το αντικείμενο στην finalization ουρά όταν το κάνει collect από το heap, μιας και έχουμε φροντίσει να κάνουμε "finalize" το αντικείμενο εμείς (δηλαδή να καθαρίσουμε και τα unmanaged resources).
  • Στην περίπτωση που δεν χρησιμοποιηθεί η κλάσση σωστά (δηλαδή δεν γίνει dispose από τον developer που την χρησιμοποίησε), τότε και πάλι δεν θα έχουμε κακή συμπεριφορά -τουλάχιστο από την κλάσση μας- διότι έστω και αργά (αφού γίνει GCed και αφου βγεί από την Finalization ουρά) τα unmanaged resources θα "καθαριστούν" διότι θα κληθεί η ~MyClassThatHandlesDBConnection()

Αρα έχουμε φτιάξει μια κλάσση που ακόμα και αν δεν χρησιμοποιηθεί με το σωστό τρόπο, παρέχει τους μηχανισμούς για να μη μένουν unmanaged resources ανοιχτά. Προφανώς το καλύτερο σενάριο είναι να χρησιμοποιηθεί η κλάσση μας σωστά και να μην χρειαστεί να βασιστούμε στο finalization του CLR. Γιαυτό και πάντα πρέπει να χρησιμοποιούμε το using block ή να καλούμε το Dispose σε οποιοδήποτε Τύπο υλοποιεί το IDisposable.

Μπορούμε λοιπόν να διατυπώσουμε τον κανόνα ότι όταν φτιάχνουμε κλάσσεις που χειρίζονται unmanaged resources, καλό είναι να κάνουμε finalization και disposing με το παραπάνω pattern.

Ελπίζω να σας φανεί χρήσιμο.....

Με την ευκαιρία εύχομαι ΚΑΛΗ ΧΡΟΝΙΑ ΜΕ ΥΓΕΙΑ ΚΑΙ ΕΥΤΥΧΙΑ ΣΕ ΟΛΟΥΣ.

Δημοσιεύτηκε στις από το μέλος Nick Makris | 0 σχόλια
Δημοσίευση στην κατηγορία:

Βάλτε λίγο "αλάτι" στη ζωή σας και στο hashing...

Γενικά

Hashing είναι η διαδικασία στην οποία από ένα αλφαριθμητικό (γενικά ένα byte stream) παράγουμε ένα άλλα αλφαριθμητικό που ονομάζεται hash value του αρχικού μυνήματος. Η δημιουργία του hash value γίνεται με ειδικούς μαθηματικούς αλγορίθμους που έχουν (εκτός των άλλων) τις ακόλουθες δύο βασικές ιδιότητες:

  • Είναι "πρακτικά αδύνατο" να αναπαραχθεί το αρχικό αλφαριθμητικό από το hash value
  • Δύο διαφορετικά αλφαριθμητικά είναι "πρακτικά αδύνατο" να δώσουν το ίδιο hash value

Με τον όρο "πρακτικά αδύνατο" εννοούμε ότι απαιτείται πολύ χρόνος και μεγάλη υπολογιστική ισχύ για να πετύχουμε τα παραπάνω. Ετσι για παράδειγμα αν έχουμε μια hash value και θέλουμε να μάθουμε το αλφαριθμητικό από το οποίο προήλθε, το μόνο που μπορούμε να κάνουμε είναι να παράγουμε hash values από όλα τα πιθανά αλφαριθμητικά (brute force attack) και να ελέγχουμε αν το παραγόμενο hash value ταιριάζει με αυτό που ψάχνουμε.

Αλγόριθμοι για Hashing

Υπάρχουν αρκετοί τέτοιοι αλγόριθμοι, οι πιο γνωστοί είναι οι:

  • SHA1 (Secure Hash Algorithm) και οι παραλλαγές του SHA256, SHA384, SHA512 κτλ.
  • MD5 (Message Digest)

Καθένας από αυτούς παράγει hash values συγκεκριμένου μεγέθους, για παράδειγμα ο MD5 παράγει 128 bit hashes, o SHA1 160 bit hashes, o SHA1 256 bit hashes κτλ.

Που είναι χρήσιμο το Hashing?

Σε πολλές περιπτώσεις, εδώ θα σχολιάσουμε αυτές που έχουν να κάνουν με αποθήκευση ευαίσθητων δεδομένων. Ισως το πιο αντιπροσωπευτικό παράδειγμα είναι η αποθήκευση passwords. Πράγματι φανταστείται ένα σύστημα στο οποίο θέλουμε να αποθηκεύουμε τα password των χρηστών σε μια βάση δεδομένων και ΔΕΝ θέλουμε να έχουμε την υπηρεσία "I forgot my password". Δηλαδή ΔΕΝ χρειάζετε να έχουμε διαθέσιμο το password κανενός χρήστη σαν clear text. Σε αυτή την περίπτωση δεν πρέπει να σώζουμε τα password με τρόπο που να μπορούν να ΑΝΑΚΤΗΘΟΥΝ. Μια καλή ιδέα λοιπόν είναι να αώζουμε hash values των passwords.

Ετσι αφενώς μπορούμε να κάνουμε έλεγχο αν ένας χρήστης έδωσε σωστό password (ελέγχοντας αν το hash του password που έδωσε είναι ίδιο με το hash που έχουμε αποθηκεύσει) και αφετέρου κανένας (ούτε ο DBA) δεν έχει πρόσβαση στο password κανενός χρήστη (πράγματι ο DBA μπορεί να δεί το hash ενός password αλλά ΔΕΝ μπορεί να ανακτήσει το clear text password).

Το μόνο που μπορεί να κάνεις κάποιος για να ανακτήσει το clear text password ξέροντας ένα hash, είναι το brute force attack που αναφέραμε παραπάνω. Αυτό όμως εν'γένει είναι μια πάρα πολύ χρονοβόρα (και resource consuming) διαδικασία. Πρέπει για κάθε πιθανό αλφαριθμητικό (πάααααρα πολλά σε πλήθος) να υπολογίζουμε το hash και να το συγκρίνουμε με το hash που θέλουμε να κάνουμε attack.

Salted Hash. Τί είναι? Γιατί είναι απαραίτητο σε κάποιες περιπτώσεις?

Τώρα που ανέφερα τη φράση "ευαίσθητα δεδομένα" ήρθαν στο μυαλό μου οι πιστωτικές μου κάρτες (ναι έχω και εγώ πολλές...) και οι cash κάρτες -πολλές πάλι αλλά με λίγο υπόλοιπο :(((.

Κάθε μια από αυτές έχει ένα τετραψήφιο PIN το οποίο, λογικά, φυλάσεται σε κάποια βάση κάποιου τραπεζικού συστήματτος. Προφανώς δεν αποθηκεύεται σαν clear text αλλά σαν hash value. Ωραία, μπορώ να κοιμάμαι ήσυχος, ο DBA ΔΕΝ μπορεί να ξέρει το PIN κάποια κάρτας μου. Χχχχχχχχχχχμμμμμμμμμμ, για μισό λεπτό, το PIN είναι ένα 4ψήφιο νούμερο, που σημαίνει ότι αν θέλω να κάνω brute force attack για να βρώ πιο νούμερο έχει το ίδιο hashed value ενός 4ψήφιου, αρκεί να δοκιμάσω 10000 νούμερα (από το 0000 μέχρι το 9999). Ο source πιθανόχωρος του brute force attack είναι πολύ περιορισμένος (ακόμα και ο Spectrum +2 που είχα κάποτε, θα μπορούσε σε μερικά δευτερόλεπτα να κάνει το attack!!!).

Σε αυτές τις περιπτώσεις (όπου ο πιθανόχωρος των ευαίσθητων δεδομένων είναι από τη φύση του περιορισμένος), χρησιμοποιούμε το λεγόμενο salted hash. Χρησιμοποιούμε ένα επιπλέον αλφαριθμητικό (το salt) και το προσθέτουμε είτε αριστερά είτε δεξιά του "ευαίσθητου δεδομένου" πριν δημιουργήσουμε το hash. Ετσι για παράδειγμα αν το PIN μιας κάρτας είναι το 1234 και το salt είναι το CARDNo τότε θα γινόταν hashed to CARDNo1234 (ή το 1234CARDNo). Ετσι ο πιθανόχωρος για το brute force attack θα ήταν πολύ μεγαλύτερος από τα 10000 νούμερα που αναφέραμε στην προηγούμενη παράγραφο.

Πως παράγω το salt? Πώς το αποθηκεύω?

Το salt παράγεται κατά κανόνα με ευριστικό τρόπο. Συνήθως είναι εξάρτηση άλλων πεδίων του record που περιέχει το "ευαίσθητο δεδομένο" που θέλουμε να αποθηκεύσουμε σαν hash. Για παράδειγμα μπορούμε να χρησιμοποιήσουμε το όνομα του χρήστη (username) σαν salt στο password. Αλλες φορές είναι πάντοτε το ίδιο για όλες τις εγγραφες και είναι αποθηκευμένο κάπου στον κώδικα σαν hardcoded τιμή, ενώ αλλές φορές παράγεται τυχαία από κάποιον random generator.

Οπως και να το έχουμε δημιουργήσει πρέπει να το αποθηκεύουμε (ή να το παράγουμε) με ασφαλή τρόπο ώστε να μην μπορεί να το βρεί κανείς εύκολα. Αν το salt γίνει compromised τότε μπορεί να έχουμε τα προβλήματα που αναφέραμε στην παραπάνω παράγραφο.

Στην ακόλουθη παράγραφο παραθέτω τον κώδικά για hashing και salted hashing. Το πού θα αποθηκεύσεται το salt.... δικό σας πρόβλημα :)))

Κώδικας για hashing και salted hashing (έτσι για να μην ξεχνιώμαστε...)


using
System;
using System.Security.Cryptography;
using System.Text;

namespace Hashing
{

    public enum HashAlgorithms
    {
        SHA1,
        SHA256,
        SHA384,
        SHA512,
        MD5
    }

    public class HashingUtils
    {      
        public static string ComputeHash(string plainText, HashAlgorithms hashAlgorithm,
            string salt)
        {
            byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText);
            byte[] saltBytes = Encoding.UTF8.GetBytes(salt);
            byte[] plainTextWithSaltBytes = new byte[plainTextBytes.Length + saltBytes.Length];
           
            for (int j = 0; j < plainTextBytes.Length; j++)
            {
                plainTextWithSaltBytes[j] = plainTextBytes[j];
            }

            for ( int j = 0; j < saltBytes.Length; j++)
            {
                plainTextWithSaltBytes[plainTextBytes.Length + j] = saltBytes[j];
            }

            HashAlgorithm hash = null;
            switch ( hashAlgorithm )
            {
                case HashAlgorithms.SHA1 :
                    hash = new SHA1Managed();
                    break;
                case HashAlgorithms.SHA256  :
                    hash = new SHA256Managed();
                    break;
                case HashAlgorithms.SHA384 :
                    hash = new SHA384Managed();
                    break;
                case HashAlgorithms.SHA512 :
                    hash = new SHA512Managed();
                    break;
                case HashAlgorithms.MD5 :
                    hash = new MD5CryptoServiceProvider();
                    break;
            }
         
            byte[] hashBytes = hash.ComputeHash(plainTextWithSaltBytes);

            return Convert.ToBase64String(hashBytes);
        }

      
        public static bool VerifyHash(string plainText, HashAlgorithms hashAlgorithm,
            string hashValue, string salt)
        {
            byte[] hashBytes = Convert.FromBase64String(hashValue);
            byte[] saltBytes = Encoding.UTF8.GetBytes(salt);
           
         
            string expectedHashString = ComputeHash(plainText, hashAlgorithm, salt);
            return (hashValue == expectedHashString);
        }
    }
}

Χρησιμοποιούμε την κλάση κάπως έτσι:

static void Main(string[] args)
        {
            string plainText = "myPIN" ;
            string salt = "mySalt";

            string hashValueInBase64Encoding = Hashing.HashingUtils.ComputeHash(plainText,
                Hashing.HashAlgorithms.SHA512, salt);

            Console.Write("This is the hashed value: " + hashValueInBase64Encoding);
          
            bool verified = Hashing.HashingUtils.VerifyHash(plainText,
                Hashing.HashAlgorithms.SHA512, hashValueInBase64Encoding, salt);

            Console.WriteLine(verified);
            Console.ReadLine(); 
        }
Δημοσιεύτηκε στις από το μέλος Nick Makris | 0 σχόλια
Δημοσίευση στην κατηγορία: ,

Query για Indexes σε Foreign Keys

Σε πολλές (αν όχι σε όλες) τις περιπτώσεις είναι απαραίτητο να έχουμε secondary indexes σε columns που κάνουν reference σε ξένα κλειδιά. Αν για παράδειγμα έχουμε δύο πίνακες (Employees και Departments) και ο πίνακας Employees έχει ένα Foreign Key constraint στο DepartmentID (δηλαδή η κάθε εγγραφή του πίνακα Employees έχει ένα reference στο ID του department που ανήκει), τότε κατά πάσα πιθανότητα θα υπάρχουν queries στα οποία οι δύο αυτοί πίνακες κάνουν JOIN (πιθανότατα INNER JOIN) στο πεδίο αυτό.

Θα ήταν λοιπόν καλό (για να μην πώ απαραίτητο) να έχουμε μια λίστα με τα ξένα κλειδιά που ΔΕΝ ΕΙΝΑΙ INDEXED

Τρέξτε αυτό το query (μόνο σε SQL 2005) και θα πάρετε αυτή τη λίστα...


-- Check that ALL FK columns are indeed indexed in the child table

select ' FK without Index : Create Secondary index on table  ' + o.name + ' for column ' + c.name as IndexesNeeded,
  fc.referenced_object_id, fc.referenced_column_id, f.name, f.type, f.type_desc, f.parent_object_id, fc.parent_column_id
from sys.foreign_keys as f, sys.foreign_key_columns as fc, sys.objects as o, sys.columns as c
where c.object_id = o.object_id and c.column_id = fc.parent_column_id and o.object_id = fc.parent_object_id and
  fc.constraint_object_id = f.object_id and fc.parent_object_id = f.parent_object_id
and
(
 Select count(*) --Parent_object_id, Parent_column_id 
 from sys.foreign_key_columns  fkc,
  sys.index_columns ic
 where fc.parent_object_id = fkc.Parent_object_id and
  fkc.Parent_object_id = ic.object_id and
  fkc.Parent_column_id = ic.column_id and
  fkc.Parent_column_id = fc.parent_column_id ) = 0
Order by o.Name

Δημοσιεύτηκε στις από το μέλος Nick Makris | 0 σχόλια
Δημοσίευση στην κατηγορία:

Δημιουργήστε Web Test σε AJAX sites με το Visual Studio Team Suite 2005 και τον Fiddler 2

Εάν έχετε δουλέψει με το Visual Studio Team Suite 2005 (εάν δεν έχετε αυτή τη version του visual studio μπορείτε να κατεβάσετε την trial από το http://www.microsoft.com/downloads/details.aspx?FamilyID=5677DDC4-5035-401F-95C3-CC6F46F6D8F7&displaylang=en), σίγουρα έχετε δεί και το Test Project Template.

Σε projects αυτού του είδους μπορείτε να δημιουργήσετε διαφόρων ειδών tests όπως Unit Tests, Web Tests, Load Test, Ordered Tests ή Manual Tests. Σε αυτή τη δημοσίευση θα ασχοληθούμε με τη δημιουργία Web Tests σε AJAX Enabled Web Sites.

Τι είναι ένα Web Test?

Είναι ένα functional test (λειτουργικός έλεγχος) σε Web οθόνες, δηλαδή έλεγχος της σωστής λειτουργίας των σελίδων μιας Web εφαρμογής. Αν για παράδειγμα θέλουμε να ελέγξουμε αν η λειτουργία login δουλεύει σωστά σε ένα Web Application, τότε στην απλούστερη περίπτωση, πρέπει να κάνουμε δοκιμαστικά login με πολλά usernames και passwords (άλλοτε σωστά και άλλοτε λανθασμένα) και να δούμε πώς αποκρίνεται το σύστημα.

Δημιουργία του "προς δοκιμή" AJAX Enabled Web Site

Θα υλοποιήσουμε ένα πολύ απλό AJAX web site με μία μόνο σελίδα που θα επιτελεί τη λειτουργία Login. Η σελίδα Login.aspx θα έχει την ακόλουθη φόρμα:

<
form id="_mainForm" runat="server">
<asp:ScriptManager ID="_scriptManager" runat="server" />
        <div>
        Ονομα: <asp:TextBox ID="_userNameTextBox" runat="server"></asp:TextBox>
        Κωδικός: <asp:TextBox ID="_passwordTextBox" runat="server"></asp:TextBox>
        <br />
        <asp:Button ID="_loginButton" runat="server" Text="Εγγραφή" OnClick="_loginButton_Click" />
        <asp:Panel ID="_loginSuccess" runat="server" Visible="false">Καλωσόρισες <asp:Label ID="_userNameLabel" runat="Server" /></asp:Panel>
        <asp:Panel ID="_loginFailedPanel" runat="server" Visible="false">Λάθος στοιχεία, παρακαλώ ξαναδοκιμάστε</asp:Panel>
        </div>
</form>

Ενώ ο server side κώδικας θα είναι κάπως έτσι:

protected void Page_Load(object sender, EventArgs e)
    {
        this._loginSuccess.Visible = false;
        this._loginFailedPanel.Visible = false;

    }
    protected void _loginButton_Click(object sender, EventArgs e)
    {
        if ((this._userNameTextBox.Text == "nikos") &&
             (this._passwordTextBox.Text == "makris"))
        {
            this._userNameLabel.Text = "nikos";
            this._loginSuccess.Visible = true;
        }
        else
        {
            this._loginFailedPanel.Visible = true;
        }
    }

Οπως βλέπετε δεν χρησιμοποιούμε (ακόμα) τίποτε από το AJAX -πέρα από το _scriptManager control, ενώ ο κώδικας είναι πάρα πολύ απλός, παρατηρήστε ότι έχουμε (χάρη ευκολίας) ένα μόνο hardcoded όνομα και κωδικό χρήστη που θεωρούμε valid -τυχαία επιλεγμένο...

Προσθέστε στο solution με αυτό το web site ένα Test Project και στη συνέχει κάντε δεξί κλίκ και επιλέξτε "Add->Web Test".
 

Δημιουργείται έτσι ένα web test (με όνομα WebΤest1) και ανοίγει ο Internet Explorer. Παρατηρήστε to "Web Test Recorder" pane στα αριστερά. Κάνωντας κλήση της σελίδας που μόλις φτιάξαμε (http://localhost/AJAXEnabledWebSite/Login.aspx), βλέπουμε ότι καταγράφετε η κλήση (αριστερά).
 

Δώστε το σωστό όνομα και κωδικό και πατήστε το πλήκτρο "Είσοδος". Παρατηρούμε ότι καταγράφονται και το "Form port parameters".

Τώρα μπορείτε να σταματήσετε την καταγραφή (πατήστε το κουμπί Stop Recording πάνω από το Web Test Recorder). Μόλις δημιουργήσαμε ένα απλό web test. Θα το εμπλουτίσουμε με ένα απλό Validation Rule για να δούμε ότι η εφαρμογή μας συμπεριφέρετσι σωστά. Κάντε δεξί κλίκ στο δεύτερο βήμα του Web Test και επιλέξτε "Add Validation Rule..."

Στο παράθυρο που εμφανίζεται επιλέξτε "Find Text" και στο αντίσοιχο πεδίο (Find Text στα δεξιά) βάλτε τη λέξη "Καλωσόρισες"

Αυτός ο απλός έλεγχος θα μας δείξει αν το σύστημα συμπεριφέρετε σωστά όταν κάνουμε login με σωστό όνομα και κωδικό. Μπορείτε να τρέξετε το test για να διαπιστώσετε ότι "περνάει" επιτυχώς (παρατηρήστε το "Test Results" toolbox κάτω αριστερά.

Μέχρι εδώ όλα φαίνονται καλά. Αν όμως χρησιμοποιήσουμε το UpdatePanel του AJAX στην aspx σελίδα:

 <form id="_mainForm" runat="server">
        <asp:ScriptManager ID="_scriptManager" runat="server" />
        <asp:UpdatePanel ID="_updatePanel" runat="server">
            <ContentTemplate>
                <div>
                    Ονομα: <asp:TextBox ID="_userNameTextBox" runat="server"></asp:TextBox>
                    Κωδικός: <asp:TextBox ID="_passwordTextBox" runat="server"></asp:TextBox>
                    <br />
                    <asp:Button ID="_loginButton" runat="server" Text="Εγγραφή" OnClick="_loginButton_Click" />
                    <asp:Panel ID="_loginSuccess" runat="server" Visible="false>Καλωσόρισες <asp:Label ID="_userNameLabel" runat="Server" /></asp:Panel>
                    <asp:Panel ID="_loginFailedPanel" runat="server" Visible="false">Λάθος στοιχεία, παρακαλώ ξαναδοκιμάστε</asp:Panel>
                </div>
            </ContentTemplate>
        </asp:UpdatePanel>
    </form>

ώστε να έχουμε partial page update, τότε άν επαναλάβουμε τη διαδικάσία εγγραφής του Web Test θα δούμε ότι ΔΕΝ ΚΑΤΑΓΡΑΦΕΤΕ το postback του _submitButton.

Αααααα.... να μια αδυναμία του recorder, τι κάνουμε τώρα? Πώς θα καταγράψουμε ένα Web Test σε AJAX sites?

Δύο λύσεις:

Προφανώς πιο άμεση και πιο απλή λύση είναι η δεύτερη, και αυτή θα παρουσιάσουμε στις επόμενες παραγράφους.

Χρήση του Fiddler2 για την καταγραφή ενός Web Test

Πριν προσωρήσουμε πρέπει να τονίσω ότι το εργαλέιο Fiddler είναι ένας HTTP Debugging Proxy. Κάνει πάρα πολλά και ενδιαφέροντα πράγματα, ένα από αυτά είναι και η καταγραφή Web κλήσεων και η αποθήκευσή τους σαν Web Test του Visual Studio 2005.  

Αφού κατεβάσετε και εγκαταστήσετε το Fiddler2, κάντε κλίκ στο εικόνίδιο "Fiddler 2" (τυπικά βρίσκετε στο "All Programs").
 
Στα αριστερά (το κίτρινο Pane) είναι αυτό στο οποίο καταγράφονται οι Web κλήσεις. Αν ανοίξετε έναν Internet Explorer στο "Tools->Internet Options-Connections-LAN Settings" και πατήσετε το πλήκτρο Advanced



θα δείτε ότι ότι έχει ορισθεί ένα Proxy στο Port 8888, αυτός έιναι ο Fiddler.  

Tip: Εαν χρησιμοποιείτε Internet Explorer 7 και θέλετε να καταγράφετε τις Web κλήσεις προς τον localhost με το Fiddler, τότε θα πρέπει να ΜΗΝ ΧΡΗΣΙΜΟΠΟΙΕΙΤΕ το "localhost" στις http κλήσεις σας. Αντί αυτού χρησιμοποιείστε το όνομα του υπολογιστή σας. Ετσι αντί για http://localhost/... πρέπει να γράψετε http://<COMPUTER_NAME>/... Τούτο διότι ο Internet Explorer 7 κάνει πάντα "Bypass proxy settings for local addresses" ακόμα και αν στο παρακάτω παράθυρο επιλογών (Tools->Internet Options->Connections->Lan Settings) έχουμε unchecked αυτή την επιλογή.
 

Οπως μπορείτε να δείτε by default ο fiddler κάνει κλήση στο www.fiddler2.com. Επιλέξτε αυτή το entry στα αριστερά και πατήστε Delete. Εν συνεχεία ανοίξτε έναν Internet Explorer και καλέστε το AJAX enabled web site που δημιουργήσαμε http://<COMPUTER_NAME>/AJAXEnabledWebSite/Login.aspx
  
Στο παράθυρο του fiddler θα εμφανιστούν τα ακόλουθα:

(ο υπολογιστής μου ονομάζεται Informatics και χρησιμοποιώ Internet Explorer 7, για αυτό και τα καταγραφόμενα URLs έχουν αυτό το όνομα και όχι Localhost).

Τώρα ήρθε η ώρα να δούμε ότι ο Fiddler πράγματι καταγράφει AJAX Web κλήσεις. Συμπληρώστε τη login φόρμα (όπως πρίν) και πατήστε το κουμπί "Είσοδος", μπορείτε να δείτε ότι ο Fiddler κατέγραψε αυτή την κλήση (η τελευταία στην επόμενη εικόνα).

Ωραία λοιπόν, καταγράψαμε στο Fiddler τις web κλήσεις που θέλαμε, τώρα πώς θα τις σώσουμε σαν ένα Visual Studio Team Suite 2005 Web Test? Πολύ απλά, επιλέξτε τις κλήσεις από το αριστερό κίτρινο pane, και στη συνέχεια "File->Save-Session(s)->as Visual Studio Web Test..."

Δώστε όποιο όνομα θέλετε και πατήστε "Save", στο επόμενο παράθυρο για την επιλογή "Plugins" μην επιλεγετε τίποτε (περισσότερα για τα plugins σε επόμενη δημοσίευση) και πατήστε "ΟΚ"

Τώρα στο Visual Studio, μπορείτε να εισάγετε αυτό το Web Test με την επιλογή "Add->Existing Item". Βάλτε το Validation Rule, Find Text που χρησιμοποιήσαμε και στο παραπάνω παράδειγμα και τρέξτε το test. Αυτό ήταν, δημιουργήσαμε ένα Web Test για το Visual Studio Team Suite 2005 σε ένα AJAX Enabled Web Site με τη βοήθεια του Fiddler 2.

Συμπεράσματα - Επεκτάσεις

Αυτό ήταν ένα απλό demo σχετικά με τα Web Tests, την αδυναμία του Built in recorder του VS 2005 να καταγράψει AJAX Partial Page Updates και ένα work around για αυτό με τη βοήθεια του Fiddler 2. Αυτά που είδαμε δεν ήταν παρά μια σταγόνα στο ωκεανό των όσων πρέπει να γνωρίζουμε για να κάνουμε ένα σωστό και ρεαλιστικό Web Test. Το ViewState και EvenτValidation correlation, τα custom Validation Rules καθώς και η χρήση μίας ή περισσότερων Datasource για τα δεδομένα που κάνουμε post, είναι πράγματα που θα δούμε σε επόμενη δημοσίευση (ελπίζω πολύ σύντομα). Stay tuned... 

Δημοσιεύτηκε στις από το μέλος Nick Makris | 0 σχόλια
Δημοσίευση στην κατηγορία: