美文网首页
70-483.C3O1-5.Debug applications

70-483.C3O1-5.Debug applications

作者: 小镭Ra | 来源:发表于2018-10-29 12:09 被阅读11次

    Note Links

    Objective 3.1: Validate application input

    Managing data integrity

    Four different types of data integrity:

    • Entity integrity
      States that each entity (a record in a database) should be uniquely identifiable. In a database, this is achieved by using a primary key column. A primary key uniquely identifies each row of data. It can be generated by the database or by your application.
    • Domain integrity
      Refers to the validity of the data that an entity contains. This can be about the type of data and the possible values that are allowed (a valid postal code, a number within a certain range, or a default value, for example).
    • Referential integrity
      The relationship that entities have with each other, such as the relationship between an order and a customer.
    • User-defined integrity
      Comprises specific business rules that you need to enforce. A business rule for a web shop might involve a new customer who is not allowed to place an order above a certain dollar amount.

    LISTING 3-1 Customer and Address classes

    public class Customer 
    { 
        public int Id { get; set; } 
     
        [Required, MaxLength(20)] 
        public string FirstName { get; set; } 
     
        [Required, MaxLength(20)] 
        public string LastName { get; set; } 
     
        [Required] 
        public Address ShippingAddress { get; set; } 
     
        [Required] 
        public Address BillingAddress { get; set; } 
    }
    
    public class Address 
    { 
        public int Id { get; set; } 
     
        [Required, MaxLength(20)] 
        public string AddressLine1 { get; set; } 
     
        [Required, MaxLength(20)] 
        public string AddressLine2 { get; set; } 
     
        [Required, MaxLength(20)] 
        public string City { get; set; } 
     
        [RegularExpression(@"^[1-9][0-9]{3}\s?[a-zA-Z]{2}$")] 
        public string ZipCode { get; set; } 
    }
    

    You can use the following predefined attributes:

    • DataTypeAttribute
    • RangeAttribute
    • RegularExpressionAttribute
    • RequiredAttribute
    • StringLengthAttribute
    • CustomValidationAttribute
    • MaxLengthAttribute
    • MinLengthAttribute

    LISTING 3-2 Saving a new customer to the database

    public class ShopContext : DbContext 
    { 
        public IDbSet<Customer> Customers { get; set; } 
     
        protected override void OnModelCreating(DbModelBuilder modelBuilder) 
        { 
            // Make sure the database knows how to handle the duplicate address property 
            modelBuilder.Entity<Customer>().HasRequired(bm => bm.BillingAddress) 
                .WithMany().WillCascadeOnDelete(false); 
        } 
    } 
     
    using (ShopContext ctx = new ShopContext()) 
    { 
        Address a = new Address 
        { 
            AddressLine1 = "Somewhere 1", 
            AddressLine2 = "At some floor", 
            City = "SomeCity", 
            ZipCode = "1111AA" 
        }; 
     
        Customer c = new Customer() 
        { 
            FirstName = "John", 
            LastName = "Doe", 
            BillingAddress = a, 
            ShippingAddress = a, 
        }; 
     
        ctx.Customers.Add(c); 
        ctx.SaveChanges(); 
    }
    

    If you forget to set the FirstName property, the Entity Framework throws the following exception:

    System.Data.Entity.Validation.DbEntityValidationException : Validation failed for one or 
    more entities. See ‘EntityValidationErrors’ property for more details.
    

    LISTING 3-3 Running manual validation

    public static class GenericValidator<T> 
    { 
        public static IList<ValidationResult> Validate(T entity) 
        { 
            var results = new List<ValidationResult>(); 
            var context = new ValidationContext(entity, null, null); 
            Validator.TryValidateObject(entity, context, results); 
            return results; 
        } 
    }
    

    A transaction helps you group a set of related operations on a database. It ensures that those operations are seen as one distinct action. If one fails, they all fail and can easily be rolled back.

    Using Parse, TryParse, and Convert

    LISTING 3-4 Using Parse

    string value = "true"; 
    bool b = bool.Parse(value); 
    Console.WriteLine(b); // displays True
    

    LISTING 3-5 Using TryParse

    string value = "1"; 
    int result; 
    bool success = int.TryParse(value, out result); 
     
    if (success) 
    {  
        // value is a valid integer, result contains the value 
    } 
    else 
    {  
        // value is not a valid integer 
    }
    

    LISTING 3-6 Using configuration options when parsing a number

    CultureInfo english = new CultureInfo("En"); 
    CultureInfo dutch = new CultureInfo("Nl"); 
     
    string value = "€19,95"; 
    decimal d = decimal.Parse(value, NumberStyles.Currency, dutch); 
    Console.WriteLine(d.ToString(english)); // Displays 19.95
    

    When parsing a DateTime, you must take into account things such as time zone differences and cultural differences, especially when working on an application that uses globalization.

    MORE INFO ABOUT DateTime.Parse Method

    The difference between Parse/TryParse and Convert is that Convert enables null values. It doesn’t throw an ArgumentNullException; instead, it returns the default value for the supplied type.

    LISTING 3-7 Using Convert with a null value

    int i = Convert.ToInt32(null); 
    Console.WriteLine(i); // Displays 0
    

    A difference between Convert and the Parse methods is that Parse takes a string only as input, while Convert can also take other base types as input.

    LISTING 3-8 Using Convert to convert from double to int

    double d = 23.15; 
    int i = Convert.ToInt32(d); 
    Console.WriteLine(i); // Displays 23
    

    When you are parsing user input, the best choice is the TryParse method.

    Using regular expressions

    A regular expression is a specific pattern used to parse and find matches in strings. It is sometimes called regex or regexp.

    MORE INFO ABOUT Regular expressions examples

    LISTING 3-9 Manually validating a ZIP Code

    static bool ValidateZipCode(string zipCode) 
    {  
       // Valid zipcodes: 1234AB | 1234 AB | 1001 AB
        if (zipCode.Length < 6) return false; 
     
        string numberPart = zipCode.Substring(0, 4); 
        int number; 
        if (!int.TryParse(numberPart, out number)) return false; 
         
        string characterPart = zipCode.Substring(4); 
     
        if (numberPart.StartsWith("0")) return false; 
        if (characterPart.Trim().Length < 2) return false; 
        if (characterPart.Length == 3 && characterPart.Trim().Length != 2) 
            return false; 
     
        return true; 
    }
    

    LISTING 3-10 Validate a ZIP Code with a regular expression

    static bool ValidateZipCodeRegEx(string zipCode) 
    { 
        Match match = Regex.Match(zipCode, @"^[1-9][0-9]{3}\s?[a-zA-Z]{2}$", 
            RegexOptions.IgnoreCase); 
        return match.Success; 
    }
    

    LISTING 3-11 Validate a ZIP Code with a regular expression

    RegexOptions options = RegexOptions.None; 
    Regex regex = new Regex(@"[ ]{2,}", options); 
     
    string input = "1 2 3 4   5"; 
    string result = regex.Replace(input, " "); 
     
    Console.WriteLine(result); // Displays 1 2 3 4 5 
    

    In this example, every single space is allowed but multiple spaces are replaced with a single space.

    Validating JSON and XML

    LISTING 3-13 Deserializing an object with the JavaScriptSerializer

    var serializer = new JavaScriptSerializer(); 
    var result = serializer.Deserialize<Dictionary<string, object>>(json);
    

    JavaScriptSerializer is in the System.Web.Script.Serialization
    namespace, in System.Web.Extensions DLL.

    You can generate an XSD file with Xsd.exe, which is a part of Visual Studio.

    Xsd.exe person.xml
    

    LISTING 3-16 Validating an XML file with a schema

    public void ValidateXML() 
    { 
        string xsdPath = "person.xsd"; 
        string xmlPath = "person.xml"; 
         
        XmlReader reader = XmlReader.Create(xmlPath); 
        XmlDocument document = new XmlDocument(); 
        document.Schemas.Add("", xsdPath); 
        document.Load(reader); 
     
        ValidationEventHandler eventHandler =  
            new ValidationEventHandler(ValidationEventHandler); 
        document.Validate(eventHandler); 
    } 
     
    static void ValidationEventHandler(object sender, 
        ValidationEventArgs e) 
    { 
        switch (e.Severity) 
        { 
            case XmlSeverityType.Error: 
                Console.WriteLine("Error: {0}", e.Message); 
                break; 
            case XmlSeverityType.Warning: 
                Console.WriteLine("Warning {0}", e.Message); 
                break; 
        } 
    }
    

    Objective summary

    • Validating application input is important to protect your application against both
      mistakes and attacks.
    • Data integrity should be managed both by your application and your data store.
    • The Parse, TryParse, and Convert functions can be used to convert between types.
    • Regular expressions, or regex, can be used to match input against a specified pattern
      or replace specified characters with other values.
    • When receiving JSON and XML files, it’s important to validate them using the built-in
      types, such as with JavaScriptSerializer and XML Schemas.

    Objective 3.2 Perform symmetric and asymmetric encryption

    Using symmetric and asymmetric encryption

    Cryptography is about encrypting and decrypting data.

    A symmetric algorithm uses one single key to encrypt and decrypt the data. You need to pass
    your original key to the receiver so he can decrypt your data.

    An asymmetric algorithm uses two different keys that are mathematically related to each other. One key is completely public and can be read and used by everyone. The other part is private and should never be shared with someone else.

    Symmetric encryption is faster than asymmetric encryption and is well-suited for larger data sets. Asymmetric encryption is not optimized for encrypting long messages, but it can be very useful for decrypting a small key. Combining these two techniques can help you transmit large amounts of data in an encrypted way.

    Initialization vector(IV) is used to add some randomness to encrypting data. It makes sure that the same data results in a different encrypted message each time.

    MORE INFO ABOUT .NET Framework Cryptography Model

    Working with encryption in the .NET Framework

    The .NET Framework has a managed implementation of the Advanced Encryption Standard (AES) algorithm in the AesManaged class. All cryptography classes can be found in the System.Security.Cryptography class.

    LISTING 3-17 Use a symmetric encryption algorithm

    public static void EncryptSomeText() 
    { 
        string original = "My secret data!"; 
     
        using (SymmetricAlgorithm symmetricAlgorithm =  
            new AesManaged()) 
        { 
            byte[] encrypted = Encrypt(symmetricAlgorithm, original); 
            string roundtrip = Decrypt(symmetricAlgorithm, encrypted); 
     
            // Displays: My secret data!  
            Console.WriteLine("Original:   {0}", original);  
            Console.WriteLine("Round Trip: {0}", roundtrip); 
        } 
    } 
     
    static byte[] Encrypt(SymmetricAlgorithm aesAlg, string plainText) 
    { 
        ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV); 
     
        using (MemoryStream msEncrypt = new MemoryStream()) 
        { 
            using (CryptoStream csEncrypt =  
                new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write)) 
            { 
                using (StreamWriter swEncrypt = new StreamWriter(csEncrypt)) 
                { 
                    swEncrypt.Write(plainText); 
                } 
                return msEncrypt.ToArray(); 
            } 
        } 
    }
    
    static string Decrypt(SymmetricAlgorithm aesAlg, byte[] cipherText) 
    { 
        ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV); 
     
        using (MemoryStream msDecrypt = new MemoryStream(cipherText)) 
        { 
            using (CryptoStream csDecrypt = 
                new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read)) 
            { 
                using (StreamReader srDecrypt = new StreamReader(csDecrypt)) 
                { 
                    return srDecrypt.ReadToEnd(); 
                } 
            } 
        } 
    }
    

    The SymmetricAlgorithm class has both a method for creating an encryptor and a decryptor. By using the CryptoStream class, you can encrypt or decrypt a byte sequence.

    You can use the RSACryptoServiceProvider and DSACryptoServiceProvider classes for asymmetric encryption.

    LISTING 3-18 Exporting a public key

    RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); 
    string publicKeyXML = rsa.ToXmlString(false); 
    string privateKeyXML = rsa.ToXmlString(true); 
     
    Console.WriteLine(publicKeyXML); 
    Console.WriteLine(privateKeyXML); 
    // Displays: 
    //<RSAKeyValue> 
    //  <Modulus> 
    //    tYo35ywT0Q0KCNhFPu207bS8rrTk91YaxNcD2ElQ1eoWpdYnoCsdj1KaW/as9zFLYW5slg5Qq8ltdkxZuU
    //    fh0j2t+7ZFH8RRAD808GkZTrUi1zv3yqMjQDphHOcNfWh+dQrPmp1ShFxEGuA9Y4Ij9RINU5jcfviPa 
    //    B1ClLXaGbc= 
    //  </Modulus> 
    //  <Exponent>AQAB</Exponent> 
    //</RSAKeyValue> 
    //<RSAKeyValue> 
    //  <Modulus> 
    //    tYo35ywT0Q0KCNhFPu207bS8rrTk91YaxNcD2ElQ1eoWpdYnoCsdj1KaW/as9zFLYW5slg5Qq8ltdkxZuU 
    //    fh0j2t+7ZFH8RRAD808GkZTrUi1zv3yqMjQDphHOcNfWh+dQrPmp1ShFxEGuA9Y4Ij9RINU5jcfviPa 
    //    B1ClLXaGbc= 
    //  </Modulus> 
    //  <Exponent>AQAB</Exponent> 
    //  <P> 
    //    4uhNaN3cPSUzr+KxHmpKyeaD39RT+kWjjDcn/9sTAV/HmDzFzjsiov3KyJ+3XCXucx5TU0lhDOLc/ 
    //    cO+Xrquqw== 
    //  </P> 
    //  <Q> 
    //       zNDVw6oL7YNglrFAeqmgIL3Oj2PkUxrWvoYHCbuFwJKpkWvFBRwZfKXHzzU0zaU5bGdX7M24hW8z5s0 
    //       eF9CRJQ== 
    //  </Q> 
    //  <DP> 
    //       jkS+/GhWxZPEw5vsF7jnaY3502ZqvPna4HhYwQgX832dRKueDn9vaSidc4sIyWMTDeTOs+LHUfAQRZ/ 
    //       shbKg/w== 
    //  </DP> 
    //  <DQ> 
    //       HV4QWJboUO0Wi2Ts/umViTxOAudq1LOzeOwU1ENsITmmULCoNlxaFzJaHQ7e/GGlgzKqO80fmRph0c 
    //       U1fGqudQ== 
    //  </DQ> 
    //  <InverseQ> 
    //       BW1VUOgXpkRnn2twvb72uxcbK6+o9ns3xa4Ypm+++7vzlg6t/Iyvk94xNJWjjgR+XsSpN6JEtztWol8 
    //       bv8HEyA== 
    //  </InverseQ> 
    //  <D> 
    //       IOZUrUNyr+8iA2pWWkowAOhBTZQg7qYfIc8ptjfLO4k544IFGmTV7ZR1vvbcb8vyMk0Vxrf/bLKLcOX 
    //       zWL2rMeWYGuoTbZEeUbr0SlmesHARL7X/feCm9MIyPjhlhJieRVG3h4f+TyAVo70jmYVcSou+xAaad3 
    //       7o3Pa8Vny6qIk= 
    //  </D> 
    //</RSAKeyValue>
    

    LISTING 3-19 Using a public and private key to encrypt and decrypt data

    UnicodeEncoding ByteConverter = new UnicodeEncoding(); 
    byte[] dataToEncrypt = ByteConverter.GetBytes("My Secret Data!"); 
     
    byte[] encryptedData; 
    using (RSACryptoServiceProvider RSA = new RSACryptoServiceProvider()) 
    { 
        RSA.FromXmlString(publicKeyXML); 
        encryptedData = RSA.Encrypt(dataToEncrypt, false); 
    } 
     
    byte[] decryptedData; 
    using (RSACryptoServiceProvider RSA = new RSACryptoServiceProvider()) 
    { 
        RSA.FromXmlString(privateKeyXML); 
        decryptedData = RSA.Decrypt(encryptedData, false); 
    } 
     
    string decryptedString = ByteConverter.GetString(decryptedData); 
    Console.WriteLine(decryptedString); // Displays: My Secret Data!
    

    The .NET Framework offers a secure location for storing asymmetric keys in a key container.

    LISTING 3-20 Using a key container for storing an asymmetric key

    string containerName = "SecretContainer"; 
    CspParameters csp = new CspParameters() { KeyContainerName = containerName }; 
    byte[] encryptedData; 
    using (RSACryptoServiceProvider RSA = new RSACryptoServiceProvider(csp)) 
    { 
        encryptedData = RSA.Encrypt(dataToEncrypt, false); 
    }
    

    Loading the key from the key container is the exact same process. You can securely store your asymmetric key without malicious users being able to read it.

    Using hashing

    LISTING 3-21 A naïve set implementation

    class Set<T> 
    { 
        private List<T> list = new List<T>(); 
     
        public void Insert(T item) 
        { 
            if (!Contains(item)) 
                list.Add(item); 
        } 
     
        public bool Contains(T item) 
        { 
            foreach (T member in list) 
                if (member.Equals(item)) 
                    return true; 
            return false; 
        } 
    }
    

    Hashing is the process of taking a large set of data and mapping it to a smaller data set of fixed length. For example, mapping all names to a specific integer. Instead of checking the complete name, you would have to use only an integer value.

    LISTING 3-22 A set implementation that uses hashing

    class Set<T> 
    { 
        private List<T>[] buckets = new List<T>[100]; 
     
        public void Insert(T item) 
        { 
            int bucket = GetBucket(item.GetHashCode()); 
            if (Contains(item, bucket)) 
                return; 
            if (buckets[bucket] == null) 
                buckets[bucket] = new List<T>(); 
            buckets[bucket].Add(item); 
        } 
        public bool Contains(T item) 
        { 
            return Contains(item, GetBucket(item.GetHashCode())); 
        } 
     
        private int GetBucket(int hashcode) 
        { 
            // A Hash code can be negative. To make sure that you end up with a positive   
            // value cast the value to an unsigned int. The unchecked block makes sure that    
            // you can cast a value larger than int to an int safely. 
            unchecked 
            { 
                return (int)((uint)hashcode % (uint)buckets.Length); 
            } 
        } 
     
        private bool Contains(T item, int bucket) 
        { 
            if (buckets[bucket] != null) 
                foreach (T member in buckets[bucket]) 
                    if (member.Equals(item)) 
                        return true; 
            return false; 
        } 
    }
    

    As a general guideline, the distribution of hash codes must be as random as possible. This is why the set implementation uses the GetHashCode method on each object to calculate in which bucket it should go.

    Equal items should have equal hash codes. Combined with the encryption technologies that the .NET Framework offers, hashing is an important technique to validate the authenticity of a message.

    MORE INFO ABOUT Object.GetHashCode Method

    LISTING 3-23 Using SHA256Managed to calculate a hash code

    UnicodeEncoding byteConverter = new UnicodeEncoding(); 
    SHA256 sha256 = SHA256.Create(); 
     
    string data = "A paragraph of text"; 
    byte[] hashA = sha256.ComputeHash(byteConverter.GetBytes(data)); 
    data = "A paragraph of changed text"; 
    byte[] hashB = sha256.ComputeHash(byteConverter.GetBytes(data)); 
     
    data = "A paragraph of text"; 
    byte[] hashC = sha256.ComputeHash(byteConverter.GetBytes(data)); 
     
    Console.WriteLine(hashA.SequenceEqual(hashB)); // Displays: false 
    Console.WriteLine(hashA.SequenceEqual(hashC)); // Displays: true
    

    Managing and creating certificates

    Digital certificates are the area where both hashing and asymmetric encryption come together.

    EXAMPLE:
    If Alice sends a message to Bob, she first hashes her message to generate a hash code. Alice then encrypts the hash code with her private key to create a personal signature. Bob receives Alice’s message and signature. He decrypts the signature using Alice’s public key and now he has both the message and the hash code. He can then hash the message and see whether his hash code and the hash code from Alice match.

    When working on your development or testing environment, You can create certificates by using the Makecert.exe tool. This tool generates X.509 certificates for testing purposes. The X.509 certificate is a widely used standard for defining digital certificates.

    makecert testCert.cer
    

    This command generates a file called testCert.cer that you can use as a certificate. You first need to install this certificate on your computer to be able to use it. After installation, it’s stored in a certificate store. The following line creates a certificate and installs it in a custom certificate store named testCertStore:

    makecert -n "CN=WouterDeKort" -sr currentuser -ss testCertStore
    

    LISTING 3-24 Signing and verifying data with a certificate

    public static void SignAndVerify() 
    { 
        string textToSign = "Test paragraph"; 
        byte[] signature = Sign(textToSign, "cn=WouterDeKort"); 
        // Uncomment this line to make the verification step fail 
        // signature[0] = 0; 
        Console.WriteLine(Verify(textToSign, signature)); 
    } 
     
    static byte[] Sign(string text, string certSubject) 
    { 
        X509Certificate2 cert = GetCertificate(); 
        var csp = (RSACryptoServiceProvider)cert.PrivateKey; 
        byte[] hash = HashData(text); 
        return csp.SignHash(hash, CryptoConfig.MapNameToOID("SHA1")); 
    } 
     
    static bool Verify(string text, byte[] signature) 
    { 
        X509Certificate2 cert = GetCertificate(); 
        var csp = (RSACryptoServiceProvider)cert.PublicKey.Key; 
        byte[] hash = HashData(text); 
        return csp.VerifyHash(hash,  
            CryptoConfig.MapNameToOID("SHA1"),  
            signature); 
    } 
     
    private static byte[] HashData(string text) 
    { 
        HashAlgorithm hashAlgorithm = new SHA1Managed(); 
        UnicodeEncoding encoding = new UnicodeEncoding(); 
        byte[] data = encoding.GetBytes(text); 
        byte[] hash = hashAlgorithm .ComputeHash(data); 
        return hash; 
    } 
     
    private static X509Certificate2 GetCertificate() 
    { 
        X509Store my = new X509Store("testCertStore",  
            StoreLocation.CurrentUser); 
        my.Open(OpenFlags.ReadOnly); 
     
        var certificate = my.Certificates[0]; 
     
        return certificate; 
    }
    

    MakeCert is deprecated, use New-SelfSignedCertificate instead.

    Using code access permissions

    CAS, Code Access Security

    When using CAS, you need to ask for permission to execute certain operations or access protected resources. CLR enforces security restrictions on managed code and makes sure that your code has the correct permissions to access privileged resources.

    Applications that are installed on your computer or on your local intranet have full trust. When running in a sandboxed environment such as Internet Explorer or SQL Server, CAS restricts the operations that an application can execute.

    CAS performs the following functions in the .NET Framework:

    • Defines permissions for accessing system resources.
    • Enables code to demand that its callers have specific permissions. For example, a library that exposes methods that create files should enforce that its callers have the right for file input/output.
    • Enables code to demand that its callers possess a digital signature. This way, code can make sure that it’s only called by callers from a particular organization or location.
    • Enforces all those restrictions at runtime.

    The call stack is a data structure that stores information about all the active methods at a specific moment.

    CAS walks the call stack and sees whether every element on the stack has the required permissions. This way, you can be sure that a less-trusted method cannot call some restricted code through a highly trusted method.
    The base class for all things related to CAS is System.Security.CodeAccessPermission. Permissions that inherit from CodeAccessPermission are permissions such as FileIOPermission, ReflectionPermission, or SecurityPermission. When applying one of those permissions, you ask the CLR for the permission to execute a protected operation or access a resource.

    You can specify CAS in two ways: declarative or imperative.

    Declarative means that you use attributes to apply security information to your code.

    LISTING 3-25 Declarative CAS

    [FileIOPermission(SecurityAction.Demand, 
        AllLocalFiles = FileIOPermissionAccess.Read)] 
    public void DeclarativeCAS() 
    { 
        // Method body 
    }
    

    The imperative way means that you explicitly ask for the permission in the code.

    LISTING 3-26 Imperative CAS

    FileIOPermission f = new FileIOPermission(PermissionState.None); 
    f.AllLocalFiles = FileIOPermissionAccess.Read; 
    try 
    { 
        f.Demand(); 
    } 
    catch (SecurityException s) 
    { 
        Console.WriteLine(s.Message); 
    }
    

    MORE INFO ABOUT CODE ACCESS SECURITY

    Securing string data

    A SecureString automatically encrypts its value, and also pinned to a specific memory location. It is a mutable string that can be made read-only when necessary. It implements IDisposable.

    A SecureString doesn’t completely solve all security problems. Because it needs to be initialized at some point, the data that is used to initialize the SecureString is still in memory. To minimize this risk and force you to think about it, SecureString can deal with only individual characters at a time. It’s not possible to pass a string directly to a SecureString.

    LISTING 3-27 Initializing a SecureString

    using (SecureString ss = new SecureString()) 
    { 
        Console.Write("Please enter password: "); 
        while (true) 
        { 
            ConsoleKeyInfo cki = Console.ReadKey(true); 
            if (cki.Key == ConsoleKey.Enter) break; 
     
            ss.AppendChar(cki.KeyChar); 
            Console.Write("*"); 
        } 
        ss.MakeReadOnly(); 
    }
    

    LISTING 3-28 Getting the value of a SecureString

    public static void ConvertToUnsecureString(SecureString securePassword) 
    { 
        IntPtr unmanagedString = IntPtr.Zero; 
        try 
        { 
            unmanagedString = Marshal.SecureStringToGlobalAllocUnicode(securePassword); 
            Console.WriteLine(Marshal.PtrToStringUni(unmanagedString)); 
        } 
        finally 
        { 
            Marshal.ZeroFreeGlobalAllocUnicode(unmanagedString); 
        } 
    }
    

    The finally statement makes sure that the string is removed from memory even if there is an exception thrown in the code.

    TABLE 3-1 Methods for working with SecureString

    Decrypt method Clear memory method
    SecureStringToBSTR ZeroFreeBSTR
    SecureStringToCoTaskMemAnsi ZeroFreeCoTaskMemAnsi
    SecureStringToCoTaskMemUnicode ZeroFreeCoTaskMemUnicode
    SecureStringToGlobalAllocAnsi ZeroFreeGlobalAllocAnsi
    SecureStringToGlobalAllocUnicode ZeroFreeGlobalAllocUnicode

    It’s important to realize that a SecureString is not completely secure.

    Objective summary

    • A symmetric algorithm uses the same key to encrypt and decrypt data.
    • An asymmetric algorithm uses a public and private key that are mathematically linked.
    • Hashing is the process of converting a large amount of data to a smaller hash code.
    • Digital certificates can be used to verify the authenticity of an author.
    • CAS are used to restrict the resources and operations an application can access and execute.
    • System.Security.SecureString can be used to keep sensitive string data in memory.

    Objective 3.3 Manage assemblies

    Assemblies are completely self-contained, they contain all the information they need to run. This is called the assembly’s manifest.

    Assembly is language-neutral.

    An assembly can be versioned, which enables you to have different versions of a specific assembly on one system without causing conflicts.

    Signing assemblies using a strong name

    A regular assembly is what Visual Studio generates for you by default.

    A strong-named assembly is signed with a public/private key pair that uniquely identifies the publisher of the assembly and the content of the assembly.

    Benefits of strong-named assembly:

    • Strong names guarantee uniqueness
      No other assembly can have the exact same strong name.
    • Strong names protect your versioning lineage
      Users can be sure that the new version originates from the same publisher.
    • Strong names provide a strong integrity check
      The .NET Framework sees whether a strong-named assembly has changed since the moment it was signed.

    A strong-named assembly can reference only other assemblies that are also strongly named.

    You can view the public key by using the Strong Name tool (Sn.exe) .

    LISTING 3-29 Inspecting the public key of a signed assembly

    C:\>sn -Tp C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.D 
    ata.dll 
     
    Microsoft (R) .NET Framework Strong Name Utility  Version 4.0.30319.17929 
    Copyright (c) Microsoft Corporation.  All rights reserved. 
     
    Identity public key (hash algorithm: Unknown): 
    00000000000000000400000000000000 
     
    Signature public key (hash algorithm: sha256): 
    002400000c800000140100000602000000240000525341310008000001000100613399aff18ef1 
    a2c2514a273a42d9042b72321f1757102df9ebada69923e2738406c21e5b801552ab8d200a65a2 
    35e001ac9adc25f2d811eb09496a4c6a59d4619589c69f5baf0c4179a47311d92555cd006acc8b 
    5959f2bd6e10e360c34537a1d266da8085856583c85d81da7f3ec01ed9564c58d93d713cd0172c 
    8e23a10f0239b80c96b07736f5d8b022542a4e74251a5f432824318b3539a5a087f8e53d2f135f 
    9ca47f3bb2e10aff0af0849504fb7cea3ff192dc8de0edad64c68efde34c56d302ad55fd6e80f3 
    02d5efcdeae953658d3452561b5f36c542efdbdd9f888538d374cef106acf7d93a4445c3c73cd9 
    11f0571aaf3d54da12b11ddec375b3 
     
    Public key token is b77a5c561934e089
    

    The public key token is a small string that represents the public key. It is generated by hashing the public key and taking the last eight bytes. If you reference another assembly, you store only the public key token, which preserves space in the assembly manifest. The CLR does not use the public key token when making security decisions because it could happen that several public keys have the same public key token.

    You can use delayed signing to avoid private key leak. When using delayed signing, you use only the public key to sign an assembly and you delay using the private key until the project is ready for deployment.

    A strongly named assembly does not prove that the assembly comes from the original publisher. It only shows that the person who created the assembly has access to the private key. If you want to make sure that users can verify you as the publisher, you have to use something called Authenticode.

    Putting an assembly in the GAC

    GAC, Global Assembly Cache

    Normally, you want to avoid installing assemblies in the GAC. One reason to deploy to the GAC is when an assembly is shared by multiple applications. Other reasons for installing an assembly into the GAC can be the enhanced security (normally only users with administrator rights can alter the GAC) or the situation where you want to deploy multiple versions of the same assembly.

    Deploying an assembly in the GAC can be done in two ways:

    • For production scenarios, use a specific installation program that has access to the GAC such as the Windows Installer 2.0.
    • In development scenarios, use a tool called the Global Assembly Cache tool (Gacutil.exe).

    View the content of your GAC by running the following command from a developer command prompt:

    gacutil -l
    

    Installing an assembly in the GAC can be done with the following command:

    gacutil –i [assembly name]
    

    Remove an assembly from the GAC:

    gacutil –u [assembly name]
    

    Versioning assemblies

    AssemblyFileVersionAttribute is the one that should be incremented with each build. This is not something you want to do on the client, where it would get incremented with every developer build. Instead, you should integrate this into your build process on your build server.

    AssemblyVersionAttribute should be incremented manually. This should be done when you plan to deploy a specific version to production.

    You can deploy multiple versions of the same assembly to the GAC and avoid the DLL problem that happened with regular DLL files. This is called side-by-side hosting, in which multiple versions of an assembly are hosted together on one computer.

    These configuration files can be used to influence the binding of referenced assemblies:

    • Application configuration files
    • Publisher policy files
    • Machine configuration files

    LISTING 3-30 Redirecting assembly bindings to a newer version

    <configuration> 
       <runtime> 
          <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> 
           <dependentAssembly> 
             <assemblyIdentity name="myAssembly" 
                               publicKeyToken="32ab4ba45e0a69a1" 
                               culture="en-us" /> 
             <!-- Redirecting to version 2.0.0.0 of the assembly. --> 
             <bindingRedirect oldVersion="1.0.0.0" 
                              newVersion="2.0.0.0"/> 
           </dependentAssembly> 
          </assemblyBinding> 
       </runtime> 
    </configuration>
    

    MORE INFO ABOUT HOW THE RUNTIME LOCATES ASSEMBLIES

    You can specify extra locations where the CLR should look in the configuration file of the application.

    LISTING 3-31 Specifying additional locations for assemblies

    <?xml version="1.0" encoding="utf-8" ?> 
    <configuration> 
      <runtime> 
        <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> 
          <probing privatePath="MyLibraries"/> 
        </assemblyBinding> 
      </runtime> 
    </configuration>
    

    A codebase element can specify a location for an assembly that is outside of the application’s directory. These assemblies have to be strongly named if they are not in the current application’s folder.

    LISTING 3-32 Specifying additional locations for assemblies

    <?xml version="1.0" encoding="utf-8" ?> 
    <configuration> 
      <runtime> 
        <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> 
          <dependentAssembly> 
            <codeBase version="1.0.0.0" 
              href= "http://www.mydomain.com/ReferencedAssembly.dll"/> 
          </dependentAssembly> 
        </assemblyBinding> 
      </runtime> 
    </configuration>
    

    Creating a WinMD assembly

    WinMD, Windows Metadata

    WinMD files can contain both code and metadata.

    Objective summary

    • An assembly is a compiled unit of code that contains metadata.
    • An assembly can be strongly signed to make sure that no one can tamper with the content.
    • Signed assemblies can be put in the GAC.
    • An assembly can be versioned, and applications will use the assembly version they were developed with. It’s possible to use configuration files to change these bindings.
    • A WinMD assembly is a special type of assembly that is used by WinRT to map nonnative languages to the native WinRT components.

    Objective 3.4 Debug an application

    Build configurations

    In release mode, the compiled code is fully optimized, and no extra information for debugging purposes is created.
    In debug mode, there is no optimization applied, and additional information is outputted.

    Example demonstrate the difference between a debug and a release build:

    LISTING 3-33 A simple console application

    using System; 
    using System.Threading; 
     
    public static class Program 
    { 
        public static void Main() 
        { 
            Timer t = new Timer(TimerCallback, null, 0, 2000); 
            Console.ReadLine(); 
        } 
      
        private static void TimerCallback(Object o) 
        { 
            Console.WriteLine("In TimerCallback: " + DateTime.Now); 
            GC.Collect(); 
        } 
    }
    

    When you run this application in debug mode, it does a nice job of outputting the time every 2 seconds and keeps on doing this until you terminate the application. But when you execute this application in release mode, it outputs the current date and time only once. The compiler optimizes the code. In this scenario, it sees that the Timer object is not used anymore, so it’s no longer considered a root object and the garbage collector removes it from memory.

    In debug configuration, the compiler inserts extra no-operation (NOP) instructions and branch instructions. NOP instructions are instructions that effectively don’t do anything (for example, an assignment to a variable that’s never used). A branch instruction is a piece of code that is executed conditionally (for example, when some variable is true or false).

    MORE INFO ABOUT DEBUGGER BASICS

    Creating and managing compiler directives

    LISTING 3-34 Checking for the debug symbol

    public void DebugDirective() 
    { 
        #if DEBUG 
            Console.WriteLine("Debug mode"); 
        #else 
            Console.WriteLine("Not debug"); 
        #endif 
    }
    

    When using the #if directive, you can use the operators you are used to from C#: == (equality), != (inequality), && (and), || (or) and !(not) to test for true or false.

    LISTING 3-35 Defining a custom symbol

    #define MySymbol 
     
    // … 
     
    public void UseCustomSymbol() 
    { 
        #if MySymbol 
            Console.WriteLine("Custom symbol is defined"); 
        #endif 
    }
    

    Using directives this way can make your code harder to understand, and you should try to avoid them if possible. A scenario in which using preprocessor directives can be necessary is when you are building a library that targets multiple platforms.

    LISTING 3-36 Using preprocessor directives to target multiple platforms

    public Assembly LoadAssembly<T>() 
    {  
        #if !WINRT 
            Assembly assembly = typeof(T).Assembly; 
        #else 
            Assembly assembly = typeof(T).GetTypeInfo().Assembly; 
        #endif 
     
        return assembly; 
    }
    

    #undef can be used to remove the definition of a symbol. This can be used in a situation where you want to debug a piece of code that’s normally included only in a release build. You can then use the #undef directive to remove the debug symbol.

    You can use #warning and #error to report an error or warning to the compiler.

    LISTING 3-37 The warning and error directives

    #warning This code is obsolete 
     
    #if DEBUG 
    #error Debug build is not allowed 
    #endif
    

    The #line directive can be used to modify the compiler’s line number and even the name of the file. You can also hide a line of code from the debugger.

    LISTING 3-38 The line directive

    #line 200 "OtherFileName" 
        int a;    // line 200 
    #line default 
        int b;   // line 4 
    #line hidden  
        int c; // hidden 
        int d; // line 7
    

    LISTING 3-39 The pragma warning directive

    #pragma warning disable  
     while (false) 
    { 
        Console.WriteLine("Unreachable code"); 
    } 
    #pragma warning restore
    

    LISTING 3-40 Disabling and enabling specific warnings

    #pragma warning disable 0162, 0168 
    int i; 
    #pragma warning restore 0162 
    while (false) 
    { 
        Console.WriteLine("Unreachable code"); 
    } 
    #pragma warning restore 
    

    LISTING 3-41 Call a method only in a debug build

    public void SomeMethod() 
    {  
        #if DEBUG 
        Log("Step1"); 
        #endif 
    } 
    private static void Log(string message) 
    { 
        Console.WriteLine("message"); 
    } 
    

    LISTING 3-42 Applying the ConditionalAttribute

    [Conditional("DEBUG")] 
    private static void Log(string message) 
    { 
        Console.WriteLine("message"); 
    }
    

    LISTING 3-43 Applying the DebuggerDisplayAttribute

    [DebuggerDisplay("Name = {FirstName} {LastName")] 
    public class Person 
    { 
        public string FirstName { get; set; } 
        public string LastName { get; set; } 
    }
    

    Managing program database files and symbols

    A program database (PDB) file, which is an extra data source that annotates your application’s code with additional information that can be useful during debugging.

    /debug:full vs /debug:pdbonly:
    When you specify the full flag, a PDB file is created, and the specific assembly has debug information. With the pdbonly flag, the generated assembly is not modified, and only the PDB file is generated. pdbonly is recommended in release build.

    The important thing is that this GUID is created at compile time, so if you recompile your application, you get a new PDB file that matches your recompiled build exactly.

    MORE INFO ABOUT USING PDBCOPY

    Objective summary

    • Visual Studio build configurations can be used to configure the compiler.
    • A debug build outputs a nonoptimized version of the code that contains extra instructions to help debugging.
    • A release build outputs optimized code that can be deployed to a production environment.
    • Compiler directives can be used to give extra instructions to the compiler. You can use them, for example, to include code only in certain build configurations or to suppress certain warnings.
    • A program database (PDB) file contains extra information that is required when debugging an application.

    Objective 3.5 Implement diagnostics in an application

    Logging and tracing

    Tracing is a way for you to monitor the execution of your application while it’s running.

    Logging is always enabled and is used for error reporting.

    LISTING 3-45 Using the Debug class

    Debug.WriteLine("Starting application"); 
    Debug.Indent(); 
    int i = 1 + 2; 
    Debug.Assert(i == 3); 
    Debug.WriteLineIf(i > 0, "i is greater than 0");
    

    LISTING 3-46 Using the TraceSource class

    TraceSource traceSource = new TraceSource("myTraceSource", 
        SourceLevels.All); 
     
    traceSource.TraceInformation("Tracing application.."); 
    traceSource.TraceEvent(TraceEventType.Critical, 0, "Critical trace"); 
    traceSource.TraceData(TraceEventType.Information, 1,  
        new object[] { "a", "b", "c" }); 
     
    traceSource.Flush(); 
    traceSource.Close(); 
     
    // Outputs: 
    // myTraceSource Information: 0 : Tracing application.. 
    // myTraceSource Critical: 0 : Critical trace 
    // myTraceSource Information: 1 : a, b, c
    

    Options for the TraceEventType enum:

    • Critical
      This is the most severe option. It should be used sparingly and only for very serious and irrecoverable errors.
    • Error
      This enum member has a slightly lower priority than Critical, but it still indicates that something is wrong in the application. It should typically be used to flag a problem that has been handled or recovered from.
    • Warning
      This value indicates something unusual has occurred that may be worth
      investigating further. For example, you notice that a certain operation suddenly takes longer to process than normal or you flag a warning that the server is getting low on memory.
    • Information
      This value indicates that the process is executing correctly, but there is some interesting information to include in the tracing output file. It may be information that a user has logged onto a system or that something has been added to the database.
    • Verbose
      This is the loosest of all the severity related values in the enum. It should be
      used for information that is not indicating anything wrong with the application and is likely to appear in vast quantities. For example, when instrumenting all methods in a type to trace their beginning and ending, it is typical to use the verbose event type.
    • Stop, Start, Suspend, Resume, Transfer
      These event types are not indications of severity, but mark the trace event as relating to the logical flow of the application. They are known as activity event types and mark a logical operation’s starting or stopping, or transferring control to another logical operation.

    Both the Debug and TraceSource classes have a Listeners property. This property holds a collection of TraceListeners, which process the information from the Write, Fail, and Trace methods.

    Both the Debug and the TraceSource class use an instance of the DefaultTraceListener class. The DefaultTraceListener writes to the Output window and shows the message box when assertion fails.

    TABLE 3-2 TraceListeners in the .NET Framework

    Name Output
    ConsoleTraceListener Standard output or error stream
    DelimitedListTraceListener TextWriter
    EventLogTraceListener EventLog
    EventSchemaTraceListener XML-encoded, schema-compliant log file
    TextWriterTraceListener TextWriter
    XmlWriterTraceListener XML-encoded data to a TextWriter or stream

    If you don’t want the DefaultTraceListener to be active, you need to clear the current listeners collection. You can add as many listeners as you want.

    LISTING 3-47 Configuring TraceListener.

    Stream outputFile = File.Create("tracefile.txt"); 
    TextWriterTraceListener textListener = 
        new TextWriterTraceListener(outputFile); 
     
    TraceSource traceSource = new TraceSource("myTraceSource", 
        SourceLevels.All); 
     
    traceSource.Listeners.Clear(); 
    traceSource.Listeners.Add(textListener); 
     
    traceSource.TraceInformation("Trace output"); 
     
    traceSource.Flush(); 
    traceSource.Close();
    

    Instead of configuring the listeners through code, you can also use a configuration file.

    LISTING 3-48 Using a configuration file for tracing

    <?xml version="1.0" encoding="utf-8" ?> 
    <configuration> 
      <system.diagnostics> 
        <sources> 
          <source name="myTraceSource" switchName="defaultSwitch"> 
            <listeners> 
              <add initializeData="output.txt"  
                   type="System.Diagnostics.TextWriterTraceListeer"             
                   name="myLocalListener"> 
                   <filter type="System.Diagnostics.EventTypeFilter"  
                    initializeData="Warning"/> 
              </add> 
              <add name="consoleListener" /> 
              <remove name="Default"/> 
            </listeners> 
          </source> 
        </sources> 
        <sharedListeners> 
          <add initializeData="output.xml" type="System.Diagnostics.XmlWriterTraceListener" 
             name="xmlListener" traceOutputOptions="None" /> 
          <add type="System.Diagnostics.ConsoleTraceListener" name="consoleListener" 
              traceOutputOptions="None" /> 
        </sharedListeners> 
        <switches> 
          <add name="defaultSwitch" value="All" /> 
        </switches> 
      </system.diagnostics> 
    </configuration>
    

    The configuration file also defines a switch, which is used by a trace source to determine whether it should do something with a trace message it receives. This way, you can determine which trace messages you want to see. Lowering the number of messages enhances performance and will result in a smaller output file. After you have found the particular area that you want to focus on, you can set your switch to a more detailed level.

    A filter is applied to an individual listener. When you have multiple listeners for one single trace source, you can use filters to determine which trace events are actually processed by the listener. You could have a listener that sends text messages only for the critical events in a trace source, for example.

    LISTING 3-49 Writing data to the event log

    using System; 
    using System.Diagnostics; 
     
    class MySample 
    { 
        public static void Main() 
        { 
            if (!EventLog.SourceExists("MySource")) 
            { 
                EventLog.CreateEventSource("MySource", "MyNewLog"); 
                Console.WriteLine("CreatedEventSource"); 
                Console.WriteLine("Please restart application"); 
                Console.ReadKey(); 
                return; 
            } 
     
            EventLog myLog = new EventLog(); 
            myLog.Source = "MySource"; 
            myLog.WriteEntry("Log event!"); 
        } 
    }
    

    These messages can then be viewed by the Windows Event Viewer.

    You can also read programmatically from the event log.

    LISTING 3-50 Reading data from the event log

    EventLog log = new EventLog("MyNewLog"); 
     
    Console.WriteLine("Total entries: " + log.Entries.Count); 
    EventLogEntry last = log.Entries[log.Entries.Count - 1]; 
    Console.WriteLine("Index:   " + last.Index); 
    Console.WriteLine("Source:  " + last.Source); 
    Console.WriteLine("Type:    " + last.EntryType); 
    Console.WriteLine("Time:    " + last.TimeWritten); 
    Console.WriteLine("Message: " + last.Message);
    

    The EventLog also gives you the option to subscribe to changes in the event log.

    LISTING 3-51 Writing data to the event log

    using System; 
    using System.Diagnostics; 
      
    class EventLogSample 
    { 
        public static void Main() 
        { 
            EventLog applicationLog = new EventLog("Application", ".", "testEventLogEvent"); 
            applicationLog.EntryWritten += (sender, e) => 
            {
                Console.WriteLine(e.Entry.Message); 
            }; 
            applicationLog.EnableRaisingEvents = true; 
            applicationLog.WriteEntry("Test message", EventLogEntryType.Information); 
             
            Console.ReadKey();
        } 
    }
    

    Profiling your application

    Profiling is the process of determining how your application uses certain resources.

    With performance, one thing is always true: Don’t get into premature optimizations.

    A guideline is to write your code as easy and maintainable as possible. When you run into performance problems, you can use a profiler to actually measure which part of your application is causing problems.

    A simple way of measuring the execution time of some code is by using the Stopwatch class.

    LISTING 3-52 Using the StopWatch class

    using System; 
    using System.Diagnostics; 
    using System.Text; 
     
    namespace Profiling 
    { 
        class Program 
        { 
            const int numberOfIterations = 100000; 
            static void Main(string[] args) 
            {
                Stopwatch sw = new Stopwatch(); 
                sw.Start(); 
                Algorithm1(); 
                sw.Stop(); 
      
                Console.WriteLine(sw.Elapsed); 
                 
                sw.Reset(); 
                sw.Start(); 
                Algorithm2(); 
                sw.Stop(); 
                Console.WriteLine(sw.Elapsed); 
                Console.WriteLine("Ready…"); 
                Console.ReadLine();        } 
     
            private static void Algorithm2() 
            { 
                string result = ""; 
      
                for (int x = 0; x < numberOfIterations; x++) 
                { 
                    result += ‘a’; 
                } 
            } 
     
            private static void Algorithm1() 
            { 
                StringBuilder sb = new StringBuilder(); 
                for (int x = 0; x < numberOfIterations; x++) 
                { 
                    sb.Append(‘a’);
                } 
      
                string result = sb.ToString(); 
            } 
        } 
    }
    // Displays 
    // 00:00:00.0007635 
    // 00:00:01.4071420
    

    MORE INFO ABOUT PERFORMANCE TOOLS

    Creating and monitoring performance counters

    LISTING 3-53 Reading data from a performance counter

    using System; 
    using System.Diagnostics; 
     
    namespace PerformanceCounters 
    { 
        class Program 
        { 
            static void Main(string[] args) 
            { 
                Console.WriteLine("Press escape key to stop"); 
                using (PerformanceCounter pc =  
                             new PerformanceCounter("Memory", "Available Bytes")) 
                { 
                    string text = "Available memory: "; 
                    Console.Write(text); 
                    do 
                    { 
                        while (!Console.KeyAvailable) 
                        { 
                            Console.Write(pc.RawValue); 
                            Console.SetCursorPosition(text.Length, Console.CursorTop); 
                        } 
                    } while (Console.ReadKey(true).Key != ConsoleKey.Escape); 
                } 
            } 
        } 
    }    
    

    All performance counters are part of a category, and within that category they have a unique name. To access the performance counters, your application has to run under full trust, or the account that it’s running under should be an administrator or be a part of the Performance Monitor Users group.

    LISTING 3-54 Reading data from a performance counter

    using System; 
    using System.Diagnostics; 
     
    namespace PerformanceCounters 
    { 
        class Program 
        {
            static void Main(string[] args) 
            { 
                if (CreatePerformanceCounters()) 
                { 
                    Console.WriteLine("Created performance counters"); 
                    Console.WriteLine("Please restart application"); 
                    Console.ReadKey(); 
                    return; 
                } 
                var totalOperationsCounter = new PerformanceCounter( 
                    "MyCategory", 
                    "# operations executed", 
                    "",                 
                    false); 
                var operationsPerSecondCounter = new PerformanceCounter( 
                    "MyCategory", 
                    "# operations / sec", 
                    "", 
                   false); 
     
                totalOperationsCounter.Increment(); 
                operationsPerSecondCounter.Increment(); 
            } 
            private static bool CreatePerformanceCounters() 
            { 
                if (!PerformanceCounterCategory.Exists("MyCategory")) 
                { 
                    CounterCreationDataCollection counters =  
                        new CounterCreationDataCollection 
                    { 
                        new CounterCreationData( 
                            "# operations executed", 
                            "Total number of operations executed", 
                            PerformanceCounterType.NumberOfItems32), 
                        new CounterCreationData( 
                            "# operations / sec", 
                            "Number of operations executed per second", 
                            PerformanceCounterType.RateOfCountsPerSecond32) 
                    }; 
      
                    PerformanceCounterCategory.Create("MyCategory", 
                            "Sample category for Codeproject", counters); 
      
                    return true; 
                } 
                return false; 
            } 
        } 
    }
    

    Useful types of performance counters:

    • NumberOfItems32/NumberOfItems64
      These types can be used for counting the number of operations or items. NumberOfItems64 is the same as NumberOfItems32, except that it uses a larger field to accommodate for larger values.
    • RateOfCountsPerSecond32/RateOfCountsPerSecond64
      These types can be used to calculate the amount per second of an item or operation. RateOfCountsPerSecond64 is the same as RateOfCountsPerSecond32, except that it uses larger fields to accommodate for larger values.
    • AvergateTimer32
      Calculates the average time to perform a process or process an item.

    Objective summary

    • Logging and tracing are important to monitor an application that is in production and should be implemented right from the start.
    • You can use the Debug and TraceSource classes to log and trace messages. By configuring different listeners, you can configure your application to know which data to send where.
    • When you are experiencing performance problems, you can profile your application to find the root cause and fix it.
    • Performance counters can be used to constantly monitor the health of your applications.

    Chapter summary

    • Validating application input is important to ensure the stability and security of your application. You can use the Parse, TryParse, and Convert functions to parse user input. Regular Expressions can be used for matching patterns.
    • Cryptography uses symmetric and asymmetric algorithms together with hashing to secure data.
    • Code access permissions can be used to restrict the types of operations a program may execute.
    • An assembly is a self-contained unit that contains application code and metadata. An assembly can be signed, versioned, and shared by putting it in the GAC.
    • By selecting the correct build configurations, you can output additional information to create program database files that can be used to debug an application.
    • By using logging, tracing, and performance counters, you can monitor an application while it’s in production.

    Note Links

    相关文章

      网友评论

          本文标题:70-483.C3O1-5.Debug applications

          本文链接:https://www.haomeiwen.com/subject/xnsltqtx.html