Click here to Skip to main content
15,858,479 members
Articles / Web Development / ASP.NET
Article

HTMLEditor Provider - How to write a custom provider for ASP.NET 2.0

Rate me:
Please Sign up or sign in to vote.
4.75/5 (29 votes)
5 Sep 200614 min read 121.7K   2.4K   123   26
A tutorial on how to use the Provider Templates to create your own provider.

Sample Image - HTMLEditor.png

Introduction

While working on a recent ASP.NET project, we came across an interesting problem. In several areas of the application, we wanted to allow the end user to enter formatted HTML text. There are several excellent web based HTML Editor components available on the web, some open source, some commercial. How do we decide which one to use, and if we sell the application to clients, how do we maintain proper licensing? The answer we came up with was to supply the HTML Editor via the provider model. This would allow us to ship the application with a basic HTML Editor with a licensing scheme we are comfortable with, and still allow the client (who may have already purchased a commercial editor) to implement an editor of their choice with minimal effort and cost.

Armed with a plan, I fired up Google and started to search for info on creating an HTML editor provider. The results were disappointing. I found hundreds of articles on customizing Microsoft's built-in providers (Role, Membership, etc.) but little to nothing about creating your own, unique provider. Eventually, I came across Microsoft's Provider Toolkit which offered some provider template files along with white papers and code samples. The problem is that they don't take you all the way to the end. The instructions that come with the templates only describe how to perform the search and replace functions to customize the templates, but not how to add your own parameters, or worse - how to implement it once you've created the classes! It was all I had however, so I sat down with the code and started to plug away at it. This article is the result of those efforts. While I'm not sure that I'll be able to answer every question you may have about creating a custom provider, at least maybe you'll have a jumping off point to get you started.

What I will not do in this article is attempt to explain what the provider model is or how it might fit into your particular needs and schemes. If you are unfamiliar with providers, I strongly urge you to do some research on this topic before proceeding with this article. The articles and white papers that are on the Provider Toolkit's page are an excellent start.

Getting Started

Download and extract the Provider Toolkit, as well as the project that accompanies this article. I'm not going to cover every single step of the process herein, so having the completed project will help to fill in the gaps that aren't explicitly laid out for you. The template files will be extracted to a MSDN folder in your My Documents directory. Follow the directions in the readme.txt to create a new C# class project, alter the files, and add them to the project.

Image 2The instructions that come with the toolkit want you to rename the files to match the Product Catalog paradigm that they suggest. You'll probably want to name them differently, so I suggest skipping the renaming step and just drag them in verbatim, then perform the find/replace step. Be wary of the residual "SQL" still hanging out in line 17 of the MyProviderConfiguation.cs file.

MyProvider.cs

The first and most crucial step in creating our provider is to define our provider interface. MyProvider inherits from ProviderBase. If you look at ProviderBase, you'll see that it already defines the Name and Description properties, as well as the Initialize() function, so we'll inherit these and don't need to recreate them. So what will we need to require from every single implementation of our interface? To be honest, the only thing we absolutely require is a way to pass the HTML Editor to the requesting application, so we'll need to define a method or property that is capable of passing pretty much anything back to the host. The first thought when trying to represent anything is to use a type of Object, and that will work here, but is probably not the best choice. Ultimately, we'll be adding the HTML Editor to the Controls collection of the host, so it makes more sense to simply define this as a Control. Note that this must be a read-only control, because we will never know ahead of time what kind of control will be represented, and thus can never write to this property.

The next imperative is to provide a way for the host application to get/set the text of the editor. If you are familiar with various HTML editors, then you might be aware that this property is rarely named the same way on any two given editors. I've seen the text referred to as Text, Value, and Content off the top of my head. However, it probably makes the most sense to stick with the Microsoft implementation of the term, and stick with Text. This is almost always going to be represented as HTML or XML, so a simple String field will do.

Finally, in order to maintain a common look and feel in the host application, and especially in the toolbar layout which is (more often than not) not dynamic, we should request the Width and Height properties be set so that they remain consistent through the application and between various editors. In other words, if I switch from the FCK Editor to a Telerik control, it shouldn't throw off the spacing and layout in my host application. These are implemented as Units, and can be converted as needed within the static provider. So with all that said, here is our base provider model.

C#
public abstract class HTMLEditorProvider : ProviderBase
{
    public abstract Control HTMLEditor { get; }
    public abstract Unit Width { get; set; }
    public abstract Unit Height { get; set; }
    public abstract string Text { get; set; }
}

MyProviderConfiguration.cs

Here is where the magic of the .NET configuration libraries really starts to shine. We inherit from ConfigurationSection, which the MSDN documentation describes as "Represents a section within a configuration file." That sounds perfect for our needs. We extend the class slightly by adding properties representing the default provider, and a collection class of providers.

C#
public class HTMLEditorConfiguration : ConfigurationSection
{
    [ConfigurationProperty("providers")]
    public ProviderSettingsCollection Providers
    {
        get
        {
            return (ProviderSettingsCollection)base["providers"];
        }
    }

    [ConfigurationProperty("defaultProvider",
          DefaultValue = "TextboxHTMLProvider")]
    [StringValidator(MinLength = 1)]
    public string DefaultProvider
    {
        get
        {
            return (string)base["defaultProvider"];
        }
        set
        {
            base["defaultProvider"] = value;
        }
    }
}

MyProviderCollection.cs

There isn't much to detail about this class. Here, we inherit from ProviderCollection, which is a simple collection object like any other. This will be used to store and access our list of providers obtained from Web.config. All we really need to do is customize it to work with our provider class.

C#
public class HTMLEditorProviderCollection : ProviderCollection
{
    public override void Add(ProviderBase provider)
    {
        if (provider == null)
            throw new ArgumentNullException("The provider" +
                              " parameter cannot be null.");

        if (!(provider is HTMLEditorProvider))
            throw new ArgumentException("The provider " +
              "parameter must be of type HTMLEditorProvider.");

        base.Add(provider);
    }

    new public HTMLEditorProvider this[string name]
    {
        get { return (HTMLEditorProvider)base[name]; }
    }

    public void CopyTo(HTMLEditorProvider[] array, int index)
    {
        base.CopyTo(array, index);
    }
}

ProviderManager.cs

There is a good bit of code in the ProviderManager class. The good news is, if you performed your cut and paste properly when adding the template files to the project, then you really won't need to do much of anything to this class other than to make sure that the name of the default provider is what you want it to be. We use the ConfigurationManager class to read in the section that represents our provider. Assuming that we are able to find a valid section and default provider, then the providers that exist in our section are read into the collection, and we pull the proper provider based on the default provider specified. Note that we expose the Provider and Providers publicly - this is going to be our program's "window" to access the HTML Editor.

C#
public class HTMLEditorManager
{
    //Initialization related variables and logic
    private static bool isInitialized = false;
    private static Exception initializationException;

    private static object initializationLock = new object();

    static HTMLEditorManager()
    {
        Initialize();
    }

    private static void Initialize()
    {
        try
        {
            //Get the feature's configuration info
            HTMLEditorConfiguration qc =
                (HTMLEditorConfiguration)
                 ConfigurationManager.GetSection("HTMLEditorProvider");

            if (qc.DefaultProvider == null ||
                qc.Providers == null ||
                qc.Providers.Count < 1)
                  throw new ProviderException("You must specify" +
                                    " a valid default provider.");

            //Instantiate the providers
            providerCollection = new HTMLEditorProviderCollection();
            ProvidersHelper.InstantiateProviders(qc.Providers,
              providerCollection, typeof(HTMLEditorProvider));
            providerCollection.SetReadOnly();
            defaultProvider = providerCollection[qc.DefaultProvider];
            if (defaultProvider == null)
            {
              throw new ConfigurationErrorsException(
                "You must specify a default provider for the feature.",
                qc.ElementInformation.Properties[
                          "defaultProvider"].Source,
                qc.ElementInformation.Properties[
                          "defaultProvider"].LineNumber);
            }
        }
        catch (Exception ex)
        {
            initializationException = ex;
            isInitialized = true;
            throw ex;
        }

        isInitialized = true; //error-free initialization
    }

    //Public feature API
    private static HTMLEditorProvider defaultProvider;
    private static HTMLEditorProviderCollection providerCollection;

    public static HTMLEditorProvider Provider
    {
        get
        {
            return defaultProvider;
        }
    }

    public static HTMLEditorProviderCollection Providers
    {
        get
        {
            return providerCollection;
        }
    }
}

MyStaticProvider.cs

This file represents the actual provider. At the moment, it's in the same project as all the other files, and while that might be OK depending on your needs (for example, you might bundle your "shipping" default provider this way), most of the time this file will exist in one or more separate or external projects. All you need to do is create a new project, and add a reference to your provider assembly (as well as System.Config and System.Web), and if needed, a reference to the assembly containing your HTML editor of choice. The static provider inherits from MyProvider, so we'll need to implement the properties and methods defined in the provider model, such as (in this example) Height, Width, Text, and most importantly, HTMLEditor. Don't worry, if you are building this in Visual Studio, it won't let you build the project unless these properties are properly accounted for.

Start by adding a private object of the type of the HTML editor you wish to represent. In the example below, we are going to define a FreeTextBox provider, so add a FreeTextBoxControl. Then, go ahead and add the Height, Width, Text, and HTMLEditor properties so that they all reference the FreeTextBoxControl. If you've ever built a custom user or server control, then this should be pretty familiar to you.

Once that's done, all that's left is to handle the Initialize() function. The job of Initialize() is to read in the provider parameters set in Web.config, make sure they are valid, and then set the appropriate properties (or run the appropriate methods) that are needed by your control. The parameters are passed via a NameValueCollection called config. As an example, to get the Height parameter, you would examine config["height"]. Easy, right?

There are two approaches that can be taken to this information gathering scheme, and which path you take depends on your application requirements and personal programming style. One approach is disallow any parameters that aren't explicitly required by your provider. The way to do this is to examine config for each required parameter, and after you act on it, remove it from the collection. At the end of the Initialize() function, check to see if config has any elements left in it. If it does, then this is an indication that some erroneous parameters exist, and you can throw an appropriate error.

The second approach, and the one I took in my provider model, is to simply ignore any parameters that exist but don't belong. This approach is a little friendlier to the end user should they make an honest mistake, but at the cost of allowing errors in to the application without the end user knowing it. For example, if the Height parameter was misspelled in the Web.config, the webmaster might be scratching his head for a while figuring out why the size of the HTML editor is always wrong. You can decide which model works for you and your clients.

Image 3The templates contain a lot of code that was either removed or commented out in my code examples, such as references to the DoWork() sample function, anything relating to the connectionString or SQL routines, and some config parameter checks in the Initialize() function. Note that my code sample is admittedly poor, as I over simplified things to help make them clear. In a real world example, you would want to do a lot of error checking, such as making sure that the Height and Width parameters are numeric and within the range allowed by your application, as well as checks to make sure that the required parameters exist at all, and that disallowed parameters are reported.
C#
public class FreeTextBoxHTMLProvider : HTMLEditorProvider
{
    private FreeTextBox textbox = new FreeTextBox();


    public override System.Web.UI.Control HTMLEditor
    {
        get { return textbox; }
    }


    public override System.Web.UI.WebControls.Unit Width
    {
        get { return textbox.Width; }
        set { textbox.Width = value; }
    }


    public override System.Web.UI.WebControls.Unit Height
    {
        get { return textbox.Height; }
        set { textbox.Height = value; }
    }


    public override string Text
    {
        get { return textbox.Text; }
        set { textbox.Text = value; }
    }


    public override void Initialize(string name,
      System.Collections.Specialized.NameValueCollection config)
    {
        if ((config == null) || (config.Count == 0))
            throw new ArgumentNullException("You must " +
                  "supply a valid configuration dictionary.");

        if (string.IsNullOrEmpty(config["description"]))
        {
            config.Remove("description");
            config.Add("description", "FreeTextBoxHTMLEditorProvider");
        }

        //Let ProviderBase perform the basic initialization
        base.Initialize(name, config);

        //Perform feature-specific provider initialization here
        //A great deal more error checking and handling should exist here

        textbox.ToolbarStyleConfiguration =
            ToolbarStyleConfiguration.Office2003;

        string text = config["text"];
        if (!String.IsNullOrEmpty(text))
            Text = text;
        else
            Text = "";

        string height = config["height"];
        if (!String.IsNullOrEmpty(height))
            Height = Unit.Pixel(Convert.ToInt32(height));
        else
            Height = Unit.Pixel(600);

        string width = config["width"];
        if (!String.IsNullOrEmpty(width))
            Width = Unit.Pixel(Convert.ToInt32(width));
        else
            Width = Unit.Pixel(800);
    }
}

Web.config

Finally, we get down to the portion of code that brings everything together. Because the Web.config is a text file, and thus "disconnected" from the assemblies that we are referencing (and yet contains the information that directs everything else), it also is the file that will cause more problems and headaches than anything else. Hopefully, if you follow my model, you'll be OK. I tried to name the assemblies, namespaces, and class names in a fairly unique manner so that when referenced from Web.config, you can connect the dots and figure out if I am referring to an assembly name, a namespace, or a class name. Any mistakes made here will result in problems, and can be problematic to troubleshoot.

This time, I'll start by showing you the code, and then describing what everything refers to. I also strongly advise that you open up the accompanying project at this point as well, so that you can locate the assemblies and namespaces that I did not include in the code above for brevity's sake.

XML
<?xml version="1.0"?>
  <configuration>
    <configSections>
      <section name="HTMLEditorProvider" 
         type="SeaburyDesign.HTMLEditorConfiguration, HTMLEditor" 
         allowDefinition="MachineToApplication"/>
    </configSections>
  <appSettings />
  <connectionStrings />
  <HTMLEditorProvider defaultProvider="FCKEditorHTMLProvider">
    <providers>
      <add name="TextboxHTMLProvider" 
          type="MyCompany1.TextboxHTMLEditorProvider, 
                TextboxHTMLProvider" 
          Height="600" Width="800"/>
      <add name="FreeTextBoxHTMLProvider" 
          type="MyCompany2.FreeTextBoxHTMLProvider, 
                FreeTextBoxHTMLProvider" 
          Height="600" Width="800"/>
      <add name="FCKEditorHTMLProvider" 
          type="MyCompany3.FCKEditorHTMLProvider, 
                FCKEditorHTMLProvider" 
          Height="600" Width="800"/>
    </providers>
  </HTMLEditorProvider>
  <system.web>
    <compilation debug="true"/>
    <authentication mode="Windows"/>
  </system.web>
</configuration>

The first element that needs to exist is the <section> definition within <configSections>. The name of this section can be anything you like, but it must match the section name requested in the ConfigurationManager.GetSection() line in the ProviderManager class, and it also must match the custom section tags which we'll discuss in a second. The type is the fully qualified name of the MyProviderConfiguration class name, followed by a comma and then the name of the assembly (*.dll) that the provider exists in. This is where a lot of people get tripped up, so make sure to check this against the name of the DLL that is created by your project. In this case, the name of the assembly is HTMLEditor.dll. Finally, add the allowDefinition="MachineToApplication" property as listed above. This rather cryptic looking tag simply notes that the section can exist in the Web.config (or in the Machine.config).

Image 4The assembly that contains your provider code (in this case, HTMLEditor.dll) and any assemblies required by the HTML editor you are building a provider for (i.e., FreeTextBox.dll) need to exist in your application's /bin directory. While it should go without saying, any files or folders that your editor depends on (i.e., images, JavaScript) also need to be present in whatever location is required by the editor. Be careful to avoid hard coded paths to these resources in your release code.

The final element required is the provider section itself, which must be named exactly as defined in the section name above (in this case, HTMLEditorProvider). The <providers> element must contain a list of one or more providers, however only one of these providers can be used at any given time. Which one is used depends on which provider is specified by the defaultProvider property (in this case, FCKEditorHTMLProvider). Changing all of the providers on the web site is as simple changing the defaultProvider value.

Each provider must contain a name parameter, which is how defaultProvider will reference it. Just like the section definition above, the type parameter consists of the fully qualified class name of your static provider, followed by a comma and the name of the assembly that contains the class. Finally, we specify the properties we wish to pass to our provider class. In this example, I have set the Height and Width properties of each editor to be the same values, but I could have just as easily made them different, for example, to adjust the size to match the toolbar layout. Using my implementation, we could have left these properties out and defaulted to the sizes set by the provider itself. Your implementation may differ.

Wrapping it all up - Accessing the Provider from the Application

Default.aspx

HTML
<body>
  <form id="form1" runat="server">
    <div>
      <asp:Panel ID="Panel1" runat="server" 
                 Height="50px" Width="125px">
      </asp:Panel>
    </div>
  </form>
</body>

Default.aspx.cs

C#
using SeaburyDesign;

public partial class _Default : System.Web.UI.Page 
{
    protected void Page_Load(object sender, EventArgs e)
    {
        HTMLEditorProvider p = HTMLEditorManager.Provider;
        p.Text = "SeaburyDesign.com Rocks!";
        Panel1.Controls.Add(p.HTMLEditor);
    }
}

Look at this thing of beauty. Three lines of code (you could actually narrow it down to one depending on your needs and creativity), and you've instantiated an HTML Editor via a provider, in a decoupled environment. This is so basic that I won't even bother going into detail on it, my only caveat being to make sure that you add a reference to the assembly containing MyProvider.

Taking it Further

I want to make it clear that while fully functional, this isn't meant to be a complete solution, it requires some fleshing out. More properties and/or functions could be added to make it more useful. Events could be raised and subscribed to, such as a Text_Changed event. Certainly a good deal more error checking and handling should be implemented. But the purpose behind this article was not to supply you with an HTML Editor Provider, it was to teach you what parts are required by a custom provider and how to go about understanding and implementing them, and I hope that I have accomplished that here. If you have questions, please ask. If I can't answer them, hopefully someone smarter than me (and that's not much of a feat) will be able to supply an answer. The best way to understand this is to get your hands dirty. Dig in and try things out. See what breaks it and what doesn't.

If you find any bugs or problems in my code or simply in my logic, please let me know so that I can fix the code or the article, or at least so that the knowledge is shared. Thanks, and remember to share the knowledge.

A Quick Note on Using the Code

When you run the project, it doesn't do much other than instantiate an editor. Try changing the defaultProvider in the Web.config file to see each editor in action.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Systems Engineer Virtual RadioLogic
United States United States
Todd Davis has been working in web and application development for several years, using Silverlight, ASP.NET, VB.NET, C#, C++ and Javascript, as well as a great deal of work with SQL server and IIS.

He currently works for Virtual Radiologic in Eden Prairie, MN, however he is better known for his varied work in the open source community, especially the DotNetNuke project for which he provided several world-renowned training videos and modules. A huge advocate of open source and open knowledge sharing, everything on his website (www.SeaburyDesign.com) is always offered for free.

Whenever he is not actively coding at his laptop (a rarity to be sure), he can be found woodworking, walking with his wife and kids, or motoring along the back roads of MN on his Harley Davidson Fatboy.

Comments and Discussions

 
QuestionWell done! Pin
DaFussel23-Aug-12 3:31
DaFussel23-Aug-12 3:31 
GeneralERROR : FCK EDITOR Pin
Member 75098525-Dec-09 21:05
Member 75098525-Dec-09 21:05 
GeneralGreat Article Pin
Rui Jarimba19-Mar-09 23:06
professionalRui Jarimba19-Mar-09 23:06 
GeneralHtml Editor Pin
Member 257796818-Nov-08 18:44
Member 257796818-Nov-08 18:44 
Questionsaving problem Pin
Reneboy27-Apr-08 22:28
Reneboy27-Apr-08 22:28 
Generalvery good article Pin
sk840519-Dec-07 0:40
sk840519-Dec-07 0:40 
GeneralRe: very good article Pin
MaximilianoRios8-Apr-08 13:21
MaximilianoRios8-Apr-08 13:21 
Questionupload problem Pin
scorpion_man198213-Dec-07 10:31
scorpion_man198213-Dec-07 10:31 
Generalthank u very much Pin
hamiddd24-Sep-07 12:34
hamiddd24-Sep-07 12:34 
QuestionQuery Pin
sriramsan22-Apr-07 19:21
sriramsan22-Apr-07 19:21 
QuestionHide/show toolbar buttons is available? Pin
Young-suk18-Apr-07 14:33
Young-suk18-Apr-07 14:33 
QuestionHow to pass exception from Static Class to calling class Pin
kwilder26-Oct-06 11:58
kwilder26-Oct-06 11:58 
Questioni want to make this control as user control what i need to do Pin
hancy2926-Sep-06 0:12
hancy2926-Sep-06 0:12 
AnswerRe: i want to make this control as user control what i need to do Pin
Todd Davis26-Sep-06 2:46
Todd Davis26-Sep-06 2:46 
GeneralVery Usefull Pin
DSclub9-Sep-06 16:44
DSclub9-Sep-06 16:44 
GeneralFails to run Pin
Dewey7-Sep-06 17:05
Dewey7-Sep-06 17:05 
GeneralRe: Fails to run Pin
Todd Davis8-Sep-06 3:16
Todd Davis8-Sep-06 3:16 
GeneralRe: Fails to run Pin
Dewey8-Sep-06 14:41
Dewey8-Sep-06 14:41 
GeneralRe: Fails to run Pin
Todd Davis8-Sep-06 16:17
Todd Davis8-Sep-06 16:17 
GeneralRe: Fails to run: Partial Solution Pin
Dewey8-Sep-06 19:51
Dewey8-Sep-06 19:51 
GeneralRe: Fails to run: Partial Solution [modified] Pin
TechnicaOne2-Mar-07 19:53
TechnicaOne2-Mar-07 19:53 
GeneralGreat Pin
NinjaCross6-Sep-06 23:46
NinjaCross6-Sep-06 23:46 
GeneralRe: Great Pin
Todd Davis7-Sep-06 3:27
Todd Davis7-Sep-06 3:27 
GeneralRe: Great Pin
vik207-Sep-06 18:55
vik207-Sep-06 18:55 
GeneralRe: Great Pin
Dewey8-Sep-06 14:43
Dewey8-Sep-06 14:43 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.