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.

The 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.

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.

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.

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.

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.

The 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.

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.

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).

The 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.

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.

Share

About the Author

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.

Hi...I am trying a html editor in my web application.Beneath the editor I have a button submit.whenever I click the button submit I want to save the formatted text in to a folder as it is.But I am getting some error,when I format the text like bold it or change the color,and click submit ,it is saying 'dangerous request'.How can I do this.??your help will be highly appreciated.

When you have html code inside a control (for example, a TextBox control) and try to post the page ASP.Net validate this request and throws an exception, because of html code.
To avoid this exception you should set "ValidateRequest" property to false in <@ Page ... section. In this case you must validate if somebody tries to post something dangerous.

... and it's caught by the second catch block, not the first. The really weird thing is that when I step through and look at the exception in the second catch block, the message reads the original exception correctly, it just doesn't pass it along to the calling class.

Is there a way of passing the originating exception from the static class back to the originating class?

Thank you, I'm glad you enjoyed the article. Unfortunately, there is no way to wrap up a provider as a user control that you can drop on a page, which is kind of the point. What we are doing here is working in a disconnected mode - purposefully *not* using a user control, so that the client can change the HTML editor (or whatever) using any controls they like without having to alter our application.

Who knows, maybe in the future Microsoft will think of a way to box up providers so that we can plug them into the web config like we do controls on a page, but for now, that's just not possible.

Hi Dewey,
I'm sorry you are having problems with the project. I just tried downloading the project onto my PC here at work which has never run this code before. I unzipped it, double-clicked the solution file, and it built just fine with no errors.

Every single project in this article needs to have two references - System.Web and System.Configuration. I think I mentioned this above, but maybe I need to make it clearer. In any event, check the references in each project and make sure that those two assemblies are present, and don't have an error indicator on them. If they have an error indicator, then remove and re-add the reference in question. It's possible that perhaps your assemblies are in a different location then mine, or perhaps are stored in the GAC properly, or just corrupted for some reason.

Also, make sure you are using the latest version of VS 2005, as that's what this project was built in.

Hi Todd, I love this code, and thanks for the quick response. It does run just fine in the development environment.

However, if you take the "HTMLProviderDemo" directory and try to deploy it(standalone), it won't even bring up a page, and dies.

I never tried to build it, since the deployment should work. There is some permission or trust issue with the code, that prevents it from working outside the development environment. I'm leaning heavily in the trust issue direction.

Hmmm... I didn't try running it outside of the development environemt (at least, not from this project. I use this provider in an application at work, and it works just fine, but we use an alternate project model). I'll research it.

Strangely enough, I was reading an excellent article in Code magazine this morning which describes the shortcomings of the VS 2005 deployment model. It's a long but detailed and enlightening read. It might bring some clarity to the issue.

First there was a permission problem(I'm using Windows XP).
1. Somehow your permissions were transmitted through the zip file.
2. When looking at the security in XP, it stated that the files were from another computer, and were being blocked (I unblocked them).
3. You need read/write permission for the web.config, since you are altering it dynamically.

I re-set up my virtual directory in IIS, and everything worked. Unfortunately, I use "ASP 2.0 Web Server Here", and it still fails when running that way. I love it because I don't have to setup virtual directories all over the place.

Dewey
I think that the issue that people on XP SP2 are having is one that I recently documented and indeed it is related to a security feature of XP SP2, it catches a number of people out and then they start questioning their development environment (incorrectly), the problem normally is a result of the Atlernative Data Stream (ADS) that results from files downloaded from an un trusted location (in this case the internet) a hidden stream is attached to your download that marks it as unsafe, this is called the Zone.Identifier, in the case of a zip file, the XP uncompression function will honor this zip files un trusted mark and when you unzip the contents they will also have the Zone.Identifier attached, VS 2003 and VS 2005 will report that the projects are un trusted.

So it is unlikely the it's anything that Todd had done to exibit this behaviour.

If you are interested check out the following link to see how I have covered this issue.

My best compliments for your successfull attempt on describing the usage and advantages of a Provider approach in ASP.NET
Maybe, as also you pointed at, some code portions could be optimized and enhanced, but the whole article is clear enough to make anybody able to do this on his own.
Thanks for posting, youìve got my 5 :->

Thank you - I'm glad it was helpful. I think if more people understood the provider model, it would see a lot more use. I'm looking at using this model to provide the popup calendar, mail services, printing services - the sky is the limit really.