ASP.NET Server Control Licensing

 

Nikhil Kothari
Microsoft ASP.NET Team

Vandana Datye
Freelance Author

July 2003

Applies to:
    Microsoft® ASP.NET

Summary: Learn about the licensing requirements for ASP.NET server controls and learn about an ASP.NET control licensing implementation that can be used with versions 1.0 and 1.1 of the .NET Framework. The implementation can be extended to create custom server-side licensing schemes. (23 printed pages)

Download ASPNETControlLicensing.msi.

Background

This article assumes familiarity with Microsoft® ASP.NET programming and ASP.NET server control authoring.

This article is a more detailed exposition of licensing content in the book Developing Microsoft ASP.NET Server Controls and Components (ISBN 0-7356-1582-9) by Nikhil Kothari and Vandana Datye (© Microsoft Press, 2002. All rights reserved). The content is used with permission from Microsoft Press. For more information about the book, see https://www.microsoft.com/mspress/books/5728.asp.

Contents

Introduction
ASP.NET Server Control Licensing Requirements
Licensed Control Walkthrough
.NET Framework Licensing Architecture
ASP.NET Server Control Licensing Infrastructure
Extending the Default Licensing Scheme
Expiring License Scheme
Encrypted License Scheme
Licensing Implementation Checklist
Conclusion

Introduction

The Microsoft .NET Framework has a built-in, extensible licensing architecture that supports design-time and run-time licensing for all managed components, including business objects, Windows Forms controls and ASP.NET server controls. This article builds on that architecture to provide a licensing implementation that is optimized specifically for ASP.NET controls and that you can extend to create custom licensing schemes, such as:

  • A simple licensing scheme that only checks for the existence of valid license data to determine whether to enable a control.
  • A per-usage licensing scheme in which the license expires after a certain usage count. This scheme can be used for demonstration versions of a control. After the license expires, application developers can register (and purchase) your control and receive a non-expiring license.
  • A licensing scheme that enables an ASP.NET server control in a page only when the request comes from a specific client machine, such as the local machine. This scheme can be used in implementing trial versions of a control.
  • A licensing scheme that relies on encryption to protect against the spoofing of license data by application developers.

ASP.NET Server Control Licensing Requirements

An ASP.NET server control licensing scheme must satisfy the following requirements:

  • Support a no-compile scenario. ASP.NET Web applications often use the dynamic compilation model and therefore do not have a precompiled assembly associated with the application. The licensing mechanism should not depend on finding licenses embedded as an assembly resource in the application's assembly.
  • Support run-time licensing. Page developers use visual design-time tools as well as simple text editors to develop their pages. The licensing mechanism cannot rely on design-time checking and must provide run-time validation. Furthermore, the run-time licensing implementation should have no dependency on any (optional) design-time licensing implementation.
  • Support a license caching mechanism. License data ideally should be retrieved only once per application, rather than on every page request, because the retrieval logic could involve expensive operations such as opening files and decrypting information. Licenses should be created the first time they are needed and cached for subsequent reuse on the server. You can still validate cached licenses each time they are used to implement usage-based licensing schemes.
  • Support XCOPY deployment. ASP.NET allows page developers to deploy their Web application by simply copying files across computers on their network. The licensing scheme should not depend on the registry or on other machine-specific resources that prevent simple XCOPY deployment.

For simplicity, we have used the term server control in the preceding list. However, the licensing requirements apply to all ASP.NET server components. Similarly, the ASP.NET control licensing schemes described in this article also apply to other ASP.NET server components.

Licensed Control Walkthrough

Control licensing involves three key elements:

  • code in the control to support licensing
  • license data
  • a class that checks the license data, issues a license, and validates the license on subsequent use of the control

A Licensed Server Control

TheLicensedLabelserver control in the following listing derives from the ASP.NET System.Web.UI.WebControls.Label control and adds licensing support to it. The code shown in bold provides licensing functionality.

// LicensedLabel.cs
//
using System;
using System.ComponentModel;
using System.Web.UI.WebControls;

namespace LicensedControls {

    [
    LicenseProvider(typeof(ServerLicenseProvider))
    ]
    public class LicensedLabel : Label {

        public LicensedLabel() {
            LicenseManager.Validate(typeof(LicensedLabel));
        }
    }
}

The example illustrates the following additions that you must make to the code of any server component to support licensing:

  • In the constructor of the control, invoke the static Validate method of the System.ComponentModel.LicenseManager class and pass in the type of the component as the argument. The Validate method of LicenseManager throws a System.ComponentModel.LicenseException if the control does not have a valid license. Alternatively, in the constructor, you can invoke the static IsValid method of the LicenseManager class, which does not throw an exception. Invoke the IsValid method if you want to enable your control (say as a stripped-down version) in the absence of a valid license.
  • Apply the System.ComponentModel.LicenseProviderAttribute metadata attribute to your component and pass into it the type of the license provider (a class that derives from System.ComponentModel.LicenseProvider) that enforces licensing for your component. The implementation of the license provider for theLicensedLabelcontrol, ServerLicenseProvider, is shown in the ASP.NET Server Control Licensing Infrastructure section of this article.

As Figure 1 shows, the changes you have to make to a control to support licensing are minimal. The real licensing functionality resides in the license provider class, which is described later.

If you have implemented licensing in Windows Forms controls, you might be surprised thatLicensedLabeldoes not dispose its license. This is becauseLicensedLabeluses a license provider that caches licenses on the server.

License Data

License data provides information that is verified and incorporated into a license by the licensing architecture. You can provide license data (such as an expiration date, a usage count or a unique key) in many different ways. The type and location of the license data is dictated by the specific licensing scheme. It is customary to supply license data in a file with a .lic extension. The license data for theLicensedLabelcontrol in Figure 1 is in a file named LicensedControls.LicensedLabel.lic, which simply contains the text "LicensedControls.LicensedLabel is licensed."

Using a Licensed Control on a Page

The ReadMe document provided with the sample code for this article describes how to build the samples.

To use the LicensedLabel control in a page

  1. Copy the LicensedControls assembly (which contains theLicensedLabelcontrol) to your application's \Bin directory. This step is not needed if you are using Microsoft Visual Studio® .NET and have added a reference to the LicensedControls project in your Web application project.
  2. Copy the LicensedControls.LicensedLabel.lic file to the Licenses\LicensedControls\1.0.0.0 directory of your application.

You should now be able to use the control from any page in your application.

The following code shows a page that uses the LicensedLabel control.

<%@ Page language="c#" %>
<%@ Register TagPrefix="lc" Assembly="LicensedControls" 
  Namespace="LicensedControls" %>
<html>
  <head>
    <title>LicensedLabel Sample</title>
  </head>
  <body>
    <form method="post" runat="server" ID="Form1">
      <p>
        <lc:LicensedLabel runat="server" id="LicensedLabel1" Text="Hello 
          World!" />
      </p>
    </form>
  </body>
</html>

To see licensing in action, delete the LicensedControls.LicensedLabel.lic file or move it to a different location. Rebuild your application or make some change that will cause the application to restart. This step is needed to clear the license cache managed by ServerLicenseProvider, the license provider specified in theLicensedLabelcontrol's metadata. Request the LicensedLabelTest.aspx page in your browser. The page will generate the error shown in the following figure.

Aa479017.aspnetcontrollicensing01(en-us,MSDN.10).gif

Figure 1. The error generated by the LicensedLabelTest.aspx page when it tries to use LicensedLabel without a valid license

.NET Framework Licensing Architecture

The following figure (Figure 2) illustrates the licensing architecture of the .NET Framework. It shows the main steps that occur when a page tries to instantiate theLicensedLabelcontrol described in the previous section. Although the actual steps are in the context of a server control, the figure shows the classes that constitute the .NET Framework licensing architecture and the key steps common to any run-time licensing scheme. The exact steps performed by a license provider are specific to the particular licensing scheme the provider implements. For example, the license caching functionality shown in the figure is specific to ServerLicenseProvider, as described in the ASP.NET Server Control Licensing Infrastructure section of this article. The classes shown in bold are .NET Framework classes, and those shown in italics are derived classes that we implemented.

Aa479017.aspnetcontrollicensing02(en-us,MSDN.10).gif

Figure 2. The licensing architecture of the .NET Framework

The main steps in enforcing licensing for a control are:

  1. The licensed control invokes the static System.ComponentModel.LicenseManager.Validate method in its constructor. (Alternatively, the control could invoke the static LicenseManager.IsValid method in its constructor. In that case, the return type will differ from that shown in the figure and an exception will not be thrown).
  2. The LicenseManager.Validate method examines the component's metadata and gets the type of the license provider from the LicenseProviderAttribute attribute applied to the component. The license provider class must derive from the System.ComponentModel.LicenseProvider class.
  3. LicenseManager instantiates the license provider class whose type is specified in the System.ComponentModel.LicenseProviderAttribute metadata attribute, passes the type of the component to the license provider, and indicates whether the component is being used at design time or at run time.
  4. The license provider looks up the component's license in the license cache. If a license is found, the license provider validates the license. Note that the license cache lookup and license storage are not generic requirements but are specific to ServerLicenseProvider, the license provider that we have implemented.
    1. (First-time only) The license provider fetches license data and validates it. If the data is not valid, the license provider throws a System.ComponentModel.LicenseException exception.
    2. (First-time only) If the license data is valid, the license provider creates a license (a class derived from System.ComponentModel.License). In addition, the license provider validates the license, and, if the license is valid, stores it in the license cache.
  5. The license provider returns a valid license to the license manager, or throws a license exception.
  6. The LicenseManager.Validate method returns a valid license, or passes the license exception to the calling code.
  7. If LicenseManager returns a valid license, the constructor initializes the class and the control is instantiated. Otherwise, the constructor passes the LicenseException exception to the code that attempted to instantiate the control. The error message shown in the figure in the Licensed Control Walkthrough section of this article is produced by the ASP.NET runtime, which handles the license exception passed by the control's constructor when a page uses a licensed control without a valid license.

First-time creation refers to the first instantiation of the component in the Web application. If another instance of the component is created on the same page or on a different page in the application (in the same request or in a subsequent request), steps 4a and 4b will not occur. For performance reasons,ServerLicenseProvidercaches licenses per application (not per page or per session).

The licensing architecture in the .NET Framework is designed to make it difficult (but not impossible) to use a component illegally. If a user tries to use a licensed component without a license, licensing makes it obvious to the user that the component is being used illegally. Licensing does not make a component tamper-proof.

The licensing architecture in the .NET Framework is provided by the following four classes in the System.ComponentModel namespace:

  • LicenseManager: This class is responsible for instantiating the license provider specified in the component's metadata. The license manager also passes to the license provider the type of the component and the licensing context, which indicates whether the component is being used at design time or at run time. Beyond invoking the Validate or IsValid method of the LicenseManager class in your component's constructor, you do not need to know other details about LicenseManager.
  • LicenseProviderAttribute: This attribute specifies the type of the license provider that is responsible for creating and validating the license for a component. You must apply this attribute to a component that supports licensing.
  • LicenseProvider: This class contains the core functionality of any licensing scheme—that is, the tasks of issuing and validating licenses. To implement licensing support, you must create a custom license provider by deriving from the LicenseProvider class and implement the abstract GetLicense method of the base class to provide the licensing logic. Our implementation of a license provider, ServerLicenseProvider, is described in the next section of this article.
  • License: This class is a software abstraction for license data (such as is contained a .lic file). To implement a license class, you must derive from the License class and implement the abstract LicenseKey property of the base class. We implement a license class that works withServerLicenseProviderin the next section of this article.

The .NET Framework provides a default implementation of a license provider in the System.ComponentModel.LicFileLicenseProvider class. This license provider relies on a visual designer (such as Visual Studio .NET) to fetch licensing data at design time and, during compilation, to embed the license data as a resource into the assembly of the application that uses the licensed component. The LicFileLicenseProvider class can be used by Windows Forms controls but it does not satisfy the requirements for ASP.NET server control licensing described in the ASP.NET Server Control Licensing Requirements section of this article.

ASP.NET Server Control Licensing Infrastructure

This section describes our core licensing implementation, which provides the plumbing for an ASP.NET server control licensing scheme. The implementation is contained in two classes,ServerLicenseProviderand ServerLicense, which respectively derive from the LicenseProvider and License classes. We hope to have built-in support for licensing in a similar set of base classes in a future release of ASP.NET. You can use and extend theServerLicenseProviderandServerLicenseclasses without examining their source code, just as you would use classes in the .NET Framework. For completeness, however, this section includes the code for these classes.

The ServerLicenseProvider Class

The ServerLicenseProvider class derives from LicenseProvider and overrides the GetLicense method to implement core server control licensing requirements.ServerLicenseProvidersatisfies the server licensing requirements stated earlier in this article—a no-compile model, run-time licensing support, license caching, and XCOPY deployment.ServerLicenseProviderimplements a default licensing scheme that loads license data from .lic text files that are stored in a directory named Licenses in the root directory of the Web application. The structure under this directory is based on the name and version of the assembly as shown in the Licensed Control Walkthrough section of this article. The default scheme relies on finding the following text in the .lic file: "<Full type name of component> is licensed."

// ServerLicenseProvider.cs
//
using System;
using System.Collections;
using System.Collections.Specialized;
using System.ComponentModel;
using System.IO;
using System.Diagnostics;
using System.Globalization;
using System.Web;

namespace LicensedControls {

    public class ServerLicenseProvider : LicenseProvider {

        private static readonly ServerLicenseCollector LicenseCollector = 
          new ServerLicenseCollector();

        protected virtual ServerLicense CreateLicense(Type type, string 
          key) {
            return new ServerLicense(type, key);
        }

        protected virtual ServerLicense CreateEmptyLicense(Type type) {
            return new ServerLicense(type, String.Empty);

        }

        public override License GetLicense(LicenseContext context, Type 
          type, object instance, bool allowExceptions) {
            ServerLicense license = null;
            string errorMessage = null;

            if (context.UsageMode == LicenseUsageMode.Designtime) {
                license = CreateEmptyLicense(type);
            }
            else {
                license = LicenseCollector.GetLicense(type);

                if (license == null) {
                    string licenseData = GetLicenseData(type);
                    if ((licenseData != null) && (licenseData.Length != 
                      0)) {

                        if (ValidateLicenseData(type, licenseData)) {
                            ServerLicense newLicense = CreateLicense(type, 
                              licenseData);

                            if (ValidateLicense(newLicense, out 
                              errorMessage)) {
                                license = newLicense;
                                LicenseCollector.AddLicense(type, 
                                  license);
                            }
                        }
                    }
                }
                else {
                    if (ValidateLicense(license, out errorMessage) == 
                      false) {
                        license = null;
                    }
                }
            }

            if (allowExceptions && (license == null)) {
                if (errorMessage == null) {
                    throw new LicenseException(type);
                }
                else {
                    throw new LicenseException(type, instance, 
                      errorMessage);
                }
            }

            return license;
        }

        protected virtual string GetLicenseData(Type type) {
            string licenseData = null;
            Stream licenseStream = null;

            try {
                licenseStream = GetLicenseDataStream(type);

                if (licenseStream != null) {
                    StreamReader sr = new StreamReader(licenseStream);

                    licenseData = sr.ReadLine();
                }
            }
            finally {
                if (licenseStream != null) {
                    licenseStream.Close();
                    licenseStream = null;
                }
            }

            return licenseData;
        }

        protected virtual Stream GetLicenseDataStream(Type type) {
            string assemblyPart = type.Assembly.GetName().Name;
            string versionPart = 
              type.Assembly.GetName().Version.ToString();
            string relativePath = "~/licenses/" + assemblyPart + "/" + 
              versionPart + "/" + type.FullName + ".lic";

            string licensesFile = null;
            try {
                licensesFile = 
                  HttpContext.Current.Server.MapPath(relativePath);
                if (File.Exists(licensesFile) == false) {
                    licensesFile = null;
                }
            }
            catch {
            }

            if (licensesFile != null) {
                return new FileStream(licensesFile, FileMode.Open, 
                  FileAccess.Read, FileShare.Read);
            }
            return null;
        }

        protected virtual bool ValidateLicense(ServerLicense license, out 
          string errorMessage) {
            errorMessage = null;
            return true;
        }

        protected virtual bool ValidateLicenseData(Type type, string 
          licenseData) {
            string licenseKey = type.FullName + " is licensed.";
            return String.Compare(licenseKey, licenseData, true, 
              CultureInfo.InvariantCulture) == 0;
        }


        private sealed class ServerLicenseCollector {

            private IDictionary _collectedLicenses;

            public ServerLicenseCollector() {
                _collectedLicenses = new HybridDictionary();
            }

            public void AddLicense(Type objectType, ServerLicense license) 
              {
                if (objectType == null) {
                    throw new ArgumentNullException("objectType");
                }
                if (license == null) {
                    throw new ArgumentNullException("objectType");
                }

                _collectedLicenses[objectType] = license;
            }

            public ServerLicense GetLicense(Type objectType) {
                if (objectType == null) {
                    throw new ArgumentNullException("objectType");
                }

                if (_collectedLicenses.Count == 0) {
                    return null;
                }

                return (ServerLicense)_collectedLicenses[objectType];
            }

            public void RemoveLicense(Type objectType) {
                if (objectType == null) {
                    throw new ArgumentNullException("objectType");
                }

                _collectedLicenses.Remove(objectType);
            }
        }
    }
}

The ServerLicense Class

ServerLicense is the base license class that is compatible with ServerLicenseProvider.ServerLicensederives from the License class and implements the abstract LicenseKey property of the base class.

// ServerLicense.cs
//
using System;
using System.ComponentModel;
using System.Diagnostics;

namespace LicensedControls {

    public class ServerLicense : License {

        private Type _type;
        private string _key;

        public ServerLicense(Type type, string key) {
            _type = type;
            _key = key;
        }

        public override string LicenseKey {
            get {
                return _key;
            }
        }

        public Type LicensedType {
            get {
                return _type;
            }
        }

        public override void Dispose() {
        }
    }
}

Extending the Default Licensing Scheme

TheServerLicenseProviderandServerLicenseclasses implement a simple, default licensing scheme. You can extend these classes to implement your own custom licensing schemes with more complex validation logic. To implement a custom licensing scheme, derive your own license provider fromServerLicenseProviderand override one or more of its virtual methods, which are described in the following table. To complete the implementation of your licensing scheme, you might also have to implement a license class that derives fromServerLicenseand works with your license provider.

The following table describes the overridable methods defined in theServerLicenseProviderclass.

Overridable ServerLicenseProvider Method Description
protected virtual ServerLicense CreateLicense(Type type, string key) Creates and returns a ServerLicense instance for the specified licensed type. The string argument is the validated license data associated with the type. Derived license providers can override this method to return derived licenses. For an example, see the ExpiringLicenseProvider described in the Expiring License Scheme section of this article.
protected virtual ServerLicense CreateEmptyLicense(Type type) Creates and returns an empty ServerLicense instance that is not associated with real license data. ServerLicenseProvider uses empty licenses to enable design-time use. Derived license providers can override this method to return an empty derived license.
protected virtual string GetLicenseData(Type type)

Retrieves license data from a license stream by reading the first line of data in the stream. Derived license providers can override this method to read from other license stores that are not stream based.
protected virtual Stream GetLicenseDataStream(Type type)

Opens a stream used to read license data. This method also contains the logic to form the virtual path to the appropriate .lic file. Derived license providers can override this method to return a custom stream implementation as shown in the EncryptedLicenseProvider example in the Encrypted License Scheme section of this article.
protected virtual bool ValidateLicense(ServerLicense license, out string errorMessage)

Validates cached licenses. This validation happens each time a license is requested. Derived license providers can override this method to implement their own validation logic. The ExpiringLicenseProvider example described in the Expiring License Scheme section of this article implements a usage-based licensing scheme.
protected virtual bool ValidateLicenseData(Type type, string licenseData)

Validates license data before a license is created and returns true if the data is valid. Derived license providers can implement custom validation rules by overriding this method.

Expiring License Scheme

This section and the following one demonstrate how you can extend the default licensing implementation we provide to create custom licensing schemes. The expiring license scheme shown in this section scheme extends the default scheme by disabling the control after the control has been used a specified number of times. This scheme can be used for a demonstration version of a control.

The expiring license scheme is implemented in theExpiringLicenseProviderclass.

// ExpiringLicenseProvider.cs
//
using System;
using System.Diagnostics;
using System.Globalization;

namespace LicensedControls {

    public class ExpiringLicenseProvider : ServerLicenseProvider {

        protected override ServerLicense CreateLicense(Type type, string 
          key) {
            string[] parts = key.Split(';');
            Debug.Assert(parts.Length == 2);

            return new ExpiringLicense(type, key, Int32.Parse(parts[1], 
              CultureInfo.InvariantCulture));
        }

        protected override bool ValidateLicense(ServerLicense license, out 
          string errorMessage) {
            errorMessage = null;

            ExpiringLicense testLicense = (ExpiringLicense)license;

            testLicense.IncrementUsageCounter();
            if (testLicense.IsExpired) {
                errorMessage = "The License for " + 
                  testLicense.LicensedType.Name + " has expired.";
                return false;
            }
            return true;
        }

        protected override bool ValidateLicenseData(Type type, string 
          licenseData) {
            string[] parts = licenseData.Split(';');

            if (parts.Length == 2) {
                return base.ValidateLicenseData(type, parts[0]);
            }
            else {
                return false;
            }
        }
    }
}

The ExpiringLicenseProvider class derives fromServerLicenseProviderand overrides the following methods of the base class:

  • CreateEmptyLicense to create a license that is not associated with real license data. The empty license is suitable for use at design-time.
  • CreateLicense to create a license with the usage count specified in the .lic file.
  • ValidateLicense to check whether the license has expired.ValidateLicenseincrements the usage count before making the check.
  • ValidateLicenseData to check that the text string in the .lic file contains two parts, separated by a semicolon: "<Full type name> is licensed.;<usage count>".

The ExpiringLicenseProvider class uses theExpiringLicenseclass as its license type.

// ExpiringLicense.cs
//
using System;

namespace LicensedControls {

    public class ExpiringLicense : ServerLicense {

        private int _usageLimit;
        private int _usageCount;

        public ExpiringLicense(Type type, string key, int usageLimit) : 
          base(type, key) {
            _usageLimit = usageLimit;
        }

        public bool IsExpired {
            get {
                return _usageCount > _usageLimit;
            }
        }

        public void IncrementUsageCounter() {
            _usageCount++;
        }
    }
}

The ExpiringLicense class implements the following logic:

  • Defines two private fields to store the usage limit and the usage count.
  • Exposes theIncrementUsageCountermethod, which increments the usage count each time theExpiringLicenseobject is accessed.
  • Exposes theIsExpiredproperty, which determines whether the license has expired by comparing the usage count with the usage limit.

Note that the usage count can be saved inExpiringLicenseitself because a server license is cached.ServerLicenseProvidercreates only one instance of a license for a given component type and saves licenses in its internal license cache. However, storing data in a cached license object is not foolproof. For example, the usage count would be reset to zero if the application were restarted, thus allowing a greater total usage limit than specified in the license data. Similarly, if the application were deployed on a server farm, the total usage count on all servers could exceed the intended usage limit. However, the logic implemented inExpiringLicenseis acceptable because the goal of licensing is to protect against the unlicensed use of a component without making a significant impact on the performance of the application.

The following code shows a control that uses theExpiringLicenseProviderclass for its licensing scheme.

// ExpiringLicensedLabel.cs
//
using System;
using System.ComponentModel;
using System.Web.UI.WebControls;

namespace LicensedControls {

    [
    LicenseProvider(typeof(ExpiringLicenseProvider))
    ]
    public class ExpiringLicensedLabel : Label {

        public ExpiringLicensedLabel() {
            LicenseManager.Validate(typeof(ExpiringLicensedLabel));
        }
    }
}

The text in the .lic file forExpiringLicensedLabelis "LicensedControls.ExpiringLicensedLabel is licensed.;5".

When the license is specified in a clear text file, as in this example, the application developer can easily spoof the data in the file. You can improve the security of your licensing scheme by encrypting the license data, as shown in the next section.

Encrypted License Scheme

This section extends the default scheme to implement an encrypted license provider that decrypts encrypted license data as it is read in. The following code shows theEncryptedLicenseProviderclass.

// EncryptedLicenseProvider.cs
//
using System;
using System.Diagnostics;
using System.IO;
using System.Security.Cryptography;

namespace LicensedControls {

    public class EncryptedLicenseProvider : ServerLicenseProvider {

        // This is a 64-bit key generated from the string
        // "5FB281F6".
        //
        private static readonly byte[] encryptionKeyBytes =
            new byte[] { 0x35, 0x46, 0x42, 0x32, 0x38, 0x31, 0x46, 0x36 };

        protected override Stream GetLicenseDataStream(Type type) {
            Stream baseStream = base.GetLicenseDataStream(type);

            if (baseStream == null) {
                return null;
            }

            DESCryptoServiceProvider des = new DESCryptoServiceProvider();
            des.Key = encryptionKeyBytes;
            des.IV = encryptionKeyBytes;

            ICryptoTransform desDecryptor = des.CreateDecryptor();
            return new CryptoStream(baseStream, desDecryptor, 
              CryptoStreamMode.Read);
        }
    }
}

The EncryptedLicenseProvider class derives fromServerLicenseProviderand overrides theGetLicenseDataStreammethod, which creates a System.IO.Stream object to read the license data.EncryptedLicenseProviderwraps this stream with a System.Security.Cryptography.CryptoStream to decrypt license data as it is read in. The CryptoStream used in the sample employs the Data Encryption Standard (DES) cryptography algorithm with a 64-bit encryption key, which is embedded in theEncryptedLicenseProviderclass itself as the privateencryptionKeyBytesfield. It permissible to embed the key in this manner because the licensing architecture is designed to make it harder—not impossible—to break licenses. The Win32 security APIs provide more sophisticated mechanisms for storing encryption keys; however, those techniques are generally not amenable to XCOPY deployment.

ServerLicense is adequate as the license class for EncryptedLicenseProvider; therefore no derived license class is implemented for this license provider.

TheEncryptedLicensedLabelcontrol shown in the following code uses the licensing scheme implemented in the EncryptedLicenseProvider class.

// EncryptedLicensedLabel.cs
//

using System;
using System.ComponentModel;
using System.Web.UI.WebControls;

namespace LicensedControls {

    [
    LicenseProvider(typeof(EncryptedLicenseProvider))
    ]
    public class EncryptedLicensedLabel : Label {

        public EncryptedLicensedLabel() {
            LicenseManager.Validate(typeof(EncryptedLicensedLabel));
        }
    }
}

The .lic file associated with EncryptedLicensedLabel is shown in the following figure.

Aa479017.aspnetcontrollicensing03(en-us,MSDN.10).gif

Figure 3. The contents of the LicensedControls.EncryptedLicensedLabel file

The contents of this file were encrypted using the same key as that embedded in EncryptedLicenseProvider. The data in the file is the string "LicensedControls.EncryptedLicensedLabel is licensed." However, it is not human-readable because of encryption. The encryption tool EncLicGen.exe and its source code, EncryptedLicenseGenerator.cs, are provided in this article's sample files.

Encryption makes licensing schemes more robust. For example, you could use encryption in an expiring license scheme to improve its security. Instead of encrypting a fixed string as this sample does, you might encrypt a combination of your own data and the user's registration information.

Licensing Implementation Checklist

The following list describes the tasks you need to perform when implementing licensing for your server controls:

  • Implement a license provider that derives from theServerLicenseProviderclass and override one or more of the base class's virtual methods to provide the logic for your licensing scheme.
  • Implement a license class that derives from theServerLicenseclass and works with the license provider you implemented (see previous bullet). This step is not needed ifServerLicenseis suitable for your licensing scheme (as shown in theEncryptedLicenseProviderexample in the previous section of this article).
  • Add licensing support to your component by applying the LicenseProviderAttribute metadata attribute and passing into it the type of the license provider you implemented (see first bullet). Also invoke LicenseManager.Validate from your component's constructor.
  • Create licensing data for your component. You can save this data in a .lic file or in any other form required by your licensing scheme.
  • (optional) Create a tool to generate license data. An example is the encryption tool EncLicGen.exe, which is provided with the sample files for this article. If you are creating components for commercial distribution, you will find it useful to have a tool that automates the creation of license data.
  • If the license data for your components is contained in files (such as .lic files), provide instructions to the application developer for creating the directory structure required by your licensing scheme and copying the license files to the necessary location in the Web application.

Conclusion

This article examined the requirements for ASP.NET server control licensing and the .NET Framework licensing architecture. We showed how licensing functionality is provided by three key elements—code in the control to support licensing, license data, and a license provider class that issues and validates licenses. We provided a default ASP.NET server control licensing implementation, which creates the plumbing for server control licensing schemes, and demonstrated how to extend this default licensing implementation by creating different custom licensing schemes.

© Microsoft Corporation. All rights reserved.