|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionWhile 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 StartedDownload 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.
MyProvider.csThe first and most crucial step in creating our provider is to define our provider interface. 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 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 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.csHere is where the magic of the .NET configuration libraries really starts to shine. We inherit from 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.csThere isn't much to detail about this class. Here, we inherit from 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.csThere is a good bit of code in the 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.csThis 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 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 Once that's done, all that's left is to handle the 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 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
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.configFinally, 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 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
The final element required is the provider section itself, which must be named exactly as defined in the section name above (in this case, Each provider must contain a name parameter, which is how Wrapping it all up - Accessing the Provider from the ApplicationDefault.aspx <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 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 Taking it FurtherI 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 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 CodeWhen you run the project, it doesn't do much other than instantiate an editor. Try changing the
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||