A license is a policy or agreement between you and the page developer that describes the legal usage of your controls. Licensing enables you to enforce that policy to determine how page developers use your controls and to authorize who can use your controls. Licensing also allows you to enable and disable certain features based on the type of license granted to the developer. Licensing is particularly applicable to commercial control development. The .NET Framework defines an extensible and common licensing framework for use by all components. The licensing framework isn't designed to be completely tamper-proof. Rather, the licensing framework makes using unlicensed components relatively harder and makes it obvious when a developer is violating the licensing policy. Using the licensing framework, you can incorporate custom and arbitrarily complex licensing schemes for your component, such as the following:
The licensing framework revolves around the System.ComponentModel.LicenseProvider class, which you must implement to define a licensing scheme. You can then associate this license provider with your component by using the System.ComponentModel.LicenseProviderAttribute metadata attribute. Your component should incorporate license validation by invoking the IsValid or Validate methods of the LicenseManager class in its constructor. The LicenseManager inspects the component's metadata to select the appropriate LicenseProvider and to retrieve a validated License object from it. LicenseManager defines two usage modes: design time and run time. This allows a component to implement different licensing models for design-time usage and run-time usage. The samples in this section will demonstrate how these classes allow you to incorporate licensing into your server controls. Listing 17-8 contains the implementation of a set of licensed controls associated with various licensing providers. Listing 17-8 A set of licensed server controls using varying licensing schemesusingSystem; usingSystem.ComponentModel; usingSystem.Web.UI.WebControls; namespaceMSPress.ServerControls{ [LicenseProvider(typeof(ServerLicenseProvider))] publicclassLicensedLabel:Label{ publicLicensedLabel(){ LicenseManager.Validate(typeof(LicensedLabel)); } } [LicenseProvider(typeof(EncryptedLicenseProvider))] publicclassEncryptedLicensedLabel:Label{ publicEncryptedLicensedLabel(){ LicenseManager.Validate(typeof(EncryptedLicensedLabel)); } } [LicenseProvider(typeof(ExpiringLicenseProvider))] publicclassDemoLabel:Label{ publicDemoLabel(){ LicenseManager.Validate(typeof(DemoLabel)); } } } The samples in Listing 17-8 demonstrate the two additions you have to make to any control to incorporate licensing. First, you need to add metadata that specifies the type of license provider to use by applying the LicenseProviderAttribute attribute. For example, the DemoLabel control specifies that the ExpiringLicenseProvider class should be used when the control's license is validated. Next you need to invoke the Validate method of the LicenseManager and pass in your control's type as the argument. All usage of the control will now be subject to your licensing scheme. When a control's license is not found or is invalid, the license provider throws a System.ComponentModel.License Exception . We will look at the specifics of the license providers used in the samples shown later in this section. Figure 17-3 shows an example of the error page that is generated when an unlicensed control is used on a page. Figure 17-3. An example of the error generated when a page uses an unlicensed control
The licensing model employed by Visual Studio .NET collects license information from components used by a page developer at design time and embeds them as resources into an assembly at compile time, which can then be extracted and inspected for validity at run time. This model does not perfectly fit the needs of server controls and Web applications. When you implement licensing, you should be sure that the licensing scheme satisfies the following critical elements:
ServerLicenseProviderIn this section, we will use the extensibility of the licensing framework to implement ServerLicenseProvider , a custom license provider implementation. ServerLicenseProvider offers a framework for implementing licensing schemes optimized for server controls and components. You can expect to see a similar framework built into future versions of ASP.NET, enabling all custom controls to implement their licensing logic by using a shared infrastructure. The LicensedLabel control you saw in Listing 17-8 uses the default licensing scheme built into ServerLicenseProvider . The code for this license provider is shown in Listing 17-9. Listing 17-9 ServerLicenseProvider offers a framework for implementing licensing schemes specific to server controls and components.usingSystem; usingSystem.Collections; usingSystem.Collections.Specialized; usingSystem.ComponentModel; usingSystem.IO; usingSystem.Diagnostics; usingSystem.Globalization; usingSystem.Web; namespaceMSPress.ServerControls{ publicclassServerLicenseProvider:LicenseProvider{ privatestaticreadonly ServerLicenseCollectorLicenseCollector= newServerLicenseCollector(); protectedvirtualServerLicenseCreateLicense(Typetype, stringkey){ returnnewServerLicense(type,key); } publicoverrideLicenseGetLicense(LicenseContextcontext, Typetype,objectinstance,boolallowExceptions){ ServerLicenselicense=null; stringerrorMessage=null; if(context.UsageMode==LicenseUsageMode.Designtime){ license=CreateLicense(type,String.Empty); } else{ license=LicenseCollector.GetLicense(type); if(license==null){ stringlicenseData=GetLicenseData(type); if((licenseData!=null)&& (licenseData.Length!=0)){ if(ValidateLicenseData(type,licenseData)){ ServerLicensenewLicense= CreateLicense(type,licenseData); if(ValidateLicense(newLicense, outerrorMessage)){ license=newLicense; LicenseCollector.AddLicense(type, license); } } } } else{ if(ValidateLicense(license,outerrorMessage)== false){ license=null; } } } if(allowExceptions&&(license==null)){ if(errorMessage==null){ thrownewLicenseException(type); } else{ thrownewLicenseException(type,instance, errorMessage); } } returnlicense; } protectedvirtualstringGetLicenseData(Typetype){ stringlicenseData=null; StreamlicenseStream=null; try{ licenseStream=GetLicenseDataStream(type); if(licenseStream!=null){ StreamReadersr=newStreamReader(licenseStream); licenseData=sr.ReadLine(); } } finally{ if(licenseStream!=null){ licenseStream.Close(); licenseStream=null; } } returnlicenseData; } protectedvirtualStreamGetLicenseDataStream(Typetype){ stringassemblyPart=type.Assembly.GetName().Name; stringversionPart= type.Assembly.GetName().Version.ToString(); stringrelativePath= "~/licenses/" +assemblyPart+ "/" + versionPart+ "/" +type.FullName+ ".lic"; stringlicensesFile=null; try{ licensesFile= HttpContext.Current.Server.MapPath(relativePath); if(File.Exists(licensesFile)==false){ licensesFile=null; } } catch{ } if(licensesFile!=null){ returnnewFileStream(licensesFile,FileMode.Open, FileAccess.Read,FileShare.Read); } returnnull; } protectedvirtualboolValidateLicense(ServerLicenselicense, outstringerrorMessage){ errorMessage=null; returntrue; } protectedvirtualboolValidateLicenseData(Typetype, stringlicenseData){ stringlicenseKey=type.FullName+ " islicensed."; returnString.Compare(licenseKey,licenseData,true, CultureInfo.InvariantCulture)==0; } privatesealedclassServerLicenseCollector{ privateIDictionary_collectedLicenses; publicServerLicenseCollector(){ _collectedLicenses=newHybridDictionary(); } publicvoidAddLicense(TypeobjectType, ServerLicenselicense){ if(objectType==null){ thrownewArgumentNullException("objectType"); } if(license==null){ thrownewArgumentNullException("objectType"); } _collectedLicenses[objectType]=license; } publicServerLicenseGetLicense(TypeobjectType){ if(objectType==null){ thrownewArgumentNullException("objectType"); } if(_collectedLicenses.Count==0){ returnnull; } return(ServerLicense)_collectedLicenses[objectType]; } publicvoidRemoveLicense(TypeobjectType){ if(objectType==null){ thrownewArgumentNullException("objectType"); } _collectedLicenses.Remove(objectType); } } } } ServerLicenseProvider is associated with a derived license object. The implementation of the ServerLicense class is shown in Listing 17-10. Listing 17-10 ServerLicense implements the base class of licenses used along with ServerLicenseProvider .usingSystem; usingSystem.ComponentModel; usingSystem.Diagnostics; namespaceMSPress.ServerControls{ publicclassServerLicense:License{ privateType_type; privatestring_key; publicServerLicense(Typetype,stringkey){ _type=type; _key=key; } publicoverridestringLicenseKey{ get{ return_key; } } publicTypeLicensedType{ get{ return_type; } } publicoverridevoidDispose(){ } } } The implementation of ServerLicenseProvider demonstrates the following key points:
This simplistic model obviously isn't very useful for real licensing purposes. Therefore, ServerLicenseProvider also creates a framework for developing custom license providers with more complex validation logic. You can implement custom licensing schemes by deriving your own license provider from ServerLicenseProvider and overriding one or more of its virtual methods, which Table 17-1 describes. Table 17-1. Overridable Methods Defined in ServerLicenseProvider
ExpiringLicenseProviderThe DemoLabel control shown in Listing 17-8 uses ExpiringLicenseProvider to implement a licensing scheme with a license that expires after the control has been used a specified number of times. Listings 17-11 and 17-12 contain the implementation of the license provider and its associated license. Listing 17-11 ExpiringLicenseProvider implements a usage-based licensing scheme.usingSystem; usingSystem.Diagnostics; usingSystem.Globalization; namespaceMSPress.ServerControls{ publicclassExpiringLicenseProvider:ServerLicenseProvider{ protectedoverrideServerLicenseCreateLicense(Typetype, stringkey){ string[]parts=key.Split(';'); Debug.Assert(parts.Length==2); returnnewExpiringLicense(type,key, Int32.Parse(parts[1],CultureInfo.InvariantCulture)); } protectedoverrideboolValidateLicense(ServerLicenselicense, outstringerrorMessage){ errorMessage=null; ExpiringLicensetestLicense=(ExpiringLicense)license; testLicense.IncrementUsageCounter(); if(testLicense.IsExpired){ errorMessage= "TheLicensefor " + testLicense.LicensedType.Name+ " hasexpired."; returnfalse; } returntrue; } protectedoverrideboolValidateLicenseData(Typetype, stringlicenseData){ string[]parts=licenseData.Split(';'); if(parts.Length==2){ returnbase.ValidateLicenseData(type,parts[0]); } else{ returnfalse; } } } } Listing 17-12 ExpiringLicense is used to track usage of the license.usingSystem; namespaceMSPress.ServerControls{ publicclassExpiringLicense:ServerLicense{ privateint_usageLimit; privateint_usageCount; publicExpiringLicense(Typetype,stringkey,intusageLimit): base(type,key){ _usageLimit=usageLimit; } publicboolIsExpired{ get{ return_usageCount>_usageLimit; } } publicvoidIncrementUsageCounter(){ _usageCount++; } } } ExpiringLicenseProvider derives from ServerLicenseProvider and overrides various methods to implement a usage-based licensing scheme. This class is associated with a derived license object, ExpiringLicense , which tracks the usage limit and the usage count. The .lic file associated with DemoLabel contains the line "MSPress.ServerControls.DemoLabel is licensed.;5" as its data. The license provider implementation overrides the ValidateLicense method to extract the usage limit that is appended to the license data in the .lic file before it is checked for validity and a license object is created. The license provider overrides the ValidateLicense method to increment the usage count on the cached license object each time the object is used and to ensure that the license has not expired. The page developer can modify the textual data in the .lic file. Encrypting license data can help improve the robustness of the licensing scheme. EncryptedLicenseProviderThe next example shows the implementation of a different license provider. Unlike the samples you've seen so far, EncryptedLicenseProvider stores its license data in encrypted form by using the Data Encryption Standard (DES) cryptography algorithm. A powerful way to use this form of license data is to encrypt registration information provided by page developers so that only your license provider can decrypt the information to create a valid license. Listing 17-13 contains the implementation of the license provider. Listing 17-13 EncryptedLicenseProvider works against an encrypted license file.usingSystem; usingSystem.Diagnostics; usingSystem.IO; usingSystem.Security.Cryptography; namespaceMSPress.ServerControls{ publicclassEncryptedLicenseProvider:ServerLicenseProvider{ //Thisisa64-bitkeygeneratedfromthestring //5FB281F6. privatestaticreadonlybyte[]encryptionKeyBytes= newbyte[]{ 0x35,0x46,0x42,0x32,0x38,0x31,0x46,0x36}; protectedoverrideStreamGetLicenseDataStream(Typetype){ StreambaseStream=base.GetLicenseDataStream(type); if(baseStream==null){ returnnull; } DESCryptoServiceProviderdes= newDESCryptoServiceProvider(); des.Key=encryptionKeyBytes; des.IV=encryptionKeyBytes; ICryptoTransformdesDecryptor=des.CreateDecryptor(); returnnewCryptoStream(baseStream,desDecryptor, CryptoStreamMode.Read); } } } EncryptedLicenseProvider derives from ServerLicenseProvider and overrides the GetLicenseDataStream method to wrap the underlying Stream created by the base class's corresponding method to read in the license data, with a CryptoStream to decrypt license data as it is read in. The CryptoStream used in the sample employs the DES cryptography algorithm with a 64-bit encryption key. The .lic file associated with the EncryptedLicensedLabel control shown in Listing 17-8 is encrypted by using the same key. We have provided the code for the encryption tool, EncLicGen.exe, in EncryptedLicenseGenerator.cs in this book's sample files. The data contained within the .lic file is still "MSPress.ServerControls.EncryptedLicensedLabel is licensed.", but it appears as gibberish because of the encryption. Figure 17-4 shows how this data looks. Figure 17-4. The encrypted data within MSPress.Server Controls.EncryptedLicensedLabel.lic
Encryption adds another level of robustness to the licensing scheme. A real licensing scheme you develop for your components might encrypt a combination of your own data and the user 's registration information, instead of encrypting a fixed static string as this sample does. Note that the encryption key is embedded in the code itself in this sample. This is permissible to some degree because the licensing framework is designed to make it harder ”not impossible ”to break licenses. The Win32 security APIs provide more sophisticated mechanisms for storing encryption keys. |